Compare commits

..

2 Commits
scp ... trs80

Author SHA1 Message Date
David Given
f2c6487d49 Add test-only TRS-80 formats. 2022-10-13 22:53:02 +02:00
David Given
4cece77bff Remove stray cout printing. 2022-10-13 22:52:47 +02:00
201 changed files with 8625 additions and 13457 deletions

View File

@@ -18,19 +18,17 @@ AlwaysBreakBeforeMultilineStrings: 'true'
AlwaysBreakTemplateDeclarations: 'Yes'
BinPackArguments: 'false'
BinPackParameters: 'false'
BreakBeforeBraces: Allman
BreakConstructorInitializers: 'AfterColon'
BreakBeforeBraces: Allman
BreakInheritanceList: AfterColon
BreakStringLiterals: 'true'
ColumnLimit: '80'
ConstructorInitializerAllOnOneLineOrOnePerLine: 'true'
FixNamespaceComments: 'false'
IncludeBlocks: Preserve
IndentCaseLabels: 'true'
IndentWidth: '4'
ColumnLimit: '80'
ConstructorInitializerAllOnOneLineOrOnePerLine: 'true'
IncludeBlocks: Preserve
IndentWrappedFunctionNames: 'false'
KeepEmptyLinesAtTheStartOfBlocks: 'true'
NamespaceIndentation: All
PointerAlignment: Left
ReflowComments: 'true'
SortIncludes: 'false'

View File

@@ -10,39 +10,16 @@ jobs:
- name: apt
run: sudo apt update && sudo apt install libudev-dev libsqlite3-dev protobuf-compiler libwxgtk3.0-gtk3-dev libfmt-dev
- name: make
run: CXXFLAGS="-Wp,-D_GLIBCXX_ASSERTIONS" make -j2
run: CXXFLAGS="-Wp,-D_GLIBCXX_ASSERTIONS" make
build-macos-current:
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 dylibbundler libjpeg
run: brew install sqlite pkg-config libusb protobuf wxwidgets fmt make coreutils
- name: make
run: gmake -j2
- name: Upload build artifacts
uses: actions/upload-artifact@v2
with:
name: ${{ github.event.repository.name }}.${{ github.sha }}
path: FluxEngine.pkg
build-macos-10-15:
runs-on: macos-10.15
steps:
- uses: actions/checkout@v2
- name: brew
run: brew install sqlite pkg-config libusb protobuf wxwidgets fmt make coreutils dylibbundler libjpeg
- name: make
run: |
gmake -j2
mv FluxEngine.pkg FluxEngine-10.15.pkg
- name: Upload build artifacts
uses: actions/upload-artifact@v2
with:
name: ${{ github.event.repository.name }}.${{ github.sha }}
path: FluxEngine-10.15.pkg
run: gmake
build-windows:
runs-on: windows-latest
@@ -67,10 +44,9 @@ jobs:
mingw-w64-i686-zlib
mingw-w64-i686-nsis
zip
vim
- uses: actions/checkout@v1
- name: build
run: make -j2
run: make
- name: nsis
run: |
@@ -80,10 +56,10 @@ jobs:
- name: zip
run: |
zip -9 fluxengine-windows.zip fluxengine.exe fluxengine-gui.exe upgrade-flux-file.exe brother120tool.exe brother240tool.exe FluxEngine.cydsn/CortexM3/ARM_GCC_541/Release/FluxEngine.hex fluxengine-installer.exe
zip -9 fluxengine.zip fluxengine.exe fluxengine-gui.exe upgrade-flux-file.exe brother120tool.exe brother240tool.exe FluxEngine.cydsn/CortexM3/ARM_GCC_541/Release/FluxEngine.hex fluxengine-installer.exe
- name: Upload build artifacts
uses: actions/upload-artifact@v2
with:
name: ${{ github.event.repository.name }}.${{ github.sha }}
path: fluxengine-windows.zip
path: fluxengine.zip

View File

@@ -29,12 +29,11 @@ jobs:
mingw-w64-i686-zlib
mingw-w64-i686-nsis
zip
vim
- uses: actions/checkout@v3
- name: build
run: |
make -j2
make
- name: nsis
run: |
@@ -78,42 +77,3 @@ 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 dylibbundler libjpeg
- 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

@@ -111,8 +111,6 @@ PROTOS = \
arch/micropolis/micropolis.proto \
arch/mx/mx.proto \
arch/northstar/northstar.proto \
arch/rolandd20/rolandd20.proto \
arch/smaky6/smaky6.proto \
arch/tids990/tids990.proto \
arch/victor9k/victor9k.proto \
arch/zilogmcz/zilogmcz.proto \
@@ -161,23 +159,16 @@ include tests/build.mk
do-encodedecodetest = $(eval $(do-encodedecodetest-impl))
define do-encodedecodetest-impl
tests: $(OBJDIR)/$1$3.flux.encodedecode
$(OBJDIR)/$1$3.flux.encodedecode: scripts/encodedecodetest.sh $(FLUXENGINE_BIN) $2
tests: $(OBJDIR)/$1$3.encodedecode
$(OBJDIR)/$1$3.encodedecode: scripts/encodedecodetest.sh $(FLUXENGINE_BIN) $2
@mkdir -p $(dir $$@)
@echo ENCODEDECODETEST $1 flux $(FLUXENGINE_BIN) $2 $3
@echo ENCODEDECODETEST $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 $1 scp $(FLUXENGINE_BIN) $2 $3
@scripts/encodedecodetest.sh $1 scp $(FLUXENGINE_BIN) $2 $3 > $$@
endef
$(call do-encodedecodetest,agat840)
$(call do-encodedecodetest,amiga)
$(call do-encodedecodetest,appleii140)
$(call do-encodedecodetest,apple2)
$(call do-encodedecodetest,atarist360)
$(call do-encodedecodetest,atarist370)
$(call do-encodedecodetest,atarist400)
@@ -205,6 +196,10 @@ $(call do-encodedecodetest,mac800,scripts/mac800_test.textpb)
$(call do-encodedecodetest,n88basic)
$(call do-encodedecodetest,rx50)
$(call do-encodedecodetest,tids990)
$(call do-encodedecodetest,trs80_88,,--35)
$(call do-encodedecodetest,trs80_88,,--40)
$(call do-encodedecodetest,trs80_175,,--35)
$(call do-encodedecodetest,trs80_175,,--40)
$(call do-encodedecodetest,victor9k_ss)
$(call do-encodedecodetest,victor9k_ds)

View File

@@ -106,7 +106,7 @@ people who've had it work).
| [Acorn DFS](doc/disk-acorndfs.md) | 🦄 | 🦖* | |
| [Ampro Little Board](doc/disk-ampro.md) | 🦖 | 🦖* | |
| [Agat](doc/disk-agat.md) | 🦖 | | Soviet Union Apple-II-like computer |
| [Apple II](doc/disk-apple2.md) | 🦄 | 🦄 | both 140kB and 640kB formats |
| [Apple II](doc/disk-apple2.md) | 🦄 | 🦄 | |
| [Amiga](doc/disk-amiga.md) | 🦄 | 🦄 | |
| [Commodore 64 1541/1581](doc/disk-c64.md) | 🦄 | 🦄 | and probably the other formats |
| [Brother 120kB](doc/disk-brother.md) | 🦄 | 🦄 | |
@@ -115,9 +115,7 @@ people who've had it work).
| [Elektronika BK](doc/disk-bd.md) | 🦄 | 🦄 | Soviet Union PDP-11 clone |
| [Macintosh 400kB/800kB](doc/disk-macintosh.md) | 🦄 | 🦄 | |
| [NEC PC-98](doc/disk-ibm.md) | 🦄 | 🦄 | trimode drive not required |
| [pSOS](doc/disk-ibm.md) | 🦄 | 🦖* | pSOS PHILE file system |
| [Sharp X68000](doc/disk-ibm.md) | 🦄 | 🦄 | yet another IBM scheme |
| [Smaky 6](doc/disk-smaky6.md) | 🦖 | | 5.25" hard sectored |
| [Sharp X68000](doc/disk-ibm.md) | 🦄 | 🦄 | |
| [TRS-80](doc/disk-trs80.md) | 🦖 | 🦖* | a minor variation of the IBM scheme |
{: .datatable }

View File

@@ -13,6 +13,16 @@ 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:
@@ -33,7 +43,7 @@ public:
const auto& rawbits = readRawBits(AESLANIER_RECORD_SIZE*16);
const auto& bytes = decodeFmMfm(rawbits).slice(0, AESLANIER_RECORD_SIZE);
const auto& reversed = bytes.reverseBits();
const auto& reversed = reverse_bits(bytes);
_sector->logicalTrack = reversed[1];
_sector->logicalSide = 0;

View File

@@ -3,16 +3,7 @@
#define AGAT_SECTOR_SIZE 256
static constexpr uint64_t SECTOR_ID = 0x8924555549111444;
static constexpr uint64_t DATA_ID = 0x8924555514444911;
class Encoder;
class EncoderProto;
class Decoder;
class DecoderProto;
extern std::unique_ptr<Decoder> createAgatDecoder(const DecoderProto& config);
extern std::unique_ptr<Encoder> createAgatEncoder(const EncoderProto& config);
extern uint8_t agatChecksum(const Bytes& bytes);

View File

@@ -1,19 +1,5 @@
syntax = "proto2";
import "lib/common.proto";
message AgatDecoderProto {}
message AgatEncoderProto {
optional double target_clock_period_us = 1
[default=2.00, (help)="Data clock period of target format."];
optional double target_rotational_period_ms = 2
[default=200.0, (help)="Rotational period of target format."];
optional int32 post_index_gap_bytes = 3
[default=40, (help)="Post-index gap before first sector header."];
optional int32 pre_sector_gap_bytes = 4
[default=11, (help)="Gap before each sector header."];
optional int32 pre_data_gap_bytes = 5
[default=2, (help)="Gap before each sector data record."];
}

View File

@@ -33,7 +33,10 @@
*
*/
static const uint64_t SECTOR_ID = 0x8924555549111444;
static const FluxPattern SECTOR_PATTERN(64, SECTOR_ID);
static const uint64_t DATA_ID = 0x8924555514444911;
static const FluxPattern DATA_PATTERN(64, DATA_ID);
static const FluxMatchers ALL_PATTERNS = {

View File

@@ -1,118 +0,0 @@
#include "lib/globals.h"
#include "lib/decoders/decoders.h"
#include "lib/encoders/encoders.h"
#include "agat.h"
#include "lib/crc.h"
#include "lib/readerwriter.h"
#include "lib/image.h"
#include "lib/layout.h"
#include "arch/agat/agat.pb.h"
#include "lib/encoders/encoders.pb.h"
class AgatEncoder : public Encoder
{
public:
AgatEncoder(const EncoderProto& config):
Encoder(config),
_config(config.agat())
{
}
private:
void writeRawBits(uint64_t data, int width)
{
_cursor += width;
_lastBit = data & 1;
for (int i = 0; i < width; i++)
{
unsigned pos = _cursor - i - 1;
if (pos < _bits.size())
_bits[pos] = data & 1;
data >>= 1;
}
}
void writeBytes(const Bytes& bytes)
{
encodeMfm(_bits, _cursor, bytes, _lastBit);
}
void writeByte(uint8_t byte)
{
Bytes b;
b.writer().write_8(byte);
writeBytes(b);
}
void writeFillerRawBytes(int count, uint16_t byte)
{
for (int i = 0; i < count; i++)
writeRawBits(byte, 16);
};
void writeFillerBytes(int count, uint8_t byte)
{
Bytes b{byte};
for (int i = 0; i < count; i++)
writeBytes(b);
};
public:
std::unique_ptr<Fluxmap> encode(std::shared_ptr<const TrackInfo>& trackInfo,
const std::vector<std::shared_ptr<const Sector>>& sectors,
const Image& image) override
{
auto trackLayout = Layout::getLayoutOfTrack(
trackInfo->logicalTrack, trackInfo->logicalSide);
double clockRateUs = _config.target_clock_period_us() / 2.0;
int bitsPerRevolution =
(_config.target_rotational_period_ms() * 1000.0) / clockRateUs;
_bits.resize(bitsPerRevolution);
_cursor = 0;
writeFillerRawBytes(_config.post_index_gap_bytes(), 0xaaaa);
for (const auto& sector : sectors)
{
/* Header */
writeFillerRawBytes(_config.pre_sector_gap_bytes(), 0xaaaa);
writeRawBits(SECTOR_ID, 64);
writeByte(0x5a);
writeByte((sector->logicalTrack << 1) | sector->logicalSide);
writeByte(sector->logicalSector);
writeByte(0x5a);
/* Data */
writeFillerRawBytes(_config.pre_data_gap_bytes(), 0xaaaa);
auto data = sector->data.slice(0, AGAT_SECTOR_SIZE);
writeRawBits(DATA_ID, 64);
writeBytes(data);
writeByte(agatChecksum(data));
writeByte(0x5a);
}
if (_cursor >= _bits.size())
Error() << "track data overrun";
fillBitmapTo(_bits, _cursor, _bits.size(), {true, false});
auto fluxmap = std::make_unique<Fluxmap>();
fluxmap->appendBits(_bits,
calculatePhysicalClockPeriod(_config.target_clock_period_us() * 1e3,
_config.target_rotational_period_ms() * 1e6));
return fluxmap;
}
private:
const AgatEncoderProto& _config;
uint32_t _cursor;
bool _lastBit;
std::vector<bool> _bits;
};
std::unique_ptr<Encoder> createAgatEncoder(const EncoderProto& config)
{
return std::unique_ptr<Encoder>(new AgatEncoder(config));
}

View File

@@ -2,10 +2,7 @@ syntax = "proto2";
import "lib/common.proto";
message Apple2DecoderProto {
optional uint32 side_one_track_offset = 1
[ default = 0, (help) = "offset to apply to track numbers on side 1" ];
}
message Apple2DecoderProto {}
message Apple2EncoderProto
{
@@ -16,7 +13,4 @@ message Apple2EncoderProto
/* Apple II disk drives spin at 300rpm. */
optional double rotational_period_ms = 2
[ default = 200.0, (help) = "rotational period on the real device" ];
optional uint32 side_one_track_offset = 3
[ default = 0, (help) = "offset to apply to track numbers on side 1" ];
}

View File

@@ -5,8 +5,6 @@
#include "decoders/decoders.h"
#include "sector.h"
#include "apple2.h"
#include "arch/apple2/apple2.pb.h"
#include "lib/decoders/decoders.pb.h"
#include "bytes.h"
#include "fmt/format.h"
#include <string.h>
@@ -14,25 +12,22 @@
const FluxPattern SECTOR_RECORD_PATTERN(24, APPLE2_SECTOR_RECORD);
const FluxPattern DATA_RECORD_PATTERN(24, APPLE2_DATA_RECORD);
const FluxMatchers ANY_RECORD_PATTERN(
{&SECTOR_RECORD_PATTERN, &DATA_RECORD_PATTERN});
const FluxMatchers ANY_RECORD_PATTERN({ &SECTOR_RECORD_PATTERN, &DATA_RECORD_PATTERN });
static int decode_data_gcr(uint8_t gcr)
{
switch (gcr)
{
#define GCR_ENTRY(gcr, data) \
case gcr: \
return data;
#include "data_gcr.h"
#undef GCR_ENTRY
#define GCR_ENTRY(gcr, data) \
case gcr: return data;
#include "data_gcr.h"
#undef GCR_ENTRY
}
return -1;
}
/* This is extremely inspired by the MESS implementation, written by Nathan
* Woods and R. Belmont:
* https://github.com/mamedev/mame/blob/7914a6083a3b3a8c243ae6c3b8cb50b023f21e0e/src/lib/formats/ap2_dsk.cpp
/* This is extremely inspired by the MESS implementation, written by Nathan Woods
* and R. Belmont: https://github.com/mamedev/mame/blob/7914a6083a3b3a8c243ae6c3b8cb50b023f21e0e/src/lib/formats/ap2_dsk.cpp
*/
static Bytes decode_crazy_data(const uint8_t* inp, Sector::Status& status)
{
@@ -52,11 +47,9 @@ static Bytes decode_crazy_data(const uint8_t* inp, Sector::Status& status)
{
/* 3 * 2 bit */
output[i + 0] = ((checksum >> 1) & 0x01) | ((checksum << 1) & 0x02);
output[i + 86] =
((checksum >> 3) & 0x01) | ((checksum >> 1) & 0x02);
output[i + 86] = ((checksum >> 3) & 0x01) | ((checksum >> 1) & 0x02);
if ((i + 172) < APPLE2_SECTOR_LENGTH)
output[i + 172] =
((checksum >> 5) & 0x01) | ((checksum >> 3) & 0x02);
output[i + 172] = ((checksum >> 5) & 0x01) | ((checksum >> 3) & 0x02);
}
}
@@ -74,102 +67,88 @@ static uint8_t combine(uint16_t word)
class Apple2Decoder : public Decoder
{
public:
Apple2Decoder(const DecoderProto& config): Decoder(config) {}
Apple2Decoder(const DecoderProto& config):
Decoder(config)
{}
nanoseconds_t advanceToNextRecord() override
{
return seekToPattern(ANY_RECORD_PATTERN);
}
{
return seekToPattern(ANY_RECORD_PATTERN);
}
void decodeSectorRecord() override
{
if (readRaw24() != APPLE2_SECTOR_RECORD)
return;
{
if (readRaw24() != APPLE2_SECTOR_RECORD)
return;
/* Read header. */
/* Read header. */
auto header = toBytes(readRawBits(8 * 8)).slice(0, 8);
ByteReader br(header);
auto header = toBytes(readRawBits(8*8)).slice(0, 8);
ByteReader br(header);
uint8_t volume = combine(br.read_be16());
_sector->logicalTrack = combine(br.read_be16());
_sector->logicalSide = _sector->physicalSide;
_sector->logicalSector = combine(br.read_be16());
uint8_t checksum = combine(br.read_be16());
uint8_t volume = combine(br.read_be16());
_sector->logicalTrack = combine(br.read_be16());
_sector->logicalSector = combine(br.read_be16());
uint8_t checksum = combine(br.read_be16());
// If the checksum is correct, upgrade the sector from MISSING
// to DATA_MISSING in anticipation of its data record
if (checksum ==
(volume ^ _sector->logicalTrack ^ _sector->logicalSector))
_sector->status =
Sector::DATA_MISSING; /* unintuitive but correct */
if (_sector->logicalSide == 1)
_sector->logicalTrack -= _config.apple2().side_one_track_offset();
/* Sanity check. */
if (_sector->logicalTrack > 100)
{
_sector->status = Sector::MISSING;
return;
}
}
// If the checksum is correct, upgrade the sector from MISSING
// to DATA_MISSING in anticipation of its data record
if (checksum == (volume ^ _sector->logicalTrack ^ _sector->logicalSector))
_sector->status = Sector::DATA_MISSING; /* unintuitive but correct */
}
void decodeDataRecord() override
{
/* Check ID. */
{
/* Check ID. */
if (readRaw24() != APPLE2_DATA_RECORD)
return;
if (readRaw24() != APPLE2_DATA_RECORD)
return;
// Sometimes there's a 1-bit gap between APPLE2_DATA_RECORD and
// the data itself. This has been seen on real world disks
// such as the Apple II Operating System Kit from Apple2Online.
// However, I haven't seen it described in any of the various
// references.
//
// This extra '0' bit would not affect the real disk interface,
// as it was a '1' reaching the top bit of a shift register
// that triggered a byte to be available, but it affects the
// way the data is read here.
//
// While the floppies tested only seemed to need this applied
// to the first byte of the data record, applying it
// consistently to all of them doesn't seem to hurt, and
// simplifies the code.
// Sometimes there's a 1-bit gap between APPLE2_DATA_RECORD and
// the data itself. This has been seen on real world disks
// such as the Apple II Operating System Kit from Apple2Online.
// However, I haven't seen it described in any of the various
// references.
//
// This extra '0' bit would not affect the real disk interface,
// as it was a '1' reaching the top bit of a shift register
// that triggered a byte to be available, but it affects the
// way the data is read here.
//
// While the floppies tested only seemed to need this applied
// to the first byte of the data record, applying it
// consistently to all of them doesn't seem to hurt, and
// simplifies the code.
/* Read and decode data. */
/* Read and decode data. */
auto readApple8 = [&]()
{
auto result = 0;
while ((result & 0x80) == 0)
{
auto b = readRawBits(1);
if (b.empty())
break;
result = (result << 1) | b[0];
}
return result;
};
auto readApple8 = [&]() {
auto result = 0;
while((result & 0x80) == 0) {
auto b = readRawBits(1);
if(b.empty()) break;
result = (result << 1) | b[0];
}
return result;
};
constexpr unsigned recordLength = APPLE2_ENCODED_SECTOR_LENGTH + 2;
uint8_t bytes[recordLength];
for (auto& byte : bytes)
{
byte = readApple8();
}
constexpr unsigned recordLength = APPLE2_ENCODED_SECTOR_LENGTH+2;
uint8_t bytes[recordLength];
for(auto &byte : bytes) {
byte = readApple8();
}
// Upgrade the sector from MISSING to BAD_CHECKSUM.
// If decode_crazy_data succeeds, it upgrades the sector to
// OK.
_sector->status = Sector::BAD_CHECKSUM;
_sector->data = decode_crazy_data(&bytes[0], _sector->status);
}
// Upgrade the sector from MISSING to BAD_CHECKSUM.
// If decode_crazy_data succeeds, it upgrades the sector to
// OK.
_sector->status = Sector::BAD_CHECKSUM;
_sector->data = decode_crazy_data(&bytes[0], _sector->status);
}
};
std::unique_ptr<Decoder> createApple2Decoder(const DecoderProto& config)
{
return std::unique_ptr<Decoder>(new Apple2Decoder(config));
return std::unique_ptr<Decoder>(new Apple2Decoder(config));
}

View File

@@ -56,7 +56,8 @@ public:
std::unique_ptr<Fluxmap> fluxmap(new Fluxmap);
fluxmap->appendBits(bits,
calculatePhysicalClockPeriod(_config.clock_period_us() * 1e3,
calculatePhysicalClockPeriod(
_config.clock_period_us() * 1e3,
_config.rotational_period_ms() * 1e6));
return fluxmap;
}
@@ -131,17 +132,13 @@ private:
// extra padding.
write_ff40(sector.logicalSector == 0 ? 32 : 8);
int track = sector.logicalTrack;
if (sector.logicalSide == 1)
track += _config.side_one_track_offset();
// Write address field: APPLE2_SECTOR_RECORD + sector identifier +
// DE AA EB
write_bits(APPLE2_SECTOR_RECORD, 24);
write_gcr44(volume_id);
write_gcr44(track);
write_gcr44(sector.logicalTrack);
write_gcr44(sector.logicalSector);
write_gcr44(volume_id ^ track ^ sector.logicalSector);
write_gcr44(volume_id ^ sector.logicalTrack ^ sector.logicalSector);
write_bits(0xDEAAEB, 24);
// Write data syncing leader: FF40 + APPLE2_DATA_RECORD + sector

View File

@@ -1,8 +1,5 @@
LIBARCH_SRCS = \
arch/aeslanier/decoder.cc \
arch/agat/agat.cc \
arch/agat/decoder.cc \
arch/agat/encoder.cc \
arch/amiga/amiga.cc \
arch/amiga/decoder.cc \
arch/amiga/encoder.cc \
@@ -19,18 +16,18 @@ 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/rolandd20/decoder.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)

View File

@@ -147,7 +147,6 @@ public:
_sector->logicalSide = br.read_8();
_sector->logicalSector = br.read_8();
_currentSectorSize = 1 << (br.read_8() + 7);
uint16_t gotCrc = crc16(CCITT_POLY, bytes.slice(0, br.pos));
uint16_t wantCrc = br.read_be16();
if (wantCrc == gotCrc)
@@ -207,18 +206,6 @@ public:
uint16_t wantCrc = br.read_be16();
_sector->status =
(wantCrc == gotCrc) ? Sector::OK : Sector::BAD_CHECKSUM;
auto layout = Layout::getLayoutOfTrack(
_sector->logicalTrack, _sector->logicalSide);
if (_currentSectorSize != layout->sectorSize)
std::cerr << fmt::format(
"Warning: configured sector size for t{}.h{}.s{} is {} bytes "
"but that seen on disk is {} bytes\n",
_sector->logicalTrack,
_sector->logicalSide,
_sector->logicalSector,
layout->sectorSize,
_currentSectorSize);
}
private:

View File

@@ -16,14 +16,14 @@ static bool lastBit;
static double clockRateUsForTrack(unsigned track)
{
if (track < 16)
return 2.63;
return 2.623;
if (track < 32)
return 2.89;
return 2.861;
if (track < 48)
return 3.20;
return 3.148;
if (track < 64)
return 3.57;
return 3.98;
return 3.497;
return 3.934;
}
static unsigned sectorsForTrack(unsigned track)

View File

@@ -1,56 +0,0 @@
#include "lib/globals.h"
#include "lib/decoders/decoders.h"
#include "lib/crc.h"
#include "lib/fluxmap.h"
#include "lib/decoders/fluxmapreader.h"
#include "lib/sector.h"
#include "lib/bytes.h"
#include "rolandd20.h"
#include <string.h>
/* Sector header record:
*
* BF FF FF FF FF FF FE AB
*
* This encodes to:
*
* e d 5 5 5 5 5 5
* 1110 1101 0101 0101 0101 0101 0101 0101
* 5 5 5 5 5 5 5 5
* 0101 0101 0101 0101 0101 0101 0101 0101
* 5 5 5 5 5 5 5 5
* 0101 0101 0101 0101 0101 0101 0101 0101
* 5 5 5 4 4 4 4 5
* 0101 0101 0101 0100 0100 0100 0100 0101
*/
static const FluxPattern SECTOR_PATTERN(64, 0xed55555555555555LL);
class RolandD20Decoder : public Decoder
{
public:
RolandD20Decoder(const DecoderProto& config):
Decoder(config)
{}
nanoseconds_t advanceToNextRecord() override
{
return seekToPattern(SECTOR_PATTERN);
}
void decodeSectorRecord() override
{
auto rawbits = readRawBits(256);
const auto& bytes = decodeFmMfm(rawbits);
fmt::print("{} ", _sector->clock);
hexdump(std::cout, bytes);
}
};
std::unique_ptr<Decoder> createRolandD20Decoder(const DecoderProto& config)
{
return std::unique_ptr<Decoder>(new RolandD20Decoder(config));
}

View File

@@ -1,4 +0,0 @@
#pragma once
extern std::unique_ptr<Decoder> createRolandD20Decoder(const DecoderProto& config);

View File

@@ -1,5 +0,0 @@
syntax = "proto2";
message RolandD20DecoderProto {}

View File

@@ -1,154 +0,0 @@
#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));
}

View File

@@ -1,10 +0,0 @@
#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

View File

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

View File

@@ -55,7 +55,6 @@ proto_cc_library {
"./arch/micropolis/micropolis.proto",
"./arch/mx/mx.proto",
"./arch/northstar/northstar.proto",
"./arch/rolandd20/rolandd20.proto",
"./arch/tids990/tids990.proto",
"./arch/victor9k/victor9k.proto",
"./arch/zilogmcz/zilogmcz.proto",
@@ -94,7 +93,6 @@ clibrary {
"./arch/mx/decoder.cc",
"./arch/northstar/decoder.cc",
"./arch/northstar/encoder.cc",
"./arch/rolandd20/rolandd20.cc",
"./arch/tids990/decoder.cc",
"./arch/tids990/encoder.cc",
"./arch/victor9k/decoder.cc",

View File

@@ -30,34 +30,6 @@ FluxEngine can remap the sectors from physical to logical using modifiers. If
you don't specify a remapping modifier, you get the sectors in the order they
appear on the disk.
If you don't want an image in physical sector order, specify one of these options:
- `--appledos` Selects AppleDOS sector translation
- `--prodos` Selects ProDOS sector translation
- `--cpm` Selects CP/M SoftCard sector translation[^1][^2]
These options also select the appropriate file system; FluxEngine has read-only
support for all of these. For example:
```
fluxengine ls appleii140 --appledos -f image.flux
```
In addition, some third-party systems use 80-track double sides drives, with
the same underlying disk format. These are supported with the `appleii640`
profile. The complication here is that the AppleDOS filesystem only supports up
to 50 tracks, so it needs tweaking to support larger disks. It treats the
second side of the disk as a completely different volume. To access these
files, use `--appledos --side1`.
[^1]: CP/M disks use the ProDOS translation for the first three tracks and a
different translation for all the tracks thereafter.
[^2]: 80-track CP/M disks are interesting because all the tracks on the second
side have on-disk track numbering from 80..159; the Apple II on-disk format
doesn't have a side byte, so presumably this is to allow tracks on the two
sides to be distinguished from each other. AppleDOS and ProDOS disks don't
do this.
Reading discs
-------------
@@ -65,25 +37,35 @@ Reading discs
Just do:
```
fluxengine read appleii140
fluxengine read apple2
```
(or `appleii640`)
You should end up with an `apple2.img` which is 143360 bytes long. It will be in
physical sector ordering. You can specify a sector ordering, `--appledos` or
`--prodos` to get an image intended for use in an emulator, due to the logical
sector mapping issue described above:
You should end up with an `appleii140.img` which is 143360 bytes long. It will
be in physical sector ordering if you don't specify a file system format as
described above.
```
fluxengine read apple2 --prodos
```
You will also need this for filesystem access.
Writing discs
-------------
Just do:
```
fluxengine write appleii140 -i appleii140.img
fluxengine write apple2 -i apple2.img
```
If your image is in logical sector ordering (images intended for emulators
usually are), specify a modifier of `--appledos` or `--prodos`:
```
fluxengine write apple2 --prodos -i apple2.img
```
The image will be expected to be in physical sector ordering if you don't
specify a file system format as described above.
Useful references
-----------------

View File

@@ -1,43 +0,0 @@
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,7 +23,6 @@ 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
@@ -83,7 +82,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.type=MACHFS
fluxengine format ibm1440 -f drive:1 --filesystem.machfs=
```
Some filesystems won't work on some disks --- don't try this with Amiga FFS, for

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 39 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

View File

@@ -155,14 +155,6 @@ more common tools.
encoding. You can specify a profile if you want to write a subset of the
disk.
- `fluxengine merge -s <fluxfile> -s <fluxfile...> -d <fluxfile`
Merges data from multiple flux files together. This is useful if you have
several reads from an unreliable disk where each read has a different set
of good sectors. By merging the flux files, you get to combine all the
data. Don't use this on reads of different disks, for obvious results! Note
that this works on flux files, not on flux sources.
- `fluxengine inspect -s <flux source> -c <cylinder> -h <head> -B`
Reads flux (possibly from a disk) and does various analyses of it to try
@@ -221,11 +213,6 @@ 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

@@ -1,41 +0,0 @@
<?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

@@ -1,5 +0,0 @@
#!/bin/sh
dir=`dirname "$0"`
cd "$dir"
export DYLD_FALLBACK_LIBRARY_PATH=../Resources:/opt/local/lib
exec ./fluxengine-gui "$@"

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 365 B

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 428 B

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 327 B

View File

@@ -9,7 +9,6 @@ LIBFLUXENGINE_SRCS = \
lib/decoders/fmmfm.cc \
lib/encoders/encoders.cc \
lib/environment.cc \
lib/fl2.cc \
lib/flags.cc \
lib/fluxmap.cc \
lib/fluxsink/a2rfluxsink.cc \
@@ -23,8 +22,6 @@ 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 \
@@ -70,7 +67,6 @@ LIBFLUXENGINE_SRCS = \
lib/utils.cc \
lib/vfs/acorndfs.cc \
lib/vfs/amigaffs.cc \
lib/vfs/appledos.cc \
lib/vfs/applesingle.cc \
lib/vfs/brother120fs.cc \
lib/vfs/cbmfs.cc \
@@ -78,8 +74,6 @@ LIBFLUXENGINE_SRCS = \
lib/vfs/fatfs.cc \
lib/vfs/machfs.cc \
lib/vfs/prodos.cc \
lib/vfs/smaky6fs.cc \
lib/vfs/philefs.cc \
lib/vfs/vfs.cc \
lib/vfs/fluxsectorinterface.cc \
lib/vfs/imagesectorinterface.cc \

View File

@@ -205,34 +205,6 @@ 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;
}
Bytes Bytes::operator + (const Bytes& other)
{
Bytes output;
ByteWriter bw(output);
bw += *this;
bw += other;
return output;
}
Bytes Bytes::operator * (size_t count)
{
Bytes output;
ByteWriter bw(output);
while (count--)
bw += *this;
return output;
}
uint8_t toByte(
std::vector<bool>::const_iterator start,
std::vector<bool>::const_iterator end)

View File

@@ -62,10 +62,6 @@ public:
Bytes compress() const;
Bytes decompress() const;
std::vector<bool> toBits() const;
Bytes reverseBits() const;
Bytes operator + (const Bytes& other);
Bytes operator * (size_t count);
ByteReader reader() const;
ByteWriter writer();

View File

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

View File

@@ -13,44 +13,37 @@ import "lib/common.proto";
import "lib/layout.proto";
// NEXT_TAG: 21
message ConfigProto
{
optional string comment = 8;
optional bool is_extension = 13;
repeated string include = 19;
message ConfigProto {
optional string comment = 8;
optional bool is_extension = 13;
repeated string include = 19;
optional LayoutProto layout = 18;
optional LayoutProto layout = 18;
optional ImageReaderProto image_reader = 12;
optional ImageWriterProto image_writer = 9;
optional ImageReaderProto image_reader = 12;
optional ImageWriterProto image_writer = 9;
optional FluxSourceProto flux_source = 10;
optional FluxSinkProto flux_sink = 11;
optional DriveProto drive = 15;
optional FluxSourceProto flux_source = 10;
optional FluxSinkProto flux_sink = 11;
optional DriveProto drive = 15;
optional EncoderProto encoder = 3;
optional DecoderProto decoder = 4;
optional UsbProto usb = 5;
optional EncoderProto encoder = 3;
optional DecoderProto decoder = 4;
optional UsbProto usb = 5;
optional RangeProto tracks = 6;
optional RangeProto heads = 7;
optional int32 tpi = 16 [ (help) = "TPI of image; if 0, use TPI of drive" ];
optional RangeProto tracks = 6;
optional RangeProto heads = 7;
optional int32 tpi = 16 [ (help) = "TPI of image; if 0, use TPI of drive" ];
optional FilesystemProto filesystem = 17;
repeated OptionProto option = 20;
optional FilesystemProto filesystem = 17;
repeated OptionProto option = 20;
}
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 string exclusivity_group = 5 [
(help) =
"options with the same group cannot be selected at the same time"
];
optional ConfigProto config = 4
[ (help) = "option data", (recurse) = false ];
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" ];
}

View File

@@ -1,228 +1,223 @@
#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/rolandd20/rolandd20.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::kRolandd20, createRolandD20Decoder },
{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();
}
#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();
}

View File

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

View File

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

View File

@@ -202,23 +202,6 @@ 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,7 +100,6 @@ 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

@@ -35,15 +35,6 @@ message DriveProto
optional int32 tpi = 11 [ default = 96, (help) = "TPI of drive" ];
optional double rotational_period_ms = 12
[ default = 0, (help) = "Rotational period of the drive in milliseconds (0 to autodetect)"];
enum ErrorBehaviour {
NOTHING = 0;
JIGGLE = 1;
RECALIBRATE = 2;
}
optional ErrorBehaviour error_behaviour = 15
[ default = JIGGLE, (help) = "what to do when an error occurs during reads" ];
}
// vim: ts=4 sw=4 et

View File

@@ -2,7 +2,6 @@
#include "fluxmap.h"
#include "decoders/decoders.h"
#include "encoders/encoders.h"
#include "arch/agat/agat.h"
#include "arch/amiga/amiga.h"
#include "arch/apple2/apple2.h"
#include "arch/brother/brother.h"
@@ -26,7 +25,6 @@ std::unique_ptr<Encoder> Encoder::create(
std::function<std::unique_ptr<Encoder>(const EncoderProto&)>>
encoders = {
{EncoderProto::kAmiga, createAmigaEncoder },
{EncoderProto::kAgat, createAgatEncoder },
{EncoderProto::kApple2, createApple2Encoder },
{EncoderProto::kBrother, createBrotherEncoder },
{EncoderProto::kC64, createCommodore64Encoder},

View File

@@ -6,7 +6,6 @@ class Fluxmap;
class Image;
class Layout;
class Sector;
class TrackInfo;
class Encoder
{

View File

@@ -1,6 +1,5 @@
syntax = "proto2";
import "arch/agat/agat.proto";
import "arch/amiga/amiga.proto";
import "arch/apple2/apple2.proto";
import "arch/brother/brother.proto";
@@ -26,6 +25,5 @@ message EncoderProto
MicropolisEncoderProto micropolis = 10;
Victor9kEncoderProto victor9k = 11;
Apple2EncoderProto apple2 = 12;
AgatEncoderProto agat = 13;
}
}

View File

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

View File

@@ -1,69 +0,0 @@
#include "globals.h"
#include "proto.h"
#include "fluxmap.h"
#include "fmt/format.h"
#include "lib/fl2.pb.h"
#include <fstream>
static void upgradeFluxFile(FluxFileProto& proto)
{
if (proto.version() == FluxFileVersion::VERSION_1)
{
/* Change a flux datastream with multiple segments separated by F_DESYNC
* into multiple flux segments. */
for (auto& track : *proto.mutable_track())
{
if (track.flux_size() != 0)
{
Fluxmap oldFlux(track.flux(0));
track.clear_flux();
for (const auto& flux : oldFlux.split())
track.add_flux(flux->rawBytes());
}
}
proto.set_version(FluxFileVersion::VERSION_2);
}
if (proto.version() > FluxFileVersion::VERSION_2)
Error() << fmt::format(
"this is a version {} flux file, but this build of the client can "
"only handle up to version {} --- please upgrade",
proto.version(),
FluxFileVersion::VERSION_2);
}
FluxFileProto loadFl2File(const std::string filename)
{
std::ifstream ifs(filename, std::ios::in | std::ios::binary);
if (!ifs.is_open())
Error() << fmt::format(
"cannot open input file '{}': {}", filename, strerror(errno));
char buffer[16];
ifs.read(buffer, sizeof(buffer));
if (strncmp(buffer, "SQLite format 3", 16) == 0)
Error() << "this flux file is too old; please use the "
"upgrade-flux-file tool to upgrade it";
FluxFileProto proto;
ifs.seekg(0);
if (!proto.ParseFromIstream(&ifs))
Error() << fmt::format("unable to read input file '{}'", filename);
upgradeFluxFile(proto);
return proto;
}
void saveFl2File(const std::string filename, FluxFileProto& proto)
{
proto.set_magic(FluxMagic::MAGIC);
proto.set_version(FluxFileVersion::VERSION_2);
std::ofstream of(filename, std::ios::out | std::ios::binary);
if (!proto.SerializeToOstream(&of))
Error() << fmt::format("unable to write output file '{}'", filename);
of.close();
if (of.fail())
Error() << "FL2 write I/O error: " << strerror(errno);
}

View File

@@ -1,10 +0,0 @@
#ifndef FL2_H
#define FL2_H
class FluxFileProto;
extern FluxFileProto loadFl2File(const std::string filename);
extern void saveFl2File(const std::string filename, FluxFileProto& proto);
#endif

View File

@@ -55,39 +55,31 @@ static bool setFallbackFlag(
}
else
{
if (FlagGroup::applyOption(path))
return false;
for (const auto& configs : config.option())
{
if (path == configs.name())
{
if (configs.config().option_size() > 0)
Error() << fmt::format(
"option '{}' has an option inside it, which isn't "
"allowed",
path);
if (configs.config().include_size() > 0)
Error() << fmt::format(
"option '{}' is trying to include something, which "
"isn't allowed",
path);
Logger() << fmt::format("OPTION: {}", configs.message());
config.MergeFrom(configs.config());
return false;
}
}
}
}
Error() << "unrecognised flag; try --help";
}
bool FlagGroup::applyOption(const std::string& option)
{
for (const auto& configs : config.option())
{
if (option == configs.name())
{
if (configs.config().option_size() > 0)
Error() << fmt::format(
"option '{}' has an option inside it, which isn't "
"allowed",
option);
if (configs.config().include_size() > 0)
Error() << fmt::format(
"option '{}' is trying to include something, which "
"isn't allowed",
option);
Logger() << fmt::format("OPTION: {}", configs.message());
config.MergeFrom(configs.config());
return true;
}
}
return false;
}
std::vector<std::string> FlagGroup::parseFlagsWithFilenames(int argc,
const char* argv[],
std::function<bool(const std::string&)> callback)
@@ -252,6 +244,7 @@ ConfigProto FlagGroup::parseSingleConfigFile(const std::string& filename,
ss << f.rdbuf();
ConfigProto config;
std::cout << ss.str() << '\n';
if (!google::protobuf::TextFormat::MergeFromString(ss.str(), &config))
Error() << "couldn't load external config proto";
return config;

View File

@@ -12,42 +12,21 @@ public:
FlagGroup(std::initializer_list<FlagGroup*> groups);
public:
void parseFlags(
int argc,
const char* argv[],
std::function<bool(const std::string&)> callback =
[](const auto&)
{
return false;
});
void parseFlags(int argc, const char* argv[],
std::function<bool(const std::string&)> callback =
[](const auto&){ return false; });
std::vector<std::string> parseFlagsWithFilenames(
int argc,
const char* argv[],
std::function<bool(const std::string&)> callback =
[](const auto&)
{
return false;
});
void parseFlagsWithConfigFiles(int argc,
const char* argv[],
const std::map<std::string, std::string>& configFiles);
/* Load one config file (or internal config file), without expanding
* includes. */
static ConfigProto parseSingleConfigFile(const std::string& filename,
const std::map<std::string, std::string>& configFiles);
/* Load a top-level config file (or internal config file), expanding
* includes. */
static void parseConfigFile(const std::string& filename,
const std::map<std::string, std::string>& configFiles);
/* Modify the current config to engage the named option. */
static bool applyOption(const std::string& option);
int argc, const char* argv[],
std::function<bool(const std::string&)> callback =
[](const auto&){ return false; });
void parseFlagsWithConfigFiles(int argc, const char* argv[],
const std::map<std::string, std::string>& configFiles);
static ConfigProto parseSingleConfigFile(
const std::string& filename,
const std::map<std::string, std::string>& configFiles);
static void parseConfigFile(
const std::string& filename,
const std::map<std::string, std::string>& configFiles);
void addFlag(Flag* flag);
void checkInitialised() const;
@@ -61,25 +40,14 @@ class Flag
{
public:
Flag(const std::vector<std::string>& names, const std::string helptext);
virtual ~Flag(){};
virtual ~Flag() {};
void checkInitialised() const
{
_group.checkInitialised();
}
{ _group.checkInitialised(); }
const std::string& name() const
{
return _names[0];
}
const std::vector<std::string> names() const
{
return _names;
}
const std::string& helptext() const
{
return _helptext;
}
const std::string& name() const { return _names[0]; }
const std::vector<std::string> names() const { return _names; }
const std::string& helptext() const { return _helptext; }
virtual bool hasArgument() const = 0;
virtual const std::string defaultValueAsString() const = 0;
@@ -94,26 +62,15 @@ private:
class ActionFlag : Flag
{
public:
ActionFlag(const std::vector<std::string>& names,
const std::string helptext,
std::function<void(void)> callback):
ActionFlag(const std::vector<std::string>& names, const std::string helptext,
std::function<void(void)> callback):
Flag(names, helptext),
_callback(callback)
{
}
{}
bool hasArgument() const
{
return false;
}
const std::string defaultValueAsString() const
{
return "";
}
void set(const std::string& value)
{
_callback();
}
bool hasArgument() const { return false; }
const std::string defaultValueAsString() const { return ""; }
void set(const std::string& value) { _callback(); }
private:
const std::function<void(void)> _callback;
@@ -122,30 +79,16 @@ private:
class SettableFlag : public Flag
{
public:
SettableFlag(
const std::vector<std::string>& names, const std::string helptext):
SettableFlag(const std::vector<std::string>& names, const std::string helptext):
Flag(names, helptext)
{
}
{}
operator bool() const
{
checkInitialised();
return _value;
}
{ checkInitialised(); return _value; }
bool hasArgument() const
{
return false;
}
const std::string defaultValueAsString() const
{
return "false";
}
void set(const std::string& value)
{
_value = true;
}
bool hasArgument() const { return false; }
const std::string defaultValueAsString() const { return "false"; }
void set(const std::string& value) { _value = true; }
private:
bool _value = false;
@@ -155,122 +98,72 @@ template <typename T>
class ValueFlag : public Flag
{
public:
ValueFlag(
const std::vector<std::string>& names,
const std::string helptext,
const T defaultValue,
std::function<void(const T&)> callback =
[](const T&)
{
}):
ValueFlag(const std::vector<std::string>& names, const std::string helptext,
const T defaultValue,
std::function<void(const T&)> callback = [](const T&) {}):
Flag(names, helptext),
_defaultValue(defaultValue),
_value(defaultValue),
_callback(callback)
{
}
_callback(callback)
{}
const T& get() const
{
checkInitialised();
return _value;
}
{ checkInitialised(); return _value; }
operator const T&() const
{
return get();
}
operator const T& () const
{ return get(); }
bool isSet() const
{
return _isSet;
}
bool isSet() const
{ return _isSet; }
void setDefaultValue(T value)
{
_value = _defaultValue = value;
}
bool hasArgument() const
{
return true;
}
bool hasArgument() const { return true; }
protected:
T _defaultValue;
T _value;
bool _isSet = false;
std::function<void(const T&)> _callback;
bool _isSet = false;
std::function<void(const T&)> _callback;
};
class StringFlag : public ValueFlag<std::string>
{
public:
StringFlag(
const std::vector<std::string>& names,
const std::string helptext,
const std::string defaultValue = "",
std::function<void(const std::string&)> callback =
[](const std::string&)
{
}):
StringFlag(const std::vector<std::string>& names, const std::string helptext,
const std::string defaultValue = "",
std::function<void(const std::string&)> callback = [](const std::string&) {}):
ValueFlag(names, helptext, defaultValue, callback)
{
}
{}
const std::string defaultValueAsString() const
{
return _defaultValue;
}
void set(const std::string& value)
{
_value = value;
_callback(_value);
_isSet = true;
}
const std::string defaultValueAsString() const { return _defaultValue; }
void set(const std::string& value) { _value = value; _callback(_value); _isSet = true; }
};
class IntFlag : public ValueFlag<int>
{
public:
IntFlag(
const std::vector<std::string>& names,
const std::string helptext,
int defaultValue = 0,
std::function<void(const int&)> callback =
[](const int&)
{
}):
IntFlag(const std::vector<std::string>& names, const std::string helptext,
int defaultValue = 0,
std::function<void(const int&)> callback = [](const int&) {}):
ValueFlag(names, helptext, defaultValue, callback)
{
}
{}
const std::string defaultValueAsString() const
{
return std::to_string(_defaultValue);
}
void set(const std::string& value)
{
_value = std::stoi(value);
_callback(_value);
_isSet = true;
}
const std::string defaultValueAsString() const { return std::to_string(_defaultValue); }
void set(const std::string& value) { _value = std::stoi(value); _callback(_value); _isSet = true; }
};
class HexIntFlag : public IntFlag
{
public:
HexIntFlag(
const std::vector<std::string>& names,
const std::string helptext,
int defaultValue = 0,
std::function<void(const int&)> callback =
[](const int&)
{
}):
HexIntFlag(const std::vector<std::string>& names, const std::string helptext,
int defaultValue = 0,
std::function<void(const int&)> callback = [](const int&) {}):
IntFlag(names, helptext, defaultValue, callback)
{
}
{}
const std::string defaultValueAsString() const;
};
@@ -278,49 +171,26 @@ public:
class DoubleFlag : public ValueFlag<double>
{
public:
DoubleFlag(
const std::vector<std::string>& names,
const std::string helptext,
double defaultValue = 1.0,
std::function<void(const double&)> callback =
[](const double&)
{
}):
DoubleFlag(const std::vector<std::string>& names, const std::string helptext,
double defaultValue = 1.0,
std::function<void(const double&)> callback = [](const double&) {}):
ValueFlag(names, helptext, defaultValue, callback)
{
}
{}
const std::string defaultValueAsString() const
{
return std::to_string(_defaultValue);
}
void set(const std::string& value)
{
_value = std::stod(value);
_callback(_value);
_isSet = true;
}
const std::string defaultValueAsString() const { return std::to_string(_defaultValue); }
void set(const std::string& value) { _value = std::stod(value); _callback(_value); _isSet = true; }
};
class BoolFlag : public ValueFlag<bool>
{
public:
BoolFlag(
const std::vector<std::string>& names,
const std::string helptext,
bool defaultValue = false,
std::function<void(const bool&)> callback =
[](const bool&)
{
}):
BoolFlag(const std::vector<std::string>& names, const std::string helptext,
bool defaultValue = false,
std::function<void(const bool&)> callback = [](const bool&) {}):
ValueFlag(names, helptext, defaultValue, callback)
{
}
{}
const std::string defaultValueAsString() const
{
return _defaultValue ? "true" : "false";
}
const std::string defaultValueAsString() const { return _defaultValue ? "true" : "false"; }
void set(const std::string& value);
};

View File

@@ -10,32 +10,32 @@ class TrackInfo;
struct Record
{
nanoseconds_t clock = 0;
nanoseconds_t startTime = 0;
nanoseconds_t endTime = 0;
uint32_t position = 0;
Bytes rawData;
nanoseconds_t clock = 0;
nanoseconds_t startTime = 0;
nanoseconds_t endTime = 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

@@ -76,89 +76,3 @@ std::vector<std::unique_ptr<const Fluxmap>> Fluxmap::split() const
return maps;
}
/*
* Tries to guess the clock by finding the smallest common interval.
* Returns nanoseconds.
*/
Fluxmap::ClockData Fluxmap::guessClock(
double noiseFloorFactor, double signalLevelFactor) const
{
ClockData data = {};
FluxmapReader fr(*this);
while (!fr.eof())
{
unsigned interval;
fr.findEvent(F_BIT_PULSE, interval);
if (interval > 0xff)
continue;
data.buckets[interval]++;
}
uint32_t max =
*std::max_element(std::begin(data.buckets), std::end(data.buckets));
uint32_t min =
*std::min_element(std::begin(data.buckets), std::end(data.buckets));
data.noiseFloor = min + (max - min) * noiseFloorFactor;
data.signalLevel = min + (max - min) * signalLevelFactor;
/* Find a point solidly within the first pulse. */
int pulseindex = 0;
while (pulseindex < 256)
{
if (data.buckets[pulseindex] > data.signalLevel)
break;
pulseindex++;
}
if (pulseindex == -1)
return data;
/* Find the upper and lower bounds of the pulse. */
int peaklo = pulseindex;
while (peaklo > 0)
{
if (data.buckets[peaklo] < data.noiseFloor)
break;
peaklo--;
}
int peakhi = pulseindex;
while (peakhi < 255)
{
if (data.buckets[peakhi] < data.noiseFloor)
break;
peakhi++;
}
/* Find the total accumulated size of the pulse. */
uint32_t total_size = 0;
for (int i = peaklo; i < peakhi; i++)
total_size += data.buckets[i];
/* Now find the median. */
uint32_t count = 0;
int median = peaklo;
while (median < peakhi)
{
count += data.buckets[median];
if (count > (total_size / 2))
break;
median++;
}
/*
* Okay, the median should now be a good candidate for the (or a) clock.
* How this maps onto the actual clock rate depends on the encoding.
*/
data.peakStart = peaklo * NS_PER_TICK;
data.peakEnd = peakhi * NS_PER_TICK;
data.median = median * NS_PER_TICK;
return data;
}

View File

@@ -17,57 +17,42 @@ public:
unsigned zeroes = 0;
nanoseconds_t ns() const
{
return ticks * NS_PER_TICK;
}
{ return ticks * NS_PER_TICK; }
operator std::string()
{
operator std::string () {
return fmt::format("[b:{}, t:{}, z:{}]", bytes, ticks, zeroes);
}
};
public:
Fluxmap() {}
Fluxmap() {}
Fluxmap(const std::string& s)
{
appendBytes((const uint8_t*)s.c_str(), s.size());
}
Fluxmap(const std::string& s)
{
appendBytes((const uint8_t*) s.c_str(), s.size());
}
Fluxmap(const Bytes bytes)
{
appendBytes(bytes);
}
Fluxmap(const Bytes bytes)
{
appendBytes(bytes);
}
nanoseconds_t duration() const
{
return _duration;
}
unsigned ticks() const
{
return _ticks;
}
size_t bytes() const
{
return _bytes.size();
}
const Bytes& rawBytes() const
{
return _bytes;
}
nanoseconds_t duration() const { return _duration; }
unsigned ticks() const { return _ticks; }
size_t bytes() const { return _bytes.size(); }
const Bytes& rawBytes() const { return _bytes; }
const uint8_t* ptr() const
{
if (!_bytes.empty())
return &_bytes[0];
return NULL;
}
{
if (!_bytes.empty())
return &_bytes[0];
return NULL;
}
Fluxmap& appendInterval(uint32_t ticks);
Fluxmap& appendPulse();
Fluxmap& appendIndex();
Fluxmap& appendDesync();
Fluxmap& appendDesync();
Fluxmap& appendBytes(const Bytes& bytes);
Fluxmap& appendBytes(const uint8_t* ptr, size_t len);
@@ -77,27 +62,13 @@ public:
return appendBytes(&byte, 1);
}
Fluxmap& appendBits(const std::vector<bool>& bits, nanoseconds_t clock);
Fluxmap& appendBits(const std::vector<bool>& bits, nanoseconds_t clock);
std::unique_ptr<const Fluxmap> precompensate(
int threshold_ticks, int amount_ticks);
std::unique_ptr<const Fluxmap> precompensate(int threshold_ticks, int amount_ticks);
std::vector<std::unique_ptr<const Fluxmap>> split() const;
struct ClockData
{
nanoseconds_t median;
uint32_t noiseFloor;
uint32_t signalLevel;
nanoseconds_t peakStart;
nanoseconds_t peakEnd;
uint32_t buckets[256];
};
ClockData guessClock(
double noiseFloorFactor = 0.01, double signalLevelFactor = 0.05) const;
private:
uint8_t& findLastByte();
uint8_t& findLastByte();
private:
nanoseconds_t _duration = 0;

View File

@@ -9,69 +9,73 @@
#include "proto.h"
#include "fmt/format.h"
#include "lib/fl2.pb.h"
#include "fl2.h"
#include <fstream>
#include <sys/stat.h>
#include <sys/types.h>
#include <filesystem>
class Fl2FluxSink : public FluxSink
{
public:
Fl2FluxSink(const Fl2FluxSinkProto& lconfig):
Fl2FluxSink(lconfig.filename())
{
}
Fl2FluxSink(const Fl2FluxSinkProto& lconfig):
Fl2FluxSink(lconfig.filename())
{
}
Fl2FluxSink(const std::string& filename): _filename(filename)
{
std::ofstream of(filename);
if (!of.is_open())
Error() << "cannot open output file";
of.close();
std::filesystem::remove(filename);
}
Fl2FluxSink(const std::string& filename):
_filename(filename),
_of(_filename, std::ios::out | std::ios::binary)
{
if (!_of.is_open())
Error() << "cannot open output file";
}
~Fl2FluxSink()
{
FluxFileProto proto;
for (const auto& e : _data)
{
auto track = proto.add_track();
track->set_track(e.first.first);
track->set_head(e.first.second);
for (const auto& fluxBytes : e.second)
track->add_flux(fluxBytes);
}
~Fl2FluxSink()
{
FluxFileProto proto;
proto.set_magic(FluxMagic::MAGIC);
proto.set_version(FluxFileVersion::VERSION_2);
for (const auto& e : _data)
{
auto track = proto.add_track();
track->set_track(e.first.first);
track->set_head(e.first.second);
for (const auto& fluxBytes : e.second)
track->add_flux(fluxBytes);
}
saveFl2File(_filename, proto);
}
if (!proto.SerializeToOstream(&_of))
Error() << "unable to write output file";
_of.close();
if (_of.fail())
Error() << "FL2 write I/O error: " << strerror(errno);
}
public:
void writeFlux(int track, int head, const Fluxmap& fluxmap) override
{
auto& vector = _data[std::make_pair(track, head)];
vector.push_back(fluxmap.rawBytes());
}
void writeFlux(int track, int head, const Fluxmap& fluxmap) override
{
auto& vector = _data[std::make_pair(track, head)];
vector.push_back(fluxmap.rawBytes());
}
operator std::string() const override
{
return fmt::format("fl2({})", _filename);
}
operator std::string () const override
{
return fmt::format("fl2({})", _filename);
}
private:
std::string _filename;
std::map<std::pair<unsigned, unsigned>, std::vector<Bytes>> _data;
std::string _filename;
std::ofstream _of;
std::map<std::pair<unsigned, unsigned>, std::vector<Bytes>> _data;
};
std::unique_ptr<FluxSink> FluxSink::createFl2FluxSink(
const Fl2FluxSinkProto& config)
std::unique_ptr<FluxSink> FluxSink::createFl2FluxSink(const Fl2FluxSinkProto& config)
{
return std::unique_ptr<FluxSink>(new Fl2FluxSink(config));
}
std::unique_ptr<FluxSink> FluxSink::createFl2FluxSink(
const std::string& filename)
std::unique_ptr<FluxSink> FluxSink::createFl2FluxSink(const std::string& filename)
{
return std::unique_ptr<FluxSink>(new Fl2FluxSink(filename));
}

View File

@@ -9,85 +9,55 @@
std::unique_ptr<FluxSink> FluxSink::create(const FluxSinkProto& config)
{
switch (config.type())
{
case FluxSinkProto::DRIVE:
return createHardwareFluxSink(config.drive());
switch (config.dest_case())
{
case FluxSinkProto::kDrive:
return createHardwareFluxSink(config.drive());
case FluxSinkProto::A2R:
return createA2RFluxSink(config.a2r());
case FluxSinkProto::kA2R:
return createA2RFluxSink(config.a2r());
case FluxSinkProto::AU:
return createAuFluxSink(config.au());
case FluxSinkProto::kAu:
return createAuFluxSink(config.au());
case FluxSinkProto::VCD:
return createVcdFluxSink(config.vcd());
case FluxSinkProto::kVcd:
return createVcdFluxSink(config.vcd());
case FluxSinkProto::SCP:
return createScpFluxSink(config.scp());
case FluxSinkProto::kScp:
return createScpFluxSink(config.scp());
case FluxSinkProto::FLUX:
return createFl2FluxSink(config.fl2());
case FluxSinkProto::kFl2:
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->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));
}},
};
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)); }},
};
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,25 +27,15 @@ message Fl2FluxSinkProto {
optional string filename = 1 [default = "flux.fl2", (help) = ".fl2 file to write to"];
}
// Next: 10
// Next: 9
message FluxSinkProto {
enum FluxSinkType {
NOT_SET = 0;
DRIVE = 1;
A2R = 2;
AU = 3;
VCD = 4;
SCP = 5;
FLUX = 6;
oneof dest {
HardwareFluxSinkProto drive = 2;
A2RFluxSinkProto a2r = 8;
AuFluxSinkProto au = 3;
VcdFluxSinkProto vcd = 4;
ScpFluxSinkProto scp = 5;
Fl2FluxSinkProto fl2 = 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,7 +9,6 @@
#include "proto.h"
#include "fmt/format.h"
#include "fluxmap.h"
#include "layout.h"
#include "scp.h"
#include <fstream>
#include <sys/stat.h>
@@ -17,7 +16,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)
@@ -30,170 +29,150 @@ 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)
{
int minTrack;
int maxTrack;
int minSide;
int maxSide;
Layout::getBounds(
Layout::computeLocations(), minTrack, maxTrack, minSide, maxSide);
ScpFluxSink(const ScpFluxSinkProto& lconfig):
_config(lconfig)
{
bool singlesided = config.heads().start() == config.heads().end();
_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() != 48)
_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;
_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;
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);
}
std::cout << fmt::format("SCP: writing 96 tpi {} file containing {} tracks\n",
singlesided ? "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);
}
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();
}
~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();
}
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;
}
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++;
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;
}
revolution++;
revTicks = 0;
startOffset = fluxdataWriter.pos;
}
if (fmr.eof())
break;
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;
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;
}
}
FluxmapReader fmr(fluxmap);
Bytes fluxdata;
ByteWriter fluxdataWriter(fluxdata);
_fileheader.revolutions = revolution;
write_le32(
_fileheader.track[strack], trackdataWriter.pos + sizeof(ScpHeader));
trackdataWriter += Bytes((uint8_t*)&trackheader, sizeof(trackheader));
trackdataWriter += 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);
operator std::string() const
{
return fmt::format("scp({})", _config.filename());
}
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());
}
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

@@ -4,7 +4,6 @@
#include "lib/fl2.pb.h"
#include "fluxsource/fluxsource.h"
#include "proto.h"
#include "fl2.h"
#include "fmt/format.h"
#include "fluxmap.h"
#include <fstream>
@@ -12,36 +11,38 @@
class Fl2FluxSourceIterator : public FluxSourceIterator
{
public:
Fl2FluxSourceIterator(const TrackFluxProto& proto): _proto(proto) {}
Fl2FluxSourceIterator(const TrackFluxProto& proto):
_proto(proto)
{}
bool hasNext() const override
{
return _count < _proto.flux_size();
}
bool hasNext() const override
{
return _count < _proto.flux_size();
}
std::unique_ptr<const Fluxmap> next() override
{
auto bytes = _proto.flux(_count);
_count++;
return std::make_unique<Fluxmap>(bytes);
}
std::unique_ptr<const Fluxmap> next() override
{
auto bytes = _proto.flux(_count);
_count++;
return std::make_unique<Fluxmap>(bytes);
}
private:
const TrackFluxProto& _proto;
int _count = 0;
const TrackFluxProto& _proto;
int _count = 0;
};
class EmptyFluxSourceIterator : public FluxSourceIterator
{
bool hasNext() const override
{
return false;
}
bool hasNext() const override
{
return false;
}
std::unique_ptr<const Fluxmap> next() override
{
Error() << "no flux to read";
}
std::unique_ptr<const Fluxmap> next() override
{
Error() << "no flux to read";
}
};
class Fl2FluxSource : public FluxSource
@@ -49,7 +50,15 @@ class Fl2FluxSource : public FluxSource
public:
Fl2FluxSource(const Fl2FluxSourceProto& config): _config(config)
{
_proto = loadFl2File(_config.filename());
std::ifstream ifs(_config.filename(), std::ios::in | std::ios::binary);
if (!ifs.is_open())
Error() << fmt::format("cannot open input file '{}': {}",
_config.filename(),
strerror(errno));
if (!_proto.ParseFromIstream(&ifs))
Error() << "unable to read input file";
upgradeFluxFile();
}
public:
@@ -58,7 +67,7 @@ public:
for (const auto& trackFlux : _proto.track())
{
if ((trackFlux.track() == track) && (trackFlux.head() == head))
return std::make_unique<Fl2FluxSourceIterator>(trackFlux);
return std::make_unique<Fl2FluxSourceIterator>(trackFlux);
}
return std::make_unique<EmptyFluxSourceIterator>();
@@ -73,6 +82,31 @@ private:
Error() << fmt::format("FL2 read I/O error: {}", strerror(errno));
}
void upgradeFluxFile()
{
if (_proto.version() == FluxFileVersion::VERSION_1)
{
/* Change a flux datastream with multiple segments separated by F_DESYNC into multiple
* flux segments. */
for (auto& track : *_proto.mutable_track())
{
if (track.flux_size() != 0)
{
Fluxmap oldFlux(track.flux(0));
track.clear_flux();
for (const auto& flux : oldFlux.split())
track.add_flux(flux->rawBytes());
}
}
_proto.set_version(FluxFileVersion::VERSION_2);
}
if (_proto.version() > FluxFileVersion::VERSION_2)
Error() << fmt::format("this is a version {} flux file, but this build of the client can only handle up to version {} --- please upgrade", _proto.version(), FluxFileVersion::VERSION_2);
}
private:
const Fl2FluxSourceProto& _config;
FluxFileProto _proto;
@@ -81,5 +115,12 @@ private:
std::unique_ptr<FluxSource> FluxSource::createFl2FluxSource(
const Fl2FluxSourceProto& config)
{
char buffer[16];
std::ifstream(config.filename(), std::ios::in | std::ios::binary)
.read(buffer, 16);
if (strncmp(buffer, "SQLite format 3", 16) == 0)
Error() << "this flux file is too old; please use the "
"upgrade-flux-file tool to upgrade it";
return std::unique_ptr<FluxSource>(new Fl2FluxSource(config));
}

View File

@@ -17,137 +17,93 @@ static bool ends_with(const std::string& value, const std::string& ending)
std::unique_ptr<FluxSource> FluxSource::create(const FluxSourceProto& config)
{
switch (config.type())
{
case FluxSourceProto::DRIVE:
return createHardwareFluxSource(config.drive());
switch (config.source_case())
{
case FluxSourceProto::kDrive:
return createHardwareFluxSource(config.drive());
case FluxSourceProto::ERASE:
return createEraseFluxSource(config.erase());
case FluxSourceProto::kErase:
return createEraseFluxSource(config.erase());
case FluxSourceProto::KRYOFLUX:
return createKryofluxFluxSource(config.kryoflux());
case FluxSourceProto::kKryoflux:
return createKryofluxFluxSource(config.kryoflux());
case FluxSourceProto::TEST_PATTERN:
return createTestPatternFluxSource(config.test_pattern());
case FluxSourceProto::kTestPattern:
return createTestPatternFluxSource(config.test_pattern());
case FluxSourceProto::SCP:
return createScpFluxSource(config.scp());
case FluxSourceProto::kScp:
return createScpFluxSource(config.scp());
case FluxSourceProto::CWF:
return createCwfFluxSource(config.cwf());
case FluxSourceProto::kCwf:
return createCwfFluxSource(config.cwf());
case FluxSourceProto::FLUX:
return createFl2FluxSource(config.fl2());
case FluxSourceProto::kFl2:
return createFl2FluxSource(config.fl2());
case FluxSourceProto::FLX:
return createFlxFluxSource(config.flx());
default:
Error() << "bad input disk configuration";
return std::unique_ptr<FluxSource>();
}
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("^(.*\\.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);
}},
};
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)); }},
};
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,15 +14,14 @@ class HardwareFluxSourceProto;
class KryofluxFluxSourceProto;
class ScpFluxSourceProto;
class TestPatternFluxSourceProto;
class FlxFluxSourceProto;
class FluxSourceIterator
{
public:
virtual ~FluxSourceIterator() {}
virtual ~FluxSourceIterator() {}
virtual bool hasNext() const = 0;
virtual std::unique_ptr<const Fluxmap> next() = 0;
virtual bool hasNext() const = 0;
virtual std::unique_ptr<const Fluxmap> next() = 0;
};
class FluxSource
@@ -31,48 +30,32 @@ public:
virtual ~FluxSource() {}
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);
static std::unique_ptr<FluxSource> createTestPatternFluxSource(
const TestPatternFluxSourceProto& config);
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> createHardwareFluxSource(const HardwareFluxSourceProto& config);
static std::unique_ptr<FluxSource> createKryofluxFluxSource(const KryofluxFluxSourceProto& config);
static std::unique_ptr<FluxSource> createScpFluxSource(const ScpFluxSourceProto& config);
static std::unique_ptr<FluxSource> createTestPatternFluxSource(const TestPatternFluxSourceProto& config);
public:
static std::unique_ptr<FluxSource> createMemoryFluxSource(
const DiskFlux& flux);
static std::unique_ptr<FluxSource> createMemoryFluxSource(const DiskFlux& flux);
static std::unique_ptr<FluxSource> create(const FluxSourceProto& spec);
static void updateConfigForFilename(
FluxSourceProto* proto, const std::string& filename);
static void updateConfigForFilename(FluxSourceProto* proto, const std::string& filename);
public:
virtual std::unique_ptr<FluxSourceIterator> readFlux(
int track, int side) = 0;
virtual std::unique_ptr<FluxSourceIterator> readFlux(int track, int side) = 0;
virtual void recalibrate() {}
virtual void seek(int track) {}
virtual bool isHardware()
{
return false;
}
virtual bool isHardware() { return false; }
};
class TrivialFluxSource : public FluxSource
{
public:
std::unique_ptr<FluxSourceIterator> readFlux(int track, int side);
virtual std::unique_ptr<const Fluxmap> readSingleFlux(
int track, int side) = 0;
virtual std::unique_ptr<const Fluxmap> readSingleFlux(int track, int side) = 0;
};
#endif

View File

@@ -30,33 +30,15 @@ message Fl2FluxSourceProto {
(help) = ".fl2 file to read flux from"];
}
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;
oneof source {
HardwareFluxSourceProto drive = 2;
TestPatternFluxSourceProto test_pattern = 3;
EraseFluxSourceProto erase = 4;
KryofluxFluxSourceProto kryoflux = 5;
ScpFluxSourceProto scp = 6;
CwfFluxSourceProto cwf = 7;
Fl2FluxSourceProto fl2 = 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;
}

View File

@@ -1,50 +0,0 @@
#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;
}

View File

@@ -1,16 +0,0 @@
#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

@@ -1,32 +0,0 @@
#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

@@ -30,9 +30,7 @@ private:
std::unique_ptr<const Fluxmap> next()
{
usbSetDrive(config.drive().drive(),
config.drive().high_density(),
config.drive().index_mode());
usbSetDrive(config.drive().drive(), config.drive().high_density(), config.drive().index_mode());
usbSeek(_track);
Bytes data = usbRead(_head,
@@ -53,7 +51,7 @@ private:
public:
HardwareFluxSource(const HardwareFluxSourceProto& conf): _config(conf)
{
measureDiskRotation(_oneRevolution, _hardSectorThreshold);
measureDiskRotation(_oneRevolution, _hardSectorThreshold);
}
~HardwareFluxSource() {}
@@ -61,7 +59,8 @@ public:
public:
std::unique_ptr<FluxSourceIterator> readFlux(int track, int head) override
{
return std::make_unique<HardwareFluxSourceIterator>(*this, track, head);
return std::make_unique<HardwareFluxSourceIterator>(
*this, track, head);
}
void recalibrate() override
@@ -69,11 +68,6 @@ public:
usbRecalibrate();
}
void seek(int track) override
{
usbSeek(track);
}
bool isHardware() override
{
return true;

View File

@@ -6,6 +6,7 @@
#include "fluxsource/fluxsource.h"
#include "scp.h"
#include "proto.h"
#include "lib/logger.h"
#include "fmt/format.h"
#include <fstream>
@@ -21,130 +22,122 @@ static int headno(int strack)
static int strackno(int track, int side)
{
return (track << 1) | side;
return (track << 1) | side;
}
class ScpFluxSource : public TrivialFluxSource
{
public:
ScpFluxSource(const ScpFluxSourceProto& config): _config(config)
ScpFluxSource(const ScpFluxSourceProto& config):
_config(config)
{
_if.open(_config.filename(), std::ios::in | std::ios::binary);
if (!_if.is_open())
Error() << fmt::format("cannot open input file '{}': {}",
_config.filename(),
strerror(errno));
_if.open(_config.filename(), std::ios::in | std::ios::binary);
if (!_if.is_open())
Error() << fmt::format("cannot open input file '{}': {}", _config.filename(), strerror(errno));
_if.read((char*)&_header, sizeof(_header));
check_for_error();
_if.read((char*) &_header, sizeof(_header));
check_for_error();
if ((_header.file_id[0] != 'S') || (_header.file_id[1] != 'C') ||
(_header.file_id[2] != 'P'))
Error() << "input not a SCP file";
if ((_header.file_id[0] != 'S')
|| (_header.file_id[1] != 'C')
|| (_header.file_id[2] != 'P'))
Error() << "input not a SCP file";
::config.set_tpi((_header.flags & SCP_FLAG_96TPI) ? 96 : 48);
_resolution = 25 * (_header.resolution + 1);
int startSide = (_header.heads == 2) ? 1 : 0;
int endSide = (_header.heads == 1) ? 0 : 1;
_resolution = 25 * (_header.resolution + 1);
int startSide = (_header.heads == 2) ? 1 : 0;
int endSide = (_header.heads == 1) ? 0 : 1;
if ((_header.cell_width != 0) && (_header.cell_width != 16))
Error() << "currently only 16-bit cells in SCP files are supported";
if ((_header.cell_width != 0) && (_header.cell_width != 16))
Error() << "currently only 16-bit cells in SCP files are supported";
std::cout << fmt::format("SCP tracks {}-{}, heads {}-{}\n",
trackno(_header.start_track),
trackno(_header.end_track),
startSide,
endSide);
std::cout << fmt::format("SCP sample resolution: {} ns\n", _resolution);
}
Logger() << fmt::format("SCP: tracks {}-{}, heads {}-{}",
trackno(_header.start_track), trackno(_header.end_track), startSide, endSide);
Logger() << fmt::format("SCP sample resolution: {} ns\n", _resolution);
}
public:
std::unique_ptr<const Fluxmap> readSingleFlux(int track, int side) override
{
int strack = strackno(track, side);
if (strack >= ARRAY_SIZE(_header.track))
return std::make_unique<Fluxmap>();
uint32_t offset = Bytes(_header.track[strack], 4).reader().read_le32();
if (offset == 0)
return std::make_unique<Fluxmap>();
int strack = strackno(track, side);
if (strack >= ARRAY_SIZE(_header.track))
return std::make_unique<Fluxmap>();
uint32_t offset = Bytes(_header.track[strack], 4).reader().read_le32();
if (offset == 0)
return std::make_unique<Fluxmap>();
ScpTrackHeader trackheader;
_if.seekg(offset, std::ios::beg);
_if.read((char*)&trackheader, sizeof(trackheader));
check_for_error();
ScpTrackHeader trackheader;
_if.seekg(offset, std::ios::beg);
_if.read((char*) &trackheader, sizeof(trackheader));
check_for_error();
if ((trackheader.track_id[0] != 'T') ||
(trackheader.track_id[1] != 'R') ||
(trackheader.track_id[2] != 'K'))
Error() << "corrupt SCP file";
if ((trackheader.track_id[0] != 'T')
|| (trackheader.track_id[1] != 'R')
|| (trackheader.track_id[2] != 'K'))
Error() << "corrupt SCP file";
std::vector<ScpTrackRevolution> revs(_header.revolutions);
for (int revolution = 0; revolution < _header.revolutions; revolution++)
{
ScpTrackRevolution trackrev;
_if.read((char*)&trackrev, sizeof(trackrev));
check_for_error();
revs[revolution] = trackrev;
}
std::vector<ScpTrackRevolution> revs(_header.revolutions);
for (int revolution = 0; revolution < _header.revolutions; revolution++)
{
ScpTrackRevolution trackrev;
_if.read((char*) &trackrev, sizeof(trackrev));
check_for_error();
revs[revolution] = trackrev;
}
auto fluxmap = std::make_unique<Fluxmap>();
nanoseconds_t pending = 0;
unsigned inputBytes = 0;
for (int revolution = 0; revolution < _header.revolutions; revolution++)
{
if (revolution != 0)
fluxmap->appendIndex();
auto fluxmap = std::make_unique<Fluxmap>();
nanoseconds_t pending = 0;
unsigned inputBytes = 0;
for (int revolution = 0; revolution < _header.revolutions; revolution++)
{
if (revolution != 0)
fluxmap->appendIndex();
uint32_t datalength =
Bytes(revs[revolution].length, 4).reader().read_le32();
uint32_t dataoffset =
Bytes(revs[revolution].offset, 4).reader().read_le32();
uint32_t datalength = Bytes(revs[revolution].length, 4).reader().read_le32();
uint32_t dataoffset = Bytes(revs[revolution].offset, 4).reader().read_le32();
Bytes data(datalength * 2);
_if.seekg(dataoffset + offset, std::ios::beg);
_if.read((char*)data.begin(), data.size());
check_for_error();
Bytes data(datalength*2);
_if.seekg(dataoffset + offset, std::ios::beg);
_if.read((char*) data.begin(), data.size());
check_for_error();
ByteReader br(data);
for (int cell = 0; cell < datalength; cell++)
{
uint16_t interval = br.read_be16();
if (interval)
{
fluxmap->appendInterval(
(interval + pending) * _resolution / NS_PER_TICK);
fluxmap->appendPulse();
pending = 0;
}
else
pending += 0x10000;
}
ByteReader br(data);
for (int cell = 0; cell < datalength; cell++)
{
uint16_t interval = br.read_be16();
if (interval)
{
fluxmap->appendInterval((interval + pending) * _resolution / NS_PER_TICK);
fluxmap->appendPulse();
pending = 0;
}
else
pending += 0x10000;
}
inputBytes += datalength * 2;
}
inputBytes += datalength*2;
}
return fluxmap;
return fluxmap;
}
void recalibrate() {}
private:
void check_for_error()
{
if (_if.fail())
Error() << fmt::format("SCP read I/O error: {}", strerror(errno));
}
void check_for_error()
{
if (_if.fail())
Error() << fmt::format("SCP read I/O error: {}", strerror(errno));
}
private:
const ScpFluxSourceProto& _config;
std::ifstream _if;
ScpHeader _header;
nanoseconds_t _resolution;
std::ifstream _if;
ScpHeader _header;
nanoseconds_t _resolution;
};
std::unique_ptr<FluxSource> FluxSource::createScpFluxSource(
const ScpFluxSourceProto& config)
std::unique_ptr<FluxSource> FluxSource::createScpFluxSource(const ScpFluxSourceProto& config)
{
return std::unique_ptr<FluxSource>(new ScpFluxSource(config));
}

View File

@@ -23,10 +23,7 @@ void hexdump(std::ostream& stream, const Bytes& buffer)
break;
uint8_t c = buffer[pos+i];
if ((c >= 32) && (c <= 126))
stream << (char)c;
else
stream << '.';
stream << (isprint(c) ? (char)c : '.');
}
stream << std::endl;

View File

@@ -31,7 +31,7 @@ void Image::createBlankImage()
unsigned side = trackAndHead.second;
auto trackLayout = Layout::getLayoutOfTrack(track, side);
Bytes blank(trackLayout->sectorSize);
for (unsigned sectorId : trackLayout->naturalSectorOrder)
for (unsigned sectorId : trackLayout->logicalSectorOrder)
put(track, side, sectorId)->data = blank;
}
}

View File

@@ -1,8 +1,6 @@
#ifndef IMAGE_H
#define IMAGE_H
class Sector;
struct Geometry
{
unsigned numTracks = 0;

View File

@@ -14,39 +14,39 @@
std::unique_ptr<ImageReader> ImageReader::create(const ImageReaderProto& config)
{
switch (config.type())
switch (config.format_case())
{
case ImageReaderProto::DIM:
case ImageReaderProto::kDim:
return ImageReader::createDimImageReader(config);
case ImageReaderProto::D88:
case ImageReaderProto::kD88:
return ImageReader::createD88ImageReader(config);
case ImageReaderProto::FDI:
case ImageReaderProto::kFdi:
return ImageReader::createFdiImageReader(config);
case ImageReaderProto::IMD:
case ImageReaderProto::kImd:
return ImageReader::createIMDImageReader(config);
case ImageReaderProto::IMG:
case ImageReaderProto::kImg:
return ImageReader::createImgImageReader(config);
case ImageReaderProto::DISKCOPY:
case ImageReaderProto::kDiskcopy:
return ImageReader::createDiskCopyImageReader(config);
case ImageReaderProto::JV3:
case ImageReaderProto::kJv3:
return ImageReader::createJv3ImageReader(config);
case ImageReaderProto::D64:
case ImageReaderProto::kD64:
return ImageReader::createD64ImageReader(config);
case ImageReaderProto::NFD:
case ImageReaderProto::kNfd:
return ImageReader::createNFDImageReader(config);
case ImageReaderProto::NSI:
case ImageReaderProto::kNsi:
return ImageReader::createNsiImageReader(config);
case ImageReaderProto::TD0:
case ImageReaderProto::kTd0:
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->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); }},
{".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(); }},
// clang-format on
};
@@ -112,7 +112,7 @@ std::unique_ptr<Image> ImageReader::readMappedImage()
auto newSector = std::make_shared<Sector>();
*newSector = *e;
newSector->logicalSector =
trackLayout->filesystemToNaturalSectorMap.at(e->logicalSector);
trackLayout->filesystemToLogicalSectorMap.at(e->logicalSector);
sectors.insert(newSector);
}

View File

@@ -13,9 +13,9 @@ message Td0InputProto {}
message DimInputProto {}
message FdiInputProto {}
message D88InputProto {}
message NfdInputProto {}
message NFDInputProto {}
// NEXT_TAG: 15
// NEXT_TAG: 14
message ImageReaderProto
{
optional string filename = 1 [ (help) = "filename of input sector image" ];
@@ -24,32 +24,18 @@ message ImageReaderProto
default = false
];
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;
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;
}
}

View File

@@ -40,7 +40,7 @@ public:
break;
auto trackLayout = Layout::getLayoutOfTrack(track, side);
for (int sectorId : trackLayout->naturalSectorOrder)
for (int sectorId : trackLayout->logicalSectorOrder)
{
Bytes data(trackLayout->sectorSize);
inputFile.read((char*)data.begin(), data.size());

View File

@@ -14,30 +14,30 @@
std::unique_ptr<ImageWriter> ImageWriter::create(const ImageWriterProto& config)
{
switch (config.type())
switch (config.format_case())
{
case ImageWriterProto::IMG:
case ImageWriterProto::kImg:
return ImageWriter::createImgImageWriter(config);
case ImageWriterProto::D64:
case ImageWriterProto::kD64:
return ImageWriter::createD64ImageWriter(config);
case ImageWriterProto::LDBS:
case ImageWriterProto::kLdbs:
return ImageWriter::createLDBSImageWriter(config);
case ImageWriterProto::DISKCOPY:
case ImageWriterProto::kDiskcopy:
return ImageWriter::createDiskCopyImageWriter(config);
case ImageWriterProto::NSI:
case ImageWriterProto::kNsi:
return ImageWriter::createNsiImageWriter(config);
case ImageWriterProto::RAW:
case ImageWriterProto::kRaw:
return ImageWriter::createRawImageWriter(config);
case ImageWriterProto::D88:
case ImageWriterProto::kD88:
return ImageWriter::createD88ImageWriter(config);
case ImageWriterProto::IMD:
case ImageWriterProto::kImd:
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->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); }},
{".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(); }},
// clang-format on
};
@@ -218,7 +218,7 @@ void ImageWriter::writeMappedImage(const Image& image)
auto newSector = std::make_shared<Sector>();
*newSector = *e;
newSector->logicalSector =
trackLayout->naturalToFilesystemSectorMap.at(e->logicalSector);
trackLayout->logicalToFilesystemSectorMap.at(e->logicalSector);
sectors.insert(newSector);
}

View File

@@ -63,35 +63,24 @@ message ImdOutputProto
optional string comment = 3 [ (help) = "comment to set in IMD file" ];
}
// NEXT_TAG: 12
// NEXT_TAG: 11
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
];
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;
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;
}
}

View File

@@ -37,7 +37,7 @@ public:
int side = p.second;
auto trackLayout = Layout::getLayoutOfTrack(track, side);
for (int sectorId : trackLayout->naturalSectorOrder)
for (int sectorId : trackLayout->logicalSectorOrder)
{
const auto& sector = image.get(track, side, sectorId);
if (sector)

View File

@@ -4,6 +4,9 @@
#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 =
@@ -58,25 +61,6 @@ 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)
{
@@ -125,24 +109,10 @@ std::vector<unsigned> Layout::expandSectorList(
Error() << "LAYOUT: if you use a sector count, you can't use an "
"explicit sector list";
std::set<unsigned> sectorset;
int id = sectorsProto.start_sector();
for (int i = 0; i < sectorsProto.count(); i++)
{
while (sectorset.find(id) != sectorset.end())
{
id++;
if (id >= (sectorsProto.start_sector() + sectorsProto.count()))
id -= sectorsProto.count();
}
sectorset.insert(id);
sectors.push_back(id);
id += sectorsProto.skew();
if (id >= (sectorsProto.start_sector() + sectorsProto.count()))
id -= sectorsProto.count();
}
sectors.push_back(
sectorsProto.start_sector() +
((i * sectorsProto.skew()) % sectorsProto.count()));
}
else if (sectorsProto.sector_size() > 0)
{
@@ -158,57 +128,66 @@ std::vector<unsigned> Layout::expandSectorList(
std::shared_ptr<const TrackInfo> Layout::getLayoutOfTrack(
unsigned logicalTrack, unsigned logicalSide)
{
auto trackInfo = std::make_shared<TrackInfo>();
LayoutProto::LayoutdataProto layoutdata;
for (const auto& f : config.layout().layoutdata())
auto& layout = (*layoutCache)[std::make_pair(logicalTrack, logicalSide)];
if (!layout)
{
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;
layout = std::make_shared<TrackInfo>();
layoutdata.MergeFrom(f);
}
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;
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->naturalSectorOrder = trackInfo->diskSectorOrder;
std::sort(trackInfo->naturalSectorOrder.begin(),
trackInfo->naturalSectorOrder.end());
trackInfo->numSectors = trackInfo->naturalSectorOrder.size();
layoutdata.MergeFrom(f);
}
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 "
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
trackInfo->filesystemSectorOrder = trackInfo->naturalSectorOrder;
}
else
{
for (unsigned sectorId : layout->logicalSectorOrder)
layout->filesystemSectorOrder.push_back(sectorId);
}
for (int i = 0; i < trackInfo->numSectors; i++)
{
unsigned fid = trackInfo->naturalSectorOrder[i];
unsigned lid = trackInfo->filesystemSectorOrder[i];
trackInfo->filesystemToNaturalSectorMap[fid] = lid;
trackInfo->naturalToFilesystemSectorMap[lid] = fid;
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;
}
}
return trackInfo;
return layout;
}
std::shared_ptr<const TrackInfo> Layout::getLayoutOfTrackPhysical(

View File

@@ -23,19 +23,10 @@ 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.
* which Locations are going to be read from or written to. 8/
*/
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(
@@ -54,10 +45,9 @@ public:
const SectorListProto& sectorsProto);
};
class TrackInfo
{
class TrackInfo {
public:
TrackInfo() {}
TrackInfo() {}
private:
/* Can't copy. */
@@ -90,23 +80,20 @@ public:
/* Number of bytes in a sector. */
unsigned sectorSize = 0;
/* Sector IDs in sector ID order. This is the order in which the appear in
* disk images. */
std::vector<unsigned> naturalSectorOrder;
/* Sector IDs in disk order. This is the order they are written to the disk.
*/
/* Sector IDs in disk order. */
std::vector<unsigned> diskSectorOrder;
/* Sector IDs in filesystem order. This is the order in which the filesystem
* uses them. */
/* Sector IDs in logical order. */
std::vector<unsigned> logicalSectorOrder;
/* Sector IDs in filesystem order. */
std::vector<unsigned> filesystemSectorOrder;
/* Mapping of filesystem order to natural order. */
std::map<unsigned, unsigned> filesystemToNaturalSectorMap;
/* Mapping of filesystem order to logical order. */
std::map<unsigned, unsigned> filesystemToLogicalSectorMap;
/* Mapping of natural order to filesystem order. */
std::map<unsigned, unsigned> naturalToFilesystemSectorMap;
/* Mapping of logical order to filesystem order. */
std::map<unsigned, unsigned> logicalToFilesystemSectorMap;
};
#endif

View File

@@ -4,245 +4,229 @@
#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();
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;
}
};
if (f->options().GetExtension(::recurse) &&
(f->type() == google::protobuf::FieldDescriptor::TYPE_MESSAGE))
recurse(f->message_type(), n + ".");
fields[n] = f;
}
};
recurse(descriptor, "");
return fields;
recurse(descriptor, "");
return fields;
}

View File

File diff suppressed because it is too large Load Diff

View File

@@ -16,7 +16,7 @@ static USB* usb = NULL;
USB::~USB() {}
static std::shared_ptr<CandidateDevice> selectDevice()
static std::unique_ptr<CandidateDevice> selectDevice()
{
auto candidates = findUsbDevices();
if (candidates.size() == 0)
@@ -29,14 +29,14 @@ static std::shared_ptr<CandidateDevice> selectDevice()
for (auto& c : candidates)
{
if (c->serial == wantedSerial)
return c;
return std::move(c);
}
Error() << "serial number not found (try without one to list or "
"autodetect devices)";
}
if (candidates.size() == 1)
return candidates[0];
return std::move(candidates[0]);
std::cerr << "More than one device detected; use --usb.serial=<serial> to "
"select one:\n";

View File

@@ -24,9 +24,9 @@ static const std::string get_serial_number(const libusbp::device& device)
}
}
std::vector<std::shared_ptr<CandidateDevice>> findUsbDevices()
std::vector<std::unique_ptr<CandidateDevice>> findUsbDevices()
{
std::vector<std::shared_ptr<CandidateDevice>> candidates;
std::vector<std::unique_ptr<CandidateDevice>> candidates;
for (const auto& it : libusbp::list_connected_devices())
{
auto candidate = std::make_unique<CandidateDevice>();
@@ -42,10 +42,7 @@ std::vector<std::shared_ptr<CandidateDevice>> findUsbDevices()
{
libusbp::serial_port port(candidate->device);
candidate->serialPort = port.get_name();
candidate->type = DEVICE_GREASEWEAZLE;
}
else if (id == FLUXENGINE_ID)
candidate->type = DEVICE_FLUXENGINE;
candidates.push_back(std::move(candidate));
}
@@ -53,19 +50,3 @@ std::vector<std::shared_ptr<CandidateDevice>> findUsbDevices()
return candidates;
}
std::string getDeviceName(DeviceType type)
{
switch (type)
{
case DEVICE_GREASEWEAZLE:
return "Greaseweazle";
case DEVICE_FLUXENGINE:
return "FluxEngine";
default:
return "unknown";
}
}

View File

@@ -4,24 +4,15 @@
#include "libusbp_config.h"
#include "libusbp.hpp"
enum DeviceType
{
DEVICE_FLUXENGINE,
DEVICE_GREASEWEAZLE
};
extern std::string getDeviceName(DeviceType type);
struct CandidateDevice
{
DeviceType type;
libusbp::device device;
uint32_t id;
std::string serial;
std::string serialPort;
};
extern std::vector<std::shared_ptr<CandidateDevice>> findUsbDevices();
extern std::vector<std::unique_ptr<CandidateDevice>> findUsbDevices();
#endif

View File

@@ -3,7 +3,6 @@
#include "lib/bytes.h"
#include <fmt/format.h>
#include <iomanip>
#include <fstream>
bool emergencyStop = false;
@@ -191,10 +190,3 @@ 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,7 +18,6 @@ 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

@@ -1,182 +0,0 @@
#include "lib/globals.h"
#include "lib/vfs/vfs.h"
#include "lib/utils.h"
#include "lib/config.pb.h"
#include <fmt/format.h>
/* This is described here:
* http://fileformats.archiveteam.org/wiki/Apple_DOS_file_system
* (also in Inside AppleDOS)
*/
class AppledosFilesystem : public Filesystem
{
static constexpr int VTOC_BLOCK = 17 * 16;
class AppledosDirent : public Dirent
{
public:
AppledosDirent(const Bytes& de)
{
ByteReader br(de);
track = br.read_8();
sector = br.read_8();
flags = br.read_8();
filename = br.read(30);
length = br.read_le16() * 256;
for (char& c : filename)
c &= 0x7f;
filename = rightTrimWhitespace(filename);
path = {filename};
file_type = TYPE_FILE;
attributes[FILENAME] = filename;
attributes[LENGTH] = std::to_string(length);
attributes[FILE_TYPE] = "file";
attributes["appledos.flags"] = fmt::format("0x{:x}", flags);
}
uint8_t track;
uint8_t sector;
uint8_t flags;
};
public:
AppledosFilesystem(
const AppledosProto& config, std::shared_ptr<SectorInterface> sectors):
Filesystem(sectors),
_config(config)
{
}
uint32_t capabilities() const
{
return OP_LIST | OP_GETDIRENT | OP_GETFSDATA | OP_GETFILE;
}
std::map<std::string, std::string> getMetadata() override
{
mount();
std::map<std::string, std::string> attributes;
attributes[VOLUME_NAME] = "";
attributes[TOTAL_BLOCKS] = std::to_string(_vtoc[0x34] * _vtoc[0x35]);
attributes[USED_BLOCKS] = "0";
attributes[BLOCK_SIZE] = "256";
return attributes;
}
std::vector<std::shared_ptr<Dirent>> list(const Path& path) override
{
mount();
if (path.size() != 0)
throw BadPathException();
std::vector<std::shared_ptr<Dirent>> results;
for (auto& de : _dirents)
results.push_back(de);
return results;
}
std::shared_ptr<Dirent> getDirent(const Path& path) override
{
mount();
if (path.size() != 1)
throw BadPathException();
return find(path.front());
}
Bytes getFile(const Path& path) override
{
mount();
if (path.size() != 1)
throw BadPathException();
auto dirent = find(path.front());
int tstrack = dirent->track;
int tssector = dirent->sector;
Bytes bytes;
ByteWriter bw(bytes);
while (tstrack)
{
Bytes ts = getAppleSector(tstrack * 16 + tssector);
ByteReader br(ts);
br.seek(0x0c);
while (!br.eof())
{
int track = br.read_8();
int sector = br.read_8();
if (!track)
goto done;
bw += getAppleSector(track * 16 + sector);
}
tstrack = ts[1];
tssector = ts[2];
}
done:
return bytes;
}
private:
void mount()
{
_vtoc = getAppleSector(VTOC_BLOCK);
if ((_vtoc[0x27] != 122) || (_vtoc[0x36] != 0) || (_vtoc[0x37] != 1))
throw BadFilesystemException();
_dirents.clear();
int track = _vtoc[1];
int sector = _vtoc[2];
while (track)
{
Bytes dir = getAppleSector(track * 16 + sector);
ByteReader br(dir);
br.seek(0x0b);
while (!br.eof())
{
Bytes fde = br.read(0x23);
if ((fde[0] != 0) && (fde[0] != 255))
_dirents.push_back(std::make_shared<AppledosDirent>(fde));
}
track = dir[1];
sector = dir[2];
}
}
std::shared_ptr<AppledosDirent> find(const std::string filename)
{
for (auto& de : _dirents)
if (de->filename == filename)
return de;
throw FileNotFoundException();
}
Bytes getAppleSector(uint32_t number, uint32_t count = 1)
{
return getLogicalSector(
number + _config.filesystem_offset_sectors(), count);
}
private:
const AppledosProto& _config;
Bytes _vtoc;
std::vector<std::shared_ptr<AppledosDirent>> _dirents;
};
std::unique_ptr<Filesystem> Filesystem::createAppledosFilesystem(
const FilesystemProto& config, std::shared_ptr<SectorInterface> sectors)
{
return std::make_unique<AppledosFilesystem>(config.appledos(), sectors);
}

View File

@@ -31,18 +31,7 @@ public:
{
ByteReader br(bytes);
filename = br.read(8);
for (int i = 0; filename.size(); i++)
{
if (filename[i] == ' ')
{
filename = filename.substr(0, i);
break;
}
if ((filename[i] < 32) || (filename[i] > 126))
throw BadFilesystemException();
}
filename = filename.substr(0, filename.find(' '));
path = {filename};
brotherType = br.read_8();
@@ -102,7 +91,7 @@ public:
for (int d = 0; d < SECTOR_SIZE / 16; d++)
{
Bytes buffer = bytes.slice(d * 16, 16);
if (buffer[0] & 0x80)
if (buffer[0] == 0xf0)
continue;
auto de = std::make_shared<Brother120Dirent>(buffer);

View File

@@ -3,77 +3,77 @@
#include "lib/config.pb.h"
#include <fmt/format.h>
class CpmFsFilesystem : public Filesystem
class Entry
{
class Entry
public:
Entry(const Bytes& bytes, int map_entry_size)
{
public:
Entry(const Bytes& bytes, int map_entry_size)
user = bytes[0] & 0x0f;
{
user = bytes[0] & 0x0f;
std::stringstream ss;
ss << (char)(user + '0') << ':';
for (int i = 1; i <= 8; i++)
{
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());
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();
}
public:
std::string filename;
std::string mode;
unsigned user;
unsigned extent;
unsigned records;
std::vector<unsigned> allocation_map;
};
{
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
{
public:
CpmFsFilesystem(
const CpmFsProto& config, std::shared_ptr<SectorInterface> sectors):
@@ -188,6 +188,7 @@ public:
/* Find a directory entry for this logical extent. */
std::unique_ptr<Entry> entry;
bool moreExtents = false;
for (int d = 0; d < _config.dir_entries(); d++)
{
entry = getEntry(d);
@@ -195,10 +196,9 @@ public:
continue;
if (path[0] != entry->filename)
continue;
if (entry->extent < logicalExtent)
continue;
if ((entry->extent & ~_logicalExtentMask) ==
(logicalExtent & ~_logicalExtentMask))
if (entry->extent > logicalExtent)
moreExtents = true;
if (entry->extent == logicalExtent)
break;
}
@@ -212,10 +212,10 @@ public:
/* Copy the data out. */
int i =
(logicalExtent & _logicalExtentMask) * _blocksPerLogicalExtent;
(entry->extent & ~_logicalExtentMask) * _blocksPerLogicalExtent;
unsigned records =
(entry->extent == logicalExtent) ? entry->records : 128;
while (records != 0)
while ((records != 0) && (i != entry->allocation_map.size()))
{
Bytes block;
unsigned blockid = entry->allocation_map[i];
@@ -265,7 +265,7 @@ private:
physicalExtentSize = _config.block_size() * 8;
}
_logicalExtentsPerEntry = physicalExtentSize / 16384;
_logicalExtentMask = _logicalExtentsPerEntry - 1;
_logicalExtentMask = (1 << _logicalExtentsPerEntry) - 1;
_blocksPerLogicalExtent = 16384 / _config.block_size();
_directory = getCpmBlock(0, _dirBlocks);

View File

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

View File

@@ -72,7 +72,7 @@ public:
if (!imageContainsAllSectorsOf(_changedSectors,
track,
side,
trackLayout->naturalSectorOrder))
trackLayout->logicalSectorOrder))
{
/* If we don't have any loaded sectors for this track, pre-read
* it. */
@@ -83,7 +83,7 @@ public:
/* Now merge the loaded track with the changed one, and write
* the result back. */
for (unsigned sectorId : trackLayout->naturalSectorOrder)
for (unsigned sectorId : trackLayout->logicalSectorOrder)
{
if (!_changedSectors.contains(track, side, sectorId))
_changedSectors.put(track, side, sectorId)->data =

View File

@@ -1,296 +0,0 @@
#include "lib/globals.h"
#include "lib/vfs/vfs.h"
#include "lib/config.pb.h"
#include "lib/utils.h"
#include <fmt/format.h>
/* Root block:
*
* 00-0b volume name
* 0c 01
* 0d 2a
* 0e 79
* 0f 6d
* 10 07 0x07c10c19, creation timestamp
* 11 c1 ^
* 12 0c ^
* 13 19 ^
* 14 2f
* 15 00
* 16 00
* 17 18
* 18 03 0x320, number of blocks on the disk
* 19 20 ^
* 1a 00 0x0010, first data block?
* 1b 10 ^
* 1c 00 0x0010, address of bitmap in HCS
* 1d 10 ^
* 1e 00 0x0011, address of FLIST in HCS
* 1f 11 ^
* 20 00 0x0017, address of last block of FLIST?
* 21 17 ^
* 22 00
* 23 6b
* 24 00
* 25 20
*
* 14 files
* file id 3 is not used
* directory at 0xc00
* 0x4000, block 0x10, volume bitmap
* 0x4400, block 0x11, flist, 7 blocks long?
* file descriptors seem to be 64 bytes
*
* File descriptor, 64 bytes:
* 00 file type
* 0e+04 length in bytes
* 14... spans
* word: start block
* word: number of blocks
*
00000C00 00 01 42 49 54 4D 41 50 2E 53 59 53 00 00 00 00 ..BITMAP.SYS....
00008040 41 00 00 00 07 C1 0C 19 1E 00 00 18 00 02 00 00 A...............
00008050 04 00 00 01 00 10 00 01 00 00 00 00 00 00 00 00 ................
00008060 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00008070 00 00 00 00 00 00 00 00 00 00 00 01 00 01 01 00 ................
00000C10 00 02 46 4C 49 53 54 2E 53 59 53 00 00 00 00 00 ..FLIST.SYS.....
00008080 41 00 00 00 07 C1 0C 19 1E 00 00 18 00 02 00 00 A...............
00008090 1C 00 00 01 00 11 00 07 00 00 00 00 00 00 00 00 ................
000080A0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
000080B0 00 00 00 00 00 00 00 00 00 00 00 07 00 07 01 00 ................
00000C20 00 04 53 4B 45 4C 00 00 00 00 00 00 00 00 00 00 ..SKEL..........
00008100 01 00 00 03 07 C1 0C 19 19 00 00 19 00 02 00 00 ................
00008110 55 00 00 01 00 20 00 16 00 00 00 00 00 00 00 00 U.... ..........
00008120 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00008130 00 00 00 00 00 00 00 00 00 00 00 16 00 16 01 00 ................
00000C30 00 05 43 4F 44 45 00 00 00 00 00 00 00 00 00 00 ..CODE..........
00004540 01 00 00 03 07 C1 0C 19 26 00 00 1F 00 02 00 08 ........&.......
00004550 10 00 00 01 00 36 02 04 00 00 00 00 00 00 00 00 .....6..........
00004560 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00004570 00 00 00 00 00 00 00 00 00 00 02 04 02 04 01 00 ................
00000C40 00 06 53 43 53 49 00 00 00 00 00 00 00 00 00 00 ..SCSI..........
00000C50 00 07 41 4F 46 00 00 00 00 00 00 00 00 00 00 00 ..AOF...........
00000C60 00 08 4D 43 46 00 00 00 00 00 00 00 00 00 00 00 ..MCF...........
00000C70 00 09 53 59 53 54 45 4D 2E 53 43 46 00 00 00 00 ..SYSTEM.SCF....
00000C80 00 0A 53 59 53 54 45 4D 2E 50 44 46 00 00 00 00 ..SYSTEM.PDF....
00000C90 00 0B 43 4C 54 31 2E 43 4C 54 00 00 00 00 00 00 ..CLT1.CLT......
00000CA0 00 0C 43 4C 54 32 2E 43 4C 54 00 00 00 00 00 00 ..CLT2.CLT......
00000CB0 00 0D 43 4C 54 33 2E 43 4C 54 00 00 00 00 00 00 ..CLT3.CLT......
00000CC0 00 0E 43 4C 54 34 2E 43 4C 54 00 00 00 00 00 00 ..CLT4.CLT......
00000CD0 00 0F 47 52 45 59 2E 43 4C 54 00 00 00 00 00 00 ..GREY.CLT......
*/
static void trimZeros(std::string s)
{
s.erase(std::remove(s.begin(), s.end(), 0), s.end());
}
class PhileFilesystem : public Filesystem
{
struct Span
{
uint16_t startBlock;
uint16_t blockCount;
};
class PhileDirent : public Dirent
{
public:
PhileDirent(
int fileno, const std::string& filename, const Bytes& filedes):
_fileno(fileno)
{
file_type = TYPE_FILE;
ByteReader br(filedes);
br.seek(0x0e);
length = br.read_be32();
{
std::stringstream ss;
ss << 'R';
if (filedes[0] & 0x40)
ss << 'S';
mode = ss.str();
}
this->filename = filename;
path = {filename};
attributes[Filesystem::FILENAME] = filename;
attributes[Filesystem::LENGTH] = std::to_string(length);
attributes[Filesystem::FILE_TYPE] = "file";
attributes[Filesystem::MODE] = mode;
int spans = br.read_be16();
for (int i = 0; i < spans; i++)
{
Span span;
span.startBlock = br.read_be16();
span.blockCount = br.read_be16();
_spans.push_back(span);
}
attributes["phile.spans"] = std::to_string(spans);
}
const std::vector<Span>& spans() const
{
return _spans;
}
private:
int _fileno;
std::vector<Span> _spans;
};
public:
PhileFilesystem(
const PhileProto& config, std::shared_ptr<SectorInterface> sectors):
Filesystem(sectors),
_config(config)
{
}
uint32_t capabilities() const
{
return OP_GETFSDATA | OP_LIST | OP_GETFILE | OP_GETDIRENT;
}
FilesystemStatus check() override
{
return FS_OK;
}
std::map<std::string, std::string> getMetadata() override
{
mount();
std::string volumename = _rootBlock.reader().read(0x0c);
trimZeros(volumename);
std::map<std::string, std::string> attributes;
attributes[VOLUME_NAME] = volumename;
attributes[TOTAL_BLOCKS] = std::to_string(_totalBlocks);
attributes[USED_BLOCKS] = attributes[TOTAL_BLOCKS];
attributes[BLOCK_SIZE] = std::to_string(_config.block_size());
return attributes;
}
std::shared_ptr<Dirent> getDirent(const Path& path) override
{
mount();
if (path.size() != 1)
throw BadPathException();
return findFile(path.front());
}
std::vector<std::shared_ptr<Dirent>> list(const Path& path) override
{
mount();
if (!path.empty())
throw FileNotFoundException();
std::vector<std::shared_ptr<Dirent>> result;
for (auto& de : _dirents)
result.push_back(de.second);
return result;
}
Bytes getFile(const Path& path) override
{
mount();
if (path.size() != 1)
throw BadPathException();
auto dirent = findFile(path.front());
Bytes data;
ByteWriter bw(data);
for (const auto& span : dirent->spans())
bw += getPsosBlock(span.startBlock, span.blockCount);
data.resize(dirent->length);
return data;
}
private:
void mount()
{
_sectorSize = getLogicalSectorSize();
_blockSectors = _config.block_size() / _sectorSize;
_rootBlock = getPsosBlock(2, 1);
_bitmapBlockNumber = _rootBlock.reader().seek(0x1c).read_be16();
_filedesBlockNumber = _rootBlock.reader().seek(0x1e).read_be16();
_filedesLength =
_rootBlock.reader().seek(0x20).read_be16() - _filedesBlockNumber + 1;
_totalBlocks = _rootBlock.reader().seek(0x18).read_be16();
Bytes directoryBlock = getPsosBlock(3, 1);
Bytes filedesBlock = getPsosBlock(_filedesBlockNumber, _filedesLength);
_dirents.clear();
ByteReader br(directoryBlock);
ByteReader fr(filedesBlock);
while (!br.eof())
{
uint16_t fileno = br.read_be16();
std::string filename = br.read(14);
trimZeros(filename);
if (fileno)
{
fr.seek(fileno * 64);
Bytes filedes = fr.read(64);
auto dirent =
std::make_unique<PhileDirent>(fileno, filename, filedes);
_dirents[fileno] = std::move(dirent);
}
}
}
std::shared_ptr<PhileDirent> findFile(const std::string filename)
{
for (const auto& dirent : _dirents)
{
if (dirent.second->filename == filename)
return dirent.second;
}
throw FileNotFoundException();
}
Bytes getPsosBlock(uint32_t number, uint32_t count = 1)
{
unsigned sector = number * _blockSectors;
return getLogicalSector(sector, _blockSectors * count);
}
private:
const PhileProto& _config;
int _sectorSize;
int _blockSectors;
int _totalBlocks;
int _bitmapBlockNumber;
int _filedesBlockNumber;
int _filedesLength;
Bytes _rootBlock;
std::map<int, std::shared_ptr<PhileDirent>> _dirents;
};
std::unique_ptr<Filesystem> Filesystem::createPhileFilesystem(
const FilesystemProto& config, std::shared_ptr<SectorInterface> sectors)
{
return std::make_unique<PhileFilesystem>(config.phile(), sectors);
}

View File

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

View File

@@ -2,7 +2,6 @@
#define VFS_H
#include "lib/bytes.h"
#include <fmt/format.h>
class Sector;
class Image;
@@ -59,11 +58,6 @@ 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) {}
@@ -248,12 +242,6 @@ 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> createAppledosFilesystem(
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> createPhileFilesystem(
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,53 +59,20 @@ message CbmfsProto
message ProdosProto {}
message AppledosProto {
optional uint32 filesystem_offset_sectors = 1 [
default = 0,
(help) = "offset the entire offset up the disk this many sectors"
];
}
message Smaky6FsProto {}
message PhileProto {
optional uint32 block_size = 1 [
default = 1024,
(help) = "Phile filesystem block size"
];
}
// NEXT_TAG: 14
// NEXT_TAG: 10
message FilesystemProto
{
enum FilesystemType {
NOT_SET = 0;
ACORNDFS = 1;
BROTHER120 = 2;
FATFS = 3;
CPMFS = 4;
AMIGAFFS = 5;
MACHFS = 6;
CBMFS = 7;
PRODOS = 8;
SMAKY6 = 9;
APPLEDOS = 10;
PHILE = 11;
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;
}
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 AppledosProto appledos = 12;
optional Smaky6FsProto smaky6 = 11;
optional PhileProto phile = 13;
optional SectorListProto sector_order = 9 [(help) = "specify the filesystem order of sectors"];
}

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