mirror of
				https://github.com/davidgiven/fluxengine.git
				synced 2025-10-24 11:11:02 -07:00 
			
		
		
		
	Compare commits
	
		
			46 Commits
		
	
	
		
			FluxEngine
			...
			FluxEngine
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | ebcb9c4bb0 | ||
|  | 2520834b18 | ||
|  | a1f3087046 | ||
|  | e9670e205e | ||
|  | 658e2b7295 | ||
|  | 7b4a8d6de2 | ||
|  | e8f7b51aef | ||
|  | 9d6bc57a5f | ||
|  | 73766f92b4 | ||
|  | 80badf3b54 | ||
|  | 116529f85a | ||
|  | 5a2b2bc07a | ||
|  | 41070395c0 | ||
|  | 4304d1eede | ||
|  | 46f1b0aef4 | ||
|  | 9923d67a7c | ||
|  | 99335a84fd | ||
|  | c266779433 | ||
|  | bdcc12cd53 | ||
|  | 7988d0fe24 | ||
|  | 27f5c294b1 | ||
|  | b9a53e0d1c | ||
|  | f8b6d5e6fb | ||
|  | 04ff31c348 | ||
|  | 77b4aebd1b | ||
|  | 4056364300 | ||
|  | 60bfe050d3 | ||
|  | 28d0ce765e | ||
|  | 4954d33307 | ||
|  | 55f3354287 | ||
|  | d6ae373fa8 | ||
|  | a626d5f9a0 | ||
|  | 29db67528d | ||
|  | 31d7477c6a | ||
|  | 56af9eaf18 | ||
|  | 5de0636fe7 | ||
|  | f9117b8d11 | ||
|  | 10d385375f | ||
|  | 2f72c3f8f0 | ||
|  | 54edff9b94 | ||
|  | 112377f885 | ||
|  | 87e29fc386 | ||
|  | b1db5c48b1 | ||
|  | 38fab7edcb | ||
|  | d8172154c3 | ||
|  | eb924780ab | 
| @@ -15,7 +15,7 @@ install: | ||||
|  | ||||
| build_script: | ||||
|   - make | ||||
|   - zip -9 fluxengine.zip fluxengine.exe brother120tool.exe | ||||
|   - zip -9 fluxengine.zip fluxengine.exe brother120tool.exe FluxEngine.cydsn/CortexM3/ARM_GCC_541/Release/FluxEngine.hex | ||||
|  | ||||
| artifacts: | ||||
|   - path: fluxengine.zip | ||||
|   | ||||
| @@ -3,6 +3,10 @@ streams | ||||
| .*\.flux | ||||
| .*\.img | ||||
| .*\.raw | ||||
| .*\.orig | ||||
| .vscode | ||||
| remote | ||||
| FluxEngine.cydsn/CortexM3 | ||||
| FluxEngine.cydsn/Generated_Source | ||||
| FluxEngine.cydsn/codegentemp | ||||
|  | ||||
|   | ||||
							
								
								
									
										4626
									
								
								FluxEngine.cydsn/CortexM3/ARM_GCC_541/Release/FluxEngine.hex
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4626
									
								
								FluxEngine.cydsn/CortexM3/ARM_GCC_541/Release/FluxEngine.hex
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -530,31 +530,38 @@ | ||||
| <CyGuid_ebc4f06d-207f-49c2-a540-72acf4adabc0 type_name="CyDesigner.Common.ProjMgmt.Model.CyPrjMgmtFolderSerialize" version="3"> | ||||
| <CyGuid_2f73275c-45bf-46ba-b3b1-00a2fe0c8dd8 type_name="CyDesigner.Common.ProjMgmt.Model.CyPrjMgmtBaseContainerSerialize" version="1"> | ||||
| <CyGuid_31768f72-0253-412b-af77-e7dba74d1330 type_name="CyDesigner.Common.ProjMgmt.Model.CyPrjMgmtItemSerialize" version="2" name="LED" persistent=""> | ||||
| <Hidden v="False" /> | ||||
| <Hidden v="True" /> | ||||
| </CyGuid_31768f72-0253-412b-af77-e7dba74d1330> | ||||
| <CyGuid_0820c2e7-528d-4137-9a08-97257b946089 type_name="CyDesigner.Common.ProjMgmt.Model.CyPrjMgmtItemListSerialize" version="2"> | ||||
| <dependencies> | ||||
| <CyGuid_8b8ab257-35d3-4473-b57b-36315200b38b type_name="CyDesigner.Common.ProjMgmt.Model.CyPrjMgmtFileSerialize" version="3" xml_contents_version="1"> | ||||
| <CyGuid_31768f72-0253-412b-af77-e7dba74d1330 type_name="CyDesigner.Common.ProjMgmt.Model.CyPrjMgmtItemSerialize" version="2" name="LED_aliases.h" persistent="Generated_Source\PSoC5\LED_aliases.h"> | ||||
| <Hidden v="False" /> | ||||
| <Hidden v="True" /> | ||||
| </CyGuid_31768f72-0253-412b-af77-e7dba74d1330> | ||||
| <build_action v="HEADER;;;;" /> | ||||
| <PropertyDeltas /> | ||||
| </CyGuid_8b8ab257-35d3-4473-b57b-36315200b38b> | ||||
| <CyGuid_8b8ab257-35d3-4473-b57b-36315200b38b type_name="CyDesigner.Common.ProjMgmt.Model.CyPrjMgmtFileSerialize" version="3" xml_contents_version="1"> | ||||
| <CyGuid_31768f72-0253-412b-af77-e7dba74d1330 type_name="CyDesigner.Common.ProjMgmt.Model.CyPrjMgmtItemSerialize" version="2" name="LED.c" persistent="Generated_Source\PSoC5\LED.c"> | ||||
| <Hidden v="False" /> | ||||
| <Hidden v="True" /> | ||||
| </CyGuid_31768f72-0253-412b-af77-e7dba74d1330> | ||||
| <build_action v="SOURCE_C;CortexM3;;;" /> | ||||
| <PropertyDeltas /> | ||||
| </CyGuid_8b8ab257-35d3-4473-b57b-36315200b38b> | ||||
| <CyGuid_8b8ab257-35d3-4473-b57b-36315200b38b type_name="CyDesigner.Common.ProjMgmt.Model.CyPrjMgmtFileSerialize" version="3" xml_contents_version="1"> | ||||
| <CyGuid_31768f72-0253-412b-af77-e7dba74d1330 type_name="CyDesigner.Common.ProjMgmt.Model.CyPrjMgmtItemSerialize" version="2" name="LED.h" persistent="Generated_Source\PSoC5\LED.h"> | ||||
| <Hidden v="False" /> | ||||
| <Hidden v="True" /> | ||||
| </CyGuid_31768f72-0253-412b-af77-e7dba74d1330> | ||||
| <build_action v="HEADER;;;;" /> | ||||
| <PropertyDeltas /> | ||||
| </CyGuid_8b8ab257-35d3-4473-b57b-36315200b38b> | ||||
| <CyGuid_8b8ab257-35d3-4473-b57b-36315200b38b type_name="CyDesigner.Common.ProjMgmt.Model.CyPrjMgmtFileSerialize" version="3" xml_contents_version="1"> | ||||
| <CyGuid_31768f72-0253-412b-af77-e7dba74d1330 type_name="CyDesigner.Common.ProjMgmt.Model.CyPrjMgmtItemSerialize" version="2" name="LED_PM.c" persistent="Generated_Source\PSoC5\LED_PM.c"> | ||||
| <Hidden v="True" /> | ||||
| </CyGuid_31768f72-0253-412b-af77-e7dba74d1330> | ||||
| <build_action v="SOURCE_C;CortexM3;;;" /> | ||||
| <PropertyDeltas /> | ||||
| </CyGuid_8b8ab257-35d3-4473-b57b-36315200b38b> | ||||
| </dependencies> | ||||
| </CyGuid_0820c2e7-528d-4137-9a08-97257b946089> | ||||
| </CyGuid_2f73275c-45bf-46ba-b3b1-00a2fe0c8dd8> | ||||
| @@ -3282,6 +3289,39 @@ | ||||
| </CyGuid_2f73275c-45bf-46ba-b3b1-00a2fe0c8dd8> | ||||
| <filters /> | ||||
| </CyGuid_ebc4f06d-207f-49c2-a540-72acf4adabc0> | ||||
| <CyGuid_ebc4f06d-207f-49c2-a540-72acf4adabc0 type_name="CyDesigner.Common.ProjMgmt.Model.CyPrjMgmtFolderSerialize" version="3"> | ||||
| <CyGuid_2f73275c-45bf-46ba-b3b1-00a2fe0c8dd8 type_name="CyDesigner.Common.ProjMgmt.Model.CyPrjMgmtBaseContainerSerialize" version="1"> | ||||
| <CyGuid_31768f72-0253-412b-af77-e7dba74d1330 type_name="CyDesigner.Common.ProjMgmt.Model.CyPrjMgmtItemSerialize" version="2" name="LED_PIN" persistent=""> | ||||
| <Hidden v="False" /> | ||||
| </CyGuid_31768f72-0253-412b-af77-e7dba74d1330> | ||||
| <CyGuid_0820c2e7-528d-4137-9a08-97257b946089 type_name="CyDesigner.Common.ProjMgmt.Model.CyPrjMgmtItemListSerialize" version="2"> | ||||
| <dependencies> | ||||
| <CyGuid_8b8ab257-35d3-4473-b57b-36315200b38b type_name="CyDesigner.Common.ProjMgmt.Model.CyPrjMgmtFileSerialize" version="3" xml_contents_version="1"> | ||||
| <CyGuid_31768f72-0253-412b-af77-e7dba74d1330 type_name="CyDesigner.Common.ProjMgmt.Model.CyPrjMgmtItemSerialize" version="2" name="LED_PIN_aliases.h" persistent="Generated_Source\PSoC5\LED_PIN_aliases.h"> | ||||
| <Hidden v="False" /> | ||||
| </CyGuid_31768f72-0253-412b-af77-e7dba74d1330> | ||||
| <build_action v="HEADER;;;;" /> | ||||
| <PropertyDeltas /> | ||||
| </CyGuid_8b8ab257-35d3-4473-b57b-36315200b38b> | ||||
| <CyGuid_8b8ab257-35d3-4473-b57b-36315200b38b type_name="CyDesigner.Common.ProjMgmt.Model.CyPrjMgmtFileSerialize" version="3" xml_contents_version="1"> | ||||
| <CyGuid_31768f72-0253-412b-af77-e7dba74d1330 type_name="CyDesigner.Common.ProjMgmt.Model.CyPrjMgmtItemSerialize" version="2" name="LED_PIN.c" persistent="Generated_Source\PSoC5\LED_PIN.c"> | ||||
| <Hidden v="False" /> | ||||
| </CyGuid_31768f72-0253-412b-af77-e7dba74d1330> | ||||
| <build_action v="SOURCE_C;CortexM3;;;" /> | ||||
| <PropertyDeltas /> | ||||
| </CyGuid_8b8ab257-35d3-4473-b57b-36315200b38b> | ||||
| <CyGuid_8b8ab257-35d3-4473-b57b-36315200b38b type_name="CyDesigner.Common.ProjMgmt.Model.CyPrjMgmtFileSerialize" version="3" xml_contents_version="1"> | ||||
| <CyGuid_31768f72-0253-412b-af77-e7dba74d1330 type_name="CyDesigner.Common.ProjMgmt.Model.CyPrjMgmtItemSerialize" version="2" name="LED_PIN.h" persistent="Generated_Source\PSoC5\LED_PIN.h"> | ||||
| <Hidden v="False" /> | ||||
| </CyGuid_31768f72-0253-412b-af77-e7dba74d1330> | ||||
| <build_action v="HEADER;;;;" /> | ||||
| <PropertyDeltas /> | ||||
| </CyGuid_8b8ab257-35d3-4473-b57b-36315200b38b> | ||||
| </dependencies> | ||||
| </CyGuid_0820c2e7-528d-4137-9a08-97257b946089> | ||||
| </CyGuid_2f73275c-45bf-46ba-b3b1-00a2fe0c8dd8> | ||||
| <filters /> | ||||
| </CyGuid_ebc4f06d-207f-49c2-a540-72acf4adabc0> | ||||
| </dependencies> | ||||
| </CyGuid_0820c2e7-528d-4137-9a08-97257b946089> | ||||
| </CyGuid_2f73275c-45bf-46ba-b3b1-00a2fe0c8dd8> | ||||
| @@ -3700,6 +3740,6 @@ | ||||
| </ignored_deps> | ||||
| </CyGuid_495451fe-d201-4d01-b22d-5d3f5609ac37> | ||||
| <boot_component v="" /> | ||||
| <current_generation v="60" /> | ||||
| <current_generation v="68" /> | ||||
| </CyGuid_fec8f9e8-2365-4bdb-96d3-a4380222e01b> | ||||
| </CyXmlSerializer> | ||||
										
											Binary file not shown.
										
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										4
									
								
								Makefile
									
									
									
									
									
								
							
							
						
						
									
										4
									
								
								Makefile
									
									
									
									
									
								
							| @@ -1,8 +1,8 @@ | ||||
| PACKAGES = zlib sqlite3 libusb-1.0 | ||||
|  | ||||
| export CFLAGS = -O3 -g --std=c++14 \ | ||||
| export CFLAGS = -Os -g --std=c++14 \ | ||||
| 	-ffunction-sections -fdata-sections | ||||
| export LDFLAGS = -O3 | ||||
| export LDFLAGS = -Os | ||||
|  | ||||
| ifeq ($(OS), Windows_NT) | ||||
| export CXX = /mingw32/bin/g++ | ||||
|   | ||||
| @@ -92,6 +92,8 @@ void AmigaDecoder::decodeSectorRecord() | ||||
|  | ||||
|     uint32_t wanteddatachecksum = deinterleave(ptr, 4).reader().read_be32(); | ||||
|     uint32_t gotdatachecksum = checksum(rawbytes.slice(62, 1024)); | ||||
|     _sector->data = deinterleave(ptr, 512); | ||||
|  | ||||
|     _sector->data.clear(); | ||||
|     _sector->data.writer().append(deinterleave(ptr, 512)).append(recoveryinfo); | ||||
|     _sector->status = (gotdatachecksum == wanteddatachecksum) ? Sector::OK : Sector::BAD_CHECKSUM; | ||||
| } | ||||
|   | ||||
| @@ -134,6 +134,9 @@ void IbmDecoder::decodeSectorRecord() | ||||
|     uint16_t gotCrc = crc16(CCITT_POLY, bytes.slice(0, _currentHeaderLength + 5)); | ||||
|     if (wantCrc == gotCrc) | ||||
|         _sector->status = Sector::DATA_MISSING; /* correct but unintuitive */ | ||||
|  | ||||
|     if (_ignoreSideByte) | ||||
|         _sector->logicalSide = _sector->physicalSide; | ||||
| } | ||||
|  | ||||
| void IbmDecoder::decodeDataRecord() | ||||
|   | ||||
| @@ -31,8 +31,9 @@ struct IbmIdam | ||||
| class IbmDecoder : public AbstractDecoder | ||||
| { | ||||
| public: | ||||
|     IbmDecoder(unsigned sectorBase): | ||||
|         _sectorBase(sectorBase) | ||||
|     IbmDecoder(unsigned sectorBase, bool ignoreSideByte=false): | ||||
|         _sectorBase(sectorBase), | ||||
|         _ignoreSideByte(ignoreSideByte) | ||||
|     {} | ||||
|  | ||||
|     RecordType advanceToNextRecord(); | ||||
| @@ -41,6 +42,7 @@ public: | ||||
|  | ||||
| private: | ||||
|     unsigned _sectorBase; | ||||
|     bool _ignoreSideByte; | ||||
|     unsigned _currentSectorSize; | ||||
|     unsigned _currentHeaderLength; | ||||
| }; | ||||
|   | ||||
| @@ -180,5 +180,7 @@ void MacintoshDecoder::decodeDataRecord() | ||||
|         inputbuffer[i] = decode_data_gcr(inputbuffer[i]); | ||||
|          | ||||
|     _sector->status = Sector::BAD_CHECKSUM; | ||||
|     _sector->data = decode_crazy_data(inputbuffer, _sector->status); | ||||
|     Bytes userData = decode_crazy_data(inputbuffer, _sector->status); | ||||
|     _sector->data.clear(); | ||||
|     _sector->data.writer().append(userData.slice(12, 512)).append(userData.slice(0, 12)); | ||||
| } | ||||
|   | ||||
| @@ -103,7 +103,43 @@ the unconnected pins and solder a short piece of wire to a GND pin on the | ||||
| board. Alternatively you'll need to splice it into your drive's power supply | ||||
| cable somehow. (The black one.) | ||||
|  | ||||
| ## Building the firmware | ||||
| ## Programming the board | ||||
|  | ||||
| You've got two options here. You can either use the precompiled firmware | ||||
| supplied with the source, or else install the Cypress SDK and build it | ||||
| yourself. If you want to hack the firmware source you need the latter, but | ||||
| if you trust me to do it for you use the precompiled firmware. In either | ||||
| case you'll need Windows and have to install some Cypress stuff. | ||||
|  | ||||
| **Before you read this:** If you're on Windows, good news! You can download a | ||||
| precompiled version of the FluxEngine client and precompiled firmware [from | ||||
| the GitHub releases | ||||
| page](https://github.com/davidgiven/fluxengine/releases/latest). Simply unzip | ||||
| it somewhere and run the `.exe` files from a `cmd` window (or other shell). | ||||
| Follow the instructions below to program the board with the firmware. | ||||
|  | ||||
| ### Using the precompiled firmware | ||||
|  | ||||
| On your Windows machine, [install the PSoC | ||||
| Programmer](https://www.cypress.com/products/psoc-programming-solutions). | ||||
| **Note:** _not_ the Cypress Programmer, which is for a different board! | ||||
| Cypress will make you register. | ||||
|  | ||||
| Once done, run it. Plug the blunt end of the FluxEngine board into a USB | ||||
| port (the end which is a USB connector). The programmer should detect it | ||||
| and report it as a KitProg. You may be prompted to upgrade the programmer | ||||
| hardware; if so, follow the instructions and do it. | ||||
|  | ||||
| Now go to File -> File Load and open | ||||
| `FluxEngine.cydsn/CortexM3/ARM_GCC_541/Release/FluxEngine.hex` in the | ||||
| project. If you're on Windows, the precompiled zipfile also contains a copy | ||||
| of this file. Press the Program button (the one in the toolbar marked with a | ||||
| down arrow). Stuff will happen and you should be left with three green boxes | ||||
| in the status bar and 'Programming Succeeded' at the top of the log window. | ||||
|  | ||||
| You're done. You can unplug the board and close the programmer. | ||||
|  | ||||
| ### Building the firmware yourself | ||||
|  | ||||
| On your Windows machine, [install the Cypress SDK and CY8CKIT-059 | ||||
| BSP](http://www.cypress.com/documentation/development-kitsboards/cy8ckit-059-psoc-5lp-prototyping-kit-onboard-programmer-and). | ||||
| @@ -118,7 +154,7 @@ tutorial and making the LED on your board flash. It'll tell you where all the | ||||
| controls are and how to program the board. Remember that the big end of the | ||||
| board plugs into your computer for programming. | ||||
|  | ||||
| When you're ready, open the `FluxEngine.cydsn/FluxEngine.cywrk` workspace, | ||||
| When you're ready, open the `FluxEngine.cydsn/FluxEngine.cyprj` project, | ||||
| pick 'Program' from the menu, and the firmware should compile and be | ||||
| programmed onto your board. | ||||
|  | ||||
| @@ -139,11 +175,6 @@ the port and proceed normally. | ||||
|  | ||||
| ## Building the client | ||||
|  | ||||
| **Before you read this:** If you're on Windows, good news! You can download a | ||||
| *precompiled version of the FluxEngine client [from the GitHub releases | ||||
| *page](https://github.com/davidgiven/fluxengine/releases/latest). Simply unzip | ||||
| *it somewhere and run it from a `cmd` window (or other shell). | ||||
|  | ||||
| The client software is where the intelligence, such as it is, is. It's pretty | ||||
| generic libusb stuff and should build and run on Windows, Linux and OSX as | ||||
| well, although on Windows it'll need MSYS2 and mingw32. You'll need to | ||||
|   | ||||
| @@ -23,6 +23,17 @@ You should end up with an `amiga.adf` which is 901120 bytes long (for a | ||||
| normal DD disk) --- it ought to be a perfectly normal ADF file which you can | ||||
| use in an emulator. | ||||
|  | ||||
| If you want the metadata as well, specify a 528 byte sector size for the | ||||
| output image: | ||||
|  | ||||
| ``` | ||||
| fluxengine read amiga -o amiga.adf:b=528 | ||||
| ``` | ||||
|  | ||||
| You will end up with a 929280 byte long image which you probably _can't_ use | ||||
| in an emulator; each sector will contain the 512 bytes of user payload | ||||
| followed by the 16 bytes of metadata. | ||||
|  | ||||
| Useful references | ||||
| ----------------- | ||||
|  | ||||
|   | ||||
| @@ -129,8 +129,8 @@ reverse engineered it to find out. | ||||
|  | ||||
| Standard Linux mtools will access the filesystem image and allow you to move | ||||
| files in and out. However, you'll need to change the media type bytes at | ||||
| offsets 0x015 and 0x100 from 0x58 to 0xf0 before mtools will touch it. Once | ||||
| done, this will work: | ||||
| offsets 0x015 and 0x100 from 0x58 to 0xf0 before mtools will touch it. The | ||||
| supplied `brother240tool` will do this. Once done, this will work: | ||||
|  | ||||
| ``` | ||||
| mdir -i brother.img | ||||
|   | ||||
| @@ -23,7 +23,7 @@ computer](https://ilesj.wordpress.com/2014/05/14/1541-why-so-complicated/) of | ||||
| 300 bytes per second (!). (The drive itself could transfer data reasonably | ||||
| quickly.) | ||||
|  | ||||
| A standard 1541 disk has 35 tracks of 17 to 20 sectors, each 256 bytes long. | ||||
| A standard 1541 disk has 35 tracks of 17 to 21 sectors, each 256 bytes long. | ||||
|  | ||||
| Reading discs | ||||
| ------------- | ||||
| @@ -34,15 +34,14 @@ Just do: | ||||
| fluxengine read c64 | ||||
| ``` | ||||
|  | ||||
| You should end up with an `c64.img` which is 187136 bytes long (for a normal | ||||
| 1541 disk). | ||||
| You should end up with an `c64.d64` file which is 174848 bytes long. You can | ||||
| load this straight into a Commodore 64 emulator such as | ||||
| [VICE](http://vice-emu.sourceforge.net/). | ||||
|  | ||||
| **Big warning!** The image may not work in an emulator. Commodore 64 disk images are | ||||
| **Big warning!** Commodore 64 disk images are | ||||
| complicated due to the way the tracks are different sizes and the odd sector | ||||
| size. FluxEngine chooses to store them in a simple 256 x 20 x 35 layout, | ||||
| with holes where missing sectors should be. This was easiest. If anyone can | ||||
| suggest a better way, please [get in | ||||
| touch](https://github.com/davidgiven/fluxengine/issues/new). | ||||
| size, so you need the special D64 or LDBS output formats to represent them | ||||
| sensibly. Don't use IMG unless you know what you're doing. | ||||
|  | ||||
| Useful references | ||||
| ----------------- | ||||
|   | ||||
| @@ -50,6 +50,10 @@ with holes where missing sectors should be. This was easiest. If anyone can | ||||
| suggest a better way, please [get in | ||||
| touch](https://github.com/davidgiven/fluxengine/issues/new). | ||||
|  | ||||
| The 12 bytes of metadata _follow_ the 512 bytes of user payload in the sector | ||||
| image. If you don't want it, specify a geometry in the output file with a | ||||
| 512-byte sectore size like `-o mac.img:c=80:h=1:s=12:b=512`. | ||||
|  | ||||
| Useful references | ||||
| ----------------- | ||||
|  | ||||
|   | ||||
| @@ -123,8 +123,12 @@ admittedly expensive.) | ||||
|     sheet](http://www.bitsavers.org/pdf/mitsubishi/floppy/M4851/TJ2-G30211A_M4851_DSHH_48TPI_OEM_Manual_Nov83.pdf): | ||||
|     the equivalent data sheet for a representative 5.25" drive. | ||||
|  | ||||
|   - [The DRG Business Machines YD-174 manual](https://electrickery.hosting.philpem.me.uk/comp/divcomp/doc/YE_Data_YD-174_8inchFloppyDriveTechnicalManual.pdf): | ||||
| 	the equivalent manual (data sheets hadn't been invented then) for a | ||||
| 	representative 8" drive. | ||||
|  | ||||
|   - [KryoFlux stream file | ||||
|     documentation](https://www.kryoflux.com/download/kryoflux_stream_protocol_rev1.1.pdf): | ||||
|     the format of KryoFlux stream files (partially supported by FluxEngine) | ||||
|  | ||||
|    | ||||
|    | ||||
|   | ||||
							
								
								
									
										89
									
								
								doc/using.md
									
									
									
									
									
								
							
							
						
						
									
										89
									
								
								doc/using.md
									
									
									
									
									
								
							| @@ -112,6 +112,51 @@ sensible for the command you're using. | ||||
| **Important note:** FluxEngine _always_ uses zero-based units (even if the | ||||
| *disk format says otherwise). | ||||
|  | ||||
| ### Input and output specifiers | ||||
|  | ||||
| These use a very similar syntax to the source and destination specifiers | ||||
| (because they're based on the same microformat library!) but are used for | ||||
| input and output _images_: i.e. nicely lined up arrays of sectors which you | ||||
| can actually do something with. | ||||
|  | ||||
| Use `--input` (`-i`) or `--output` (`-o`) as appropriate to tell FluxEngine | ||||
| where you want to read from or write to. The actual format is autodetected | ||||
| based on the extension: | ||||
|  | ||||
|   - `.img` or `.adf`: raw sector images in CHS order. Append | ||||
|     `:c=80:h=2:s=9:b=512` to set the geometry; that specifies 80 cylinders, 2 | ||||
|     heads, 9 sectors, 512 bytes per sector. For output files (`--output`) the | ||||
|     geometry will be autodetected if left unspecified. For input files you | ||||
|     normally have to specify it. | ||||
|  | ||||
|   - `.ldbs`: John Elliott's [LDBS disk image | ||||
|     format](http://www.seasip.info/Unix/LibDsk/ldbs.html), which is | ||||
|     consumable by the [libdsk](http://www.seasip.info/Unix/LibDsk/) suite of | ||||
|     tools. This allows things like variable numbers of sectors per track | ||||
|     (e.g. Macintosh or Commodore 64) and also provides information about | ||||
|     whether sectors were read correctly. You can use libdsk to convert this | ||||
|     to other formats, using a command like this: | ||||
|  | ||||
|     ``` | ||||
|     $ dsktrans out.ldbs -otype tele out.td0 | ||||
|     ``` | ||||
|  | ||||
|     ...to convert to TeleDisk format. (Note you have to use dsktrans rather | ||||
|     than dskconv due to a minor bug in the geometry hadnling.) | ||||
|  | ||||
|     FluxEngine's LDBS support is currently limited to write only, and | ||||
|     it doesn't store a lot of the more esoteric LDBS features like format | ||||
|     types, timings, and data rates. | ||||
|  | ||||
|   - `.d64`: the venerable Commodore 64 disk image format as used by the 1540, | ||||
|     1541, etc. This is a special-purpose format due to the weird layout of | ||||
|     1540 disks and while you can use this for non-Commodore disks the result | ||||
|     will be gibberish. Use this to image Commodore 64 disks and load the | ||||
|     result into an emulator. | ||||
|  | ||||
|     FluxEngine's D64 support is currently limited to write only. It will work | ||||
|     with up to 40 logical tracks. | ||||
|  | ||||
| ### High density disks | ||||
|  | ||||
| High density disks use a different magnetic medium to low and double density | ||||
| @@ -176,10 +221,39 @@ directory. | ||||
|   file format in a non-backwards-compatible way; this tool will upgrade flux | ||||
|   files to the new format. | ||||
|  | ||||
|   - `fluxengine convert`: converts flux files from various formats to various | ||||
|   other formats. You can use this to convert Catweasel flux files to | ||||
|   FluxEngine's native format, FluxEngine flux files to various other formats | ||||
|   useful for debugging (including VCD which can be loaded into | ||||
|   [sigrok](http://sigrok.org)), and bidirectional conversion to and from | ||||
|   Supercard Pro `.scp` format. | ||||
|  | ||||
|   **Important SCP note:** import (`fluxengine convert scptoflux`) should be | ||||
|   fairly robust, but export (`fluxengine convert fluxtoscp`) should only be | ||||
|   done with great caution as FluxEngine files contain features which can't be | ||||
|   represented very well in `.scp` format and they're probably pretty dubious. | ||||
|   As ever, please [get in | ||||
|   touch](https://github.com/davidgiven/fluxengine/issues/new) with any reports. | ||||
|  | ||||
| Commands which normally take `--source` or `--dest` get a sensible default if | ||||
| left unspecified. `fluxengine read ibm` on its own will read drive 0 and | ||||
| write an `ibm.img` file. | ||||
|  | ||||
| ## Visualisation | ||||
|  | ||||
| When doing a read (either from a real disk or from a flux file) you can use | ||||
| `--write-svg=output.svg` to write out a graphical visualisation of where the | ||||
| sectors are on the disk. Here's a IBM PC 1232kB disk: | ||||
|  | ||||
|  | ||||
|  | ||||
| Blue represents data, light blue a header, and red is a bad sector. Side zero | ||||
| is on the left and side one is on the right. | ||||
|  | ||||
| The visualiser is extremely primitive and you have to explicitly tell it how | ||||
| big your disk is, in milliseconds. The default is 200ms (for a normal 3.5" | ||||
| disk). For a 5.25" disk, use `--visualiser-period=166`. | ||||
|  | ||||
| ## Extra programs | ||||
|  | ||||
| Supplied with FluxEngine, but not part of FluxEngine, are some little tools I | ||||
| @@ -187,25 +261,24 @@ wrote to do useful things. These are built alongside FluxEngine. | ||||
|  | ||||
|   - `brother120tool`: extracts files from a 120kB Brother filesystem image. | ||||
|  | ||||
|   - `cwftoflux`: converts (one flavour of) CatWeasel flux file into a | ||||
|     FluxEngine flux file. | ||||
|  | ||||
| ## The recommended workflow | ||||
|  | ||||
| So you've just received, say, a huge pile of old Brother word processor disks containing valuable historical data, and you want to read them. | ||||
| So you've just received, say, a huge pile of old Brother word processor disks | ||||
| containing valuable historical data, and you want to read them. | ||||
|  | ||||
| Typically I do this: | ||||
|  | ||||
| ``` | ||||
| $ fluxengine read brother -s :d=0 -o brother.img --write-flux=brother.flux | ||||
| $ fluxengine read brother -s :d=0 -o brother.img --write-flux=brother.flux --write-svg=brother.svg | ||||
| ``` | ||||
|  | ||||
| This will read the disk in drive 0 and write out a filesystem image. It'll | ||||
| also copy the flux to brother.flux. If I then need to tweak the settings, I | ||||
| can rerun the decode without having to physically touch the disk like this: | ||||
| also copy the flux to brother.flux and write out an SVG visualisation. If I | ||||
| then need to tweak the settings, I can rerun the decode without having to | ||||
| physically touch the disk like this: | ||||
|  | ||||
| ``` | ||||
| $ fluxengine read brother -s brother.flux -o brother.img | ||||
| $ fluxengine read brother -s brother.flux -o brother.img --write-svg=brother.svg | ||||
| ``` | ||||
|  | ||||
| Apart from being drastically faster, this avoids touching the (potentially | ||||
|   | ||||
							
								
								
									
										1
									
								
								doc/visualiser.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								doc/visualiser.svg
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							| After Width: | Height: | Size: 394 KiB | 
							
								
								
									
										11
									
								
								lib/bytes.cc
									
									
									
									
									
								
							
							
						
						
									
										11
									
								
								lib/bytes.cc
									
									
									
									
									
								
							| @@ -2,6 +2,7 @@ | ||||
| #include "bytes.h" | ||||
| #include "fmt/format.h" | ||||
| #include "common/crunch.h" | ||||
| #include <fstream> | ||||
| #include <zlib.h> | ||||
|  | ||||
| static std::shared_ptr<std::vector<uint8_t>> createVector(unsigned size) | ||||
| @@ -280,6 +281,16 @@ Bytes Bytes::uncrunch() const | ||||
|     return output; | ||||
| } | ||||
|  | ||||
| void Bytes::writeToFile(const std::string& filename) const | ||||
| { | ||||
|     std::ofstream f(filename, std::ios::out | std::ios::binary); | ||||
|     if (!f.is_open()) | ||||
|         Error() << fmt::format("cannot open output file '{}'", filename); | ||||
|  | ||||
|     f.write((const char*) cbegin(), size()); | ||||
|     f.close(); | ||||
| } | ||||
|  | ||||
| ByteReader Bytes::reader() const | ||||
| { | ||||
|     return ByteReader(*this); | ||||
|   | ||||
| @@ -56,6 +56,8 @@ public: | ||||
|     ByteReader reader() const; | ||||
|     ByteWriter writer(); | ||||
|  | ||||
|     void writeToFile(const std::string& filename) const; | ||||
|  | ||||
| private: | ||||
|     std::shared_ptr<std::vector<uint8_t>> _data; | ||||
|     unsigned _low; | ||||
|   | ||||
| @@ -5,6 +5,11 @@ | ||||
| #include <regex> | ||||
| #include <sstream> | ||||
|  | ||||
| MissingModifierException::MissingModifierException(const std::string& mod): | ||||
|     mod(mod), | ||||
|     std::runtime_error(fmt::format("missing mandatory modifier '{}'", mod)) | ||||
| {} | ||||
|  | ||||
| std::vector<std::string> DataSpec::split( | ||||
|         const std::string& s, const std::string& delimiter) | ||||
| { | ||||
| @@ -74,31 +79,108 @@ void DataSpec::set(const std::string& spec) | ||||
|     filename = words[0]; | ||||
|     if (words.size() > 1) | ||||
|     { | ||||
|         locations.clear(); | ||||
|  | ||||
|         for (size_t i = 1; i < words.size(); i++) | ||||
|         { | ||||
|             auto mod = parseMod(words[i]); | ||||
|             if ((mod.name != "t") && (mod.name != "s") && (mod.name != "d")) | ||||
|                 Error() << fmt::format("unknown data modifier '{}'", mod.name); | ||||
|             modifiers[mod.name] = mod; | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
|         const auto& drives = modifiers["d"].data; | ||||
| const DataSpec::Modifier& DataSpec::at(const std::string& mod) const | ||||
| { | ||||
|     try | ||||
|     { | ||||
|         return modifiers.at(mod); | ||||
|     } | ||||
|     catch (const std::out_of_range& e) | ||||
|     { | ||||
|         throw MissingModifierException(mod); | ||||
|     } | ||||
| } | ||||
|  | ||||
| bool DataSpec::has(const std::string& mod) const | ||||
| { | ||||
|     return modifiers.find(mod) != modifiers.end(); | ||||
| } | ||||
|  | ||||
| FluxSpec::FluxSpec(const DataSpec& spec) | ||||
| { | ||||
|     try  | ||||
|     { | ||||
|         filename = spec.filename; | ||||
|  | ||||
|         locations.clear(); | ||||
|  | ||||
|         const auto& drives = spec.at("d").data; | ||||
|         if (drives.size() != 1) | ||||
|             Error() << "you must specify exactly one drive"; | ||||
|         drive = *drives.begin(); | ||||
|  | ||||
|         const auto& tracks = modifiers["t"].data; | ||||
|         const auto& sides = modifiers["s"].data; | ||||
|         const auto& tracks = spec.at("t").data; | ||||
|         const auto& sides = spec.at("s").data; | ||||
|         for (auto track : tracks) | ||||
|         { | ||||
|             for (auto side : sides) | ||||
|                 locations.push_back({ drive, track, side }); | ||||
|         } | ||||
|  | ||||
|         for (const auto& e : spec.modifiers) | ||||
|         { | ||||
|             const auto name = e.second.name; | ||||
|             if ((name != "t") && (name != "s") && (name != "d")) | ||||
|                 Error() << fmt::format("unknown fluxspec modifier '{}'", name); | ||||
|         } | ||||
|     } | ||||
|     catch (const MissingModifierException& e) | ||||
|     { | ||||
|         Error() << e.what() << " in fluxspec '" << spec << "'"; | ||||
|     } | ||||
| } | ||||
|  | ||||
| ImageSpec::ImageSpec(const DataSpec& spec) | ||||
| { | ||||
|     try | ||||
|     { | ||||
|         filename = spec.filename; | ||||
|  | ||||
|         if (!spec.has("c") && !spec.has("h") && !spec.has("s") && !spec.has("b")) | ||||
|         { | ||||
|             cylinders = heads = sectors = bytes = 0; | ||||
|             initialised = false; | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             cylinders = spec.at("c").only(); | ||||
|             heads = spec.at("h").only(); | ||||
|             sectors = spec.at("s").only(); | ||||
|             bytes = spec.at("b").only(); | ||||
|             initialised = true; | ||||
|         } | ||||
|     } | ||||
|     catch (const MissingModifierException& e) | ||||
|     { | ||||
|         Error() << e.what() << " in imagespec '" << spec << "'"; | ||||
|     } | ||||
|  | ||||
|     for (const auto& e : spec.modifiers) | ||||
|     { | ||||
|         const auto name = e.second.name; | ||||
|         if ((name != "c") && (name != "h") && (name != "s") && (name != "b")) | ||||
|             Error() << fmt::format("unknown fluxspec modifier '{}'", name); | ||||
|     } | ||||
| } | ||||
|  | ||||
| ImageSpec::ImageSpec(const std::string filename, | ||||
|         unsigned cylinders, unsigned heads, unsigned sectors, unsigned bytes): | ||||
|     filename(filename), | ||||
|     cylinders(cylinders), | ||||
|     heads(heads), | ||||
|     sectors(sectors), | ||||
|     bytes(bytes), | ||||
|     initialised(true) | ||||
| {} | ||||
|  | ||||
| DataSpec::operator std::string(void) const | ||||
| { | ||||
|     std::stringstream ss; | ||||
|   | ||||
| @@ -1,8 +1,57 @@ | ||||
| #ifndef DATASPEC_H | ||||
| #define DATASPEC_H | ||||
|  | ||||
| class MissingModifierException : public std::runtime_error | ||||
| { | ||||
| public: | ||||
|     MissingModifierException(const std::string& mod); | ||||
|     const std::string mod; | ||||
| }; | ||||
|  | ||||
| class DataSpec | ||||
| { | ||||
| public: | ||||
|     struct Modifier | ||||
|     { | ||||
|         std::string name; | ||||
|         std::set<unsigned> data; | ||||
|         std::string source; | ||||
|  | ||||
|         bool operator == (const Modifier& other) const | ||||
|         { return (name == other.name) && (data == other.data); } | ||||
|  | ||||
|         bool operator != (const Modifier& other) const | ||||
|         { return (name != other.name) || (data != other.data); } | ||||
|  | ||||
|         unsigned only() const | ||||
|         { | ||||
|             if (data.size() != 1) | ||||
|                 Error() << "modifier " << name << " can only have one value"; | ||||
|             return *(data.begin()); | ||||
|         } | ||||
|     }; | ||||
|  | ||||
| public: | ||||
|     static std::vector<std::string> split( | ||||
|         const std::string& s, const std::string& delimiter); | ||||
|     static Modifier parseMod(const std::string& spec); | ||||
|  | ||||
| public: | ||||
|     DataSpec(const std::string& spec) | ||||
|     { set(spec); } | ||||
|  | ||||
|     void set(const std::string& spec); | ||||
|     operator std::string () const; | ||||
|  | ||||
|     const Modifier& at(const std::string& mod) const; | ||||
|     bool has(const std::string& mod) const; | ||||
|  | ||||
|     std::string filename; | ||||
|     std::map<std::string, Modifier> modifiers; | ||||
| }; | ||||
|  | ||||
| class FluxSpec | ||||
| { | ||||
| public: | ||||
|     struct Location | ||||
|     { | ||||
| @@ -17,36 +66,29 @@ public: | ||||
|         { return (drive != other.drive) || (track != other.track) || (side != other.side); } | ||||
|     }; | ||||
|  | ||||
|     struct Modifier | ||||
|     { | ||||
|         std::string name; | ||||
|         std::set<unsigned> data; | ||||
|         std::string source; | ||||
|  | ||||
|         bool operator == (const Modifier& other) const | ||||
|         { return (name == other.name) && (data == other.data); } | ||||
|  | ||||
|         bool operator != (const Modifier& other) const | ||||
|         { return (name != other.name) || (data != other.data); } | ||||
|     }; | ||||
| public: | ||||
|     FluxSpec(const DataSpec& dataspec); | ||||
|  | ||||
| public: | ||||
|     static std::vector<std::string> split( | ||||
|         const std::string& s, const std::string& delimiter); | ||||
|     static Modifier parseMod(const std::string& spec); | ||||
|  | ||||
| public: | ||||
|     DataSpec(const std::string& spec) | ||||
|     { set(spec); } | ||||
|  | ||||
|     void set(const std::string& spec); | ||||
|     operator std::string () const; | ||||
|  | ||||
|     std::string filename; | ||||
|     std::map<std::string, Modifier> modifiers; | ||||
|     std::vector<Location> locations; | ||||
|     unsigned drive; | ||||
|     unsigned revolutions; | ||||
| }; | ||||
|  | ||||
| class ImageSpec | ||||
| { | ||||
| public: | ||||
|     ImageSpec(const DataSpec& dataspec); | ||||
|     ImageSpec(const std::string filename, | ||||
|         unsigned cylinders, unsigned heads, unsigned sectors, unsigned bytes); | ||||
|  | ||||
| public: | ||||
|     std::string filename; | ||||
|     unsigned cylinders; | ||||
|     unsigned heads; | ||||
|     unsigned sectors; | ||||
|     unsigned bytes; | ||||
|     bool initialised : 1; | ||||
| }; | ||||
|  | ||||
| static inline std::ostream& operator << (std::ostream& os, const DataSpec& dataSpec) | ||||
|   | ||||
| @@ -43,19 +43,25 @@ void AbstractDecoder::decodeToSectors(Track& track) | ||||
|  | ||||
|         recordStart = fmr.tell(); | ||||
|         decodeSectorRecord(); | ||||
|         pushRecord(recordStart, fmr.tell()); | ||||
|         Fluxmap::Position recordEnd = fmr.tell(); | ||||
|         pushRecord(recordStart, recordEnd); | ||||
|         if (sector.status == Sector::DATA_MISSING) | ||||
|         { | ||||
|             /* The data is in a separate record. */ | ||||
|  | ||||
|             sector.headerStartTime = recordStart.ns(); | ||||
|             sector.headerEndTime = recordEnd.ns(); | ||||
|             r = advanceToNextRecord(); | ||||
|             if (r == DATA_RECORD) | ||||
|             { | ||||
|                 recordStart = fmr.tell(); | ||||
|                 decodeDataRecord(); | ||||
|                 pushRecord(recordStart, fmr.tell()); | ||||
|                 recordEnd = fmr.tell(); | ||||
|                 pushRecord(recordStart, recordEnd); | ||||
|             } | ||||
|         } | ||||
|         sector.dataStartTime = recordStart.ns(); | ||||
|         sector.dataEndTime = recordEnd.ns(); | ||||
|  | ||||
|         if (sector.status != Sector::MISSING) | ||||
|             track.sectors.push_back(sector); | ||||
|   | ||||
							
								
								
									
										88
									
								
								lib/flags.cc
									
									
									
									
									
								
							
							
						
						
									
										88
									
								
								lib/flags.cc
									
									
									
									
									
								
							| @@ -29,7 +29,7 @@ void FlagGroup::addFlag(Flag* flag) | ||||
|     _flags.push_back(flag); | ||||
| } | ||||
|  | ||||
| void FlagGroup::parseFlags(int argc, const char* argv[]) | ||||
| std::vector<std::string> FlagGroup::parseFlagsWithFilenames(int argc, const char* argv[]) | ||||
| { | ||||
|     if (_initialised) | ||||
|         throw std::runtime_error("called parseFlags() twice"); | ||||
| @@ -66,6 +66,7 @@ void FlagGroup::parseFlags(int argc, const char* argv[]) | ||||
|  | ||||
|     /* Now actually parse them. */ | ||||
|  | ||||
|     std::vector<std::string> filenames; | ||||
|     int index = 1; | ||||
|     while (index < argc) | ||||
|     { | ||||
| @@ -76,52 +77,73 @@ void FlagGroup::parseFlags(int argc, const char* argv[]) | ||||
|         std::string value; | ||||
|         bool usesthat = false; | ||||
|  | ||||
|         if ((thisarg.size() == 0) || (thisarg[0] != '-')) | ||||
|             Error() << "non-option parameter " << thisarg << " seen (try --help)"; | ||||
|         if ((thisarg.size() > 1) && (thisarg[1] == '-')) | ||||
|         if (thisarg.size() == 0) | ||||
|         { | ||||
|             /* Long option. */ | ||||
|  | ||||
|             auto equals = thisarg.rfind('='); | ||||
|             if (equals != std::string::npos) | ||||
|             { | ||||
|                 key = thisarg.substr(0, equals); | ||||
|                 value = thisarg.substr(equals+1); | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 key = thisarg; | ||||
|                 value = thatarg; | ||||
|                 usesthat = true; | ||||
|             } | ||||
|             /* Ignore this argument. */ | ||||
|         } | ||||
|         else if (thisarg[0] != '-') | ||||
|         { | ||||
|             /* This is a filename. */ | ||||
|             filenames.push_back(thisarg); | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             /* Short option. */ | ||||
|             /* This is a flag. */ | ||||
|  | ||||
|             if (thisarg.size() > 2) | ||||
|             if ((thisarg.size() > 1) && (thisarg[1] == '-')) | ||||
|             { | ||||
|                 key = thisarg.substr(0, 2); | ||||
|                 value = thisarg.substr(2); | ||||
|                 /* Long option. */ | ||||
|  | ||||
|                 auto equals = thisarg.rfind('='); | ||||
|                 if (equals != std::string::npos) | ||||
|                 { | ||||
|                     key = thisarg.substr(0, equals); | ||||
|                     value = thisarg.substr(equals+1); | ||||
|                 } | ||||
|                 else | ||||
|                 { | ||||
|                     key = thisarg; | ||||
|                     value = thatarg; | ||||
|                     usesthat = true; | ||||
|                 } | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 key = thisarg; | ||||
|                 value = thatarg; | ||||
|                 usesthat = true; | ||||
|                 /* Short option. */ | ||||
|  | ||||
|                 if (thisarg.size() > 2) | ||||
|                 { | ||||
|                     key = thisarg.substr(0, 2); | ||||
|                     value = thisarg.substr(2); | ||||
|                 } | ||||
|                 else | ||||
|                 { | ||||
|                     key = thisarg; | ||||
|                     value = thatarg; | ||||
|                     usesthat = true; | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             auto flag = flags_by_name.find(key); | ||||
|             if (flag == flags_by_name.end()) | ||||
|                 Error() << "unknown flag '" << key << "'; try --help"; | ||||
|  | ||||
|             flag->second->set(value); | ||||
|             if (usesthat && flag->second->hasArgument()) | ||||
|                 index++; | ||||
|         } | ||||
|  | ||||
|         auto flag = flags_by_name.find(key); | ||||
|         if (flag == flags_by_name.end()) | ||||
|             Error() << "unknown flag '" << key << "'; try --help"; | ||||
|  | ||||
|         flag->second->set(value); | ||||
|  | ||||
|         index++; | ||||
|         if (usesthat && flag->second->hasArgument()) | ||||
|             index++; | ||||
|     } | ||||
|  | ||||
|     return filenames; | ||||
| } | ||||
|  | ||||
| void FlagGroup::parseFlags(int argc, const char* argv[]) | ||||
| { | ||||
|     auto filenames = parseFlagsWithFilenames(argc, argv); | ||||
|     if (!filenames.empty()) | ||||
|         Error() << "non-option parameter " << *filenames.begin() << " seen (try --help)"; | ||||
| } | ||||
|  | ||||
| void FlagGroup::checkInitialised() const | ||||
|   | ||||
| @@ -14,6 +14,7 @@ public: | ||||
|  | ||||
| public: | ||||
|     void parseFlags(int argc, const char* argv[]); | ||||
|     std::vector<std::string> parseFlagsWithFilenames(int argc, const char* argv[]); | ||||
|     void addFlag(Flag* flag); | ||||
|     void checkInitialised() const; | ||||
|  | ||||
|   | ||||
| @@ -10,7 +10,7 @@ static bool ends_with(const std::string& value, const std::string& ending) | ||||
|     return std::equal(ending.rbegin(), ending.rend(), value.rbegin()); | ||||
| } | ||||
|  | ||||
| std::unique_ptr<FluxSink> FluxSink::create(const DataSpec& spec) | ||||
| std::unique_ptr<FluxSink> FluxSink::create(const FluxSpec& spec) | ||||
| { | ||||
|     const auto& filename = spec.filename; | ||||
|  | ||||
|   | ||||
| @@ -2,7 +2,7 @@ | ||||
| #define FLUXSINK_H | ||||
|  | ||||
| class Fluxmap; | ||||
| class DataSpec; | ||||
| class FluxSpec; | ||||
|  | ||||
| class FluxSink | ||||
| { | ||||
| @@ -14,7 +14,7 @@ private: | ||||
|     static std::unique_ptr<FluxSink> createHardwareFluxSink(unsigned drive); | ||||
|  | ||||
| public: | ||||
|     static std::unique_ptr<FluxSink> create(const DataSpec& spec); | ||||
|     static std::unique_ptr<FluxSink> create(const FluxSpec& spec); | ||||
|  | ||||
| public: | ||||
|     virtual void writeFlux(int track, int side, Fluxmap& fluxmap) = 0; | ||||
|   | ||||
| @@ -10,7 +10,7 @@ static bool ends_with(const std::string& value, const std::string& ending) | ||||
|     return std::equal(ending.rbegin(), ending.rend(), value.rbegin()); | ||||
| } | ||||
|  | ||||
| std::unique_ptr<FluxSource> FluxSource::create(const DataSpec& spec) | ||||
| std::unique_ptr<FluxSource> FluxSource::create(const FluxSpec& spec) | ||||
| { | ||||
|     const auto& filename = spec.filename; | ||||
|  | ||||
|   | ||||
| @@ -6,7 +6,7 @@ | ||||
| extern FlagGroup hardwareFluxSourceFlags; | ||||
|  | ||||
| class Fluxmap; | ||||
| class DataSpec; | ||||
| class FluxSpec; | ||||
|  | ||||
| class FluxSource | ||||
| { | ||||
| @@ -19,7 +19,7 @@ private: | ||||
|     static std::unique_ptr<FluxSource> createStreamFluxSource(const std::string& path); | ||||
|  | ||||
| public: | ||||
|     static std::unique_ptr<FluxSource> create(const DataSpec& spec); | ||||
|     static std::unique_ptr<FluxSource> create(const FluxSpec& spec); | ||||
|  | ||||
| public: | ||||
|     virtual std::unique_ptr<Fluxmap> readFlux(int track, int side) = 0; | ||||
|   | ||||
| @@ -24,7 +24,7 @@ public: | ||||
|     void recalibrate() {} | ||||
|  | ||||
| private: | ||||
|     const std::string& _path; | ||||
|     const std::string _path; | ||||
| }; | ||||
|  | ||||
| std::unique_ptr<FluxSource> FluxSource::createStreamFluxSource(const std::string& path) | ||||
|   | ||||
| @@ -22,7 +22,7 @@ extern void hexdumpForSrp16(std::ostream& stream, const Bytes& bytes); | ||||
| class Error | ||||
| { | ||||
| public: | ||||
|     ~Error() | ||||
|     [[ noreturn ]] ~Error() | ||||
|     { | ||||
|         std::cerr << "Error: " << _stream.str() << std::endl; | ||||
|         exit(1); | ||||
|   | ||||
							
								
								
									
										147
									
								
								lib/image.cc
									
									
									
									
									
								
							
							
						
						
									
										147
									
								
								lib/image.cc
									
									
									
									
									
								
							| @@ -1,150 +1,25 @@ | ||||
| #include "globals.h" | ||||
| #include "image.h" | ||||
| #include "flags.h" | ||||
| #include "dataspec.h" | ||||
| #include "sector.h" | ||||
| #include "sectorset.h" | ||||
| #include "imagereader/imagereader.h" | ||||
| #include "imagewriter/imagewriter.h" | ||||
| #include "fmt/format.h" | ||||
| #include <algorithm> | ||||
| #include <iostream> | ||||
| #include <fstream> | ||||
|  | ||||
| Geometry guessGeometry(const SectorSet& sectors) | ||||
| SectorSet readSectorsFromFile(const ImageSpec& spec) | ||||
| { | ||||
| 	Geometry g; | ||||
| 	sectors.calculateSize(g.tracks, g.heads, g.sectors, g.sectorSize); | ||||
| 	return g; | ||||
| 	return ImageReader::create(spec)->readImage(); | ||||
| } | ||||
|  | ||||
| void readSectorsFromFile(SectorSet& sectors, const Geometry& geometry, | ||||
| 		const std::string& filename) | ||||
| void writeSectorsToFile(const SectorSet& sectors, const ImageSpec& spec) | ||||
| { | ||||
|     std::ifstream inputFile(filename, std::ios::in | std::ios::binary); | ||||
|     if (!inputFile.is_open()) | ||||
| 		Error() << "cannot open input file"; | ||||
|  | ||||
|     size_t headSize = geometry.sectors * geometry.sectorSize; | ||||
|     size_t trackSize = headSize * geometry.heads; | ||||
|  | ||||
|     std::cout << fmt::format("{} tracks, {} heads, {} sectors, {} bytes per sector, {} kB total", | ||||
| 					geometry.tracks, geometry.heads, | ||||
| 					geometry.sectors, geometry.sectorSize, | ||||
| 					geometry.tracks * trackSize / 1024) | ||||
| 			  << std::endl; | ||||
|  | ||||
| 	for (int track = 0; track < geometry.tracks; track++) | ||||
| 	{ | ||||
| 		for (int head = 0; head < geometry.heads; head++) | ||||
| 		{ | ||||
| 			for (int sectorId = 0; sectorId < geometry.sectors; sectorId++) | ||||
| 			{ | ||||
| 				inputFile.seekg(track*trackSize + head*headSize + sectorId*geometry.sectorSize, std::ios::beg); | ||||
|  | ||||
| 				Bytes data(geometry.sectorSize); | ||||
| 				inputFile.read((char*) data.begin(), geometry.sectorSize); | ||||
|  | ||||
| 				std::unique_ptr<Sector>& sector = sectors.get(track, head, sectorId); | ||||
| 				sector.reset(new Sector); | ||||
| 				sector->status = Sector::OK; | ||||
| 				sector->logicalTrack = sector->physicalTrack = track; | ||||
| 				sector->logicalSide = sector->physicalSide = head; | ||||
| 				sector->logicalSector = sectorId; | ||||
| 				sector->data = data; | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| void writeSectorsToFile(const SectorSet& sectors, const Geometry& geometry, | ||||
| 		const std::string& filename) | ||||
| { | ||||
| 	/* Emit the map. */ | ||||
|  | ||||
| 	int badSectors = 0; | ||||
| 	int missingSectors = 0; | ||||
| 	int totalSectors = 0; | ||||
| 	std::cout << "H.SS Tracks --->" << std::endl; | ||||
| 	for (int head = 0; head < geometry.heads; head++) | ||||
| 	{ | ||||
| 		for (int sectorId = 0; sectorId < geometry.sectors; sectorId++) | ||||
| 		{ | ||||
| 			std::cout << fmt::format("{}.{:2} ", head, sectorId); | ||||
| 			for (int track = 0; track < geometry.tracks; track++) | ||||
| 			{ | ||||
| 				Sector* sector = sectors.get(track, head, sectorId); | ||||
| 				if (!sector) | ||||
| 				{ | ||||
| 					std::cout << 'X'; | ||||
| 					missingSectors++; | ||||
| 				} | ||||
| 				else | ||||
| 				{ | ||||
| 					switch (sector->status) | ||||
| 					{ | ||||
| 						case Sector::OK: | ||||
|                             std::cout << '.'; | ||||
|                             break; | ||||
|  | ||||
|                         case Sector::BAD_CHECKSUM: | ||||
|                             std::cout << 'B'; | ||||
|                             badSectors++; | ||||
|                             break; | ||||
|  | ||||
|                         case Sector::CONFLICT: | ||||
|                             std::cout << 'C'; | ||||
|                             badSectors++; | ||||
|                             break; | ||||
|  | ||||
|                         default: | ||||
|                             std::cout << '?'; | ||||
|                             break; | ||||
|                     } | ||||
| 				} | ||||
| 				totalSectors++; | ||||
| 			} | ||||
| 			std::cout << std::endl; | ||||
| 		} | ||||
| 	} | ||||
| 	int goodSectors = totalSectors - missingSectors - badSectors; | ||||
| 	if (totalSectors == 0) | ||||
| 		std::cout << "No sectors in output; skipping analysis" << std::endl; | ||||
| 	else | ||||
| 	{ | ||||
| 		std::cout << "Good sectors: " << goodSectors << "/" << totalSectors | ||||
| 				  << " (" << (100*goodSectors/totalSectors) << "%)" | ||||
| 				  << std::endl; | ||||
| 		std::cout << "Missing sectors: " << missingSectors << "/" << totalSectors | ||||
| 				  << " (" << (100*missingSectors/totalSectors) << "%)" | ||||
| 				  << std::endl; | ||||
| 		std::cout << "Bad sectors: " << badSectors << "/" << totalSectors | ||||
| 				  << " (" << (100*badSectors/totalSectors) << "%)" | ||||
| 				  << std::endl; | ||||
|     } | ||||
|  | ||||
|     size_t headSize = geometry.sectors * geometry.sectorSize; | ||||
|     size_t trackSize = headSize * geometry.heads; | ||||
|  | ||||
|     std::cout << fmt::format("{} tracks, {} heads, {} sectors, {} bytes per sector, {} kB total", | ||||
| 					geometry.tracks, geometry.heads, | ||||
| 					geometry.sectors, geometry.sectorSize, | ||||
| 					geometry.tracks * trackSize / 1024) | ||||
| 			  << std::endl; | ||||
|  | ||||
|     std::ofstream outputFile(filename, std::ios::out | std::ios::binary); | ||||
|     if (!outputFile.is_open()) | ||||
| 		Error() << "cannot open output file"; | ||||
|  | ||||
| 	for (int track = 0; track < geometry.tracks; track++) | ||||
| 	{ | ||||
| 		for (int head = 0; head < geometry.heads; head++) | ||||
| 		{ | ||||
| 			for (int sectorId = 0; sectorId < geometry.sectors; sectorId++) | ||||
| 			{ | ||||
| 				auto sector = sectors.get(track, head, sectorId); | ||||
| 				if (sector) | ||||
| 				{ | ||||
| 					outputFile.seekp(sector->logicalTrack*trackSize + sector->logicalSide*headSize + sector->logicalSector*geometry.sectorSize, std::ios::beg); | ||||
| 					outputFile.write((const char*) sector->data.cbegin(), sector->data.size()); | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	std::unique_ptr<ImageWriter> writer(ImageWriter::create(sectors, spec)); | ||||
| 	writer->adjustGeometry(); | ||||
| 	writer->printMap(); | ||||
| 	writer->writeImage(); | ||||
| } | ||||
|   | ||||
							
								
								
									
										21
									
								
								lib/image.h
									
									
									
									
									
								
							
							
						
						
									
										21
									
								
								lib/image.h
									
									
									
									
									
								
							| @@ -2,26 +2,13 @@ | ||||
| #define IMAGE_H | ||||
|  | ||||
| class SectorSet; | ||||
| class ImageSpec; | ||||
|  | ||||
| class Geometry | ||||
| { | ||||
| public: | ||||
| 	int tracks; | ||||
| 	int heads; | ||||
| 	int sectors; | ||||
| 	int sectorSize; | ||||
| }; | ||||
|  | ||||
| extern Geometry guessGeometry(const SectorSet& sectors); | ||||
|  | ||||
| extern void readSectorsFromFile( | ||||
| 	SectorSet& sectors, | ||||
| 	const Geometry& geometry, | ||||
| 	const std::string& filename); | ||||
| extern SectorSet readSectorsFromFile( | ||||
| 	const ImageSpec& filename); | ||||
|  | ||||
| extern void writeSectorsToFile( | ||||
| 	const SectorSet& sectors, | ||||
| 	const Geometry& geometry, | ||||
| 	const std::string& filename); | ||||
| 	const ImageSpec& filename); | ||||
|  | ||||
| #endif | ||||
|   | ||||
							
								
								
									
										52
									
								
								lib/imagereader/imagereader.cc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								lib/imagereader/imagereader.cc
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,52 @@ | ||||
| #include "globals.h" | ||||
| #include "image.h" | ||||
| #include "flags.h" | ||||
| #include "dataspec.h" | ||||
| #include "sector.h" | ||||
| #include "sectorset.h" | ||||
| #include "imagereader/imagereader.h" | ||||
| #include "fmt/format.h" | ||||
|  | ||||
| std::map<std::string, ImageReader::Constructor> ImageReader::formats = | ||||
| { | ||||
| 	{".adf", ImageReader::createImgImageReader}, | ||||
| 	{".d81", ImageReader::createImgImageReader}, | ||||
| 	{".img", ImageReader::createImgImageReader}, | ||||
| }; | ||||
|  | ||||
| static bool ends_with(const std::string& value, const std::string& ending) | ||||
| { | ||||
|     if (ending.size() > value.size()) | ||||
|         return false; | ||||
|     return std::equal(ending.rbegin(), ending.rend(), value.rbegin()); | ||||
| } | ||||
|  | ||||
| ImageReader::Constructor ImageReader::findConstructor(const ImageSpec& spec) | ||||
| { | ||||
|     const auto& filename = spec.filename; | ||||
|  | ||||
| 	for (const auto& e : formats) | ||||
| 	{ | ||||
| 		if (ends_with(filename, e.first)) | ||||
| 			return e.second; | ||||
| 	} | ||||
|  | ||||
| 	return NULL; | ||||
| } | ||||
|  | ||||
| std::unique_ptr<ImageReader> ImageReader::create(const ImageSpec& spec) | ||||
| { | ||||
|     verifyImageSpec(spec); | ||||
|     return findConstructor(spec)(spec); | ||||
| } | ||||
|  | ||||
| void ImageReader::verifyImageSpec(const ImageSpec& spec) | ||||
| { | ||||
|     if (!findConstructor(spec)) | ||||
|         Error() << "unrecognised image filename extension"; | ||||
| } | ||||
|  | ||||
| ImageReader::ImageReader(const ImageSpec& spec): | ||||
|     spec(spec) | ||||
| {} | ||||
|  | ||||
							
								
								
									
										38
									
								
								lib/imagereader/imagereader.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								lib/imagereader/imagereader.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,38 @@ | ||||
| #ifndef IMAGEREADER_H | ||||
| #define IMAGEREADER_H | ||||
|  | ||||
| class SectorSet; | ||||
| class ImageSpec; | ||||
|  | ||||
| class ImageReader | ||||
| { | ||||
| public: | ||||
| 	ImageReader(const ImageSpec& spec); | ||||
| 	virtual ~ImageReader() {}; | ||||
|  | ||||
| public: | ||||
|     static std::unique_ptr<ImageReader> create(const ImageSpec& spec); | ||||
| 	static void verifyImageSpec(const ImageSpec& spec); | ||||
|  | ||||
| private: | ||||
| 	typedef  | ||||
| 		std::function< | ||||
| 			std::unique_ptr<ImageReader>(const ImageSpec& spec) | ||||
| 		> | ||||
| 		Constructor; | ||||
|  | ||||
| 	static std::map<std::string, Constructor> formats; | ||||
|  | ||||
|     static std::unique_ptr<ImageReader> createImgImageReader(const ImageSpec& spec); | ||||
|  | ||||
| 	static Constructor findConstructor(const ImageSpec& spec); | ||||
|  | ||||
| public: | ||||
| 	virtual SectorSet readImage() = 0; | ||||
|  | ||||
| protected: | ||||
| 	ImageSpec spec; | ||||
| }; | ||||
|  | ||||
| #endif | ||||
|  | ||||
							
								
								
									
										66
									
								
								lib/imagereader/imgimagereader.cc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										66
									
								
								lib/imagereader/imgimagereader.cc
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,66 @@ | ||||
| #include "globals.h" | ||||
| #include "image.h" | ||||
| #include "flags.h" | ||||
| #include "dataspec.h" | ||||
| #include "sector.h" | ||||
| #include "sectorset.h" | ||||
| #include "imagereader/imagereader.h" | ||||
| #include "fmt/format.h" | ||||
| #include <algorithm> | ||||
| #include <iostream> | ||||
| #include <fstream> | ||||
|  | ||||
| class ImgImageReader : public ImageReader | ||||
| { | ||||
| public: | ||||
| 	ImgImageReader(const ImageSpec& spec): | ||||
| 		ImageReader(spec) | ||||
| 	{} | ||||
|  | ||||
| 	SectorSet readImage() | ||||
| 	{ | ||||
|         std::ifstream inputFile(spec.filename, std::ios::in | std::ios::binary); | ||||
|         if (!inputFile.is_open()) | ||||
|             Error() << "cannot open input file"; | ||||
|  | ||||
|         size_t headSize = spec.sectors * spec.bytes; | ||||
|         size_t trackSize = headSize * spec.heads; | ||||
|  | ||||
|         std::cout << fmt::format("reading {} tracks, {} heads, {} sectors, {} bytes per sector, {} kB total", | ||||
|                         spec.cylinders, spec.heads, | ||||
|                         spec.sectors, spec.bytes, | ||||
|                         spec.cylinders * trackSize / 1024) | ||||
|                 << std::endl; | ||||
|  | ||||
|         SectorSet sectors; | ||||
|         for (int track = 0; track < spec.cylinders; track++) | ||||
|         { | ||||
|             for (int head = 0; head < spec.heads; head++) | ||||
|             { | ||||
|                 for (int sectorId = 0; sectorId < spec.sectors; sectorId++) | ||||
|                 { | ||||
|                     inputFile.seekg(track*trackSize + head*headSize + sectorId*spec.bytes, std::ios::beg); | ||||
|  | ||||
|                     Bytes data(spec.bytes); | ||||
|                     inputFile.read((char*) data.begin(), spec.bytes); | ||||
|  | ||||
|                     std::unique_ptr<Sector>& sector = sectors.get(track, head, sectorId); | ||||
|                     sector.reset(new Sector); | ||||
|                     sector->status = Sector::OK; | ||||
|                     sector->logicalTrack = sector->physicalTrack = track; | ||||
|                     sector->logicalSide = sector->physicalSide = head; | ||||
|                     sector->logicalSector = sectorId; | ||||
|                     sector->data = data; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         return sectors; | ||||
| 	} | ||||
| }; | ||||
|  | ||||
| std::unique_ptr<ImageReader> ImageReader::createImgImageReader( | ||||
| 	const ImageSpec& spec) | ||||
| { | ||||
|     return std::unique_ptr<ImageReader>(new ImgImageReader(spec)); | ||||
| } | ||||
|  | ||||
							
								
								
									
										64
									
								
								lib/imagewriter/d64imagewriter.cc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										64
									
								
								lib/imagewriter/d64imagewriter.cc
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,64 @@ | ||||
| #include "globals.h" | ||||
| #include "image.h" | ||||
| #include "flags.h" | ||||
| #include "dataspec.h" | ||||
| #include "sector.h" | ||||
| #include "sectorset.h" | ||||
| #include "imagewriter/imagewriter.h" | ||||
| #include "fmt/format.h" | ||||
| #include "ldbs.h" | ||||
| #include <algorithm> | ||||
| #include <iostream> | ||||
| #include <fstream> | ||||
|  | ||||
| static int sectors_per_track(int track) | ||||
| { | ||||
|     if (track < 17) | ||||
|         return 21; | ||||
|     if (track < 24) | ||||
|         return 19; | ||||
|     if (track < 30) | ||||
|         return 18; | ||||
|     return 17; | ||||
| } | ||||
|  | ||||
| class D64ImageWriter : public ImageWriter | ||||
| { | ||||
| public: | ||||
| 	D64ImageWriter(const SectorSet& sectors, const ImageSpec& spec): | ||||
| 		ImageWriter(sectors, spec) | ||||
| 	{} | ||||
|  | ||||
| 	void writeImage() | ||||
| 	{ | ||||
| 		std::cout << "writing D64 triangular image\n"; | ||||
|  | ||||
| 		std::ofstream outputFile(spec.filename, std::ios::out | std::ios::binary); | ||||
| 		if (!outputFile.is_open()) | ||||
| 			Error() << "cannot open output file"; | ||||
|  | ||||
|         uint32_t offset = 0; | ||||
| 		for (int track = 0; track < 40; track++) | ||||
| 		{ | ||||
|             int sectorCount = sectors_per_track(track); | ||||
|             for (int sectorId = 0; sectorId < sectorCount; sectorId++) | ||||
|             { | ||||
|                 const auto& sector = sectors.get(track, 0, sectorId); | ||||
|                 if (sector) | ||||
|                 { | ||||
|                     outputFile.seekp(offset); | ||||
|                     outputFile.write((const char*) sector->data.cbegin(), 256); | ||||
|                 } | ||||
|  | ||||
|                 offset += 256; | ||||
|             } | ||||
| 		} | ||||
|     } | ||||
| }; | ||||
|  | ||||
| std::unique_ptr<ImageWriter> ImageWriter::createD64ImageWriter( | ||||
| 	const SectorSet& sectors, const ImageSpec& spec) | ||||
| { | ||||
|     return std::unique_ptr<ImageWriter>(new D64ImageWriter(sectors, spec)); | ||||
| } | ||||
|  | ||||
							
								
								
									
										128
									
								
								lib/imagewriter/imagewriter.cc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										128
									
								
								lib/imagewriter/imagewriter.cc
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,128 @@ | ||||
| #include "globals.h" | ||||
| #include "image.h" | ||||
| #include "flags.h" | ||||
| #include "dataspec.h" | ||||
| #include "sector.h" | ||||
| #include "sectorset.h" | ||||
| #include "imagewriter/imagewriter.h" | ||||
| #include "fmt/format.h" | ||||
|  | ||||
| std::map<std::string, ImageWriter::Constructor> ImageWriter::formats = | ||||
| { | ||||
| 	{".adf", ImageWriter::createImgImageWriter}, | ||||
| 	{".d64", ImageWriter::createD64ImageWriter}, | ||||
| 	{".d81", ImageWriter::createImgImageWriter}, | ||||
| 	{".img", ImageWriter::createImgImageWriter}, | ||||
| 	{".ldbs", ImageWriter::createLDBSImageWriter}, | ||||
| }; | ||||
|  | ||||
| static bool ends_with(const std::string& value, const std::string& ending) | ||||
| { | ||||
|     if (ending.size() > value.size()) | ||||
|         return false; | ||||
|     return std::equal(ending.rbegin(), ending.rend(), value.rbegin()); | ||||
| } | ||||
|  | ||||
| ImageWriter::Constructor ImageWriter::findConstructor(const ImageSpec& spec) | ||||
| { | ||||
|     const auto& filename = spec.filename; | ||||
|  | ||||
| 	for (const auto& e : formats) | ||||
| 	{ | ||||
| 		if (ends_with(filename, e.first)) | ||||
| 			return e.second; | ||||
| 	} | ||||
|  | ||||
| 	return NULL; | ||||
| } | ||||
|  | ||||
| std::unique_ptr<ImageWriter> ImageWriter::create(const SectorSet& sectors, const ImageSpec& spec) | ||||
| { | ||||
| 	verifyImageSpec(spec); | ||||
| 	return findConstructor(spec)(sectors, spec); | ||||
| } | ||||
|  | ||||
| void ImageWriter::verifyImageSpec(const ImageSpec& spec) | ||||
| { | ||||
| 	if (!findConstructor(spec)) | ||||
| 		Error() << "unrecognised image filename extension"; | ||||
| } | ||||
|  | ||||
| ImageWriter::ImageWriter(const SectorSet& sectors, const ImageSpec& spec): | ||||
|     sectors(sectors), | ||||
|     spec(spec) | ||||
| {} | ||||
|  | ||||
| void ImageWriter::adjustGeometry() | ||||
| { | ||||
| 	if (!spec.initialised) | ||||
| 	{ | ||||
| 		sectors.calculateSize(spec.cylinders, spec.heads, spec.sectors, spec.bytes); | ||||
|         spec.initialised = true; | ||||
| 		std::cout << "Autodetecting output geometry\n"; | ||||
| 	} | ||||
| } | ||||
|  | ||||
| void ImageWriter::printMap() | ||||
| { | ||||
| 	int badSectors = 0; | ||||
| 	int missingSectors = 0; | ||||
| 	int totalSectors = 0; | ||||
| 	std::cout << "H.SS Tracks --->" << std::endl; | ||||
| 	for (int head = 0; head < spec.heads; head++) | ||||
| 	{ | ||||
| 		for (int sectorId = 0; sectorId < spec.sectors; sectorId++) | ||||
| 		{ | ||||
| 			std::cout << fmt::format("{}.{:2} ", head, sectorId); | ||||
| 			for (int track = 0; track < spec.cylinders; track++) | ||||
| 			{ | ||||
| 				const auto& sector = sectors.get(track, head, sectorId); | ||||
| 				if (!sector) | ||||
| 				{ | ||||
| 					std::cout << 'X'; | ||||
| 					missingSectors++; | ||||
| 				} | ||||
| 				else | ||||
| 				{ | ||||
| 					switch (sector->status) | ||||
| 					{ | ||||
| 						case Sector::OK: | ||||
|                             std::cout << '.'; | ||||
|                             break; | ||||
|  | ||||
|                         case Sector::BAD_CHECKSUM: | ||||
|                             std::cout << 'B'; | ||||
|                             badSectors++; | ||||
|                             break; | ||||
|  | ||||
|                         case Sector::CONFLICT: | ||||
|                             std::cout << 'C'; | ||||
|                             badSectors++; | ||||
|                             break; | ||||
|  | ||||
|                         default: | ||||
|                             std::cout << '?'; | ||||
|                             break; | ||||
|                     } | ||||
| 				} | ||||
| 				totalSectors++; | ||||
| 			} | ||||
| 			std::cout << std::endl; | ||||
| 		} | ||||
| 	} | ||||
| 	int goodSectors = totalSectors - missingSectors - badSectors; | ||||
| 	if (totalSectors == 0) | ||||
| 		std::cout << "No sectors in output; skipping analysis" << std::endl; | ||||
| 	else | ||||
| 	{ | ||||
| 		std::cout << "Good sectors: " << goodSectors << "/" << totalSectors | ||||
| 				  << " (" << (100*goodSectors/totalSectors) << "%)" | ||||
| 				  << std::endl; | ||||
| 		std::cout << "Missing sectors: " << missingSectors << "/" << totalSectors | ||||
| 				  << " (" << (100*missingSectors/totalSectors) << "%)" | ||||
| 				  << std::endl; | ||||
| 		std::cout << "Bad sectors: " << badSectors << "/" << totalSectors | ||||
| 				  << " (" << (100*badSectors/totalSectors) << "%)" | ||||
| 				  << std::endl; | ||||
|     } | ||||
| } | ||||
							
								
								
									
										45
									
								
								lib/imagewriter/imagewriter.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								lib/imagewriter/imagewriter.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,45 @@ | ||||
| #ifndef IMAGEWRITER_H | ||||
| #define IMAGEWRITER_H | ||||
|  | ||||
| class SectorSet; | ||||
| class ImageSpec; | ||||
|  | ||||
| class ImageWriter | ||||
| { | ||||
| public: | ||||
| 	ImageWriter(const SectorSet& sectors, const ImageSpec& spec); | ||||
| 	virtual ~ImageWriter() {}; | ||||
|  | ||||
| public: | ||||
|     static std::unique_ptr<ImageWriter> create(const SectorSet& sectors, const ImageSpec& spec); | ||||
| 	static void verifyImageSpec(const ImageSpec& filename); | ||||
|  | ||||
| private: | ||||
| 	typedef  | ||||
| 		std::function< | ||||
| 			std::unique_ptr<ImageWriter>(const SectorSet& sectors, const ImageSpec& spec) | ||||
| 		> | ||||
| 		Constructor; | ||||
|  | ||||
| 	static std::map<std::string, Constructor> formats; | ||||
|  | ||||
|     static std::unique_ptr<ImageWriter> createImgImageWriter( | ||||
| 		const SectorSet& sectors, const ImageSpec& spec); | ||||
|     static std::unique_ptr<ImageWriter> createLDBSImageWriter( | ||||
| 		const SectorSet& sectors, const ImageSpec& spec); | ||||
|     static std::unique_ptr<ImageWriter> createD64ImageWriter( | ||||
| 		const SectorSet& sectors, const ImageSpec& spec); | ||||
|  | ||||
| 	static Constructor findConstructor(const ImageSpec& spec); | ||||
|  | ||||
| public: | ||||
| 	virtual void adjustGeometry(); | ||||
| 	void printMap(); | ||||
| 	virtual void writeImage() = 0; | ||||
|  | ||||
| protected: | ||||
| 	const SectorSet& sectors; | ||||
| 	ImageSpec spec; | ||||
| }; | ||||
|  | ||||
| #endif | ||||
							
								
								
									
										62
									
								
								lib/imagewriter/imgimagewriter.cc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										62
									
								
								lib/imagewriter/imgimagewriter.cc
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,62 @@ | ||||
| #include "globals.h" | ||||
| #include "image.h" | ||||
| #include "flags.h" | ||||
| #include "dataspec.h" | ||||
| #include "sector.h" | ||||
| #include "sectorset.h" | ||||
| #include "imagewriter/imagewriter.h" | ||||
| #include "fmt/format.h" | ||||
| #include <algorithm> | ||||
| #include <iostream> | ||||
| #include <fstream> | ||||
|  | ||||
| class ImgImageWriter : public ImageWriter | ||||
| { | ||||
| public: | ||||
| 	ImgImageWriter(const SectorSet& sectors, const ImageSpec& spec): | ||||
| 		ImageWriter(sectors, spec) | ||||
| 	{} | ||||
|  | ||||
| 	void writeImage() | ||||
| 	{ | ||||
| 		unsigned numCylinders = spec.cylinders; | ||||
| 		unsigned numHeads = spec.heads; | ||||
| 		unsigned numSectors = spec.sectors; | ||||
| 		unsigned numBytes = spec.bytes; | ||||
|  | ||||
| 		size_t headSize = numSectors * numBytes; | ||||
| 		size_t trackSize = headSize * numHeads; | ||||
|  | ||||
| 		std::cout << fmt::format("writing {} tracks, {} heads, {} sectors, {} bytes per sector, {} kB total", | ||||
| 						numCylinders, numHeads, | ||||
| 						numSectors, numBytes, | ||||
| 						numCylinders * trackSize / 1024) | ||||
| 				<< std::endl; | ||||
|  | ||||
| 		std::ofstream outputFile(spec.filename, std::ios::out | std::ios::binary); | ||||
| 		if (!outputFile.is_open()) | ||||
| 			Error() << "cannot open output file"; | ||||
|  | ||||
| 		for (int track = 0; track < numCylinders; track++) | ||||
| 		{ | ||||
| 			for (int head = 0; head < numHeads; head++) | ||||
| 			{ | ||||
| 				for (int sectorId = 0; sectorId < numSectors; sectorId++) | ||||
| 				{ | ||||
| 					const auto& sector = sectors.get(track, head, sectorId); | ||||
| 					if (sector) | ||||
| 					{ | ||||
| 						outputFile.seekp(sector->logicalTrack*trackSize + sector->logicalSide*headSize + sector->logicalSector*numBytes, std::ios::beg); | ||||
| 						outputFile.write((const char*) sector->data.cbegin(), sector->data.size()); | ||||
| 					} | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| }; | ||||
|  | ||||
| std::unique_ptr<ImageWriter> ImageWriter::createImgImageWriter( | ||||
| 	const SectorSet& sectors, const ImageSpec& spec) | ||||
| { | ||||
|     return std::unique_ptr<ImageWriter>(new ImgImageWriter(sectors, spec)); | ||||
| } | ||||
							
								
								
									
										107
									
								
								lib/imagewriter/ldbsimagewriter.cc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										107
									
								
								lib/imagewriter/ldbsimagewriter.cc
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,107 @@ | ||||
| #include "globals.h" | ||||
| #include "image.h" | ||||
| #include "flags.h" | ||||
| #include "dataspec.h" | ||||
| #include "sector.h" | ||||
| #include "sectorset.h" | ||||
| #include "imagewriter/imagewriter.h" | ||||
| #include "fmt/format.h" | ||||
| #include "ldbs.h" | ||||
| #include <algorithm> | ||||
| #include <iostream> | ||||
| #include <fstream> | ||||
|  | ||||
| class LDBSImageWriter : public ImageWriter | ||||
| { | ||||
| public: | ||||
| 	LDBSImageWriter(const SectorSet& sectors, const ImageSpec& spec): | ||||
| 		ImageWriter(sectors, spec) | ||||
| 	{} | ||||
|  | ||||
| 	void writeImage() | ||||
| 	{ | ||||
|         LDBS ldbs; | ||||
|  | ||||
| 		unsigned numCylinders = spec.cylinders; | ||||
| 		unsigned numHeads = spec.heads; | ||||
| 		unsigned numSectors = spec.sectors; | ||||
| 		unsigned numBytes = spec.bytes; | ||||
| 		std::cout << fmt::format("writing {} tracks, {} heads, {} sectors, {} bytes per sector", | ||||
| 						numCylinders, numHeads, | ||||
| 						numSectors, numBytes) | ||||
| 				<< std::endl; | ||||
|  | ||||
|         Bytes trackDirectory; | ||||
|         ByteWriter trackDirectoryWriter(trackDirectory); | ||||
|         int trackDirectorySize = 0; | ||||
|         trackDirectoryWriter.write_le16(0); | ||||
|  | ||||
| 		for (int track = 0; track < numCylinders; track++) | ||||
| 		{ | ||||
| 			for (int head = 0; head < numHeads; head++) | ||||
| 			{ | ||||
|                 Bytes trackHeader; | ||||
|                 ByteWriter trackHeaderWriter(trackHeader); | ||||
|  | ||||
|                 int actualSectors = 0; | ||||
| 				for (int sectorId = 0; sectorId < numSectors; sectorId++) | ||||
| 				{ | ||||
| 					const auto& sector = sectors.get(track, head, sectorId); | ||||
| 					if (sector) | ||||
|                         actualSectors++; | ||||
|                 } | ||||
|  | ||||
|                 trackHeaderWriter.write_le16(0x000C); /* offset of sector headers */ | ||||
|                 trackHeaderWriter.write_le16(0x0012); /* length of each sector descriptor */ | ||||
|                 trackHeaderWriter.write_le16(actualSectors); | ||||
|                 trackHeaderWriter.write_8(0); /* data rate unknown */ | ||||
|                 trackHeaderWriter.write_8(0); /* recording mode unknown */ | ||||
|                 trackHeaderWriter.write_8(0); /* format gap length */ | ||||
|                 trackHeaderWriter.write_8(0); /* filler byte */ | ||||
|                 trackHeaderWriter.write_le16(0); /* approximate track length */ | ||||
|  | ||||
| 				for (int sectorId = 0; sectorId < numSectors; sectorId++) | ||||
| 				{ | ||||
| 					const auto& sector = sectors.get(track, head, sectorId); | ||||
| 					if (sector) | ||||
| 					{ | ||||
|                         uint32_t sectorLabel = (('S') << 24) | ((track & 0xff) << 16) | (head << 8) | sectorId; | ||||
|                         uint32_t sectorAddress = ldbs.put(sector->data, sectorLabel); | ||||
|  | ||||
|                         trackHeaderWriter.write_8(track); | ||||
|                         trackHeaderWriter.write_8(head); | ||||
|                         trackHeaderWriter.write_8(sectorId); | ||||
|                         trackHeaderWriter.write_8(0); /* power-of-two size */ | ||||
|                         trackHeaderWriter.write_8((sector->status == Sector::OK) ? 0x00 : 0x20); /* 8272 status 1 */ | ||||
|                         trackHeaderWriter.write_8(0); /* 8272 status 2 */ | ||||
|                         trackHeaderWriter.write_8(1); /* number of copies */ | ||||
|                         trackHeaderWriter.write_8(0); /* filler byte */ | ||||
|                         trackHeaderWriter.write_le32(sectorAddress); | ||||
|                         trackHeaderWriter.write_le16(0); /* trailing bytes */ | ||||
|                         trackHeaderWriter.write_le16(0); /* approximate offset */ | ||||
|                         trackHeaderWriter.write_le16(sector->data.size()); | ||||
| 					} | ||||
| 				} | ||||
|  | ||||
|                 uint32_t trackLabel = (('T') << 24) | ((track & 0xff) << 16) | ((track >> 8) << 8) | head; | ||||
|                 uint32_t trackHeaderAddress = ldbs.put(trackHeader, trackLabel); | ||||
|                 trackDirectoryWriter.write_be32(trackLabel); | ||||
|                 trackDirectoryWriter.write_le32(trackHeaderAddress); | ||||
|                 trackDirectorySize++; | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
|         trackDirectoryWriter.seek(0); | ||||
|         trackDirectoryWriter.write_le16(trackDirectorySize); | ||||
|  | ||||
|         uint32_t trackDirectoryAddress = ldbs.put(trackDirectory, LDBS_TRACK_BLOCK); | ||||
|         Bytes data = ldbs.write(trackDirectoryAddress); | ||||
|         data.writeToFile(spec.filename); | ||||
|     } | ||||
| }; | ||||
|  | ||||
| std::unique_ptr<ImageWriter> ImageWriter::createLDBSImageWriter( | ||||
| 	const SectorSet& sectors, const ImageSpec& spec) | ||||
| { | ||||
|     return std::unique_ptr<ImageWriter>(new LDBSImageWriter(sectors, spec)); | ||||
| } | ||||
							
								
								
									
										81
									
								
								lib/ldbs.cc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										81
									
								
								lib/ldbs.cc
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,81 @@ | ||||
| #include "globals.h" | ||||
| #include <string.h> | ||||
| #include "bytes.h" | ||||
| #include "ldbs.h" | ||||
| #include "fmt/format.h" | ||||
|  | ||||
| LDBS::LDBS() | ||||
| {} | ||||
|  | ||||
| uint32_t LDBS::put(const Bytes& data, uint32_t type) | ||||
| { | ||||
|     uint32_t address = top; | ||||
|     Block& block = blocks[address]; | ||||
|     block.type = type; | ||||
|     block.data = data; | ||||
|  | ||||
|     top += data.size() + 20; | ||||
|     return address; | ||||
| } | ||||
|  | ||||
| uint32_t LDBS::read(const Bytes& data) | ||||
| { | ||||
|     ByteReader br(data); | ||||
|  | ||||
|     br.seek(0); | ||||
|     if ((br.read_be32() != LDBS_FILE_MAGIC) | ||||
|         || (br.read_be32() != LDBS_FILE_TYPE)) | ||||
|         Error() << "not a valid LDBS file"; | ||||
|  | ||||
|     uint32_t address = br.read_le32(); | ||||
|     br.skip(4); | ||||
|     uint32_t trackDirectory = br.read_le32(); | ||||
|  | ||||
|     while (address) | ||||
|     { | ||||
|         br.seek(address); | ||||
|         if (br.read_be32() != LDBS_BLOCK_MAGIC) | ||||
|             Error() << fmt::format("invalid block at address 0x{:x}", address); | ||||
|  | ||||
|         Block& block = blocks[address]; | ||||
|         block.type = br.read_be32(); | ||||
|  | ||||
|         uint32_t size = br.read_le32(); | ||||
|         br.skip(4); | ||||
|         address = br.read_le32(); | ||||
|  | ||||
|         block.data.writer().append(br.read(size)); | ||||
|     } | ||||
|          | ||||
|     top = data.size(); | ||||
|     return trackDirectory; | ||||
| } | ||||
|  | ||||
| const Bytes LDBS::write(uint32_t trackDirectory) | ||||
| { | ||||
|     Bytes data(top); | ||||
|     ByteWriter bw(data); | ||||
|  | ||||
|     uint32_t previous = 0; | ||||
|     for (const auto& e : blocks) | ||||
|     { | ||||
|         bw.seek(e.first); | ||||
|         bw.write_be32(LDBS_BLOCK_MAGIC); | ||||
|         bw.write_be32(e.second.type); | ||||
|         bw.write_le32(e.second.data.size()); | ||||
|         bw.write_le32(e.second.data.size()); | ||||
|         bw.write_le32(previous); | ||||
|         bw.append(e.second.data); | ||||
|  | ||||
|         previous = e.first; | ||||
|     } | ||||
|  | ||||
|     bw.seek(0); | ||||
|     bw.write_be32(LDBS_FILE_MAGIC); | ||||
|     bw.write_be32(LDBS_FILE_TYPE); | ||||
|     bw.write_le32(previous); | ||||
|     bw.write_le32(0); | ||||
|     bw.write_le32(trackDirectory); | ||||
|      | ||||
|     return data; | ||||
| } | ||||
							
								
								
									
										41
									
								
								lib/ldbs.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								lib/ldbs.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,41 @@ | ||||
| #ifndef LDBS_H | ||||
| #define LDBS_H | ||||
|  | ||||
| class Bytes; | ||||
|  | ||||
| /* A very simple interface to John Elliott's LDBS image format: | ||||
|  * http://www.seasip.info/Unix/LibDsk/ldbs.html | ||||
|  */ | ||||
|  | ||||
| #define LDBS_FILE_MAGIC 0x4C425301 /* "LBS\01" */ | ||||
| #define LDBS_FILE_TYPE 0x44534B02 /* "DSK\02" */ | ||||
| #define LDBS_BLOCK_MAGIC 0x4C444201 /* "LDB\01" */ | ||||
| #define LDBS_TRACK_BLOCK 0x44495201 /* "DIR\01" */ | ||||
|  | ||||
| class LDBS | ||||
| { | ||||
| public: | ||||
|     LDBS(); | ||||
|  | ||||
| public: | ||||
|     const Bytes& get(uint32_t address) const | ||||
|     { return blocks.at(address).data; } | ||||
|  | ||||
|     uint32_t put(const Bytes& data, uint32_t type); | ||||
|  | ||||
| public: | ||||
|     const Bytes write(uint32_t trackDirectory); | ||||
|     uint32_t read(const Bytes& bytes); | ||||
|  | ||||
| private: | ||||
|     struct Block | ||||
|     { | ||||
|         uint32_t type; | ||||
|         Bytes data; | ||||
|     }; | ||||
|  | ||||
|     std::map<uint32_t, Block> blocks; | ||||
|     unsigned top = 20; | ||||
| }; | ||||
|  | ||||
| #endif | ||||
| @@ -9,25 +9,37 @@ | ||||
| #include "decoders/decoders.h" | ||||
| #include "sector.h" | ||||
| #include "sectorset.h" | ||||
| #include "visualiser.h" | ||||
| #include "record.h" | ||||
| #include "image.h" | ||||
| #include "bytes.h" | ||||
| #include "decoders/rawbits.h" | ||||
| #include "track.h" | ||||
| #include "imagewriter/imagewriter.h" | ||||
| #include "fmt/format.h" | ||||
|  | ||||
| FlagGroup readerFlags { &hardwareFluxSourceFlags, &fluxmapReaderFlags }; | ||||
| FlagGroup readerFlags { &hardwareFluxSourceFlags, &fluxmapReaderFlags, &visualiserFlags }; | ||||
|  | ||||
| static DataSpecFlag source( | ||||
|     { "--source", "-s" }, | ||||
|     "source for data", | ||||
|     ":t=0-79:s=0-1:d=0"); | ||||
|  | ||||
| static DataSpecFlag output( | ||||
| 	{ "--output", "-o" }, | ||||
| 	"output image file to write to", | ||||
| 	""); | ||||
|  | ||||
| static StringFlag destination( | ||||
|     { "--write-flux", "-f" }, | ||||
|     "write the raw magnetic flux to this file", | ||||
|     ""); | ||||
|  | ||||
| static StringFlag visualise( | ||||
| 	{ "--write-svg" }, | ||||
| 	"write a visualisation of the disk to this file", | ||||
| 	""); | ||||
|  | ||||
| static SettableFlag justRead( | ||||
| 	{ "--just-read" }, | ||||
| 	"just read the disk and do no further processing"); | ||||
| @@ -52,6 +64,11 @@ void setReaderDefaultSource(const std::string& source) | ||||
|     ::source.set(source); | ||||
| } | ||||
|  | ||||
| void setReaderDefaultOutput(const std::string& output) | ||||
| { | ||||
|     ::output.set(output); | ||||
| } | ||||
|  | ||||
| void setReaderRevolutions(int revolutions) | ||||
| { | ||||
| 	setHardwareFluxSourceRevolutions(revolutions); | ||||
| @@ -71,9 +88,9 @@ void Track::readFluxmap() | ||||
|  | ||||
| std::vector<std::unique_ptr<Track>> readTracks() | ||||
| { | ||||
|     const DataSpec& dataSpec = source; | ||||
|     const FluxSpec spec(source); | ||||
|  | ||||
|     std::cout << "Reading from: " << dataSpec << std::endl; | ||||
|     std::cout << "Reading from: " << source << std::endl; | ||||
|  | ||||
| 	setHardwareFluxSourceDensity(highDensityFlag); | ||||
|  | ||||
| @@ -92,10 +109,10 @@ std::vector<std::unique_ptr<Track>> readTracks() | ||||
| 		); | ||||
| 	} | ||||
|  | ||||
| 	std::shared_ptr<FluxSource> fluxSource = FluxSource::create(dataSpec); | ||||
| 	std::shared_ptr<FluxSource> fluxSource = FluxSource::create(spec); | ||||
|  | ||||
| 	std::vector<std::unique_ptr<Track>> tracks; | ||||
|     for (const auto& location : dataSpec.locations) | ||||
|     for (const auto& location : spec.locations) | ||||
| 	{ | ||||
| 		auto track = std::make_unique<Track>(location.track, location.side); | ||||
| 		track->fluxsource = fluxSource; | ||||
| @@ -132,7 +149,7 @@ static void replace_sector(std::unique_ptr<Sector>& replacing, Sector& replaceme | ||||
| 			return; | ||||
| 		} | ||||
| 	} | ||||
| 	if (!replacing || (replacing->status != Sector::OK)) | ||||
| 	if (!replacing || ((replacing->status != Sector::OK) && (replacement.status == Sector::OK))) | ||||
| 	{ | ||||
| 		if (!replacing) | ||||
| 			replacing.reset(new Sector); | ||||
| @@ -140,8 +157,11 @@ static void replace_sector(std::unique_ptr<Sector>& replacing, Sector& replaceme | ||||
| 	} | ||||
| } | ||||
|  | ||||
| void readDiskCommand(AbstractDecoder& decoder, const std::string& outputFilename) | ||||
| void readDiskCommand(AbstractDecoder& decoder) | ||||
| { | ||||
| 	const ImageSpec outputSpec(output); | ||||
| 	ImageWriter::verifyImageSpec(outputSpec); | ||||
|          | ||||
| 	bool failures = false; | ||||
| 	SectorSet allSectors; | ||||
| 	auto tracks = readTracks(); | ||||
| @@ -239,8 +259,10 @@ void readDiskCommand(AbstractDecoder& decoder, const std::string& outputFilename | ||||
|         std::cout << size << " bytes decoded." << std::endl; | ||||
|     } | ||||
|  | ||||
| 	Geometry geometry = guessGeometry(allSectors); | ||||
|     writeSectorsToFile(allSectors, geometry, outputFilename); | ||||
| 	if (!visualise.get().empty()) | ||||
| 		visualiseSectorsToFile(allSectors, visualise.get()); | ||||
|  | ||||
|     writeSectorsToFile(allSectors, outputSpec); | ||||
| 	if (failures) | ||||
| 		std::cerr << "Warning: some sectors could not be decoded." << std::endl; | ||||
| } | ||||
|   | ||||
| @@ -11,10 +11,11 @@ class Track; | ||||
| extern FlagGroup readerFlags; | ||||
|  | ||||
| extern void setReaderDefaultSource(const std::string& source); | ||||
| extern void setReaderDefaultOutput(const std::string& output); | ||||
| extern void setReaderRevolutions(int revolutions); | ||||
|  | ||||
| extern std::vector<std::unique_ptr<Track>> readTracks(); | ||||
|  | ||||
| extern void readDiskCommand(AbstractDecoder& decoder, const std::string& outputFilename); | ||||
| extern void readDiskCommand(AbstractDecoder& decoder); | ||||
|  | ||||
| #endif | ||||
|   | ||||
| @@ -27,6 +27,10 @@ public: | ||||
| 	Status status = Status::INTERNAL_ERROR; | ||||
|     Fluxmap::Position position; | ||||
|     nanoseconds_t clock = 0; | ||||
|     nanoseconds_t headerStartTime = 0; | ||||
|     nanoseconds_t headerEndTime = 0; | ||||
|     nanoseconds_t dataStartTime = 0; | ||||
|     nanoseconds_t dataEndTime = 0; | ||||
|     int physicalTrack = 0; | ||||
|     int physicalSide = 0; | ||||
|     int logicalTrack = 0; | ||||
|   | ||||
| @@ -18,8 +18,9 @@ Sector* SectorSet::get(int track, int head, int sector) const | ||||
| 	return i->second.get(); | ||||
| } | ||||
|  | ||||
| void SectorSet::calculateSize(int& numTracks, int& numHeads, int& numSectors, | ||||
| 	int& sectorSize) const | ||||
| void SectorSet::calculateSize( | ||||
| 	unsigned& numTracks, unsigned& numHeads, | ||||
| 	unsigned& numSectors, unsigned& sectorSize) const | ||||
| { | ||||
| 	numTracks = numHeads = numSectors = sectorSize = 0; | ||||
|  | ||||
| @@ -28,10 +29,10 @@ void SectorSet::calculateSize(int& numTracks, int& numHeads, int& numSectors, | ||||
| 		auto& sector = i.second; | ||||
| 		if (sector) | ||||
| 		{ | ||||
| 			numTracks = std::max(numTracks, sector->logicalTrack+1); | ||||
| 			numHeads = std::max(numHeads, sector->logicalSide+1); | ||||
| 			numSectors = std::max(numSectors, sector->logicalSector+1); | ||||
| 			sectorSize = std::max(sectorSize, (int)sector->data.size()); | ||||
| 			numTracks = std::max(numTracks, (unsigned)sector->logicalTrack+1); | ||||
| 			numHeads = std::max(numHeads, (unsigned)sector->logicalSide+1); | ||||
| 			numSectors = std::max(numSectors, (unsigned)sector->logicalSector+1); | ||||
| 			sectorSize = std::max(sectorSize, (unsigned)sector->data.size()); | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|   | ||||
| @@ -17,8 +17,12 @@ public: | ||||
| 	std::unique_ptr<Sector>& get(int track, int head, int sector); | ||||
| 	Sector* get(int track, int head, int sector) const; | ||||
|  | ||||
| 	void calculateSize(int& numTracks, int& numHeads, int& numSectors, | ||||
| 		int& sectorSize) const; | ||||
| 	const std::map<const key_t, std::unique_ptr<Sector>>& get() const | ||||
| 	{ return _data; } | ||||
|  | ||||
| 	void calculateSize( | ||||
| 		unsigned& numTracks, unsigned& numHeads, unsigned& numSectors, | ||||
| 		unsigned& sectorSize) const; | ||||
|  | ||||
| private: | ||||
| 	std::map<const key_t, std::unique_ptr<Sector>> _data; | ||||
|   | ||||
							
								
								
									
										95
									
								
								lib/visualiser.cc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										95
									
								
								lib/visualiser.cc
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,95 @@ | ||||
| #define _USE_MATH_DEFINES | ||||
| #include "globals.h" | ||||
| #include "image.h" | ||||
| #include "sector.h" | ||||
| #include "sectorset.h" | ||||
| #include "visualiser.h" | ||||
| #include "fmt/format.h" | ||||
| #include "flags.h" | ||||
| #include <iostream> | ||||
| #include <fstream> | ||||
| #include <math.h> | ||||
|  | ||||
| FlagGroup visualiserFlags; | ||||
|  | ||||
| static IntFlag period( | ||||
|     { "--visualiser-period" }, | ||||
|     "rotational period for use by the visualiser (milliseconds)", | ||||
|     200); | ||||
|  | ||||
| static const int SIZE = 480; | ||||
| static const int BORDER = 10; | ||||
| static const int RADIUS = (SIZE/2) - (BORDER/2); | ||||
| static const int CORE = 50; | ||||
| static const int TRACKS = 83; | ||||
| static const double TRACK_SPACING = double(RADIUS-CORE) / TRACKS; | ||||
|  | ||||
| void visualiseSectorsToFile(const SectorSet& sectors, const std::string& filename) | ||||
| { | ||||
|     std::cout << "writing visualisation\n"; | ||||
|     std::ofstream f(filename, std::ios::out); | ||||
|     if (!f.is_open()) | ||||
|         Error() << "cannot open visualisation file"; | ||||
|  | ||||
|     f << fmt::format("<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"{0} {1} {2} {3}\">", | ||||
|         0, 0, SIZE*2, SIZE); | ||||
|  | ||||
|     const double radians_per_ns = 2.0*M_PI / (period*1e6); | ||||
|  | ||||
|     auto drawSide = [&](int side) | ||||
|     { | ||||
|         f << fmt::format("<g transform='matrix(1 0 0 -1 {} {})'>", SIZE/2 + (side*SIZE), SIZE/2); | ||||
|         f << fmt::format("<circle cx='0' cy='0' r='{}' stroke='none' fill='#ccc'/>", RADIUS); | ||||
|  | ||||
|         for (int physicalTrack = 0; physicalTrack < TRACKS; physicalTrack++) | ||||
|         { | ||||
|             double radius = CORE + physicalTrack*TRACK_SPACING; | ||||
|             f << fmt::format("<circle cx='0' cy='0' r='{}' stroke='#888' stroke-width='0.5' fill='none'/>", radius); | ||||
|  | ||||
|             auto drawArc = [&](const std::unique_ptr<Sector>& sector, nanoseconds_t start, nanoseconds_t end, const std::string& colour) | ||||
|             { | ||||
|                 start %= period*1000000; | ||||
|                 end %= period*1000000; | ||||
|                 if (end < start) | ||||
|                     end += period*1000000; | ||||
|                  | ||||
|                 double theta1 = start * radians_per_ns; | ||||
|                 double theta2 = end * radians_per_ns; | ||||
|                 int large = (theta2 - theta1) >= M_PI; | ||||
|  | ||||
|                 f << fmt::format("\n<!-- {} {} = {} {} -->", start, end, theta1, theta2); | ||||
|                 f << fmt::format("<path fill='none' stroke='{}' stroke-width='1.5' d='", colour); | ||||
|                 f << fmt::format("M {} {} ", cos(theta1)*radius, sin(theta1)*radius); | ||||
|                 f << fmt::format("A {0} {0} 0 {3} 1 {1} {2}", radius, cos(theta2)*radius, sin(theta2)*radius, large); | ||||
|                 f << fmt::format("'><title>Track {} Head {} Sector {}; {}ms to {}ms</title></path>", | ||||
|                     sector->logicalTrack, sector->logicalSide, sector->logicalSector, | ||||
|                     start/1e6, end/1e6); | ||||
|             }; | ||||
|  | ||||
|             /* Sadly, SectorSets aren't indexable by physical track. */ | ||||
|             for (const auto& e : sectors.get()) | ||||
|             { | ||||
|                 const auto& sector = e.second; | ||||
|                 if ((sector->physicalSide == side) && (sector->physicalTrack == physicalTrack)) | ||||
|                 { | ||||
|                     const char* colour = "#f00"; | ||||
|                     if (sector->status == Sector::OK) | ||||
|                         colour = "#00f"; | ||||
|                     if (sector->headerStartTime && sector->headerEndTime) | ||||
|                         drawArc(sector, sector->headerStartTime, sector->headerEndTime, "#0ff"); | ||||
|                     if (sector->dataStartTime && sector->dataEndTime) | ||||
|                         drawArc(sector, sector->dataStartTime, sector->dataEndTime, colour); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         f << "</g>"; | ||||
|     }; | ||||
|  | ||||
|     f << fmt::format("<rect x='0' y='0' width='{}' height='{}' stroke='none' fill='#fff'/>", SIZE*2, SIZE); | ||||
|  | ||||
|     drawSide(0); | ||||
|     drawSide(1); | ||||
|  | ||||
|     f << "</svg>"; | ||||
| } | ||||
							
								
								
									
										12
									
								
								lib/visualiser.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								lib/visualiser.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,12 @@ | ||||
| #ifndef VISUALISER_H | ||||
| #define VISUALISER_H | ||||
|  | ||||
| #include "flags.h" | ||||
|  | ||||
| class SectorSet; | ||||
|  | ||||
| extern FlagGroup visualiserFlags; | ||||
|  | ||||
| extern void visualiseSectorsToFile(const SectorSet& sectors, const std::string& filename); | ||||
|  | ||||
| #endif | ||||
| @@ -22,6 +22,11 @@ static DataSpecFlag dest( | ||||
|     "destination for data", | ||||
|     ":d=0:t=0-79:s=0-1"); | ||||
|  | ||||
| static DataSpecFlag input( | ||||
|     { "--input", "-i" }, | ||||
|     "input image file to read from", | ||||
|     ""); | ||||
|  | ||||
| static SettableFlag highDensityFlag( | ||||
| 	{ "--high-density", "-H" }, | ||||
| 	"set the drive to high density mode"); | ||||
| @@ -33,12 +38,17 @@ void setWriterDefaultDest(const std::string& dest) | ||||
|     ::dest.set(dest); | ||||
| } | ||||
|  | ||||
| void setWriterDefaultInput(const std::string& input) | ||||
| { | ||||
|     ::input.set(input); | ||||
| } | ||||
|  | ||||
| void writeTracks( | ||||
| 	const std::function<std::unique_ptr<Fluxmap>(int track, int side)> producer) | ||||
| { | ||||
|     const DataSpec& spec = dest; | ||||
|     const FluxSpec spec(dest); | ||||
|  | ||||
|     std::cout << "Writing to: " << spec << std::endl; | ||||
|     std::cout << "Writing to: " << dest << std::endl; | ||||
|  | ||||
| 	setHardwareFluxSourceDensity(highDensityFlag); | ||||
| 	setHardwareFluxSinkDensity(highDensityFlag); | ||||
| @@ -103,12 +113,10 @@ void fillBitmapTo(std::vector<bool>& bitmap, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| void writeDiskCommand( | ||||
|     AbstractEncoder& encoder, const Geometry& geometry, const std::string& inputFilename) | ||||
| void writeDiskCommand(AbstractEncoder& encoder) | ||||
| { | ||||
|     SectorSet allSectors; | ||||
|  | ||||
| 	readSectorsFromFile(allSectors, geometry, inputFilename); | ||||
|     const ImageSpec spec(input); | ||||
| 	SectorSet allSectors = readSectorsFromFile(spec); | ||||
| 	writeTracks( | ||||
| 		[&](int track, int side) -> std::unique_ptr<Fluxmap> | ||||
| 		{ | ||||
|   | ||||
| @@ -10,6 +10,7 @@ class AbstractEncoder; | ||||
| class Geometry; | ||||
|  | ||||
| extern void setWriterDefaultDest(const std::string& dest); | ||||
| extern void setWriterDefaultInput(const std::string& input); | ||||
|  | ||||
| extern void writeTracks(const std::function<std::unique_ptr<Fluxmap>(int track, int side)> producer); | ||||
|  | ||||
| @@ -17,7 +18,6 @@ extern void fillBitmapTo(std::vector<bool>& bitmap, | ||||
| 		unsigned& cursor, unsigned terminateAt, | ||||
| 		const std::vector<bool>& pattern); | ||||
| 	 | ||||
| extern void writeDiskCommand( | ||||
|     AbstractEncoder& encoder, const Geometry& geometry, const std::string& inputFilename); | ||||
| extern void writeDiskCommand(AbstractEncoder& encoder); | ||||
|  | ||||
| #endif | ||||
|   | ||||
							
								
								
									
										30
									
								
								mkninja.sh
									
									
									
									
									
								
							
							
						
						
									
										30
									
								
								mkninja.sh
									
									
									
									
									
								
							| @@ -137,6 +137,12 @@ buildlibrary libfmt.a \ | ||||
|     dep/fmt/posix.cc \ | ||||
|  | ||||
| buildlibrary libbackend.a \ | ||||
| 	lib/imagereader/imagereader.cc \ | ||||
| 	lib/imagereader/imgimagereader.cc \ | ||||
| 	lib/imagewriter/d64imagewriter.cc \ | ||||
| 	lib/imagewriter/imagewriter.cc \ | ||||
| 	lib/imagewriter/imgimagewriter.cc \ | ||||
| 	lib/imagewriter/ldbsimagewriter.cc \ | ||||
|     arch/aeslanier/decoder.cc \ | ||||
|     arch/amiga/decoder.cc \ | ||||
|     arch/apple2/decoder.cc \ | ||||
| @@ -171,17 +177,20 @@ buildlibrary libbackend.a \ | ||||
|     lib/globals.cc \ | ||||
|     lib/hexdump.cc \ | ||||
|     lib/image.cc \ | ||||
|     lib/ldbs.cc \ | ||||
|     lib/reader.cc \ | ||||
|     lib/sector.cc \ | ||||
|     lib/sectorset.cc \ | ||||
|     lib/sql.cc \ | ||||
|     lib/usb.cc \ | ||||
|     lib/visualiser.cc \ | ||||
|     lib/writer.cc \ | ||||
|  | ||||
| buildlibrary libfrontend.a \ | ||||
|     src/fe-cwftoflux.cc \ | ||||
|     src/fe-erase.cc \ | ||||
|     src/fe-fluxtoau.cc \ | ||||
|     src/fe-fluxtoscp.cc \ | ||||
|     src/fe-fluxtovcd.cc \ | ||||
|     src/fe-inspect.cc \ | ||||
|     src/fe-readadfs.cc \ | ||||
| @@ -200,6 +209,7 @@ buildlibrary libfrontend.a \ | ||||
|     src/fe-readvictor9k.cc \ | ||||
|     src/fe-readzilogmcz.cc \ | ||||
|     src/fe-rpm.cc \ | ||||
|     src/fe-scptoflux.cc \ | ||||
|     src/fe-seek.cc \ | ||||
|     src/fe-testbulktransport.cc \ | ||||
|     src/fe-upgradefluxfile.cc \ | ||||
| @@ -223,12 +233,20 @@ buildsimpleprogram brother120tool \ | ||||
|     libemu.a \ | ||||
|     libfmt.a \ | ||||
|  | ||||
| buildsimpleprogram brother240tool \ | ||||
| 	-Idep/emu \ | ||||
|     tools/brother240tool.cc \ | ||||
|     libbackend.a \ | ||||
|     libemu.a \ | ||||
|     libfmt.a \ | ||||
|  | ||||
| runtest bitaccumulator-test tests/bitaccumulator.cc | ||||
| runtest bytes-test          tests/bytes.cc | ||||
| runtest compression-test    tests/compression.cc | ||||
| runtest crunch-test         tests/crunch.cc | ||||
| runtest dataspec-test       tests/dataspec.cc | ||||
| runtest flags-test          tests/flags.cc | ||||
| runtest fmmfm-test          tests/fmmfm.cc | ||||
| runtest bitaccumulator-test tests/bitaccumulator.cc | ||||
| runtest kryoflux-test       tests/kryoflux.cc | ||||
| runtest compression-test    tests/compression.cc | ||||
| runtest bytes-test          tests/bytes.cc | ||||
| runtest crunch-test         tests/crunch.cc | ||||
| runtest fluxpattern-test    tests/fluxpattern.cc | ||||
| runtest fmmfm-test          tests/fmmfm.cc | ||||
| runtest kryoflux-test       tests/kryoflux.cc | ||||
| runtest ldbs-test           tests/ldbs.cc | ||||
|   | ||||
| @@ -3,7 +3,7 @@ | ||||
| #include "fluxmap.h" | ||||
| #include "writer.h" | ||||
|  | ||||
| static FlagGroup flags; | ||||
| static FlagGroup flags { &writerFlags }; | ||||
|  | ||||
| int mainErase(int argc, const char* argv[]) | ||||
| { | ||||
|   | ||||
| @@ -34,14 +34,15 @@ int mainConvertFluxToAu(int argc, const char* argv[]) | ||||
| { | ||||
|     flags.parseFlags(argc, argv); | ||||
|  | ||||
|     const auto& locations = source.get().locations; | ||||
|     FluxSpec spec(source); | ||||
|     const auto& locations = spec.locations; | ||||
|     if (locations.size() != 1) | ||||
|         Error() << "the source dataspec must contain exactly one track (two sides count as two tracks)"; | ||||
|     const auto& location = *(locations.begin()); | ||||
|  | ||||
|     std::cerr << "Reading source flux...\n"; | ||||
|     setHardwareFluxSourceDensity(highDensityFlag); | ||||
|     std::shared_ptr<FluxSource> fluxsource = FluxSource::create(source); | ||||
|     std::shared_ptr<FluxSource> fluxsource = FluxSource::create(spec); | ||||
|     const auto& fluxmap = fluxsource->readFlux(location.track, location.side); | ||||
|     unsigned totalTicks = fluxmap->ticks() + 2; | ||||
|     unsigned channels = withIndex ? 2 : 1; | ||||
|   | ||||
							
								
								
									
										188
									
								
								src/fe-fluxtoscp.cc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										188
									
								
								src/fe-fluxtoscp.cc
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,188 @@ | ||||
| #include "globals.h" | ||||
| #include "flags.h" | ||||
| #include "fluxmap.h" | ||||
| #include "sql.h" | ||||
| #include "bytes.h" | ||||
| #include "protocol.h" | ||||
| #include "dataspec.h" | ||||
| #include "fmt/format.h" | ||||
| #include "decoders/fluxmapreader.h" | ||||
| #include "scp.h" | ||||
| #include <fstream> | ||||
| #include <algorithm> | ||||
|  | ||||
| static FlagGroup flags { }; | ||||
|  | ||||
| static SettableFlag fortyTrackMode( | ||||
|     { "--48", "-4" }, | ||||
|     "set 48 tpi mode; only every other physical track is emitted" | ||||
| ); | ||||
|  | ||||
| static SettableFlag singleSided( | ||||
|     { "--single-sided", "-s" }, | ||||
|     "only emit side 0" | ||||
| ); | ||||
|  | ||||
| static IntFlag diskType( | ||||
|     { "--disk-type" }, | ||||
|     "sets the SCP disk type byte", | ||||
|     0xff | ||||
| ); | ||||
|  | ||||
| static sqlite3* inputDb; | ||||
|  | ||||
| static void syntax() | ||||
| { | ||||
|     std::cout << "Syntax: fluxengine convert fluxtoscp <fluxfile> <scpfile>\n"; | ||||
|     exit(0); | ||||
| } | ||||
|  | ||||
| static void write_le32(uint8_t dest[4], uint32_t v) | ||||
| { | ||||
|     dest[0] = v; | ||||
|     dest[1] = v >> 8; | ||||
|     dest[2] = v >> 16; | ||||
|     dest[3] = v >> 24; | ||||
| } | ||||
|  | ||||
| static int strackno(int track, int side) | ||||
| { | ||||
|     if (fortyTrackMode) | ||||
|         track /= 2; | ||||
|     if (singleSided) | ||||
|         return track; | ||||
|     else | ||||
|         return (track << 1) | side; | ||||
| } | ||||
|  | ||||
| int mainConvertFluxToScp(int argc, const char* argv[]) | ||||
| { | ||||
|     auto filenames = flags.parseFlagsWithFilenames(argc, argv); | ||||
|     if (filenames.size() != 2) | ||||
|         syntax(); | ||||
|  | ||||
|     inputDb = sqlOpen(filenames[0], SQLITE_OPEN_READONLY); | ||||
|     auto tracks = sqlFindFlux(inputDb); | ||||
|  | ||||
|     int maxTrack = 0; | ||||
|     int maxSide = 0; | ||||
|     for (auto p : tracks) | ||||
|     { | ||||
|         if (singleSided && (p.second == 1)) | ||||
|             continue; | ||||
|         maxTrack = std::max(maxTrack, (int)p.first); | ||||
|         maxSide = std::max(maxSide, (int)p.second); | ||||
|     } | ||||
|     int maxStrack = strackno(maxTrack, maxSide); | ||||
|  | ||||
|     std::cout << fmt::format("Writing {} {} SCP file containing {} SCP tracks\n", | ||||
|         fortyTrackMode ? "48 tpi" : "96 tpi", | ||||
|         singleSided ? "single sided" : "double sided", | ||||
|         maxStrack + 1 | ||||
|     ); | ||||
|  | ||||
|     ScpHeader fileheader = {0}; | ||||
|     fileheader.file_id[0] = 'S'; | ||||
|     fileheader.file_id[1] = 'C'; | ||||
|     fileheader.file_id[2] = 'P'; | ||||
|     fileheader.file_id[3] = 0x18; /* Version 1.8 of the spec */ | ||||
|     fileheader.type = diskType; | ||||
|     fileheader.revolutions = 5; | ||||
|     fileheader.start_track = 0; | ||||
|     fileheader.end_track = maxStrack; | ||||
|     fileheader.flags = SCP_FLAG_INDEXED | (fortyTrackMode ? 0 : SCP_FLAG_96TPI); | ||||
|     fileheader.cell_width = 0; | ||||
|     fileheader.heads = singleSided ? 1 : 0; | ||||
|  | ||||
|     Bytes trackdata; | ||||
|     ByteWriter trackdataWriter(trackdata); | ||||
|  | ||||
|     int trackstep = 1 + fortyTrackMode; | ||||
|     int maxside = singleSided ? 0 : 1; | ||||
|     for (int track = 0; track <= maxTrack; track += trackstep) | ||||
|     { | ||||
|         for (int side = 0; side <= maxside; side++) | ||||
|         { | ||||
|             int strack = strackno(track, side); | ||||
|             std::cout << fmt::format("FE track {}.{}, SCP track {}: ", track, side, strack) << std::flush; | ||||
|  | ||||
|             auto fluxmap = sqlReadFlux(inputDb, track, side); | ||||
|             ScpTrack trackheader = {0}; | ||||
|             trackheader.track_id[0] = 'T'; | ||||
|             trackheader.track_id[1] = 'R'; | ||||
|             trackheader.track_id[2] = 'K'; | ||||
|             trackheader.strack = strack; | ||||
|  | ||||
|             FluxmapReader fmr(*fluxmap); | ||||
|             Bytes fluxdata; | ||||
|             ByteWriter fluxdataWriter(fluxdata); | ||||
|  | ||||
|             int revolution = 0; | ||||
|             unsigned revTicks = 0; | ||||
|             unsigned totalTicks = 0; | ||||
|             unsigned ticksSinceLastPulse = 0; | ||||
|             uint32_t startOffset = 0; | ||||
|             while (revolution < 5) | ||||
|             { | ||||
|                 unsigned ticks; | ||||
|                 int opcode = fmr.readOpcode(ticks); | ||||
|                 if (ticks) | ||||
|                 { | ||||
|                     ticksSinceLastPulse += ticks; | ||||
|                     totalTicks += ticks; | ||||
|                     revTicks += ticks; | ||||
|                 } | ||||
|  | ||||
|                 switch (opcode) | ||||
|                 { | ||||
|                     case -1: /* end of flux, treat like an index marker */ | ||||
|                     case F_OP_INDEX: | ||||
|                     { | ||||
|                         auto* revheader = &trackheader.revolution[revolution]; | ||||
|                         write_le32(revheader->offset, startOffset + sizeof(ScpTrack)); | ||||
|                         write_le32(revheader->length, (fluxdataWriter.pos - startOffset) / 2); | ||||
|                         write_le32(revheader->index, revTicks * NS_PER_TICK / 25); | ||||
|                         revolution++; | ||||
|                         revheader++; | ||||
|                         revTicks = 0; | ||||
|                         startOffset = fluxdataWriter.pos; | ||||
|                         break; | ||||
|                     } | ||||
|  | ||||
|                     case F_OP_PULSE: | ||||
|                     { | ||||
|                         unsigned t = ticksSinceLastPulse * NS_PER_TICK / 25; | ||||
|                         while (t >= 0x10000) | ||||
|                         { | ||||
|                             fluxdataWriter.write_be16(0); | ||||
|                             t -= 0x10000; | ||||
|                         } | ||||
|                         fluxdataWriter.write_be16(t); | ||||
|                         ticksSinceLastPulse = 0; | ||||
|                         break; | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             write_le32(fileheader.track[strack], trackdataWriter.pos + sizeof(ScpHeader)); | ||||
|             trackdataWriter += Bytes((uint8_t*)&trackheader, sizeof(trackheader)); | ||||
|             trackdataWriter += fluxdata; | ||||
|  | ||||
|             std::cout << fmt::format("{} ms in {} bytes\n", | ||||
|                 totalTicks * MS_PER_TICK, | ||||
|                 fluxdata.size()); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     sqlClose(inputDb); | ||||
|      | ||||
|     std::cout << "Writing output file...\n"; | ||||
|     std::ofstream of(filenames[1], std::ios::out | std::ios::binary); | ||||
|     if (!of.is_open()) | ||||
|         Error() << "cannot open output file"; | ||||
|     of.write((const char*) &fileheader, sizeof(fileheader)); | ||||
|     of.write((const char*) trackdata.begin(), trackdata.size()); | ||||
|     of.close(); | ||||
|  | ||||
|     return 0; | ||||
| } | ||||
| @@ -30,14 +30,15 @@ int mainConvertFluxToVcd(int argc, const char* argv[]) | ||||
| { | ||||
|     flags.parseFlags(argc, argv); | ||||
|  | ||||
|     const auto& locations = source.get().locations; | ||||
|     const FluxSpec spec(source); | ||||
|     const auto& locations = spec.locations; | ||||
|     if (locations.size() != 1) | ||||
|         Error() << "the source dataspec must contain exactly one track (two sides count as two tracks)"; | ||||
|     const auto& location = *(locations.begin()); | ||||
|  | ||||
|     std::cerr << "Reading source flux...\n"; | ||||
|     setHardwareFluxSourceDensity(highDensityFlag); | ||||
|     std::shared_ptr<FluxSource> fluxsource = FluxSource::create(source); | ||||
|     std::shared_ptr<FluxSource> fluxsource = FluxSource::create(spec); | ||||
|     const auto& fluxmap = fluxsource->readFlux(location.track, location.side); | ||||
|  | ||||
|     std::cerr << "Writing destination VCD...\n"; | ||||
|   | ||||
| @@ -12,11 +12,6 @@ | ||||
|  | ||||
| static FlagGroup flags { &readerFlags }; | ||||
|  | ||||
| static StringFlag outputFilename( | ||||
|     { "--output", "-o" }, | ||||
|     "The output image file to write to.", | ||||
|     "adfs.img"); | ||||
|  | ||||
| static IntFlag sectorIdBase( | ||||
| 	{ "--sector-id-base" }, | ||||
| 	"Sector ID of the first sector.", | ||||
| @@ -25,10 +20,11 @@ static IntFlag sectorIdBase( | ||||
| int mainReadADFS(int argc, const char* argv[]) | ||||
| { | ||||
| 	setReaderDefaultSource(":t=0-79:s=0-1"); | ||||
| 	setReaderDefaultOutput("adfs.img"); | ||||
|     flags.parseFlags(argc, argv); | ||||
|  | ||||
| 	IbmDecoder decoder(sectorIdBase); | ||||
| 	readDiskCommand(decoder, outputFilename); | ||||
| 	readDiskCommand(decoder); | ||||
|     return 0; | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -20,11 +20,12 @@ static StringFlag outputFilename( | ||||
| int mainReadAESLanier(int argc, const char* argv[]) | ||||
| { | ||||
| 	setReaderDefaultSource(":t=0-79:s=0"); | ||||
| 	setReaderDefaultOutput("aeslanier.img"); | ||||
|     setReaderRevolutions(2); | ||||
|     flags.parseFlags(argc, argv); | ||||
|  | ||||
| 	AesLanierDecoder decoder; | ||||
| 	readDiskCommand(decoder, outputFilename); | ||||
| 	readDiskCommand(decoder); | ||||
|     return 0; | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -13,19 +13,15 @@ | ||||
|  | ||||
| static FlagGroup flags { &readerFlags }; | ||||
|  | ||||
| static StringFlag outputFilename( | ||||
|     { "--output", "-o" }, | ||||
|     "The output image file to write to.", | ||||
|     "amiga.adf"); | ||||
|  | ||||
| int mainReadAmiga(int argc, const char* argv[]) | ||||
| { | ||||
| 	setReaderDefaultSource(":t=0-79:s=0-1"); | ||||
| 	setReaderDefaultOutput("amiga.adf:c=80:h=2:s=11:b=512"); | ||||
|     setReaderRevolutions(2); | ||||
|     flags.parseFlags(argc, argv); | ||||
|  | ||||
| 	AmigaDecoder decoder; | ||||
| 	readDiskCommand(decoder, outputFilename); | ||||
| 	readDiskCommand(decoder); | ||||
|  | ||||
|     return 0; | ||||
| } | ||||
|   | ||||
| @@ -12,11 +12,6 @@ | ||||
|  | ||||
| static FlagGroup flags { &readerFlags }; | ||||
|  | ||||
| static StringFlag outputFilename( | ||||
|     { "--output", "-o" }, | ||||
|     "The output image file to write to.", | ||||
|     "ampro.img"); | ||||
|  | ||||
| static IntFlag sectorIdBase( | ||||
| 	{ "--sector-id-base" }, | ||||
| 	"Sector ID of the first sector.", | ||||
| @@ -25,11 +20,12 @@ static IntFlag sectorIdBase( | ||||
| int mainReadAmpro(int argc, const char* argv[]) | ||||
| { | ||||
| 	setReaderDefaultSource(":t=0-79:s=0"); | ||||
| 	setReaderDefaultOutput("ampro.adf"); | ||||
|     setReaderRevolutions(2); | ||||
|     flags.parseFlags(argc, argv); | ||||
|  | ||||
| 	IbmDecoder decoder(sectorIdBase); | ||||
| 	readDiskCommand(decoder, outputFilename); | ||||
| 	readDiskCommand(decoder); | ||||
|     return 0; | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -13,19 +13,15 @@ | ||||
|  | ||||
| static FlagGroup flags { &readerFlags }; | ||||
|  | ||||
| static StringFlag outputFilename( | ||||
|     { "--output", "-o" }, | ||||
|     "The output image file to write to.", | ||||
|     "apple2.img"); | ||||
|  | ||||
| int mainReadApple2(int argc, const char* argv[]) | ||||
| { | ||||
| 	setReaderDefaultSource(":t=0-79:s=0"); | ||||
| 	setReaderDefaultOutput("apple2.adf"); | ||||
|     setReaderRevolutions(2); | ||||
|     flags.parseFlags(argc, argv); | ||||
|  | ||||
| 	Apple2Decoder decoder; | ||||
| 	readDiskCommand(decoder, outputFilename); | ||||
| 	readDiskCommand(decoder); | ||||
|  | ||||
|     return 0; | ||||
| } | ||||
|   | ||||
| @@ -14,19 +14,15 @@ | ||||
|  | ||||
| static FlagGroup flags { &readerFlags }; | ||||
|  | ||||
| static StringFlag outputFilename( | ||||
|     { "--output", "-o" }, | ||||
|     "The output image file to write to.", | ||||
|     "brother.img"); | ||||
|  | ||||
| int mainReadBrother(int argc, const char* argv[]) | ||||
| { | ||||
| 	setReaderDefaultSource(":t=0-81:s=0"); | ||||
| 	setReaderDefaultOutput("brother.img"); | ||||
|     setReaderRevolutions(2); | ||||
|     flags.parseFlags(argc, argv); | ||||
|  | ||||
| 	BrotherDecoder decoder; | ||||
| 	readDiskCommand(decoder, outputFilename); | ||||
| 	readDiskCommand(decoder); | ||||
|  | ||||
|     return 0; | ||||
| } | ||||
|   | ||||
| @@ -13,19 +13,15 @@ | ||||
|  | ||||
| static FlagGroup flags { &readerFlags }; | ||||
|  | ||||
| static StringFlag outputFilename( | ||||
|     { "--output", "-o" }, | ||||
|     "The output image file to write to.", | ||||
|     "c64.img"); | ||||
|  | ||||
| int mainReadC64(int argc, const char* argv[]) | ||||
| { | ||||
| 	setReaderDefaultSource(":t=0-79x2:s=0"); | ||||
| 	setReaderDefaultOutput("c64.d64"); | ||||
|     setReaderRevolutions(2); | ||||
|     flags.parseFlags(argc, argv); | ||||
|  | ||||
| 	Commodore64Decoder decoder; | ||||
| 	readDiskCommand(decoder, outputFilename); | ||||
| 	readDiskCommand(decoder); | ||||
|  | ||||
|     return 0; | ||||
| } | ||||
|   | ||||
| @@ -12,11 +12,6 @@ | ||||
|  | ||||
| static FlagGroup flags { &readerFlags }; | ||||
|  | ||||
| static StringFlag outputFilename( | ||||
|     { "--output", "-o" }, | ||||
|     "The output image file to write to.", | ||||
|     "dfs.img"); | ||||
|  | ||||
| static IntFlag sectorIdBase( | ||||
| 	{ "--sector-id-base" }, | ||||
| 	"Sector ID of the first sector.", | ||||
| @@ -25,11 +20,12 @@ static IntFlag sectorIdBase( | ||||
| int mainReadDFS(int argc, const char* argv[]) | ||||
| { | ||||
| 	setReaderDefaultSource(":t=0-79:s=0"); | ||||
| 	setReaderDefaultOutput("dfs.img"); | ||||
|     setReaderRevolutions(2); | ||||
|     flags.parseFlags(argc, argv); | ||||
|  | ||||
| 	IbmDecoder decoder(sectorIdBase); | ||||
| 	readDiskCommand(decoder, outputFilename); | ||||
| 	readDiskCommand(decoder); | ||||
|     return 0; | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -13,19 +13,15 @@ | ||||
|  | ||||
| static FlagGroup flags { &readerFlags }; | ||||
|  | ||||
| static StringFlag outputFilename( | ||||
|     { "--output", "-o" }, | ||||
|     "The output image file to write to.", | ||||
|     "f85.img"); | ||||
|  | ||||
| int mainReadF85(int argc, const char* argv[]) | ||||
| { | ||||
| 	setReaderDefaultSource(":t=0-79:s=0"); | ||||
| 	setReaderDefaultOutput("f85.img"); | ||||
|     setReaderRevolutions(2); | ||||
|     flags.parseFlags(argc, argv); | ||||
|  | ||||
| 	DurangoF85Decoder decoder; | ||||
| 	readDiskCommand(decoder, outputFilename); | ||||
| 	readDiskCommand(decoder); | ||||
|  | ||||
|     return 0; | ||||
| } | ||||
|   | ||||
| @@ -12,19 +12,15 @@ | ||||
|  | ||||
| static FlagGroup flags { &readerFlags }; | ||||
|  | ||||
| static StringFlag outputFilename( | ||||
|     { "--output", "-o" }, | ||||
|     "The output image file to write to.", | ||||
|     "fb100.img"); | ||||
|  | ||||
| int mainReadFB100(int argc, const char* argv[]) | ||||
| { | ||||
| 	setReaderDefaultSource(":t=0-79x2:s=0"); | ||||
| 	setReaderDefaultOutput("fb100.img"); | ||||
|     setReaderRevolutions(2); | ||||
|     flags.parseFlags(argc, argv); | ||||
|  | ||||
| 	Fb100Decoder decoder; | ||||
| 	readDiskCommand(decoder, outputFilename); | ||||
| 	readDiskCommand(decoder); | ||||
|     return 0; | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -12,23 +12,24 @@ | ||||
|  | ||||
| static FlagGroup flags { &readerFlags }; | ||||
|  | ||||
| static StringFlag outputFilename( | ||||
|     { "--output", "-o" }, | ||||
|     "The output image file to write to.", | ||||
|     "ibm.img"); | ||||
|  | ||||
| static IntFlag sectorIdBase( | ||||
| 	{ "--sector-id-base" }, | ||||
| 	"Sector ID of the first sector.", | ||||
| 	1); | ||||
|  | ||||
| static BoolFlag ignoreSideByte( | ||||
| 	{ "--ignore-side-byte" }, | ||||
| 	"Ignore the side byte in the sector ID, and use the physical side instead.", | ||||
| 	false); | ||||
|  | ||||
| int mainReadIBM(int argc, const char* argv[]) | ||||
| { | ||||
| 	setReaderDefaultSource(":t=0-79:s=0-1"); | ||||
| 	setReaderDefaultOutput("ibm.img"); | ||||
|     flags.parseFlags(argc, argv); | ||||
|  | ||||
| 	IbmDecoder decoder(sectorIdBase); | ||||
| 	readDiskCommand(decoder, outputFilename); | ||||
| 	IbmDecoder decoder(sectorIdBase, ignoreSideByte); | ||||
| 	readDiskCommand(decoder); | ||||
|     return 0; | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -13,19 +13,15 @@ | ||||
|  | ||||
| static FlagGroup flags { &readerFlags }; | ||||
|  | ||||
| static StringFlag outputFilename( | ||||
|     { "--output", "-o" }, | ||||
|     "The output image file to write to.", | ||||
|     "mac.img"); | ||||
|  | ||||
| int mainReadMac(int argc, const char* argv[]) | ||||
| { | ||||
| 	setReaderDefaultSource(":t=0-79:s=0-1"); | ||||
| 	setReaderDefaultOutput("mac.img"); | ||||
|     setReaderRevolutions(2); | ||||
|     flags.parseFlags(argc, argv); | ||||
|  | ||||
| 	MacintoshDecoder decoder; | ||||
| 	readDiskCommand(decoder, outputFilename); | ||||
| 	readDiskCommand(decoder); | ||||
|  | ||||
|     return 0; | ||||
| } | ||||
|   | ||||
| @@ -12,18 +12,14 @@ | ||||
|  | ||||
| static FlagGroup flags { &readerFlags }; | ||||
|  | ||||
| static StringFlag outputFilename( | ||||
|     { "--output", "-o" }, | ||||
|     "The output image file to write to.", | ||||
|     "mx.img"); | ||||
|  | ||||
| int mainReadMx(int argc, const char* argv[]) | ||||
| { | ||||
| 	setReaderDefaultSource(":t=0-79:s=0-1"); | ||||
| 	setReaderDefaultOutput("mx.img"); | ||||
|     flags.parseFlags(argc, argv); | ||||
|  | ||||
| 	MxDecoder decoder; | ||||
| 	readDiskCommand(decoder, outputFilename); | ||||
| 	readDiskCommand(decoder); | ||||
|     return 0; | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -13,19 +13,15 @@ | ||||
|  | ||||
| static FlagGroup flags { &readerFlags }; | ||||
|  | ||||
| static StringFlag outputFilename( | ||||
|     { "--output", "-o" }, | ||||
|     "The output image file to write to.", | ||||
|     "victor9k.img"); | ||||
|  | ||||
| int mainReadVictor9K(int argc, const char* argv[]) | ||||
| { | ||||
| 	setReaderDefaultSource(":t=0-79:s=0"); | ||||
| 	setReaderDefaultOutput("victor9k.img"); | ||||
|     setReaderRevolutions(2); | ||||
|     flags.parseFlags(argc, argv); | ||||
|  | ||||
| 	Victor9kDecoder decoder; | ||||
| 	readDiskCommand(decoder, outputFilename); | ||||
| 	readDiskCommand(decoder); | ||||
|  | ||||
|     return 0; | ||||
| } | ||||
|   | ||||
| @@ -12,19 +12,15 @@ | ||||
|  | ||||
| static FlagGroup flags { &readerFlags }; | ||||
|  | ||||
| static StringFlag outputFilename( | ||||
|     { "--output", "-o" }, | ||||
|     "The output image file to write to.", | ||||
|     "zilogmcz.img"); | ||||
|  | ||||
| int mainReadZilogMCZ(int argc, const char* argv[]) | ||||
| { | ||||
| 	setReaderDefaultSource(":t=0-76:s=0"); | ||||
| 	setReaderDefaultOutput("zilogmcz.img"); | ||||
|     setReaderRevolutions(2); | ||||
|     flags.parseFlags(argc, argv); | ||||
|  | ||||
| 	ZilogMczDecoder decoder; | ||||
| 	readDiskCommand(decoder, outputFilename); | ||||
| 	readDiskCommand(decoder); | ||||
|     return 0; | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -8,13 +8,14 @@ static FlagGroup flags; | ||||
| static DataSpecFlag source( | ||||
|     { "--source", "-s" }, | ||||
|     "source for data", | ||||
|     ":d=0"); | ||||
|     ":d=0:t=0:s=0"); | ||||
|  | ||||
| int mainRpm(int argc, const char* argv[]) | ||||
| { | ||||
|     flags.parseFlags(argc, argv); | ||||
|  | ||||
|     usbSetDrive(source.get().drive, false); | ||||
|     FluxSpec spec(source); | ||||
|     usbSetDrive(spec.drive, false); | ||||
|     nanoseconds_t period = usbGetRotationalPeriod(); | ||||
|     std::cout << "Rotational period is " << period/1000 << " ms (" << 60e6/period << " rpm)" << std::endl; | ||||
|  | ||||
|   | ||||
							
								
								
									
										142
									
								
								src/fe-scptoflux.cc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										142
									
								
								src/fe-scptoflux.cc
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,142 @@ | ||||
| #include "globals.h" | ||||
| #include "fluxmap.h" | ||||
| #include "sql.h" | ||||
| #include "bytes.h" | ||||
| #include "protocol.h" | ||||
| #include "fmt/format.h" | ||||
| #include "scp.h" | ||||
| #include <fstream> | ||||
|  | ||||
| static std::ifstream inputFile; | ||||
| static sqlite3* outputDb; | ||||
| static ScpHeader header; | ||||
| static nanoseconds_t resolution; | ||||
| static int startSide; | ||||
| static int endSide; | ||||
|  | ||||
| static void syntax() | ||||
| { | ||||
|     std::cout << "Syntax: fluxengine convert cwftoflux <cwffile> <fluxfile>\n"; | ||||
|     exit(0); | ||||
| } | ||||
|  | ||||
| static void check_for_error() | ||||
| { | ||||
|     if (inputFile.fail()) | ||||
|         Error() << fmt::format("I/O error: {}", strerror(errno)); | ||||
| } | ||||
|  | ||||
| static int trackno(int strack) | ||||
| { | ||||
|     if (startSide == endSide) | ||||
|         return strack; | ||||
|     return strack >> 1; | ||||
| } | ||||
|  | ||||
| static int headno(int strack) | ||||
| { | ||||
|     if (startSide == endSide) | ||||
|         return startSide; | ||||
|     return strack & 1; | ||||
| } | ||||
|  | ||||
| static void read_header() | ||||
| { | ||||
|     inputFile.read((char*) &header, sizeof(header)); | ||||
|     check_for_error(); | ||||
|  | ||||
|     if ((header.file_id[0] != 'S') | ||||
|             || (header.file_id[1] != 'C') | ||||
|             || (header.file_id[2] != 'P')) | ||||
|         Error() << "input not a SCP file"; | ||||
|  | ||||
|     resolution = 25 * (header.resolution + 1); | ||||
|     startSide = (header.heads == 2) ? 1 : 0; | ||||
|     endSide = (header.heads == 1) ? 0 : 1; | ||||
|  | ||||
|     if ((header.cell_width != 0) && (header.cell_width != 16)) | ||||
|         Error() << "currently only 16-bit cells are supported"; | ||||
|  | ||||
|     std::cout << fmt::format("tracks {}-{}, heads {}-{}\n", | ||||
|         trackno(header.start_track), trackno(header.end_track), startSide, endSide); | ||||
|     std::cout << fmt::format("sample resolution: {} ns\n", resolution); | ||||
| } | ||||
|  | ||||
| static void read_track(int strack) | ||||
| { | ||||
|     uint32_t offset = Bytes(header.track[strack], 4).reader().read_le32(); | ||||
|  | ||||
|     ScpTrack trackheader; | ||||
|     inputFile.seekg(offset, std::ios::beg); | ||||
|     inputFile.read((char*) &trackheader, sizeof(trackheader)); | ||||
|     check_for_error(); | ||||
|  | ||||
|     if ((trackheader.track_id[0] != 'T') | ||||
|             || (trackheader.track_id[1] != 'R') | ||||
|             || (trackheader.track_id[2] != 'K')) | ||||
|         Error() << "corrupt SCP file"; | ||||
|  | ||||
|     std::cout << fmt::format("{}.{}: ", trackno(strack), headno(strack)) | ||||
|         << std::flush; | ||||
|  | ||||
|     Fluxmap fluxmap; | ||||
|     nanoseconds_t pending = 0; | ||||
|     unsigned inputBytes = 0; | ||||
|     for (int revolution = 0; revolution < header.revolutions; revolution++) | ||||
|     { | ||||
|         if (revolution != 0) | ||||
|             fluxmap.appendIndex(); | ||||
|  | ||||
|         uint32_t datalength = Bytes(trackheader.revolution[revolution].length, 4).reader().read_le32(); | ||||
|         uint32_t dataoffset = Bytes(trackheader.revolution[revolution].offset, 4).reader().read_le32(); | ||||
|  | ||||
|         Bytes data(datalength*2); | ||||
|         inputFile.seekg(dataoffset + offset, std::ios::beg); | ||||
|         inputFile.read((char*) data.begin(), data.size()); | ||||
|         check_for_error(); | ||||
|  | ||||
|         ByteReader br(data); | ||||
|         for (int cell = 0; cell < datalength; cell++) | ||||
|         { | ||||
|             uint16_t interval = br.read_be16(); | ||||
|             if (interval) | ||||
|             { | ||||
|                 fluxmap.appendInterval((interval + pending) * resolution / NS_PER_TICK); | ||||
|                 fluxmap.appendPulse(); | ||||
|                 pending = 0; | ||||
|             } | ||||
|             else | ||||
|                 pending += 0x10000; | ||||
|         } | ||||
|  | ||||
|         inputBytes += datalength*2; | ||||
|     } | ||||
|  | ||||
|     std::cout << fmt::format(" {} ms in {} input bytes and {} output bytes\n", | ||||
|         fluxmap.duration() / 1e6, inputBytes, fluxmap.bytes()); | ||||
|     sqlWriteFlux(outputDb, trackno(strack), headno(strack), fluxmap); | ||||
| } | ||||
|  | ||||
| int mainConvertScpToFlux(int argc, const char* argv[]) | ||||
| { | ||||
|     if (argc != 3) | ||||
|         syntax(); | ||||
|      | ||||
|     inputFile.open(argv[1], std::ios::in | std::ios::binary); | ||||
|     if (!inputFile.is_open()) | ||||
| 		Error() << fmt::format("cannot open input file '{}'", argv[1]); | ||||
|  | ||||
|     outputDb = sqlOpen(argv[2], SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE); | ||||
|     sqlPrepareFlux(outputDb); | ||||
|     sqlWriteIntProperty(outputDb, "version", FLUX_VERSION_CURRENT); | ||||
|     sqlStmt(outputDb, "BEGIN;"); | ||||
|  | ||||
|     read_header(); | ||||
|     inputFile.seekg(sizeof(header), std::ios::beg); | ||||
|     for (unsigned i=header.start_track; i<=header.end_track; i++) | ||||
|         read_track(i); | ||||
|  | ||||
|     sqlStmt(outputDb, "COMMIT;"); | ||||
|     sqlClose(outputDb); | ||||
|     return 0; | ||||
| } | ||||
| @@ -10,19 +10,14 @@ | ||||
|  | ||||
| static FlagGroup flags { &writerFlags, &brotherEncoderFlags }; | ||||
|  | ||||
| static StringFlag inputFilename( | ||||
|     { "--input", "-i" }, | ||||
|     "The input image file to read from.", | ||||
|     "brother.img"); | ||||
|  | ||||
| int mainWriteBrother(int argc, const char* argv[]) | ||||
| { | ||||
|     setWriterDefaultInput(":c=78:h=1:s=12:b=256"); | ||||
| 	setWriterDefaultDest(":d=0:t=0-77:s=0"); | ||||
|     flags.parseFlags(argc, argv); | ||||
|  | ||||
| 	BrotherEncoder encoder; | ||||
| 	Geometry geometry = {78, 1, 12, 256}; | ||||
| 	writeDiskCommand(encoder, geometry, inputFilename); | ||||
| 	writeDiskCommand(encoder); | ||||
|  | ||||
|     return 0; | ||||
| } | ||||
|   | ||||
| @@ -5,7 +5,9 @@ typedef int command_cb(int agrc, const char* argv[]); | ||||
| extern command_cb mainErase; | ||||
| extern command_cb mainConvertCwfToFlux; | ||||
| extern command_cb mainConvertFluxToAu; | ||||
| extern command_cb mainConvertFluxToScp; | ||||
| extern command_cb mainConvertFluxToVcd; | ||||
| extern command_cb mainConvertScpToFlux; | ||||
| extern command_cb mainInspect; | ||||
| extern command_cb mainReadADFS; | ||||
| extern command_cb mainReadAESLanier; | ||||
| @@ -83,7 +85,9 @@ static std::vector<Command> writeables = | ||||
| static std::vector<Command> convertables = | ||||
| { | ||||
|     { "cwftoflux",     mainConvertCwfToFlux, "Converts CatWeasel stream files to flux.", }, | ||||
|     { "scptoflux",     mainConvertScpToFlux, "Converts Supercard Pro stream files to flux.", }, | ||||
|     { "fluxtoau",      mainConvertFluxToAu,  "Converts (one track of a) flux file to an .au audio file.", }, | ||||
|     { "fluxtoscp",     mainConvertFluxToScp, "Converrt a flux file to a Supercard Pro file.", }, | ||||
|     { "fluxtovcd",     mainConvertFluxToVcd, "Converts (one track of a) flux file to a VCD file.", }, | ||||
| }; | ||||
|  | ||||
|   | ||||
							
								
								
									
										43
									
								
								src/scp.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								src/scp.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,43 @@ | ||||
| #ifndef SCP_H | ||||
| #define SCP_H | ||||
|  | ||||
| struct ScpHeader | ||||
| { | ||||
|     char file_id[3];       // file ID - 'SCP' | ||||
|     uint8_t version;       // major/minor in nibbles | ||||
|     uint8_t type;          // disk type - subclass/class in nibbles | ||||
|     uint8_t revolutions;   // up to 5 | ||||
|     uint8_t start_track;   // 0..165 | ||||
|     uint8_t end_track;     // 0..165 | ||||
|     uint8_t flags;         // see below | ||||
|     uint8_t cell_width;    // in bits, 0 meaning 16 | ||||
|     uint8_t heads;         // 0 = both, 1 = side 0 only, 2 = side 1 only | ||||
|     uint8_t resolution;    // 25ns * (resolution+1) | ||||
|     uint8_t checksum[4];   // of data after this point | ||||
|     uint8_t track[165][4]; // track offsets, not necessarily 165 | ||||
| }; | ||||
|  | ||||
| enum | ||||
| { | ||||
|     SCP_FLAG_INDEXED    = (1<<0), | ||||
|     SCP_FLAG_96TPI      = (1<<1), | ||||
|     SCP_FLAG_360RPM     = (1<<2), | ||||
|     SCP_FLAG_NORMALIZED = (1<<3), | ||||
|     SCP_FLAG_READWRITE  = (1<<4), | ||||
|     SCP_FLAG_FOOTER     = (1<<5) | ||||
| }; | ||||
|  | ||||
| struct ScpTrack | ||||
| { | ||||
|     char track_id[3];      // 'TRK' | ||||
|     uint8_t strack;        // SCP track number | ||||
|     struct | ||||
|     { | ||||
|         uint8_t index[4];  // time for one revolution | ||||
|         uint8_t length[4]; // number of bitcells | ||||
|         uint8_t offset[4]; // offset to bitcell data, relative to track header | ||||
|     } | ||||
|     revolution[5]; | ||||
| }; | ||||
|  | ||||
| #endif | ||||
| @@ -33,48 +33,79 @@ static void test_parsemod(void) | ||||
|         == (DataSpec::Modifier{"x", {2, 4, 9}})); | ||||
| } | ||||
|  | ||||
| static void test_dataspec(void) | ||||
| static void test_fluxspec(void) | ||||
| { | ||||
|     DataSpec spec("foo:t=0-2:s=0-1:d=0"); | ||||
|     assert(spec.filename == "foo"); | ||||
|     assert((spec.locations | ||||
|         == std::vector<DataSpec::Location> | ||||
|         {{0, 0, 0}, {0, 0, 1}, {0, 1, 0}, {0, 1, 1}, {0, 2, 0}, {0, 2, 1}})); | ||||
|     assert((std::string)spec == "foo:d=0:s=0-1:t=0-2"); | ||||
|  | ||||
|     { | ||||
|         FluxSpec fspec(spec); | ||||
|         assert(fspec.filename == "foo"); | ||||
|         assert((fspec.locations | ||||
|             == std::vector<FluxSpec::Location> | ||||
|             {{0, 0, 0}, {0, 0, 1}, {0, 1, 0}, {0, 1, 1}, {0, 2, 0}, {0, 2, 1}})); | ||||
|         assert((std::string)spec == "foo:d=0:s=0-1:t=0-2"); | ||||
|     } | ||||
|  | ||||
|     spec.set("bar"); | ||||
|     assert(spec.filename == "bar"); | ||||
|     assert((spec.locations | ||||
|         == std::vector<DataSpec::Location> | ||||
|         {{0, 0, 0}, {0, 0, 1}, {0, 1, 0}, {0, 1, 1}, {0, 2, 0}, {0, 2, 1}})); | ||||
|     assert((std::string)spec == "bar:d=0:s=0-1:t=0-2"); | ||||
|     { | ||||
|         FluxSpec fspec(spec); | ||||
|         assert(fspec.filename == "bar"); | ||||
|         assert((fspec.locations | ||||
|             == std::vector<FluxSpec::Location> | ||||
|             {{0, 0, 0}, {0, 0, 1}, {0, 1, 0}, {0, 1, 1}, {0, 2, 0}, {0, 2, 1}})); | ||||
|         assert((std::string)spec == "bar:d=0:s=0-1:t=0-2"); | ||||
|     } | ||||
|  | ||||
|     spec.set(":t=0"); | ||||
|     assert(spec.filename.empty()); | ||||
|     assert((spec.locations | ||||
|         == std::vector<DataSpec::Location> | ||||
|         {{0, 0, 0}, {0, 0, 1}})); | ||||
|     assert((std::string)spec == ":d=0:s=0-1:t=0"); | ||||
|     { | ||||
|         FluxSpec fspec(spec); | ||||
|         assert(fspec.filename.empty()); | ||||
|         assert((fspec.locations | ||||
|             == std::vector<FluxSpec::Location> | ||||
|             {{0, 0, 0}, {0, 0, 1}})); | ||||
|         assert((std::string)spec == ":d=0:s=0-1:t=0"); | ||||
|     } | ||||
|  | ||||
|     spec.set(":s=1"); | ||||
|     assert(spec.filename.empty()); | ||||
|     assert((spec.locations | ||||
|         == std::vector<DataSpec::Location> | ||||
|         {{0, 0, 1}})); | ||||
|     assert((std::string)spec == ":d=0:s=1:t=0"); | ||||
|     { | ||||
|         FluxSpec fspec(spec); | ||||
|         assert(fspec.filename.empty()); | ||||
|         assert((fspec.locations | ||||
|             == std::vector<FluxSpec::Location> | ||||
|             {{0, 0, 1}})); | ||||
|         assert((std::string)spec == ":d=0:s=1:t=0"); | ||||
|     } | ||||
|  | ||||
|     spec.set(":t=9:d=1"); | ||||
|     assert(spec.filename.empty()); | ||||
|     assert((spec.locations | ||||
|         == std::vector<DataSpec::Location> | ||||
|         {{1, 9, 1}})); | ||||
|     assert((std::string)spec == ":d=1:s=1:t=9"); | ||||
|     { | ||||
|         FluxSpec fspec(spec); | ||||
|         assert(fspec.filename.empty()); | ||||
|         assert((fspec.locations | ||||
|             == std::vector<FluxSpec::Location> | ||||
|             {{1, 9, 1}})); | ||||
|         assert((std::string)spec == ":d=1:s=1:t=9"); | ||||
|     } | ||||
| } | ||||
|  | ||||
| static void test_imagespec(void) | ||||
| { | ||||
|     DataSpec spec("foo:c=9:h=2:s=99:b=256"); | ||||
|  | ||||
|     { | ||||
|         ImageSpec ispec(spec); | ||||
|         assert(ispec.filename == "foo"); | ||||
|         assert(ispec.cylinders == 9); | ||||
|         assert(ispec.heads == 2); | ||||
|         assert(ispec.sectors == 99); | ||||
|         assert(ispec.bytes = 256); | ||||
|     } | ||||
| } | ||||
|  | ||||
| int main(int argc, const char* argv[]) | ||||
| { | ||||
|     test_split(); | ||||
|     test_parsemod(); | ||||
|     test_dataspec(); | ||||
|     test_fluxspec(); | ||||
|     test_imagespec(); | ||||
|     return 0; | ||||
| } | ||||
|   | ||||
							
								
								
									
										70
									
								
								tests/ldbs.cc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										70
									
								
								tests/ldbs.cc
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,70 @@ | ||||
| #include <stdio.h> | ||||
| #include <unistd.h> | ||||
| #include <stdlib.h> | ||||
| #include "globals.h" | ||||
| #include "bytes.h" | ||||
| #include "ldbs.h" | ||||
|  | ||||
| static Bytes testdata | ||||
| { | ||||
|     'L',  'B',  'S',  0x01,  'D',  'S',  'K',  0x02, | ||||
|     0x29, 0x00, 0x00, 0x00,  0x00, 0x00, 0x00, 0x00, | ||||
|     0x34, 0x12, 0x00, 0x00,  'L',  'D',  'B',  0x01, | ||||
|     0x00, 0x00, 0x00, 0x01,  0x01, 0x00, 0x00, 0x00, | ||||
|     0x01, 0x00, 0x00, 0x00,  0x00, 0x00, 0x00, 0x00, | ||||
|     0x01, 'L',  'D',  'B',   0x01, 0x00, 0x00, 0x00, | ||||
|     0x02, 0x01, 0x00, 0x00,  0x00, 0x01, 0x00, 0x00, | ||||
|     0x00, 0x14, 0x00, 0x00,  0x00, 0x02 | ||||
| }; | ||||
|  | ||||
| static void assertBytes(const Bytes& want, const Bytes& got) | ||||
| { | ||||
|     if (want != got) | ||||
|     { | ||||
|         std::cout << "Wanted bytes:" << std::endl; | ||||
|         hexdump(std::cout, want); | ||||
|         std::cout << std::endl << "Produced bytes:" << std::endl; | ||||
|         hexdump(std::cout, got); | ||||
|         abort(); | ||||
|     } | ||||
| } | ||||
| static void test_getset() | ||||
| { | ||||
|     LDBS ldbs; | ||||
|  | ||||
|     uint32_t block1 = ldbs.put(Bytes { 1 }, 1); | ||||
|     uint32_t block2 = ldbs.put(Bytes { 2 }, 2); | ||||
|     assert(block1 != block2); | ||||
|  | ||||
|     assert(ldbs.get(block1) == Bytes { 1 }); | ||||
|     assert(ldbs.get(block2) == Bytes { 2 }); | ||||
| } | ||||
|  | ||||
| static void test_write() | ||||
| { | ||||
|     LDBS ldbs; | ||||
|  | ||||
|     uint32_t block1 = ldbs.put(Bytes { 1 }, 1); | ||||
|     uint32_t block2 = ldbs.put(Bytes { 2 }, 2); | ||||
|     Bytes data = ldbs.write(0x1234); | ||||
|  | ||||
|     assertBytes(testdata, data); | ||||
| } | ||||
|  | ||||
| static void test_read() | ||||
| { | ||||
|     LDBS ldbs; | ||||
|     uint32_t trackDirectory = ldbs.read(testdata); | ||||
|  | ||||
|     assert(trackDirectory == 0x1234); | ||||
|     assert(ldbs.get(0x14) == Bytes { 1 }); | ||||
|     assert(ldbs.get(0x29) == Bytes { 2 }); | ||||
| } | ||||
|  | ||||
| int main(int argc, const char* argv[]) | ||||
| { | ||||
|     test_getset(); | ||||
|     test_write(); | ||||
|     test_read(); | ||||
|     return 0; | ||||
| } | ||||
							
								
								
									
										56
									
								
								tools/brother240tool.cc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								tools/brother240tool.cc
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,56 @@ | ||||
| #include "globals.h" | ||||
| #include "fmt/format.h" | ||||
| #include <fstream> | ||||
|  | ||||
| static std::fstream inputFile; | ||||
|  | ||||
| void syntax() | ||||
| { | ||||
|     std::cout << "Syntax: brother240tool <image>\n" | ||||
|                  "The disk image will be flipped from Brother to DOS format and back\n" | ||||
|                  "again.\n"; | ||||
|     exit(0); | ||||
| } | ||||
|  | ||||
| uint8_t getbyte(uint32_t offset) | ||||
| { | ||||
|     inputFile.seekg(offset, std::ifstream::beg); | ||||
|     return inputFile.get(); | ||||
| } | ||||
|  | ||||
| void putbyte(uint32_t offset, uint8_t value) | ||||
| { | ||||
|     inputFile.seekp(offset, std::ifstream::beg); | ||||
|     inputFile.put(value); | ||||
| } | ||||
|  | ||||
| int main(int argc, const char* argv[]) | ||||
| { | ||||
|     if (argc < 2) | ||||
|         syntax(); | ||||
|      | ||||
|     inputFile.open(argv[1], std::ios::in | std::ios::out | std::ios::binary); | ||||
|     if (!inputFile.is_open()) | ||||
| 		Error() << fmt::format("cannot open input file '{}'", argv[1]); | ||||
|  | ||||
|     uint8_t b1 = getbyte(0x015); | ||||
|     uint8_t b2 = getbyte(0x100); | ||||
|     if ((b1 == 0x58) && (b2 == 0x58)) | ||||
|     { | ||||
|         std::cerr << "Flipping from Brother to DOS.\n"; | ||||
|         putbyte(0x015, 0xf0); | ||||
|         putbyte(0x100, 0xf0); | ||||
|     } | ||||
|     else if ((b1 == 0xf0) && (b2 == 0xf0)) | ||||
|     { | ||||
|         std::cerr << "Flipping from DOS to Brother.\n"; | ||||
|         putbyte(0x015, 0x58); | ||||
|         putbyte(0x100, 0x58); | ||||
|     } | ||||
|     else | ||||
|         Error() << "Unknown image format."; | ||||
|  | ||||
|     inputFile.close(); | ||||
|     return 0; | ||||
| } | ||||
|  | ||||
		Reference in New Issue
	
	Block a user