Compare commits
166 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
acf32c2a5c | ||
|
|
41460457a3 | ||
|
|
ec68ce3bfa | ||
|
|
a777a5be30 | ||
|
|
b553a8b1fb | ||
|
|
b119e1f72d | ||
|
|
7345d3e6c1 | ||
|
|
e9b7a7bb52 | ||
|
|
2022732dd9 | ||
|
|
63544647b6 | ||
|
|
6b62585ad5 | ||
|
|
14027210f7 | ||
|
|
3df17b23b8 | ||
|
|
cbf3f56562 | ||
|
|
1f74d9189f | ||
|
|
137658d1d6 | ||
|
|
5b627bd2b1 | ||
|
|
38ff08885a | ||
|
|
a89993aabb | ||
|
|
d6353403e2 | ||
|
|
bc62ee04c0 | ||
|
|
d3ff836b63 | ||
|
|
a7aac5578e | ||
|
|
add5a141d3 | ||
|
|
330410ec61 | ||
|
|
d0f49dcfa6 | ||
|
|
124f6ab7cb | ||
|
|
471f63592e | ||
|
|
50e210c72f | ||
|
|
d3396aa535 | ||
|
|
5ed8b838bc | ||
|
|
d1757eacc2 | ||
|
|
0692e5f5d5 | ||
|
|
e4204196cd | ||
|
|
241d4342e4 | ||
|
|
c04cbc631c | ||
|
|
29b273ad7b | ||
|
|
9720dab2f6 | ||
|
|
bddc64a324 | ||
|
|
eb324f14de | ||
|
|
b78a057c81 | ||
|
|
5751725213 | ||
|
|
f194392f99 | ||
|
|
fea62178af | ||
|
|
33ef4ce8de | ||
|
|
3728120f95 | ||
|
|
2944b9b3f6 | ||
|
|
3430574364 | ||
|
|
fc5a5212c0 | ||
|
|
20f724ed13 | ||
|
|
94c1d21938 | ||
|
|
a1a9666b6f | ||
|
|
0551ddc276 | ||
|
|
049ffd3b04 | ||
|
|
c28f757c5c | ||
|
|
91dbb86e64 | ||
|
|
27a04ee22b | ||
|
|
5cefce9922 | ||
|
|
8fb4c90bed | ||
|
|
81753669cc | ||
|
|
0a0a72bcf3 | ||
|
|
c4a6e3e063 | ||
|
|
1138e6b77f | ||
|
|
030f9218d6 | ||
|
|
2fff32e8f2 | ||
|
|
5b2aa9926f | ||
|
|
921e178e83 | ||
|
|
25ffd900c8 | ||
|
|
7ea4e116cc | ||
|
|
a9daec36f5 | ||
|
|
cebc7c6cd2 | ||
|
|
3f85c9f006 | ||
|
|
ed5efd7b87 | ||
|
|
4984a53bfd | ||
|
|
b0c77653a2 | ||
|
|
909f0d628b | ||
|
|
e27e3ada92 | ||
|
|
339ea3b5a4 | ||
|
|
9bd8b8915e | ||
|
|
35008656a9 | ||
|
|
825089458f | ||
|
|
4a086d94b7 | ||
|
|
0aeddf7e98 | ||
|
|
4922d1deb4 | ||
|
|
86d0893261 | ||
|
|
e4c67f18bd | ||
|
|
d07c5a94e1 | ||
|
|
a91dee27e7 | ||
|
|
e3219087c9 | ||
|
|
cc9ec84aec | ||
|
|
a33cc5710c | ||
|
|
c2b148288a | ||
|
|
a483567564 | ||
|
|
bd99bc6d94 | ||
|
|
8f79071aad | ||
|
|
ef9071049b | ||
|
|
60e1ab8cca | ||
|
|
d3dbfd3154 | ||
|
|
ee2dffb498 | ||
|
|
6d9510cc65 | ||
|
|
49f0f5d000 | ||
|
|
d8528d889a | ||
|
|
ec439a5f2a | ||
|
|
d88110488e | ||
|
|
2f4b15293a | ||
|
|
3d71f32587 | ||
|
|
5136dda598 | ||
|
|
d26940304b | ||
|
|
f9f11b4966 | ||
|
|
1297b568ba | ||
|
|
fd10840cc0 | ||
|
|
7e86be979d | ||
|
|
731d1efcc4 | ||
|
|
dfda8be30c | ||
|
|
7dd1b6d8e9 | ||
|
|
ec1bcdb9e5 | ||
|
|
c0f46d2bd4 | ||
|
|
615dca3130 | ||
|
|
9cf9597c54 | ||
|
|
e24ee648e7 | ||
|
|
e3518dc389 | ||
|
|
693ba20606 | ||
|
|
b947c6c186 | ||
|
|
7f8ecb8514 | ||
|
|
4df6afa9c1 | ||
|
|
d0620f8efe | ||
|
|
2b1a6dbb03 | ||
|
|
c6ef667c3f | ||
|
|
8c1d1bec93 | ||
|
|
8be174e65a | ||
|
|
6d37fafb02 | ||
|
|
46ce882daa | ||
|
|
6d14cbdb9b | ||
|
|
4bf6b433ae | ||
|
|
87a1b2e6f8 | ||
|
|
c6ee48ec85 | ||
|
|
b58a6b1649 | ||
|
|
bd9736df93 | ||
|
|
3b9c966e3d | ||
|
|
96c9a89171 | ||
|
|
c374ffd15e | ||
|
|
c53109e1a1 | ||
|
|
4598b3a7a6 | ||
|
|
cf975b74bf | ||
|
|
5d65dcf3c8 | ||
|
|
f299ec1f8d | ||
|
|
6677034774 | ||
|
|
3c7c4639a9 | ||
|
|
7e9a1268a5 | ||
|
|
a60b8e68ca | ||
|
|
b2161aa67e | ||
|
|
d1fffb1d08 | ||
|
|
52d66d9555 | ||
|
|
66f2d359e2 | ||
|
|
8327f33ee6 | ||
|
|
d4a94551d9 | ||
|
|
d2a545d83e | ||
|
|
aee4eac271 | ||
|
|
cbeddb11bc | ||
|
|
4c51cf8316 | ||
|
|
345cd6bd92 | ||
|
|
48540245b5 | ||
|
|
088bd9434d | ||
|
|
8ba8c58377 | ||
|
|
4ae43d0503 | ||
|
|
d20ce3dde7 |
@@ -18,17 +18,19 @@ AlwaysBreakBeforeMultilineStrings: 'true'
|
||||
AlwaysBreakTemplateDeclarations: 'Yes'
|
||||
BinPackArguments: 'false'
|
||||
BinPackParameters: 'false'
|
||||
BreakConstructorInitializers: 'AfterColon'
|
||||
BreakBeforeBraces: Allman
|
||||
BreakConstructorInitializers: 'AfterColon'
|
||||
BreakInheritanceList: AfterColon
|
||||
BreakStringLiterals: 'true'
|
||||
IndentCaseLabels: 'true'
|
||||
IndentWidth: '4'
|
||||
ColumnLimit: '80'
|
||||
ConstructorInitializerAllOnOneLineOrOnePerLine: 'true'
|
||||
FixNamespaceComments: 'false'
|
||||
IncludeBlocks: Preserve
|
||||
IndentCaseLabels: 'true'
|
||||
IndentWidth: '4'
|
||||
IndentWrappedFunctionNames: 'false'
|
||||
KeepEmptyLinesAtTheStartOfBlocks: 'true'
|
||||
NamespaceIndentation: All
|
||||
PointerAlignment: Left
|
||||
ReflowComments: 'true'
|
||||
SortIncludes: 'false'
|
||||
|
||||
38
.github/workflows/ccpp.yml
vendored
@@ -10,16 +10,39 @@ 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
|
||||
run: CXXFLAGS="-Wp,-D_GLIBCXX_ASSERTIONS" make -j2
|
||||
|
||||
build-macos:
|
||||
build-macos-current:
|
||||
runs-on: macos-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: brew
|
||||
run: brew install sqlite pkg-config libusb protobuf wxwidgets fmt make coreutils
|
||||
run: brew install sqlite pkg-config libusb protobuf wxwidgets fmt make coreutils dylibbundler libjpeg
|
||||
- name: make
|
||||
run: gmake
|
||||
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
|
||||
|
||||
build-windows:
|
||||
runs-on: windows-latest
|
||||
@@ -44,9 +67,10 @@ jobs:
|
||||
mingw-w64-i686-zlib
|
||||
mingw-w64-i686-nsis
|
||||
zip
|
||||
vim
|
||||
- uses: actions/checkout@v1
|
||||
- name: build
|
||||
run: make
|
||||
run: make -j2
|
||||
|
||||
- name: nsis
|
||||
run: |
|
||||
@@ -56,10 +80,10 @@ jobs:
|
||||
|
||||
- name: zip
|
||||
run: |
|
||||
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
|
||||
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
|
||||
|
||||
- name: Upload build artifacts
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: ${{ github.event.repository.name }}.${{ github.sha }}
|
||||
path: fluxengine.zip
|
||||
path: fluxengine-windows.zip
|
||||
|
||||
42
.github/workflows/release.yml
vendored
@@ -29,11 +29,12 @@ jobs:
|
||||
mingw-w64-i686-zlib
|
||||
mingw-w64-i686-nsis
|
||||
zip
|
||||
vim
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: build
|
||||
run: |
|
||||
make
|
||||
make -j2
|
||||
|
||||
- name: nsis
|
||||
run: |
|
||||
@@ -77,3 +78,42 @@ jobs:
|
||||
tag_name: dev
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
build-macos:
|
||||
runs-on: macos-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: brew
|
||||
run: brew install sqlite pkg-config libusb protobuf wxwidgets fmt make coreutils 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 }}
|
||||
|
||||
|
||||
|
||||
|
||||
16
Makefile
@@ -111,6 +111,7 @@ PROTOS = \
|
||||
arch/micropolis/micropolis.proto \
|
||||
arch/mx/mx.proto \
|
||||
arch/northstar/northstar.proto \
|
||||
arch/smaky6/smaky6.proto \
|
||||
arch/tids990/tids990.proto \
|
||||
arch/victor9k/victor9k.proto \
|
||||
arch/zilogmcz/zilogmcz.proto \
|
||||
@@ -159,16 +160,23 @@ include tests/build.mk
|
||||
do-encodedecodetest = $(eval $(do-encodedecodetest-impl))
|
||||
define do-encodedecodetest-impl
|
||||
|
||||
tests: $(OBJDIR)/$1$3.encodedecode
|
||||
$(OBJDIR)/$1$3.encodedecode: scripts/encodedecodetest.sh $(FLUXENGINE_BIN) $2
|
||||
tests: $(OBJDIR)/$1$3.flux.encodedecode
|
||||
$(OBJDIR)/$1$3.flux.encodedecode: scripts/encodedecodetest.sh $(FLUXENGINE_BIN) $2
|
||||
@mkdir -p $(dir $$@)
|
||||
@echo ENCODEDECODETEST $1 $3
|
||||
@echo ENCODEDECODETEST $1 flux $(FLUXENGINE_BIN) $2 $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,apple2)
|
||||
$(call do-encodedecodetest,appleii140)
|
||||
$(call do-encodedecodetest,atarist360)
|
||||
$(call do-encodedecodetest,atarist370)
|
||||
$(call do-encodedecodetest,atarist400)
|
||||
|
||||
@@ -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) | 🦄 | 🦄 | |
|
||||
| [Apple II](doc/disk-apple2.md) | 🦄 | 🦄 | both 140kB and 640kB formats |
|
||||
| [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,7 +115,9 @@ 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 |
|
||||
| [Sharp X68000](doc/disk-ibm.md) | 🦄 | 🦄 | |
|
||||
| [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 |
|
||||
| [TRS-80](doc/disk-trs80.md) | 🦖 | 🦖* | a minor variation of the IBM scheme |
|
||||
{: .datatable }
|
||||
|
||||
|
||||
@@ -13,16 +13,6 @@ static const FluxPattern SECTOR_PATTERN(32, AESLANIER_RECORD_SEPARATOR);
|
||||
|
||||
/* This is actually M2FM, rather than MFM, but it our MFM/FM decoder copes fine with it. */
|
||||
|
||||
static Bytes reverse_bits(const Bytes& input)
|
||||
{
|
||||
Bytes output;
|
||||
ByteWriter bw(output);
|
||||
|
||||
for (uint8_t b : input)
|
||||
bw.write_8(reverse_bits(b));
|
||||
return output;
|
||||
}
|
||||
|
||||
class AesLanierDecoder : public Decoder
|
||||
{
|
||||
public:
|
||||
@@ -43,7 +33,7 @@ public:
|
||||
|
||||
const auto& rawbits = readRawBits(AESLANIER_RECORD_SIZE*16);
|
||||
const auto& bytes = decodeFmMfm(rawbits).slice(0, AESLANIER_RECORD_SIZE);
|
||||
const auto& reversed = reverse_bits(bytes);
|
||||
const auto& reversed = bytes.reverseBits();
|
||||
|
||||
_sector->logicalTrack = reversed[1];
|
||||
_sector->logicalSide = 0;
|
||||
|
||||
@@ -3,7 +3,16 @@
|
||||
|
||||
#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);
|
||||
|
||||
|
||||
@@ -1,5 +1,19 @@
|
||||
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."];
|
||||
}
|
||||
|
||||
|
||||
@@ -33,10 +33,7 @@
|
||||
*
|
||||
*/
|
||||
|
||||
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 = {
|
||||
|
||||
118
arch/agat/encoder.cc
Normal file
@@ -0,0 +1,118 @@
|
||||
#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));
|
||||
}
|
||||
@@ -2,7 +2,10 @@ syntax = "proto2";
|
||||
|
||||
import "lib/common.proto";
|
||||
|
||||
message Apple2DecoderProto {}
|
||||
message Apple2DecoderProto {
|
||||
optional uint32 side_one_track_offset = 1
|
||||
[ default = 0, (help) = "offset to apply to track numbers on side 1" ];
|
||||
}
|
||||
|
||||
message Apple2EncoderProto
|
||||
{
|
||||
@@ -13,4 +16,7 @@ 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" ];
|
||||
}
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
#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>
|
||||
@@ -12,22 +14,25 @@
|
||||
|
||||
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)
|
||||
{
|
||||
@@ -47,9 +52,11 @@ 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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,88 +74,102 @@ 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->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->logicalSide = _sector->physicalSide;
|
||||
_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 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;
|
||||
}
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -56,8 +56,7 @@ 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;
|
||||
}
|
||||
@@ -132,13 +131,17 @@ 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(sector.logicalTrack);
|
||||
write_gcr44(track);
|
||||
write_gcr44(sector.logicalSector);
|
||||
write_gcr44(volume_id ^ sector.logicalTrack ^ sector.logicalSector);
|
||||
write_gcr44(volume_id ^ track ^ sector.logicalSector);
|
||||
write_bits(0xDEAAEB, 24);
|
||||
|
||||
// Write data syncing leader: FF40 + APPLE2_DATA_RECORD + sector
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
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 \
|
||||
@@ -16,18 +19,17 @@ LIBARCH_SRCS = \
|
||||
arch/ibm/encoder.cc \
|
||||
arch/macintosh/decoder.cc \
|
||||
arch/macintosh/encoder.cc \
|
||||
arch/micropolis/decoder.cc \
|
||||
arch/micropolis/encoder.cc \
|
||||
arch/mx/decoder.cc \
|
||||
arch/northstar/decoder.cc \
|
||||
arch/northstar/encoder.cc \
|
||||
arch/smaky6/decoder.cc \
|
||||
arch/tids990/decoder.cc \
|
||||
arch/tids990/encoder.cc \
|
||||
arch/victor9k/decoder.cc \
|
||||
arch/victor9k/encoder.cc \
|
||||
arch/zilogmcz/decoder.cc \
|
||||
arch/tids990/decoder.cc \
|
||||
arch/tids990/encoder.cc \
|
||||
arch/micropolis/decoder.cc \
|
||||
arch/micropolis/encoder.cc \
|
||||
arch/northstar/decoder.cc \
|
||||
arch/northstar/encoder.cc \
|
||||
arch/agat/agat.cc \
|
||||
arch/agat/decoder.cc \
|
||||
|
||||
LIBARCH_OBJS = $(patsubst %.cc, $(OBJDIR)/%.o, $(LIBARCH_SRCS))
|
||||
OBJS += $(LIBARCH_OBJS)
|
||||
|
||||
@@ -147,6 +147,7 @@ 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)
|
||||
@@ -206,6 +207,18 @@ 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:
|
||||
|
||||
@@ -16,14 +16,14 @@ static bool lastBit;
|
||||
static double clockRateUsForTrack(unsigned track)
|
||||
{
|
||||
if (track < 16)
|
||||
return 2.623;
|
||||
return 2.63;
|
||||
if (track < 32)
|
||||
return 2.861;
|
||||
return 2.89;
|
||||
if (track < 48)
|
||||
return 3.148;
|
||||
return 3.20;
|
||||
if (track < 64)
|
||||
return 3.497;
|
||||
return 3.934;
|
||||
return 3.57;
|
||||
return 3.98;
|
||||
}
|
||||
|
||||
static unsigned sectorsForTrack(unsigned track)
|
||||
|
||||
154
arch/smaky6/decoder.cc
Normal file
@@ -0,0 +1,154 @@
|
||||
#include "globals.h"
|
||||
#include "fluxmap.h"
|
||||
#include "decoders/fluxmapreader.h"
|
||||
#include "protocol.h"
|
||||
#include "decoders/decoders.h"
|
||||
#include "sector.h"
|
||||
#include "smaky6.h"
|
||||
#include "bytes.h"
|
||||
#include "crc.h"
|
||||
#include "fmt/format.h"
|
||||
#include "lib/decoders/decoders.pb.h"
|
||||
#include <string.h>
|
||||
#include <algorithm>
|
||||
|
||||
static const FluxPattern SECTOR_PATTERN(32, 0x54892aaa);
|
||||
|
||||
class Smaky6Decoder : public Decoder
|
||||
{
|
||||
public:
|
||||
Smaky6Decoder(const DecoderProto& config):
|
||||
Decoder(config),
|
||||
_config(config.smaky6())
|
||||
{
|
||||
}
|
||||
|
||||
private:
|
||||
/* Returns the sector ID of the _current_ sector. */
|
||||
int advanceToNextSector()
|
||||
{
|
||||
auto previous = tell();
|
||||
seekToIndexMark();
|
||||
auto now = tell();
|
||||
if ((now.ns() - previous.ns()) < 9e6)
|
||||
{
|
||||
seekToIndexMark();
|
||||
auto next = tell();
|
||||
if ((next.ns() - now.ns()) < 9e6)
|
||||
{
|
||||
/* We just found sector 0. */
|
||||
|
||||
_sectorId = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Spurious... */
|
||||
|
||||
seek(now);
|
||||
}
|
||||
}
|
||||
|
||||
return _sectorId++;
|
||||
}
|
||||
|
||||
public:
|
||||
void beginTrack() override
|
||||
{
|
||||
/* Find the start-of-track index marks, which will be an interval
|
||||
* of about 6ms. */
|
||||
|
||||
seekToIndexMark();
|
||||
_sectorId = 99;
|
||||
for (;;)
|
||||
{
|
||||
auto pos = tell();
|
||||
advanceToNextSector();
|
||||
if (_sectorId < 99)
|
||||
{
|
||||
seek(pos);
|
||||
break;
|
||||
}
|
||||
|
||||
if (eof())
|
||||
return;
|
||||
}
|
||||
|
||||
/* Now we know where to start counting, start finding sectors. */
|
||||
|
||||
_sectorStarts.clear();
|
||||
for (;;)
|
||||
{
|
||||
auto now = tell();
|
||||
if (eof())
|
||||
break;
|
||||
|
||||
int id = advanceToNextSector();
|
||||
if (id < 16)
|
||||
_sectorStarts.push_back(std::make_pair(id, now));
|
||||
}
|
||||
|
||||
_sectorIndex = 0;
|
||||
}
|
||||
|
||||
nanoseconds_t advanceToNextRecord() override
|
||||
{
|
||||
if (_sectorIndex == _sectorStarts.size())
|
||||
{
|
||||
seekToIndexMark();
|
||||
return 0;
|
||||
}
|
||||
|
||||
const auto& p = _sectorStarts[_sectorIndex++];
|
||||
_sectorId = p.first;
|
||||
seek(p.second);
|
||||
|
||||
nanoseconds_t clock = seekToPattern(SECTOR_PATTERN);
|
||||
_sector->headerStartTime = tell().ns();
|
||||
|
||||
return clock;
|
||||
}
|
||||
|
||||
void decodeSectorRecord() override
|
||||
{
|
||||
readRawBits(33);
|
||||
const auto& rawbits = readRawBits(SMAKY6_RECORD_SIZE * 16);
|
||||
if (rawbits.size() < SMAKY6_SECTOR_SIZE)
|
||||
return;
|
||||
const auto& rawbytes =
|
||||
toBytes(rawbits).slice(0, SMAKY6_RECORD_SIZE * 16);
|
||||
|
||||
/* The Smaky bytes are stored backwards! Backwards! */
|
||||
|
||||
const auto& bytes =
|
||||
decodeFmMfm(rawbits).slice(0, SMAKY6_RECORD_SIZE).reverseBits();
|
||||
ByteReader br(bytes);
|
||||
|
||||
uint8_t track = br.read_8();
|
||||
Bytes data = br.read(SMAKY6_SECTOR_SIZE);
|
||||
uint8_t wantedChecksum = br.read_8();
|
||||
uint8_t gotChecksum = sumBytes(data) & 0xff;
|
||||
|
||||
if (track != _sector->physicalTrack)
|
||||
return;
|
||||
|
||||
_sector->logicalTrack = _sector->physicalTrack;
|
||||
_sector->logicalSide = _sector->physicalSide;
|
||||
_sector->logicalSector = _sectorId;
|
||||
|
||||
_sector->data = data;
|
||||
_sector->status =
|
||||
(wantedChecksum == gotChecksum) ? Sector::OK : Sector::BAD_CHECKSUM;
|
||||
}
|
||||
|
||||
private:
|
||||
const Smaky6DecoderProto& _config;
|
||||
nanoseconds_t _startOfTrack;
|
||||
std::vector<std::pair<int, Fluxmap::Position>> _sectorStarts;
|
||||
int _sectorId;
|
||||
int _sectorIndex;
|
||||
};
|
||||
|
||||
std::unique_ptr<Decoder> createSmaky6Decoder(const DecoderProto& config)
|
||||
{
|
||||
return std::unique_ptr<Decoder>(new Smaky6Decoder(config));
|
||||
}
|
||||
10
arch/smaky6/smaky6.h
Normal file
@@ -0,0 +1,10 @@
|
||||
#ifndef SMAKY6_H
|
||||
#define SMAKY6_H
|
||||
|
||||
#define SMAKY6_SECTOR_SIZE 256
|
||||
#define SMAKY6_RECORD_SIZE (1 + SMAKY6_SECTOR_SIZE + 1)
|
||||
|
||||
extern std::unique_ptr<Decoder> createSmaky6Decoder(const DecoderProto& config);
|
||||
|
||||
#endif
|
||||
|
||||
6
arch/smaky6/smaky6.proto
Normal file
@@ -0,0 +1,6 @@
|
||||
syntax = "proto2";
|
||||
|
||||
import "lib/common.proto";
|
||||
|
||||
message Smaky6DecoderProto {}
|
||||
|
||||
@@ -30,6 +30,34 @@ 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
|
||||
-------------
|
||||
@@ -37,35 +65,25 @@ Reading discs
|
||||
Just do:
|
||||
|
||||
```
|
||||
fluxengine read apple2
|
||||
fluxengine read appleii140
|
||||
```
|
||||
|
||||
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:
|
||||
(or `appleii640`)
|
||||
|
||||
```
|
||||
fluxengine read apple2 --prodos
|
||||
```
|
||||
|
||||
You will also need this for filesystem access.
|
||||
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.
|
||||
|
||||
Writing discs
|
||||
-------------
|
||||
|
||||
Just do:
|
||||
```
|
||||
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
|
||||
fluxengine write appleii140 -i appleii140.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
|
||||
-----------------
|
||||
|
||||
43
doc/disk-smaky6.md
Normal file
@@ -0,0 +1,43 @@
|
||||
Disk: Smaky 6
|
||||
=============
|
||||
|
||||
The Smaky 6 is a Swiss computer from 1978 produced by Epsitec. It's based
|
||||
around a Z80 processor and has one or two Micropolis 5.25" drives which use
|
||||
16-sector hard sectored disks. The disk format is single-sided with 77 tracks
|
||||
and 256-byte sectors, resulting in 308kB disks. It uses MFM with a custom
|
||||
sector record scheme. It was later superceded by a 68000-based Smaky which used
|
||||
different disks.
|
||||
|
||||
FluxEngine supports these, although because the Micropolis drives use a 100tpi
|
||||
track pitch, you can't read Smaky 6 disks with a normal PC 96tpi drive. You
|
||||
will have to find a 100tpi drive from somewhere (they're rare).
|
||||
|
||||
Reading disks
|
||||
-------------
|
||||
|
||||
You must use a 100-tpi 80-track 5.25" floppy drive.
|
||||
|
||||
To read a Smaky 6 floppy, do:
|
||||
|
||||
```
|
||||
fluxengine read smaky6
|
||||
```
|
||||
|
||||
You should end up with a `smaky6.img` file.
|
||||
|
||||
|
||||
Filesystem access
|
||||
-----------------
|
||||
|
||||
There is experimental read-only support for the Smaky 6 filesystem, allowing
|
||||
the directory to be listed and files read from disks. It's not known whether
|
||||
this is completely correct, so don't trust it! See the [Filesystem
|
||||
Access](filesystem.md) page for more information.
|
||||
|
||||
|
||||
Useful references
|
||||
-----------------
|
||||
|
||||
- [Smaky Info, 1978-2002 (in French)](https://www.smaky.ch/theme.php?id=sminfo)
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@ The following file systems are supported so far.
|
||||
| FatFS (a.k.a. MS-DOS) | Y | Y | FAT12, FAT16, FAT32; not Atari (AFAIK!) |
|
||||
| Macintosh HFS | Y | Y | Only AppleDouble files may be written |
|
||||
| Apple ProDOS | Y | | |
|
||||
| Smaky 6 | Y | | |
|
||||
{: .datatable }
|
||||
|
||||
Please not that Atari disks do _not_ use standard FatFS, and the library I'm
|
||||
@@ -82,7 +83,7 @@ default; for example, Macintosh HFS filesystems are common on 3.5" floppies. You
|
||||
can do this as follows:
|
||||
|
||||
```
|
||||
fluxengine format ibm1440 -f drive:1 --filesystem.machfs=
|
||||
fluxengine format ibm1440 -f drive:1 --filesystem.type=MACHFS
|
||||
```
|
||||
|
||||
Some filesystems won't work on some disks --- don't try this with Amiga FFS, for
|
||||
|
||||
BIN
doc/ju475-hd-hi.png
Normal file
|
After Width: | Height: | Size: 39 KiB |
BIN
doc/ju475-hd-lo.png
Normal file
|
After Width: | Height: | Size: 33 KiB |
13
doc/using.md
@@ -155,6 +155,14 @@ 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
|
||||
@@ -213,6 +221,11 @@ FluxEngine supports a number of ways to get or put flux. When using the `-s` or
|
||||
Read from a Kryoflux stream, where `<path>` is the directory containing the
|
||||
stream files. **Read only.**
|
||||
|
||||
- `flx:<directory>`
|
||||
|
||||
Read from a FLUXCOPY stream, where `<path>` is the directory containing the
|
||||
stream files. **Read only.**
|
||||
|
||||
- `erase:`
|
||||
|
||||
Read nothing --- writing this to a disk will magnetically erase a track.
|
||||
|
||||
41
extras/FluxEngine.app.template/Contents/Info.plist
Normal file
@@ -0,0 +1,41 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>English</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>FluxEngine.sh</string>
|
||||
<key>CFBundleGetInfoString</key>
|
||||
<string>FluxEngine 1.0</string>
|
||||
<key>CFBundleIconFile</key>
|
||||
<string>FluxEngine.icns</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>com.cowlark.fluxengine.gui</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleLocalizations</key>
|
||||
<array>
|
||||
<string>en</string>
|
||||
</array>
|
||||
<key>CFBundleName</key>
|
||||
<string>FluxEngine</string>
|
||||
<key>CFBundleDisplayName</key>
|
||||
<string>FluxEngine</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.0</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>FluxEngine</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1.0</string>
|
||||
<key>LSMinimumSystemVersion</key>
|
||||
<string>10.13.0</string>
|
||||
<key>LSRequiresCarbon</key>
|
||||
<true/>
|
||||
<key>NSAppleScriptEnabled</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
5
extras/FluxEngine.app.template/Contents/MacOS/FluxEngine.sh
Executable file
@@ -0,0 +1,5 @@
|
||||
#!/bin/sh
|
||||
dir=`dirname "$0"`
|
||||
cd "$dir"
|
||||
export DYLD_FALLBACK_LIBRARY_PATH=../Resources:/opt/local/lib
|
||||
exec ./fluxengine-gui "$@"
|
||||
BIN
extras/fluxfile.piko
Normal file
|
After Width: | Height: | Size: 2.0 KiB |
BIN
extras/fluxfile.png
Normal file
|
After Width: | Height: | Size: 365 B |
BIN
extras/hardware.piko
Normal file
|
After Width: | Height: | Size: 2.2 KiB |
BIN
extras/hardware.png
Normal file
|
After Width: | Height: | Size: 428 B |
BIN
extras/imagefile.piko
Normal file
|
After Width: | Height: | Size: 2.6 KiB |
BIN
extras/imagefile.png
Normal file
|
After Width: | Height: | Size: 327 B |
@@ -9,6 +9,7 @@ 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 \
|
||||
@@ -22,6 +23,8 @@ LIBFLUXENGINE_SRCS = \
|
||||
lib/fluxsource/erasefluxsource.cc \
|
||||
lib/fluxsource/fl2fluxsource.cc \
|
||||
lib/fluxsource/fluxsource.cc \
|
||||
lib/fluxsource/flx.cc \
|
||||
lib/fluxsource/flxfluxsource.cc \
|
||||
lib/fluxsource/hardwarefluxsource.cc \
|
||||
lib/fluxsource/kryoflux.cc \
|
||||
lib/fluxsource/kryofluxfluxsource.cc \
|
||||
@@ -67,6 +70,7 @@ 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 \
|
||||
@@ -74,6 +78,8 @@ 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 \
|
||||
|
||||
28
lib/bytes.cc
@@ -205,6 +205,34 @@ 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)
|
||||
|
||||
@@ -62,6 +62,10 @@ 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();
|
||||
|
||||
@@ -10,6 +10,7 @@ message RangeProto {
|
||||
|
||||
extend google.protobuf.FieldOptions {
|
||||
optional string help = 50000;
|
||||
optional bool recurse = 50001 [default = true];
|
||||
}
|
||||
|
||||
enum IndexMode {
|
||||
|
||||
@@ -13,37 +13,44 @@ 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 FluxSourceProto flux_source = 10;
|
||||
optional FluxSinkProto flux_sink = 11;
|
||||
optional DriveProto drive = 15;
|
||||
optional ImageReaderProto image_reader = 12;
|
||||
optional ImageWriterProto image_writer = 9;
|
||||
|
||||
optional EncoderProto encoder = 3;
|
||||
optional DecoderProto decoder = 4;
|
||||
optional UsbProto usb = 5;
|
||||
optional FluxSourceProto flux_source = 10;
|
||||
optional FluxSinkProto flux_sink = 11;
|
||||
optional DriveProto drive = 15;
|
||||
|
||||
optional RangeProto tracks = 6;
|
||||
optional RangeProto heads = 7;
|
||||
optional int32 tpi = 16 [ (help) = "TPI of image; if 0, use TPI of drive" ];
|
||||
optional EncoderProto encoder = 3;
|
||||
optional DecoderProto decoder = 4;
|
||||
optional UsbProto usb = 5;
|
||||
|
||||
optional FilesystemProto filesystem = 17;
|
||||
|
||||
repeated OptionProto option = 20;
|
||||
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;
|
||||
}
|
||||
|
||||
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" ];
|
||||
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 ];
|
||||
}
|
||||
|
||||
|
||||
@@ -1,223 +1,226 @@
|
||||
#include "globals.h"
|
||||
#include "flags.h"
|
||||
#include "fluxmap.h"
|
||||
#include "decoders/decoders.h"
|
||||
#include "encoders/encoders.h"
|
||||
#include "arch/agat/agat.h"
|
||||
#include "arch/aeslanier/aeslanier.h"
|
||||
#include "arch/amiga/amiga.h"
|
||||
#include "arch/apple2/apple2.h"
|
||||
#include "arch/brother/brother.h"
|
||||
#include "arch/c64/c64.h"
|
||||
#include "arch/f85/f85.h"
|
||||
#include "arch/fb100/fb100.h"
|
||||
#include "arch/ibm/ibm.h"
|
||||
#include "arch/macintosh/macintosh.h"
|
||||
#include "arch/micropolis/micropolis.h"
|
||||
#include "arch/mx/mx.h"
|
||||
#include "arch/northstar/northstar.h"
|
||||
#include "arch/tids990/tids990.h"
|
||||
#include "arch/victor9k/victor9k.h"
|
||||
#include "arch/zilogmcz/zilogmcz.h"
|
||||
#include "decoders/fluxmapreader.h"
|
||||
#include "flux.h"
|
||||
#include "protocol.h"
|
||||
#include "decoders/rawbits.h"
|
||||
#include "sector.h"
|
||||
#include "image.h"
|
||||
#include "lib/decoders/decoders.pb.h"
|
||||
#include "lib/layout.h"
|
||||
#include "fmt/format.h"
|
||||
#include <numeric>
|
||||
|
||||
std::unique_ptr<Decoder> Decoder::create(const DecoderProto& config)
|
||||
{
|
||||
static const std::map<int,
|
||||
std::function<std::unique_ptr<Decoder>(const DecoderProto&)>>
|
||||
decoders = {
|
||||
{DecoderProto::kAgat, createAgatDecoder },
|
||||
{DecoderProto::kAeslanier, createAesLanierDecoder },
|
||||
{DecoderProto::kAmiga, createAmigaDecoder },
|
||||
{DecoderProto::kApple2, createApple2Decoder },
|
||||
{DecoderProto::kBrother, createBrotherDecoder },
|
||||
{DecoderProto::kC64, createCommodore64Decoder},
|
||||
{DecoderProto::kF85, createDurangoF85Decoder },
|
||||
{DecoderProto::kFb100, createFb100Decoder },
|
||||
{DecoderProto::kIbm, createIbmDecoder },
|
||||
{DecoderProto::kMacintosh, createMacintoshDecoder },
|
||||
{DecoderProto::kMicropolis, createMicropolisDecoder },
|
||||
{DecoderProto::kMx, createMxDecoder },
|
||||
{DecoderProto::kNorthstar, createNorthstarDecoder },
|
||||
{DecoderProto::kTids990, createTids990Decoder },
|
||||
{DecoderProto::kVictor9K, createVictor9kDecoder },
|
||||
{DecoderProto::kZilogmcz, createZilogMczDecoder },
|
||||
};
|
||||
|
||||
auto decoder = decoders.find(config.format_case());
|
||||
if (decoder == decoders.end())
|
||||
Error() << "no decoder specified";
|
||||
|
||||
return (decoder->second)(config);
|
||||
}
|
||||
|
||||
std::shared_ptr<TrackDataFlux> Decoder::decodeToSectors(
|
||||
std::shared_ptr<const Fluxmap> fluxmap,
|
||||
std::shared_ptr<const TrackInfo>& trackInfo)
|
||||
{
|
||||
_trackdata = std::make_shared<TrackDataFlux>();
|
||||
_trackdata->fluxmap = fluxmap;
|
||||
_trackdata->trackInfo = trackInfo;
|
||||
|
||||
FluxmapReader fmr(*fluxmap);
|
||||
_fmr = &fmr;
|
||||
|
||||
auto newSector = [&]
|
||||
{
|
||||
_sector = std::make_shared<Sector>(trackInfo, 0);
|
||||
_sector->status = Sector::MISSING;
|
||||
};
|
||||
|
||||
newSector();
|
||||
beginTrack();
|
||||
for (;;)
|
||||
{
|
||||
newSector();
|
||||
|
||||
Fluxmap::Position recordStart = fmr.tell();
|
||||
_sector->clock = advanceToNextRecord();
|
||||
if (fmr.eof() || !_sector->clock)
|
||||
return _trackdata;
|
||||
|
||||
/* Read the sector record. */
|
||||
|
||||
Fluxmap::Position before = fmr.tell();
|
||||
decodeSectorRecord();
|
||||
Fluxmap::Position after = fmr.tell();
|
||||
pushRecord(before, after);
|
||||
|
||||
if (_sector->status != Sector::DATA_MISSING)
|
||||
{
|
||||
_sector->position = before.bytes;
|
||||
_sector->dataStartTime = before.ns();
|
||||
_sector->dataEndTime = after.ns();
|
||||
}
|
||||
else
|
||||
{
|
||||
/* The data is in a separate record. */
|
||||
|
||||
for (;;)
|
||||
{
|
||||
_sector->headerStartTime = before.ns();
|
||||
_sector->headerEndTime = after.ns();
|
||||
|
||||
_sector->clock = advanceToNextRecord();
|
||||
if (fmr.eof() || !_sector->clock)
|
||||
break;
|
||||
|
||||
before = fmr.tell();
|
||||
decodeDataRecord();
|
||||
after = fmr.tell();
|
||||
|
||||
if (_sector->status != Sector::DATA_MISSING)
|
||||
{
|
||||
_sector->position = before.bytes;
|
||||
_sector->dataStartTime = before.ns();
|
||||
_sector->dataEndTime = after.ns();
|
||||
pushRecord(before, after);
|
||||
break;
|
||||
}
|
||||
|
||||
fmr.skipToEvent(F_BIT_PULSE);
|
||||
resetFluxDecoder();
|
||||
}
|
||||
}
|
||||
|
||||
if (_sector->status != Sector::MISSING)
|
||||
{
|
||||
auto trackLayout = Layout::getLayoutOfTrack(
|
||||
_sector->logicalTrack, _sector->logicalSide);
|
||||
_trackdata->sectors.push_back(_sector);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Decoder::pushRecord(
|
||||
const Fluxmap::Position& start, const Fluxmap::Position& end)
|
||||
{
|
||||
Fluxmap::Position here = _fmr->tell();
|
||||
|
||||
auto record = std::make_shared<Record>();
|
||||
_trackdata->records.push_back(record);
|
||||
_sector->records.push_back(record);
|
||||
|
||||
record->startTime = start.ns();
|
||||
record->endTime = end.ns();
|
||||
record->clock = _sector->clock;
|
||||
|
||||
record->rawData = toBytes(_recordBits);
|
||||
_recordBits.clear();
|
||||
}
|
||||
|
||||
void Decoder::resetFluxDecoder()
|
||||
{
|
||||
_decoder.reset(new FluxDecoder(_fmr, _sector->clock, _config));
|
||||
}
|
||||
|
||||
nanoseconds_t Decoder::seekToPattern(const FluxMatcher& pattern)
|
||||
{
|
||||
nanoseconds_t clock = _fmr->seekToPattern(pattern);
|
||||
_decoder.reset(new FluxDecoder(_fmr, clock, _config));
|
||||
return clock;
|
||||
}
|
||||
|
||||
void Decoder::seekToIndexMark()
|
||||
{
|
||||
_fmr->skipToEvent(F_BIT_PULSE);
|
||||
_fmr->seekToIndexMark();
|
||||
}
|
||||
|
||||
std::vector<bool> Decoder::readRawBits(unsigned count)
|
||||
{
|
||||
auto bits = _decoder->readBits(count);
|
||||
_recordBits.insert(_recordBits.end(), bits.begin(), bits.end());
|
||||
return bits;
|
||||
}
|
||||
|
||||
uint8_t Decoder::readRaw8()
|
||||
{
|
||||
return toBytes(readRawBits(8)).reader().read_8();
|
||||
}
|
||||
|
||||
uint16_t Decoder::readRaw16()
|
||||
{
|
||||
return toBytes(readRawBits(16)).reader().read_be16();
|
||||
}
|
||||
|
||||
uint32_t Decoder::readRaw20()
|
||||
{
|
||||
std::vector<bool> bits(4);
|
||||
for (bool b : readRawBits(20))
|
||||
bits.push_back(b);
|
||||
|
||||
return toBytes(bits).reader().read_be24();
|
||||
}
|
||||
|
||||
uint32_t Decoder::readRaw24()
|
||||
{
|
||||
return toBytes(readRawBits(24)).reader().read_be24();
|
||||
}
|
||||
|
||||
uint32_t Decoder::readRaw32()
|
||||
{
|
||||
return toBytes(readRawBits(32)).reader().read_be32();
|
||||
}
|
||||
|
||||
uint64_t Decoder::readRaw48()
|
||||
{
|
||||
return toBytes(readRawBits(48)).reader().read_be48();
|
||||
}
|
||||
|
||||
uint64_t Decoder::readRaw64()
|
||||
{
|
||||
return toBytes(readRawBits(64)).reader().read_be64();
|
||||
}
|
||||
#include "globals.h"
|
||||
#include "flags.h"
|
||||
#include "fluxmap.h"
|
||||
#include "decoders/decoders.h"
|
||||
#include "encoders/encoders.h"
|
||||
#include "arch/agat/agat.h"
|
||||
#include "arch/aeslanier/aeslanier.h"
|
||||
#include "arch/amiga/amiga.h"
|
||||
#include "arch/apple2/apple2.h"
|
||||
#include "arch/brother/brother.h"
|
||||
#include "arch/c64/c64.h"
|
||||
#include "arch/f85/f85.h"
|
||||
#include "arch/fb100/fb100.h"
|
||||
#include "arch/ibm/ibm.h"
|
||||
#include "arch/macintosh/macintosh.h"
|
||||
#include "arch/micropolis/micropolis.h"
|
||||
#include "arch/mx/mx.h"
|
||||
#include "arch/northstar/northstar.h"
|
||||
#include "arch/smaky6/smaky6.h"
|
||||
#include "arch/tids990/tids990.h"
|
||||
#include "arch/victor9k/victor9k.h"
|
||||
#include "arch/zilogmcz/zilogmcz.h"
|
||||
#include "decoders/fluxmapreader.h"
|
||||
#include "flux.h"
|
||||
#include "protocol.h"
|
||||
#include "decoders/rawbits.h"
|
||||
#include "sector.h"
|
||||
#include "image.h"
|
||||
#include "lib/decoders/decoders.pb.h"
|
||||
#include "lib/layout.h"
|
||||
#include "fmt/format.h"
|
||||
#include <numeric>
|
||||
|
||||
std::unique_ptr<Decoder> Decoder::create(const DecoderProto& config)
|
||||
{
|
||||
static const std::map<int,
|
||||
std::function<std::unique_ptr<Decoder>(const DecoderProto&)>>
|
||||
decoders = {
|
||||
{DecoderProto::kAgat, createAgatDecoder },
|
||||
{DecoderProto::kAeslanier, createAesLanierDecoder },
|
||||
{DecoderProto::kAmiga, createAmigaDecoder },
|
||||
{DecoderProto::kApple2, createApple2Decoder },
|
||||
{DecoderProto::kBrother, createBrotherDecoder },
|
||||
{DecoderProto::kC64, createCommodore64Decoder},
|
||||
{DecoderProto::kF85, createDurangoF85Decoder },
|
||||
{DecoderProto::kFb100, createFb100Decoder },
|
||||
{DecoderProto::kIbm, createIbmDecoder },
|
||||
{DecoderProto::kMacintosh, createMacintoshDecoder },
|
||||
{DecoderProto::kMicropolis, createMicropolisDecoder },
|
||||
{DecoderProto::kMx, createMxDecoder },
|
||||
{DecoderProto::kNorthstar, createNorthstarDecoder },
|
||||
{DecoderProto::kSmaky6, createSmaky6Decoder },
|
||||
{DecoderProto::kTids990, createTids990Decoder },
|
||||
{DecoderProto::kVictor9K, createVictor9kDecoder },
|
||||
{DecoderProto::kZilogmcz, createZilogMczDecoder },
|
||||
};
|
||||
|
||||
auto decoder = decoders.find(config.format_case());
|
||||
if (decoder == decoders.end())
|
||||
Error() << "no decoder specified";
|
||||
|
||||
return (decoder->second)(config);
|
||||
}
|
||||
|
||||
std::shared_ptr<TrackDataFlux> Decoder::decodeToSectors(
|
||||
std::shared_ptr<const Fluxmap> fluxmap,
|
||||
std::shared_ptr<const TrackInfo>& trackInfo)
|
||||
{
|
||||
_trackdata = std::make_shared<TrackDataFlux>();
|
||||
_trackdata->fluxmap = fluxmap;
|
||||
_trackdata->trackInfo = trackInfo;
|
||||
|
||||
FluxmapReader fmr(*fluxmap);
|
||||
_fmr = &fmr;
|
||||
|
||||
auto newSector = [&]
|
||||
{
|
||||
_sector = std::make_shared<Sector>(trackInfo, 0);
|
||||
_sector->status = Sector::MISSING;
|
||||
};
|
||||
|
||||
newSector();
|
||||
beginTrack();
|
||||
for (;;)
|
||||
{
|
||||
newSector();
|
||||
|
||||
Fluxmap::Position recordStart = fmr.tell();
|
||||
_sector->clock = advanceToNextRecord();
|
||||
if (fmr.eof() || !_sector->clock)
|
||||
return _trackdata;
|
||||
|
||||
/* Read the sector record. */
|
||||
|
||||
Fluxmap::Position before = fmr.tell();
|
||||
decodeSectorRecord();
|
||||
Fluxmap::Position after = fmr.tell();
|
||||
pushRecord(before, after);
|
||||
|
||||
if (_sector->status != Sector::DATA_MISSING)
|
||||
{
|
||||
_sector->position = before.bytes;
|
||||
_sector->dataStartTime = before.ns();
|
||||
_sector->dataEndTime = after.ns();
|
||||
}
|
||||
else
|
||||
{
|
||||
/* The data is in a separate record. */
|
||||
|
||||
for (;;)
|
||||
{
|
||||
_sector->headerStartTime = before.ns();
|
||||
_sector->headerEndTime = after.ns();
|
||||
|
||||
_sector->clock = advanceToNextRecord();
|
||||
if (fmr.eof() || !_sector->clock)
|
||||
break;
|
||||
|
||||
before = fmr.tell();
|
||||
decodeDataRecord();
|
||||
after = fmr.tell();
|
||||
|
||||
if (_sector->status != Sector::DATA_MISSING)
|
||||
{
|
||||
_sector->position = before.bytes;
|
||||
_sector->dataStartTime = before.ns();
|
||||
_sector->dataEndTime = after.ns();
|
||||
pushRecord(before, after);
|
||||
break;
|
||||
}
|
||||
|
||||
fmr.skipToEvent(F_BIT_PULSE);
|
||||
resetFluxDecoder();
|
||||
}
|
||||
}
|
||||
|
||||
if (_sector->status != Sector::MISSING)
|
||||
{
|
||||
auto trackLayout = Layout::getLayoutOfTrack(
|
||||
_sector->logicalTrack, _sector->logicalSide);
|
||||
_trackdata->sectors.push_back(_sector);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Decoder::pushRecord(
|
||||
const Fluxmap::Position& start, const Fluxmap::Position& end)
|
||||
{
|
||||
Fluxmap::Position here = _fmr->tell();
|
||||
|
||||
auto record = std::make_shared<Record>();
|
||||
_trackdata->records.push_back(record);
|
||||
_sector->records.push_back(record);
|
||||
|
||||
record->position = start.bytes;
|
||||
record->startTime = start.ns();
|
||||
record->endTime = end.ns();
|
||||
record->clock = _sector->clock;
|
||||
|
||||
record->rawData = toBytes(_recordBits);
|
||||
_recordBits.clear();
|
||||
}
|
||||
|
||||
void Decoder::resetFluxDecoder()
|
||||
{
|
||||
_decoder.reset(new FluxDecoder(_fmr, _sector->clock, _config));
|
||||
}
|
||||
|
||||
nanoseconds_t Decoder::seekToPattern(const FluxMatcher& pattern)
|
||||
{
|
||||
nanoseconds_t clock = _fmr->seekToPattern(pattern);
|
||||
_decoder.reset(new FluxDecoder(_fmr, clock, _config));
|
||||
return clock;
|
||||
}
|
||||
|
||||
void Decoder::seekToIndexMark()
|
||||
{
|
||||
_fmr->skipToEvent(F_BIT_PULSE);
|
||||
_fmr->seekToIndexMark();
|
||||
}
|
||||
|
||||
std::vector<bool> Decoder::readRawBits(unsigned count)
|
||||
{
|
||||
auto bits = _decoder->readBits(count);
|
||||
_recordBits.insert(_recordBits.end(), bits.begin(), bits.end());
|
||||
return bits;
|
||||
}
|
||||
|
||||
uint8_t Decoder::readRaw8()
|
||||
{
|
||||
return toBytes(readRawBits(8)).reader().read_8();
|
||||
}
|
||||
|
||||
uint16_t Decoder::readRaw16()
|
||||
{
|
||||
return toBytes(readRawBits(16)).reader().read_be16();
|
||||
}
|
||||
|
||||
uint32_t Decoder::readRaw20()
|
||||
{
|
||||
std::vector<bool> bits(4);
|
||||
for (bool b : readRawBits(20))
|
||||
bits.push_back(b);
|
||||
|
||||
return toBytes(bits).reader().read_be24();
|
||||
}
|
||||
|
||||
uint32_t Decoder::readRaw24()
|
||||
{
|
||||
return toBytes(readRawBits(24)).reader().read_be24();
|
||||
}
|
||||
|
||||
uint32_t Decoder::readRaw32()
|
||||
{
|
||||
return toBytes(readRawBits(32)).reader().read_be32();
|
||||
}
|
||||
|
||||
uint64_t Decoder::readRaw48()
|
||||
{
|
||||
return toBytes(readRawBits(48)).reader().read_be48();
|
||||
}
|
||||
|
||||
uint64_t Decoder::readRaw64()
|
||||
{
|
||||
return toBytes(readRawBits(64)).reader().read_be64();
|
||||
}
|
||||
|
||||
@@ -71,6 +71,11 @@ public:
|
||||
return _fmr->tell();
|
||||
}
|
||||
|
||||
void rewind()
|
||||
{
|
||||
_fmr->rewind();
|
||||
}
|
||||
|
||||
void seek(const Fluxmap::Position& pos)
|
||||
{
|
||||
return _fmr->seek(pos);
|
||||
|
||||
@@ -13,13 +13,14 @@ import "arch/macintosh/macintosh.proto";
|
||||
import "arch/micropolis/micropolis.proto";
|
||||
import "arch/mx/mx.proto";
|
||||
import "arch/northstar/northstar.proto";
|
||||
import "arch/smaky6/smaky6.proto";
|
||||
import "arch/tids990/tids990.proto";
|
||||
import "arch/victor9k/victor9k.proto";
|
||||
import "arch/zilogmcz/zilogmcz.proto";
|
||||
import "lib/fluxsink/fluxsink.proto";
|
||||
import "lib/common.proto";
|
||||
|
||||
//NEXT: 30
|
||||
//NEXT: 31
|
||||
message DecoderProto {
|
||||
optional double pulse_debounce_threshold = 1 [default = 0.30,
|
||||
(help) = "ignore pulses with intervals shorter than this, in fractions of a clock"];
|
||||
@@ -46,6 +47,7 @@ message DecoderProto {
|
||||
MicropolisDecoderProto micropolis = 14;
|
||||
MxDecoderProto mx = 15;
|
||||
NorthstarDecoderProto northstar = 24;
|
||||
Smaky6DecoderProto smaky6 = 30;
|
||||
Tids990DecoderProto tids990 = 16;
|
||||
Victor9kDecoderProto victor9k = 17;
|
||||
ZilogMczDecoderProto zilogmcz = 18;
|
||||
|
||||
@@ -202,6 +202,23 @@ void FluxmapReader::seek(nanoseconds_t ns)
|
||||
_pos.zeroes = 0;
|
||||
}
|
||||
|
||||
void FluxmapReader::seekToByte(unsigned b)
|
||||
{
|
||||
if (b < _pos.bytes)
|
||||
{
|
||||
_pos.ticks = 0;
|
||||
_pos.bytes = 0;
|
||||
}
|
||||
|
||||
while (!eof() && (_pos.bytes < b))
|
||||
{
|
||||
int e;
|
||||
unsigned t;
|
||||
getNextEvent(e, t);
|
||||
}
|
||||
_pos.zeroes = 0;
|
||||
}
|
||||
|
||||
nanoseconds_t FluxmapReader::seekToPattern(const FluxMatcher& pattern)
|
||||
{
|
||||
const FluxMatcher* unused;
|
||||
|
||||
@@ -100,6 +100,7 @@ public:
|
||||
|
||||
/* Important! You can only reliably seek to 1 bits. */
|
||||
void seek(nanoseconds_t ns);
|
||||
void seekToByte(unsigned byte);
|
||||
|
||||
void seekToIndexMark();
|
||||
nanoseconds_t seekToPattern(const FluxMatcher& pattern);
|
||||
|
||||
@@ -35,6 +35,15 @@ 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
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#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"
|
||||
@@ -25,6 +26,7 @@ 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},
|
||||
|
||||
@@ -6,6 +6,7 @@ class Fluxmap;
|
||||
class Image;
|
||||
class Layout;
|
||||
class Sector;
|
||||
class TrackInfo;
|
||||
|
||||
class Encoder
|
||||
{
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
syntax = "proto2";
|
||||
|
||||
import "arch/agat/agat.proto";
|
||||
import "arch/amiga/amiga.proto";
|
||||
import "arch/apple2/apple2.proto";
|
||||
import "arch/brother/brother.proto";
|
||||
@@ -25,5 +26,6 @@ message EncoderProto
|
||||
MicropolisEncoderProto micropolis = 10;
|
||||
Victor9kEncoderProto victor9k = 11;
|
||||
Apple2EncoderProto apple2 = 12;
|
||||
AgatEncoderProto agat = 13;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,8 +5,9 @@ static std::unique_ptr<std::set<LocalBase*>> variables;
|
||||
|
||||
void Environment::reset()
|
||||
{
|
||||
for (LocalBase* var : *variables)
|
||||
var->reset();
|
||||
if (variables)
|
||||
for (LocalBase* var : *variables)
|
||||
var->reset();
|
||||
}
|
||||
|
||||
void Environment::addVariable(LocalBase* local)
|
||||
|
||||
69
lib/fl2.cc
Normal file
@@ -0,0 +1,69 @@
|
||||
#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);
|
||||
}
|
||||
10
lib/fl2.h
Normal file
@@ -0,0 +1,10 @@
|
||||
#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
|
||||
|
||||
49
lib/flags.cc
@@ -55,31 +55,39 @@ static bool setFallbackFlag(
|
||||
}
|
||||
else
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
if (FlagGroup::applyOption(path))
|
||||
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)
|
||||
@@ -244,7 +252,6 @@ 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;
|
||||
|
||||
272
lib/flags.h
@@ -12,21 +12,42 @@ 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);
|
||||
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);
|
||||
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);
|
||||
|
||||
void addFlag(Flag* flag);
|
||||
void checkInitialised() const;
|
||||
|
||||
@@ -40,14 +61,25 @@ 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;
|
||||
@@ -62,15 +94,26 @@ 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;
|
||||
@@ -79,16 +122,30 @@ 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;
|
||||
@@ -98,72 +155,122 @@ 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;
|
||||
};
|
||||
@@ -171,26 +278,49 @@ 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);
|
||||
};
|
||||
|
||||
|
||||
28
lib/flux.h
@@ -10,32 +10,32 @@ class TrackInfo;
|
||||
|
||||
struct Record
|
||||
{
|
||||
nanoseconds_t clock = 0;
|
||||
nanoseconds_t startTime = 0;
|
||||
nanoseconds_t endTime = 0;
|
||||
Bytes rawData;
|
||||
nanoseconds_t clock = 0;
|
||||
nanoseconds_t startTime = 0;
|
||||
nanoseconds_t endTime = 0;
|
||||
uint32_t position = 0;
|
||||
Bytes rawData;
|
||||
};
|
||||
|
||||
struct TrackDataFlux
|
||||
{
|
||||
std::shared_ptr<const TrackInfo> trackInfo;
|
||||
std::shared_ptr<const Fluxmap> fluxmap;
|
||||
std::vector<std::shared_ptr<const Record>> records;
|
||||
std::vector<std::shared_ptr<const Sector>> sectors;
|
||||
std::shared_ptr<const TrackInfo> trackInfo;
|
||||
std::shared_ptr<const Fluxmap> fluxmap;
|
||||
std::vector<std::shared_ptr<const Record>> records;
|
||||
std::vector<std::shared_ptr<const Sector>> sectors;
|
||||
};
|
||||
|
||||
struct TrackFlux
|
||||
{
|
||||
std::shared_ptr<const TrackInfo> trackInfo;
|
||||
std::vector<std::shared_ptr<TrackDataFlux>> trackDatas;
|
||||
std::set<std::shared_ptr<const Sector>> sectors;
|
||||
std::shared_ptr<const TrackInfo> trackInfo;
|
||||
std::vector<std::shared_ptr<TrackDataFlux>> trackDatas;
|
||||
std::set<std::shared_ptr<const Sector>> sectors;
|
||||
};
|
||||
|
||||
struct DiskFlux
|
||||
{
|
||||
std::vector<std::shared_ptr<TrackFlux>> tracks;
|
||||
std::shared_ptr<const Image> image;
|
||||
std::vector<std::shared_ptr<TrackFlux>> tracks;
|
||||
std::shared_ptr<const Image> image;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
@@ -9,73 +9,69 @@
|
||||
#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),
|
||||
_of(_filename, std::ios::out | std::ios::binary)
|
||||
{
|
||||
if (!_of.is_open())
|
||||
Error() << "cannot open output file";
|
||||
}
|
||||
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()
|
||||
{
|
||||
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);
|
||||
}
|
||||
~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);
|
||||
}
|
||||
|
||||
if (!proto.SerializeToOstream(&_of))
|
||||
Error() << "unable to write output file";
|
||||
_of.close();
|
||||
if (_of.fail())
|
||||
Error() << "FL2 write I/O error: " << strerror(errno);
|
||||
}
|
||||
saveFl2File(_filename, proto);
|
||||
}
|
||||
|
||||
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::ofstream _of;
|
||||
std::map<std::pair<unsigned, unsigned>, std::vector<Bytes>> _data;
|
||||
std::string _filename;
|
||||
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));
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -9,55 +9,85 @@
|
||||
|
||||
std::unique_ptr<FluxSink> FluxSink::create(const FluxSinkProto& config)
|
||||
{
|
||||
switch (config.dest_case())
|
||||
{
|
||||
case FluxSinkProto::kDrive:
|
||||
return createHardwareFluxSink(config.drive());
|
||||
switch (config.type())
|
||||
{
|
||||
case FluxSinkProto::DRIVE:
|
||||
return createHardwareFluxSink(config.drive());
|
||||
|
||||
case FluxSinkProto::kA2R:
|
||||
return createA2RFluxSink(config.a2r());
|
||||
case FluxSinkProto::A2R:
|
||||
return createA2RFluxSink(config.a2r());
|
||||
|
||||
case FluxSinkProto::kAu:
|
||||
return createAuFluxSink(config.au());
|
||||
case FluxSinkProto::AU:
|
||||
return createAuFluxSink(config.au());
|
||||
|
||||
case FluxSinkProto::kVcd:
|
||||
return createVcdFluxSink(config.vcd());
|
||||
case FluxSinkProto::VCD:
|
||||
return createVcdFluxSink(config.vcd());
|
||||
|
||||
case FluxSinkProto::kScp:
|
||||
return createScpFluxSink(config.scp());
|
||||
case FluxSinkProto::SCP:
|
||||
return createScpFluxSink(config.scp());
|
||||
|
||||
case FluxSinkProto::kFl2:
|
||||
return createFl2FluxSink(config.fl2());
|
||||
case FluxSinkProto::FLUX:
|
||||
return createFl2FluxSink(config.fl2());
|
||||
|
||||
default:
|
||||
Error() << "bad output disk config";
|
||||
return std::unique_ptr<FluxSink>();
|
||||
}
|
||||
default:
|
||||
Error() << "bad output disk config";
|
||||
return std::unique_ptr<FluxSink>();
|
||||
}
|
||||
}
|
||||
|
||||
void FluxSink::updateConfigForFilename(FluxSinkProto* proto, const std::string& filename)
|
||||
void FluxSink::updateConfigForFilename(
|
||||
FluxSinkProto* proto, const std::string& filename)
|
||||
{
|
||||
static const std::vector<std::pair<std::regex, std::function<void(const std::string&, FluxSinkProto*)>>> formats =
|
||||
{
|
||||
{ std::regex("^(.*\\.a2r)$"), [](auto& s, auto* proto) { proto->mutable_a2r()->set_filename(s); }},
|
||||
{ std::regex("^(.*\\.flux)$"), [](auto& s, auto* proto) { proto->mutable_fl2()->set_filename(s); }},
|
||||
{ std::regex("^(.*\\.scp)$"), [](auto& s, auto* proto) { proto->mutable_scp()->set_filename(s); }},
|
||||
{ std::regex("^vcd:(.*)$"), [](auto& s, auto* proto) { proto->mutable_vcd()->set_directory(s); }},
|
||||
{ std::regex("^au:(.*)$"), [](auto& s, auto* proto) { proto->mutable_au()->set_directory(s); }},
|
||||
{ std::regex("^drive:(.*)"), [](auto& s, auto* proto) { proto->mutable_drive(); config.mutable_drive()->set_drive(std::stoi(s)); }},
|
||||
};
|
||||
static const std::vector<std::pair<std::regex,
|
||||
std::function<void(const std::string&, FluxSinkProto*)>>>
|
||||
formats = {
|
||||
{std::regex("^(.*\\.a2r)$"),
|
||||
[](auto& s, auto* proto)
|
||||
{
|
||||
proto->set_type(FluxSinkProto::A2R);
|
||||
proto->mutable_a2r()->set_filename(s);
|
||||
}},
|
||||
{std::regex("^(.*\\.flux)$"),
|
||||
[](auto& s, auto* proto)
|
||||
{
|
||||
proto->set_type(FluxSinkProto::FLUX);
|
||||
proto->mutable_fl2()->set_filename(s);
|
||||
}},
|
||||
{std::regex("^(.*\\.scp)$"),
|
||||
[](auto& s, auto* proto)
|
||||
{
|
||||
proto->set_type(FluxSinkProto::SCP);
|
||||
proto->mutable_scp()->set_filename(s);
|
||||
}},
|
||||
{std::regex("^vcd:(.*)$"),
|
||||
[](auto& s, auto* proto)
|
||||
{
|
||||
proto->set_type(FluxSinkProto::VCD);
|
||||
proto->mutable_vcd()->set_directory(s);
|
||||
}},
|
||||
{std::regex("^au:(.*)$"),
|
||||
[](auto& s, auto* proto)
|
||||
{
|
||||
proto->set_type(FluxSinkProto::AU);
|
||||
proto->mutable_au()->set_directory(s);
|
||||
}},
|
||||
{std::regex("^drive:(.*)"),
|
||||
[](auto& s, auto* proto)
|
||||
{
|
||||
proto->set_type(FluxSinkProto::DRIVE);
|
||||
config.mutable_drive()->set_drive(std::stoi(s));
|
||||
}},
|
||||
};
|
||||
|
||||
for (const auto& it : formats)
|
||||
{
|
||||
std::smatch match;
|
||||
if (std::regex_match(filename, match, it.first))
|
||||
{
|
||||
it.second(match[1], proto);
|
||||
return;
|
||||
}
|
||||
}
|
||||
for (const auto& it : formats)
|
||||
{
|
||||
std::smatch match;
|
||||
if (std::regex_match(filename, match, it.first))
|
||||
{
|
||||
it.second(match[1], proto);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Error() << fmt::format("unrecognised flux filename '{}'", filename);
|
||||
Error() << fmt::format("unrecognised flux filename '{}'", filename);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ message A2RFluxSinkProto {
|
||||
}
|
||||
|
||||
message VcdFluxSinkProto {
|
||||
optional string directory = 1 [default = "vcdfiles", (help) = "directory to write .vcd files to"];
|
||||
optional string directory = 1 [default = "vcdfiles", (help) = "directory to write .vcd files to"];
|
||||
}
|
||||
|
||||
message ScpFluxSinkProto {
|
||||
@@ -27,15 +27,25 @@ message Fl2FluxSinkProto {
|
||||
optional string filename = 1 [default = "flux.fl2", (help) = ".fl2 file to write to"];
|
||||
}
|
||||
|
||||
// Next: 9
|
||||
// Next: 10
|
||||
message FluxSinkProto {
|
||||
oneof dest {
|
||||
HardwareFluxSinkProto drive = 2;
|
||||
A2RFluxSinkProto a2r = 8;
|
||||
AuFluxSinkProto au = 3;
|
||||
VcdFluxSinkProto vcd = 4;
|
||||
ScpFluxSinkProto scp = 5;
|
||||
Fl2FluxSinkProto fl2 = 6;
|
||||
enum FluxSinkType {
|
||||
NOT_SET = 0;
|
||||
DRIVE = 1;
|
||||
A2R = 2;
|
||||
AU = 3;
|
||||
VCD = 4;
|
||||
SCP = 5;
|
||||
FLUX = 6;
|
||||
}
|
||||
|
||||
optional FluxSinkType type = 9 [default = NOT_SET, (help) = "flux sink type"];
|
||||
|
||||
optional HardwareFluxSinkProto drive = 2;
|
||||
optional A2RFluxSinkProto a2r = 8;
|
||||
optional AuFluxSinkProto au = 3;
|
||||
optional VcdFluxSinkProto vcd = 4;
|
||||
optional ScpFluxSinkProto scp = 5;
|
||||
optional Fl2FluxSinkProto fl2 = 6;
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include "proto.h"
|
||||
#include "fmt/format.h"
|
||||
#include "fluxmap.h"
|
||||
#include "layout.h"
|
||||
#include "scp.h"
|
||||
#include <fstream>
|
||||
#include <sys/stat.h>
|
||||
@@ -16,7 +17,7 @@
|
||||
|
||||
static int strackno(int track, int side)
|
||||
{
|
||||
return (track << 1) | side;
|
||||
return (track << 1) | side;
|
||||
}
|
||||
|
||||
static void write_le32(uint8_t dest[4], uint32_t v)
|
||||
@@ -29,150 +30,170 @@ static void write_le32(uint8_t dest[4], uint32_t v)
|
||||
|
||||
static void appendChecksum(uint32_t& checksum, const Bytes& bytes)
|
||||
{
|
||||
ByteReader br(bytes);
|
||||
while (!br.eof())
|
||||
checksum += br.read_8();
|
||||
ByteReader br(bytes);
|
||||
while (!br.eof())
|
||||
checksum += br.read_8();
|
||||
}
|
||||
|
||||
class ScpFluxSink : public FluxSink
|
||||
{
|
||||
public:
|
||||
ScpFluxSink(const ScpFluxSinkProto& lconfig):
|
||||
_config(lconfig)
|
||||
{
|
||||
bool singlesided = config.heads().start() == config.heads().end();
|
||||
ScpFluxSink(const ScpFluxSinkProto& lconfig): _config(lconfig)
|
||||
{
|
||||
int minTrack;
|
||||
int maxTrack;
|
||||
int minSide;
|
||||
int maxSide;
|
||||
Layout::getBounds(
|
||||
Layout::computeLocations(), minTrack, maxTrack, minSide, maxSide);
|
||||
|
||||
_fileheader.file_id[0] = 'S';
|
||||
_fileheader.file_id[1] = 'C';
|
||||
_fileheader.file_id[2] = 'P';
|
||||
_fileheader.version = 0x18; /* Version 1.8 of the spec */
|
||||
_fileheader.type = _config.type_byte();
|
||||
_fileheader.start_track = strackno(config.tracks().start(), config.heads().start());
|
||||
_fileheader.end_track = strackno(config.tracks().end(), config.heads().end());
|
||||
_fileheader.flags = SCP_FLAG_INDEXED
|
||||
| SCP_FLAG_96TPI;
|
||||
_fileheader.cell_width = 0;
|
||||
_fileheader.heads = singlesided;
|
||||
_fileheader.file_id[0] = 'S';
|
||||
_fileheader.file_id[1] = 'C';
|
||||
_fileheader.file_id[2] = 'P';
|
||||
_fileheader.version = 0x18; /* Version 1.8 of the spec */
|
||||
_fileheader.type = _config.type_byte();
|
||||
_fileheader.start_track = strackno(minTrack, minSide);
|
||||
_fileheader.end_track = strackno(maxTrack, maxSide);
|
||||
_fileheader.flags = SCP_FLAG_INDEXED;
|
||||
if (config.tpi() == 96)
|
||||
_fileheader.flags |= SCP_FLAG_96TPI;
|
||||
_fileheader.cell_width = 0;
|
||||
if ((minSide == 0) && (maxSide == 0))
|
||||
_fileheader.heads = 1;
|
||||
else if ((minSide == 1) && (maxSide == 1))
|
||||
_fileheader.heads = 2;
|
||||
else
|
||||
_fileheader.heads = 0;
|
||||
|
||||
std::cout << fmt::format("SCP: writing 96 tpi {} file containing {} tracks\n",
|
||||
singlesided ? "single sided" : "double sided",
|
||||
_fileheader.end_track - _fileheader.start_track + 1
|
||||
);
|
||||
std::cout << fmt::format(
|
||||
"SCP: writing 96 tpi {} file containing {} tracks\n",
|
||||
(minSide == maxSide) ? "single sided" : "double sided",
|
||||
_fileheader.end_track - _fileheader.start_track + 1);
|
||||
}
|
||||
|
||||
}
|
||||
~ScpFluxSink()
|
||||
{
|
||||
uint32_t checksum = 0;
|
||||
appendChecksum(checksum,
|
||||
Bytes((const uint8_t*)&_fileheader, sizeof(_fileheader))
|
||||
.slice(0x10));
|
||||
appendChecksum(checksum, _trackdata);
|
||||
write_le32(_fileheader.checksum, checksum);
|
||||
|
||||
~ScpFluxSink()
|
||||
{
|
||||
uint32_t checksum = 0;
|
||||
appendChecksum(checksum,
|
||||
Bytes((const uint8_t*) &_fileheader, sizeof(_fileheader))
|
||||
.slice(0x10));
|
||||
appendChecksum(checksum, _trackdata);
|
||||
write_le32(_fileheader.checksum, checksum);
|
||||
|
||||
std::cout << "SCP: writing output file...\n";
|
||||
std::ofstream of(_config.filename(), std::ios::out | std::ios::binary);
|
||||
if (!of.is_open())
|
||||
Error() << "cannot open output file";
|
||||
of.write((const char*) &_fileheader, sizeof(_fileheader));
|
||||
_trackdata.writeTo(of);
|
||||
of.close();
|
||||
}
|
||||
std::cout << "SCP: writing output file...\n";
|
||||
std::ofstream of(_config.filename(), std::ios::out | std::ios::binary);
|
||||
if (!of.is_open())
|
||||
Error() << "cannot open output file";
|
||||
of.write((const char*)&_fileheader, sizeof(_fileheader));
|
||||
_trackdata.writeTo(of);
|
||||
of.close();
|
||||
}
|
||||
|
||||
public:
|
||||
void writeFlux(int track, int head, const Fluxmap& fluxmap) override
|
||||
{
|
||||
ByteWriter trackdataWriter(_trackdata);
|
||||
trackdataWriter.seekToEnd();
|
||||
int strack = strackno(track, head);
|
||||
void writeFlux(int track, int head, const Fluxmap& fluxmap) override
|
||||
{
|
||||
ByteWriter trackdataWriter(_trackdata);
|
||||
trackdataWriter.seekToEnd();
|
||||
int strack = strackno(track, head);
|
||||
|
||||
if (strack >= std::size(_fileheader.track)) {
|
||||
std::cout << fmt::format("SCP: cannot write track {} head {}, "
|
||||
"there are not not enough Track Data Headers.\n",
|
||||
track, head);
|
||||
return;
|
||||
if (strack >= std::size(_fileheader.track))
|
||||
{
|
||||
std::cout << fmt::format(
|
||||
"SCP: cannot write track {} head {}, "
|
||||
"there are not not enough Track Data Headers.\n",
|
||||
track,
|
||||
head);
|
||||
return;
|
||||
}
|
||||
ScpTrack trackheader = {0};
|
||||
trackheader.header.track_id[0] = 'T';
|
||||
trackheader.header.track_id[1] = 'R';
|
||||
trackheader.header.track_id[2] = 'K';
|
||||
trackheader.header.strack = strack;
|
||||
|
||||
FluxmapReader fmr(fluxmap);
|
||||
Bytes fluxdata;
|
||||
ByteWriter fluxdataWriter(fluxdata);
|
||||
|
||||
int revolution =
|
||||
-1; // -1 indicates that we are before the first index pulse
|
||||
if (_config.align_with_index())
|
||||
{
|
||||
fmr.skipToEvent(F_BIT_INDEX);
|
||||
revolution = 0;
|
||||
}
|
||||
unsigned revTicks = 0;
|
||||
unsigned totalTicks = 0;
|
||||
unsigned ticksSinceLastPulse = 0;
|
||||
uint32_t startOffset = 0;
|
||||
while (revolution < 5)
|
||||
{
|
||||
unsigned ticks;
|
||||
int event;
|
||||
fmr.getNextEvent(event, ticks);
|
||||
|
||||
ticksSinceLastPulse += ticks;
|
||||
totalTicks += ticks;
|
||||
revTicks += ticks;
|
||||
|
||||
// if we haven't output any revolutions yet by the end of the track,
|
||||
// assume that the whole track is one rev
|
||||
// also discard any duplicate index pulses
|
||||
if (((fmr.eof() && revolution <= 0) ||
|
||||
((event & F_BIT_INDEX)) && revTicks > 0))
|
||||
{
|
||||
if (fmr.eof() && revolution == -1)
|
||||
revolution = 0;
|
||||
if (revolution >= 0)
|
||||
{
|
||||
auto* revheader = &trackheader.revolution[revolution];
|
||||
write_le32(
|
||||
revheader->offset, startOffset + sizeof(ScpTrack));
|
||||
write_le32(revheader->length,
|
||||
(fluxdataWriter.pos - startOffset) / 2);
|
||||
write_le32(revheader->index, revTicks * NS_PER_TICK / 25);
|
||||
revheader++;
|
||||
}
|
||||
ScpTrack trackheader = {0};
|
||||
trackheader.header.track_id[0] = 'T';
|
||||
trackheader.header.track_id[1] = 'R';
|
||||
trackheader.header.track_id[2] = 'K';
|
||||
trackheader.header.strack = strack;
|
||||
revolution++;
|
||||
revTicks = 0;
|
||||
startOffset = fluxdataWriter.pos;
|
||||
}
|
||||
if (fmr.eof())
|
||||
break;
|
||||
|
||||
FluxmapReader fmr(fluxmap);
|
||||
Bytes fluxdata;
|
||||
ByteWriter fluxdataWriter(fluxdata);
|
||||
if (event & F_BIT_PULSE)
|
||||
{
|
||||
unsigned t = ticksSinceLastPulse * NS_PER_TICK / 25;
|
||||
while (t >= 0x10000)
|
||||
{
|
||||
fluxdataWriter.write_be16(0);
|
||||
t -= 0x10000;
|
||||
}
|
||||
fluxdataWriter.write_be16(t);
|
||||
ticksSinceLastPulse = 0;
|
||||
}
|
||||
}
|
||||
|
||||
int revolution = -1; // -1 indicates that we are before the first index pulse
|
||||
if (_config.align_with_index()) {
|
||||
fmr.skipToEvent(F_BIT_INDEX);
|
||||
revolution = 0;
|
||||
}
|
||||
unsigned revTicks = 0;
|
||||
unsigned totalTicks = 0;
|
||||
unsigned ticksSinceLastPulse = 0;
|
||||
uint32_t startOffset = 0;
|
||||
while (revolution < 5)
|
||||
{
|
||||
unsigned ticks;
|
||||
int event;
|
||||
fmr.getNextEvent(event, ticks);
|
||||
_fileheader.revolutions = revolution;
|
||||
write_le32(
|
||||
_fileheader.track[strack], trackdataWriter.pos + sizeof(ScpHeader));
|
||||
trackdataWriter += Bytes((uint8_t*)&trackheader, sizeof(trackheader));
|
||||
trackdataWriter += fluxdata;
|
||||
}
|
||||
|
||||
ticksSinceLastPulse += ticks;
|
||||
totalTicks += ticks;
|
||||
revTicks += ticks;
|
||||
|
||||
// if we haven't output any revolutions yet by the end of the track,
|
||||
// assume that the whole track is one rev
|
||||
// also discard any duplicate index pulses
|
||||
if (((fmr.eof() && revolution <= 0) || ((event & F_BIT_INDEX)) && revTicks > 0))
|
||||
{
|
||||
if (fmr.eof() && revolution == -1)
|
||||
revolution = 0;
|
||||
if (revolution >= 0) {
|
||||
auto* revheader = &trackheader.revolution[revolution];
|
||||
write_le32(revheader->offset, startOffset + sizeof(ScpTrack));
|
||||
write_le32(revheader->length, (fluxdataWriter.pos - startOffset) / 2);
|
||||
write_le32(revheader->index, revTicks * NS_PER_TICK / 25);
|
||||
revheader++;
|
||||
}
|
||||
revolution++;
|
||||
revTicks = 0;
|
||||
startOffset = fluxdataWriter.pos;
|
||||
}
|
||||
if (fmr.eof()) break;
|
||||
|
||||
if (event & F_BIT_PULSE)
|
||||
{
|
||||
unsigned t = ticksSinceLastPulse * NS_PER_TICK / 25;
|
||||
while (t >= 0x10000)
|
||||
{
|
||||
fluxdataWriter.write_be16(0);
|
||||
t -= 0x10000;
|
||||
}
|
||||
fluxdataWriter.write_be16(t);
|
||||
ticksSinceLastPulse = 0;
|
||||
}
|
||||
}
|
||||
|
||||
_fileheader.revolutions = revolution;
|
||||
write_le32(_fileheader.track[strack], trackdataWriter.pos + sizeof(ScpHeader));
|
||||
trackdataWriter += Bytes((uint8_t*)&trackheader, sizeof(trackheader));
|
||||
trackdataWriter += fluxdata;
|
||||
}
|
||||
|
||||
operator std::string () const
|
||||
{
|
||||
return fmt::format("scp({})", _config.filename());
|
||||
}
|
||||
operator std::string() const
|
||||
{
|
||||
return fmt::format("scp({})", _config.filename());
|
||||
}
|
||||
|
||||
private:
|
||||
const ScpFluxSinkProto& _config;
|
||||
ScpHeader _fileheader = {0};
|
||||
Bytes _trackdata;
|
||||
const ScpFluxSinkProto& _config;
|
||||
ScpHeader _fileheader = {0};
|
||||
Bytes _trackdata;
|
||||
};
|
||||
|
||||
std::unique_ptr<FluxSink> FluxSink::createScpFluxSink(const ScpFluxSinkProto& config)
|
||||
std::unique_ptr<FluxSink> FluxSink::createScpFluxSink(
|
||||
const ScpFluxSinkProto& config)
|
||||
{
|
||||
return std::unique_ptr<FluxSink>(new ScpFluxSink(config));
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include "lib/fl2.pb.h"
|
||||
#include "fluxsource/fluxsource.h"
|
||||
#include "proto.h"
|
||||
#include "fl2.h"
|
||||
#include "fmt/format.h"
|
||||
#include "fluxmap.h"
|
||||
#include <fstream>
|
||||
@@ -11,38 +12,36 @@
|
||||
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
|
||||
@@ -50,15 +49,7 @@ class Fl2FluxSource : public FluxSource
|
||||
public:
|
||||
Fl2FluxSource(const Fl2FluxSourceProto& config): _config(config)
|
||||
{
|
||||
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();
|
||||
_proto = loadFl2File(_config.filename());
|
||||
}
|
||||
|
||||
public:
|
||||
@@ -67,7 +58,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>();
|
||||
@@ -82,31 +73,6 @@ 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;
|
||||
@@ -115,12 +81,5 @@ 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));
|
||||
}
|
||||
|
||||
@@ -17,93 +17,137 @@ static bool ends_with(const std::string& value, const std::string& ending)
|
||||
|
||||
std::unique_ptr<FluxSource> FluxSource::create(const FluxSourceProto& config)
|
||||
{
|
||||
switch (config.source_case())
|
||||
{
|
||||
case FluxSourceProto::kDrive:
|
||||
return createHardwareFluxSource(config.drive());
|
||||
switch (config.type())
|
||||
{
|
||||
case FluxSourceProto::DRIVE:
|
||||
return createHardwareFluxSource(config.drive());
|
||||
|
||||
case FluxSourceProto::kErase:
|
||||
return createEraseFluxSource(config.erase());
|
||||
case FluxSourceProto::ERASE:
|
||||
return createEraseFluxSource(config.erase());
|
||||
|
||||
case FluxSourceProto::kKryoflux:
|
||||
return createKryofluxFluxSource(config.kryoflux());
|
||||
case FluxSourceProto::KRYOFLUX:
|
||||
return createKryofluxFluxSource(config.kryoflux());
|
||||
|
||||
case FluxSourceProto::kTestPattern:
|
||||
return createTestPatternFluxSource(config.test_pattern());
|
||||
case FluxSourceProto::TEST_PATTERN:
|
||||
return createTestPatternFluxSource(config.test_pattern());
|
||||
|
||||
case FluxSourceProto::kScp:
|
||||
return createScpFluxSource(config.scp());
|
||||
case FluxSourceProto::SCP:
|
||||
return createScpFluxSource(config.scp());
|
||||
|
||||
case FluxSourceProto::kCwf:
|
||||
return createCwfFluxSource(config.cwf());
|
||||
case FluxSourceProto::CWF:
|
||||
return createCwfFluxSource(config.cwf());
|
||||
|
||||
case FluxSourceProto::kFl2:
|
||||
return createFl2FluxSource(config.fl2());
|
||||
case FluxSourceProto::FLUX:
|
||||
return createFl2FluxSource(config.fl2());
|
||||
|
||||
default:
|
||||
Error() << "bad input disk configuration";
|
||||
return std::unique_ptr<FluxSource>();
|
||||
}
|
||||
case FluxSourceProto::FLX:
|
||||
return createFlxFluxSource(config.flx());
|
||||
|
||||
default:
|
||||
Error() << "bad input disk configuration";
|
||||
return std::unique_ptr<FluxSource>();
|
||||
}
|
||||
}
|
||||
|
||||
void FluxSource::updateConfigForFilename(FluxSourceProto* proto, const std::string& filename)
|
||||
void FluxSource::updateConfigForFilename(
|
||||
FluxSourceProto* proto, const std::string& filename)
|
||||
{
|
||||
|
||||
static const std::vector<std::pair<std::regex, std::function<void(const std::string&, FluxSourceProto*)>>> formats =
|
||||
{
|
||||
{ std::regex("^(.*\\.a2r)$"), [](auto& s, auto* proto) { }},
|
||||
{ std::regex("^(.*\\.flux)$"), [](auto& s, auto* proto) { proto->mutable_fl2()->set_filename(s); }},
|
||||
{ std::regex("^(.*\\.scp)$"), [](auto& s, auto* proto) { proto->mutable_scp()->set_filename(s); }},
|
||||
{ std::regex("^(.*\\.cwf)$"), [](auto& s, auto* proto) { proto->mutable_cwf()->set_filename(s); }},
|
||||
{ std::regex("^erase:$"), [](auto& s, auto* proto) { proto->mutable_erase(); }},
|
||||
{ std::regex("^kryoflux:(.*)$"), [](auto& s, auto* proto) { proto->mutable_kryoflux()->set_directory(s); }},
|
||||
{ std::regex("^testpattern:(.*)"), [](auto& s, auto* proto) { proto->mutable_test_pattern(); }},
|
||||
{ std::regex("^drive:(.*)"), [](auto& s, auto* proto) { proto->mutable_drive(); config.mutable_drive()->set_drive(std::stoi(s)); }},
|
||||
};
|
||||
static const std::vector<std::pair<std::regex,
|
||||
std::function<void(const std::string&, FluxSourceProto*)>>>
|
||||
formats = {
|
||||
{std::regex("^(.*\\.flux)$"),
|
||||
[](auto& s, auto* proto)
|
||||
{
|
||||
proto->set_type(FluxSourceProto::FLUX);
|
||||
proto->mutable_fl2()->set_filename(s);
|
||||
}},
|
||||
{std::regex("^(.*\\.scp)$"),
|
||||
[](auto& s, auto* proto)
|
||||
{
|
||||
proto->set_type(FluxSourceProto::SCP);
|
||||
proto->mutable_scp()->set_filename(s);
|
||||
}},
|
||||
{std::regex("^(.*\\.cwf)$"),
|
||||
[](auto& s, auto* proto)
|
||||
{
|
||||
proto->set_type(FluxSourceProto::CWF);
|
||||
proto->mutable_cwf()->set_filename(s);
|
||||
}},
|
||||
{std::regex("^erase:$"),
|
||||
[](auto& s, auto* proto)
|
||||
{
|
||||
proto->set_type(FluxSourceProto::ERASE);
|
||||
}},
|
||||
{std::regex("^kryoflux:(.*)$"),
|
||||
[](auto& s, auto* proto)
|
||||
{
|
||||
proto->set_type(FluxSourceProto::KRYOFLUX);
|
||||
proto->mutable_kryoflux()->set_directory(s);
|
||||
}},
|
||||
{std::regex("^testpattern:(.*)"),
|
||||
[](auto& s, auto* proto)
|
||||
{
|
||||
proto->set_type(FluxSourceProto::TEST_PATTERN);
|
||||
}},
|
||||
{std::regex("^drive:(.*)"),
|
||||
[](auto& s, auto* proto)
|
||||
{
|
||||
proto->set_type(FluxSourceProto::DRIVE);
|
||||
config.mutable_drive()->set_drive(std::stoi(s));
|
||||
}},
|
||||
{std::regex("^flx:(.*)$"),
|
||||
[](auto& s, auto* proto)
|
||||
{
|
||||
proto->set_type(FluxSourceProto::FLX);
|
||||
proto->mutable_flx()->set_directory(s);
|
||||
}},
|
||||
};
|
||||
|
||||
for (const auto& it : formats)
|
||||
{
|
||||
std::smatch match;
|
||||
if (std::regex_match(filename, match, it.first))
|
||||
{
|
||||
it.second(match[1], proto);
|
||||
return;
|
||||
}
|
||||
}
|
||||
for (const auto& it : formats)
|
||||
{
|
||||
std::smatch match;
|
||||
if (std::regex_match(filename, match, it.first))
|
||||
{
|
||||
it.second(match[1], proto);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Error() << fmt::format("unrecognised flux filename '{}'", filename);
|
||||
Error() << fmt::format("unrecognised flux filename '{}'", filename);
|
||||
}
|
||||
|
||||
class TrivialFluxSourceIterator : public FluxSourceIterator
|
||||
{
|
||||
public:
|
||||
TrivialFluxSourceIterator(TrivialFluxSource* fluxSource, int track, int head):
|
||||
_fluxSource(fluxSource),
|
||||
_track(track),
|
||||
_head(head)
|
||||
{}
|
||||
TrivialFluxSourceIterator(
|
||||
TrivialFluxSource* fluxSource, int track, int head):
|
||||
_fluxSource(fluxSource),
|
||||
_track(track),
|
||||
_head(head)
|
||||
{
|
||||
}
|
||||
|
||||
bool hasNext() const override
|
||||
{
|
||||
return !!_fluxSource;
|
||||
}
|
||||
bool hasNext() const override
|
||||
{
|
||||
return !!_fluxSource;
|
||||
}
|
||||
|
||||
std::unique_ptr<const Fluxmap> next() override
|
||||
{
|
||||
auto fluxmap = _fluxSource->readSingleFlux(_track, _head);
|
||||
_fluxSource = nullptr;
|
||||
return fluxmap;
|
||||
}
|
||||
std::unique_ptr<const Fluxmap> next() override
|
||||
{
|
||||
auto fluxmap = _fluxSource->readSingleFlux(_track, _head);
|
||||
_fluxSource = nullptr;
|
||||
return fluxmap;
|
||||
}
|
||||
|
||||
private:
|
||||
TrivialFluxSource* _fluxSource;
|
||||
int _track;
|
||||
int _head;
|
||||
TrivialFluxSource* _fluxSource;
|
||||
int _track;
|
||||
int _head;
|
||||
};
|
||||
|
||||
std::unique_ptr<FluxSourceIterator> TrivialFluxSource::readFlux(int track, int head)
|
||||
std::unique_ptr<FluxSourceIterator> TrivialFluxSource::readFlux(
|
||||
int track, int head)
|
||||
{
|
||||
return std::make_unique<TrivialFluxSourceIterator>(this, track, head);
|
||||
return std::make_unique<TrivialFluxSourceIterator>(this, track, head);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -14,14 +14,15 @@ 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
|
||||
@@ -30,32 +31,48 @@ 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> 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> 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);
|
||||
|
||||
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 bool isHardware() { return false; }
|
||||
virtual void seek(int track) {}
|
||||
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
|
||||
|
||||
|
||||
@@ -30,15 +30,33 @@ message Fl2FluxSourceProto {
|
||||
(help) = ".fl2 file to read flux from"];
|
||||
}
|
||||
|
||||
message FluxSourceProto {
|
||||
oneof source {
|
||||
HardwareFluxSourceProto drive = 2;
|
||||
TestPatternFluxSourceProto test_pattern = 3;
|
||||
EraseFluxSourceProto erase = 4;
|
||||
KryofluxFluxSourceProto kryoflux = 5;
|
||||
ScpFluxSourceProto scp = 6;
|
||||
CwfFluxSourceProto cwf = 7;
|
||||
Fl2FluxSourceProto fl2 = 8;
|
||||
}
|
||||
message FlxFluxSourceProto {
|
||||
optional string directory = 1 [(help) = "path to FLX stream directory"];
|
||||
}
|
||||
|
||||
// NEXT: 11
|
||||
message FluxSourceProto {
|
||||
enum FluxSourceType {
|
||||
NOT_SET = 0;
|
||||
DRIVE = 1;
|
||||
TEST_PATTERN = 2;
|
||||
ERASE = 3;
|
||||
KRYOFLUX = 4;
|
||||
SCP = 5;
|
||||
CWF = 6;
|
||||
FLUX = 7;
|
||||
FLX = 8;
|
||||
}
|
||||
|
||||
optional FluxSourceType type = 9 [default = NOT_SET, (help) = "flux source type"];
|
||||
|
||||
optional HardwareFluxSourceProto drive = 2;
|
||||
optional TestPatternFluxSourceProto test_pattern = 3;
|
||||
optional EraseFluxSourceProto erase = 4;
|
||||
optional KryofluxFluxSourceProto kryoflux = 5;
|
||||
optional ScpFluxSourceProto scp = 6;
|
||||
optional CwfFluxSourceProto cwf = 7;
|
||||
optional Fl2FluxSourceProto fl2 = 8;
|
||||
optional FlxFluxSourceProto flx = 10;
|
||||
}
|
||||
|
||||
|
||||
50
lib/fluxsource/flx.cc
Normal file
@@ -0,0 +1,50 @@
|
||||
#include "globals.h"
|
||||
#include "fluxmap.h"
|
||||
#include "kryoflux.h"
|
||||
#include "protocol.h"
|
||||
#include "lib/fluxsource/flx.h"
|
||||
#include "fmt/format.h"
|
||||
|
||||
std::unique_ptr<Fluxmap> readFlxBytes(const Bytes& bytes)
|
||||
{
|
||||
ByteReader br(bytes);
|
||||
|
||||
/* Skip header. */
|
||||
|
||||
for (;;)
|
||||
{
|
||||
if (br.eof())
|
||||
Error() << fmt::format("malformed FLX stream");
|
||||
uint8_t b = br.read_8();
|
||||
if (b == 0)
|
||||
break;
|
||||
}
|
||||
|
||||
auto fluxmap = std::make_unique<Fluxmap>();
|
||||
while (!br.eof())
|
||||
{
|
||||
uint8_t b = br.read_8();
|
||||
switch (b)
|
||||
{
|
||||
case FLX_INDEX:
|
||||
fluxmap->appendIndex();
|
||||
continue;
|
||||
|
||||
case FLX_STOP:
|
||||
goto stop;
|
||||
|
||||
default:
|
||||
{
|
||||
if (b < 32)
|
||||
Error() << fmt::format("unknown FLX opcode 0x{:2x}", b);
|
||||
nanoseconds_t interval = b * FLX_TICK_NS;
|
||||
fluxmap->appendInterval(interval / NS_PER_TICK);
|
||||
fluxmap->appendPulse();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
stop:
|
||||
|
||||
return fluxmap;
|
||||
}
|
||||
16
lib/fluxsource/flx.h
Normal file
@@ -0,0 +1,16 @@
|
||||
#ifndef FLX_H
|
||||
#define FLX_H
|
||||
|
||||
#define FLX_TICK_NS 40 /* ns per tick */
|
||||
|
||||
/* Special FLX opcodes */
|
||||
|
||||
enum
|
||||
{
|
||||
FLX_INDEX = 0x08,
|
||||
FLX_STOP = 0x0d
|
||||
};
|
||||
|
||||
extern std::unique_ptr<Fluxmap> readFlxBytes(const Bytes& bytes);
|
||||
|
||||
#endif
|
||||
32
lib/fluxsource/flxfluxsource.cc
Normal file
@@ -0,0 +1,32 @@
|
||||
#include "lib/globals.h"
|
||||
#include "lib/fluxmap.h"
|
||||
#include "lib/fluxsource/fluxsource.pb.h"
|
||||
#include "lib/fluxsource/fluxsource.h"
|
||||
#include "lib/fluxsource/flx.h"
|
||||
|
||||
class FlxFluxSource : public TrivialFluxSource
|
||||
{
|
||||
public:
|
||||
FlxFluxSource(const FlxFluxSourceProto& config): _path(config.directory())
|
||||
{
|
||||
}
|
||||
|
||||
public:
|
||||
std::unique_ptr<const Fluxmap> readSingleFlux(int track, int side) override
|
||||
{
|
||||
std::string path =
|
||||
fmt::format("{}/@TR{:02}S{}@.FLX", _path, track, side + 1);
|
||||
return readFlxBytes(Bytes::readFromFile(path));
|
||||
}
|
||||
|
||||
void recalibrate() {}
|
||||
|
||||
private:
|
||||
const std::string _path;
|
||||
};
|
||||
|
||||
std::unique_ptr<FluxSource> FluxSource::createFlxFluxSource(
|
||||
const FlxFluxSourceProto& config)
|
||||
{
|
||||
return std::make_unique<FlxFluxSource>(config);
|
||||
}
|
||||
@@ -30,7 +30,9 @@ 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,
|
||||
@@ -51,7 +53,7 @@ private:
|
||||
public:
|
||||
HardwareFluxSource(const HardwareFluxSourceProto& conf): _config(conf)
|
||||
{
|
||||
measureDiskRotation(_oneRevolution, _hardSectorThreshold);
|
||||
measureDiskRotation(_oneRevolution, _hardSectorThreshold);
|
||||
}
|
||||
|
||||
~HardwareFluxSource() {}
|
||||
@@ -59,8 +61,7 @@ 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
|
||||
@@ -68,6 +69,11 @@ public:
|
||||
usbRecalibrate();
|
||||
}
|
||||
|
||||
void seek(int track) override
|
||||
{
|
||||
usbSeek(track);
|
||||
}
|
||||
|
||||
bool isHardware() override
|
||||
{
|
||||
return true;
|
||||
|
||||
@@ -23,7 +23,10 @@ void hexdump(std::ostream& stream, const Bytes& buffer)
|
||||
break;
|
||||
|
||||
uint8_t c = buffer[pos+i];
|
||||
stream << (isprint(c) ? (char)c : '.');
|
||||
if ((c >= 32) && (c <= 126))
|
||||
stream << (char)c;
|
||||
else
|
||||
stream << '.';
|
||||
}
|
||||
stream << std::endl;
|
||||
|
||||
|
||||
@@ -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->logicalSectorOrder)
|
||||
for (unsigned sectorId : trackLayout->naturalSectorOrder)
|
||||
put(track, side, sectorId)->data = blank;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
#ifndef IMAGE_H
|
||||
#define IMAGE_H
|
||||
|
||||
class Sector;
|
||||
|
||||
struct Geometry
|
||||
{
|
||||
unsigned numTracks = 0;
|
||||
|
||||
@@ -77,6 +77,7 @@ public:
|
||||
config.set_tpi(48);
|
||||
}
|
||||
|
||||
config.clear_layout();
|
||||
auto layout = config.mutable_layout();
|
||||
std::unique_ptr<Image> image(new Image);
|
||||
for (int track = 0; track < trackTableSize / 4; track++)
|
||||
|
||||
@@ -14,39 +14,39 @@
|
||||
|
||||
std::unique_ptr<ImageReader> ImageReader::create(const ImageReaderProto& config)
|
||||
{
|
||||
switch (config.format_case())
|
||||
switch (config.type())
|
||||
{
|
||||
case ImageReaderProto::kDim:
|
||||
case ImageReaderProto::DIM:
|
||||
return ImageReader::createDimImageReader(config);
|
||||
|
||||
case ImageReaderProto::kD88:
|
||||
case ImageReaderProto::D88:
|
||||
return ImageReader::createD88ImageReader(config);
|
||||
|
||||
case ImageReaderProto::kFdi:
|
||||
case ImageReaderProto::FDI:
|
||||
return ImageReader::createFdiImageReader(config);
|
||||
|
||||
case ImageReaderProto::kImd:
|
||||
case ImageReaderProto::IMD:
|
||||
return ImageReader::createIMDImageReader(config);
|
||||
|
||||
case ImageReaderProto::kImg:
|
||||
case ImageReaderProto::IMG:
|
||||
return ImageReader::createImgImageReader(config);
|
||||
|
||||
case ImageReaderProto::kDiskcopy:
|
||||
case ImageReaderProto::DISKCOPY:
|
||||
return ImageReader::createDiskCopyImageReader(config);
|
||||
|
||||
case ImageReaderProto::kJv3:
|
||||
case ImageReaderProto::JV3:
|
||||
return ImageReader::createJv3ImageReader(config);
|
||||
|
||||
case ImageReaderProto::kD64:
|
||||
case ImageReaderProto::D64:
|
||||
return ImageReader::createD64ImageReader(config);
|
||||
|
||||
case ImageReaderProto::kNfd:
|
||||
case ImageReaderProto::NFD:
|
||||
return ImageReader::createNFDImageReader(config);
|
||||
|
||||
case ImageReaderProto::kNsi:
|
||||
case ImageReaderProto::NSI:
|
||||
return ImageReader::createNsiImageReader(config);
|
||||
|
||||
case ImageReaderProto::kTd0:
|
||||
case ImageReaderProto::TD0:
|
||||
return ImageReader::createTd0ImageReader(config);
|
||||
|
||||
default:
|
||||
@@ -61,23 +61,23 @@ void ImageReader::updateConfigForFilename(
|
||||
static const std::map<std::string, std::function<void(ImageReaderProto*)>>
|
||||
formats = {
|
||||
// clang-format off
|
||||
{".adf", [](auto* proto) { proto->mutable_img(); }},
|
||||
{".d64", [](auto* proto) { proto->mutable_d64(); }},
|
||||
{".d81", [](auto* proto) { proto->mutable_img(); }},
|
||||
{".d88", [](auto* proto) { proto->mutable_d88(); }},
|
||||
{".dim", [](auto* proto) { proto->mutable_dim(); }},
|
||||
{".diskcopy", [](auto* proto) { proto->mutable_diskcopy(); }},
|
||||
{".dsk", [](auto* proto) { proto->mutable_img(); }},
|
||||
{".fdi", [](auto* proto) { proto->mutable_fdi(); }},
|
||||
{".imd", [](auto* proto) { proto->mutable_imd(); }},
|
||||
{".img", [](auto* proto) { proto->mutable_img(); }},
|
||||
{".jv3", [](auto* proto) { proto->mutable_jv3(); }},
|
||||
{".nfd", [](auto* proto) { proto->mutable_nfd(); }},
|
||||
{".nsi", [](auto* proto) { proto->mutable_nsi(); }},
|
||||
{".st", [](auto* proto) { proto->mutable_img(); }},
|
||||
{".td0", [](auto* proto) { proto->mutable_td0(); }},
|
||||
{".vgi", [](auto* proto) { proto->mutable_img(); }},
|
||||
{".xdf", [](auto* proto) { proto->mutable_img(); }},
|
||||
{".adf", [](auto* proto) { proto->set_type(ImageReaderProto::IMG); }},
|
||||
{".d64", [](auto* proto) { proto->set_type(ImageReaderProto::D64); }},
|
||||
{".d81", [](auto* proto) { proto->set_type(ImageReaderProto::IMG); }},
|
||||
{".d88", [](auto* proto) { proto->set_type(ImageReaderProto::D88); }},
|
||||
{".dim", [](auto* proto) { proto->set_type(ImageReaderProto::DIM); }},
|
||||
{".diskcopy", [](auto* proto) { proto->set_type(ImageReaderProto::DISKCOPY); }},
|
||||
{".dsk", [](auto* proto) { proto->set_type(ImageReaderProto::IMG); }},
|
||||
{".fdi", [](auto* proto) { proto->set_type(ImageReaderProto::FDI); }},
|
||||
{".imd", [](auto* proto) { proto->set_type(ImageReaderProto::IMD); }},
|
||||
{".img", [](auto* proto) { proto->set_type(ImageReaderProto::IMG); }},
|
||||
{".jv3", [](auto* proto) { proto->set_type(ImageReaderProto::JV3); }},
|
||||
{".nfd", [](auto* proto) { proto->set_type(ImageReaderProto::NFD); }},
|
||||
{".nsi", [](auto* proto) { proto->set_type(ImageReaderProto::NSI); }},
|
||||
{".st", [](auto* proto) { proto->set_type(ImageReaderProto::IMG); }},
|
||||
{".td0", [](auto* proto) { proto->set_type(ImageReaderProto::TD0); }},
|
||||
{".vgi", [](auto* proto) { proto->set_type(ImageReaderProto::IMG); }},
|
||||
{".xdf", [](auto* proto) { proto->set_type(ImageReaderProto::IMG); }},
|
||||
// clang-format on
|
||||
};
|
||||
|
||||
@@ -112,7 +112,7 @@ std::unique_ptr<Image> ImageReader::readMappedImage()
|
||||
auto newSector = std::make_shared<Sector>();
|
||||
*newSector = *e;
|
||||
newSector->logicalSector =
|
||||
trackLayout->filesystemToLogicalSectorMap.at(e->logicalSector);
|
||||
trackLayout->filesystemToNaturalSectorMap.at(e->logicalSector);
|
||||
sectors.insert(newSector);
|
||||
}
|
||||
|
||||
|
||||
@@ -13,9 +13,9 @@ message Td0InputProto {}
|
||||
message DimInputProto {}
|
||||
message FdiInputProto {}
|
||||
message D88InputProto {}
|
||||
message NFDInputProto {}
|
||||
message NfdInputProto {}
|
||||
|
||||
// NEXT_TAG: 14
|
||||
// NEXT_TAG: 15
|
||||
message ImageReaderProto
|
||||
{
|
||||
optional string filename = 1 [ (help) = "filename of input sector image" ];
|
||||
@@ -24,18 +24,32 @@ message ImageReaderProto
|
||||
default = false
|
||||
];
|
||||
|
||||
oneof format
|
||||
{
|
||||
ImgInputOutputProto img = 2;
|
||||
DiskCopyInputProto diskcopy = 3;
|
||||
ImdInputProto imd = 4;
|
||||
Jv3InputProto jv3 = 5;
|
||||
D64InputProto d64 = 6;
|
||||
NsiInputProto nsi = 7;
|
||||
Td0InputProto td0 = 8;
|
||||
DimInputProto dim = 9;
|
||||
FdiInputProto fdi = 10;
|
||||
D88InputProto d88 = 11;
|
||||
NFDInputProto nfd = 12;
|
||||
}
|
||||
enum ImageReaderType {
|
||||
NOT_SET = 0;
|
||||
IMG = 1;
|
||||
DISKCOPY = 2;
|
||||
IMD = 3;
|
||||
JV3 = 4;
|
||||
D64 = 5;
|
||||
NSI = 6;
|
||||
TD0 = 7;
|
||||
DIM = 8;
|
||||
FDI = 9;
|
||||
D88 = 10;
|
||||
NFD = 11;
|
||||
}
|
||||
|
||||
optional ImageReaderType type = 14 [default = NOT_SET, (help) = "input image type"];
|
||||
|
||||
optional ImgInputOutputProto img = 2;
|
||||
optional DiskCopyInputProto diskcopy = 3;
|
||||
optional ImdInputProto imd = 4;
|
||||
optional Jv3InputProto jv3 = 5;
|
||||
optional D64InputProto d64 = 6;
|
||||
optional NsiInputProto nsi = 7;
|
||||
optional Td0InputProto td0 = 8;
|
||||
optional DimInputProto dim = 9;
|
||||
optional FdiInputProto fdi = 10;
|
||||
optional D88InputProto d88 = 11;
|
||||
optional NfdInputProto nfd = 12;
|
||||
}
|
||||
|
||||
@@ -40,7 +40,7 @@ public:
|
||||
break;
|
||||
|
||||
auto trackLayout = Layout::getLayoutOfTrack(track, side);
|
||||
for (int sectorId : trackLayout->logicalSectorOrder)
|
||||
for (int sectorId : trackLayout->naturalSectorOrder)
|
||||
{
|
||||
Bytes data(trackLayout->sectorSize);
|
||||
inputFile.read((char*)data.begin(), data.size());
|
||||
|
||||
@@ -14,30 +14,30 @@
|
||||
|
||||
std::unique_ptr<ImageWriter> ImageWriter::create(const ImageWriterProto& config)
|
||||
{
|
||||
switch (config.format_case())
|
||||
switch (config.type())
|
||||
{
|
||||
case ImageWriterProto::kImg:
|
||||
case ImageWriterProto::IMG:
|
||||
return ImageWriter::createImgImageWriter(config);
|
||||
|
||||
case ImageWriterProto::kD64:
|
||||
case ImageWriterProto::D64:
|
||||
return ImageWriter::createD64ImageWriter(config);
|
||||
|
||||
case ImageWriterProto::kLdbs:
|
||||
case ImageWriterProto::LDBS:
|
||||
return ImageWriter::createLDBSImageWriter(config);
|
||||
|
||||
case ImageWriterProto::kDiskcopy:
|
||||
case ImageWriterProto::DISKCOPY:
|
||||
return ImageWriter::createDiskCopyImageWriter(config);
|
||||
|
||||
case ImageWriterProto::kNsi:
|
||||
case ImageWriterProto::NSI:
|
||||
return ImageWriter::createNsiImageWriter(config);
|
||||
|
||||
case ImageWriterProto::kRaw:
|
||||
case ImageWriterProto::RAW:
|
||||
return ImageWriter::createRawImageWriter(config);
|
||||
|
||||
case ImageWriterProto::kD88:
|
||||
case ImageWriterProto::D88:
|
||||
return ImageWriter::createD88ImageWriter(config);
|
||||
|
||||
case ImageWriterProto::kImd:
|
||||
case ImageWriterProto::IMD:
|
||||
return ImageWriter::createImdImageWriter(config);
|
||||
|
||||
default:
|
||||
@@ -52,20 +52,20 @@ void ImageWriter::updateConfigForFilename(
|
||||
static const std::map<std::string, std::function<void(ImageWriterProto*)>>
|
||||
formats = {
|
||||
// clang-format off
|
||||
{".adf", [](auto* proto) { proto->mutable_img(); }},
|
||||
{".d64", [](auto* proto) { proto->mutable_d64(); }},
|
||||
{".d81", [](auto* proto) { proto->mutable_img(); }},
|
||||
{".d88", [](auto* proto) { proto->mutable_d88(); }},
|
||||
{".diskcopy", [](auto* proto) { proto->mutable_diskcopy(); }},
|
||||
{".dsk", [](auto* proto) { proto->mutable_img(); }},
|
||||
{".img", [](auto* proto) { proto->mutable_img(); }},
|
||||
{".imd", [](auto* proto) { proto->mutable_imd(); }},
|
||||
{".ldbs", [](auto* proto) { proto->mutable_ldbs(); }},
|
||||
{".nsi", [](auto* proto) { proto->mutable_nsi(); }},
|
||||
{".raw", [](auto* proto) { proto->mutable_raw(); }},
|
||||
{".st", [](auto* proto) { proto->mutable_img(); }},
|
||||
{".vgi", [](auto* proto) { proto->mutable_img(); }},
|
||||
{".xdf", [](auto* proto) { proto->mutable_img(); }},
|
||||
{".adf", [](auto* proto) { proto->set_type(ImageWriterProto::IMG); }},
|
||||
{".d64", [](auto* proto) { proto->set_type(ImageWriterProto::D64); }},
|
||||
{".d81", [](auto* proto) { proto->set_type(ImageWriterProto::IMG); }},
|
||||
{".d88", [](auto* proto) { proto->set_type(ImageWriterProto::D88); }},
|
||||
{".diskcopy", [](auto* proto) { proto->set_type(ImageWriterProto::DISKCOPY); }},
|
||||
{".dsk", [](auto* proto) { proto->set_type(ImageWriterProto::IMG); }},
|
||||
{".img", [](auto* proto) { proto->set_type(ImageWriterProto::IMG); }},
|
||||
{".imd", [](auto* proto) { proto->set_type(ImageWriterProto::IMD); }},
|
||||
{".ldbs", [](auto* proto) { proto->set_type(ImageWriterProto::LDBS); }},
|
||||
{".nsi", [](auto* proto) { proto->set_type(ImageWriterProto::NSI); }},
|
||||
{".raw", [](auto* proto) { proto->set_type(ImageWriterProto::RAW); }},
|
||||
{".st", [](auto* proto) { proto->set_type(ImageWriterProto::IMG); }},
|
||||
{".vgi", [](auto* proto) { proto->set_type(ImageWriterProto::IMG); }},
|
||||
{".xdf", [](auto* proto) { proto->set_type(ImageWriterProto::IMG); }},
|
||||
// clang-format on
|
||||
};
|
||||
|
||||
@@ -218,7 +218,7 @@ void ImageWriter::writeMappedImage(const Image& image)
|
||||
auto newSector = std::make_shared<Sector>();
|
||||
*newSector = *e;
|
||||
newSector->logicalSector =
|
||||
trackLayout->logicalToFilesystemSectorMap.at(e->logicalSector);
|
||||
trackLayout->naturalToFilesystemSectorMap.at(e->logicalSector);
|
||||
sectors.insert(newSector);
|
||||
}
|
||||
|
||||
|
||||
@@ -63,24 +63,35 @@ message ImdOutputProto
|
||||
optional string comment = 3 [ (help) = "comment to set in IMD file" ];
|
||||
}
|
||||
|
||||
// NEXT_TAG: 11
|
||||
// NEXT_TAG: 12
|
||||
message ImageWriterProto
|
||||
{
|
||||
enum ImageWriterType {
|
||||
NOT_SET = 0;
|
||||
IMG = 1;
|
||||
D64 = 2;
|
||||
LDBS = 3;
|
||||
DISKCOPY = 4;
|
||||
NSI = 5;
|
||||
RAW = 6;
|
||||
D88 = 7;
|
||||
IMD = 8;
|
||||
}
|
||||
|
||||
optional string filename = 1 [ (help) = "filename of output sector image" ];
|
||||
optional bool filesystem_sector_order = 10 [
|
||||
(help) = "read/write sector image in filesystem order",
|
||||
default = false
|
||||
];
|
||||
|
||||
oneof format
|
||||
{
|
||||
ImgInputOutputProto img = 2;
|
||||
D64OutputProto d64 = 3;
|
||||
LDBSOutputProto ldbs = 4;
|
||||
DiskCopyOutputProto diskcopy = 5;
|
||||
NsiOutputProto nsi = 6;
|
||||
RawOutputProto raw = 7;
|
||||
D88OutputProto d88 = 8;
|
||||
ImdOutputProto imd = 9;
|
||||
}
|
||||
optional ImageWriterType type = 11 [ default = NOT_SET, (help) = "image writer type" ];
|
||||
|
||||
optional ImgInputOutputProto img = 2;
|
||||
optional D64OutputProto d64 = 3;
|
||||
optional LDBSOutputProto ldbs = 4;
|
||||
optional DiskCopyOutputProto diskcopy = 5;
|
||||
optional NsiOutputProto nsi = 6;
|
||||
optional RawOutputProto raw = 7;
|
||||
optional D88OutputProto d88 = 8;
|
||||
optional ImdOutputProto imd = 9;
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ public:
|
||||
int side = p.second;
|
||||
|
||||
auto trackLayout = Layout::getLayoutOfTrack(track, side);
|
||||
for (int sectorId : trackLayout->logicalSectorOrder)
|
||||
for (int sectorId : trackLayout->naturalSectorOrder)
|
||||
{
|
||||
const auto& sector = image.get(track, side, sectorId);
|
||||
if (sector)
|
||||
|
||||
145
lib/layout.cc
@@ -4,9 +4,6 @@
|
||||
#include "lib/environment.h"
|
||||
#include <fmt/format.h>
|
||||
|
||||
static Local<std::map<std::pair<int, int>, std::shared_ptr<TrackInfo>>>
|
||||
layoutCache;
|
||||
|
||||
static unsigned getTrackStep()
|
||||
{
|
||||
unsigned track_step =
|
||||
@@ -61,6 +58,25 @@ 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)
|
||||
{
|
||||
@@ -109,10 +125,24 @@ 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++)
|
||||
sectors.push_back(
|
||||
sectorsProto.start_sector() +
|
||||
((i * sectorsProto.skew()) % sectorsProto.count()));
|
||||
{
|
||||
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();
|
||||
}
|
||||
}
|
||||
else if (sectorsProto.sector_size() > 0)
|
||||
{
|
||||
@@ -128,66 +158,57 @@ std::vector<unsigned> Layout::expandSectorList(
|
||||
std::shared_ptr<const TrackInfo> Layout::getLayoutOfTrack(
|
||||
unsigned logicalTrack, unsigned logicalSide)
|
||||
{
|
||||
auto& layout = (*layoutCache)[std::make_pair(logicalTrack, logicalSide)];
|
||||
if (!layout)
|
||||
auto trackInfo = std::make_shared<TrackInfo>();
|
||||
|
||||
LayoutProto::LayoutdataProto layoutdata;
|
||||
for (const auto& f : config.layout().layoutdata())
|
||||
{
|
||||
layout = std::make_shared<TrackInfo>();
|
||||
if (f.has_track() && f.has_up_to_track() &&
|
||||
((logicalTrack < f.track()) || (logicalTrack > f.up_to_track())))
|
||||
continue;
|
||||
if (f.has_track() && !f.has_up_to_track() &&
|
||||
(logicalTrack != f.track()))
|
||||
continue;
|
||||
if (f.has_side() && (f.side() != logicalSide))
|
||||
continue;
|
||||
|
||||
LayoutProto::LayoutdataProto layoutdata;
|
||||
for (const auto& f : config.layout().layoutdata())
|
||||
{
|
||||
if (f.has_track() && f.has_up_to_track() &&
|
||||
((logicalTrack < f.track()) ||
|
||||
(logicalTrack > f.up_to_track())))
|
||||
continue;
|
||||
if (f.has_track() && !f.has_up_to_track() &&
|
||||
(logicalTrack != f.track()))
|
||||
continue;
|
||||
if (f.has_side() && (f.side() != logicalSide))
|
||||
continue;
|
||||
|
||||
layoutdata.MergeFrom(f);
|
||||
}
|
||||
|
||||
layout->numTracks = config.layout().tracks();
|
||||
layout->numSides = config.layout().sides();
|
||||
layout->sectorSize = layoutdata.sector_size();
|
||||
layout->logicalTrack = logicalTrack;
|
||||
layout->logicalSide = logicalSide;
|
||||
layout->physicalTrack = remapTrackLogicalToPhysical(logicalTrack);
|
||||
layout->physicalSide = logicalSide ^ config.layout().swap_sides();
|
||||
layout->groupSize = getTrackStep();
|
||||
layout->diskSectorOrder = expandSectorList(layoutdata.physical());
|
||||
layout->logicalSectorOrder = layout->diskSectorOrder;
|
||||
std::sort(
|
||||
layout->diskSectorOrder.begin(), layout->diskSectorOrder.end());
|
||||
layout->numSectors = layout->logicalSectorOrder.size();
|
||||
|
||||
if (layoutdata.has_filesystem())
|
||||
{
|
||||
layout->filesystemSectorOrder =
|
||||
expandSectorList(layoutdata.filesystem());
|
||||
if (layout->filesystemSectorOrder.size() != layout->numSectors)
|
||||
Error()
|
||||
<< "filesystem sector order list doesn't contain the right "
|
||||
"number of sectors";
|
||||
}
|
||||
else
|
||||
{
|
||||
for (unsigned sectorId : layout->logicalSectorOrder)
|
||||
layout->filesystemSectorOrder.push_back(sectorId);
|
||||
}
|
||||
|
||||
for (int i = 0; i < layout->numSectors; i++)
|
||||
{
|
||||
unsigned f = layout->logicalSectorOrder[i];
|
||||
unsigned l = layout->filesystemSectorOrder[i];
|
||||
layout->filesystemToLogicalSectorMap[f] = l;
|
||||
layout->logicalToFilesystemSectorMap[l] = f;
|
||||
}
|
||||
layoutdata.MergeFrom(f);
|
||||
}
|
||||
|
||||
return layout;
|
||||
trackInfo->numTracks = config.layout().tracks();
|
||||
trackInfo->numSides = config.layout().sides();
|
||||
trackInfo->sectorSize = layoutdata.sector_size();
|
||||
trackInfo->logicalTrack = logicalTrack;
|
||||
trackInfo->logicalSide = logicalSide;
|
||||
trackInfo->physicalTrack = remapTrackLogicalToPhysical(logicalTrack);
|
||||
trackInfo->physicalSide = logicalSide ^ config.layout().swap_sides();
|
||||
trackInfo->groupSize = getTrackStep();
|
||||
trackInfo->diskSectorOrder = expandSectorList(layoutdata.physical());
|
||||
trackInfo->naturalSectorOrder = trackInfo->diskSectorOrder;
|
||||
std::sort(trackInfo->naturalSectorOrder.begin(),
|
||||
trackInfo->naturalSectorOrder.end());
|
||||
trackInfo->numSectors = trackInfo->naturalSectorOrder.size();
|
||||
|
||||
if (layoutdata.has_filesystem())
|
||||
{
|
||||
trackInfo->filesystemSectorOrder =
|
||||
expandSectorList(layoutdata.filesystem());
|
||||
if (trackInfo->filesystemSectorOrder.size() != trackInfo->numSectors)
|
||||
Error() << "filesystem sector order list doesn't contain the right "
|
||||
"number of sectors";
|
||||
}
|
||||
else
|
||||
trackInfo->filesystemSectorOrder = trackInfo->naturalSectorOrder;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
return trackInfo;
|
||||
}
|
||||
|
||||
std::shared_ptr<const TrackInfo> Layout::getLayoutOfTrackPhysical(
|
||||
|
||||
37
lib/layout.h
@@ -23,10 +23,19 @@ public:
|
||||
static unsigned remapSideLogicalToPhysical(unsigned logicalSide);
|
||||
|
||||
/* Uses the layout and current track and heads settings to determine
|
||||
* which Locations are going to be read from or written to. 8/
|
||||
* which Locations are going to be read from or written to.
|
||||
*/
|
||||
static std::vector<std::shared_ptr<const TrackInfo>> computeLocations();
|
||||
|
||||
/* Given a list of locations, determines the minimum and maximum track
|
||||
* and side settings. */
|
||||
static void getBounds(
|
||||
const std::vector<std::shared_ptr<const TrackInfo>>& locations,
|
||||
int& minTrack,
|
||||
int& maxTrack,
|
||||
int& minSide,
|
||||
int& maxSide);
|
||||
|
||||
/* Returns a series of <track, side> pairs representing the filesystem
|
||||
* ordering of the disk, in logical numbers. */
|
||||
static std::vector<std::pair<int, int>> getTrackOrdering(
|
||||
@@ -45,9 +54,10 @@ public:
|
||||
const SectorListProto& sectorsProto);
|
||||
};
|
||||
|
||||
class TrackInfo {
|
||||
class TrackInfo
|
||||
{
|
||||
public:
|
||||
TrackInfo() {}
|
||||
TrackInfo() {}
|
||||
|
||||
private:
|
||||
/* Can't copy. */
|
||||
@@ -80,20 +90,23 @@ public:
|
||||
/* Number of bytes in a sector. */
|
||||
unsigned sectorSize = 0;
|
||||
|
||||
/* Sector IDs in disk order. */
|
||||
/* 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.
|
||||
*/
|
||||
std::vector<unsigned> diskSectorOrder;
|
||||
|
||||
/* Sector IDs in logical order. */
|
||||
std::vector<unsigned> logicalSectorOrder;
|
||||
|
||||
/* Sector IDs in filesystem order. */
|
||||
/* Sector IDs in filesystem order. This is the order in which the filesystem
|
||||
* uses them. */
|
||||
std::vector<unsigned> filesystemSectorOrder;
|
||||
|
||||
/* Mapping of filesystem order to logical order. */
|
||||
std::map<unsigned, unsigned> filesystemToLogicalSectorMap;
|
||||
/* Mapping of filesystem order to natural order. */
|
||||
std::map<unsigned, unsigned> filesystemToNaturalSectorMap;
|
||||
|
||||
/* Mapping of logical order to filesystem order. */
|
||||
std::map<unsigned, unsigned> logicalToFilesystemSectorMap;
|
||||
/* Mapping of natural order to filesystem order. */
|
||||
std::map<unsigned, unsigned> naturalToFilesystemSectorMap;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
342
lib/proto.cc
@@ -4,229 +4,245 @@
|
||||
#include "fmt/format.h"
|
||||
#include <regex>
|
||||
|
||||
ConfigProto config = []() {
|
||||
ConfigProto config;
|
||||
config.mutable_drive()->set_drive(0);
|
||||
config.mutable_drive()->set_drive(0);
|
||||
return config;
|
||||
ConfigProto config = []()
|
||||
{
|
||||
ConfigProto config;
|
||||
config.mutable_drive()->set_drive(0);
|
||||
config.mutable_drive()->set_drive(0);
|
||||
return config;
|
||||
}();
|
||||
|
||||
static double toDouble(const std::string& value)
|
||||
{
|
||||
size_t idx;
|
||||
double d = std::stod(value, &idx);
|
||||
if (value[idx] != '\0')
|
||||
Error() << fmt::format("invalid number '{}'", value);
|
||||
return d;
|
||||
size_t idx;
|
||||
double d = std::stod(value, &idx);
|
||||
if (value[idx] != '\0')
|
||||
Error() << fmt::format("invalid number '{}'", value);
|
||||
return d;
|
||||
}
|
||||
|
||||
static int64_t toInt64(const std::string& value)
|
||||
{
|
||||
size_t idx;
|
||||
int64_t d = std::stoll(value, &idx);
|
||||
if (value[idx] != '\0')
|
||||
Error() << fmt::format("invalid number '{}'", value);
|
||||
return d;
|
||||
size_t idx;
|
||||
int64_t d = std::stoll(value, &idx);
|
||||
if (value[idx] != '\0')
|
||||
Error() << fmt::format("invalid number '{}'", value);
|
||||
return d;
|
||||
}
|
||||
|
||||
static uint64_t toUint64(const std::string& value)
|
||||
{
|
||||
size_t idx;
|
||||
uint64_t d = std::stoull(value, &idx);
|
||||
if (value[idx] != '\0')
|
||||
Error() << fmt::format("invalid number '{}'", value);
|
||||
return d;
|
||||
size_t idx;
|
||||
uint64_t d = std::stoull(value, &idx);
|
||||
if (value[idx] != '\0')
|
||||
Error() << fmt::format("invalid number '{}'", value);
|
||||
return d;
|
||||
}
|
||||
|
||||
void setRange(RangeProto* range, const std::string& data)
|
||||
{
|
||||
static const std::regex DATA_REGEX("([0-9]+)(?:(?:-([0-9]+))|(?:\\+([0-9]+)))?(?:x([0-9]+))?");
|
||||
static const std::regex DATA_REGEX(
|
||||
"([0-9]+)(?:(?:-([0-9]+))|(?:\\+([0-9]+)))?(?:x([0-9]+))?");
|
||||
|
||||
std::smatch dmatch;
|
||||
if (!std::regex_match(data, dmatch, DATA_REGEX))
|
||||
Error() << "invalid range '" << data << "'";
|
||||
|
||||
int start = std::stoi(dmatch[1]);
|
||||
range->set_start(start);
|
||||
range->set_end(start);
|
||||
range->clear_step();
|
||||
if (!dmatch[2].str().empty())
|
||||
range->set_end(std::stoi(dmatch[2]));
|
||||
if (!dmatch[3].str().empty())
|
||||
range->set_end(std::stoi(dmatch[3]) - range->start());
|
||||
if (!dmatch[4].str().empty())
|
||||
range->set_step(std::stoi(dmatch[4]));
|
||||
std::smatch dmatch;
|
||||
if (!std::regex_match(data, dmatch, DATA_REGEX))
|
||||
Error() << "invalid range '" << data << "'";
|
||||
|
||||
int start = std::stoi(dmatch[1]);
|
||||
range->set_start(start);
|
||||
range->set_end(start);
|
||||
range->clear_step();
|
||||
if (!dmatch[2].str().empty())
|
||||
range->set_end(std::stoi(dmatch[2]));
|
||||
if (!dmatch[3].str().empty())
|
||||
range->set_end(std::stoi(dmatch[3]) - range->start());
|
||||
if (!dmatch[4].str().empty())
|
||||
range->set_step(std::stoi(dmatch[4]));
|
||||
}
|
||||
|
||||
ProtoField resolveProtoPath(google::protobuf::Message* message, const std::string& path)
|
||||
ProtoField resolveProtoPath(
|
||||
google::protobuf::Message* message, const std::string& path)
|
||||
{
|
||||
std::string::size_type dot = path.rfind('.');
|
||||
std::string leading = (dot == std::string::npos) ? "" : path.substr(0, dot);
|
||||
std::string trailing = (dot == std::string::npos) ? path : path.substr(dot+1);
|
||||
std::string::size_type dot = path.rfind('.');
|
||||
std::string leading = (dot == std::string::npos) ? "" : path.substr(0, dot);
|
||||
std::string trailing =
|
||||
(dot == std::string::npos) ? path : path.substr(dot + 1);
|
||||
|
||||
const auto* descriptor = message->GetDescriptor();
|
||||
const auto* descriptor = message->GetDescriptor();
|
||||
|
||||
std::string item;
|
||||
std::string item;
|
||||
std::stringstream ss(leading);
|
||||
while (std::getline(ss, item, '.'))
|
||||
{
|
||||
const auto* field = descriptor->FindFieldByName(item);
|
||||
if (!field)
|
||||
Error() << fmt::format("no such config field '{}' in '{}'", item, path);
|
||||
if (field->type() != google::protobuf::FieldDescriptor::TYPE_MESSAGE)
|
||||
Error() << fmt::format("config field '{}' in '{}' is not a message", item, path);
|
||||
{
|
||||
const auto* field = descriptor->FindFieldByName(item);
|
||||
if (!field)
|
||||
Error() << fmt::format(
|
||||
"no such config field '{}' in '{}'", item, path);
|
||||
if (field->type() != google::protobuf::FieldDescriptor::TYPE_MESSAGE)
|
||||
Error() << fmt::format(
|
||||
"config field '{}' in '{}' is not a message", item, path);
|
||||
|
||||
const auto* reflection = message->GetReflection();
|
||||
switch (field->label())
|
||||
{
|
||||
case google::protobuf::FieldDescriptor::LABEL_OPTIONAL:
|
||||
message = reflection->MutableMessage(message, field);
|
||||
break;
|
||||
const auto* reflection = message->GetReflection();
|
||||
switch (field->label())
|
||||
{
|
||||
case google::protobuf::FieldDescriptor::LABEL_OPTIONAL:
|
||||
message = reflection->MutableMessage(message, field);
|
||||
break;
|
||||
|
||||
case google::protobuf::FieldDescriptor::LABEL_REPEATED:
|
||||
if (reflection->FieldSize(*message, field) == 0)
|
||||
message = reflection->AddMessage(message, field);
|
||||
else
|
||||
message = reflection->MutableRepeatedMessage(message, field, 0);
|
||||
break;
|
||||
case google::protobuf::FieldDescriptor::LABEL_REPEATED:
|
||||
if (reflection->FieldSize(*message, field) == 0)
|
||||
message = reflection->AddMessage(message, field);
|
||||
else
|
||||
message =
|
||||
reflection->MutableRepeatedMessage(message, field, 0);
|
||||
break;
|
||||
|
||||
default:
|
||||
Error() << "bad proto label " << field->label();
|
||||
}
|
||||
default:
|
||||
Error() << "bad proto label " << field->label();
|
||||
}
|
||||
|
||||
descriptor = message->GetDescriptor();
|
||||
descriptor = message->GetDescriptor();
|
||||
}
|
||||
|
||||
const auto* field = descriptor->FindFieldByName(trailing);
|
||||
if (!field)
|
||||
Error() << fmt::format("no such config field '{}' in '{}'", trailing, path);
|
||||
const auto* field = descriptor->FindFieldByName(trailing);
|
||||
if (!field)
|
||||
Error() << fmt::format(
|
||||
"no such config field '{}' in '{}'", trailing, path);
|
||||
|
||||
return std::make_pair(message, field);
|
||||
return std::make_pair(message, field);
|
||||
}
|
||||
|
||||
void setProtoFieldFromString(ProtoField& protoField, const std::string& value)
|
||||
{
|
||||
google::protobuf::Message* message = protoField.first;
|
||||
const google::protobuf::FieldDescriptor* field = protoField.second;
|
||||
google::protobuf::Message* message = protoField.first;
|
||||
const google::protobuf::FieldDescriptor* field = protoField.second;
|
||||
|
||||
const auto* reflection = message->GetReflection();
|
||||
switch (field->type())
|
||||
{
|
||||
case google::protobuf::FieldDescriptor::TYPE_DOUBLE:
|
||||
reflection->SetDouble(message, field, toDouble(value));
|
||||
break;
|
||||
const auto* reflection = message->GetReflection();
|
||||
switch (field->type())
|
||||
{
|
||||
case google::protobuf::FieldDescriptor::TYPE_DOUBLE:
|
||||
reflection->SetDouble(message, field, toDouble(value));
|
||||
break;
|
||||
|
||||
case google::protobuf::FieldDescriptor::TYPE_INT32:
|
||||
reflection->SetInt32(message, field, toInt64(value));
|
||||
break;
|
||||
case google::protobuf::FieldDescriptor::TYPE_INT32:
|
||||
reflection->SetInt32(message, field, toInt64(value));
|
||||
break;
|
||||
|
||||
case google::protobuf::FieldDescriptor::TYPE_INT64:
|
||||
reflection->SetInt64(message, field, toInt64(value));
|
||||
break;
|
||||
case google::protobuf::FieldDescriptor::TYPE_INT64:
|
||||
reflection->SetInt64(message, field, toInt64(value));
|
||||
break;
|
||||
|
||||
case google::protobuf::FieldDescriptor::TYPE_UINT32:
|
||||
reflection->SetUInt32(message, field, toUint64(value));
|
||||
break;
|
||||
case google::protobuf::FieldDescriptor::TYPE_UINT32:
|
||||
reflection->SetUInt32(message, field, toUint64(value));
|
||||
break;
|
||||
|
||||
case google::protobuf::FieldDescriptor::TYPE_UINT64:
|
||||
reflection->SetUInt64(message, field, toUint64(value));
|
||||
break;
|
||||
case google::protobuf::FieldDescriptor::TYPE_UINT64:
|
||||
reflection->SetUInt64(message, field, toUint64(value));
|
||||
break;
|
||||
|
||||
case google::protobuf::FieldDescriptor::TYPE_STRING:
|
||||
reflection->SetString(message, field, value);
|
||||
break;
|
||||
case google::protobuf::FieldDescriptor::TYPE_STRING:
|
||||
reflection->SetString(message, field, value);
|
||||
break;
|
||||
|
||||
case google::protobuf::FieldDescriptor::TYPE_BOOL:
|
||||
{
|
||||
static const std::map<std::string, bool> boolvalues = {
|
||||
{ "false", false },
|
||||
{ "f", false },
|
||||
{ "no", false },
|
||||
{ "n", false },
|
||||
{ "0", false },
|
||||
{ "true", true },
|
||||
{ "t", true },
|
||||
{ "yes", true },
|
||||
{ "y", true },
|
||||
{ "1", true },
|
||||
};
|
||||
case google::protobuf::FieldDescriptor::TYPE_BOOL:
|
||||
{
|
||||
static const std::map<std::string, bool> boolvalues = {
|
||||
{"false", false},
|
||||
{"f", false},
|
||||
{"no", false},
|
||||
{"n", false},
|
||||
{"0", false},
|
||||
{"true", true },
|
||||
{"t", true },
|
||||
{"yes", true },
|
||||
{"y", true },
|
||||
{"1", true },
|
||||
};
|
||||
|
||||
const auto& it = boolvalues.find(value);
|
||||
if (it == boolvalues.end())
|
||||
Error() << "invalid boolean value";
|
||||
reflection->SetBool(message, field, it->second);
|
||||
break;
|
||||
}
|
||||
const auto& it = boolvalues.find(value);
|
||||
if (it == boolvalues.end())
|
||||
Error() << "invalid boolean value";
|
||||
reflection->SetBool(message, field, it->second);
|
||||
break;
|
||||
}
|
||||
|
||||
case google::protobuf::FieldDescriptor::TYPE_ENUM:
|
||||
{
|
||||
const auto* enumfield = field->enum_type();
|
||||
const auto* enumvalue = enumfield->FindValueByName(value);
|
||||
if (!enumvalue)
|
||||
Error() << fmt::format("unrecognised enum value '{}'", value);
|
||||
case google::protobuf::FieldDescriptor::TYPE_ENUM:
|
||||
{
|
||||
const auto* enumfield = field->enum_type();
|
||||
const auto* enumvalue = enumfield->FindValueByName(value);
|
||||
if (!enumvalue)
|
||||
Error() << fmt::format("unrecognised enum value '{}'", value);
|
||||
|
||||
reflection->SetEnum(message, field, enumvalue);
|
||||
break;
|
||||
}
|
||||
reflection->SetEnum(message, field, enumvalue);
|
||||
break;
|
||||
}
|
||||
|
||||
case google::protobuf::FieldDescriptor::TYPE_MESSAGE:
|
||||
if (field->message_type() == RangeProto::descriptor())
|
||||
{
|
||||
setRange((RangeProto*)reflection->MutableMessage(message, field), value);
|
||||
break;
|
||||
}
|
||||
if (field->containing_oneof() && value.empty())
|
||||
{
|
||||
reflection->MutableMessage(message, field);
|
||||
break;
|
||||
}
|
||||
/* fall through */
|
||||
default:
|
||||
Error() << "can't set this config value type";
|
||||
}
|
||||
case google::protobuf::FieldDescriptor::TYPE_MESSAGE:
|
||||
if (field->message_type() == RangeProto::descriptor())
|
||||
{
|
||||
setRange(
|
||||
(RangeProto*)reflection->MutableMessage(message, field),
|
||||
value);
|
||||
break;
|
||||
}
|
||||
if (field->containing_oneof() && value.empty())
|
||||
{
|
||||
reflection->MutableMessage(message, field);
|
||||
break;
|
||||
}
|
||||
/* fall through */
|
||||
default:
|
||||
Error() << "can't set this config value type";
|
||||
}
|
||||
}
|
||||
|
||||
void setProtoByString(google::protobuf::Message* message, const std::string& path, const std::string& value)
|
||||
void setProtoByString(google::protobuf::Message* message,
|
||||
const std::string& path,
|
||||
const std::string& value)
|
||||
{
|
||||
ProtoField protoField = resolveProtoPath(message, path);
|
||||
setProtoFieldFromString(protoField, value);
|
||||
ProtoField protoField = resolveProtoPath(message, path);
|
||||
setProtoFieldFromString(protoField, value);
|
||||
}
|
||||
|
||||
std::set<unsigned> iterate(const RangeProto& range)
|
||||
{
|
||||
std::set<unsigned> set;
|
||||
int end = range.has_end()? range.end() : range.start();
|
||||
for (unsigned i=range.start(); i<=end; i+=range.step())
|
||||
set.insert(i);
|
||||
return set;
|
||||
std::set<unsigned> set;
|
||||
int end = range.has_end() ? range.end() : range.start();
|
||||
for (unsigned i = range.start(); i <= end; i += range.step())
|
||||
set.insert(i);
|
||||
return set;
|
||||
}
|
||||
|
||||
std::set<unsigned> iterate(unsigned start, unsigned count)
|
||||
{
|
||||
std::set<unsigned> set;
|
||||
for (unsigned i=0; i<count; i++)
|
||||
set.insert(start + i);
|
||||
return set;
|
||||
std::set<unsigned> set;
|
||||
for (unsigned i = 0; i < count; i++)
|
||||
set.insert(start + i);
|
||||
return set;
|
||||
}
|
||||
|
||||
std::map<std::string, const google::protobuf::FieldDescriptor*> findAllProtoFields(google::protobuf::Message* message)
|
||||
std::map<std::string, const google::protobuf::FieldDescriptor*>
|
||||
findAllProtoFields(google::protobuf::Message* message)
|
||||
{
|
||||
std::map<std::string, const google::protobuf::FieldDescriptor*> fields;
|
||||
const auto* descriptor = message->GetDescriptor();
|
||||
std::map<std::string, const google::protobuf::FieldDescriptor*> fields;
|
||||
const auto* descriptor = message->GetDescriptor();
|
||||
|
||||
std::function<void(const google::protobuf::Descriptor*, const std::string&)> recurse =
|
||||
[&](auto* d, const auto& s) {
|
||||
for (int i=0; i<d->field_count(); i++)
|
||||
{
|
||||
const google::protobuf::FieldDescriptor* f = d->field(i);
|
||||
std::string n = s + f->name();
|
||||
if (f->type() == google::protobuf::FieldDescriptor::TYPE_MESSAGE)
|
||||
recurse(f->message_type(), n + ".");
|
||||
fields[n] = f;
|
||||
}
|
||||
};
|
||||
std::function<void(const google::protobuf::Descriptor*, const std::string&)>
|
||||
recurse = [&](auto* d, const auto& s)
|
||||
{
|
||||
for (int i = 0; i < d->field_count(); i++)
|
||||
{
|
||||
const google::protobuf::FieldDescriptor* f = d->field(i);
|
||||
std::string n = s + f->name();
|
||||
|
||||
recurse(descriptor, "");
|
||||
return fields;
|
||||
if (f->options().GetExtension(::recurse) &&
|
||||
(f->type() == google::protobuf::FieldDescriptor::TYPE_MESSAGE))
|
||||
recurse(f->message_type(), n + ".");
|
||||
|
||||
fields[n] = f;
|
||||
}
|
||||
};
|
||||
|
||||
recurse(descriptor, "");
|
||||
return fields;
|
||||
}
|
||||
|
||||
|
||||
1273
lib/readerwriter.cc
@@ -16,7 +16,7 @@ static USB* usb = NULL;
|
||||
|
||||
USB::~USB() {}
|
||||
|
||||
static std::unique_ptr<CandidateDevice> selectDevice()
|
||||
static std::shared_ptr<CandidateDevice> selectDevice()
|
||||
{
|
||||
auto candidates = findUsbDevices();
|
||||
if (candidates.size() == 0)
|
||||
@@ -29,14 +29,14 @@ static std::unique_ptr<CandidateDevice> selectDevice()
|
||||
for (auto& c : candidates)
|
||||
{
|
||||
if (c->serial == wantedSerial)
|
||||
return std::move(c);
|
||||
return c;
|
||||
}
|
||||
Error() << "serial number not found (try without one to list or "
|
||||
"autodetect devices)";
|
||||
}
|
||||
|
||||
if (candidates.size() == 1)
|
||||
return std::move(candidates[0]);
|
||||
return candidates[0];
|
||||
|
||||
std::cerr << "More than one device detected; use --usb.serial=<serial> to "
|
||||
"select one:\n";
|
||||
|
||||
@@ -24,9 +24,9 @@ static const std::string get_serial_number(const libusbp::device& device)
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::unique_ptr<CandidateDevice>> findUsbDevices()
|
||||
std::vector<std::shared_ptr<CandidateDevice>> findUsbDevices()
|
||||
{
|
||||
std::vector<std::unique_ptr<CandidateDevice>> candidates;
|
||||
std::vector<std::shared_ptr<CandidateDevice>> candidates;
|
||||
for (const auto& it : libusbp::list_connected_devices())
|
||||
{
|
||||
auto candidate = std::make_unique<CandidateDevice>();
|
||||
@@ -42,7 +42,10 @@ std::vector<std::unique_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));
|
||||
}
|
||||
@@ -50,3 +53,19 @@ std::vector<std::unique_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";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,15 +4,24 @@
|
||||
#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::unique_ptr<CandidateDevice>> findUsbDevices();
|
||||
extern std::vector<std::shared_ptr<CandidateDevice>> findUsbDevices();
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include "lib/bytes.h"
|
||||
#include <fmt/format.h>
|
||||
#include <iomanip>
|
||||
#include <fstream>
|
||||
|
||||
bool emergencyStop = false;
|
||||
|
||||
@@ -190,3 +191,10 @@ std::string tohex(const std::string& s)
|
||||
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
bool doesFileExist(const std::string& filename)
|
||||
{
|
||||
std::ifstream f(filename);
|
||||
return f.good();
|
||||
}
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ extern std::string toIso8601(time_t t);
|
||||
extern std::string quote(const std::string& s);
|
||||
extern std::string unhex(const std::string& s);
|
||||
extern std::string tohex(const std::string& s);
|
||||
extern bool doesFileExist(const std::string& filename);
|
||||
|
||||
/* If set, any running job will terminate as soon as possible (with an error).
|
||||
*/
|
||||
|
||||
182
lib/vfs/appledos.cc
Normal file
@@ -0,0 +1,182 @@
|
||||
#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);
|
||||
}
|
||||
@@ -31,7 +31,18 @@ public:
|
||||
{
|
||||
ByteReader br(bytes);
|
||||
filename = br.read(8);
|
||||
filename = filename.substr(0, filename.find(' '));
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
path = {filename};
|
||||
|
||||
brotherType = br.read_8();
|
||||
@@ -91,7 +102,7 @@ public:
|
||||
for (int d = 0; d < SECTOR_SIZE / 16; d++)
|
||||
{
|
||||
Bytes buffer = bytes.slice(d * 16, 16);
|
||||
if (buffer[0] == 0xf0)
|
||||
if (buffer[0] & 0x80)
|
||||
continue;
|
||||
|
||||
auto de = std::make_shared<Brother120Dirent>(buffer);
|
||||
|
||||
152
lib/vfs/cpmfs.cc
@@ -3,77 +3,77 @@
|
||||
#include "lib/config.pb.h"
|
||||
#include <fmt/format.h>
|
||||
|
||||
class Entry
|
||||
{
|
||||
public:
|
||||
Entry(const Bytes& bytes, int map_entry_size)
|
||||
{
|
||||
user = bytes[0] & 0x0f;
|
||||
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << (char)(user + '0') << ':';
|
||||
|
||||
for (int i = 1; i <= 8; i++)
|
||||
{
|
||||
uint8_t c = bytes[i] & 0x7f;
|
||||
if (c == ' ')
|
||||
break;
|
||||
ss << (char)c;
|
||||
}
|
||||
for (int i = 9; i <= 11; i++)
|
||||
{
|
||||
uint8_t c = bytes[i] & 0x7f;
|
||||
if (c == ' ')
|
||||
break;
|
||||
if (i == 9)
|
||||
ss << '.';
|
||||
ss << (char)c;
|
||||
}
|
||||
filename = ss.str();
|
||||
}
|
||||
|
||||
{
|
||||
std::stringstream ss;
|
||||
if (bytes[9] & 0x80)
|
||||
ss << 'R';
|
||||
if (bytes[10] & 0x80)
|
||||
ss << 'S';
|
||||
if (bytes[11] & 0x80)
|
||||
ss << 'A';
|
||||
mode = ss.str();
|
||||
}
|
||||
|
||||
extent = bytes[12] | (bytes[14] << 5);
|
||||
records = bytes[15];
|
||||
|
||||
ByteReader br(bytes);
|
||||
br.seek(16);
|
||||
switch (map_entry_size)
|
||||
{
|
||||
case 1:
|
||||
for (int i = 0; i < 16; i++)
|
||||
allocation_map.push_back(br.read_8());
|
||||
break;
|
||||
|
||||
case 2:
|
||||
for (int i = 0; i < 8; i++)
|
||||
allocation_map.push_back(br.read_le16());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
std::string filename;
|
||||
std::string mode;
|
||||
unsigned user;
|
||||
unsigned extent;
|
||||
unsigned records;
|
||||
std::vector<unsigned> allocation_map;
|
||||
};
|
||||
|
||||
class CpmFsFilesystem : public Filesystem
|
||||
{
|
||||
class Entry
|
||||
{
|
||||
public:
|
||||
Entry(const Bytes& bytes, int map_entry_size)
|
||||
{
|
||||
user = bytes[0] & 0x0f;
|
||||
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << (char)(user + '0') << ':';
|
||||
|
||||
for (int i = 1; i <= 8; i++)
|
||||
{
|
||||
uint8_t c = bytes[i] & 0x7f;
|
||||
if (c == ' ')
|
||||
break;
|
||||
ss << (char)c;
|
||||
}
|
||||
for (int i = 9; i <= 11; i++)
|
||||
{
|
||||
uint8_t c = bytes[i] & 0x7f;
|
||||
if (c == ' ')
|
||||
break;
|
||||
if (i == 9)
|
||||
ss << '.';
|
||||
ss << (char)c;
|
||||
}
|
||||
filename = ss.str();
|
||||
}
|
||||
|
||||
{
|
||||
std::stringstream ss;
|
||||
if (bytes[9] & 0x80)
|
||||
ss << 'R';
|
||||
if (bytes[10] & 0x80)
|
||||
ss << 'S';
|
||||
if (bytes[11] & 0x80)
|
||||
ss << 'A';
|
||||
mode = ss.str();
|
||||
}
|
||||
|
||||
extent = bytes[12] | (bytes[14] << 5);
|
||||
records = bytes[15];
|
||||
|
||||
ByteReader br(bytes);
|
||||
br.seek(16);
|
||||
switch (map_entry_size)
|
||||
{
|
||||
case 1:
|
||||
for (int i = 0; i < 16; i++)
|
||||
allocation_map.push_back(br.read_8());
|
||||
break;
|
||||
|
||||
case 2:
|
||||
for (int i = 0; i < 8; i++)
|
||||
allocation_map.push_back(br.read_le16());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
std::string filename;
|
||||
std::string mode;
|
||||
unsigned user;
|
||||
unsigned extent;
|
||||
unsigned records;
|
||||
std::vector<unsigned> allocation_map;
|
||||
};
|
||||
|
||||
public:
|
||||
CpmFsFilesystem(
|
||||
const CpmFsProto& config, std::shared_ptr<SectorInterface> sectors):
|
||||
@@ -188,7 +188,6 @@ 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);
|
||||
@@ -196,9 +195,10 @@ public:
|
||||
continue;
|
||||
if (path[0] != entry->filename)
|
||||
continue;
|
||||
if (entry->extent > logicalExtent)
|
||||
moreExtents = true;
|
||||
if (entry->extent == logicalExtent)
|
||||
if (entry->extent < logicalExtent)
|
||||
continue;
|
||||
if ((entry->extent & ~_logicalExtentMask) ==
|
||||
(logicalExtent & ~_logicalExtentMask))
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -212,10 +212,10 @@ public:
|
||||
/* Copy the data out. */
|
||||
|
||||
int i =
|
||||
(entry->extent & ~_logicalExtentMask) * _blocksPerLogicalExtent;
|
||||
(logicalExtent & _logicalExtentMask) * _blocksPerLogicalExtent;
|
||||
unsigned records =
|
||||
(entry->extent == logicalExtent) ? entry->records : 128;
|
||||
while ((records != 0) && (i != entry->allocation_map.size()))
|
||||
while (records != 0)
|
||||
{
|
||||
Bytes block;
|
||||
unsigned blockid = entry->allocation_map[i];
|
||||
@@ -265,7 +265,7 @@ private:
|
||||
physicalExtentSize = _config.block_size() * 8;
|
||||
}
|
||||
_logicalExtentsPerEntry = physicalExtentSize / 16384;
|
||||
_logicalExtentMask = (1 << _logicalExtentsPerEntry) - 1;
|
||||
_logicalExtentMask = _logicalExtentsPerEntry - 1;
|
||||
_blocksPerLogicalExtent = 16384 / _config.block_size();
|
||||
|
||||
_directory = getCpmBlock(0, _dirBlocks);
|
||||
|
||||
@@ -246,11 +246,11 @@ public:
|
||||
switch (cmd)
|
||||
{
|
||||
case GET_SECTOR_SIZE:
|
||||
*(DWORD*)buffer = getLogicalSectorSize();
|
||||
*(WORD*)buffer = getLogicalSectorSize();
|
||||
break;
|
||||
|
||||
case GET_SECTOR_COUNT:
|
||||
*(DWORD*)buffer = getLogicalSectorCount();
|
||||
*(LBA_t*)buffer = getLogicalSectorCount();
|
||||
break;
|
||||
|
||||
case CTRL_SYNC:
|
||||
|
||||
@@ -72,7 +72,7 @@ public:
|
||||
if (!imageContainsAllSectorsOf(_changedSectors,
|
||||
track,
|
||||
side,
|
||||
trackLayout->logicalSectorOrder))
|
||||
trackLayout->naturalSectorOrder))
|
||||
{
|
||||
/* 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->logicalSectorOrder)
|
||||
for (unsigned sectorId : trackLayout->naturalSectorOrder)
|
||||
{
|
||||
if (!_changedSectors.contains(track, side, sectorId))
|
||||
_changedSectors.put(track, side, sectorId)->data =
|
||||
|
||||
296
lib/vfs/philefs.cc
Normal file
@@ -0,0 +1,296 @@
|
||||
#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);
|
||||
}
|
||||
211
lib/vfs/smaky6fs.cc
Normal file
@@ -0,0 +1,211 @@
|
||||
#include "lib/globals.h"
|
||||
#include "lib/vfs/vfs.h"
|
||||
#include "lib/config.pb.h"
|
||||
#include "lib/utils.h"
|
||||
#include <fmt/format.h>
|
||||
|
||||
/* A directory entry looks like:
|
||||
*
|
||||
* 00-09: ten byte filename FFFFFFFF.EE
|
||||
* 0a-0b: word: start sector
|
||||
* 0c-17: unknown
|
||||
*/
|
||||
|
||||
class Smaky6Filesystem : public Filesystem
|
||||
{
|
||||
class Entry
|
||||
{
|
||||
public:
|
||||
Entry(const Bytes& bytes)
|
||||
{
|
||||
ByteReader br(bytes);
|
||||
br.seek(10);
|
||||
startSector = br.read_le16();
|
||||
endSector = br.read_le16();
|
||||
}
|
||||
|
||||
public:
|
||||
std::string filename;
|
||||
std::string mode;
|
||||
uint16_t startSector;
|
||||
uint16_t endSector;
|
||||
};
|
||||
|
||||
class SmakyDirent : public Dirent
|
||||
{
|
||||
public:
|
||||
SmakyDirent(const Bytes& dbuf)
|
||||
{
|
||||
{
|
||||
std::stringstream ss;
|
||||
|
||||
for (int i = 0; i <= 7; i++)
|
||||
{
|
||||
uint8_t c = dbuf[i] & 0x7f;
|
||||
if (c == ' ')
|
||||
break;
|
||||
ss << (char)c;
|
||||
}
|
||||
for (int i = 8; i <= 9; i++)
|
||||
{
|
||||
uint8_t c = dbuf[i] & 0x7f;
|
||||
if (c == ' ')
|
||||
break;
|
||||
if (i == 8)
|
||||
ss << '.';
|
||||
ss << (char)c;
|
||||
}
|
||||
filename = ss.str();
|
||||
}
|
||||
|
||||
std::string metadataBytes;
|
||||
{
|
||||
std::stringstream ss;
|
||||
|
||||
for (int i = 10; i < 0x18; i++)
|
||||
ss << fmt::format("{:02x} ", (uint8_t)dbuf[i]);
|
||||
|
||||
metadataBytes = ss.str();
|
||||
}
|
||||
|
||||
ByteReader br(dbuf);
|
||||
|
||||
br.skip(10); /* filename */
|
||||
startSector = br.read_le16();
|
||||
endSector = br.read_le16();
|
||||
br.skip(2); /* unknown */
|
||||
lastSectorLength = br.read_le16();
|
||||
|
||||
file_type = TYPE_FILE;
|
||||
length = (endSector - startSector - 1) * 256 + lastSectorLength;
|
||||
|
||||
path = {filename};
|
||||
attributes[Filesystem::FILENAME] = filename;
|
||||
attributes[Filesystem::LENGTH] = std::to_string(length);
|
||||
attributes[Filesystem::FILE_TYPE] = "file";
|
||||
attributes[Filesystem::MODE] = "";
|
||||
attributes["smaky6.start_sector"] = std::to_string(startSector);
|
||||
attributes["smaky6.end_sector"] = std::to_string(endSector);
|
||||
attributes["smaky6.sectors"] =
|
||||
std::to_string(endSector - startSector);
|
||||
attributes["smaky6.metadata_bytes"] = metadataBytes;
|
||||
}
|
||||
|
||||
public:
|
||||
unsigned startSector;
|
||||
unsigned endSector;
|
||||
unsigned lastSectorLength;
|
||||
};
|
||||
|
||||
friend class Directory;
|
||||
class Directory
|
||||
{
|
||||
public:
|
||||
Directory(Smaky6Filesystem* fs)
|
||||
{
|
||||
/* Read the directory. */
|
||||
|
||||
auto bytes = fs->getLogicalSector(0, 3);
|
||||
ByteReader br(bytes);
|
||||
|
||||
for (int i = 0; i < 32; i++)
|
||||
{
|
||||
auto dbuf = bytes.slice(i * 0x18, 0x18);
|
||||
if (dbuf[0])
|
||||
{
|
||||
auto de = std::make_shared<SmakyDirent>(dbuf);
|
||||
dirents.push_back(de);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<SmakyDirent> findFile(const std::string& filename)
|
||||
{
|
||||
for (auto& de : dirents)
|
||||
if (de->filename == filename)
|
||||
return de;
|
||||
|
||||
throw FileNotFoundException();
|
||||
}
|
||||
|
||||
public:
|
||||
std::vector<std::shared_ptr<SmakyDirent>> dirents;
|
||||
};
|
||||
|
||||
public:
|
||||
Smaky6Filesystem(
|
||||
const Smaky6FsProto& config, std::shared_ptr<SectorInterface> sectors):
|
||||
Filesystem(sectors),
|
||||
_config(config)
|
||||
{
|
||||
}
|
||||
|
||||
uint32_t capabilities() const
|
||||
{
|
||||
return OP_LIST | OP_GETFILE | OP_GETFSDATA | OP_GETDIRENT;
|
||||
}
|
||||
|
||||
FilesystemStatus check() override
|
||||
{
|
||||
return FS_OK;
|
||||
}
|
||||
|
||||
std::map<std::string, std::string> getMetadata() override
|
||||
{
|
||||
Directory dir(this);
|
||||
unsigned usedBlocks = 3;
|
||||
for (auto& de : dir.dirents)
|
||||
usedBlocks += (de->endSector - de->startSector);
|
||||
|
||||
std::map<std::string, std::string> attributes;
|
||||
attributes[VOLUME_NAME] = "";
|
||||
attributes[TOTAL_BLOCKS] = std::to_string(getLogicalSectorCount());
|
||||
attributes[USED_BLOCKS] = std::to_string(usedBlocks);
|
||||
attributes[BLOCK_SIZE] = std::to_string(getLogicalSectorSize(0, 0));
|
||||
return attributes;
|
||||
}
|
||||
|
||||
std::vector<std::shared_ptr<Dirent>> list(const Path& path) override
|
||||
{
|
||||
if (!path.empty())
|
||||
throw FileNotFoundException();
|
||||
|
||||
Directory dir(this);
|
||||
std::vector<std::shared_ptr<Dirent>> result;
|
||||
for (auto& de : dir.dirents)
|
||||
result.push_back(de);
|
||||
return result;
|
||||
}
|
||||
|
||||
std::shared_ptr<Dirent> getDirent(const Path& path) override
|
||||
{
|
||||
Directory dir(this);
|
||||
if (path.size() != 1)
|
||||
throw BadPathException();
|
||||
|
||||
return dir.findFile(path[0]);
|
||||
}
|
||||
|
||||
Bytes getFile(const Path& path) override
|
||||
{
|
||||
if (path.size() != 1)
|
||||
throw BadPathException(path);
|
||||
|
||||
Directory dir(this);
|
||||
auto de = dir.findFile(path[0]);
|
||||
|
||||
Bytes data =
|
||||
getLogicalSector(de->startSector, de->endSector - de->startSector);
|
||||
data = data.slice(0, de->length);
|
||||
return data;
|
||||
}
|
||||
|
||||
private:
|
||||
const Smaky6FsProto& _config;
|
||||
};
|
||||
|
||||
std::unique_ptr<Filesystem> Filesystem::createSmaky6Filesystem(
|
||||
const FilesystemProto& config, std::shared_ptr<SectorInterface> sectors)
|
||||
{
|
||||
return std::make_unique<Smaky6Filesystem>(config.smaky6(), sectors);
|
||||
}
|
||||
@@ -177,32 +177,41 @@ Filesystem::Filesystem(std::shared_ptr<SectorInterface> sectors):
|
||||
std::unique_ptr<Filesystem> Filesystem::createFilesystem(
|
||||
const FilesystemProto& config, std::shared_ptr<SectorInterface> image)
|
||||
{
|
||||
switch (config.filesystem_case())
|
||||
switch (config.type())
|
||||
{
|
||||
case FilesystemProto::kBrother120:
|
||||
case FilesystemProto::BROTHER120:
|
||||
return Filesystem::createBrother120Filesystem(config, image);
|
||||
|
||||
case FilesystemProto::kAcorndfs:
|
||||
case FilesystemProto::ACORNDFS:
|
||||
return Filesystem::createAcornDfsFilesystem(config, image);
|
||||
|
||||
case FilesystemProto::kFatfs:
|
||||
case FilesystemProto::FATFS:
|
||||
return Filesystem::createFatFsFilesystem(config, image);
|
||||
|
||||
case FilesystemProto::kCpmfs:
|
||||
case FilesystemProto::CPMFS:
|
||||
return Filesystem::createCpmFsFilesystem(config, image);
|
||||
|
||||
case FilesystemProto::kAmigaffs:
|
||||
case FilesystemProto::AMIGAFFS:
|
||||
return Filesystem::createAmigaFfsFilesystem(config, image);
|
||||
|
||||
case FilesystemProto::kMachfs:
|
||||
case FilesystemProto::MACHFS:
|
||||
return Filesystem::createMacHfsFilesystem(config, image);
|
||||
|
||||
case FilesystemProto::kCbmfs:
|
||||
case FilesystemProto::CBMFS:
|
||||
return Filesystem::createCbmfsFilesystem(config, image);
|
||||
|
||||
case FilesystemProto::kProdos:
|
||||
case FilesystemProto::PRODOS:
|
||||
return Filesystem::createProdosFilesystem(config, image);
|
||||
|
||||
case FilesystemProto::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>();
|
||||
@@ -218,13 +227,12 @@ std::unique_ptr<Filesystem> Filesystem::createFilesystemFromConfig()
|
||||
std::shared_ptr<Decoder> decoder;
|
||||
std::shared_ptr<FluxSink> fluxSink;
|
||||
std::shared_ptr<Encoder> encoder;
|
||||
if (config.flux_source().source_case() !=
|
||||
FluxSourceProto::SOURCE_NOT_SET)
|
||||
if (config.flux_source().type() != FluxSourceProto::NOT_SET)
|
||||
{
|
||||
fluxSource = FluxSource::create(config.flux_source());
|
||||
decoder = Decoder::create(config.decoder());
|
||||
}
|
||||
if (config.flux_sink().has_drive())
|
||||
if (config.flux_sink().type() == FluxSinkProto::DRIVE)
|
||||
{
|
||||
fluxSink = FluxSink::create(config.flux_sink());
|
||||
encoder = Encoder::create(config.encoder());
|
||||
@@ -236,11 +244,10 @@ std::unique_ptr<Filesystem> Filesystem::createFilesystemFromConfig()
|
||||
{
|
||||
std::shared_ptr<ImageReader> reader;
|
||||
std::shared_ptr<ImageWriter> writer;
|
||||
if (config.image_reader().format_case() !=
|
||||
ImageReaderProto::FORMAT_NOT_SET)
|
||||
if ((config.image_reader().type() != ImageReaderProto::NOT_SET) &&
|
||||
doesFileExist(config.image_reader().filename()))
|
||||
reader = ImageReader::create(config.image_reader());
|
||||
if (config.image_writer().format_case() !=
|
||||
ImageWriterProto::FORMAT_NOT_SET)
|
||||
if (config.image_writer().type() != ImageWriterProto::NOT_SET)
|
||||
writer = ImageWriter::create(config.image_writer());
|
||||
|
||||
sectorInterface =
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#define VFS_H
|
||||
|
||||
#include "lib/bytes.h"
|
||||
#include <fmt/format.h>
|
||||
|
||||
class Sector;
|
||||
class Image;
|
||||
@@ -58,6 +59,11 @@ public:
|
||||
class BadPathException : public FilesystemException
|
||||
{
|
||||
public:
|
||||
BadPathException(const Path& path):
|
||||
FilesystemException(fmt::format("Bad path: '{}'", path.to_str()))
|
||||
{
|
||||
}
|
||||
|
||||
BadPathException(): FilesystemException("Bad path") {}
|
||||
|
||||
BadPathException(const std::string& msg): FilesystemException(msg) {}
|
||||
@@ -242,6 +248,12 @@ 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);
|
||||
|
||||
@@ -59,20 +59,53 @@ message CbmfsProto
|
||||
|
||||
message ProdosProto {}
|
||||
|
||||
// NEXT_TAG: 10
|
||||
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
|
||||
message FilesystemProto
|
||||
{
|
||||
oneof filesystem
|
||||
{
|
||||
AcornDfsProto acorndfs = 1;
|
||||
Brother120FsProto brother120 = 2;
|
||||
FatFsProto fatfs = 3;
|
||||
CpmFsProto cpmfs = 4;
|
||||
AmigaFfsProto amigaffs = 5;
|
||||
MacHfsProto machfs = 6;
|
||||
CbmfsProto cbmfs = 7;
|
||||
ProdosProto prodos = 8;
|
||||
enum FilesystemType {
|
||||
NOT_SET = 0;
|
||||
ACORNDFS = 1;
|
||||
BROTHER120 = 2;
|
||||
FATFS = 3;
|
||||
CPMFS = 4;
|
||||
AMIGAFFS = 5;
|
||||
MACHFS = 6;
|
||||
CBMFS = 7;
|
||||
PRODOS = 8;
|
||||
SMAKY6 = 9;
|
||||
APPLEDOS = 10;
|
||||
PHILE = 11;
|
||||
}
|
||||
|
||||
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"];
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ FLUXENGINE_SRCS = \
|
||||
src/fe-getfileinfo.cc \
|
||||
src/fe-inspect.cc \
|
||||
src/fe-ls.cc \
|
||||
src/fe-merge.cc \
|
||||
src/fe-mkdir.cc \
|
||||
src/fe-mv.cc \
|
||||
src/fe-rm.cc \
|
||||
|
||||
@@ -211,10 +211,10 @@ static void draw_x_graticules(Agg2D& painter, double x1, double y1, double x2, d
|
||||
|
||||
int mainAnalyseDriveResponse(int argc, const char* argv[])
|
||||
{
|
||||
config.mutable_flux_source()->mutable_drive();
|
||||
config.mutable_flux_source()->set_type(FluxSourceProto::DRIVE);
|
||||
flags.parseFlagsWithConfigFiles(argc, argv, {});
|
||||
|
||||
if (!config.flux_sink().has_drive())
|
||||
if (config.flux_sink().type() != FluxSinkProto::DRIVE)
|
||||
Error() << "this only makes sense with a real disk drive";
|
||||
|
||||
usbSetDrive(config.drive().drive(),
|
||||
|
||||
@@ -210,7 +210,7 @@ static nanoseconds_t guessClock(const Fluxmap& fluxmap)
|
||||
|
||||
int mainInspect(int argc, const char* argv[])
|
||||
{
|
||||
config.mutable_flux_source()->mutable_drive();
|
||||
config.mutable_flux_source()->set_type(FluxSourceProto::DRIVE);
|
||||
flags.parseFlagsWithConfigFiles(argc, argv, {});
|
||||
|
||||
std::unique_ptr<FluxSource> fluxSource(FluxSource::create(config.flux_source()));
|
||||
|
||||
65
src/fe-merge.cc
Normal file
@@ -0,0 +1,65 @@
|
||||
#include "globals.h"
|
||||
#include "flags.h"
|
||||
#include "fluxmap.h"
|
||||
#include "sector.h"
|
||||
#include "proto.h"
|
||||
#include "flux.h"
|
||||
#include "fl2.h"
|
||||
#include "fl2.pb.h"
|
||||
#include "fmt/format.h"
|
||||
#include "fluxengine.h"
|
||||
#include <fstream>
|
||||
|
||||
static FlagGroup flags;
|
||||
|
||||
static std::vector<std::string> inputFluxFiles;
|
||||
|
||||
static StringFlag sourceFlux({"-s", "--source"},
|
||||
"flux file to read from (repeatable)",
|
||||
"",
|
||||
[](const auto& value)
|
||||
{
|
||||
inputFluxFiles.push_back(value);
|
||||
});
|
||||
|
||||
static StringFlag destFlux(
|
||||
{"-d", "--dest"}, "destination flux file to write to", "");
|
||||
|
||||
int mainMerge(int argc, const char* argv[])
|
||||
{
|
||||
flags.parseFlags(argc, argv);
|
||||
|
||||
if (inputFluxFiles.empty())
|
||||
Error() << "you must specify at least one input flux file (with -s)";
|
||||
if (destFlux.get() == "")
|
||||
Error() << "you must specify an output flux file (with -d)";
|
||||
|
||||
std::map<std::pair<int, int>, TrackFluxProto> data;
|
||||
for (const auto& s : inputFluxFiles)
|
||||
{
|
||||
fmt::print("Reading {}...\n", s);
|
||||
FluxFileProto f = loadFl2File(s);
|
||||
|
||||
for (auto& trackflux : f.track())
|
||||
{
|
||||
auto key = std::make_pair(trackflux.track(), trackflux.head());
|
||||
auto i = data.find(key);
|
||||
if (i == data.end())
|
||||
data[key] = trackflux;
|
||||
else
|
||||
{
|
||||
for (auto flux : trackflux.flux())
|
||||
i->second.add_flux(flux);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
FluxFileProto proto;
|
||||
for (auto& i : data)
|
||||
*proto.add_track() = i.second;
|
||||
|
||||
fmt::print("Writing {}...\n", destFlux.get());
|
||||
saveFl2File(destFlux.get(), proto);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -61,10 +61,10 @@ int mainRawRead(int argc, const char* argv[])
|
||||
|
||||
if (argc == 1)
|
||||
showProfiles("rawread", formats);
|
||||
config.mutable_flux_source()->mutable_drive();
|
||||
config.mutable_flux_source()->set_type(FluxSourceProto::DRIVE);
|
||||
flags.parseFlagsWithConfigFiles(argc, argv, formats);
|
||||
|
||||
if (config.flux_sink().has_drive())
|
||||
if (config.flux_sink().type() == FluxSinkProto::DRIVE)
|
||||
Error() << "you can't use rawread to write to hardware";
|
||||
|
||||
std::unique_ptr<FluxSource> fluxSource(FluxSource::create(config.flux_source()));
|
||||
|
||||
@@ -54,7 +54,7 @@ static ActionFlag eraseFlag(
|
||||
"erases the destination",
|
||||
[]()
|
||||
{
|
||||
config.mutable_flux_source()->mutable_erase();
|
||||
config.mutable_flux_source()->set_type(FluxSourceProto::ERASE);
|
||||
});
|
||||
|
||||
int mainRawWrite(int argc, const char* argv[])
|
||||
@@ -64,10 +64,10 @@ int mainRawWrite(int argc, const char* argv[])
|
||||
|
||||
if (argc == 1)
|
||||
showProfiles("rawwrite", formats);
|
||||
config.mutable_flux_sink()->mutable_drive();
|
||||
config.mutable_flux_sink()->set_type(FluxSinkProto::DRIVE);
|
||||
flags.parseFlagsWithConfigFiles(argc, argv, formats);
|
||||
|
||||
if (config.flux_source().has_drive())
|
||||
if (config.flux_source().type() == FluxSourceProto::DRIVE)
|
||||
Error() << "you can't use rawwrite to read from hardware";
|
||||
|
||||
std::unique_ptr<FluxSource> fluxSource(FluxSource::create(config.flux_source()));
|
||||
|
||||