mirror of
				https://github.com/davidgiven/fluxengine.git
				synced 2025-10-24 11:11:02 -07:00 
			
		
		
		
	Merge branch 'master' into micropolis-275
This commit is contained in:
		
							
								
								
									
										42
									
								
								.clang-format
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								.clang-format
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,42 @@ | ||||
| --- | ||||
| AccessModifierOffset: -4 | ||||
| AlignAfterOpenBracket: DontAlign | ||||
| AlignArrayOfStructures: Left | ||||
| AlignEscapedNewlines: Left | ||||
| AllowAllArgumentsOnNextLine: 'true' | ||||
| AllowAllConstructorInitializersOnNextLine: 'false' | ||||
| AllowAllParametersOfDeclarationOnNextLine: 'true' | ||||
| AllowShortBlocksOnASingleLine: 'true' | ||||
| AllowShortCaseLabelsOnASingleLine: 'true' | ||||
| AllowShortFunctionsOnASingleLine: Empty | ||||
| AllowShortIfStatementsOnASingleLine: Never | ||||
| AllowShortLambdasOnASingleLine: None | ||||
| AllowShortLoopsOnASingleLine: 'false' | ||||
| AlwaysBreakAfterDefinitionReturnType: None | ||||
| AlwaysBreakAfterReturnType: None | ||||
| AlwaysBreakBeforeMultilineStrings: 'true' | ||||
| AlwaysBreakTemplateDeclarations: 'Yes' | ||||
| BinPackArguments: 'false' | ||||
| BinPackParameters: 'false' | ||||
| BreakConstructorInitializers: 'AfterColon' | ||||
| BreakBeforeBraces: Allman | ||||
| BreakInheritanceList: AfterColon | ||||
| BreakStringLiterals: 'true' | ||||
| IndentCaseLabels: 'true' | ||||
| IndentWidth: '4' | ||||
| ColumnLimit: '80' | ||||
| ConstructorInitializerAllOnOneLineOrOnePerLine: 'true' | ||||
| IncludeBlocks: Preserve | ||||
| IndentWrappedFunctionNames: 'false' | ||||
| KeepEmptyLinesAtTheStartOfBlocks: 'true' | ||||
| PointerAlignment: Left | ||||
| ReflowComments: 'true' | ||||
| SortIncludes: 'false' | ||||
| SortUsingDeclarations: 'true' | ||||
| SpaceAfterTemplateKeyword: 'true' | ||||
| SpaceBeforeAssignmentOperators: 'true' | ||||
| SpaceBeforeCtorInitializerColon: 'false' | ||||
| SpaceBeforeInheritanceColon: 'true' | ||||
| SpaceBeforeParens: ControlStatements | ||||
| SpaceInEmptyParentheses: 'false' | ||||
| ... | ||||
							
								
								
									
										9
									
								
								.github/workflows/ccpp.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										9
									
								
								.github/workflows/ccpp.yml
									
									
									
									
										vendored
									
									
								
							| @@ -53,3 +53,12 @@ jobs: | ||||
|       run: | | ||||
|         make | ||||
|  | ||||
|     - name: zip | ||||
|       run: | | ||||
|         zip -9 fluxengine.zip fluxengine.exe brother120tool.exe brother240tool.exe FluxEngine.cydsn/CortexM3/ARM_GCC_541/Release/FluxEngine.hex | ||||
|  | ||||
|     - name: Upload build artifacts | ||||
|       uses: actions/upload-artifact@v2 | ||||
|       with: | ||||
|         name: ${{ github.event.repository.name }}.${{ github.sha }} | ||||
|         path: fluxengine.zip | ||||
|   | ||||
| @@ -21,7 +21,7 @@ | ||||
| :4005000014BF0A461A46002808BF19468448FFF7D8FE0321084602F031F9264602F04EF994F8643043B1EA6EEB689B1A41F28832934201D9FFF7FCFE00F07EFF18B9794820 | ||||
| :40054000FFF7BFFE04E000F07DFF0028F7D10BE000F072FF10B902F031F9F9E77248FFF7B0FE032001F098F8032000F077FF0128D4D16E48FFF7EEFE6D490320FFF734FE85 | ||||
| :4005800094F876106B48FFF79CFE94F87630023B142B00F2D683DFE813F01500D4031E00D4032400D4035000D4037600D403D900D403C101D4030803D4032C03D40333036C | ||||
| :4005C000D4034D0303238DF820308DF821300F238DF822302AE394F87800FFF703FF564B21E340F2DC57FFF7DFFE00232375E068227D02F0FF012AB9EB681B1ABB42F7DD44 | ||||
| :4005C000D4034D0303238DF820308DF8213010238DF822302AE394F87800FFF703FF564B21E340F2DC57FFF7DFFE00232375E068227D02F0FF012AB9EB681B1ABB42F7DD43 | ||||
| :400600000B4611E083B10022174696F87810F068277594F814E0BEF1000F02D1EB681B1AF7E701329142F3DA07228DF8202004228DF82120ADF82230F8E20220FFF782FD2F | ||||
| :400640004FF000080DF1200A02F0B8F84FF480790027C9EB0803DA1907F80A200137402FF9D10220FFF76EFD3A465146022000F04DFFB9F10109EBD108F10108B8F1400F58 | ||||
| :40068000E2D12E4B38E04FF0010A4FF000080DF1200B02F093F84FF0000959460120FFF7A3FD08EB090300270493049B1BF807203B44DBB29A4209D08DE80C0041463B4641 | ||||
| @@ -4615,12 +4615,12 @@ | ||||
| :0200000490105A | ||||
| :04000000BC90ACAF55 | ||||
| :0200000490303A | ||||
| :0200000090CF9F | ||||
| :0200000090D09E | ||||
| :0200000490402A | ||||
| :4000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000C0 | ||||
| :400040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080 | ||||
| :400080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040 | ||||
| :4000C0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 | ||||
| :0200000490501A | ||||
| :0C00000000012E16106900002E30A138FF | ||||
| :0C00000000012E16106900002E30A139FE | ||||
| :00000001FF | ||||
							
								
								
									
										2
									
								
								Makefile
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								Makefile
									
									
									
									
									
								
							| @@ -83,3 +83,5 @@ clean: | ||||
| 	@mkdir -p $(OBJDIR) | ||||
| 	@sh $< > $@ | ||||
|  | ||||
| compdb: | ||||
| 	@ninja -f .obj/build.ninja -t compdb > compile_commands.json | ||||
|   | ||||
| @@ -103,6 +103,8 @@ people who've had it work). | ||||
| | [Brother 240kB](doc/disk-brother.md)      |  🦄   |   🦄   |                                     | | ||||
| | [Brother FB-100](doc/disk-fb100.md)       |  🦖   |        | Tandy Model 100, Husky Hunter, knitting machines | | ||||
| | [Macintosh 800kB](doc/disk-macintosh.md)  |  🦄   |   🦄   | and probably the 400kB too          | | ||||
| | [NEC PC-98](doc/disk-ibm.md)              |  🦄   |   🦄   | trimode drive not required          | | ||||
| | [Sharp X68000](doc/disk-ibm.md)           |  🦄   |   🦄   |                                     | | ||||
| | [TRS-80](doc/disk-trs80.md)               |  🦖   |   🦖*  | a minor variation of the IBM scheme | | ||||
| {: .datatable } | ||||
|  | ||||
| @@ -125,9 +127,9 @@ at least, check the CRC so what data's there is probably good. | ||||
| | [DVK MX](doc/disk-mx.md)                 |  🦖   |        | Soviet PDP-11 clone | | ||||
| | [VDS Eco1](doc/disk-eco1.md)             |  🦖   |        | 8" mixed format | | ||||
| | [Micropolis](doc/disk-micropolis.md)     |  🦄   |        | Micropolis 100tpi drives | | ||||
| | [Northstar(doc/disk-northstar.md)        |  🦖   |   🦖   | 5.25" hard sectors | | ||||
| | [Northstar](doc/disk-northstar.md)       |  🦖   |   🦖   | 5.25" hard sectors | | ||||
| | [TI DS990 FD1000](doc/disk-tids990.md)   |  🦄   |  🦄    | 8" | | ||||
| | [Victor 9000](doc/disk-victor9k.md)      |  🦖   |        | 8" | | ||||
| | [Victor 9000](doc/disk-victor9k.md)      |  🦖   |        | 5.25" GCR encoded | | ||||
| | [Zilog MCZ](doc/disk-zilogmcz.md)        |  🦖   |        | 8" _and_ hard sectors | | ||||
| {: .datatable } | ||||
|  | ||||
|   | ||||
| @@ -14,6 +14,10 @@ metadata. Systems which use IBM scheme disks include but are not limited to: | ||||
|   - the TRS-80 | ||||
|   - late era Commodore machines (the 1571 and so on) | ||||
|   - most CP/M machines | ||||
|   - NEC PC-88 series | ||||
|   - NEC PC-98 series | ||||
|   - Sharp X68000 | ||||
|   - Fujitsu FM Towns | ||||
|   - etc | ||||
|  | ||||
| FluxEngine supports reading these. However, some variants are more peculiar | ||||
| @@ -93,11 +97,12 @@ correct format to use. | ||||
| Mixed-format disks | ||||
| ------------------ | ||||
|  | ||||
| Some disks, usually those belonging to early CP/M machines, have more than one | ||||
| format on the disk at once. Typically, the first few tracks will be low-density | ||||
| FM encoded and will be read by the machine's ROM; those tracks contain new | ||||
| floppy drive handling code capable of coping with MFM data, and so the rest of | ||||
| the disk will use that, allowing them to store more data. | ||||
| Some disks, such as those belonging to early CP/M machines, or N88-Basic disks | ||||
| (for PC-88 and PC-98), have more than one format on the disk at once. Typically, | ||||
| the first few tracks will be low-density FM encoded and will be read by the | ||||
| machine's ROM; those tracks contain new floppy drive handling code capable of | ||||
| coping with MFM data, and so the rest of the disk will use that, allowing them | ||||
| to store more data. | ||||
|  | ||||
| FluxEngine can read these fine, but it tends to get a bit confused when it sees | ||||
| tracks with differing numbers of sectors --- if track 0 has 32 sectors but | ||||
| @@ -105,6 +110,31 @@ track 1 has 16, it will assume that sectors 16..31 are missing on track 1 and | ||||
| size the image file accordingly. This can be worked around by specifying the | ||||
| size of each track; see the `eco1` read profile for an example. | ||||
|  | ||||
| Writing can be made to work too, but there is currently no example. Please [get | ||||
| in touch](https://github.com/davidgiven/fluxengine/issues/new) if you have | ||||
| specific requirements (nothing's come up yet). | ||||
| N88-Basic format floppies can be written by either specifying the `n88basic` | ||||
| format, or by using D88 or NFD format images which include explicit sector | ||||
| layout information. | ||||
|  | ||||
| Writing other formats can be made to work too, by creating a custom format | ||||
| specifier, using the `n88basic` format as an example. | ||||
| Please [get in touch](https://github.com/davidgiven/fluxengine/issues/new) if | ||||
| you have specific requirements. | ||||
|  | ||||
| 360rpm 3.5" disks | ||||
| ----------------- | ||||
|  | ||||
| Japanese PCs (NEC PC-98, Sharp X68000, Fujitsu FM Towns) spin their floppy | ||||
| drives at 360rpm rather than the more typical 300rpm. This was done in order | ||||
| to be fully backwards compatible with 5.25" disks, while using the exact | ||||
| same floppy controller. Later models of the PC-9821, as well as most USB floppy | ||||
| drives, feature "tri-mode" support which in addition to normal 300rpm modes, | ||||
| can change their speed to read and write 360rpm DD and HD disks. | ||||
|  | ||||
| Neither the FluxEngine or Greaseweazle hardware can currently command a | ||||
| tri-mode drive to spin at 360rpm, however an older 360rpm-only drive will work | ||||
| to read these formats. | ||||
|  | ||||
| Alternately, the FluxEngine software can resale the flux pulses to enable | ||||
| reading and writing these formats with a plain 300rpm drive. To do this, | ||||
| specify the following two additional options: | ||||
|  | ||||
|     --flux_source.rescale=1.2 --flux_sink.rescale=1.2 | ||||
|   | ||||
| @@ -16,8 +16,8 @@ the speed zone allocation on head 1 differ from head 0... | ||||
| | 1    | 4-15          | 0-7           | 18      | 224.5                | | ||||
| | 2    | 16-26         | 8-18          | 17      | 212.2                | | ||||
| | 3    | 27-37         | 19-29         | 16      | 199.9                | | ||||
| | 4    | 38-48         | 30-40         | 15      | 187.6                | | ||||
| | 5    | 49-59         | 41-51         | 14      | 175.3                | | ||||
| | 4    | 38-47\*       | 30-39\*       | 15      | 187.6                | | ||||
| | 5    | 48-59         | 40-51         | 14      | 175.3                | | ||||
| | 6    | 60-70         | 52-62         | 13      | 163.0                | | ||||
| | 7    | 71-79         | 63-74         | 12      | 149.6                | | ||||
| | 8    |               | 75-79         | 11      | 144.0                | | ||||
| @@ -26,8 +26,13 @@ the speed zone allocation on head 1 differ from head 0... | ||||
| FluxEngine, the disk always spins at 360 rpm, which corresponds to a rotational | ||||
| period of 166 ms.) | ||||
|  | ||||
| FluxEngine can read and write the single-sided variant of these. (Double-sided | ||||
| will be trivial to do, it's just not done yet.) | ||||
| \*The Victor 9000 Hardware Reference Manual has a bug in the documentation  | ||||
| and lists Zone 4 as ending with track 48 on head 0 and track 40 on head 1.  | ||||
| The above table matches observed data on various disks and the assembly  | ||||
| code in the boot loader, which ends Zone 4 with track 47 on head 0  | ||||
| and track 39 on Head 1. | ||||
|  | ||||
| FluxEngine can read and write both the single-sided and double-sided variants.  | ||||
|  | ||||
| Reading discs | ||||
| ------------- | ||||
| @@ -35,10 +40,14 @@ Reading discs | ||||
| Just do: | ||||
|  | ||||
| ``` | ||||
| fluxengine read victor9k-ss | ||||
| fluxengine read <format> | ||||
|  | ||||
| ``` | ||||
|  | ||||
| You should end up with an `victor9k.img` which is 627200 bytes long. | ||||
| ...where `<format>` can be `victor9k_ss` or `victor9k_ds`.  | ||||
|  | ||||
| For `victor9k_ss` you should end up with an `victor9k.img` which is 627200 bytes long.  | ||||
| For `victor9k_ds` you should end up with an `victor9k.img` which is 1224192 bytes long. | ||||
|  | ||||
| **Big warning!** The image is triangular, where each track occupies a different | ||||
| amount of space. Victor disk images are complicated due to the way the tracks | ||||
| @@ -50,7 +59,7 @@ Writing discs | ||||
| Just do: | ||||
|  | ||||
| ``` | ||||
| fluxengine read victor9k-ss -i victor9k.img | ||||
| fluxengine read victor9k_ss -i victor9k.img | ||||
| ``` | ||||
|  | ||||
| **Big warning!** This uses the same triangular disk image that reading uses. | ||||
|   | ||||
| @@ -16,6 +16,13 @@ FluxEngine makes things complicated when you're not using the FluxEngine client | ||||
| software with a FluxEngine board, but I'm afraid it's too late to change that | ||||
| now. Sorry. | ||||
|  | ||||
| **If you are using GreaseWeazle-compatible hardware** such as the | ||||
| [adafruit-floppy](https://github.com/adafruit/Adafruit_Floppy) project, then | ||||
| FluxEngine will still work; however, as the USB VID/PID won't be that of a real | ||||
| GreaseWeazle, the the FluxEngine client can't autodetect it. Instead, you'll | ||||
| need to specify the serial port manually with something like | ||||
| `--usb.greaseweazle.port=/dev/ttyACM0` or `--usb.greaseweazle.port=COM5`. | ||||
|  | ||||
| **If you were using a previous version on Windows** you might have installed | ||||
| the WinUSB driver. That's no longer needed, and will in fact not work. You'll | ||||
| need to use Zadig to restore the old driver; to do this, make sure the left | ||||
| @@ -30,6 +37,8 @@ Supported features with the GreaseWeazle include: | ||||
|   - simple reading and writing of disks, seeking etc | ||||
|   - erasing disks | ||||
|   - determining disk rotation speed | ||||
|   - both Shugart and normal IBM buses (via | ||||
| 	`--usb.greaseweazle.bus_type=SHUGART` or `IBMPC`; the default is `IBMPC`) | ||||
|  | ||||
| What doesn't work | ||||
| ----------------- | ||||
|   | ||||
							
								
								
									
										45
									
								
								doc/using.md
									
									
									
									
									
								
							
							
						
						
									
										45
									
								
								doc/using.md
									
									
									
									
									
								
							| @@ -267,7 +267,8 @@ FluxEngine also supports a number of file system image formats. When using the | ||||
|   - `<filename.fdi>` | ||||
|  | ||||
|   Read from a [FDI image file](https://www.pc98.org/project/doc/hdi.html), | ||||
|   commonly used by PC-98 emulators. **Read Only.** | ||||
|   commonly used by PC-98 emulators. Supports automatically configuring | ||||
|   the encoder. **Read Only.** | ||||
|    | ||||
|   - `<filename.d88>` | ||||
|  | ||||
| @@ -280,16 +281,34 @@ FluxEngine also supports a number of file system image formats. When using the | ||||
|   The D88 reader should be used with the `ibm` profile and will override | ||||
|   most encoding parameters on a track-by-track basis. | ||||
|    | ||||
|   - `<filename.nfd>` | ||||
|  | ||||
|   Read from a [NFD r0 image file](https://www.pc98.org/project/doc/nfdr0.html), | ||||
|   commonly used by various Japanese PC emulators, including the NEC PC-98. **Read Only.** | ||||
|  | ||||
|   Only r0 version files are currently supported. | ||||
|    | ||||
|   The NFD reader should be used with the `ibm` profile and will override | ||||
|   most encoding parameters on a track-by-track basis. | ||||
|    | ||||
|   - `<filename.ldbs>` | ||||
|  | ||||
| 	Write to a [LDBS generic image | ||||
| 	file](https://www.seasip.info/Unix/LibDsk/ldbs.html). **Write only.** | ||||
|    | ||||
|   - `<filename.d64>` | ||||
|   Write to a [LDBS generic image | ||||
|   file](https://www.seasip.info/Unix/LibDsk/ldbs.html). **Write only.** | ||||
|  | ||||
| 	Write to a [D64 image | ||||
| 	file](http://unusedino.de/ec64/technical/formats/d64.html), commonly used | ||||
| 	by Commodore 64 emulators. **Write only.** | ||||
|   - `<filename.d64>` | ||||
|    | ||||
|   Write to a [D64 image | ||||
|   file](http://unusedino.de/ec64/technical/formats/d64.html), commonly used by | ||||
|   Commodore 64 emulators. **Write only.** | ||||
|  | ||||
|   - `<filename.raw>` | ||||
|  | ||||
|   Write undecoded data to a raw binary file. **Write only.** This gives you the | ||||
|   underlying MFM, FM or GCR stream, without actually doing the decode into | ||||
|   user-visible bytes. However, the decode is still done in order to check for | ||||
|   correctness. Individual records are separated by three `\\0` bytes and tracks | ||||
|   are seperated by four `\\0` bytes; tracks are emitted in CHS order. | ||||
|  | ||||
| ### High density disks | ||||
|  | ||||
| @@ -339,6 +358,16 @@ behaviour. | ||||
| 	has no effect on the _drive_, so it doesn't help with flippy disks, but is | ||||
| 	useful for using very old drives with FluxEngine itself. If you use this | ||||
| 	option, then any index marks in the sampled flux are, of course, garbage. | ||||
|    | ||||
|   - `--flux_source.rescale=X, --flux_sink.rescale=X` | ||||
|    | ||||
|   When reading or writing a floppy on a drive that doesn't match the | ||||
|   original drive RPM, the flux periods can be scaled to compensate. | ||||
|    | ||||
|   For example, to read or write a PC-98 1.2MB (360rpm) floppy using a 300rpm | ||||
|   floppy drive: | ||||
|    | ||||
|   `--flux_source.rescale=1.2 --flux_sink.rescale=1.2` | ||||
|  | ||||
| ## Visualisation | ||||
|  | ||||
|   | ||||
| @@ -132,6 +132,7 @@ void AbstractDecoder::pushRecord(const Fluxmap::Position& start, const Fluxmap:: | ||||
|  | ||||
| 	auto record = std::make_shared<Record>(); | ||||
| 	_trackdata->records.push_back(record); | ||||
| 	_sector->records.push_back(record); | ||||
| 	 | ||||
| 	record->startTime = start.ns(); | ||||
| 	record->endTime = end.ns(); | ||||
|   | ||||
| @@ -108,3 +108,22 @@ std::vector<Fluxmap> Fluxmap::split() { | ||||
|     maps.push_back(map); | ||||
|     return maps; | ||||
| } | ||||
|  | ||||
| void Fluxmap::rescale(double scale) { | ||||
|     if (scale != 1.0) { | ||||
|         auto bytesOrig = _bytes; | ||||
|         _bytes = Bytes(); | ||||
|         _duration = 0; | ||||
|         _ticks = 0; | ||||
|         int lastEvent = 0; | ||||
|         for (unsigned i=0; i<bytesOrig.size(); i++) | ||||
|         { | ||||
|             lastEvent += bytesOrig[i] & 0x3f; | ||||
|             if (bytesOrig[i] & 0xc0) { | ||||
|                 appendInterval(lastEvent * scale + 0.5); | ||||
|                 findLastByte() |= bytesOrig[i] & 0xc0; | ||||
|                 lastEvent = 0; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -65,6 +65,7 @@ public: | ||||
|  | ||||
|     void precompensate(int threshold_ticks, int amount_ticks); | ||||
|     std::vector<Fluxmap> split(); | ||||
|     void rescale(double scale); | ||||
|  | ||||
| private: | ||||
| 	uint8_t& findLastByte(); | ||||
|   | ||||
| @@ -29,6 +29,7 @@ message Fl2FluxSinkProto { | ||||
| } | ||||
|  | ||||
| message FluxSinkProto { | ||||
|     optional double rescale = 7 [ default = 1.0, (help) = "amount to multiply pulse periods by" ]; | ||||
| 	oneof dest { | ||||
| 		string fluxfile = 1 [(help) = "name of destination flux file"]; | ||||
| 		HardwareFluxSinkProto drive = 2; | ||||
|   | ||||
| @@ -31,7 +31,7 @@ public: | ||||
| 				return std::make_unique<Fluxmap>(track.flux()); | ||||
| 		} | ||||
|  | ||||
| 		return std::unique_ptr<Fluxmap>(); | ||||
| 		return std::make_unique<Fluxmap>(); | ||||
|     } | ||||
|  | ||||
|     void recalibrate() {} | ||||
|   | ||||
| @@ -39,6 +39,7 @@ message Fl2FluxSourceProto { | ||||
| } | ||||
|  | ||||
| message FluxSourceProto { | ||||
| 	optional double rescale = 9 [ default = 1.0, (help) = "amount to divide pulse periods by" ]; | ||||
| 	oneof source { | ||||
| 		string fluxfile = 1 [default = "name of source flux file"]; | ||||
| 		HardwareFluxSourceProto drive = 2; | ||||
|   | ||||
| @@ -5,7 +5,6 @@ | ||||
| #include "image.h" | ||||
| #include "proto.h" | ||||
| #include "lib/config.pb.h" | ||||
| #include "imagereader/imagereaderimpl.h" | ||||
| #include "fmt/format.h" | ||||
| #include <algorithm> | ||||
| #include <iostream> | ||||
| @@ -166,6 +165,21 @@ public: | ||||
|         const Geometry& geometry = image->getGeometry(); | ||||
|         std::cout << fmt::format("D88: read {} tracks, {} sides\n", | ||||
|                         geometry.numTracks, geometry.numSides); | ||||
|  | ||||
| 		if (!config.has_heads()) | ||||
| 		{ | ||||
| 			auto* heads = config.mutable_heads(); | ||||
| 			heads->set_start(0); | ||||
| 			heads->set_end(geometry.numSides - 1); | ||||
| 		} | ||||
|  | ||||
| 		if (!config.has_cylinders()) | ||||
| 		{ | ||||
| 			auto* cylinders = config.mutable_cylinders(); | ||||
| 			cylinders->set_start(0); | ||||
| 			cylinders->set_end(geometry.numTracks - 1); | ||||
| 		} | ||||
|  | ||||
|         return image; | ||||
|     } | ||||
|  | ||||
|   | ||||
| @@ -5,7 +5,6 @@ | ||||
| #include "image.h" | ||||
| #include "proto.h" | ||||
| #include "lib/config.pb.h" | ||||
| #include "imagereader/imagereaderimpl.h" | ||||
| #include "fmt/format.h" | ||||
| #include <algorithm> | ||||
| #include <iostream> | ||||
| @@ -130,6 +129,8 @@ public: | ||||
|                     Error() << fmt::format("DIM: unknown media byte 0x%02x, could not determine write profile automatically", mediaByte); | ||||
|                     break; | ||||
|             } | ||||
|  | ||||
| 			config.mutable_decoder()->mutable_ibm(); | ||||
|         } | ||||
|  | ||||
| 		image->calculateSize(); | ||||
| @@ -137,6 +138,21 @@ public: | ||||
|         std::cout << fmt::format("DIM: read {} tracks, {} sides, {} kB total\n", | ||||
|                         geometry.numTracks, geometry.numSides, | ||||
| 						((int)inputFile.tellg() - 256) / 1024); | ||||
|  | ||||
| 		if (!config.has_heads()) | ||||
| 		{ | ||||
| 			auto* heads = config.mutable_heads(); | ||||
| 			heads->set_start(0); | ||||
| 			heads->set_end(geometry.numSides - 1); | ||||
| 		} | ||||
|  | ||||
| 		if (!config.has_cylinders()) | ||||
| 		{ | ||||
| 			auto* cylinders = config.mutable_cylinders(); | ||||
| 			cylinders->set_start(0); | ||||
| 			cylinders->set_end(geometry.numTracks - 1); | ||||
| 		} | ||||
|  | ||||
|         return image; | ||||
| 	} | ||||
|  | ||||
|   | ||||
| @@ -3,8 +3,8 @@ | ||||
| #include "sector.h" | ||||
| #include "imagereader/imagereader.h" | ||||
| #include "image.h" | ||||
| #include "proto.h" | ||||
| #include "lib/config.pb.h" | ||||
| #include "imagereader/imagereaderimpl.h" | ||||
| #include "fmt/format.h" | ||||
| #include <algorithm> | ||||
| #include <iostream> | ||||
| @@ -76,11 +76,54 @@ public: | ||||
| 			trackCount++; | ||||
|         } | ||||
|  | ||||
|         if (config.encoder().format_case() == EncoderProto::FormatCase::FORMAT_NOT_SET) | ||||
|         { | ||||
|             auto ibm = config.mutable_encoder()->mutable_ibm(); | ||||
|             auto trackdata = ibm->add_trackdata(); | ||||
|             trackdata->set_clock_rate_khz(500); | ||||
|             auto sectors = trackdata->mutable_sectors(); | ||||
|             switch (fddType) { | ||||
|                 case 0x90: | ||||
|                     std::cout << "FDI: automatically setting format to 1.2MB (1024 byte sectors)\n"; | ||||
|                     config.mutable_cylinders()->set_end(76); | ||||
|                     trackdata->set_track_length_ms(167); | ||||
|                     trackdata->set_sector_size(1024); | ||||
|                     for (int i = 0; i < 9; i++) | ||||
|                         sectors->add_sector(i); | ||||
|                     break; | ||||
|                 case 0x30: | ||||
|                     std::cout << "FDI: automatically setting format to 1.44MB\n"; | ||||
|                     trackdata->set_track_length_ms(200); | ||||
|                     trackdata->set_sector_size(512); | ||||
|                     for (int i = 0; i < 18; i++) | ||||
|                         sectors->add_sector(i); | ||||
|                     break; | ||||
|                 default: | ||||
|                     Error() << fmt::format("FDI: unknown fdd type 0x%02x, could not determine write profile automatically", fddType); | ||||
|                     break; | ||||
|             } | ||||
|         } | ||||
|  | ||||
| 		image->calculateSize(); | ||||
| 		const Geometry& geometry = image->getGeometry(); | ||||
|         std::cout << fmt::format("FDI: read {} tracks, {} sides, {} kB total\n", | ||||
|                         geometry.numTracks, geometry.numSides, | ||||
| 						((int)inputFile.tellg() - headerSize) / 1024); | ||||
|  | ||||
| 		if (!config.has_heads()) | ||||
| 		{ | ||||
| 			auto* heads = config.mutable_heads(); | ||||
| 			heads->set_start(0); | ||||
| 			heads->set_end(geometry.numSides - 1); | ||||
| 		} | ||||
|  | ||||
| 		if (!config.has_cylinders()) | ||||
| 		{ | ||||
| 			auto* cylinders = config.mutable_cylinders(); | ||||
| 			cylinders->set_start(0); | ||||
| 			cylinders->set_end(geometry.numTracks - 1); | ||||
| 		} | ||||
| 		 | ||||
|         return image; | ||||
| 	} | ||||
|  | ||||
|   | ||||
| @@ -38,6 +38,9 @@ std::unique_ptr<ImageReader> ImageReader::create(const ImageReaderProto& config) | ||||
| 		case ImageReaderProto::kD64: | ||||
| 			return ImageReader::createD64ImageReader(config); | ||||
|  | ||||
| 		case ImageReaderProto::kNfd: | ||||
| 			return ImageReader::createNFDImageReader(config); | ||||
|  | ||||
| 		case ImageReaderProto::kNsi: | ||||
| 			return ImageReader::createNsiImageReader(config); | ||||
|  | ||||
| @@ -65,6 +68,7 @@ void ImageReader::updateConfigForFilename(ImageReaderProto* proto, const std::st | ||||
| 		{".imd",      [&]() { proto->mutable_imd(); }}, | ||||
| 		{".img",      [&]() { proto->mutable_img(); }}, | ||||
| 		{".st",       [&]() { proto->mutable_img(); }}, | ||||
| 		{".nfd",      [&]() { proto->mutable_nfd(); }}, | ||||
| 		{".nsi",      [&]() { proto->mutable_nsi(); }}, | ||||
| 		{".td0",      [&]() { proto->mutable_td0(); }}, | ||||
| 		{".vgi",      [&]() { proto->mutable_img(); }}, | ||||
| @@ -87,20 +91,3 @@ void ImageReader::updateConfigForFilename(ImageReaderProto* proto, const std::st | ||||
| ImageReader::ImageReader(const ImageReaderProto& config): | ||||
|     _config(config) | ||||
| {} | ||||
|  | ||||
| void getTrackFormat(const ImgInputOutputProto& config, | ||||
| 		ImgInputOutputProto::TrackdataProto& trackdata, unsigned track, unsigned side) | ||||
| { | ||||
| 	trackdata.Clear(); | ||||
| 	for (const ImgInputOutputProto::TrackdataProto& f : config.trackdata()) | ||||
| 	{ | ||||
| 		if (f.has_track() && f.has_up_to_track() && ((track < f.track()) || (track > f.up_to_track()))) | ||||
| 			continue; | ||||
| 		if (f.has_track() && !f.has_up_to_track() && (track != f.track())) | ||||
| 			continue; | ||||
| 		if (f.has_side() && (f.side() != side)) | ||||
| 			continue; | ||||
|  | ||||
| 		trackdata.MergeFrom(f); | ||||
| 	} | ||||
| } | ||||
|   | ||||
| @@ -27,6 +27,7 @@ public: | ||||
|     static std::unique_ptr<ImageReader> createDimImageReader(const ImageReaderProto& config); | ||||
|     static std::unique_ptr<ImageReader> createFdiImageReader(const ImageReaderProto& config); | ||||
|     static std::unique_ptr<ImageReader> createD88ImageReader(const ImageReaderProto& config); | ||||
|     static std::unique_ptr<ImageReader> createNFDImageReader(const ImageReaderProto& config); | ||||
|  | ||||
| public: | ||||
| 	virtual std::unique_ptr<Image> readImage() = 0; | ||||
|   | ||||
| @@ -3,6 +3,12 @@ syntax = "proto2"; | ||||
| import "lib/common.proto"; | ||||
|  | ||||
| message ImgInputOutputProto { | ||||
| 	enum Order { | ||||
| 		UNDEFINED = 0; | ||||
| 		CHS = 1; | ||||
| 		HCS = 2; | ||||
| 	} | ||||
|  | ||||
| 	message SectorsProto { | ||||
| 		repeated int32 sector = 1 [(help) = "sector ID"]; | ||||
| 	} | ||||
| @@ -30,6 +36,7 @@ message ImgInputOutputProto { | ||||
| 	optional int32 sides = 6 [default=0, (help) = "number of sides in image"]; | ||||
| 	optional int32 physical_offset = 7 [default=0, (help) = "logical:physical track offset"]; | ||||
| 	optional int32 physical_step = 8 [default=1, (help) = "logical:physical track step"]; | ||||
| 	optional Order order = 9 [default=CHS, (help) = "the order in which to emit tracks in the image"]; | ||||
| } | ||||
|  | ||||
| message DiskCopyInputProto {} | ||||
| @@ -41,6 +48,7 @@ message Td0InputProto {} | ||||
| message DimInputProto {} | ||||
| message FdiInputProto {} | ||||
| message D88InputProto {} | ||||
| message NFDInputProto {} | ||||
|  | ||||
| message ImageReaderProto { | ||||
| 	optional string filename = 1 [(help) = "filename of input sector image"]; | ||||
| @@ -55,6 +63,7 @@ message ImageReaderProto { | ||||
| 		DimInputProto dim = 9; | ||||
| 		FdiInputProto fdi = 10; | ||||
| 		D88InputProto d88 = 11; | ||||
|         NFDInputProto nfd = 12; | ||||
| 	} | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -1,8 +0,0 @@ | ||||
| #ifndef IMAGEREADERIMPL_H | ||||
| #define IMAGEREADERIMPL_H | ||||
|  | ||||
| extern void getTrackFormat(const ImgInputOutputProto& config, | ||||
| 		ImgInputOutputProto::TrackdataProto& trackdata, unsigned track, unsigned side); | ||||
|  | ||||
| #endif | ||||
|  | ||||
| @@ -4,7 +4,7 @@ | ||||
| #include "imagereader/imagereader.h" | ||||
| #include "image.h" | ||||
| #include "lib/config.pb.h" | ||||
| #include "imagereader/imagereaderimpl.h" | ||||
| #include "imginputoutpututils.h" | ||||
| #include "fmt/format.h" | ||||
| #include <algorithm> | ||||
| #include <iostream> | ||||
| @@ -27,34 +27,31 @@ public: | ||||
| 			Error() << "IMG: bad configuration; did you remember to set the tracks, sides and trackdata fields?"; | ||||
|  | ||||
|         std::unique_ptr<Image> image(new Image); | ||||
| 		int trackCount = 0; | ||||
|         for (int track = 0; track < _config.img().tracks(); track++) | ||||
|         { | ||||
| 		for (const auto& p : getTrackOrdering(_config.img())) | ||||
| 		{ | ||||
| 			int track = p.first; | ||||
| 			int side = p.second; | ||||
|  | ||||
| 			if (inputFile.eof()) | ||||
| 				break; | ||||
| 			int physicalCylinder = track * _config.img().physical_step() + _config.img().physical_offset(); | ||||
|  | ||||
|             for (int side = 0; side < _config.img().sides(); side++) | ||||
|             { | ||||
| 				ImgInputOutputProto::TrackdataProto trackdata; | ||||
| 				getTrackFormat(_config.img(), trackdata, track, side); | ||||
| 			ImgInputOutputProto::TrackdataProto trackdata; | ||||
| 			getTrackFormat(_config.img(), trackdata, track, side); | ||||
|  | ||||
|                 for (int sectorId : getSectors(trackdata)) | ||||
|                 { | ||||
|                     Bytes data(trackdata.sector_size()); | ||||
|                     inputFile.read((char*) data.begin(), data.size()); | ||||
| 			for (int sectorId : getSectors(trackdata)) | ||||
| 			{ | ||||
| 				Bytes data(trackdata.sector_size()); | ||||
| 				inputFile.read((char*) data.begin(), data.size()); | ||||
|  | ||||
| 					const auto& sector = image->put(physicalCylinder, side, sectorId); | ||||
|                     sector->status = Sector::OK; | ||||
|                     sector->logicalTrack = track; | ||||
| 					sector->physicalCylinder = physicalCylinder; | ||||
|                     sector->logicalSide = sector->physicalHead = side; | ||||
|                     sector->logicalSector = sectorId; | ||||
|                     sector->data = data; | ||||
|                 } | ||||
|             } | ||||
|  | ||||
| 			trackCount++; | ||||
| 				const auto& sector = image->put(physicalCylinder, side, sectorId); | ||||
| 				sector->status = Sector::OK; | ||||
| 				sector->logicalTrack = track; | ||||
| 				sector->physicalCylinder = physicalCylinder; | ||||
| 				sector->logicalSide = sector->physicalHead = side; | ||||
| 				sector->logicalSector = sectorId; | ||||
| 				sector->data = data; | ||||
| 			} | ||||
|         } | ||||
|  | ||||
| 		image->calculateSize(); | ||||
|   | ||||
							
								
								
									
										149
									
								
								lib/imagereader/nfdimagereader.cc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										149
									
								
								lib/imagereader/nfdimagereader.cc
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,149 @@ | ||||
| #include "globals.h" | ||||
| #include "flags.h" | ||||
| #include "sector.h" | ||||
| #include "imagereader/imagereader.h" | ||||
| #include "image.h" | ||||
| #include "proto.h" | ||||
| #include "lib/config.pb.h" | ||||
| #include "fmt/format.h" | ||||
| #include <algorithm> | ||||
| #include <iostream> | ||||
| #include <fstream> | ||||
|  | ||||
| // reader based on this partial documentation of the D88 format: | ||||
| // https://www.pc98.org/project/doc/d88.html | ||||
|  | ||||
| class NFDImageReader : public ImageReader | ||||
| { | ||||
| public: | ||||
| 	NFDImageReader(const ImageReaderProto& config): | ||||
| 		ImageReader(config) | ||||
| 	{} | ||||
|  | ||||
| 	std::unique_ptr<Image> readImage() | ||||
| 	{ | ||||
|         std::ifstream inputFile(_config.filename(), std::ios::in | std::ios::binary); | ||||
|         if (!inputFile.is_open()) | ||||
|             Error() << "cannot open input file"; | ||||
|  | ||||
|         Bytes fileId(14); // read first entry of track table as well | ||||
|         inputFile.read((char*) fileId.begin(), fileId.size()); | ||||
|  | ||||
|         if (fileId == Bytes("T98FDDIMAGE.R1")) { | ||||
|             Error() << "NFD: r1 images are not currently supported"; | ||||
|         } | ||||
|         if (fileId != Bytes("T98FDDIMAGE.R0")) { | ||||
|             Error() << "NFD: could not find NFD header"; | ||||
|         } | ||||
|  | ||||
|         Bytes header(0x10a10); | ||||
|         inputFile.seekg( 0, std::ios::beg ); | ||||
|         inputFile.read((char*) header.begin(), header.size()); | ||||
|  | ||||
|         ByteReader headerReader(header); | ||||
|  | ||||
|         char heads = headerReader.seek(0x115).read_8(); | ||||
|         if (heads != 2) { | ||||
|             Error() << "NFD: unsupported number of heads"; | ||||
|         } | ||||
|  | ||||
|         if (config.encoder().format_case() != EncoderProto::FormatCase::FORMAT_NOT_SET) | ||||
|             std::cout << "NFD: overriding configured format"; | ||||
|  | ||||
|         auto ibm = config.mutable_encoder()->mutable_ibm(); | ||||
|         config.mutable_cylinders()->set_end(0); | ||||
|         std::cout << "NFD: HD 1.2MB mode\n"; | ||||
|         if (config.flux_sink().dest_case() == FluxSinkProto::DestCase::kDrive) { | ||||
|             config.mutable_flux_sink()->mutable_drive()->set_high_density(true); | ||||
|         } | ||||
|  | ||||
|         std::unique_ptr<Image> image(new Image); | ||||
|         for (int track = 0; track < 163; track++) | ||||
|         { | ||||
|             auto trackdata = ibm->add_trackdata(); | ||||
|             trackdata->set_clock_rate_khz(500); | ||||
|             trackdata->set_track_length_ms(167); | ||||
|             auto sectors = trackdata->mutable_sectors(); | ||||
|             int currentTrackCylinder = -1; | ||||
|             int currentTrackHead = -1; | ||||
|             int trackSectorSize = -1; | ||||
|  | ||||
|             for (int sectorInTrack = 0; sectorInTrack < 26; sectorInTrack++){ | ||||
|                 headerReader.seek(0x120 + track * 26 * 16 + sectorInTrack * 16); | ||||
|                 int cylinder = headerReader.read_8(); | ||||
|                 int head = headerReader.read_8(); | ||||
|                 int sectorId = headerReader.read_8(); | ||||
|                 int sectorSize = 128 << headerReader.read_8(); | ||||
|                 int mfm = headerReader.read_8(); | ||||
|                 int ddam = headerReader.read_8(); | ||||
|                 int status = headerReader.read_8(); | ||||
|                 headerReader.skip(9); // skip ST0, ST1, ST2, PDA, reserved(5) | ||||
|                 if (cylinder == 0xFF) | ||||
|                     continue; | ||||
|                 if (ddam != 0) | ||||
|                     Error() << "NFD: nonzero ddam currently unsupported"; | ||||
|                 if (status != 0) | ||||
|                     Error() << "NFD: nonzero fdd status codes are currently unsupported"; | ||||
|                 if (currentTrackCylinder < 0) { | ||||
|                     currentTrackCylinder = cylinder; | ||||
|                     currentTrackHead = head; | ||||
|                 } else if (currentTrackCylinder != cylinder) { | ||||
|                     Error() << "NFD: all sectors in a track must belong to the same cylinder"; | ||||
|                 } else if (currentTrackHead != head) { | ||||
|                     Error() << "NFD: all sectors in a track must belong to the same head"; | ||||
|                 } | ||||
|                 if (trackSectorSize < 0) { | ||||
|                     trackSectorSize = sectorSize; | ||||
|                     // this is the first sector we've read, use it settings for per-track data | ||||
|                     trackdata->set_cylinder(cylinder); | ||||
|                     trackdata->set_head(head); | ||||
|                     trackdata->set_sector_size(sectorSize); | ||||
|                     trackdata->set_use_fm(!mfm); | ||||
|                     if (!mfm) { | ||||
|                         trackdata->set_gap_fill_byte(0xffff); | ||||
|                         trackdata->set_idam_byte(0xf57e); | ||||
|                         trackdata->set_dam_byte(0xf56f); | ||||
|                     } | ||||
|                     // create timings to approximately match N88-BASIC | ||||
|                     if (sectorSize <= 128) { | ||||
|                         trackdata->set_gap0(0x1b); | ||||
|                         trackdata->set_gap2(0x09); | ||||
|                         trackdata->set_gap3(0x1b); | ||||
|                     } else if (sectorSize <= 256) { | ||||
|                         trackdata->set_gap0(0x36); | ||||
|                         trackdata->set_gap3(0x36); | ||||
|                     } | ||||
|                 } else if (trackSectorSize != sectorSize) { | ||||
|                     Error() << "NFD: multiple sector sizes per track are currently unsupported"; | ||||
|                 } | ||||
|                 Bytes data(sectorSize); | ||||
|                 inputFile.read((char*) data.begin(), data.size()); | ||||
|                 const auto& sector = image->put(cylinder, head, sectorId); | ||||
|                 sector->status = Sector::OK; | ||||
|                 sector->logicalTrack = cylinder; | ||||
|                 sector->physicalCylinder = cylinder; | ||||
|                 sector->logicalSide = sector->physicalHead = head; | ||||
|                 sector->logicalSector = sectorId; | ||||
|                 sector->data = data; | ||||
|  | ||||
|                 sectors->add_sector(sectorId); | ||||
|                 if (config.cylinders().end() < cylinder) | ||||
|                     config.mutable_cylinders()->set_end(cylinder); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         image->calculateSize(); | ||||
|         const Geometry& geometry = image->getGeometry(); | ||||
|         std::cout << fmt::format("NFD: read {} tracks, {} sides\n", | ||||
|                         geometry.numTracks, geometry.numSides); | ||||
|         return image; | ||||
|     } | ||||
|  | ||||
| }; | ||||
|  | ||||
| std::unique_ptr<ImageReader> ImageReader::createNFDImageReader( | ||||
| 	const ImageReaderProto& config) | ||||
| { | ||||
|     return std::unique_ptr<ImageReader>(new NFDImageReader(config)); | ||||
| } | ||||
|  | ||||
| @@ -29,6 +29,9 @@ std::unique_ptr<ImageWriter> ImageWriter::create(const ImageWriterProto& config) | ||||
| 		case ImageWriterProto::kNsi: | ||||
| 			return ImageWriter::createNsiImageWriter(config); | ||||
|  | ||||
| 		case ImageWriterProto::kRaw: | ||||
| 			return ImageWriter::createRawImageWriter(config); | ||||
|  | ||||
| 		default: | ||||
| 			Error() << "bad output image config"; | ||||
| 			return std::unique_ptr<ImageWriter>(); | ||||
| @@ -45,8 +48,9 @@ void ImageWriter::updateConfigForFilename(ImageWriterProto* proto, const std::st | ||||
| 		{".diskcopy", [&]() { proto->mutable_diskcopy(); }}, | ||||
| 		{".img",      [&]() { proto->mutable_img(); }}, | ||||
| 		{".ldbs",     [&]() { proto->mutable_ldbs(); }}, | ||||
| 		{".st",       [&]() { proto->mutable_img(); }}, | ||||
| 		{".nsi",      [&]() { proto->mutable_nsi(); }}, | ||||
| 		{".raw",      [&]() { proto->mutable_raw(); }}, | ||||
| 		{".st",       [&]() { proto->mutable_img(); }}, | ||||
| 		{".vgi",      [&]() { proto->mutable_img(); }}, | ||||
| 		{".xdf",      [&]() { proto->mutable_img(); }}, | ||||
| 	}; | ||||
|   | ||||
| @@ -14,16 +14,12 @@ public: | ||||
|     static std::unique_ptr<ImageWriter> create(const ImageWriterProto& config); | ||||
| 	static void updateConfigForFilename(ImageWriterProto* proto, const std::string& filename); | ||||
|  | ||||
|     static std::unique_ptr<ImageWriter> createImgImageWriter( | ||||
| 		const ImageWriterProto& config); | ||||
|     static std::unique_ptr<ImageWriter> createLDBSImageWriter( | ||||
| 		const ImageWriterProto& config); | ||||
|     static std::unique_ptr<ImageWriter> createD64ImageWriter( | ||||
| 		const ImageWriterProto& config); | ||||
|     static std::unique_ptr<ImageWriter> createDiskCopyImageWriter( | ||||
| 		const ImageWriterProto& config); | ||||
|     static std::unique_ptr<ImageWriter> createNsiImageWriter( | ||||
| 		const ImageWriterProto& config); | ||||
|     static std::unique_ptr<ImageWriter> createD64ImageWriter(const ImageWriterProto& config); | ||||
|     static std::unique_ptr<ImageWriter> createDiskCopyImageWriter(const ImageWriterProto& config); | ||||
|     static std::unique_ptr<ImageWriter> createImgImageWriter(const ImageWriterProto& config); | ||||
|     static std::unique_ptr<ImageWriter> createLDBSImageWriter(const ImageWriterProto& config); | ||||
|     static std::unique_ptr<ImageWriter> createNsiImageWriter(const ImageWriterProto& config); | ||||
|     static std::unique_ptr<ImageWriter> createRawImageWriter(const ImageWriterProto& config); | ||||
|  | ||||
| public: | ||||
| 	void printMap(const Image& sectors); | ||||
|   | ||||
| @@ -29,6 +29,7 @@ message LDBSOutputProto { | ||||
|  | ||||
| message DiskCopyOutputProto {} | ||||
| message NsiOutputProto {} | ||||
| message RawOutputProto {} | ||||
|  | ||||
| message ImageWriterProto { | ||||
| 	optional string filename = 1 [(help) = "filename of output sector image"]; | ||||
| @@ -38,6 +39,7 @@ message ImageWriterProto { | ||||
| 		LDBSOutputProto ldbs = 4; | ||||
| 		DiskCopyOutputProto diskcopy = 5; | ||||
| 		NsiOutputProto nsi = 6; | ||||
| 		RawOutputProto raw = 7; | ||||
| 	} | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -4,7 +4,7 @@ | ||||
| #include "imagewriter/imagewriter.h" | ||||
| #include "image.h" | ||||
| #include "lib/config.pb.h" | ||||
| #include "imagereader/imagereaderimpl.h" | ||||
| #include "imginputoutpututils.h" | ||||
| #include "fmt/format.h" | ||||
| #include <algorithm> | ||||
| #include <iostream> | ||||
| @@ -28,31 +28,31 @@ public: | ||||
| 		if (!outputFile.is_open()) | ||||
| 			Error() << "cannot open output file"; | ||||
|  | ||||
| 		for (int track = 0; track < tracks; track++) | ||||
| 		for (const auto& p : getTrackOrdering(_config.img(), tracks, sides)) | ||||
| 		{ | ||||
| 			for (int side = 0; side < sides; side++) | ||||
| 			int track = p.first; | ||||
| 			int side = p.second; | ||||
|  | ||||
| 			ImgInputOutputProto::TrackdataProto trackdata; | ||||
| 			getTrackFormat(_config.img(), trackdata, track, side); | ||||
|  | ||||
| 			auto sectors = getSectors(trackdata, geometry.numSectors); | ||||
| 			if (sectors.empty()) | ||||
| 			{ | ||||
| 				ImgInputOutputProto::TrackdataProto trackdata; | ||||
| 				getTrackFormat(_config.img(), trackdata, track, side); | ||||
| 				int maxSector = geometry.firstSector + geometry.numSectors - 1; | ||||
| 				for (int i=geometry.firstSector; i<=maxSector; i++) | ||||
| 					sectors.push_back(i); | ||||
| 			} | ||||
|  | ||||
| 				auto sectors = getSectors(trackdata, geometry.numSectors); | ||||
| 				if (sectors.empty()) | ||||
| 				{ | ||||
| 					int maxSector = geometry.firstSector + geometry.numSectors - 1; | ||||
| 					for (int i=geometry.firstSector; i<=maxSector; i++) | ||||
| 						sectors.push_back(i); | ||||
| 				} | ||||
| 			int sectorSize = trackdata.has_sector_size() ? trackdata.sector_size() : geometry.sectorSize; | ||||
|  | ||||
| 				int sectorSize = trackdata.has_sector_size() ? trackdata.sector_size() : geometry.sectorSize; | ||||
|  | ||||
| 				for (int sectorId : sectors) | ||||
| 				{ | ||||
| 					const auto& sector = image.get(track, side, sectorId); | ||||
| 					if (sector) | ||||
| 						sector->data.slice(0, sectorSize).writeTo(outputFile); | ||||
| 					else | ||||
| 						outputFile.seekp(sectorSize, std::ios::cur); | ||||
| 				} | ||||
| 			for (int sectorId : sectors) | ||||
| 			{ | ||||
| 				const auto& sector = image.get(track, side, sectorId); | ||||
| 				if (sector) | ||||
| 					sector->data.slice(0, sectorSize).writeTo(outputFile); | ||||
| 				else | ||||
| 					outputFile.seekp(sectorSize, std::ios::cur); | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
|   | ||||
							
								
								
									
										73
									
								
								lib/imagewriter/rawimagewriter.cc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										73
									
								
								lib/imagewriter/rawimagewriter.cc
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,73 @@ | ||||
| #include "globals.h" | ||||
| #include "flags.h" | ||||
| #include "sector.h" | ||||
| #include "imagewriter/imagewriter.h" | ||||
| #include "fmt/format.h" | ||||
| #include "decoders/decoders.h" | ||||
| #include "image.h" | ||||
| #include "arch/northstar/northstar.h" | ||||
| #include "lib/imagewriter/imagewriter.pb.h" | ||||
| #include <algorithm> | ||||
| #include <iostream> | ||||
| #include <fstream> | ||||
|  | ||||
| class RawImageWriter : public ImageWriter | ||||
| { | ||||
| public: | ||||
| 	RawImageWriter(const ImageWriterProto& config): | ||||
| 		ImageWriter(config) | ||||
| 	{} | ||||
|  | ||||
| 	void writeImage(const Image& image) | ||||
| 	{ | ||||
| 		const Geometry& geometry = image.getGeometry(); | ||||
|  | ||||
| 		size_t trackSize = geometry.numSectors * geometry.sectorSize; | ||||
|  | ||||
| 		if (geometry.numTracks * trackSize == 0) { | ||||
| 			std::cout << "RAW: no sectors in output; skipping image file generation." << std::endl; | ||||
| 			return; | ||||
| 		} | ||||
|  | ||||
| 		std::cout << fmt::format("RAW: writing {} cylinders, {} sides\n", | ||||
| 				geometry.numTracks, geometry.numSides); | ||||
|  | ||||
| 		std::ofstream outputFile(_config.filename(), std::ios::out | std::ios::binary); | ||||
| 		if (!outputFile.is_open()) | ||||
| 			Error() << "RAW: cannot open output file"; | ||||
|  | ||||
| 		unsigned sectorFileOffset; | ||||
| 		for (int track = 0; track < geometry.numTracks * geometry.numSides; track++) | ||||
| 		{ | ||||
| 			int side = (track < geometry.numTracks) ? 0 : 1; | ||||
|  | ||||
| 			std::vector<std::shared_ptr<Record>> records; | ||||
| 			for (int sectorId = 0; sectorId < geometry.numSectors; sectorId++) | ||||
| 			{ | ||||
| 				const auto& sector = image.get(track % geometry.numTracks, side, sectorId); | ||||
| 				if (sector) | ||||
| 					records.insert(records.end(), sector->records.begin(), sector->records.end()); | ||||
| 			} | ||||
| 			 | ||||
| 			std::sort(records.begin(), records.end(), | ||||
| 				[&](std::shared_ptr<Record> left, std::shared_ptr<Record> right) { | ||||
| 					return left->startTime < right->startTime; | ||||
| 				}); | ||||
|  | ||||
| 			for (const auto& record : records) | ||||
| 			{ | ||||
| 				record->rawData.writeTo(outputFile); | ||||
| 				Bytes(3).writeTo(outputFile); | ||||
| 			} | ||||
| 			Bytes(1).writeTo(outputFile); | ||||
| 		} | ||||
| 	} | ||||
| }; | ||||
|  | ||||
|  | ||||
| std::unique_ptr<ImageWriter> ImageWriter::createRawImageWriter( | ||||
| 	const ImageWriterProto& config) | ||||
| { | ||||
|     return std::unique_ptr<ImageWriter>(new RawImageWriter(config)); | ||||
| } | ||||
|  | ||||
							
								
								
									
										57
									
								
								lib/imginputoutpututils.cc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										57
									
								
								lib/imginputoutpututils.cc
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,57 @@ | ||||
| #include "globals.h" | ||||
| #include "lib/imagereader/imagereader.pb.h" | ||||
| #include "imginputoutpututils.h" | ||||
|  | ||||
| std::vector<std::pair<int, int>> getTrackOrdering(const ImgInputOutputProto& config, | ||||
| 	unsigned numTracks, unsigned numSides) | ||||
| { | ||||
| 	int tracks = config.has_tracks() ? config.tracks() : numTracks; | ||||
| 	int sides = config.has_sides() ? config.sides() : numSides; | ||||
|  | ||||
| 	std::vector<std::pair<int, int>> ordering; | ||||
| 	switch (config.order()) | ||||
| 	{ | ||||
| 		case ImgInputOutputProto::CHS: | ||||
| 		{ | ||||
| 			for (int track = 0; track < tracks; track++) | ||||
| 			{ | ||||
| 				for (int side = 0; side < sides; side++) | ||||
| 					ordering.push_back(std::make_pair(track, side)); | ||||
| 			} | ||||
| 			break; | ||||
| 		} | ||||
| 	 | ||||
| 		case ImgInputOutputProto::HCS: | ||||
| 		{ | ||||
| 			for (int side = 0; side < sides; side++) | ||||
| 			{ | ||||
| 				for (int track = 0; track < tracks; track++) | ||||
| 					ordering.push_back(std::make_pair(track, side)); | ||||
| 			} | ||||
| 			break; | ||||
| 		} | ||||
| 	 | ||||
| 		default: | ||||
| 			Error() << "IMG: invalid track ordering"; | ||||
| 	} | ||||
|  | ||||
| 	return ordering; | ||||
| } | ||||
|  | ||||
| void getTrackFormat(const ImgInputOutputProto& config, | ||||
| 		ImgInputOutputProto::TrackdataProto& trackdata, unsigned track, unsigned side) | ||||
| { | ||||
| 	trackdata.Clear(); | ||||
| 	for (const ImgInputOutputProto::TrackdataProto& f : config.trackdata()) | ||||
| 	{ | ||||
| 		if (f.has_track() && f.has_up_to_track() && ((track < f.track()) || (track > f.up_to_track()))) | ||||
| 			continue; | ||||
| 		if (f.has_track() && !f.has_up_to_track() && (track != f.track())) | ||||
| 			continue; | ||||
| 		if (f.has_side() && (f.side() != side)) | ||||
| 			continue; | ||||
|  | ||||
| 		trackdata.MergeFrom(f); | ||||
| 	} | ||||
| } | ||||
|  | ||||
							
								
								
									
										12
									
								
								lib/imginputoutpututils.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								lib/imginputoutpututils.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,12 @@ | ||||
| #ifndef IMGINPUTOUTPUTUTILS_H | ||||
| #define IMGINPUTOUTPUTUTILS_H | ||||
|  | ||||
| extern std::vector<std::pair<int, int>> getTrackOrdering(const ImgInputOutputProto& config, | ||||
| 		unsigned numTracks = 0, unsigned numSides = 0); | ||||
|  | ||||
| extern void getTrackFormat(const ImgInputOutputProto& config, | ||||
| 		ImgInputOutputProto::TrackdataProto& trackdata, unsigned track, unsigned side); | ||||
|  | ||||
| #endif | ||||
|  | ||||
|  | ||||
| @@ -25,6 +25,7 @@ static std::shared_ptr<Fluxmap> readFluxmap(FluxSource& fluxsource, unsigned cyl | ||||
| { | ||||
| 	std::cout << fmt::format("{0:>3}.{1}: ", cylinder, head) << std::flush; | ||||
| 	std::shared_ptr<Fluxmap> fluxmap = fluxsource.readFlux(cylinder, head); | ||||
| 	fluxmap->rescale(1.0/config.flux_source().rescale()); | ||||
| 	std::cout << fmt::format( | ||||
| 		"{0:.0} ms in {1} bytes\n", | ||||
|             fluxmap->duration()/1e6, | ||||
| @@ -52,7 +53,7 @@ static std::set<std::shared_ptr<Sector>> collect_sectors(std::set<std::shared_pt | ||||
| 			{ | ||||
| 				std::cout << fmt::format( | ||||
| 						"\n       multiple conflicting copies of sector {} seen; ", | ||||
| 						std::get<0>(sectorid), std::get<1>(sectorid), std::get<2>(sectorid)); | ||||
| 						std::get<2>(sectorid)); | ||||
| 				replacing->status = replacement->status = Sector::CONFLICT; | ||||
| 			} | ||||
| 		} | ||||
|   | ||||
							
								
								
									
										13
									
								
								lib/sector.h
									
									
									
									
									
								
							
							
						
						
									
										13
									
								
								lib/sector.h
									
									
									
									
									
								
							| @@ -4,6 +4,8 @@ | ||||
| #include "bytes.h" | ||||
| #include "fluxmap.h" | ||||
|  | ||||
| class Record; | ||||
|  | ||||
| /*  | ||||
|  * Note that sectors here used zero-based numbering throughout (to make the | ||||
|  * maths easier); traditionally floppy disk use 0-based track numbering and | ||||
| @@ -32,12 +34,13 @@ public: | ||||
|     nanoseconds_t headerEndTime = 0; | ||||
|     nanoseconds_t dataStartTime = 0; | ||||
|     nanoseconds_t dataEndTime = 0; | ||||
|     int physicalCylinder = 0; | ||||
|     int physicalHead = 0; | ||||
|     int logicalTrack = 0; | ||||
|     int logicalSide = 0; | ||||
|     int logicalSector = 0; | ||||
|     unsigned physicalCylinder = 0; | ||||
|     unsigned physicalHead = 0; | ||||
|     unsigned logicalTrack = 0; | ||||
|     unsigned logicalSide = 0; | ||||
|     unsigned logicalSector = 0; | ||||
|     Bytes data; | ||||
| 	std::vector<std::shared_ptr<Record>> records; | ||||
|  | ||||
| 	std::tuple<int, int, int, Status> key() const | ||||
| 	{ return std::make_tuple(logicalTrack, logicalSide, logicalSector, status); } | ||||
|   | ||||
| @@ -4,6 +4,7 @@ | ||||
| #include "fluxmap.h" | ||||
| #include "bytes.h" | ||||
| #include "fmt/format.h" | ||||
| #include "lib/usb/usb.pb.h" | ||||
| #include "greaseweazle.h" | ||||
| #include "serial.h" | ||||
|  | ||||
| @@ -61,8 +62,9 @@ private: | ||||
|     } | ||||
|  | ||||
| public: | ||||
|     GreaseWeazleUsb(const std::string& port): | ||||
|             _serial(SerialPort::openSerialPort(port)) | ||||
|     GreaseWeazleUsb(const std::string& port, const GreaseWeazleProto& config): | ||||
|             _serial(SerialPort::openSerialPort(port)), | ||||
|             _config(config) | ||||
|     { | ||||
|         int version = getVersion(); | ||||
|         if (version >= 29) | ||||
| @@ -79,7 +81,7 @@ public: | ||||
|  | ||||
|         /* Configure the hardware. */ | ||||
|  | ||||
|         do_command({ CMD_SET_BUS_TYPE, 3, BUS_IBMPC }); | ||||
|         do_command({ CMD_SET_BUS_TYPE, 3, (uint8_t)config.bus_type() }); | ||||
|     } | ||||
|  | ||||
|     int getVersion() | ||||
| @@ -129,7 +131,7 @@ public: | ||||
|                     .write_8(CMD_READ_FLUX) | ||||
|                     .write_8(cmd.size()) | ||||
|                     .write_le32(0) //ticks default value (guessed) | ||||
|                     .write_le32(2);//guessed | ||||
|                     .write_le16(2);//revolutions | ||||
|                 do_command(cmd); | ||||
|             } | ||||
|         } | ||||
| @@ -276,14 +278,13 @@ public: | ||||
|         if (hardSectorThreshold != 0) | ||||
|             Error() << "hard sectors are currently unsupported on the GreaseWeazel"; | ||||
|  | ||||
|         int revolutions = (readTime+_revolutions-1) / _revolutions; | ||||
|  | ||||
|         do_command({ CMD_HEAD, 3, (uint8_t)side }); | ||||
|  | ||||
|         switch (_version) | ||||
|         { | ||||
|             case V22: | ||||
|             { | ||||
|                 int revolutions = (readTime+_revolutions-1) / _revolutions; | ||||
|                 Bytes cmd(4); | ||||
|                 cmd.writer() | ||||
|                     .write_8(CMD_READ_FLUX) | ||||
| @@ -300,8 +301,8 @@ public: | ||||
|                 cmd.writer() | ||||
|                     .write_8(CMD_READ_FLUX) | ||||
|                     .write_8(cmd.size()) | ||||
|                     .write_le32(0) //ticks default value (guessed) | ||||
|                     .write_le32(revolutions + (synced ? 1 : 0)); | ||||
|                     .write_le32((readTime + (synced ? _revolutions : 0)) / _clock) | ||||
|                     .write_le32(0); | ||||
|                 do_command(cmd); | ||||
|             } | ||||
|         } | ||||
| @@ -384,14 +385,15 @@ private: | ||||
|     }; | ||||
|      | ||||
|     std::unique_ptr<SerialPort> _serial; | ||||
|     const GreaseWeazleProto& _config; | ||||
|     int _version; | ||||
|     nanoseconds_t _clock; | ||||
|     nanoseconds_t _revolutions; | ||||
| }; | ||||
|  | ||||
| USB* createGreaseWeazleUsb(const std::string& port) | ||||
| USB* createGreaseWeazleUsb(const std::string& port, const GreaseWeazleProto& config) | ||||
| { | ||||
|     return new GreaseWeazleUsb(port); | ||||
|     return new GreaseWeazleUsb(port, config); | ||||
| } | ||||
|  | ||||
| // vim: sw=4 ts=4 et | ||||
|   | ||||
| @@ -45,8 +45,16 @@ | ||||
| 			COMMTIMEOUTS commtimeouts = {0}; | ||||
| 			commtimeouts.ReadIntervalTimeout = 100; | ||||
| 			SetCommTimeouts(_handle, &commtimeouts); | ||||
|              | ||||
| 			if (!EscapeCommFunction(_handle, CLRDTR)) | ||||
| 			  Error() << fmt::format("Couldn't clear DTR: {}", | ||||
|                                      get_last_error_string()); | ||||
| 			Sleep(200); | ||||
| 			if (!EscapeCommFunction(_handle, SETDTR)) | ||||
| 			  Error() << fmt::format("Couldn't set DTR: {}", | ||||
|                                      get_last_error_string()); | ||||
|  | ||||
| 			PurgeComm(_handle, PURGE_RXABORT|PURGE_RXCLEAR|PURGE_TXABORT|PURGE_TXCLEAR); | ||||
|             PurgeComm(_handle, PURGE_RXABORT|PURGE_RXCLEAR|PURGE_TXABORT|PURGE_TXCLEAR); | ||||
| 		} | ||||
|  | ||||
| 		~SerialPortImpl() override | ||||
| @@ -113,6 +121,7 @@ | ||||
|  | ||||
| #else | ||||
|     #include <termios.h> | ||||
| 	#include <sys/ioctl.h> | ||||
|  | ||||
| 	class SerialPortImpl : public SerialPort | ||||
| 	{ | ||||
| @@ -138,6 +147,15 @@ | ||||
| 			t.c_cc[VMIN] = 1; | ||||
| 			cfsetspeed(&t, 9600); | ||||
| 			tcsetattr(_fd, TCSANOW, &t); | ||||
|  | ||||
| 			/* Toggle DTR to reset the device. */ | ||||
|  | ||||
| 			int flag = TIOCM_DTR; | ||||
| 			if (ioctl(_fd, TIOCMBIC, &flag) == -1) | ||||
| 				Error() << fmt::format("cannot clear DTR on serial port: {}", strerror(errno)); | ||||
| 			usleep(200000); | ||||
| 			if (ioctl(_fd, TIOCMBIS, &flag) == -1) | ||||
| 				Error() << fmt::format("cannot set DTR on serial port: {}", strerror(errno)); | ||||
| 		} | ||||
|  | ||||
| 		~SerialPortImpl() override | ||||
|   | ||||
							
								
								
									
										115
									
								
								lib/usb/usb.cc
									
									
									
									
									
								
							
							
						
						
									
										115
									
								
								lib/usb/usb.cc
									
									
									
									
									
								
							| @@ -13,70 +13,87 @@ | ||||
|  | ||||
| static USB* usb = NULL; | ||||
|  | ||||
| USB::~USB() | ||||
| {} | ||||
| USB::~USB() {} | ||||
|  | ||||
| static std::unique_ptr<CandidateDevice> selectDevice() | ||||
| { | ||||
| 	auto candidates = findUsbDevices({ FLUXENGINE_ID, GREASEWEAZLE_ID }); | ||||
| 	if (candidates.size() == 0) | ||||
| 		Error() << "no devices found (is one plugged in? Do you have the appropriate " | ||||
| 					"permissions?"; | ||||
|     auto candidates = findUsbDevices({FLUXENGINE_ID, GREASEWEAZLE_ID}); | ||||
|     if (candidates.size() == 0) | ||||
|         Error() << "no devices found (is one plugged in? Do you have the " | ||||
|                    "appropriate permissions?"; | ||||
|  | ||||
| 	if (config.usb().has_serial()) | ||||
| 	{ | ||||
| 		auto wantedSerial = config.usb().serial(); | ||||
| 		for (auto& c : candidates) | ||||
| 		{ | ||||
| 			if (c->serial == wantedSerial) | ||||
| 				return std::move(c); | ||||
| 		} | ||||
| 		Error() << "serial number not found (try without one to list or autodetect devices)"; | ||||
| 	} | ||||
|     if (config.usb().has_serial()) | ||||
|     { | ||||
|         auto wantedSerial = config.usb().serial(); | ||||
|         for (auto& c : candidates) | ||||
|         { | ||||
|             if (c->serial == wantedSerial) | ||||
|                 return std::move(c); | ||||
|         } | ||||
|         Error() << "serial number not found (try without one to list or " | ||||
|                    "autodetect devices)"; | ||||
|     } | ||||
|  | ||||
| 	if (candidates.size() == 1) | ||||
| 		return std::move(candidates[0]); | ||||
|     if (candidates.size() == 1) | ||||
|         return std::move(candidates[0]); | ||||
|  | ||||
| 	std::cerr << "More than one device detected; use --usb.serial=<serial> to select one:\n"; | ||||
| 	for (const auto& c : candidates) | ||||
| 	{ | ||||
| 		std::cerr << "    "; | ||||
| 		switch (c->id) | ||||
| 		{ | ||||
| 			case FLUXENGINE_ID: | ||||
| 				std::cerr << fmt::format("FluxEngine: {}\n", c->serial); | ||||
| 				break; | ||||
|     std::cerr << "More than one device detected; use --usb.serial=<serial> to " | ||||
|                  "select one:\n"; | ||||
|     for (const auto& c : candidates) | ||||
|     { | ||||
|         std::cerr << "    "; | ||||
|         switch (c->id) | ||||
|         { | ||||
|             case FLUXENGINE_ID: | ||||
|                 std::cerr << fmt::format("FluxEngine: {}\n", c->serial); | ||||
|                 break; | ||||
|  | ||||
| 			case GREASEWEAZLE_ID: | ||||
| 				std::cerr << fmt::format("GreaseWeazle: {} on {}\n", c->serial, c->serialPort); | ||||
| 				break; | ||||
| 		} | ||||
| 	} | ||||
| 	exit(1); | ||||
|             case GREASEWEAZLE_ID: | ||||
|                 std::cerr << fmt::format( | ||||
|                     "GreaseWeazle: {} on {}\n", c->serial, c->serialPort); | ||||
|                 break; | ||||
|         } | ||||
|     } | ||||
|     exit(1); | ||||
| } | ||||
|  | ||||
| USB* get_usb_impl() | ||||
| { | ||||
| 	auto candidate = selectDevice(); | ||||
| 	switch (candidate->id) | ||||
| 	{ | ||||
| 		case FLUXENGINE_ID: | ||||
| 			std::cerr << fmt::format("Using FluxEngine {}\n", candidate->serial); | ||||
| 			return createFluxengineUsb(candidate->device); | ||||
|     /* Special case for certain configurations. */ | ||||
|  | ||||
| 		case GREASEWEAZLE_ID: | ||||
| 			std::cerr << fmt::format("Using GreaseWeazle {} on {}\n", candidate->serial, candidate->serialPort); | ||||
| 			return createGreaseWeazleUsb(candidate->serialPort); | ||||
|     if (config.usb().has_greaseweazle() && | ||||
|         config.usb().greaseweazle().has_port()) | ||||
|     { | ||||
|         const auto& conf = config.usb().greaseweazle(); | ||||
|         std::cerr << fmt::format( | ||||
|             "Using GreaseWeazle on serial port {}\n", conf.port()); | ||||
|         return createGreaseWeazleUsb(conf.port(), conf); | ||||
|     } | ||||
|  | ||||
| 		default: | ||||
| 			Error() << "internal"; | ||||
| 	} | ||||
|     /* Otherwise, select a device by USB ID. */ | ||||
|  | ||||
|     auto candidate = selectDevice(); | ||||
|     switch (candidate->id) | ||||
|     { | ||||
|         case FLUXENGINE_ID: | ||||
|             std::cerr << fmt::format( | ||||
|                 "Using FluxEngine {}\n", candidate->serial); | ||||
|             return createFluxengineUsb(candidate->device); | ||||
|  | ||||
|         case GREASEWEAZLE_ID: | ||||
|             std::cerr << fmt::format("Using GreaseWeazle {} on {}\n", | ||||
|                 candidate->serial, | ||||
|                 candidate->serialPort); | ||||
|             return createGreaseWeazleUsb( | ||||
|                 candidate->serialPort, config.usb().greaseweazle()); | ||||
|  | ||||
|         default: Error() << "internal"; | ||||
|     } | ||||
| } | ||||
|  | ||||
| USB& getUsb() | ||||
| { | ||||
| 	if (!usb) | ||||
| 		usb = get_usb_impl(); | ||||
| 	return *usb; | ||||
|     if (!usb) | ||||
|         usb = get_usb_impl(); | ||||
|     return *usb; | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -5,6 +5,7 @@ | ||||
| #include "flags.h" | ||||
|  | ||||
| class Fluxmap; | ||||
| class GreaseWeazleProto; | ||||
| namespace libusbp { class device; } | ||||
|  | ||||
| class USB | ||||
| @@ -33,7 +34,7 @@ protected: | ||||
| extern USB& getUsb(); | ||||
|  | ||||
| extern USB* createFluxengineUsb(libusbp::device& device); | ||||
| extern USB* createGreaseWeazleUsb(const std::string& serialPort); | ||||
| extern USB* createGreaseWeazleUsb(const std::string& serialPort, const GreaseWeazleProto& config); | ||||
|  | ||||
| static inline int usbGetVersion()     { return getUsb().getVersion(); } | ||||
| static inline void usbRecalibrate()   { getUsb().recalibrate(); } | ||||
|   | ||||
| @@ -2,9 +2,26 @@ syntax = "proto2"; | ||||
|  | ||||
| import "lib/common.proto"; | ||||
|  | ||||
| message GreaseWeazleProto { | ||||
| 	enum BusType { | ||||
| 		BUSTYPE_INVALID = 0; | ||||
| 		IBMPC = 1; | ||||
| 		SHUGART = 2; | ||||
| 	}; | ||||
|  | ||||
| 	optional string port = 1 | ||||
| 		[(help) = "GreaseWeazle serial port to use"]; | ||||
| 	optional BusType bus_type = 2 | ||||
| 		[(help) = "which FDD bus type is in use", default = IBMPC]; | ||||
| } | ||||
|  | ||||
| message UsbProto { | ||||
| 	oneof device { | ||||
| 		string serial = 1 | ||||
| 			[(help) = "serial number of FluxEngine or GreaseWeazle device to use"]; | ||||
| 	} | ||||
|  | ||||
| 	oneof config { | ||||
| 		GreaseWeazleProto greaseweazle = 2 [(help) = "GreaseWeazle-specific options"]; | ||||
| 	} | ||||
| } | ||||
|   | ||||
| @@ -9,41 +9,42 @@ | ||||
|  | ||||
| static const std::string get_serial_number(const libusbp::device& device) | ||||
| { | ||||
| 	try | ||||
| 	{ | ||||
| 		return device.get_serial_number(); | ||||
| 	} | ||||
| 	catch (const libusbp::error& e) | ||||
| 	{ | ||||
| 		if (e.has_code(LIBUSBP_ERROR_NO_SERIAL_NUMBER)) | ||||
| 			return "n/a"; | ||||
| 		throw; | ||||
| 	} | ||||
|     try | ||||
|     { | ||||
|         return device.get_serial_number(); | ||||
|     } | ||||
|     catch (const libusbp::error& e) | ||||
|     { | ||||
|         if (e.has_code(LIBUSBP_ERROR_NO_SERIAL_NUMBER)) | ||||
|             return "n/a"; | ||||
|         throw; | ||||
|     } | ||||
| } | ||||
|  | ||||
| std::vector<std::unique_ptr<CandidateDevice>> findUsbDevices(const std::set<uint32_t>& ids) | ||||
| std::vector<std::unique_ptr<CandidateDevice>> findUsbDevices( | ||||
|     const std::set<uint32_t>& ids) | ||||
| { | ||||
| 	std::vector<std::unique_ptr<CandidateDevice>> candidates; | ||||
| 	for (const auto& it : libusbp::list_connected_devices()) | ||||
| 	{ | ||||
| 		auto candidate = std::make_unique<CandidateDevice>(); | ||||
| 		candidate->device = it; | ||||
|     std::vector<std::unique_ptr<CandidateDevice>> candidates; | ||||
|     for (const auto& it : libusbp::list_connected_devices()) | ||||
|     { | ||||
|         auto candidate = std::make_unique<CandidateDevice>(); | ||||
|         candidate->device = it; | ||||
|  | ||||
| 		uint32_t id = (it.get_vendor_id() << 16) | it.get_product_id(); | ||||
| 		if (ids.contains(id)) | ||||
| 		{ | ||||
| 			candidate->id = id; | ||||
| 			candidate->serial = get_serial_number(it); | ||||
|         uint32_t id = (it.get_vendor_id() << 16) | it.get_product_id(); | ||||
|         if (ids.find(id) != ids.end()) | ||||
|         { | ||||
|             candidate->id = id; | ||||
|             candidate->serial = get_serial_number(it); | ||||
|  | ||||
| 			if (id == GREASEWEAZLE_ID) | ||||
| 			{ | ||||
| 				libusbp::serial_port port(candidate->device); | ||||
| 				candidate->serialPort = port.get_name(); | ||||
| 			} | ||||
|             if (id == GREASEWEAZLE_ID) | ||||
|             { | ||||
|                 libusbp::serial_port port(candidate->device); | ||||
|                 candidate->serialPort = port.get_name(); | ||||
|             } | ||||
|  | ||||
| 			candidates.push_back(std::move(candidate)); | ||||
| 		} | ||||
| 	} | ||||
|             candidates.push_back(std::move(candidate)); | ||||
|         } | ||||
|     } | ||||
|  | ||||
| 	return candidates; | ||||
|     return candidates; | ||||
| } | ||||
|   | ||||
| @@ -40,6 +40,7 @@ void writeTracks( | ||||
| 			} | ||||
| 			else | ||||
| 			{ | ||||
| 				fluxmap->rescale(config.flux_sink().rescale()); | ||||
| 				/* Precompensation actually seems to make things worse, so let's leave | ||||
| 					* it disabled for now. */ | ||||
| 				//fluxmap->precompensate(PRECOMPENSATION_THRESHOLD_TICKS, 2); | ||||
| @@ -77,6 +78,7 @@ void writeTracksAndVerify( | ||||
| 			} | ||||
| 			else | ||||
| 			{ | ||||
| 				fluxmap->rescale(config.flux_sink().rescale()); | ||||
| 				std::sort(sectors.begin(), sectors.end(), sectorPointerSortPredicate); | ||||
|  | ||||
| 				for (int retry = 0;; retry++) | ||||
|   | ||||
| @@ -469,7 +469,10 @@ buildlibrary libbackend.a \ | ||||
|     lib/imagewriter/imagewriter.cc \ | ||||
|     lib/imagewriter/imgimagewriter.cc \ | ||||
|     lib/imagewriter/ldbsimagewriter.cc \ | ||||
|     lib/imagereader/nfdimagereader.cc \ | ||||
|     lib/imagewriter/nsiimagewriter.cc \ | ||||
|     lib/imagewriter/rawimagewriter.cc \ | ||||
|     lib/imginputoutpututils.cc \ | ||||
|     lib/ldbs.cc \ | ||||
|     lib/proto.cc \ | ||||
|     lib/reader.cc \ | ||||
| @@ -528,8 +531,9 @@ FORMATS="\ | ||||
|     northstar350 \ | ||||
|     northstar87 \ | ||||
|     tids990 \ | ||||
|     victor9k_ss \ | ||||
|     vgi \ | ||||
|     victor9k_ss \ | ||||
|     victor9k_ds \ | ||||
|     zilogmcz \ | ||||
|     " | ||||
|  | ||||
| @@ -641,6 +645,7 @@ encodedecodetest mac800 scripts/mac800_test.textpb | ||||
| encodedecodetest n88basic | ||||
| encodedecodetest tids990 | ||||
| encodedecodetest victor9k_ss | ||||
| encodedecodetest victor9k_ds | ||||
|  | ||||
| # vim: sw=4 ts=4 et | ||||
|  | ||||
|   | ||||
| @@ -17,6 +17,7 @@ | ||||
| #include <fstream> | ||||
|  | ||||
| static FlagGroup flags; | ||||
| static bool verify = true; | ||||
|  | ||||
| static StringFlag sourceImage( | ||||
| 	{ "--input", "-i" }, | ||||
| @@ -55,6 +56,13 @@ static StringFlag destHeads( | ||||
| 		setRange(config.mutable_heads(), value); | ||||
| 	}); | ||||
|  | ||||
| static ActionFlag noVerifyFlag( | ||||
| 	{ "--no-verify", "-n" }, | ||||
| 	"skip verification of write", | ||||
| 	[]{ | ||||
| 		verify = false; | ||||
| 	}); | ||||
|  | ||||
| int mainWrite(int argc, const char* argv[]) | ||||
| { | ||||
| 	if (argc == 1) | ||||
| @@ -68,7 +76,7 @@ int mainWrite(int argc, const char* argv[]) | ||||
| 	std::unique_ptr<FluxSink> fluxSink(FluxSink::create(config.flux_sink())); | ||||
|  | ||||
| 	std::unique_ptr<AbstractDecoder> decoder; | ||||
| 	if (config.has_decoder()) | ||||
| 	if (config.has_decoder() && verify) | ||||
| 		decoder = AbstractDecoder::create(config.decoder()); | ||||
|  | ||||
| 	std::unique_ptr<FluxSource> fluxSource; | ||||
|   | ||||
| @@ -17,7 +17,17 @@ image_reader { | ||||
|  | ||||
| image_writer { | ||||
| 	filename: "brother120.img" | ||||
| 	img {} | ||||
| 	img { | ||||
| 		tracks: 39 | ||||
| 		sides: 1 | ||||
| 		trackdata { | ||||
| 			sector_size: 256 | ||||
| 			sector_range { | ||||
| 				start_sector: 0 | ||||
| 				sector_count: 12 | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| encoder { | ||||
|   | ||||
| @@ -17,7 +17,17 @@ image_reader { | ||||
|  | ||||
| image_writer { | ||||
| 	filename: "brother240.img" | ||||
| 	img {} | ||||
| 	img { | ||||
| 		tracks: 78 | ||||
| 		sides: 1 | ||||
| 		trackdata { | ||||
| 			sector_size: 256 | ||||
| 			sector_range { | ||||
| 				start_sector: 0 | ||||
| 				sector_count: 12 | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| encoder { | ||||
|   | ||||
							
								
								
									
										459
									
								
								src/formats/victor9k_ds.textpb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										459
									
								
								src/formats/victor9k_ds.textpb
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,459 @@ | ||||
| comment: 'Victor 9000 / Sirius One 1224kB DSHD GCR variable sector)' | ||||
|  | ||||
| image_reader { | ||||
| 	filename: "victor9k_ds.img" | ||||
| 	img { | ||||
| 		tracks: 80 | ||||
| 		sides: 2 | ||||
| 		trackdata { | ||||
| 			sector_size: 512 | ||||
| 			sector_range { | ||||
| 				start_sector: 0 | ||||
| 			} | ||||
| 		} | ||||
| 		trackdata { | ||||
| 		    side: 0 | ||||
| 			track: 0 | ||||
| 			up_to_track: 3 | ||||
| 			sector_range { | ||||
| 				sector_count: 19 | ||||
| 			} | ||||
| 		} | ||||
| 		trackdata { | ||||
| 		    side: 0 | ||||
| 			track: 4 | ||||
| 			up_to_track: 15 | ||||
| 			sector_range { | ||||
| 				sector_count: 18 | ||||
| 			} | ||||
| 		} | ||||
| 		trackdata { | ||||
| 		    side: 0 | ||||
| 			track: 16 | ||||
| 			up_to_track: 26 | ||||
| 			sector_range { | ||||
| 				sector_count: 17 | ||||
| 			} | ||||
| 		} | ||||
| 		trackdata { | ||||
| 		    side: 0 | ||||
| 			track: 27 | ||||
| 			up_to_track: 37 | ||||
| 			sector_range { | ||||
| 				sector_count: 16 | ||||
| 			} | ||||
| 		} | ||||
| 		trackdata { | ||||
| 		    side: 0 | ||||
| 			track: 38 | ||||
| 			up_to_track: 47 | ||||
| 			sector_range { | ||||
| 				sector_count: 15 | ||||
| 			} | ||||
| 		} | ||||
| 		trackdata { | ||||
| 		    side: 0 | ||||
| 			track: 48 | ||||
| 			up_to_track: 59 | ||||
| 			sector_range { | ||||
| 				sector_count: 14 | ||||
| 			} | ||||
| 		} | ||||
| 		trackdata { | ||||
| 		    side: 0 | ||||
| 			track: 60 | ||||
| 			up_to_track: 70 | ||||
| 			sector_range { | ||||
| 				sector_count: 13 | ||||
| 			} | ||||
| 		} | ||||
| 		trackdata { | ||||
| 		    side: 0 | ||||
| 			track: 71 | ||||
| 			up_to_track: 79 | ||||
| 			sector_range { | ||||
| 				sector_count: 12 | ||||
| 			} | ||||
| 		} | ||||
| 		trackdata { | ||||
| 			side: 1 | ||||
| 			track: 0 | ||||
| 			up_to_track: 7 | ||||
| 			sector_range { | ||||
| 				sector_count: 18 | ||||
| 			} | ||||
| 		} | ||||
| 		trackdata { | ||||
| 			side: 1 | ||||
| 			track: 8 | ||||
| 			up_to_track: 18 | ||||
| 			sector_range { | ||||
| 				sector_count: 17 | ||||
| 			} | ||||
| 		} | ||||
| 		trackdata { | ||||
| 			side: 1 | ||||
| 			track: 19 | ||||
| 			up_to_track: 29 | ||||
| 			sector_range { | ||||
| 				sector_count: 16 | ||||
| 			} | ||||
| 		} | ||||
| 		trackdata { | ||||
| 			side: 1 | ||||
| 			track: 30 | ||||
| 			up_to_track: 39 | ||||
| 			sector_range { | ||||
| 				sector_count: 15 | ||||
| 			} | ||||
| 		} | ||||
| 		trackdata { | ||||
| 			side: 1 | ||||
| 			track: 40 | ||||
| 			up_to_track: 51 | ||||
| 			sector_range { | ||||
| 				sector_count: 14 | ||||
| 			} | ||||
| 		} | ||||
| 		trackdata { | ||||
| 			side: 1 | ||||
| 			track: 52 | ||||
| 			up_to_track: 62 | ||||
| 			sector_range { | ||||
| 				sector_count: 13 | ||||
| 			} | ||||
| 		} | ||||
| 		trackdata { | ||||
| 			side: 1 | ||||
| 			track: 63 | ||||
| 			up_to_track: 74 | ||||
| 			sector_range { | ||||
| 				sector_count: 12 | ||||
| 			} | ||||
| 		} | ||||
| 		trackdata { | ||||
| 			side: 1 | ||||
| 			track: 75 | ||||
| 			up_to_track: 79 | ||||
| 			sector_range { | ||||
| 				sector_count: 11 | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| image_writer { | ||||
| 	filename: "victor9k_ds.img" | ||||
| 	img { | ||||
| 		tracks: 80 | ||||
| 		sides: 2 | ||||
| 		trackdata { | ||||
| 			sector_size: 512 | ||||
| 			sector_range { | ||||
| 				start_sector: 0 | ||||
| 			} | ||||
| 		} | ||||
| 		trackdata { | ||||
| 			side: 0 | ||||
| 			track: 0 | ||||
| 			up_to_track: 3 | ||||
| 			sector_range { | ||||
| 				sector_count: 19 | ||||
| 			} | ||||
| 		} | ||||
| 		trackdata { | ||||
| 			side: 0 | ||||
| 			track: 4 | ||||
| 			up_to_track: 15 | ||||
| 			sector_range { | ||||
| 				sector_count: 18 | ||||
| 			} | ||||
| 		} | ||||
| 		trackdata { | ||||
| 			side: 0 | ||||
| 			track: 16 | ||||
| 			up_to_track: 26 | ||||
| 			sector_range { | ||||
| 				sector_count: 17 | ||||
| 			} | ||||
| 		} | ||||
| 		trackdata { | ||||
| 			side: 0 | ||||
| 			track: 27 | ||||
| 			up_to_track: 37 | ||||
| 			sector_range { | ||||
| 				sector_count: 16 | ||||
| 			} | ||||
| 		} | ||||
| 		trackdata { | ||||
| 			side: 0 | ||||
| 			track: 38 | ||||
| 			up_to_track: 47 | ||||
| 			sector_range { | ||||
| 				sector_count: 15 | ||||
| 			} | ||||
| 		} | ||||
| 		trackdata { | ||||
| 			side: 0 | ||||
| 			track: 48 | ||||
| 			up_to_track: 59 | ||||
| 			sector_range { | ||||
| 				sector_count: 14 | ||||
| 			} | ||||
| 		} | ||||
| 		trackdata { | ||||
| 			side: 0 | ||||
| 			track: 60 | ||||
| 			up_to_track: 70 | ||||
| 			sector_range { | ||||
| 				sector_count: 13 | ||||
| 			} | ||||
| 		} | ||||
| 		trackdata { | ||||
| 			side: 0 | ||||
| 			track: 71 | ||||
| 			up_to_track: 79 | ||||
| 			sector_range { | ||||
| 				sector_count: 12 | ||||
| 			} | ||||
| 		} | ||||
| 		trackdata { | ||||
| 			side: 1 | ||||
| 			track: 0 | ||||
| 			up_to_track: 7 | ||||
| 			sector_range { | ||||
| 				sector_count: 18 | ||||
| 			} | ||||
| 		} | ||||
| 		trackdata { | ||||
| 			side: 1 | ||||
| 			track: 8 | ||||
| 			up_to_track: 18 | ||||
| 			sector_range { | ||||
| 				sector_count: 17 | ||||
| 			} | ||||
| 		} | ||||
| 		trackdata { | ||||
| 			side: 1 | ||||
| 			track: 19 | ||||
| 			up_to_track: 29 | ||||
| 			sector_range { | ||||
| 				sector_count: 16 | ||||
| 			} | ||||
| 		} | ||||
| 		trackdata { | ||||
| 			side: 1 | ||||
| 			track: 30 | ||||
| 			up_to_track: 39 | ||||
| 			sector_range { | ||||
| 				sector_count: 15 | ||||
| 			} | ||||
| 		} | ||||
| 		trackdata { | ||||
| 			side: 1 | ||||
| 			track: 40 | ||||
| 			up_to_track: 51 | ||||
| 			sector_range { | ||||
| 				sector_count: 14 | ||||
| 			} | ||||
| 		} | ||||
| 		trackdata { | ||||
| 			side: 1 | ||||
| 			track: 52 | ||||
| 			up_to_track: 62 | ||||
| 			sector_range { | ||||
| 				sector_count: 13 | ||||
| 			} | ||||
| 		} | ||||
| 		trackdata { | ||||
| 			side: 1 | ||||
| 			track: 63 | ||||
| 			up_to_track: 74 | ||||
| 			sector_range { | ||||
| 				sector_count: 12 | ||||
| 			} | ||||
| 		} | ||||
| 		trackdata { | ||||
| 			side: 1 | ||||
| 			track: 75 | ||||
| 			up_to_track: 79 | ||||
| 			sector_range { | ||||
| 				sector_count: 11 | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| encoder { | ||||
| 	victor9k { | ||||
| 		trackdata { | ||||
| 			original_data_rate_khz: 500 | ||||
| 			post_index_gap_us: 1000.0 | ||||
| 			pre_header_sync_bits: 120 | ||||
| 			post_header_gap_bits: 48 | ||||
| 			pre_data_sync_bits: 40 | ||||
| 			post_data_gap_bits: 200 | ||||
| 		} | ||||
| 		trackdata { | ||||
| 			head: 0 | ||||
| 			min_cylinder: 0 | ||||
| 			max_cylinder: 3 | ||||
| 			original_period_ms: 237.9 | ||||
| 			sector_range { | ||||
| 				start_sector: 0 | ||||
| 				sector_count: 19 | ||||
| 			} | ||||
| 		} | ||||
| 		trackdata { | ||||
| 			head: 0 | ||||
| 			min_cylinder: 4 | ||||
| 			max_cylinder: 15 | ||||
| 			original_period_ms: 224.5 | ||||
| 			sector_range { | ||||
| 				sector_count: 18 | ||||
| 			} | ||||
| 		} | ||||
| 		trackdata { | ||||
| 			head: 0 | ||||
| 			min_cylinder: 16 | ||||
| 			max_cylinder: 26 | ||||
| 			original_period_ms: 212.2 | ||||
| 			sector_range { | ||||
| 				sector_count: 17 | ||||
| 			} | ||||
| 		} | ||||
| 		trackdata { | ||||
| 			head: 0 | ||||
| 			min_cylinder: 27 | ||||
| 			max_cylinder: 37 | ||||
| 			original_period_ms: 199.9 | ||||
| 			sector_range { | ||||
| 				sector_count: 16 | ||||
| 			} | ||||
| 		} | ||||
| 		trackdata { | ||||
| 			head: 0 | ||||
| 			min_cylinder: 38 | ||||
| 			max_cylinder: 47 | ||||
| 			original_period_ms: 187.6 | ||||
| 			sector_range { | ||||
| 				sector_count: 15 | ||||
| 			} | ||||
| 		} | ||||
| 		trackdata { | ||||
| 			head: 0 | ||||
| 			min_cylinder: 48 | ||||
| 			max_cylinder: 59 | ||||
| 			original_period_ms: 175.3 | ||||
| 			sector_range { | ||||
| 				sector_count: 14 | ||||
| 			} | ||||
| 		} | ||||
| 		trackdata { | ||||
| 			head: 0 | ||||
| 			min_cylinder: 60 | ||||
| 			max_cylinder: 70 | ||||
| 			original_period_ms: 163.0 | ||||
| 			sector_range { | ||||
| 				sector_count: 13 | ||||
| 			} | ||||
| 		} | ||||
| 		trackdata { | ||||
| 			head: 0 | ||||
| 			min_cylinder: 71 | ||||
| 			max_cylinder: 79 | ||||
| 			original_period_ms: 149.6 | ||||
| 			sector_range { | ||||
| 				sector_count: 12 | ||||
| 			} | ||||
| 		} | ||||
| 		trackdata { | ||||
| 			head: 1 | ||||
| 			min_cylinder: 0 | ||||
| 			max_cylinder: 7 | ||||
| 			original_period_ms: 224.5 | ||||
| 			sector_range { | ||||
| 				start_sector: 0 | ||||
| 				sector_count: 18 | ||||
| 			} | ||||
| 		} | ||||
| 		trackdata { | ||||
| 			head: 1 | ||||
| 			min_cylinder: 8 | ||||
| 			max_cylinder: 18 | ||||
| 			original_period_ms: 212.2 | ||||
| 			sector_range { | ||||
| 				sector_count: 17 | ||||
| 			} | ||||
| 		} | ||||
| 		trackdata { | ||||
| 			head: 1 | ||||
| 			min_cylinder: 19 | ||||
| 			max_cylinder: 29 | ||||
| 			original_period_ms: 199.9 | ||||
| 			sector_range { | ||||
| 				sector_count: 16 | ||||
| 			} | ||||
| 		} | ||||
| 		trackdata { | ||||
| 			head: 1 | ||||
| 			min_cylinder: 30 | ||||
| 			max_cylinder: 39 | ||||
| 			original_period_ms: 187.6 | ||||
| 			sector_range { | ||||
| 				sector_count: 15 | ||||
| 			} | ||||
| 		} | ||||
| 		trackdata { | ||||
| 			head: 1 | ||||
| 			min_cylinder: 40 | ||||
| 			max_cylinder: 51 | ||||
| 			original_period_ms: 175.3 | ||||
| 			sector_range { | ||||
| 				sector_count: 14 | ||||
| 			} | ||||
| 		} | ||||
| 		trackdata { | ||||
| 			head: 1 | ||||
| 			min_cylinder: 52 | ||||
| 			max_cylinder: 62 | ||||
| 			original_period_ms: 163.0 | ||||
| 			sector_range { | ||||
| 				sector_count: 13 | ||||
| 			} | ||||
| 		} | ||||
| 		trackdata { | ||||
| 			head: 1 | ||||
| 			min_cylinder: 63 | ||||
| 			max_cylinder: 74 | ||||
| 			original_period_ms: 149.6 | ||||
| 			sector_range { | ||||
| 				sector_count: 12 | ||||
| 			} | ||||
| 		} | ||||
| 		trackdata { | ||||
| 			head: 1 | ||||
| 			min_cylinder: 75 | ||||
| 			max_cylinder: 79 | ||||
| 			original_period_ms: 144.0 | ||||
| 			sector_range { | ||||
| 				sector_count: 11 | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| decoder { | ||||
| 	victor9k {} | ||||
| } | ||||
|  | ||||
| cylinders { | ||||
| 	start: 0 | ||||
| 	end: 79 | ||||
| } | ||||
|  | ||||
| heads { | ||||
| 	start: 0 | ||||
| 	end: 1 | ||||
| } | ||||
|  | ||||
| @@ -41,13 +41,13 @@ image_reader { | ||||
| 		} | ||||
| 		trackdata { | ||||
| 			track: 38 | ||||
| 			up_to_track: 48 | ||||
| 			up_to_track: 47 | ||||
| 			sector_range { | ||||
| 				sector_count: 15 | ||||
| 			} | ||||
| 		} | ||||
| 		trackdata { | ||||
| 			track: 49 | ||||
| 			track: 48 | ||||
| 			up_to_track: 59 | ||||
| 			sector_range { | ||||
| 				sector_count: 14 | ||||
| @@ -111,13 +111,13 @@ image_writer { | ||||
| 		} | ||||
| 		trackdata { | ||||
| 			track: 38 | ||||
| 			up_to_track: 48 | ||||
| 			up_to_track: 47 | ||||
| 			sector_range { | ||||
| 				sector_count: 15 | ||||
| 			} | ||||
| 		} | ||||
| 		trackdata { | ||||
| 			track: 49 | ||||
| 			track: 48 | ||||
| 			up_to_track: 59 | ||||
| 			sector_range { | ||||
| 				sector_count: 14 | ||||
| @@ -190,7 +190,7 @@ encoder { | ||||
| 		trackdata { | ||||
| 			head: 0 | ||||
| 			min_cylinder: 38 | ||||
| 			max_cylinder: 48 | ||||
| 			max_cylinder: 47 | ||||
| 			original_period_ms: 187.6 | ||||
| 			sector_range { | ||||
| 				sector_count: 15 | ||||
| @@ -198,7 +198,7 @@ encoder { | ||||
| 		} | ||||
| 		trackdata { | ||||
| 			head: 0 | ||||
| 			min_cylinder: 49 | ||||
| 			min_cylinder: 48 | ||||
| 			max_cylinder: 59 | ||||
| 			original_period_ms: 175.3 | ||||
| 			sector_range { | ||||
|   | ||||
		Reference in New Issue
	
	Block a user