Compare commits

...

25 Commits

Author SHA1 Message Date
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
36 changed files with 5569 additions and 62 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

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

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

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

@@ -129,6 +129,34 @@ based on the extension:
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
@@ -193,10 +221,31 @@ directory.
file format in a non-backwards-compatible way; this tool will upgrade flux
files to the new format.
- `fluxengine convert`: converts various formats to various other formats.
You can use this to convert Catweasel or Supercard Pro flux files to
FluxEngine's native format, for flux files to various other formats useful
for debugging (including VCD which can be loaded into
[sigrok](http://sigrok.org)).
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
@@ -204,25 +253,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

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

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

@@ -7,6 +7,13 @@
#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())
@@ -14,15 +21,29 @@ static bool ends_with(const std::string& value, const std::string& ending)
return std::equal(ending.rbegin(), ending.rend(), value.rbegin());
}
std::unique_ptr<ImageReader> ImageReader::create(const ImageSpec& spec)
ImageReader::Constructor ImageReader::findConstructor(const ImageSpec& spec)
{
const auto& filename = spec.filename;
if (ends_with(filename, ".img") || ends_with(filename, ".adf"))
return createImgImageReader(spec);
for (const auto& e : formats)
{
if (ends_with(filename, e.first))
return e.second;
}
Error() << "unrecognised image filename extension";
return std::unique_ptr<ImageReader>();
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):

View File

@@ -12,10 +12,21 @@ public:
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;

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

@@ -7,6 +7,15 @@
#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())
@@ -14,15 +23,29 @@ static bool ends_with(const std::string& value, const std::string& ending)
return std::equal(ending.rbegin(), ending.rend(), value.rbegin());
}
std::unique_ptr<ImageWriter> ImageWriter::create(const SectorSet& sectors, const ImageSpec& spec)
ImageWriter::Constructor ImageWriter::findConstructor(const ImageSpec& spec)
{
const auto& filename = spec.filename;
if (ends_with(filename, ".img") || ends_with(filename, ".adf"))
return createImgImageWriter(sectors, spec);
for (const auto& e : formats)
{
if (ends_with(filename, e.first))
return e.second;
}
Error() << "unrecognised image filename extension";
return std::unique_ptr<ImageWriter>();
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):

View File

@@ -12,10 +12,25 @@ public:
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();

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,14 +9,16 @@
#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" },
@@ -33,6 +35,11 @@ static StringFlag destination(
"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");
@@ -142,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);
@@ -153,7 +160,8 @@ static void replace_sector(std::unique_ptr<Sector>& replacing, Sector& replaceme
void readDiskCommand(AbstractDecoder& decoder)
{
const ImageSpec outputSpec(output);
ImageWriter::verifyImageSpec(outputSpec);
bool failures = false;
SectorSet allSectors;
auto tracks = readTracks();
@@ -251,6 +259,9 @@ void readDiskCommand(AbstractDecoder& decoder)
std::cout << size << " bytes decoded." << std::endl;
}
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

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

@@ -17,6 +17,9 @@ public:
std::unique_ptr<Sector>& get(int track, int head, int sector);
Sector* get(int track, int head, int sector) 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;

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

@@ -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 \
@@ -168,18 +174,16 @@ buildlibrary libbackend.a \
lib/fluxsource/kryoflux.cc \
lib/fluxsource/sqlitefluxsource.cc \
lib/fluxsource/streamfluxsource.cc \
lib/imagereader/imagereader.cc \
lib/imagereader/imgimagereader.cc \
lib/imagewriter/imagewriter.cc \
lib/imagewriter/imgimagewriter.cc \
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 \
@@ -204,6 +208,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 \
@@ -227,12 +232,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

@@ -16,7 +16,7 @@ static FlagGroup flags { &readerFlags };
int mainReadC64(int argc, const char* argv[])
{
setReaderDefaultSource(":t=0-79x2:s=0");
setReaderDefaultOutput("c64.img");
setReaderDefaultOutput("c64.d64");
setReaderRevolutions(2);
flags.parseFlags(argc, argv);

View File

@@ -17,13 +17,18 @@ static IntFlag sectorIdBase(
"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);
IbmDecoder decoder(sectorIdBase, ignoreSideByte);
readDiskCommand(decoder);
return 0;
}

167
src/fe-scptoflux.cc Normal file
View File

@@ -0,0 +1,167 @@
#include "globals.h"
#include "fluxmap.h"
#include "sql.h"
#include "bytes.h"
#include "protocol.h"
#include "fmt/format.h"
#include <fstream>
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
};
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];
};
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;
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 += interval;
}
}
std::cout << fmt::format(" {} ms in {} output bytes\n",
fluxmap.duration() / 1e6, 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;
}

View File

@@ -6,6 +6,7 @@ extern command_cb mainErase;
extern command_cb mainConvertCwfToFlux;
extern command_cb mainConvertFluxToAu;
extern command_cb mainConvertFluxToVcd;
extern command_cb mainConvertScpToFlux;
extern command_cb mainInspect;
extern command_cb mainReadADFS;
extern command_cb mainReadAESLanier;
@@ -83,6 +84,7 @@ 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.", },
{ "fluxtovcd", mainConvertFluxToVcd, "Converts (one track of a) flux file to a VCD file.", },
};

70
tests/ldbs.cc Normal file
View 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;
}