Compare commits

...

66 Commits
trs80 ... gui2

Author SHA1 Message Date
dg
534a76f3c8 In the explorer, show data from all reads, not just the first (unless you're on
real hardware).
2022-11-30 21:23:09 +00:00
dg
d8528d889a Add another missing file. 2022-11-29 20:55:45 +00:00
dg
ec439a5f2a Add file which has been missing since forever... 2022-11-29 20:55:07 +00:00
David Given
d88110488e Merge pull request #624 from davidgiven/flx
Add support for reading FLUXCOPY .flx files.
2022-11-29 21:53:13 +01:00
dg
2f4b15293a Adjust the FLX tick rate to something more resembling normality. 2022-11-29 20:26:58 +00:00
dg
3d71f32587 Update documentation. 2022-11-29 20:18:21 +00:00
dg
5136dda598 Made the FLX reader work. 2022-11-29 20:17:24 +00:00
dg
d26940304b Boilerplate for a FLX file reader. 2022-11-29 19:33:47 +00:00
David Given
f9f11b4966 Merge pull request #623 from davidgiven/smaky
Improve the flux viewer.
2022-11-28 21:42:23 +01:00
dg
1297b568ba Display the raw flux view in bytes as well as bits. 2022-11-28 20:22:34 +00:00
dg
fd10840cc0 Display raw bits in the flux viewer. 2022-11-28 20:15:57 +00:00
David Given
7e86be979d Merge pull request #622 from davidgiven/smaky
Add basic filesystem support for the Smaky 6.
2022-11-28 00:00:48 +01:00
dg
731d1efcc4 Add missing file. 2022-11-27 22:38:44 +00:00
dg
dfda8be30c Support tabs in the file viewer text view. 2022-11-27 22:24:30 +00:00
dg
7dd1b6d8e9 Add Smaky 6 filesystem documentation. 2022-11-27 22:23:55 +00:00
dg
ec1bcdb9e5 Add a simple Smaky 6 filesystem backend. 2022-11-27 22:22:59 +00:00
dg
c0f46d2bd4 Create new branch named "smaky" 2022-11-27 22:21:41 +00:00
David Given
615dca3130 Merge pull request #621 from davidgiven/osx
Add a proper OSX GUI application.
2022-11-27 17:27:04 +01:00
David Given
9cf9597c54 Try and make an OSX release. 2022-11-27 17:26:43 +01:00
David Given
e24ee648e7 Add license texts for the included libraries. 2022-11-27 17:06:28 +01:00
David Given
e3518dc389 Upload the OSX package. 2022-11-27 16:59:45 +01:00
David Given
693ba20606 Add rules to build the OSX package. 2022-11-27 16:58:50 +01:00
dg
b947c6c186 Add the skeleton of an OSX application. 2022-11-27 15:00:31 +00:00
David Given
7f8ecb8514 Fix broken conditional due to typo. 2022-11-27 15:18:07 +01:00
David Given
4df6afa9c1 Merge pull request #620 from davidgiven/b593
Correct generation of SCP files.
2022-11-27 13:28:02 +01:00
dg
d0620f8efe Fix readTracks to use locations. 2022-11-27 11:55:25 +00:00
dg
2b1a6dbb03 Make the scp flux sink use the layout to determine which tracks and sectors are
being written rather than the range flags directly.
2022-11-27 11:52:16 +00:00
David Given
c6ef667c3f Merge pull request #619 from davidgiven/b602
Fix weird error when trying to create fatfs filesystems.
2022-11-27 12:33:33 +01:00
dg
8c1d1bec93 Create new branch named "b593" 2022-11-27 11:20:30 +00:00
dg
8be174e65a Fix incorrect types when calling fatfs which was leading to really weird
behaviour when optimisation was enabled.
2022-11-27 11:11:26 +00:00
dg
6d37fafb02 Add a helper function for checking if a file exists. 2022-11-27 11:10:58 +00:00
dg
46ce882daa Allow implicit creation of disk images when writing. 2022-11-27 11:09:46 +00:00
David Given
6d14cbdb9b Merge pull request #618 from davidgiven/b609
Fix --doc to not hang.
2022-11-27 11:57:38 +01:00
dg
4bf6b433ae Create new branch named "b602" 2022-11-27 10:26:11 +00:00
dg
87a1b2e6f8 Don't hang when calling findAllProtoFields on recursive protos. 2022-11-27 10:24:40 +00:00
dg
c6ee48ec85 Create new branch named "b609" 2022-11-27 10:04:42 +00:00
David Given
b58a6b1649 Merge pull request #617 from davidgiven/smaky
Add support for the Smaky 6.
2022-11-27 01:00:21 +01:00
dg
bd9736df93 Typo fix. 2022-11-26 23:59:50 +00:00
dg
3b9c966e3d Add Smaky documentation. 2022-11-26 23:34:20 +00:00
dg
96c9a89171 Typo fixes. 2022-11-26 23:28:23 +00:00
dg
c374ffd15e Increase the smaky disk signature to 32 bits. 2022-11-26 22:56:45 +00:00
dg
c53109e1a1 Add the disk explorer. 2022-11-26 22:56:24 +00:00
dg
4598b3a7a6 Fix line endings. 2022-11-26 11:19:05 +00:00
dg
cf975b74bf Rename smaky to smaky6. 2022-11-26 11:18:38 +00:00
dg
5d65dcf3c8 Even better Smaky reads, and maybe with sector 0 in the right place. 2022-11-25 21:28:59 +00:00
dg
f299ec1f8d More Smaky improvements. 2022-11-25 20:23:24 +00:00
dg
6677034774 Improved record view. 2022-11-25 20:22:52 +00:00
dg
3c7c4639a9 Massively improved the quality of Smaky reads. 2022-11-25 19:33:29 +00:00
dg
7e9a1268a5 Add an extremely prototype version of the Smaky decoder. 2022-11-23 21:44:40 +00:00
dg
a60b8e68ca Add a Bytes method for reversing each bit. 2022-11-23 21:44:10 +00:00
dg
b2161aa67e Create new branch named "smaky" 2022-11-22 19:09:54 +00:00
David Given
d1fffb1d08 Merge pull request #616 from davidgiven/gui
Fix a crash when using the GUI.
2022-11-21 20:37:52 +01:00
dg
52d66d9555 Only iterate the environment if the set has been created. 2022-11-21 19:10:37 +00:00
David Given
66f2d359e2 Merge pull request #615 from davidgiven/vfs
Don't define a global structure called Entry
2022-11-21 20:07:51 +01:00
dg
8327f33ee6 Create new branch named "gui" 2022-11-21 18:37:47 +00:00
dg
d4a94551d9 Don't define a global structure called Entry because it conflicts with
something in adflib.
2022-11-21 18:31:59 +00:00
dg
d2a545d83e Merge from master. 2022-11-21 17:39:39 +00:00
David Given
aee4eac271 Merge pull request #613 from davidgiven/proto
Do some long needed proto cleanup
2022-11-21 00:13:32 +01:00
David Given
cbeddb11bc Fix one last use of has_ to identify a flux writer type. 2022-11-20 23:37:04 +01:00
David Given
4c51cf8316 Update documentation. 2022-11-20 23:36:47 +01:00
David Given
345cd6bd92 Convert the VFS protos to use enums rather than oneofs. 2022-11-20 10:37:45 +01:00
David Given
48540245b5 Convert the imagereader/writer to use proto enums rather than oneofs. 2022-11-20 10:25:19 +01:00
David Given
088bd9434d Switch from using a oneof to an explicit enum for the flux source/sink
configurations, as this allows default options for multiple source/sink types.
2022-11-19 22:52:52 +01:00
David Given
8ba8c58377 Add a config for the ICL Model 30. 2022-11-19 20:10:39 +01:00
David Given
4ae43d0503 Merge pull request #607 from tdaede/fix_layout_cache
Remove the layout cache and rename layout -> trackInfo.
2022-10-13 23:01:44 +02:00
Thomas Daede
d20ce3dde7 Remove the layout cache and rename layout -> trackInfo.
The layout cache is busted and seems not worth the effort.

Fixes #603.
2022-10-02 09:43:48 -07:00
124 changed files with 4562 additions and 1775 deletions

View File

@@ -21,6 +21,12 @@ jobs:
- name: make
run: gmake
- name: Upload build artifacts
uses: actions/upload-artifact@v2
with:
name: ${{ github.event.repository.name }}.${{ github.sha }}
path: FluxEngine.pkg
build-windows:
runs-on: windows-latest
defaults:
@@ -62,4 +68,4 @@ jobs:
uses: actions/upload-artifact@v2
with:
name: ${{ github.event.repository.name }}.${{ github.sha }}
path: fluxengine.zip
path: fluxengine-windows.zip

View File

@@ -77,3 +77,42 @@ jobs:
tag_name: dev
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
build-macos:
runs-on: macos-latest
steps:
- uses: actions/checkout@v2
- name: brew
run: brew install sqlite pkg-config libusb protobuf wxwidgets fmt make coreutils
- name: make
run: gmake
- name: tag
uses: EndBug/latest-tag@latest
with:
tag-name: dev
force-branch: false
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: delete-old-assets
uses: mknejp/delete-release-assets@v1
with:
token: ${{ github.token }}
tag: dev
assets: |
FluxEngine.pkg
fail-if-no-assets: false
- name: release
uses: softprops/action-gh-release@v1
with:
name: Development build ${{ env.RELEASE_DATE }}
files: |
FluxEngine.pkg
tag_name: dev
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -48,7 +48,7 @@ AR ?= $(CCPREFIX)ar
PKG_CONFIG ?= pkg-config
WX_CONFIG ?= wx-config
PROTOC ?= protoc
CFLAGS ?= -g -O3
CFLAGS ?= -g -O0
CXXFLAGS += -std=c++17
LDFLAGS ?=
PLATFORM ?= UNIX
@@ -111,6 +111,7 @@ PROTOS = \
arch/micropolis/micropolis.proto \
arch/mx/mx.proto \
arch/northstar/northstar.proto \
arch/smaky6/smaky6.proto \
arch/tids990/tids990.proto \
arch/victor9k/victor9k.proto \
arch/zilogmcz/zilogmcz.proto \
@@ -159,12 +160,18 @@ include tests/build.mk
do-encodedecodetest = $(eval $(do-encodedecodetest-impl))
define do-encodedecodetest-impl
tests: $(OBJDIR)/$1$3.encodedecode
$(OBJDIR)/$1$3.encodedecode: scripts/encodedecodetest.sh $(FLUXENGINE_BIN) $2
tests: $(OBJDIR)/$1$3.flux.encodedecode
$(OBJDIR)/$1$3.flux.encodedecode: scripts/encodedecodetest.sh $(FLUXENGINE_BIN) $2
@mkdir -p $(dir $$@)
@echo ENCODEDECODETEST $1 $3
@echo ENCODEDECODETEST .flux $1 $3
@scripts/encodedecodetest.sh $1 flux $(FLUXENGINE_BIN) $2 $3 > $$@
tests: $(OBJDIR)/$1$3.scp.encodedecode
$(OBJDIR)/$1$3.scp.encodedecode: scripts/encodedecodetest.sh $(FLUXENGINE_BIN) $2
@mkdir -p $(dir $$@)
@echo ENCODEDECODETEST .scp $1 $3
@scripts/encodedecodetest.sh $1 scp $(FLUXENGINE_BIN) $2 $3 > $$@
endef
$(call do-encodedecodetest,amiga)

View File

@@ -116,6 +116,7 @@ people who've had it work).
| [Macintosh 400kB/800kB](doc/disk-macintosh.md) | 🦄 | 🦄 | |
| [NEC PC-98](doc/disk-ibm.md) | 🦄 | 🦄 | trimode drive not required |
| [Sharp X68000](doc/disk-ibm.md) | 🦄 | 🦄 | |
| [Smaky 6](doc/disk-smaky6.md) | 🦖 | | 5.25" hard sectored |
| [TRS-80](doc/disk-trs80.md) | 🦖 | 🦖* | a minor variation of the IBM scheme |
{: .datatable }

View File

@@ -13,16 +13,6 @@ static const FluxPattern SECTOR_PATTERN(32, AESLANIER_RECORD_SEPARATOR);
/* This is actually M2FM, rather than MFM, but it our MFM/FM decoder copes fine with it. */
static Bytes reverse_bits(const Bytes& input)
{
Bytes output;
ByteWriter bw(output);
for (uint8_t b : input)
bw.write_8(reverse_bits(b));
return output;
}
class AesLanierDecoder : public Decoder
{
public:
@@ -43,7 +33,7 @@ public:
const auto& rawbits = readRawBits(AESLANIER_RECORD_SIZE*16);
const auto& bytes = decodeFmMfm(rawbits).slice(0, AESLANIER_RECORD_SIZE);
const auto& reversed = reverse_bits(bytes);
const auto& reversed = bytes.reverseBits();
_sector->logicalTrack = reversed[1];
_sector->logicalSide = 0;

View File

@@ -1,5 +1,7 @@
LIBARCH_SRCS = \
arch/aeslanier/decoder.cc \
arch/agat/agat.cc \
arch/agat/decoder.cc \
arch/amiga/amiga.cc \
arch/amiga/decoder.cc \
arch/amiga/encoder.cc \
@@ -16,18 +18,17 @@ LIBARCH_SRCS = \
arch/ibm/encoder.cc \
arch/macintosh/decoder.cc \
arch/macintosh/encoder.cc \
arch/micropolis/decoder.cc \
arch/micropolis/encoder.cc \
arch/mx/decoder.cc \
arch/northstar/decoder.cc \
arch/northstar/encoder.cc \
arch/smaky6/decoder.cc \
arch/tids990/decoder.cc \
arch/tids990/encoder.cc \
arch/victor9k/decoder.cc \
arch/victor9k/encoder.cc \
arch/zilogmcz/decoder.cc \
arch/tids990/decoder.cc \
arch/tids990/encoder.cc \
arch/micropolis/decoder.cc \
arch/micropolis/encoder.cc \
arch/northstar/decoder.cc \
arch/northstar/encoder.cc \
arch/agat/agat.cc \
arch/agat/decoder.cc \
LIBARCH_OBJS = $(patsubst %.cc, $(OBJDIR)/%.o, $(LIBARCH_SRCS))
OBJS += $(LIBARCH_OBJS)

154
arch/smaky6/decoder.cc Normal file
View File

@@ -0,0 +1,154 @@
#include "globals.h"
#include "fluxmap.h"
#include "decoders/fluxmapreader.h"
#include "protocol.h"
#include "decoders/decoders.h"
#include "sector.h"
#include "smaky6.h"
#include "bytes.h"
#include "crc.h"
#include "fmt/format.h"
#include "lib/decoders/decoders.pb.h"
#include <string.h>
#include <algorithm>
static const FluxPattern SECTOR_PATTERN(32, 0x54892aaa);
class Smaky6Decoder : public Decoder
{
public:
Smaky6Decoder(const DecoderProto& config):
Decoder(config),
_config(config.smaky6())
{
}
private:
/* Returns the sector ID of the _current_ sector. */
int advanceToNextSector()
{
auto previous = tell();
seekToIndexMark();
auto now = tell();
if ((now.ns() - previous.ns()) < 9e6)
{
seekToIndexMark();
auto next = tell();
if ((next.ns() - now.ns()) < 9e6)
{
/* We just found sector 0. */
_sectorId = 0;
}
else
{
/* Spurious... */
seek(now);
}
}
return _sectorId++;
}
public:
void beginTrack() override
{
/* Find the start-of-track index marks, which will be an interval
* of about 6ms. */
seekToIndexMark();
_sectorId = 99;
for (;;)
{
auto pos = tell();
advanceToNextSector();
if (_sectorId < 99)
{
seek(pos);
break;
}
if (eof())
return;
}
/* Now we know where to start counting, start finding sectors. */
_sectorStarts.clear();
for (;;)
{
auto now = tell();
if (eof())
break;
int id = advanceToNextSector();
if (id < 16)
_sectorStarts.push_back(std::make_pair(id, now));
}
_sectorIndex = 0;
}
nanoseconds_t advanceToNextRecord() override
{
if (_sectorIndex == _sectorStarts.size())
{
seekToIndexMark();
return 0;
}
const auto& p = _sectorStarts[_sectorIndex++];
_sectorId = p.first;
seek(p.second);
nanoseconds_t clock = seekToPattern(SECTOR_PATTERN);
_sector->headerStartTime = tell().ns();
return clock;
}
void decodeSectorRecord() override
{
readRawBits(33);
const auto& rawbits = readRawBits(SMAKY6_RECORD_SIZE * 16);
if (rawbits.size() < SMAKY6_SECTOR_SIZE)
return;
const auto& rawbytes =
toBytes(rawbits).slice(0, SMAKY6_RECORD_SIZE * 16);
/* The Smaky bytes are stored backwards! Backwards! */
const auto& bytes =
decodeFmMfm(rawbits).slice(0, SMAKY6_RECORD_SIZE).reverseBits();
ByteReader br(bytes);
uint8_t track = br.read_8();
Bytes data = br.read(SMAKY6_SECTOR_SIZE);
uint8_t wantedChecksum = br.read_8();
uint8_t gotChecksum = sumBytes(data) & 0xff;
if (track != _sector->physicalTrack)
return;
_sector->logicalTrack = _sector->physicalTrack;
_sector->logicalSide = _sector->physicalSide;
_sector->logicalSector = _sectorId;
_sector->data = data;
_sector->status =
(wantedChecksum == gotChecksum) ? Sector::OK : Sector::BAD_CHECKSUM;
}
private:
const Smaky6DecoderProto& _config;
nanoseconds_t _startOfTrack;
std::vector<std::pair<int, Fluxmap::Position>> _sectorStarts;
int _sectorId;
int _sectorIndex;
};
std::unique_ptr<Decoder> createSmaky6Decoder(const DecoderProto& config)
{
return std::unique_ptr<Decoder>(new Smaky6Decoder(config));
}

10
arch/smaky6/smaky6.h Normal file
View File

@@ -0,0 +1,10 @@
#ifndef SMAKY6_H
#define SMAKY6_H
#define SMAKY6_SECTOR_SIZE 256
#define SMAKY6_RECORD_SIZE (1 + SMAKY6_SECTOR_SIZE + 1)
extern std::unique_ptr<Decoder> createSmaky6Decoder(const DecoderProto& config);
#endif

6
arch/smaky6/smaky6.proto Normal file
View File

@@ -0,0 +1,6 @@
syntax = "proto2";
import "lib/common.proto";
message Smaky6DecoderProto {}

43
doc/disk-smaky6.md Normal file
View File

@@ -0,0 +1,43 @@
Disk: Smaky 6
=============
The Smaky 6 is a Swiss computer from 1978 produced by Epsitec. It's based
around a Z80 processor and has one or two Micropolis 5.25" drives which use
16-sector hard sectored disks. The disk format is single-sided with 77 tracks
and 256-byte sectors, resulting in 308kB disks. It uses MFM with a custom
sector record scheme. It was later superceded by a 68000-based Smaky which used
different disks.
FluxEngine supports these, although because the Micropolis drives use a 100tpi
track pitch, you can't read Smaky 6 disks with a normal PC 96tpi drive. You
will have to find a 100tpi drive from somewhere (they're rare).
Reading disks
-------------
You must use a 100-tpi 80-track 5.25" floppy drive.
To read a Smaky 6 floppy, do:
```
fluxengine read smaky6
```
You should end up with a `smaky6.img` file.
Filesystem access
-----------------
There is experimental read-only support for the Smaky 6 filesystem, allowing
the directory to be listed and files read from disks. It's not known whether
this is completely correct, so don't trust it! See the [Filesystem
Access](filesystem.md) page for more information.
Useful references
-----------------
- [Smaky Info, 1978-2002 (in French)](https://www.smaky.ch/theme.php?id=sminfo)

View File

@@ -23,6 +23,7 @@ The following file systems are supported so far.
| FatFS (a.k.a. MS-DOS) | Y | Y | FAT12, FAT16, FAT32; not Atari (AFAIK!) |
| Macintosh HFS | Y | Y | Only AppleDouble files may be written |
| Apple ProDOS | Y | | |
| Smaky 6 | Y | | |
{: .datatable }
Please not that Atari disks do _not_ use standard FatFS, and the library I'm
@@ -82,7 +83,7 @@ default; for example, Macintosh HFS filesystems are common on 3.5" floppies. You
can do this as follows:
```
fluxengine format ibm1440 -f drive:1 --filesystem.machfs=
fluxengine format ibm1440 -f drive:1 --filesystem.type=MACHFS
```
Some filesystems won't work on some disks --- don't try this with Amiga FFS, for

BIN
doc/ju475-hd-hi.png Normal file
View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

BIN
doc/ju475-hd-lo.png Normal file
View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

View File

@@ -213,6 +213,11 @@ FluxEngine supports a number of ways to get or put flux. When using the `-s` or
Read from a Kryoflux stream, where `<path>` is the directory containing the
stream files. **Read only.**
- `flx:<directory>`
Read from a FLUXCOPY stream, where `<path>` is the directory containing the
stream files. **Read only.**
- `erase:`
Read nothing --- writing this to a disk will magnetically erase a track.

View File

@@ -0,0 +1,41 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>English</string>
<key>CFBundleExecutable</key>
<string>FluxEngine.sh</string>
<key>CFBundleGetInfoString</key>
<string>FluxEngine 1.0</string>
<key>CFBundleIconFile</key>
<string>FluxEngine.icns</string>
<key>CFBundleIdentifier</key>
<string>com.cowlark.fluxengine.gui</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleLocalizations</key>
<array>
<string>en</string>
</array>
<key>CFBundleName</key>
<string>FluxEngine</string>
<key>CFBundleDisplayName</key>
<string>FluxEngine</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleSignature</key>
<string>FluxEngine</string>
<key>CFBundleVersion</key>
<string>1.0</string>
<key>LSMinimumSystemVersion</key>
<string>10.13.0</string>
<key>LSRequiresCarbon</key>
<true/>
<key>NSAppleScriptEnabled</key>
<true/>
</dict>
</plist>

View File

@@ -0,0 +1,6 @@
#!/bin/sh
dir=`dirname "$0"`
cd "$dir"
export DYLD_FALLBACK_FRAMEWORK_PATH=../Resources
exec ./fluxengine-gui "$@"

View File

@@ -22,6 +22,8 @@ LIBFLUXENGINE_SRCS = \
lib/fluxsource/erasefluxsource.cc \
lib/fluxsource/fl2fluxsource.cc \
lib/fluxsource/fluxsource.cc \
lib/fluxsource/flx.cc \
lib/fluxsource/flxfluxsource.cc \
lib/fluxsource/hardwarefluxsource.cc \
lib/fluxsource/kryoflux.cc \
lib/fluxsource/kryofluxfluxsource.cc \
@@ -74,6 +76,7 @@ LIBFLUXENGINE_SRCS = \
lib/vfs/fatfs.cc \
lib/vfs/machfs.cc \
lib/vfs/prodos.cc \
lib/vfs/smaky6fs.cc \
lib/vfs/vfs.cc \
lib/vfs/fluxsectorinterface.cc \
lib/vfs/imagesectorinterface.cc \

View File

@@ -205,6 +205,16 @@ std::vector<bool> Bytes::toBits() const
return bits;
}
Bytes Bytes::reverseBits() const
{
Bytes output;
ByteWriter bw(output);
for (uint8_t b : *this)
bw.write_8(reverse_bits(b));
return output;
}
uint8_t toByte(
std::vector<bool>::const_iterator start,
std::vector<bool>::const_iterator end)

View File

@@ -62,6 +62,7 @@ public:
Bytes compress() const;
Bytes decompress() const;
std::vector<bool> toBits() const;
Bytes reverseBits() const;
ByteReader reader() const;
ByteWriter writer();

View File

@@ -10,6 +10,7 @@ message RangeProto {
extend google.protobuf.FieldOptions {
optional string help = 50000;
optional bool recurse = 50001 [default = true];
}
enum IndexMode {

View File

@@ -44,6 +44,6 @@ message OptionProto {
optional string name = 1 [(help) = "Option name" ];
optional string comment = 2 [(help) = "Help text for option" ];
optional string message = 3 [(help) = "Message to display when option is in use" ];
optional ConfigProto config = 4 [(help) = "Option data" ];
optional ConfigProto config = 4 [(help) = "Option data", (recurse) = false ];
}

View File

@@ -1,223 +1,226 @@
#include "globals.h"
#include "flags.h"
#include "fluxmap.h"
#include "decoders/decoders.h"
#include "encoders/encoders.h"
#include "arch/agat/agat.h"
#include "arch/aeslanier/aeslanier.h"
#include "arch/amiga/amiga.h"
#include "arch/apple2/apple2.h"
#include "arch/brother/brother.h"
#include "arch/c64/c64.h"
#include "arch/f85/f85.h"
#include "arch/fb100/fb100.h"
#include "arch/ibm/ibm.h"
#include "arch/macintosh/macintosh.h"
#include "arch/micropolis/micropolis.h"
#include "arch/mx/mx.h"
#include "arch/northstar/northstar.h"
#include "arch/tids990/tids990.h"
#include "arch/victor9k/victor9k.h"
#include "arch/zilogmcz/zilogmcz.h"
#include "decoders/fluxmapreader.h"
#include "flux.h"
#include "protocol.h"
#include "decoders/rawbits.h"
#include "sector.h"
#include "image.h"
#include "lib/decoders/decoders.pb.h"
#include "lib/layout.h"
#include "fmt/format.h"
#include <numeric>
std::unique_ptr<Decoder> Decoder::create(const DecoderProto& config)
{
static const std::map<int,
std::function<std::unique_ptr<Decoder>(const DecoderProto&)>>
decoders = {
{DecoderProto::kAgat, createAgatDecoder },
{DecoderProto::kAeslanier, createAesLanierDecoder },
{DecoderProto::kAmiga, createAmigaDecoder },
{DecoderProto::kApple2, createApple2Decoder },
{DecoderProto::kBrother, createBrotherDecoder },
{DecoderProto::kC64, createCommodore64Decoder},
{DecoderProto::kF85, createDurangoF85Decoder },
{DecoderProto::kFb100, createFb100Decoder },
{DecoderProto::kIbm, createIbmDecoder },
{DecoderProto::kMacintosh, createMacintoshDecoder },
{DecoderProto::kMicropolis, createMicropolisDecoder },
{DecoderProto::kMx, createMxDecoder },
{DecoderProto::kNorthstar, createNorthstarDecoder },
{DecoderProto::kTids990, createTids990Decoder },
{DecoderProto::kVictor9K, createVictor9kDecoder },
{DecoderProto::kZilogmcz, createZilogMczDecoder },
};
auto decoder = decoders.find(config.format_case());
if (decoder == decoders.end())
Error() << "no decoder specified";
return (decoder->second)(config);
}
std::shared_ptr<TrackDataFlux> Decoder::decodeToSectors(
std::shared_ptr<const Fluxmap> fluxmap,
std::shared_ptr<const TrackInfo>& trackInfo)
{
_trackdata = std::make_shared<TrackDataFlux>();
_trackdata->fluxmap = fluxmap;
_trackdata->trackInfo = trackInfo;
FluxmapReader fmr(*fluxmap);
_fmr = &fmr;
auto newSector = [&]
{
_sector = std::make_shared<Sector>(trackInfo, 0);
_sector->status = Sector::MISSING;
};
newSector();
beginTrack();
for (;;)
{
newSector();
Fluxmap::Position recordStart = fmr.tell();
_sector->clock = advanceToNextRecord();
if (fmr.eof() || !_sector->clock)
return _trackdata;
/* Read the sector record. */
Fluxmap::Position before = fmr.tell();
decodeSectorRecord();
Fluxmap::Position after = fmr.tell();
pushRecord(before, after);
if (_sector->status != Sector::DATA_MISSING)
{
_sector->position = before.bytes;
_sector->dataStartTime = before.ns();
_sector->dataEndTime = after.ns();
}
else
{
/* The data is in a separate record. */
for (;;)
{
_sector->headerStartTime = before.ns();
_sector->headerEndTime = after.ns();
_sector->clock = advanceToNextRecord();
if (fmr.eof() || !_sector->clock)
break;
before = fmr.tell();
decodeDataRecord();
after = fmr.tell();
if (_sector->status != Sector::DATA_MISSING)
{
_sector->position = before.bytes;
_sector->dataStartTime = before.ns();
_sector->dataEndTime = after.ns();
pushRecord(before, after);
break;
}
fmr.skipToEvent(F_BIT_PULSE);
resetFluxDecoder();
}
}
if (_sector->status != Sector::MISSING)
{
auto trackLayout = Layout::getLayoutOfTrack(
_sector->logicalTrack, _sector->logicalSide);
_trackdata->sectors.push_back(_sector);
}
}
}
void Decoder::pushRecord(
const Fluxmap::Position& start, const Fluxmap::Position& end)
{
Fluxmap::Position here = _fmr->tell();
auto record = std::make_shared<Record>();
_trackdata->records.push_back(record);
_sector->records.push_back(record);
record->startTime = start.ns();
record->endTime = end.ns();
record->clock = _sector->clock;
record->rawData = toBytes(_recordBits);
_recordBits.clear();
}
void Decoder::resetFluxDecoder()
{
_decoder.reset(new FluxDecoder(_fmr, _sector->clock, _config));
}
nanoseconds_t Decoder::seekToPattern(const FluxMatcher& pattern)
{
nanoseconds_t clock = _fmr->seekToPattern(pattern);
_decoder.reset(new FluxDecoder(_fmr, clock, _config));
return clock;
}
void Decoder::seekToIndexMark()
{
_fmr->skipToEvent(F_BIT_PULSE);
_fmr->seekToIndexMark();
}
std::vector<bool> Decoder::readRawBits(unsigned count)
{
auto bits = _decoder->readBits(count);
_recordBits.insert(_recordBits.end(), bits.begin(), bits.end());
return bits;
}
uint8_t Decoder::readRaw8()
{
return toBytes(readRawBits(8)).reader().read_8();
}
uint16_t Decoder::readRaw16()
{
return toBytes(readRawBits(16)).reader().read_be16();
}
uint32_t Decoder::readRaw20()
{
std::vector<bool> bits(4);
for (bool b : readRawBits(20))
bits.push_back(b);
return toBytes(bits).reader().read_be24();
}
uint32_t Decoder::readRaw24()
{
return toBytes(readRawBits(24)).reader().read_be24();
}
uint32_t Decoder::readRaw32()
{
return toBytes(readRawBits(32)).reader().read_be32();
}
uint64_t Decoder::readRaw48()
{
return toBytes(readRawBits(48)).reader().read_be48();
}
uint64_t Decoder::readRaw64()
{
return toBytes(readRawBits(64)).reader().read_be64();
}
#include "globals.h"
#include "flags.h"
#include "fluxmap.h"
#include "decoders/decoders.h"
#include "encoders/encoders.h"
#include "arch/agat/agat.h"
#include "arch/aeslanier/aeslanier.h"
#include "arch/amiga/amiga.h"
#include "arch/apple2/apple2.h"
#include "arch/brother/brother.h"
#include "arch/c64/c64.h"
#include "arch/f85/f85.h"
#include "arch/fb100/fb100.h"
#include "arch/ibm/ibm.h"
#include "arch/macintosh/macintosh.h"
#include "arch/micropolis/micropolis.h"
#include "arch/mx/mx.h"
#include "arch/northstar/northstar.h"
#include "arch/smaky6/smaky6.h"
#include "arch/tids990/tids990.h"
#include "arch/victor9k/victor9k.h"
#include "arch/zilogmcz/zilogmcz.h"
#include "decoders/fluxmapreader.h"
#include "flux.h"
#include "protocol.h"
#include "decoders/rawbits.h"
#include "sector.h"
#include "image.h"
#include "lib/decoders/decoders.pb.h"
#include "lib/layout.h"
#include "fmt/format.h"
#include <numeric>
std::unique_ptr<Decoder> Decoder::create(const DecoderProto& config)
{
static const std::map<int,
std::function<std::unique_ptr<Decoder>(const DecoderProto&)>>
decoders = {
{DecoderProto::kAgat, createAgatDecoder },
{DecoderProto::kAeslanier, createAesLanierDecoder },
{DecoderProto::kAmiga, createAmigaDecoder },
{DecoderProto::kApple2, createApple2Decoder },
{DecoderProto::kBrother, createBrotherDecoder },
{DecoderProto::kC64, createCommodore64Decoder},
{DecoderProto::kF85, createDurangoF85Decoder },
{DecoderProto::kFb100, createFb100Decoder },
{DecoderProto::kIbm, createIbmDecoder },
{DecoderProto::kMacintosh, createMacintoshDecoder },
{DecoderProto::kMicropolis, createMicropolisDecoder },
{DecoderProto::kMx, createMxDecoder },
{DecoderProto::kNorthstar, createNorthstarDecoder },
{DecoderProto::kSmaky6, createSmaky6Decoder },
{DecoderProto::kTids990, createTids990Decoder },
{DecoderProto::kVictor9K, createVictor9kDecoder },
{DecoderProto::kZilogmcz, createZilogMczDecoder },
};
auto decoder = decoders.find(config.format_case());
if (decoder == decoders.end())
Error() << "no decoder specified";
return (decoder->second)(config);
}
std::shared_ptr<TrackDataFlux> Decoder::decodeToSectors(
std::shared_ptr<const Fluxmap> fluxmap,
std::shared_ptr<const TrackInfo>& trackInfo)
{
_trackdata = std::make_shared<TrackDataFlux>();
_trackdata->fluxmap = fluxmap;
_trackdata->trackInfo = trackInfo;
FluxmapReader fmr(*fluxmap);
_fmr = &fmr;
auto newSector = [&]
{
_sector = std::make_shared<Sector>(trackInfo, 0);
_sector->status = Sector::MISSING;
};
newSector();
beginTrack();
for (;;)
{
newSector();
Fluxmap::Position recordStart = fmr.tell();
_sector->clock = advanceToNextRecord();
if (fmr.eof() || !_sector->clock)
return _trackdata;
/* Read the sector record. */
Fluxmap::Position before = fmr.tell();
decodeSectorRecord();
Fluxmap::Position after = fmr.tell();
pushRecord(before, after);
if (_sector->status != Sector::DATA_MISSING)
{
_sector->position = before.bytes;
_sector->dataStartTime = before.ns();
_sector->dataEndTime = after.ns();
}
else
{
/* The data is in a separate record. */
for (;;)
{
_sector->headerStartTime = before.ns();
_sector->headerEndTime = after.ns();
_sector->clock = advanceToNextRecord();
if (fmr.eof() || !_sector->clock)
break;
before = fmr.tell();
decodeDataRecord();
after = fmr.tell();
if (_sector->status != Sector::DATA_MISSING)
{
_sector->position = before.bytes;
_sector->dataStartTime = before.ns();
_sector->dataEndTime = after.ns();
pushRecord(before, after);
break;
}
fmr.skipToEvent(F_BIT_PULSE);
resetFluxDecoder();
}
}
if (_sector->status != Sector::MISSING)
{
auto trackLayout = Layout::getLayoutOfTrack(
_sector->logicalTrack, _sector->logicalSide);
_trackdata->sectors.push_back(_sector);
}
}
}
void Decoder::pushRecord(
const Fluxmap::Position& start, const Fluxmap::Position& end)
{
Fluxmap::Position here = _fmr->tell();
auto record = std::make_shared<Record>();
_trackdata->records.push_back(record);
_sector->records.push_back(record);
record->position = start.bytes;
record->startTime = start.ns();
record->endTime = end.ns();
record->clock = _sector->clock;
record->rawData = toBytes(_recordBits);
_recordBits.clear();
}
void Decoder::resetFluxDecoder()
{
_decoder.reset(new FluxDecoder(_fmr, _sector->clock, _config));
}
nanoseconds_t Decoder::seekToPattern(const FluxMatcher& pattern)
{
nanoseconds_t clock = _fmr->seekToPattern(pattern);
_decoder.reset(new FluxDecoder(_fmr, clock, _config));
return clock;
}
void Decoder::seekToIndexMark()
{
_fmr->skipToEvent(F_BIT_PULSE);
_fmr->seekToIndexMark();
}
std::vector<bool> Decoder::readRawBits(unsigned count)
{
auto bits = _decoder->readBits(count);
_recordBits.insert(_recordBits.end(), bits.begin(), bits.end());
return bits;
}
uint8_t Decoder::readRaw8()
{
return toBytes(readRawBits(8)).reader().read_8();
}
uint16_t Decoder::readRaw16()
{
return toBytes(readRawBits(16)).reader().read_be16();
}
uint32_t Decoder::readRaw20()
{
std::vector<bool> bits(4);
for (bool b : readRawBits(20))
bits.push_back(b);
return toBytes(bits).reader().read_be24();
}
uint32_t Decoder::readRaw24()
{
return toBytes(readRawBits(24)).reader().read_be24();
}
uint32_t Decoder::readRaw32()
{
return toBytes(readRawBits(32)).reader().read_be32();
}
uint64_t Decoder::readRaw48()
{
return toBytes(readRawBits(48)).reader().read_be48();
}
uint64_t Decoder::readRaw64()
{
return toBytes(readRawBits(64)).reader().read_be64();
}

View File

@@ -71,6 +71,11 @@ public:
return _fmr->tell();
}
void rewind()
{
_fmr->rewind();
}
void seek(const Fluxmap::Position& pos)
{
return _fmr->seek(pos);

View File

@@ -13,13 +13,14 @@ import "arch/macintosh/macintosh.proto";
import "arch/micropolis/micropolis.proto";
import "arch/mx/mx.proto";
import "arch/northstar/northstar.proto";
import "arch/smaky6/smaky6.proto";
import "arch/tids990/tids990.proto";
import "arch/victor9k/victor9k.proto";
import "arch/zilogmcz/zilogmcz.proto";
import "lib/fluxsink/fluxsink.proto";
import "lib/common.proto";
//NEXT: 30
//NEXT: 31
message DecoderProto {
optional double pulse_debounce_threshold = 1 [default = 0.30,
(help) = "ignore pulses with intervals shorter than this, in fractions of a clock"];
@@ -46,6 +47,7 @@ message DecoderProto {
MicropolisDecoderProto micropolis = 14;
MxDecoderProto mx = 15;
NorthstarDecoderProto northstar = 24;
Smaky6DecoderProto smaky6 = 30;
Tids990DecoderProto tids990 = 16;
Victor9kDecoderProto victor9k = 17;
ZilogMczDecoderProto zilogmcz = 18;

View File

@@ -202,6 +202,23 @@ void FluxmapReader::seek(nanoseconds_t ns)
_pos.zeroes = 0;
}
void FluxmapReader::seekToByte(unsigned b)
{
if (b < _pos.bytes)
{
_pos.ticks = 0;
_pos.bytes = 0;
}
while (!eof() && (_pos.bytes < b))
{
int e;
unsigned t;
getNextEvent(e, t);
}
_pos.zeroes = 0;
}
nanoseconds_t FluxmapReader::seekToPattern(const FluxMatcher& pattern)
{
const FluxMatcher* unused;

View File

@@ -100,6 +100,7 @@ public:
/* Important! You can only reliably seek to 1 bits. */
void seek(nanoseconds_t ns);
void seekToByte(unsigned byte);
void seekToIndexMark();
nanoseconds_t seekToPattern(const FluxMatcher& pattern);

View File

@@ -5,8 +5,9 @@ static std::unique_ptr<std::set<LocalBase*>> variables;
void Environment::reset()
{
for (LocalBase* var : *variables)
var->reset();
if (variables)
for (LocalBase* var : *variables)
var->reset();
}
void Environment::addVariable(LocalBase* local)

View File

@@ -10,32 +10,32 @@ class TrackInfo;
struct Record
{
nanoseconds_t clock = 0;
nanoseconds_t startTime = 0;
nanoseconds_t endTime = 0;
Bytes rawData;
nanoseconds_t clock = 0;
nanoseconds_t startTime = 0;
nanoseconds_t endTime = 0;
uint32_t position = 0;
Bytes rawData;
};
struct TrackDataFlux
{
std::shared_ptr<const TrackInfo> trackInfo;
std::shared_ptr<const Fluxmap> fluxmap;
std::vector<std::shared_ptr<const Record>> records;
std::vector<std::shared_ptr<const Sector>> sectors;
std::shared_ptr<const TrackInfo> trackInfo;
std::shared_ptr<const Fluxmap> fluxmap;
std::vector<std::shared_ptr<const Record>> records;
std::vector<std::shared_ptr<const Sector>> sectors;
};
struct TrackFlux
{
std::shared_ptr<const TrackInfo> trackInfo;
std::vector<std::shared_ptr<TrackDataFlux>> trackDatas;
std::set<std::shared_ptr<const Sector>> sectors;
std::shared_ptr<const TrackInfo> trackInfo;
std::vector<std::shared_ptr<TrackDataFlux>> trackDatas;
std::set<std::shared_ptr<const Sector>> sectors;
};
struct DiskFlux
{
std::vector<std::shared_ptr<TrackFlux>> tracks;
std::shared_ptr<const Image> image;
std::vector<std::shared_ptr<TrackFlux>> tracks;
std::shared_ptr<const Image> image;
};
#endif

View File

@@ -9,55 +9,85 @@
std::unique_ptr<FluxSink> FluxSink::create(const FluxSinkProto& config)
{
switch (config.dest_case())
{
case FluxSinkProto::kDrive:
return createHardwareFluxSink(config.drive());
switch (config.type())
{
case FluxSinkProto::DRIVE:
return createHardwareFluxSink(config.drive());
case FluxSinkProto::kA2R:
return createA2RFluxSink(config.a2r());
case FluxSinkProto::A2R:
return createA2RFluxSink(config.a2r());
case FluxSinkProto::kAu:
return createAuFluxSink(config.au());
case FluxSinkProto::AU:
return createAuFluxSink(config.au());
case FluxSinkProto::kVcd:
return createVcdFluxSink(config.vcd());
case FluxSinkProto::VCD:
return createVcdFluxSink(config.vcd());
case FluxSinkProto::kScp:
return createScpFluxSink(config.scp());
case FluxSinkProto::SCP:
return createScpFluxSink(config.scp());
case FluxSinkProto::kFl2:
return createFl2FluxSink(config.fl2());
case FluxSinkProto::FLUX:
return createFl2FluxSink(config.fl2());
default:
Error() << "bad output disk config";
return std::unique_ptr<FluxSink>();
}
default:
Error() << "bad output disk config";
return std::unique_ptr<FluxSink>();
}
}
void FluxSink::updateConfigForFilename(FluxSinkProto* proto, const std::string& filename)
void FluxSink::updateConfigForFilename(
FluxSinkProto* proto, const std::string& filename)
{
static const std::vector<std::pair<std::regex, std::function<void(const std::string&, FluxSinkProto*)>>> formats =
{
{ std::regex("^(.*\\.a2r)$"), [](auto& s, auto* proto) { proto->mutable_a2r()->set_filename(s); }},
{ std::regex("^(.*\\.flux)$"), [](auto& s, auto* proto) { proto->mutable_fl2()->set_filename(s); }},
{ std::regex("^(.*\\.scp)$"), [](auto& s, auto* proto) { proto->mutable_scp()->set_filename(s); }},
{ std::regex("^vcd:(.*)$"), [](auto& s, auto* proto) { proto->mutable_vcd()->set_directory(s); }},
{ std::regex("^au:(.*)$"), [](auto& s, auto* proto) { proto->mutable_au()->set_directory(s); }},
{ std::regex("^drive:(.*)"), [](auto& s, auto* proto) { proto->mutable_drive(); config.mutable_drive()->set_drive(std::stoi(s)); }},
};
static const std::vector<std::pair<std::regex,
std::function<void(const std::string&, FluxSinkProto*)>>>
formats = {
{std::regex("^(.*\\.a2r)$"),
[](auto& s, auto* proto)
{
proto->set_type(FluxSinkProto::A2R);
proto->mutable_a2r()->set_filename(s);
}},
{std::regex("^(.*\\.flux)$"),
[](auto& s, auto* proto)
{
proto->set_type(FluxSinkProto::FLUX);
proto->mutable_fl2()->set_filename(s);
}},
{std::regex("^(.*\\.scp)$"),
[](auto& s, auto* proto)
{
proto->set_type(FluxSinkProto::SCP);
proto->mutable_scp()->set_filename(s);
}},
{std::regex("^vcd:(.*)$"),
[](auto& s, auto* proto)
{
proto->set_type(FluxSinkProto::VCD);
proto->mutable_vcd()->set_directory(s);
}},
{std::regex("^au:(.*)$"),
[](auto& s, auto* proto)
{
proto->set_type(FluxSinkProto::AU);
proto->mutable_au()->set_directory(s);
}},
{std::regex("^drive:(.*)"),
[](auto& s, auto* proto)
{
proto->set_type(FluxSinkProto::DRIVE);
config.mutable_drive()->set_drive(std::stoi(s));
}},
};
for (const auto& it : formats)
{
std::smatch match;
if (std::regex_match(filename, match, it.first))
{
it.second(match[1], proto);
return;
}
}
for (const auto& it : formats)
{
std::smatch match;
if (std::regex_match(filename, match, it.first))
{
it.second(match[1], proto);
return;
}
}
Error() << fmt::format("unrecognised flux filename '{}'", filename);
Error() << fmt::format("unrecognised flux filename '{}'", filename);
}

View File

@@ -14,7 +14,7 @@ message A2RFluxSinkProto {
}
message VcdFluxSinkProto {
optional string directory = 1 [default = "vcdfiles", (help) = "directory to write .vcd files to"];
optional string directory = 1 [default = "vcdfiles", (help) = "directory to write .vcd files to"];
}
message ScpFluxSinkProto {
@@ -27,15 +27,25 @@ message Fl2FluxSinkProto {
optional string filename = 1 [default = "flux.fl2", (help) = ".fl2 file to write to"];
}
// Next: 9
// Next: 10
message FluxSinkProto {
oneof dest {
HardwareFluxSinkProto drive = 2;
A2RFluxSinkProto a2r = 8;
AuFluxSinkProto au = 3;
VcdFluxSinkProto vcd = 4;
ScpFluxSinkProto scp = 5;
Fl2FluxSinkProto fl2 = 6;
enum FluxSinkType {
NOT_SET = 0;
DRIVE = 1;
A2R = 2;
AU = 3;
VCD = 4;
SCP = 5;
FLUX = 6;
}
optional FluxSinkType type = 9 [default = NOT_SET, (help) = "flux sink type"];
optional HardwareFluxSinkProto drive = 2;
optional A2RFluxSinkProto a2r = 8;
optional AuFluxSinkProto au = 3;
optional VcdFluxSinkProto vcd = 4;
optional ScpFluxSinkProto scp = 5;
optional Fl2FluxSinkProto fl2 = 6;
}

View File

@@ -9,6 +9,7 @@
#include "proto.h"
#include "fmt/format.h"
#include "fluxmap.h"
#include "layout.h"
#include "scp.h"
#include <fstream>
#include <sys/stat.h>
@@ -16,7 +17,7 @@
static int strackno(int track, int side)
{
return (track << 1) | side;
return (track << 1) | side;
}
static void write_le32(uint8_t dest[4], uint32_t v)
@@ -29,150 +30,170 @@ static void write_le32(uint8_t dest[4], uint32_t v)
static void appendChecksum(uint32_t& checksum, const Bytes& bytes)
{
ByteReader br(bytes);
while (!br.eof())
checksum += br.read_8();
ByteReader br(bytes);
while (!br.eof())
checksum += br.read_8();
}
class ScpFluxSink : public FluxSink
{
public:
ScpFluxSink(const ScpFluxSinkProto& lconfig):
_config(lconfig)
{
bool singlesided = config.heads().start() == config.heads().end();
ScpFluxSink(const ScpFluxSinkProto& lconfig): _config(lconfig)
{
int minTrack;
int maxTrack;
int minSide;
int maxSide;
Layout::getBounds(
Layout::computeLocations(), minTrack, maxTrack, minSide, maxSide);
_fileheader.file_id[0] = 'S';
_fileheader.file_id[1] = 'C';
_fileheader.file_id[2] = 'P';
_fileheader.version = 0x18; /* Version 1.8 of the spec */
_fileheader.type = _config.type_byte();
_fileheader.start_track = strackno(config.tracks().start(), config.heads().start());
_fileheader.end_track = strackno(config.tracks().end(), config.heads().end());
_fileheader.flags = SCP_FLAG_INDEXED
| SCP_FLAG_96TPI;
_fileheader.cell_width = 0;
_fileheader.heads = singlesided;
_fileheader.file_id[0] = 'S';
_fileheader.file_id[1] = 'C';
_fileheader.file_id[2] = 'P';
_fileheader.version = 0x18; /* Version 1.8 of the spec */
_fileheader.type = _config.type_byte();
_fileheader.start_track = strackno(minTrack, minSide);
_fileheader.end_track = strackno(maxTrack, maxSide);
_fileheader.flags = SCP_FLAG_INDEXED;
if (config.tpi() == 96)
_fileheader.flags |= SCP_FLAG_96TPI;
_fileheader.cell_width = 0;
if ((minSide == 0) && (maxSide == 0))
_fileheader.heads = 1;
else if ((minSide == 1) && (maxSide == 1))
_fileheader.heads = 2;
else
_fileheader.heads = 0;
std::cout << fmt::format("SCP: writing 96 tpi {} file containing {} tracks\n",
singlesided ? "single sided" : "double sided",
_fileheader.end_track - _fileheader.start_track + 1
);
std::cout << fmt::format(
"SCP: writing 96 tpi {} file containing {} tracks\n",
(minSide == maxSide) ? "single sided" : "double sided",
_fileheader.end_track - _fileheader.start_track + 1);
}
}
~ScpFluxSink()
{
uint32_t checksum = 0;
appendChecksum(checksum,
Bytes((const uint8_t*)&_fileheader, sizeof(_fileheader))
.slice(0x10));
appendChecksum(checksum, _trackdata);
write_le32(_fileheader.checksum, checksum);
~ScpFluxSink()
{
uint32_t checksum = 0;
appendChecksum(checksum,
Bytes((const uint8_t*) &_fileheader, sizeof(_fileheader))
.slice(0x10));
appendChecksum(checksum, _trackdata);
write_le32(_fileheader.checksum, checksum);
std::cout << "SCP: writing output file...\n";
std::ofstream of(_config.filename(), std::ios::out | std::ios::binary);
if (!of.is_open())
Error() << "cannot open output file";
of.write((const char*) &_fileheader, sizeof(_fileheader));
_trackdata.writeTo(of);
of.close();
}
std::cout << "SCP: writing output file...\n";
std::ofstream of(_config.filename(), std::ios::out | std::ios::binary);
if (!of.is_open())
Error() << "cannot open output file";
of.write((const char*)&_fileheader, sizeof(_fileheader));
_trackdata.writeTo(of);
of.close();
}
public:
void writeFlux(int track, int head, const Fluxmap& fluxmap) override
{
ByteWriter trackdataWriter(_trackdata);
trackdataWriter.seekToEnd();
int strack = strackno(track, head);
void writeFlux(int track, int head, const Fluxmap& fluxmap) override
{
ByteWriter trackdataWriter(_trackdata);
trackdataWriter.seekToEnd();
int strack = strackno(track, head);
if (strack >= std::size(_fileheader.track)) {
std::cout << fmt::format("SCP: cannot write track {} head {}, "
"there are not not enough Track Data Headers.\n",
track, head);
return;
if (strack >= std::size(_fileheader.track))
{
std::cout << fmt::format(
"SCP: cannot write track {} head {}, "
"there are not not enough Track Data Headers.\n",
track,
head);
return;
}
ScpTrack trackheader = {0};
trackheader.header.track_id[0] = 'T';
trackheader.header.track_id[1] = 'R';
trackheader.header.track_id[2] = 'K';
trackheader.header.strack = strack;
FluxmapReader fmr(fluxmap);
Bytes fluxdata;
ByteWriter fluxdataWriter(fluxdata);
int revolution =
-1; // -1 indicates that we are before the first index pulse
if (_config.align_with_index())
{
fmr.skipToEvent(F_BIT_INDEX);
revolution = 0;
}
unsigned revTicks = 0;
unsigned totalTicks = 0;
unsigned ticksSinceLastPulse = 0;
uint32_t startOffset = 0;
while (revolution < 5)
{
unsigned ticks;
int event;
fmr.getNextEvent(event, ticks);
ticksSinceLastPulse += ticks;
totalTicks += ticks;
revTicks += ticks;
// if we haven't output any revolutions yet by the end of the track,
// assume that the whole track is one rev
// also discard any duplicate index pulses
if (((fmr.eof() && revolution <= 0) ||
((event & F_BIT_INDEX)) && revTicks > 0))
{
if (fmr.eof() && revolution == -1)
revolution = 0;
if (revolution >= 0)
{
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);
revheader++;
}
ScpTrack trackheader = {0};
trackheader.header.track_id[0] = 'T';
trackheader.header.track_id[1] = 'R';
trackheader.header.track_id[2] = 'K';
trackheader.header.strack = strack;
revolution++;
revTicks = 0;
startOffset = fluxdataWriter.pos;
}
if (fmr.eof())
break;
FluxmapReader fmr(fluxmap);
Bytes fluxdata;
ByteWriter fluxdataWriter(fluxdata);
if (event & F_BIT_PULSE)
{
unsigned t = ticksSinceLastPulse * NS_PER_TICK / 25;
while (t >= 0x10000)
{
fluxdataWriter.write_be16(0);
t -= 0x10000;
}
fluxdataWriter.write_be16(t);
ticksSinceLastPulse = 0;
}
}
int revolution = -1; // -1 indicates that we are before the first index pulse
if (_config.align_with_index()) {
fmr.skipToEvent(F_BIT_INDEX);
revolution = 0;
}
unsigned revTicks = 0;
unsigned totalTicks = 0;
unsigned ticksSinceLastPulse = 0;
uint32_t startOffset = 0;
while (revolution < 5)
{
unsigned ticks;
int event;
fmr.getNextEvent(event, ticks);
_fileheader.revolutions = revolution;
write_le32(
_fileheader.track[strack], trackdataWriter.pos + sizeof(ScpHeader));
trackdataWriter += Bytes((uint8_t*)&trackheader, sizeof(trackheader));
trackdataWriter += fluxdata;
}
ticksSinceLastPulse += ticks;
totalTicks += ticks;
revTicks += ticks;
// if we haven't output any revolutions yet by the end of the track,
// assume that the whole track is one rev
// also discard any duplicate index pulses
if (((fmr.eof() && revolution <= 0) || ((event & F_BIT_INDEX)) && revTicks > 0))
{
if (fmr.eof() && revolution == -1)
revolution = 0;
if (revolution >= 0) {
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);
revheader++;
}
revolution++;
revTicks = 0;
startOffset = fluxdataWriter.pos;
}
if (fmr.eof()) break;
if (event & F_BIT_PULSE)
{
unsigned t = ticksSinceLastPulse * NS_PER_TICK / 25;
while (t >= 0x10000)
{
fluxdataWriter.write_be16(0);
t -= 0x10000;
}
fluxdataWriter.write_be16(t);
ticksSinceLastPulse = 0;
}
}
_fileheader.revolutions = revolution;
write_le32(_fileheader.track[strack], trackdataWriter.pos + sizeof(ScpHeader));
trackdataWriter += Bytes((uint8_t*)&trackheader, sizeof(trackheader));
trackdataWriter += fluxdata;
}
operator std::string () const
{
return fmt::format("scp({})", _config.filename());
}
operator std::string() const
{
return fmt::format("scp({})", _config.filename());
}
private:
const ScpFluxSinkProto& _config;
ScpHeader _fileheader = {0};
Bytes _trackdata;
const ScpFluxSinkProto& _config;
ScpHeader _fileheader = {0};
Bytes _trackdata;
};
std::unique_ptr<FluxSink> FluxSink::createScpFluxSink(const ScpFluxSinkProto& config)
std::unique_ptr<FluxSink> FluxSink::createScpFluxSink(
const ScpFluxSinkProto& config)
{
return std::unique_ptr<FluxSink>(new ScpFluxSink(config));
}

View File

@@ -17,93 +17,137 @@ static bool ends_with(const std::string& value, const std::string& ending)
std::unique_ptr<FluxSource> FluxSource::create(const FluxSourceProto& config)
{
switch (config.source_case())
{
case FluxSourceProto::kDrive:
return createHardwareFluxSource(config.drive());
switch (config.type())
{
case FluxSourceProto::DRIVE:
return createHardwareFluxSource(config.drive());
case FluxSourceProto::kErase:
return createEraseFluxSource(config.erase());
case FluxSourceProto::ERASE:
return createEraseFluxSource(config.erase());
case FluxSourceProto::kKryoflux:
return createKryofluxFluxSource(config.kryoflux());
case FluxSourceProto::KRYOFLUX:
return createKryofluxFluxSource(config.kryoflux());
case FluxSourceProto::kTestPattern:
return createTestPatternFluxSource(config.test_pattern());
case FluxSourceProto::TEST_PATTERN:
return createTestPatternFluxSource(config.test_pattern());
case FluxSourceProto::kScp:
return createScpFluxSource(config.scp());
case FluxSourceProto::SCP:
return createScpFluxSource(config.scp());
case FluxSourceProto::kCwf:
return createCwfFluxSource(config.cwf());
case FluxSourceProto::CWF:
return createCwfFluxSource(config.cwf());
case FluxSourceProto::kFl2:
return createFl2FluxSource(config.fl2());
case FluxSourceProto::FLUX:
return createFl2FluxSource(config.fl2());
default:
Error() << "bad input disk configuration";
return std::unique_ptr<FluxSource>();
}
case FluxSourceProto::FLX:
return createFlxFluxSource(config.flx());
default:
Error() << "bad input disk configuration";
return std::unique_ptr<FluxSource>();
}
}
void FluxSource::updateConfigForFilename(FluxSourceProto* proto, const std::string& filename)
void FluxSource::updateConfigForFilename(
FluxSourceProto* proto, const std::string& filename)
{
static const std::vector<std::pair<std::regex, std::function<void(const std::string&, FluxSourceProto*)>>> formats =
{
{ std::regex("^(.*\\.a2r)$"), [](auto& s, auto* proto) { }},
{ std::regex("^(.*\\.flux)$"), [](auto& s, auto* proto) { proto->mutable_fl2()->set_filename(s); }},
{ std::regex("^(.*\\.scp)$"), [](auto& s, auto* proto) { proto->mutable_scp()->set_filename(s); }},
{ std::regex("^(.*\\.cwf)$"), [](auto& s, auto* proto) { proto->mutable_cwf()->set_filename(s); }},
{ std::regex("^erase:$"), [](auto& s, auto* proto) { proto->mutable_erase(); }},
{ std::regex("^kryoflux:(.*)$"), [](auto& s, auto* proto) { proto->mutable_kryoflux()->set_directory(s); }},
{ std::regex("^testpattern:(.*)"), [](auto& s, auto* proto) { proto->mutable_test_pattern(); }},
{ std::regex("^drive:(.*)"), [](auto& s, auto* proto) { proto->mutable_drive(); config.mutable_drive()->set_drive(std::stoi(s)); }},
};
static const std::vector<std::pair<std::regex,
std::function<void(const std::string&, FluxSourceProto*)>>>
formats = {
{std::regex("^(.*\\.flux)$"),
[](auto& s, auto* proto)
{
proto->set_type(FluxSourceProto::FLUX);
proto->mutable_fl2()->set_filename(s);
}},
{std::regex("^(.*\\.scp)$"),
[](auto& s, auto* proto)
{
proto->set_type(FluxSourceProto::SCP);
proto->mutable_scp()->set_filename(s);
}},
{std::regex("^(.*\\.cwf)$"),
[](auto& s, auto* proto)
{
proto->set_type(FluxSourceProto::CWF);
proto->mutable_cwf()->set_filename(s);
}},
{std::regex("^erase:$"),
[](auto& s, auto* proto)
{
proto->set_type(FluxSourceProto::ERASE);
}},
{std::regex("^kryoflux:(.*)$"),
[](auto& s, auto* proto)
{
proto->set_type(FluxSourceProto::KRYOFLUX);
proto->mutable_kryoflux()->set_directory(s);
}},
{std::regex("^testpattern:(.*)"),
[](auto& s, auto* proto)
{
proto->set_type(FluxSourceProto::TEST_PATTERN);
}},
{std::regex("^drive:(.*)"),
[](auto& s, auto* proto)
{
proto->set_type(FluxSourceProto::DRIVE);
config.mutable_drive()->set_drive(std::stoi(s));
}},
{std::regex("^flx:(.*)$"),
[](auto& s, auto* proto)
{
proto->set_type(FluxSourceProto::FLX);
proto->mutable_flx()->set_directory(s);
}},
};
for (const auto& it : formats)
{
std::smatch match;
if (std::regex_match(filename, match, it.first))
{
it.second(match[1], proto);
return;
}
}
for (const auto& it : formats)
{
std::smatch match;
if (std::regex_match(filename, match, it.first))
{
it.second(match[1], proto);
return;
}
}
Error() << fmt::format("unrecognised flux filename '{}'", filename);
Error() << fmt::format("unrecognised flux filename '{}'", filename);
}
class TrivialFluxSourceIterator : public FluxSourceIterator
{
public:
TrivialFluxSourceIterator(TrivialFluxSource* fluxSource, int track, int head):
_fluxSource(fluxSource),
_track(track),
_head(head)
{}
TrivialFluxSourceIterator(
TrivialFluxSource* fluxSource, int track, int head):
_fluxSource(fluxSource),
_track(track),
_head(head)
{
}
bool hasNext() const override
{
return !!_fluxSource;
}
bool hasNext() const override
{
return !!_fluxSource;
}
std::unique_ptr<const Fluxmap> next() override
{
auto fluxmap = _fluxSource->readSingleFlux(_track, _head);
_fluxSource = nullptr;
return fluxmap;
}
std::unique_ptr<const Fluxmap> next() override
{
auto fluxmap = _fluxSource->readSingleFlux(_track, _head);
_fluxSource = nullptr;
return fluxmap;
}
private:
TrivialFluxSource* _fluxSource;
int _track;
int _head;
TrivialFluxSource* _fluxSource;
int _track;
int _head;
};
std::unique_ptr<FluxSourceIterator> TrivialFluxSource::readFlux(int track, int head)
std::unique_ptr<FluxSourceIterator> TrivialFluxSource::readFlux(
int track, int head)
{
return std::make_unique<TrivialFluxSourceIterator>(this, track, head);
return std::make_unique<TrivialFluxSourceIterator>(this, track, head);
}

View File

@@ -14,6 +14,7 @@ class HardwareFluxSourceProto;
class KryofluxFluxSourceProto;
class ScpFluxSourceProto;
class TestPatternFluxSourceProto;
class FlxFluxSourceProto;
class FluxSourceIterator
{
@@ -33,6 +34,7 @@ private:
static std::unique_ptr<FluxSource> createCwfFluxSource(const CwfFluxSourceProto& config);
static std::unique_ptr<FluxSource> createEraseFluxSource(const EraseFluxSourceProto& config);
static std::unique_ptr<FluxSource> createFl2FluxSource(const Fl2FluxSourceProto& config);
static std::unique_ptr<FluxSource> createFlxFluxSource(const FlxFluxSourceProto& config);
static std::unique_ptr<FluxSource> createHardwareFluxSource(const HardwareFluxSourceProto& config);
static std::unique_ptr<FluxSource> createKryofluxFluxSource(const KryofluxFluxSourceProto& config);
static std::unique_ptr<FluxSource> createScpFluxSource(const ScpFluxSourceProto& config);

View File

@@ -30,15 +30,33 @@ message Fl2FluxSourceProto {
(help) = ".fl2 file to read flux from"];
}
message FluxSourceProto {
oneof source {
HardwareFluxSourceProto drive = 2;
TestPatternFluxSourceProto test_pattern = 3;
EraseFluxSourceProto erase = 4;
KryofluxFluxSourceProto kryoflux = 5;
ScpFluxSourceProto scp = 6;
CwfFluxSourceProto cwf = 7;
Fl2FluxSourceProto fl2 = 8;
}
message FlxFluxSourceProto {
optional string directory = 1 [(help) = "path to FLX stream directory"];
}
// NEXT: 11
message FluxSourceProto {
enum FluxSourceType {
NOT_SET = 0;
DRIVE = 1;
TEST_PATTERN = 2;
ERASE = 3;
KRYOFLUX = 4;
SCP = 5;
CWF = 6;
FLUX = 7;
FLX = 8;
}
optional FluxSourceType type = 9 [default = NOT_SET, (help) = "flux source type"];
optional HardwareFluxSourceProto drive = 2;
optional TestPatternFluxSourceProto test_pattern = 3;
optional EraseFluxSourceProto erase = 4;
optional KryofluxFluxSourceProto kryoflux = 5;
optional ScpFluxSourceProto scp = 6;
optional CwfFluxSourceProto cwf = 7;
optional Fl2FluxSourceProto fl2 = 8;
optional FlxFluxSourceProto flx = 10;
}

50
lib/fluxsource/flx.cc Normal file
View File

@@ -0,0 +1,50 @@
#include "globals.h"
#include "fluxmap.h"
#include "kryoflux.h"
#include "protocol.h"
#include "lib/fluxsource/flx.h"
#include "fmt/format.h"
std::unique_ptr<Fluxmap> readFlxBytes(const Bytes& bytes)
{
ByteReader br(bytes);
/* Skip header. */
for (;;)
{
if (br.eof())
Error() << fmt::format("malformed FLX stream");
uint8_t b = br.read_8();
if (b == 0)
break;
}
auto fluxmap = std::make_unique<Fluxmap>();
while (!br.eof())
{
uint8_t b = br.read_8();
switch (b)
{
case FLX_INDEX:
fluxmap->appendIndex();
continue;
case FLX_STOP:
goto stop;
default:
{
if (b < 32)
Error() << fmt::format("unknown FLX opcode 0x{:2x}", b);
nanoseconds_t interval = b * FLX_TICK_NS;
fluxmap->appendInterval(interval / NS_PER_TICK);
fluxmap->appendPulse();
break;
}
}
}
stop:
return fluxmap;
}

16
lib/fluxsource/flx.h Normal file
View File

@@ -0,0 +1,16 @@
#ifndef FLX_H
#define FLX_H
#define FLX_TICK_NS 40 /* ns per tick */
/* Special FLX opcodes */
enum
{
FLX_INDEX = 0x08,
FLX_STOP = 0x0d
};
extern std::unique_ptr<Fluxmap> readFlxBytes(const Bytes& bytes);
#endif

View File

@@ -0,0 +1,32 @@
#include "lib/globals.h"
#include "lib/fluxmap.h"
#include "lib/fluxsource/fluxsource.pb.h"
#include "lib/fluxsource/fluxsource.h"
#include "lib/fluxsource/flx.h"
class FlxFluxSource : public TrivialFluxSource
{
public:
FlxFluxSource(const FlxFluxSourceProto& config): _path(config.directory())
{
}
public:
std::unique_ptr<const Fluxmap> readSingleFlux(int track, int side) override
{
std::string path =
fmt::format("{}/@TR{:02}S{}@.FLX", _path, track, side + 1);
return readFlxBytes(Bytes::readFromFile(path));
}
void recalibrate() {}
private:
const std::string _path;
};
std::unique_ptr<FluxSource> FluxSource::createFlxFluxSource(
const FlxFluxSourceProto& config)
{
return std::make_unique<FlxFluxSource>(config);
}

View File

@@ -14,39 +14,39 @@
std::unique_ptr<ImageReader> ImageReader::create(const ImageReaderProto& config)
{
switch (config.format_case())
switch (config.type())
{
case ImageReaderProto::kDim:
case ImageReaderProto::DIM:
return ImageReader::createDimImageReader(config);
case ImageReaderProto::kD88:
case ImageReaderProto::D88:
return ImageReader::createD88ImageReader(config);
case ImageReaderProto::kFdi:
case ImageReaderProto::FDI:
return ImageReader::createFdiImageReader(config);
case ImageReaderProto::kImd:
case ImageReaderProto::IMD:
return ImageReader::createIMDImageReader(config);
case ImageReaderProto::kImg:
case ImageReaderProto::IMG:
return ImageReader::createImgImageReader(config);
case ImageReaderProto::kDiskcopy:
case ImageReaderProto::DISKCOPY:
return ImageReader::createDiskCopyImageReader(config);
case ImageReaderProto::kJv3:
case ImageReaderProto::JV3:
return ImageReader::createJv3ImageReader(config);
case ImageReaderProto::kD64:
case ImageReaderProto::D64:
return ImageReader::createD64ImageReader(config);
case ImageReaderProto::kNfd:
case ImageReaderProto::NFD:
return ImageReader::createNFDImageReader(config);
case ImageReaderProto::kNsi:
case ImageReaderProto::NSI:
return ImageReader::createNsiImageReader(config);
case ImageReaderProto::kTd0:
case ImageReaderProto::TD0:
return ImageReader::createTd0ImageReader(config);
default:
@@ -61,23 +61,23 @@ void ImageReader::updateConfigForFilename(
static const std::map<std::string, std::function<void(ImageReaderProto*)>>
formats = {
// clang-format off
{".adf", [](auto* proto) { proto->mutable_img(); }},
{".d64", [](auto* proto) { proto->mutable_d64(); }},
{".d81", [](auto* proto) { proto->mutable_img(); }},
{".d88", [](auto* proto) { proto->mutable_d88(); }},
{".dim", [](auto* proto) { proto->mutable_dim(); }},
{".diskcopy", [](auto* proto) { proto->mutable_diskcopy(); }},
{".dsk", [](auto* proto) { proto->mutable_img(); }},
{".fdi", [](auto* proto) { proto->mutable_fdi(); }},
{".imd", [](auto* proto) { proto->mutable_imd(); }},
{".img", [](auto* proto) { proto->mutable_img(); }},
{".jv3", [](auto* proto) { proto->mutable_jv3(); }},
{".nfd", [](auto* proto) { proto->mutable_nfd(); }},
{".nsi", [](auto* proto) { proto->mutable_nsi(); }},
{".st", [](auto* proto) { proto->mutable_img(); }},
{".td0", [](auto* proto) { proto->mutable_td0(); }},
{".vgi", [](auto* proto) { proto->mutable_img(); }},
{".xdf", [](auto* proto) { proto->mutable_img(); }},
{".adf", [](auto* proto) { proto->set_type(ImageReaderProto::IMG); }},
{".d64", [](auto* proto) { proto->set_type(ImageReaderProto::D64); }},
{".d81", [](auto* proto) { proto->set_type(ImageReaderProto::IMG); }},
{".d88", [](auto* proto) { proto->set_type(ImageReaderProto::D88); }},
{".dim", [](auto* proto) { proto->set_type(ImageReaderProto::DIM); }},
{".diskcopy", [](auto* proto) { proto->set_type(ImageReaderProto::DISKCOPY); }},
{".dsk", [](auto* proto) { proto->set_type(ImageReaderProto::IMG); }},
{".fdi", [](auto* proto) { proto->set_type(ImageReaderProto::FDI); }},
{".imd", [](auto* proto) { proto->set_type(ImageReaderProto::IMD); }},
{".img", [](auto* proto) { proto->set_type(ImageReaderProto::IMG); }},
{".jv3", [](auto* proto) { proto->set_type(ImageReaderProto::JV3); }},
{".nfd", [](auto* proto) { proto->set_type(ImageReaderProto::NFD); }},
{".nsi", [](auto* proto) { proto->set_type(ImageReaderProto::NSI); }},
{".st", [](auto* proto) { proto->set_type(ImageReaderProto::IMG); }},
{".td0", [](auto* proto) { proto->set_type(ImageReaderProto::TD0); }},
{".vgi", [](auto* proto) { proto->set_type(ImageReaderProto::IMG); }},
{".xdf", [](auto* proto) { proto->set_type(ImageReaderProto::IMG); }},
// clang-format on
};

View File

@@ -13,9 +13,9 @@ message Td0InputProto {}
message DimInputProto {}
message FdiInputProto {}
message D88InputProto {}
message NFDInputProto {}
message NfdInputProto {}
// NEXT_TAG: 14
// NEXT_TAG: 15
message ImageReaderProto
{
optional string filename = 1 [ (help) = "filename of input sector image" ];
@@ -24,18 +24,32 @@ message ImageReaderProto
default = false
];
oneof format
{
ImgInputOutputProto img = 2;
DiskCopyInputProto diskcopy = 3;
ImdInputProto imd = 4;
Jv3InputProto jv3 = 5;
D64InputProto d64 = 6;
NsiInputProto nsi = 7;
Td0InputProto td0 = 8;
DimInputProto dim = 9;
FdiInputProto fdi = 10;
D88InputProto d88 = 11;
NFDInputProto nfd = 12;
}
enum ImageReaderType {
NOT_SET = 0;
IMG = 1;
DISKCOPY = 2;
IMD = 3;
JV3 = 4;
D64 = 5;
NSI = 6;
TD0 = 7;
DIM = 8;
FDI = 9;
D88 = 10;
NFD = 11;
}
optional ImageReaderType type = 14 [default = NOT_SET, (help) = "input image type"];
optional ImgInputOutputProto img = 2;
optional DiskCopyInputProto diskcopy = 3;
optional ImdInputProto imd = 4;
optional Jv3InputProto jv3 = 5;
optional D64InputProto d64 = 6;
optional NsiInputProto nsi = 7;
optional Td0InputProto td0 = 8;
optional DimInputProto dim = 9;
optional FdiInputProto fdi = 10;
optional D88InputProto d88 = 11;
optional NfdInputProto nfd = 12;
}

View File

@@ -14,30 +14,30 @@
std::unique_ptr<ImageWriter> ImageWriter::create(const ImageWriterProto& config)
{
switch (config.format_case())
switch (config.type())
{
case ImageWriterProto::kImg:
case ImageWriterProto::IMG:
return ImageWriter::createImgImageWriter(config);
case ImageWriterProto::kD64:
case ImageWriterProto::D64:
return ImageWriter::createD64ImageWriter(config);
case ImageWriterProto::kLdbs:
case ImageWriterProto::LDBS:
return ImageWriter::createLDBSImageWriter(config);
case ImageWriterProto::kDiskcopy:
case ImageWriterProto::DISKCOPY:
return ImageWriter::createDiskCopyImageWriter(config);
case ImageWriterProto::kNsi:
case ImageWriterProto::NSI:
return ImageWriter::createNsiImageWriter(config);
case ImageWriterProto::kRaw:
case ImageWriterProto::RAW:
return ImageWriter::createRawImageWriter(config);
case ImageWriterProto::kD88:
case ImageWriterProto::D88:
return ImageWriter::createD88ImageWriter(config);
case ImageWriterProto::kImd:
case ImageWriterProto::IMD:
return ImageWriter::createImdImageWriter(config);
default:
@@ -52,20 +52,20 @@ void ImageWriter::updateConfigForFilename(
static const std::map<std::string, std::function<void(ImageWriterProto*)>>
formats = {
// clang-format off
{".adf", [](auto* proto) { proto->mutable_img(); }},
{".d64", [](auto* proto) { proto->mutable_d64(); }},
{".d81", [](auto* proto) { proto->mutable_img(); }},
{".d88", [](auto* proto) { proto->mutable_d88(); }},
{".diskcopy", [](auto* proto) { proto->mutable_diskcopy(); }},
{".dsk", [](auto* proto) { proto->mutable_img(); }},
{".img", [](auto* proto) { proto->mutable_img(); }},
{".imd", [](auto* proto) { proto->mutable_imd(); }},
{".ldbs", [](auto* proto) { proto->mutable_ldbs(); }},
{".nsi", [](auto* proto) { proto->mutable_nsi(); }},
{".raw", [](auto* proto) { proto->mutable_raw(); }},
{".st", [](auto* proto) { proto->mutable_img(); }},
{".vgi", [](auto* proto) { proto->mutable_img(); }},
{".xdf", [](auto* proto) { proto->mutable_img(); }},
{".adf", [](auto* proto) { proto->set_type(ImageWriterProto::IMG); }},
{".d64", [](auto* proto) { proto->set_type(ImageWriterProto::D64); }},
{".d81", [](auto* proto) { proto->set_type(ImageWriterProto::IMG); }},
{".d88", [](auto* proto) { proto->set_type(ImageWriterProto::D88); }},
{".diskcopy", [](auto* proto) { proto->set_type(ImageWriterProto::DISKCOPY); }},
{".dsk", [](auto* proto) { proto->set_type(ImageWriterProto::IMG); }},
{".img", [](auto* proto) { proto->set_type(ImageWriterProto::IMG); }},
{".imd", [](auto* proto) { proto->set_type(ImageWriterProto::IMD); }},
{".ldbs", [](auto* proto) { proto->set_type(ImageWriterProto::LDBS); }},
{".nsi", [](auto* proto) { proto->set_type(ImageWriterProto::NSI); }},
{".raw", [](auto* proto) { proto->set_type(ImageWriterProto::RAW); }},
{".st", [](auto* proto) { proto->set_type(ImageWriterProto::IMG); }},
{".vgi", [](auto* proto) { proto->set_type(ImageWriterProto::IMG); }},
{".xdf", [](auto* proto) { proto->set_type(ImageWriterProto::IMG); }},
// clang-format on
};

View File

@@ -63,24 +63,35 @@ message ImdOutputProto
optional string comment = 3 [ (help) = "comment to set in IMD file" ];
}
// NEXT_TAG: 11
// NEXT_TAG: 12
message ImageWriterProto
{
enum ImageWriterType {
NOT_SET = 0;
IMG = 1;
D64 = 2;
LDBS = 3;
DISKCOPY = 4;
NSI = 5;
RAW = 6;
D88 = 7;
IMD = 8;
}
optional string filename = 1 [ (help) = "filename of output sector image" ];
optional bool filesystem_sector_order = 10 [
(help) = "read/write sector image in filesystem order",
default = false
];
oneof format
{
ImgInputOutputProto img = 2;
D64OutputProto d64 = 3;
LDBSOutputProto ldbs = 4;
DiskCopyOutputProto diskcopy = 5;
NsiOutputProto nsi = 6;
RawOutputProto raw = 7;
D88OutputProto d88 = 8;
ImdOutputProto imd = 9;
}
optional ImageWriterType type = 11 [ default = NOT_SET, (help) = "image writer type" ];
optional ImgInputOutputProto img = 2;
optional D64OutputProto d64 = 3;
optional LDBSOutputProto ldbs = 4;
optional DiskCopyOutputProto diskcopy = 5;
optional NsiOutputProto nsi = 6;
optional RawOutputProto raw = 7;
optional D88OutputProto d88 = 8;
optional ImdOutputProto imd = 9;
}

View File

@@ -4,9 +4,6 @@
#include "lib/environment.h"
#include <fmt/format.h>
static Local<std::map<std::pair<int, int>, std::shared_ptr<TrackInfo>>>
layoutCache;
static unsigned getTrackStep()
{
unsigned track_step =
@@ -61,6 +58,21 @@ std::vector<std::shared_ptr<const TrackInfo>> Layout::computeLocations()
return locations;
}
void Layout::getBounds(const std::vector<std::shared_ptr<const TrackInfo>>& locations,
int& minTrack, int& maxTrack, int& minSide, int& maxSide)
{
minTrack = minSide = INT_MAX;
maxTrack = maxSide = INT_MIN;
for (auto& ti : locations)
{
minTrack = std::min<int>(minTrack, ti->physicalTrack);
maxTrack = std::max<int>(maxTrack, ti->physicalTrack);
minSide = std::min<int>(minSide, ti->physicalSide);
maxSide = std::max<int>(maxSide, ti->physicalSide);
}
}
std::vector<std::pair<int, int>> Layout::getTrackOrdering(
unsigned guessedTracks, unsigned guessedSides)
{
@@ -128,66 +140,62 @@ std::vector<unsigned> Layout::expandSectorList(
std::shared_ptr<const TrackInfo> Layout::getLayoutOfTrack(
unsigned logicalTrack, unsigned logicalSide)
{
auto& layout = (*layoutCache)[std::make_pair(logicalTrack, logicalSide)];
if (!layout)
auto trackInfo = std::make_shared<TrackInfo>();
LayoutProto::LayoutdataProto layoutdata;
for (const auto& f : config.layout().layoutdata())
{
layout = std::make_shared<TrackInfo>();
if (f.has_track() && f.has_up_to_track() &&
((logicalTrack < f.track()) ||
(logicalTrack > f.up_to_track())))
continue;
if (f.has_track() && !f.has_up_to_track() &&
(logicalTrack != f.track()))
continue;
if (f.has_side() && (f.side() != logicalSide))
continue;
LayoutProto::LayoutdataProto layoutdata;
for (const auto& f : config.layout().layoutdata())
{
if (f.has_track() && f.has_up_to_track() &&
((logicalTrack < f.track()) ||
(logicalTrack > f.up_to_track())))
continue;
if (f.has_track() && !f.has_up_to_track() &&
(logicalTrack != f.track()))
continue;
if (f.has_side() && (f.side() != logicalSide))
continue;
layoutdata.MergeFrom(f);
}
layout->numTracks = config.layout().tracks();
layout->numSides = config.layout().sides();
layout->sectorSize = layoutdata.sector_size();
layout->logicalTrack = logicalTrack;
layout->logicalSide = logicalSide;
layout->physicalTrack = remapTrackLogicalToPhysical(logicalTrack);
layout->physicalSide = logicalSide ^ config.layout().swap_sides();
layout->groupSize = getTrackStep();
layout->diskSectorOrder = expandSectorList(layoutdata.physical());
layout->logicalSectorOrder = layout->diskSectorOrder;
std::sort(
layout->diskSectorOrder.begin(), layout->diskSectorOrder.end());
layout->numSectors = layout->logicalSectorOrder.size();
if (layoutdata.has_filesystem())
{
layout->filesystemSectorOrder =
expandSectorList(layoutdata.filesystem());
if (layout->filesystemSectorOrder.size() != layout->numSectors)
Error()
<< "filesystem sector order list doesn't contain the right "
"number of sectors";
}
else
{
for (unsigned sectorId : layout->logicalSectorOrder)
layout->filesystemSectorOrder.push_back(sectorId);
}
for (int i = 0; i < layout->numSectors; i++)
{
unsigned f = layout->logicalSectorOrder[i];
unsigned l = layout->filesystemSectorOrder[i];
layout->filesystemToLogicalSectorMap[f] = l;
layout->logicalToFilesystemSectorMap[l] = f;
}
layoutdata.MergeFrom(f);
}
return layout;
trackInfo->numTracks = config.layout().tracks();
trackInfo->numSides = config.layout().sides();
trackInfo->sectorSize = layoutdata.sector_size();
trackInfo->logicalTrack = logicalTrack;
trackInfo->logicalSide = logicalSide;
trackInfo->physicalTrack = remapTrackLogicalToPhysical(logicalTrack);
trackInfo->physicalSide = logicalSide ^ config.layout().swap_sides();
trackInfo->groupSize = getTrackStep();
trackInfo->diskSectorOrder = expandSectorList(layoutdata.physical());
trackInfo->logicalSectorOrder = trackInfo->diskSectorOrder;
std::sort(
trackInfo->diskSectorOrder.begin(), trackInfo->diskSectorOrder.end());
trackInfo->numSectors = trackInfo->logicalSectorOrder.size();
if (layoutdata.has_filesystem())
{
trackInfo->filesystemSectorOrder =
expandSectorList(layoutdata.filesystem());
if (trackInfo->filesystemSectorOrder.size() != trackInfo->numSectors)
Error()
<< "filesystem sector order list doesn't contain the right "
"number of sectors";
}
else
{
for (unsigned sectorId : trackInfo->logicalSectorOrder)
trackInfo->filesystemSectorOrder.push_back(sectorId);
}
for (int i = 0; i < trackInfo->numSectors; i++)
{
unsigned f = trackInfo->logicalSectorOrder[i];
unsigned l = trackInfo->filesystemSectorOrder[i];
trackInfo->filesystemToLogicalSectorMap[f] = l;
trackInfo->logicalToFilesystemSectorMap[l] = f;
}
return trackInfo;
}
std::shared_ptr<const TrackInfo> Layout::getLayoutOfTrackPhysical(

View File

@@ -23,10 +23,15 @@ public:
static unsigned remapSideLogicalToPhysical(unsigned logicalSide);
/* Uses the layout and current track and heads settings to determine
* which Locations are going to be read from or written to. 8/
* which Locations are going to be read from or written to.
*/
static std::vector<std::shared_ptr<const TrackInfo>> computeLocations();
/* Given a list of locations, determines the minimum and maximum track
* and side settings. */
static void getBounds(const std::vector<std::shared_ptr<const TrackInfo>>& locations,
int& minTrack, int& maxTrack, int& minSide, int& maxSide);
/* Returns a series of <track, side> pairs representing the filesystem
* ordering of the disk, in logical numbers. */
static std::vector<std::pair<int, int>> getTrackOrdering(

View File

@@ -4,229 +4,245 @@
#include "fmt/format.h"
#include <regex>
ConfigProto config = []() {
ConfigProto config;
config.mutable_drive()->set_drive(0);
config.mutable_drive()->set_drive(0);
return config;
ConfigProto config = []()
{
ConfigProto config;
config.mutable_drive()->set_drive(0);
config.mutable_drive()->set_drive(0);
return config;
}();
static double toDouble(const std::string& value)
{
size_t idx;
double d = std::stod(value, &idx);
if (value[idx] != '\0')
Error() << fmt::format("invalid number '{}'", value);
return d;
size_t idx;
double d = std::stod(value, &idx);
if (value[idx] != '\0')
Error() << fmt::format("invalid number '{}'", value);
return d;
}
static int64_t toInt64(const std::string& value)
{
size_t idx;
int64_t d = std::stoll(value, &idx);
if (value[idx] != '\0')
Error() << fmt::format("invalid number '{}'", value);
return d;
size_t idx;
int64_t d = std::stoll(value, &idx);
if (value[idx] != '\0')
Error() << fmt::format("invalid number '{}'", value);
return d;
}
static uint64_t toUint64(const std::string& value)
{
size_t idx;
uint64_t d = std::stoull(value, &idx);
if (value[idx] != '\0')
Error() << fmt::format("invalid number '{}'", value);
return d;
size_t idx;
uint64_t d = std::stoull(value, &idx);
if (value[idx] != '\0')
Error() << fmt::format("invalid number '{}'", value);
return d;
}
void setRange(RangeProto* range, const std::string& data)
{
static const std::regex DATA_REGEX("([0-9]+)(?:(?:-([0-9]+))|(?:\\+([0-9]+)))?(?:x([0-9]+))?");
static const std::regex DATA_REGEX(
"([0-9]+)(?:(?:-([0-9]+))|(?:\\+([0-9]+)))?(?:x([0-9]+))?");
std::smatch dmatch;
if (!std::regex_match(data, dmatch, DATA_REGEX))
Error() << "invalid range '" << data << "'";
int start = std::stoi(dmatch[1]);
range->set_start(start);
range->set_end(start);
range->clear_step();
if (!dmatch[2].str().empty())
range->set_end(std::stoi(dmatch[2]));
if (!dmatch[3].str().empty())
range->set_end(std::stoi(dmatch[3]) - range->start());
if (!dmatch[4].str().empty())
range->set_step(std::stoi(dmatch[4]));
std::smatch dmatch;
if (!std::regex_match(data, dmatch, DATA_REGEX))
Error() << "invalid range '" << data << "'";
int start = std::stoi(dmatch[1]);
range->set_start(start);
range->set_end(start);
range->clear_step();
if (!dmatch[2].str().empty())
range->set_end(std::stoi(dmatch[2]));
if (!dmatch[3].str().empty())
range->set_end(std::stoi(dmatch[3]) - range->start());
if (!dmatch[4].str().empty())
range->set_step(std::stoi(dmatch[4]));
}
ProtoField resolveProtoPath(google::protobuf::Message* message, const std::string& path)
ProtoField resolveProtoPath(
google::protobuf::Message* message, const std::string& path)
{
std::string::size_type dot = path.rfind('.');
std::string leading = (dot == std::string::npos) ? "" : path.substr(0, dot);
std::string trailing = (dot == std::string::npos) ? path : path.substr(dot+1);
std::string::size_type dot = path.rfind('.');
std::string leading = (dot == std::string::npos) ? "" : path.substr(0, dot);
std::string trailing =
(dot == std::string::npos) ? path : path.substr(dot + 1);
const auto* descriptor = message->GetDescriptor();
const auto* descriptor = message->GetDescriptor();
std::string item;
std::string item;
std::stringstream ss(leading);
while (std::getline(ss, item, '.'))
{
const auto* field = descriptor->FindFieldByName(item);
if (!field)
Error() << fmt::format("no such config field '{}' in '{}'", item, path);
if (field->type() != google::protobuf::FieldDescriptor::TYPE_MESSAGE)
Error() << fmt::format("config field '{}' in '{}' is not a message", item, path);
{
const auto* field = descriptor->FindFieldByName(item);
if (!field)
Error() << fmt::format(
"no such config field '{}' in '{}'", item, path);
if (field->type() != google::protobuf::FieldDescriptor::TYPE_MESSAGE)
Error() << fmt::format(
"config field '{}' in '{}' is not a message", item, path);
const auto* reflection = message->GetReflection();
switch (field->label())
{
case google::protobuf::FieldDescriptor::LABEL_OPTIONAL:
message = reflection->MutableMessage(message, field);
break;
const auto* reflection = message->GetReflection();
switch (field->label())
{
case google::protobuf::FieldDescriptor::LABEL_OPTIONAL:
message = reflection->MutableMessage(message, field);
break;
case google::protobuf::FieldDescriptor::LABEL_REPEATED:
if (reflection->FieldSize(*message, field) == 0)
message = reflection->AddMessage(message, field);
else
message = reflection->MutableRepeatedMessage(message, field, 0);
break;
case google::protobuf::FieldDescriptor::LABEL_REPEATED:
if (reflection->FieldSize(*message, field) == 0)
message = reflection->AddMessage(message, field);
else
message =
reflection->MutableRepeatedMessage(message, field, 0);
break;
default:
Error() << "bad proto label " << field->label();
}
default:
Error() << "bad proto label " << field->label();
}
descriptor = message->GetDescriptor();
descriptor = message->GetDescriptor();
}
const auto* field = descriptor->FindFieldByName(trailing);
if (!field)
Error() << fmt::format("no such config field '{}' in '{}'", trailing, path);
const auto* field = descriptor->FindFieldByName(trailing);
if (!field)
Error() << fmt::format(
"no such config field '{}' in '{}'", trailing, path);
return std::make_pair(message, field);
return std::make_pair(message, field);
}
void setProtoFieldFromString(ProtoField& protoField, const std::string& value)
{
google::protobuf::Message* message = protoField.first;
const google::protobuf::FieldDescriptor* field = protoField.second;
google::protobuf::Message* message = protoField.first;
const google::protobuf::FieldDescriptor* field = protoField.second;
const auto* reflection = message->GetReflection();
switch (field->type())
{
case google::protobuf::FieldDescriptor::TYPE_DOUBLE:
reflection->SetDouble(message, field, toDouble(value));
break;
const auto* reflection = message->GetReflection();
switch (field->type())
{
case google::protobuf::FieldDescriptor::TYPE_DOUBLE:
reflection->SetDouble(message, field, toDouble(value));
break;
case google::protobuf::FieldDescriptor::TYPE_INT32:
reflection->SetInt32(message, field, toInt64(value));
break;
case google::protobuf::FieldDescriptor::TYPE_INT32:
reflection->SetInt32(message, field, toInt64(value));
break;
case google::protobuf::FieldDescriptor::TYPE_INT64:
reflection->SetInt64(message, field, toInt64(value));
break;
case google::protobuf::FieldDescriptor::TYPE_INT64:
reflection->SetInt64(message, field, toInt64(value));
break;
case google::protobuf::FieldDescriptor::TYPE_UINT32:
reflection->SetUInt32(message, field, toUint64(value));
break;
case google::protobuf::FieldDescriptor::TYPE_UINT32:
reflection->SetUInt32(message, field, toUint64(value));
break;
case google::protobuf::FieldDescriptor::TYPE_UINT64:
reflection->SetUInt64(message, field, toUint64(value));
break;
case google::protobuf::FieldDescriptor::TYPE_UINT64:
reflection->SetUInt64(message, field, toUint64(value));
break;
case google::protobuf::FieldDescriptor::TYPE_STRING:
reflection->SetString(message, field, value);
break;
case google::protobuf::FieldDescriptor::TYPE_STRING:
reflection->SetString(message, field, value);
break;
case google::protobuf::FieldDescriptor::TYPE_BOOL:
{
static const std::map<std::string, bool> boolvalues = {
{ "false", false },
{ "f", false },
{ "no", false },
{ "n", false },
{ "0", false },
{ "true", true },
{ "t", true },
{ "yes", true },
{ "y", true },
{ "1", true },
};
case google::protobuf::FieldDescriptor::TYPE_BOOL:
{
static const std::map<std::string, bool> boolvalues = {
{"false", false},
{"f", false},
{"no", false},
{"n", false},
{"0", false},
{"true", true },
{"t", true },
{"yes", true },
{"y", true },
{"1", true },
};
const auto& it = boolvalues.find(value);
if (it == boolvalues.end())
Error() << "invalid boolean value";
reflection->SetBool(message, field, it->second);
break;
}
const auto& it = boolvalues.find(value);
if (it == boolvalues.end())
Error() << "invalid boolean value";
reflection->SetBool(message, field, it->second);
break;
}
case google::protobuf::FieldDescriptor::TYPE_ENUM:
{
const auto* enumfield = field->enum_type();
const auto* enumvalue = enumfield->FindValueByName(value);
if (!enumvalue)
Error() << fmt::format("unrecognised enum value '{}'", value);
case google::protobuf::FieldDescriptor::TYPE_ENUM:
{
const auto* enumfield = field->enum_type();
const auto* enumvalue = enumfield->FindValueByName(value);
if (!enumvalue)
Error() << fmt::format("unrecognised enum value '{}'", value);
reflection->SetEnum(message, field, enumvalue);
break;
}
reflection->SetEnum(message, field, enumvalue);
break;
}
case google::protobuf::FieldDescriptor::TYPE_MESSAGE:
if (field->message_type() == RangeProto::descriptor())
{
setRange((RangeProto*)reflection->MutableMessage(message, field), value);
break;
}
if (field->containing_oneof() && value.empty())
{
reflection->MutableMessage(message, field);
break;
}
/* fall through */
default:
Error() << "can't set this config value type";
}
case google::protobuf::FieldDescriptor::TYPE_MESSAGE:
if (field->message_type() == RangeProto::descriptor())
{
setRange(
(RangeProto*)reflection->MutableMessage(message, field),
value);
break;
}
if (field->containing_oneof() && value.empty())
{
reflection->MutableMessage(message, field);
break;
}
/* fall through */
default:
Error() << "can't set this config value type";
}
}
void setProtoByString(google::protobuf::Message* message, const std::string& path, const std::string& value)
void setProtoByString(google::protobuf::Message* message,
const std::string& path,
const std::string& value)
{
ProtoField protoField = resolveProtoPath(message, path);
setProtoFieldFromString(protoField, value);
ProtoField protoField = resolveProtoPath(message, path);
setProtoFieldFromString(protoField, value);
}
std::set<unsigned> iterate(const RangeProto& range)
{
std::set<unsigned> set;
int end = range.has_end()? range.end() : range.start();
for (unsigned i=range.start(); i<=end; i+=range.step())
set.insert(i);
return set;
std::set<unsigned> set;
int end = range.has_end() ? range.end() : range.start();
for (unsigned i = range.start(); i <= end; i += range.step())
set.insert(i);
return set;
}
std::set<unsigned> iterate(unsigned start, unsigned count)
{
std::set<unsigned> set;
for (unsigned i=0; i<count; i++)
set.insert(start + i);
return set;
std::set<unsigned> set;
for (unsigned i = 0; i < count; i++)
set.insert(start + i);
return set;
}
std::map<std::string, const google::protobuf::FieldDescriptor*> findAllProtoFields(google::protobuf::Message* message)
std::map<std::string, const google::protobuf::FieldDescriptor*>
findAllProtoFields(google::protobuf::Message* message)
{
std::map<std::string, const google::protobuf::FieldDescriptor*> fields;
const auto* descriptor = message->GetDescriptor();
std::map<std::string, const google::protobuf::FieldDescriptor*> fields;
const auto* descriptor = message->GetDescriptor();
std::function<void(const google::protobuf::Descriptor*, const std::string&)> recurse =
[&](auto* d, const auto& s) {
for (int i=0; i<d->field_count(); i++)
{
const google::protobuf::FieldDescriptor* f = d->field(i);
std::string n = s + f->name();
if (f->type() == google::protobuf::FieldDescriptor::TYPE_MESSAGE)
recurse(f->message_type(), n + ".");
fields[n] = f;
}
};
std::function<void(const google::protobuf::Descriptor*, const std::string&)>
recurse = [&](auto* d, const auto& s)
{
for (int i = 0; i < d->field_count(); i++)
{
const google::protobuf::FieldDescriptor* f = d->field(i);
std::string n = s + f->name();
recurse(descriptor, "");
return fields;
if (f->options().GetExtension(::recurse) &&
(f->type() == google::protobuf::FieldDescriptor::TYPE_MESSAGE))
recurse(f->message_type(), n + ".");
fields[n] = f;
}
};
recurse(descriptor, "");
return fields;
}

View File

File diff suppressed because it is too large Load Diff

View File

@@ -3,6 +3,7 @@
#include "lib/bytes.h"
#include <fmt/format.h>
#include <iomanip>
#include <fstream>
bool emergencyStop = false;
@@ -190,3 +191,10 @@ std::string tohex(const std::string& s)
return ss.str();
}
bool doesFileExist(const std::string& filename)
{
std::ifstream f(filename);
return f.good();
}

View File

@@ -18,6 +18,7 @@ extern std::string toIso8601(time_t t);
extern std::string quote(const std::string& s);
extern std::string unhex(const std::string& s);
extern std::string tohex(const std::string& s);
extern bool doesFileExist(const std::string& filename);
/* If set, any running job will terminate as soon as possible (with an error).
*/

View File

@@ -3,77 +3,77 @@
#include "lib/config.pb.h"
#include <fmt/format.h>
class Entry
{
public:
Entry(const Bytes& bytes, int map_entry_size)
{
user = bytes[0] & 0x0f;
{
std::stringstream ss;
ss << (char)(user + '0') << ':';
for (int i = 1; i <= 8; i++)
{
uint8_t c = bytes[i] & 0x7f;
if (c == ' ')
break;
ss << (char)c;
}
for (int i = 9; i <= 11; i++)
{
uint8_t c = bytes[i] & 0x7f;
if (c == ' ')
break;
if (i == 9)
ss << '.';
ss << (char)c;
}
filename = ss.str();
}
{
std::stringstream ss;
if (bytes[9] & 0x80)
ss << 'R';
if (bytes[10] & 0x80)
ss << 'S';
if (bytes[11] & 0x80)
ss << 'A';
mode = ss.str();
}
extent = bytes[12] | (bytes[14] << 5);
records = bytes[15];
ByteReader br(bytes);
br.seek(16);
switch (map_entry_size)
{
case 1:
for (int i = 0; i < 16; i++)
allocation_map.push_back(br.read_8());
break;
case 2:
for (int i = 0; i < 8; i++)
allocation_map.push_back(br.read_le16());
break;
}
}
public:
std::string filename;
std::string mode;
unsigned user;
unsigned extent;
unsigned records;
std::vector<unsigned> allocation_map;
};
class CpmFsFilesystem : public Filesystem
{
class Entry
{
public:
Entry(const Bytes& bytes, int map_entry_size)
{
user = bytes[0] & 0x0f;
{
std::stringstream ss;
ss << (char)(user + '0') << ':';
for (int i = 1; i <= 8; i++)
{
uint8_t c = bytes[i] & 0x7f;
if (c == ' ')
break;
ss << (char)c;
}
for (int i = 9; i <= 11; i++)
{
uint8_t c = bytes[i] & 0x7f;
if (c == ' ')
break;
if (i == 9)
ss << '.';
ss << (char)c;
}
filename = ss.str();
}
{
std::stringstream ss;
if (bytes[9] & 0x80)
ss << 'R';
if (bytes[10] & 0x80)
ss << 'S';
if (bytes[11] & 0x80)
ss << 'A';
mode = ss.str();
}
extent = bytes[12] | (bytes[14] << 5);
records = bytes[15];
ByteReader br(bytes);
br.seek(16);
switch (map_entry_size)
{
case 1:
for (int i = 0; i < 16; i++)
allocation_map.push_back(br.read_8());
break;
case 2:
for (int i = 0; i < 8; i++)
allocation_map.push_back(br.read_le16());
break;
}
}
public:
std::string filename;
std::string mode;
unsigned user;
unsigned extent;
unsigned records;
std::vector<unsigned> allocation_map;
};
public:
CpmFsFilesystem(
const CpmFsProto& config, std::shared_ptr<SectorInterface> sectors):

View File

@@ -246,11 +246,11 @@ public:
switch (cmd)
{
case GET_SECTOR_SIZE:
*(DWORD*)buffer = getLogicalSectorSize();
*(WORD*)buffer = getLogicalSectorSize();
break;
case GET_SECTOR_COUNT:
*(DWORD*)buffer = getLogicalSectorCount();
*(LBA_t*)buffer = getLogicalSectorCount();
break;
case CTRL_SYNC:

211
lib/vfs/smaky6fs.cc Normal file
View File

@@ -0,0 +1,211 @@
#include "lib/globals.h"
#include "lib/vfs/vfs.h"
#include "lib/config.pb.h"
#include "lib/utils.h"
#include <fmt/format.h>
/* A directory entry looks like:
*
* 00-09: ten byte filename FFFFFFFF.EE
* 0a-0b: word: start sector
* 0c-17: unknown
*/
class Smaky6Filesystem : public Filesystem
{
class Entry
{
public:
Entry(const Bytes& bytes)
{
ByteReader br(bytes);
br.seek(10);
startSector = br.read_le16();
endSector = br.read_le16();
}
public:
std::string filename;
std::string mode;
uint16_t startSector;
uint16_t endSector;
};
class SmakyDirent : public Dirent
{
public:
SmakyDirent(const Bytes& dbuf)
{
{
std::stringstream ss;
for (int i = 0; i <= 7; i++)
{
uint8_t c = dbuf[i] & 0x7f;
if (c == ' ')
break;
ss << (char)c;
}
for (int i = 8; i <= 9; i++)
{
uint8_t c = dbuf[i] & 0x7f;
if (c == ' ')
break;
if (i == 8)
ss << '.';
ss << (char)c;
}
filename = ss.str();
}
std::string metadataBytes;
{
std::stringstream ss;
for (int i = 10; i < 0x18; i++)
ss << fmt::format("{:02x} ", (uint8_t)dbuf[i]);
metadataBytes = ss.str();
}
ByteReader br(dbuf);
br.skip(10); /* filename */
startSector = br.read_le16();
endSector = br.read_le16();
br.skip(2); /* unknown */
lastSectorLength = br.read_le16();
file_type = TYPE_FILE;
length = (endSector - startSector - 1) * 256 + lastSectorLength;
path = {filename};
attributes[Filesystem::FILENAME] = filename;
attributes[Filesystem::LENGTH] = std::to_string(length);
attributes[Filesystem::FILE_TYPE] = "file";
attributes[Filesystem::MODE] = "";
attributes["smaky6.start_sector"] = std::to_string(startSector);
attributes["smaky6.end_sector"] = std::to_string(endSector);
attributes["smaky6.sectors"] =
std::to_string(endSector - startSector);
attributes["smaky6.metadata_bytes"] = metadataBytes;
}
public:
unsigned startSector;
unsigned endSector;
unsigned lastSectorLength;
};
friend class Directory;
class Directory
{
public:
Directory(Smaky6Filesystem* fs)
{
/* Read the directory. */
auto bytes = fs->getLogicalSector(0, 3);
ByteReader br(bytes);
for (int i = 0; i < 32; i++)
{
auto dbuf = bytes.slice(i * 0x18, 0x18);
if (dbuf[0])
{
auto de = std::make_shared<SmakyDirent>(dbuf);
dirents.push_back(de);
}
}
}
std::shared_ptr<SmakyDirent> findFile(const std::string& filename)
{
for (auto& de : dirents)
if (de->filename == filename)
return de;
throw FileNotFoundException();
}
public:
std::vector<std::shared_ptr<SmakyDirent>> dirents;
};
public:
Smaky6Filesystem(
const Smaky6FsProto& config, std::shared_ptr<SectorInterface> sectors):
Filesystem(sectors),
_config(config)
{
}
uint32_t capabilities() const
{
return OP_LIST | OP_GETFILE | OP_GETFSDATA | OP_GETDIRENT;
}
FilesystemStatus check() override
{
return FS_OK;
}
std::map<std::string, std::string> getMetadata() override
{
Directory dir(this);
unsigned usedBlocks = 3;
for (auto& de : dir.dirents)
usedBlocks += (de->endSector - de->startSector);
std::map<std::string, std::string> attributes;
attributes[VOLUME_NAME] = "";
attributes[TOTAL_BLOCKS] = std::to_string(getLogicalSectorCount());
attributes[USED_BLOCKS] = std::to_string(usedBlocks);
attributes[BLOCK_SIZE] = std::to_string(getLogicalSectorSize(0, 0));
return attributes;
}
std::vector<std::shared_ptr<Dirent>> list(const Path& path) override
{
if (!path.empty())
throw FileNotFoundException();
Directory dir(this);
std::vector<std::shared_ptr<Dirent>> result;
for (auto& de : dir.dirents)
result.push_back(de);
return result;
}
std::shared_ptr<Dirent> getDirent(const Path& path) override
{
Directory dir(this);
if (path.size() != 1)
throw BadPathException();
return dir.findFile(path[0]);
}
Bytes getFile(const Path& path) override
{
if (path.size() != 1)
throw BadPathException(path);
Directory dir(this);
auto de = dir.findFile(path[0]);
Bytes data =
getLogicalSector(de->startSector, de->endSector - de->startSector);
data = data.slice(0, de->length);
return data;
}
private:
const Smaky6FsProto& _config;
};
std::unique_ptr<Filesystem> Filesystem::createSmaky6Filesystem(
const FilesystemProto& config, std::shared_ptr<SectorInterface> sectors)
{
return std::make_unique<Smaky6Filesystem>(config.smaky6(), sectors);
}

View File

@@ -177,32 +177,35 @@ Filesystem::Filesystem(std::shared_ptr<SectorInterface> sectors):
std::unique_ptr<Filesystem> Filesystem::createFilesystem(
const FilesystemProto& config, std::shared_ptr<SectorInterface> image)
{
switch (config.filesystem_case())
switch (config.type())
{
case FilesystemProto::kBrother120:
case FilesystemProto::BROTHER120:
return Filesystem::createBrother120Filesystem(config, image);
case FilesystemProto::kAcorndfs:
case FilesystemProto::ACORNDFS:
return Filesystem::createAcornDfsFilesystem(config, image);
case FilesystemProto::kFatfs:
case FilesystemProto::FATFS:
return Filesystem::createFatFsFilesystem(config, image);
case FilesystemProto::kCpmfs:
case FilesystemProto::CPMFS:
return Filesystem::createCpmFsFilesystem(config, image);
case FilesystemProto::kAmigaffs:
case FilesystemProto::AMIGAFFS:
return Filesystem::createAmigaFfsFilesystem(config, image);
case FilesystemProto::kMachfs:
case FilesystemProto::MACHFS:
return Filesystem::createMacHfsFilesystem(config, image);
case FilesystemProto::kCbmfs:
case FilesystemProto::CBMFS:
return Filesystem::createCbmfsFilesystem(config, image);
case FilesystemProto::kProdos:
case FilesystemProto::PRODOS:
return Filesystem::createProdosFilesystem(config, image);
case FilesystemProto::SMAKY6:
return Filesystem::createSmaky6Filesystem(config, image);
default:
Error() << "no filesystem configured";
return std::unique_ptr<Filesystem>();
@@ -218,13 +221,12 @@ std::unique_ptr<Filesystem> Filesystem::createFilesystemFromConfig()
std::shared_ptr<Decoder> decoder;
std::shared_ptr<FluxSink> fluxSink;
std::shared_ptr<Encoder> encoder;
if (config.flux_source().source_case() !=
FluxSourceProto::SOURCE_NOT_SET)
if (config.flux_source().type() != FluxSourceProto::NOT_SET)
{
fluxSource = FluxSource::create(config.flux_source());
decoder = Decoder::create(config.decoder());
}
if (config.flux_sink().has_drive())
if (config.flux_sink().type() == FluxSinkProto::DRIVE)
{
fluxSink = FluxSink::create(config.flux_sink());
encoder = Encoder::create(config.encoder());
@@ -236,11 +238,10 @@ std::unique_ptr<Filesystem> Filesystem::createFilesystemFromConfig()
{
std::shared_ptr<ImageReader> reader;
std::shared_ptr<ImageWriter> writer;
if (config.image_reader().format_case() !=
ImageReaderProto::FORMAT_NOT_SET)
if ((config.image_reader().type() != ImageReaderProto::NOT_SET) &&
doesFileExist(config.image_reader().filename()))
reader = ImageReader::create(config.image_reader());
if (config.image_writer().format_case() !=
ImageWriterProto::FORMAT_NOT_SET)
if (config.image_writer().type() != ImageWriterProto::NOT_SET)
writer = ImageWriter::create(config.image_writer());
sectorInterface =

View File

@@ -2,6 +2,7 @@
#define VFS_H
#include "lib/bytes.h"
#include <fmt/format.h>
class Sector;
class Image;
@@ -58,6 +59,11 @@ public:
class BadPathException : public FilesystemException
{
public:
BadPathException(const Path& path):
FilesystemException(fmt::format("Bad path: '{}'", path.to_str()))
{
}
BadPathException(): FilesystemException("Bad path") {}
BadPathException(const std::string& msg): FilesystemException(msg) {}
@@ -242,6 +248,8 @@ public:
const FilesystemProto& config, std::shared_ptr<SectorInterface> image);
static std::unique_ptr<Filesystem> createProdosFilesystem(
const FilesystemProto& config, std::shared_ptr<SectorInterface> image);
static std::unique_ptr<Filesystem> createSmaky6Filesystem(
const FilesystemProto& config, std::shared_ptr<SectorInterface> image);
static std::unique_ptr<Filesystem> createFilesystem(
const FilesystemProto& config, std::shared_ptr<SectorInterface> image);

View File

@@ -59,20 +59,35 @@ message CbmfsProto
message ProdosProto {}
// NEXT_TAG: 10
message Smaky6FsProto {}
// NEXT_TAG: 12
message FilesystemProto
{
oneof filesystem
{
AcornDfsProto acorndfs = 1;
Brother120FsProto brother120 = 2;
FatFsProto fatfs = 3;
CpmFsProto cpmfs = 4;
AmigaFfsProto amigaffs = 5;
MacHfsProto machfs = 6;
CbmfsProto cbmfs = 7;
ProdosProto prodos = 8;
enum FilesystemType {
NOT_SET = 0;
ACORNDFS = 1;
BROTHER120 = 2;
FATFS = 3;
CPMFS = 4;
AMIGAFFS = 5;
MACHFS = 6;
CBMFS = 7;
PRODOS = 8;
SMAKY6 = 9;
}
optional FilesystemType type = 10 [default = NOT_SET, (help) = "filesystem type"];
optional AcornDfsProto acorndfs = 1;
optional Brother120FsProto brother120 = 2;
optional FatFsProto fatfs = 3;
optional CpmFsProto cpmfs = 4;
optional AmigaFfsProto amigaffs = 5;
optional MacHfsProto machfs = 6;
optional CbmfsProto cbmfs = 7;
optional ProdosProto prodos = 8;
optional Smaky6FsProto smaky6 = 11;
optional SectorListProto sector_order = 9 [(help) = "specify the filesystem order of sectors"];
}

View File

@@ -211,10 +211,10 @@ static void draw_x_graticules(Agg2D& painter, double x1, double y1, double x2, d
int mainAnalyseDriveResponse(int argc, const char* argv[])
{
config.mutable_flux_source()->mutable_drive();
config.mutable_flux_source()->set_type(FluxSourceProto::DRIVE);
flags.parseFlagsWithConfigFiles(argc, argv, {});
if (!config.flux_sink().has_drive())
if (config.flux_sink().type() != FluxSinkProto::DRIVE)
Error() << "this only makes sense with a real disk drive";
usbSetDrive(config.drive().drive(),

View File

@@ -210,7 +210,7 @@ static nanoseconds_t guessClock(const Fluxmap& fluxmap)
int mainInspect(int argc, const char* argv[])
{
config.mutable_flux_source()->mutable_drive();
config.mutable_flux_source()->set_type(FluxSourceProto::DRIVE);
flags.parseFlagsWithConfigFiles(argc, argv, {});
std::unique_ptr<FluxSource> fluxSource(FluxSource::create(config.flux_source()));

View File

@@ -61,10 +61,10 @@ int mainRawRead(int argc, const char* argv[])
if (argc == 1)
showProfiles("rawread", formats);
config.mutable_flux_source()->mutable_drive();
config.mutable_flux_source()->set_type(FluxSourceProto::DRIVE);
flags.parseFlagsWithConfigFiles(argc, argv, formats);
if (config.flux_sink().has_drive())
if (config.flux_sink().type() == FluxSinkProto::DRIVE)
Error() << "you can't use rawread to write to hardware";
std::unique_ptr<FluxSource> fluxSource(FluxSource::create(config.flux_source()));

View File

@@ -54,7 +54,7 @@ static ActionFlag eraseFlag(
"erases the destination",
[]()
{
config.mutable_flux_source()->mutable_erase();
config.mutable_flux_source()->set_type(FluxSourceProto::ERASE);
});
int mainRawWrite(int argc, const char* argv[])
@@ -64,10 +64,10 @@ int mainRawWrite(int argc, const char* argv[])
if (argc == 1)
showProfiles("rawwrite", formats);
config.mutable_flux_sink()->mutable_drive();
config.mutable_flux_sink()->set_type(FluxSinkProto::DRIVE);
flags.parseFlagsWithConfigFiles(argc, argv, formats);
if (config.flux_source().has_drive())
if (config.flux_source().type() == FluxSourceProto::DRIVE)
Error() << "you can't use rawwrite to read from hardware";
std::unique_ptr<FluxSource> fluxSource(FluxSource::create(config.flux_source()));

View File

@@ -67,10 +67,10 @@ int mainRead(int argc, const char* argv[])
{
if (argc == 1)
showProfiles("read", formats);
config.mutable_flux_source()->mutable_drive();
config.mutable_flux_source()->set_type(FluxSourceProto::DRIVE);
flags.parseFlagsWithConfigFiles(argc, argv, formats);
if (config.decoder().copy_flux_to().has_drive())
if (config.decoder().copy_flux_to().type() == FluxSinkProto::DRIVE)
Error() << "you cannot copy flux to a hardware device";
std::unique_ptr<FluxSource> fluxSource(FluxSource::create(config.flux_source()));

View File

@@ -20,7 +20,7 @@ int mainRpm(int argc, const char* argv[])
{
flags.parseFlagsWithConfigFiles(argc, argv, {});
if (!config.flux_source().has_drive())
if (config.flux_source().type() != FluxSourceProto::DRIVE)
Error() << "this only makes sense with a real disk drive";
usbSetDrive(config.drive().drive(), false, config.drive().index_mode());

View File

@@ -27,7 +27,7 @@ int mainSeek(int argc, const char* argv[])
{
flags.parseFlagsWithConfigFiles(argc, argv, {});
if (!config.flux_source().has_drive())
if (config.flux_source().type() != FluxSourceProto::DRIVE)
Error() << "this only makes sense with a real disk drive";
usbSetDrive(config.drive().drive(), false, config.drive().index_mode());

View File

@@ -67,9 +67,9 @@ int mainWrite(int argc, const char* argv[])
{
if (argc == 1)
showProfiles("write", formats);
config.mutable_flux_sink()->mutable_drive();
config.mutable_flux_sink()->set_type(FluxSinkProto::DRIVE);
if (verify)
config.mutable_flux_source()->mutable_drive();
config.mutable_flux_source()->set_type(FluxSourceProto::DRIVE);
flags.parseFlagsWithConfigFiles(argc, argv, formats);
std::unique_ptr<ImageReader> reader(ImageReader::create(config.image_reader()));
@@ -83,7 +83,7 @@ int mainWrite(int argc, const char* argv[])
decoder = Decoder::create(config.decoder());
std::unique_ptr<FluxSource> fluxSource;
if (verify && config.has_flux_source() && config.flux_source().has_drive())
if (verify && (config.flux_source().type() == FluxSourceProto::DRIVE))
fluxSource = FluxSource::create(config.flux_source());
writeDiskCommand(*image, *encoder, *fluxSink, decoder.get(), fluxSource.get());

View File

@@ -3,7 +3,7 @@ is_extension: true
image_writer {
filename: "acornadfs.img"
img {}
type: IMG
}
layout {

View File

@@ -3,7 +3,7 @@ is_extension: true
image_writer {
filename: "acornadfs.img"
img {}
type: IMG
}
layout {

View File

@@ -7,12 +7,12 @@ drive {
image_reader {
filename: "micropolis.img"
img {}
type: IMG
}
image_writer {
filename: "micropolis.img"
img {}
type: IMG
}
layout {
@@ -41,12 +41,12 @@ option {
config {
image_reader {
filename: "micropolis.vgi"
img {}
type: IMG
}
image_writer {
filename: "micropolis.vgi"
img {}
type: IMG
}
layout {

View File

@@ -3,7 +3,7 @@ is_extension: true
image_writer {
filename: "mx.img"
img {}
type: IMG
}
layout {

View File

@@ -3,12 +3,12 @@ is_extension: true
image_reader {
filename: "northstar.nsi"
nsi {}
type: NSI
}
image_writer {
filename: "northstar.nsi"
nsi {}
type: NSI
}
layout {

View File

@@ -2,12 +2,12 @@ comment: 'Acorn DFS 100kB/200kB 3.5" or 5.25" 40- or 80-track SS (ro)'
image_reader {
filename: "acorndfs.img"
img {}
type: IMG
}
image_writer {
filename: "acorndfs.img"
img {}
type: IMG
}
layout {
@@ -44,6 +44,6 @@ decoder {
}
filesystem {
acorndfs {}
type: ACORNDFS
}

View File

@@ -2,7 +2,7 @@ comment: 'AES Lanier "No Problem" 616kB 5.25" 77-track SSDD hard sectored (ro)'
image_writer {
filename: "aeslanier.img"
img {}
type: IMG
}
decoder {

View File

@@ -2,7 +2,7 @@ comment: 'Agat 840kB 5.25" 80-track DS (ro)'
image_writer {
filename: "agat.img"
img {}
type: IMG
}
layout {

View File

@@ -2,12 +2,12 @@ comment: 'Amiga 880kB 3.5" DSDD'
image_reader {
filename: "amiga.adf"
img {}
type: IMG
}
image_writer {
filename: "amiga.adf"
img {}
type: IMG
}
layout {
@@ -31,6 +31,6 @@ decoder {
}
filesystem {
amigaffs {}
type: AMIGAFFS
}

View File

@@ -2,7 +2,7 @@ comment: 'Ampro 400kB/800kB 5.25" 40/80 track SSDD/DSDD (ro)'
image_writer {
filename: "ampro.img"
img {}
type: IMG
}
layout {

View File

@@ -2,7 +2,7 @@ comment: 'Ampro 400kB/800kB 5.25" 40/80 track SSDD/DSDD (ro)'
image_writer {
filename: "ampro.img"
img {}
type: IMG
}
layout {

View File

@@ -2,7 +2,7 @@ comment: 'Apple II 140kB DOS 3.3 5.25" 40 track SSSD'
image_reader {
filename: "apple2.img"
img {}
type: IMG
}
layout {
@@ -19,7 +19,7 @@ layout {
image_writer {
filename: "apple2.img"
img {}
type: IMG
}
decoder {
@@ -86,7 +86,7 @@ option {
}
filesystem {
prodos {}
type: PRODOS
}
layout {

View File

@@ -4,12 +4,12 @@ include: '_atari'
image_reader {
filename: "atarist360.st"
img {}
type: IMG
}
image_writer {
filename: "atarist360.st"
img {}
type: IMG
}
layout {

View File

@@ -4,12 +4,12 @@ include: '_atari'
image_reader {
filename: "atarist370.st"
img {}
type: IMG
}
image_writer {
filename: "atarist370.st"
img {}
type: IMG
}
layout {

View File

@@ -4,12 +4,12 @@ include: '_atari'
image_reader {
filename: "atarist400.st"
img {}
type: IMG
}
image_writer {
filename: "atarist400.st"
img {}
type: IMG
}
layout {

View File

@@ -4,12 +4,12 @@ include: '_atari'
image_reader {
filename: "atarist410.st"
img {}
type: IMG
}
image_writer {
filename: "atarist410.st"
img {}
type: IMG
}
layout {

View File

@@ -4,12 +4,12 @@ include: '_atari'
image_reader {
filename: "atarist720.st"
img {}
type: IMG
}
image_writer {
filename: "atarist720.st"
img {}
type: IMG
}
layout {

View File

@@ -4,12 +4,12 @@ include: '_atari'
image_reader {
filename: "atarist740.st"
img {}
type: IMG
}
image_writer {
filename: "atarist740.st"
img {}
type: IMG
}
layout {

View File

@@ -4,12 +4,12 @@ include: '_atari'
image_reader {
filename: "atarist800.st"
img {}
type: IMG
}
image_writer {
filename: "atarist800.st"
img {}
type: IMG
}
layout {

View File

@@ -4,12 +4,12 @@ include: '_atari'
image_reader {
filename: "atarist820.st"
img {}
type: IMG
}
image_writer {
filename: "atarist820.st"
img {}
type: IMG
}
layout {

View File

@@ -2,12 +2,12 @@ comment: 'BK 800kB 5.25"/3.5" 80-track 10-sector DSDD'
image_reader {
filename: "bk800.img"
img {}
type: IMG
}
image_writer {
filename: "bk800.img"
img {}
type: IMG
}
layout {

View File

@@ -2,12 +2,12 @@ comment: 'Brother 120kB 3.5" 39-track SS GCR'
image_reader {
filename: "brother120.img"
img {}
type: IMG
}
image_writer {
filename: "brother120.img"
img {}
type: IMG
}
layout {
@@ -41,6 +41,6 @@ drive {
tpi: 48
filesystem {
brother120 {}
type: BROTHER120
}

View File

@@ -2,12 +2,12 @@ comment: 'Brother 240kB 3.5" 78-track SS GCR'
image_reader {
filename: "brother240.img"
img {}
type: IMG
}
image_writer {
filename: "brother240.img"
img {}
type: IMG
}
layout {
@@ -36,6 +36,6 @@ drive {
}
filesystem {
fatfs {}
type: FATFS
}

View File

@@ -46,6 +46,7 @@ FORMATS = \
ibm180 \
ibm360 \
ibm720 \
icl30 \
mac400 \
mac800 \
micropolis143 \
@@ -62,6 +63,7 @@ FORMATS = \
northstar87 \
rx50 \
shugart_drive \
smaky6 \
tids990 \
victor9k_ds \
victor9k_ss \

View File

@@ -2,12 +2,12 @@ comment: 'CMD FD2000 1620kB 3.5" DSHD'
image_reader {
filename: "cmd_fd2000.img"
img {}
type: IMG
}
image_writer {
filename: "cmd_fd2000.img"
img {}
type: IMG
}
layout {

View File

@@ -2,12 +2,12 @@ comment: 'Commodore 1541 171kB/192kB 5.25" 35/40-track SS GCR'
image_reader {
filename: "commodore1541.d64"
d64 {}
type: D64
}
image_writer {
filename: "commodore1541.d64"
d64 {}
type: D64
}
layout {
@@ -61,7 +61,7 @@ decoder {
tpi: 48
filesystem {
cbmfs {}
type: CBMFS
}
option {

View File

@@ -2,12 +2,12 @@ comment: 'Commodore 1581 800kB 3.5" DSDD'
image_reader {
filename: "commodore1581.d81"
img {}
type: IMG
}
image_writer {
filename: "commodore1581.d81"
img {}
type: IMG
}
layout {

View File

@@ -2,7 +2,7 @@ comment: 'VDS Eco1 1210kB 77-track mixed format DSHD (ro)'
image_writer {
filename: "eco1.img"
img {}
type: IMG
}
layout {
@@ -42,6 +42,7 @@ decoder {
}
filesystem {
type: CPMFS
cpmfs {
filesystem_start {
track: 2

View File

@@ -2,7 +2,7 @@ comment: 'Epson PF-10 40-track DS DD (ro)'
image_writer {
filename: "epsonpf10.img"
img {}
type: IMG
}
layout {
@@ -24,6 +24,7 @@ decoder {
tpi: 48
filesystem {
type: CPMFS
cpmfs {
filesystem_start {
track: 4

View File

@@ -2,7 +2,7 @@ comment: 'Durango F85 461kB 5.25" 77-track SS (ro)'
image_writer {
filename: "f85.img"
img {}
type: IMG
}
decoder {

View File

@@ -2,7 +2,7 @@ comment: 'Brother FB-100 100kB 3.5" 40-track SS (ro)'
image_writer {
filename: "fb100.img"
img {}
type: IMG
}
decoder {

View File

@@ -2,7 +2,7 @@ comment: 'Hewlett-Packard 9121 264kB 3.5" SSDD'
image_reader {
filename: "hp9121.img"
img {}
type: IMG
}
layout {

View File

@@ -7,12 +7,12 @@ drive {
image_reader {
filename: "hplif770.img"
img {}
type: IMG
}
image_writer {
filename: "hplif770.img"
img {}
type: IMG
}
layout {

View File

@@ -2,18 +2,13 @@ comment: 'PC 3.5"/5.25" autodetect double sided format'
image_writer {
filename: "ibm.img"
img {}
type: IMG
}
decoder {
ibm {}
}
tracks {
start: 0
end: 81
}
heads {
start: 0
end: 1

View File

@@ -2,12 +2,12 @@ comment: 'PC 1200kB 5.25" 80-track 15-sector DSHD'
image_reader {
filename: "ibm1200.img"
img {}
type: IMG
}
image_writer {
filename: "ibm1200.img"
img {}
type: IMG
}
layout {
@@ -40,7 +40,7 @@ drive {
}
filesystem {
fatfs {}
type: FATFS
}

View File

@@ -2,12 +2,12 @@ comment: 'Japanese PC 1232kB 5.25"/3.5" 77-track 8-sector DSHD'
image_reader {
filename: "ibm1232.img"
img {}
type: IMG
}
image_writer {
filename: "ibm1232.img"
img {}
type: IMG
}
layout {
@@ -40,7 +40,7 @@ drive {
}
filesystem {
fatfs {}
type: FATFS
}

View File

@@ -2,12 +2,12 @@ comment: 'PC 1440kB 3.5" 80-track 18-sector DSHD'
image_reader {
filename: "ibm1440.img"
img {}
type: IMG
}
image_writer {
filename: "ibm1440.img"
img {}
type: IMG
}
layout {
@@ -36,6 +36,6 @@ decoder {
}
filesystem {
fatfs {}
type: FATFS
}

View File

@@ -2,12 +2,12 @@ comment: 'PC 180kB 5.25" 40-track 9-sector SSDD'
image_reader {
filename: "ibm180.img"
img {}
type: IMG
}
image_writer {
filename: "ibm180.img"
img {}
type: IMG
}
layout {
@@ -42,7 +42,7 @@ drive {
tpi: 48
filesystem {
fatfs {}
type: FATFS
}

View File

@@ -2,12 +2,12 @@ comment: 'PC 360kB 5.25" 40-track 9-sector DSDD'
image_reader {
filename: "ibm360.img"
img {}
type: IMG
}
image_writer {
filename: "ibm360.img"
img {}
type: IMG
}
layout {
@@ -38,7 +38,7 @@ decoder {
tpi: 48
filesystem {
fatfs {}
type: FATFS
}

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