Compare commits

...

45 Commits

Author SHA1 Message Date
David Given
e9670e205e Don't erroneously crash out if the t and s parameters are omitted from the rpm
command.
2019-10-14 22:25:43 +02:00
David Given
658e2b7295 Properly initialise flags.
Fixes: #109
2019-09-22 21:55:37 +02:00
David Given
7b4a8d6de2 Merge pull request #108 from davidgiven/scp
Add very beta support for scp import and export
2019-09-21 22:32:52 +02:00
David Given
e8f7b51aef Another documentation tweak. 2019-09-21 22:15:44 +02:00
David Given
9d6bc57a5f Update documentation. 2019-08-31 12:32:52 +02:00
David Given
73766f92b4 Fix the flux to scp converter. 2019-08-31 12:31:33 +02:00
David Given
80badf3b54 Remember to check in the build file changes... 2019-08-31 12:09:11 +02:00
David Given
116529f85a Archival, non-working checkin of the flux to scp converter (it appears to
produce garbage).
2019-08-31 01:41:11 +02:00
David Given
5a2b2bc07a Allow support for command lines with non-argument filenames. 2019-08-31 01:40:44 +02:00
David Given
41070395c0 Merge pull request #105 from davidgiven/scp
Add conversion support for Supercard Pro scp files.
2019-08-28 23:52:27 +02:00
David Given
4304d1eede Add the Supercard Pro decoder. 2019-08-28 23:32:09 +02:00
David Given
46f1b0aef4 Merge pull request #104 from davidgiven/sampler
Fix a nasty sampler bug leading to corrupted data
2019-08-28 00:23:21 +02:00
David Given
9923d67a7c Merge pull request #103 from davidgiven/visualiser
Add a simple disk visualiser.
2019-08-28 00:20:49 +02:00
David Given
99335a84fd Add documentation for the visualiser. 2019-08-28 00:19:18 +02:00
David Given
c266779433 Fix a bug where index pulses where being turned into flux pulses on read,
leading to completely broken data whenever an index pulse happened.
2019-08-27 23:58:07 +02:00
David Given
bdcc12cd53 Correctly import M_PI. 2019-08-27 23:08:13 +02:00
David Given
7988d0fe24 Don't replace bad sectors with more bad sectors. This means that if a sector is
permanently bad, the one which wins and goes into the output SectorSet is the
first one, not the last one. Frequently the last sector is truncated by the end
of read and so it isn't useful.
2019-08-27 22:39:24 +02:00
David Given
27f5c294b1 The visualiser period can now be specified in a flag. 2019-08-27 01:21:49 +02:00
David Given
b9a53e0d1c First draft of the visualiser. 2019-08-27 01:07:57 +02:00
David Given
f8b6d5e6fb Merge. 2019-08-25 00:24:34 +02:00
David Given
04ff31c348 Add a flag to the IBM decoder to tell it to ignore the logical sector IDs (some
formats don't use these).
2019-08-25 00:24:02 +02:00
David Given
77b4aebd1b Fix crashing bug when reading Kryoflux streams. 2019-08-24 23:53:22 +02:00
David Given
4056364300 Merge pull request #99 from davidgiven/extensions
Validate image extensions before reading, not after.
2019-08-22 22:09:42 +02:00
David Given
60bfe050d3 Refactor the way image extensions are handled to be generally cleaner. Add
support for validating ImageSpecs before we actually want to read/write an
image, so as to allow us to check the extension *before* wasting time reading a
disk. Make .d81 an alias of .img.
2019-08-21 00:45:10 +02:00
David Given
28d0ce765e Merge pull request #93 from davidgiven/hex
Add precompiled firmware
2019-08-15 22:26:50 +02:00
David Given
4954d33307 Add documentation for using the precompiled firmware. 2019-08-15 22:19:30 +02:00
David Given
55f3354287 Add precompiled hex for the firmware. 2019-08-15 21:52:11 +02:00
David Given
d6ae373fa8 Merge pull request #92 from davidgiven/d64
Add write-only support for D64 disk images.
2019-08-15 20:46:02 +02:00
David Given
a626d5f9a0 Add write-only support for D64 disk images. 2019-08-15 20:30:07 +02:00
David Given
29db67528d Merge pull request #90 from davidgiven/write
Add support for writing LDBS image files.
2019-08-10 21:40:56 +02:00
David Given
31d7477c6a Add LDBS documentation. 2019-08-10 21:35:21 +02:00
David Given
56af9eaf18 The LDBS ST1 field is now populated correctly on error. 2019-08-10 21:25:07 +02:00
David Given
5de0636fe7 First attempt at writing LDBS files. Not quite right. 2019-08-09 23:21:55 +02:00
David Given
f9117b8d11 Added a simple library for reading and writing LDBS files. 2019-08-09 22:41:07 +02:00
David Given
10d385375f Merge pull request #87 from davidgiven/write
Add support for pluggable input and output formats.
2019-08-09 21:22:03 +02:00
David Given
2f72c3f8f0 Mac images now put the metadata second. 2019-08-09 21:13:29 +02:00
David Given
54edff9b94 Amiga disks can now optionally emit the metadata. 2019-08-09 21:04:48 +02:00
David Given
112377f885 Add pluggable image readers, plus some documentation. 2019-08-09 20:56:06 +02:00
David Given
87e29fc386 Merge from write branch (because we want the new image code). 2019-08-08 23:27:11 +02:00
David Given
b1db5c48b1 Ignore more temporary files. 2019-08-08 23:26:42 +02:00
David Given
38fab7edcb Refactor all the image writing stuff into a ImageWriter subclass hierarchy with
a factory based on extension.
2019-08-08 00:27:41 +02:00
David Given
d8172154c3 Output images now get geometry specs to indicate what kind of file to write. 2019-08-07 23:23:07 +02:00
David Given
eb924780ab Refactor dataspecs to allow them to be used for other things too. 2019-08-06 23:50:02 +02:00
David Given
28e0ef0463 Merge pull request #84 from davidgiven/cleanup
Mechanical refactor to rearrange the source files into a more pleasing order.
2019-08-06 22:33:05 +02:00
David Given
4b07c38782 Mechanical refactor to rearrange the source files into a more pleasing order. 2019-08-06 22:25:11 +02:00
107 changed files with 6531 additions and 437 deletions

View File

@@ -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

View File

@@ -3,6 +3,10 @@ streams
.*\.flux
.*\.img
.*\.raw
.*\.orig
.vscode
remote
FluxEngine.cydsn/CortexM3
FluxEngine.cydsn/Generated_Source
FluxEngine.cydsn/codegentemp

View File

File diff suppressed because it is too large Load Diff

View File

@@ -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>

View File

Binary file not shown.

View File

Binary file not shown.

View File

@@ -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++
@@ -22,7 +22,7 @@ export LIBS = $(shell pkg-config --libs $(PACKAGES))
export EXTENSION =
endif
CFLAGS += -Ilib -Idep/fmt
CFLAGS += -Ilib -Idep/fmt -Iarch
export OBJDIR = .obj

View File

View File

@@ -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;
}

View File

View File

View File

View File

View File

View File

View File

View File

@@ -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()

View File

@@ -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;
};

View File

@@ -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));
}

View File

View File

View File

@@ -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

View File

@@ -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
-----------------

View File

@@ -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
-----------------

View File

@@ -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
-----------------

View File

@@ -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:
![A disk visualisation](./visualiser.svg)
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
View File

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 394 KiB

View File

@@ -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);

View File

@@ -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;

View File

@@ -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;

View File

@@ -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)

View File

@@ -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);

View File

@@ -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

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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)

View File

@@ -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);

View File

@@ -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();
}

View File

@@ -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

View 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)
{}

View 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

View 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));
}

View 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));
}

View 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;
}
}

View 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

View 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));
}

View 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
View 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
View 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

View File

@@ -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;
}

View File

@@ -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

View File

@@ -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;

View File

@@ -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());
}
}
}

View File

@@ -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
View 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
View 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

View File

@@ -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>
{

View File

@@ -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

View File

@@ -137,13 +137,26 @@ buildlibrary libfmt.a \
dep/fmt/posix.cc \
buildlibrary libbackend.a \
lib/aeslanier/decoder.cc \
lib/amiga/decoder.cc \
lib/apple2/decoder.cc \
lib/brother/decoder.cc \
lib/brother/encoder.cc \
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 \
arch/brother/decoder.cc \
arch/brother/encoder.cc \
arch/c64/decoder.cc \
arch/f85/decoder.cc \
arch/fb100/decoder.cc \
arch/ibm/decoder.cc \
arch/macintosh/decoder.cc \
arch/mx/decoder.cc \
arch/victor9k/decoder.cc \
arch/zilogmcz/decoder.cc \
lib/bytes.cc \
lib/c64/decoder.cc \
lib/common/crunch.c \
lib/crc.cc \
lib/dataspec.cc \
@@ -151,8 +164,6 @@ buildlibrary libbackend.a \
lib/decoders/fluxmapreader.cc \
lib/decoders/fmmfm.cc \
lib/encoders/encoders.cc \
lib/f85/decoder.cc \
lib/fb100/decoder.cc \
lib/flags.cc \
lib/fluxmap.cc \
lib/fluxsink/fluxsink.cc \
@@ -165,23 +176,21 @@ buildlibrary libbackend.a \
lib/fluxsource/streamfluxsource.cc \
lib/globals.cc \
lib/hexdump.cc \
lib/ibm/decoder.cc \
lib/image.cc \
lib/macintosh/decoder.cc \
lib/mx/decoder.cc \
lib/ldbs.cc \
lib/reader.cc \
lib/sector.cc \
lib/sectorset.cc \
lib/sql.cc \
lib/usb.cc \
lib/victor9k/decoder.cc \
lib/visualiser.cc \
lib/writer.cc \
lib/zilogmcz/decoder.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,13 @@ buildsimpleprogram brother120tool \
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

View File

@@ -3,7 +3,7 @@
#include "fluxmap.h"
#include "writer.h"
static FlagGroup flags;
static FlagGroup flags { &writerFlags };
int mainErase(int argc, const char* argv[])
{

View File

@@ -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
View 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;
}

View File

@@ -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";

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

Some files were not shown because too many files have changed in this diff Show More