Compare commits

..

77 Commits
docs ... psos

Author SHA1 Message Date
David Given
3876c07164 Merge branch 'master' into psos 2023-08-20 21:42:13 +02:00
David Given
ed315eade9 Merge pull request #668 from davidgiven/ms2000
Add basic support for the MS2000 Microdos file system.
2023-08-19 23:54:27 +02:00
David Given
7456fd0c90 Make the MS2000 stuff work again. Write documentation. 2023-08-19 23:29:55 +02:00
David Given
44160e66ac Merge branch 'master' into ms2000 2023-08-19 22:59:31 +02:00
David Given
9bd969a57b Merge pull request #688 from davidgiven/lif
Add HP9122 support; fix HP9121 support.
2023-08-19 22:55:39 +02:00
David Given
0b585078d8 Merge pull request #704 from ejona86/micropolis-ecc
Micropolis: Add Vector ECC support
2023-08-19 21:54:22 +02:00
David Given
0d495ed934 Merge pull request #710 from davidgiven/usb
Make work on FreeBSD
2023-08-19 21:23:54 +02:00
David Given
95b703b1ea Tidy reporting of USB errors. 2023-08-19 20:46:41 +02:00
David Given
688061397b Adjust error messages. 2023-08-19 20:39:55 +02:00
Poul-Henning Kamp
1f00176455 Make the non-gui executable build on FreeBSD 2023-08-14 19:51:21 +00:00
David Given
90da6b1e72 Merge pull request #706 from ejona86/pkg-config-protobuf
Makefile: Eagerly run pkg-config for protobuf
2023-08-06 01:12:49 +02:00
Eric Anderson
4deb45dc3f Makefile: Eagerly run pkg-config for protobuf
Protobuf added a dependency on absl and now pkg-config is incredibly
slow. `pkg-config --libs protobuf` and `--cflags` each take around 1.5
seconds on my laptop. Running pkg-config only once reduces a 100%
incremental build for 'make all' from 90 seconds to 3.2 seconds.

Unfortunately we will pay the 3 seconds every time we execute make, even
for something that doesn't need protobuf.
2023-08-05 13:02:50 -07:00
David Given
eeec5d106a Update missing file. 2023-08-02 14:08:38 +02:00
David Given
4e42d1d197 Release and ccpp workflows now run in different environments. 2023-08-02 14:08:19 +02:00
David Given
495d08c447 Merge pull request #705 from davidgiven/d20
Update D20 documentation.
2023-08-02 13:46:10 +02:00
David Given
1b859015ae Update documentation. 2023-08-02 13:42:23 +02:00
David Given
3db2109e01 Merge pull request #700 from davidgiven/d20
Add support for the Roland-D20 filesystem.
2023-07-31 23:01:49 +01:00
David Given
294ac87503 Update documentation for the Roland D20 format. 2023-07-31 23:36:45 +02:00
David Given
c297adb0c7 Try to fix Mac builds. 2023-07-31 22:30:52 +02:00
David Given
446b965794 Handle Roland extents properly if the directory entries are in the wrong order.
Deal with block numbers >39 (they go in the bottom of the disk).
2023-07-31 22:20:08 +02:00
Eric Anderson
96d4df296d Micropolis: Add Vector ECC support 2023-07-29 14:03:08 -07:00
David Given
a149aac0e9 Merge pull request #702 from ejona86/micropolis-encodedecode
Micropolis: Fix encoder and decoder to support encodedecodetest
2023-07-29 17:20:58 +01:00
David Given
aacc7be9f3 Merge pull request #703 from ejona86/vgi-hcs
Micropolis: Add missing HCS order for VGI
2023-07-29 17:17:44 +01:00
Eric Anderson
7409955701 Micropolis: Add missing HCS order for VGI 2023-07-29 07:12:35 -07:00
Eric Anderson
c623d95a80 Micropolis: Fix encoder and decoder to support encodedecodetest
These changes should not impact reading/writing from real disks. This
includes a bug fix to Fluxmap where it might miss a trailing interval
when adding bits, as mentioned in #333.

With the Fluxmap bug fixed, the encoder now includes index pulses in its
output. The decoder was relaxed to allow reading precisely one track.

We don't actually add an encodedecodetest for micropolis, though,
because the SCP encoder is unhappy with so many revolutions.
2023-07-29 07:08:23 -07:00
David Given
1927cc7fe1 Fix issue where trying to rename files by clicking on the tree wasn't working. 2023-07-27 23:44:33 +02:00
David Given
4eca254daf Add support for renaming files. 2023-07-27 23:44:04 +02:00
David Given
c7d4fee3f6 Add support for deleting files. 2023-07-27 23:19:50 +02:00
David Given
a6f798ae5b Mangle and demangle filenames. Remember to write the correct extent numbers in
multiextent files.
2023-07-27 23:09:57 +02:00
David Given
c9ae836e52 Add very brittle write support. 2023-07-27 22:49:10 +02:00
David Given
e3ffa63f7f Make sure that the rotational speed is measured even if reads are done through
Browse Disk.
2023-07-27 22:14:48 +02:00
David Given
4ffc2cc1dc Add support for, hopefully, multi-extent files. 2023-07-27 00:30:44 +02:00
David Given
7f9ba14687 Correct erroneous index. 2023-07-26 22:37:56 +02:00
David Given
a24933e272 Merge from master. 2023-07-26 22:33:40 +02:00
David Given
20bdacbecf Add initial support for the Roland-D20 filesystem. 2023-07-26 22:31:20 +02:00
David Given
ab9d6cf5ed Merge pull request #699 from davidgiven/wx
UI improvements
2023-07-25 23:03:02 +01:00
David Given
1f5903a9a0 Don't use std::filesystem; it makes life harder on Windows with its wide
strings.
2023-07-25 23:35:01 +02:00
David Given
bb073b6bb3 Apparently Mingw can't automatically convert from path to string. 2023-07-25 23:23:04 +02:00
David Given
516241f8f5 Replace the image read file picker with a simple one. 2023-07-25 23:11:52 +02:00
David Given
977b6831a0 When reading Kryoflux streams, you can specify any file in the directory and it
will work (as the GUI now forces you to do this).
2023-07-25 22:48:17 +02:00
David Given
c61effb54f Add a file type box to the flux source selection page. 2023-07-25 22:27:09 +02:00
David Given
346d989944 When reading Kryoflux streams, allow the user to specify any file within the
directory and have it work (as that's what the GUI does now).
2023-07-25 22:51:34 +02:00
David Given
60a73c8d1e Add a file type box to the flux source selection page. 2023-07-25 22:27:09 +02:00
dg
e52db4a837 Typo fix. 2023-07-24 20:56:37 +00:00
dg
4e317643bc Try and install compatible versions of protobuf. 2023-07-24 20:53:51 +00:00
David Given
5f520bf375 Merge pull request #698 from davidgiven/zilogmcz
Add support for the ZDOS filesystem for the Zilog MCZ.
2023-07-24 22:16:33 +02:00
David Given
2efe521b3a Update documentation. 2023-07-24 21:48:37 +02:00
David Given
5c21103646 Get the ZDOS filesystem driver working. 2023-07-24 21:46:49 +02:00
David Given
9444696f37 Merge pull request #697 from davidgiven/ro
Allow read-only flux and image in the GUI.
2023-07-24 08:20:39 +02:00
David Given
082fe4e787 Hack in boilerplate for a ZDos filesystem. 2023-07-24 08:18:18 +02:00
David Given
5e13cf23f9 Allow read-only image reader/writers in the GUI. 2023-07-24 07:53:47 +02:00
David Given
8f98a1f557 Consolidate the image constructors in the same way that's been done for the
flux constructors.
2023-07-24 07:50:16 +02:00
David Given
5b21e8798b Allow read-only flux sources in the GUI. 2023-07-24 07:39:59 +02:00
David Given
b9ef5b7db8 Rename all the flux and image types to prefix the enums, due to them being in
the global namespace now.
2023-07-24 02:18:53 +02:00
David Given
9867f8c302 Combine enums for flux source/sink types. config.cc now knows whether they're
read-only, write-only, and read-write.
2023-07-24 00:50:54 +02:00
David Given
315889faf6 Warning fix. 2023-07-23 22:49:23 +02:00
David Given
798e8fee89 Merge pull request #692 from davidgiven/protobuf
Rename the `requires` config field to `prerequisite`
2023-07-08 00:43:15 +02:00
dg
e1c49db329 Use brew --prefix to detect the installation path when copying licenses from
packages.
2023-07-07 22:10:52 +00:00
dg
dae9537472 Warning fixes. 2023-07-07 21:51:24 +00:00
dg
1330d56cdd Fix a bunch of errors caused by changes to libfmt. 2023-07-07 21:32:21 +00:00
David Given
6ce3ce20d0 Remove stray debugging code. 2023-07-07 01:03:31 +02:00
David Given
362c5ee9b0 Rename the requires config field to prerequisite, as requires is about to
become a C++ keyword.
2023-07-07 00:34:03 +02:00
David Given
0f34ce0278 Merge pull request #690 from Deledrius/nsi-fix
Fix incorrect product name in installer.
2023-06-26 14:27:39 +02:00
Joseph Davies
0c27c7c4c8 Fix incorrect product name in installer. 2023-06-25 16:18:03 -07:00
David Given
37595bf73c Update the HP formats to not use the reserved tracks at the end of the disk. 2023-06-15 00:13:28 +02:00
David Given
952aea46ba The HP9122 format appears to be double-sided. 2023-06-13 23:00:00 +02:00
David Given
6a6536cf27 Discover that the HP9121 format is actually 70 track. Add support for the
HP9122 format.
2023-06-13 20:16:41 +02:00
David Given
696368c92a Read LIF volume size information correctly. 2023-06-13 20:08:47 +02:00
David Given
e3edc9327e Don't crash if there is no disk usage data. 2023-06-13 20:08:31 +02:00
David Given
8d2e6a664d Adjust the 264 format to have sector numbers in, hopefully, the right place. 2023-06-13 19:54:46 +02:00
David Given
9db6efe7a2 Merge pull request #686 from davidgiven/docs
Update documentation.
2023-06-03 00:30:34 +02:00
dg
ba1f8b8ed8 Add missing file. Reformat. 2023-05-06 00:28:13 +00:00
dg
10605b3908 Add a read-only MS2000 file system, and a format (with no encoder or decoder). 2023-05-06 00:21:10 +00:00
dg
e31e547322 Add a routine to count the number of bits in a word. 2023-05-06 00:20:48 +00:00
dg
9484a1b870 Swap minutes and seconds, as this seems to be more correct. 2023-04-07 16:38:08 +00:00
dg
0a5a814a88 Typo fix. 2023-04-05 17:17:15 +00:00
dg
08ce455d1d Properly terminate pSOS filenames. Make a guess at the ctime format. 2023-04-05 17:13:49 +00:00
125 changed files with 2776 additions and 2003 deletions

View File

@@ -43,7 +43,7 @@ jobs:
uses: actions/upload-artifact@v2
with:
name: ${{ github.event.repository.name }}.${{ github.sha }}
path: fluxengine.FluxEngine.pkg
path: fluxengine/FluxEngine.pkg
build-windows:
runs-on: windows-latest
@@ -69,6 +69,9 @@ jobs:
mingw-w64-i686-nsis
zip
vim
- name: update-protobuf
run: |
pacman -U --noconfirm https://repo.msys2.org/mingw/mingw32/mingw-w64-i686-protobuf-21.9-1-any.pkg.tar.zst
- uses: actions/checkout@v2
with:
repository: 'davidgiven/fluxengine'

View File

@@ -1,7 +1,7 @@
name: Autorelease
concurrency:
group: environment-${{ github.head_ref }}
group: environment-release-${{ github.head_ref }}
cancel-in-progress: true
on:
@@ -36,6 +36,10 @@ jobs:
vim
- uses: actions/checkout@v3
- name: update-protobuf
run: |
pacman -U --noconfirm https://repo.msys2.org/mingw/mingw32/mingw-w64-i686-protobuf-21.9-1-any.pkg.tar.zst
- name: build
run: |
make -j2

View File

@@ -30,6 +30,11 @@ ifeq ($(shell uname),Darwin)
-framework Foundation
endif
ifeq ($(shell uname),FreeBSD)
PLATFORM = FreeBSD
CFLAGS += -I/usr/local/include
endif
#Check the Make version.
@@ -138,12 +143,12 @@ PROTOS = \
PROTO_HDRS = $(patsubst %.proto, $(OBJDIR)/%.pb.h, $(PROTOS))
PROTO_SRCS = $(patsubst %.proto, $(OBJDIR)/%.pb.cc, $(PROTOS))
PROTO_OBJS = $(patsubst %.cc, %.o, $(PROTO_SRCS))
PROTO_CFLAGS = $(shell $(PKG_CONFIG) --cflags protobuf)
PROTO_CFLAGS := $(shell $(PKG_CONFIG) --cflags protobuf)
$(PROTO_SRCS): | $(PROTO_HDRS)
$(PROTO_OBJS): CFLAGS += $(PROTO_CFLAGS)
PROTO_LIB = $(OBJDIR)/libproto.a
$(PROTO_LIB): $(PROTO_OBJS)
PROTO_LDFLAGS = $(shell $(PKG_CONFIG) --libs protobuf) -pthread
PROTO_LDFLAGS := $(shell $(PKG_CONFIG) --libs protobuf) -pthread
.PRECIOUS: $(PROTO_HDRS) $(PROTO_SRCS)
include dep/agg/build.mk
@@ -198,6 +203,7 @@ $(call do-encodedecodetest,commodore,scripts/commodore1541_test.textpb,--192 --d
$(call do-encodedecodetest,commodore,,--800 --drive.tpi=135)
$(call do-encodedecodetest,commodore,,--1620 --drive.tpi=135)
$(call do-encodedecodetest,hplif,,--264 --drive.tpi=135)
$(call do-encodedecodetest,hplif,,--608 --drive.tpi=135)
$(call do-encodedecodetest,hplif,,--616 --drive.tpi=135)
$(call do-encodedecodetest,hplif,,--770 --drive.tpi=135)
$(call do-encodedecodetest,ibm,,--1200 --drive.tpi=96)

View File

@@ -128,17 +128,18 @@ choices because they can store multiple types of file system.
| [`icl30`](doc/disk-icl30.md) | ICL Model 30: CP/M; 263kB 35-track DSSD | 🦖 | | CPMFS |
| [`mac`](doc/disk-mac.md) | Macintosh: 400kB/800kB 3.5" GCR | 🦄 | 🦄 | MACHFS |
| [`micropolis`](doc/disk-micropolis.md) | Micropolis: 100tpi MetaFloppy disks | 🦄 | 🦄 | |
| [`ms2000`](doc/disk-ms2000.md) | : MS2000 Microdisk Development System | | | MICRODOS |
| [`mx`](doc/disk-mx.md) | DVK MX: Soviet-era PDP-11 clone | 🦖 | | |
| [`n88basic`](doc/disk-n88basic.md) | N88-BASIC: PC8800/PC98 5.25" 77-track 26-sector DSHD | 🦄 | 🦄 | |
| [`northstar`](doc/disk-northstar.md) | Northstar: 5.25" hard sectored | 🦄 | 🦄 | |
| [`psos`](doc/disk-psos.md) | pSOS: 800kB DSDD with PHILE | 🦄 | 🦄 | PHILE |
| [`rolandd20`](doc/disk-rolandd20.md) | Roland D20: 3.5" electronic synthesiser disks | 🦖 | | |
| [`rolandd20`](doc/disk-rolandd20.md) | Roland D20: 3.5" electronic synthesiser disks | 🦄 | 🦖 | ROLAND |
| [`rx50`](doc/disk-rx50.md) | Digital RX50: 400kB 5.25" 80-track 10-sector SSDD | 🦖 | 🦖 | |
| [`smaky6`](doc/disk-smaky6.md) | Smaky 6: 308kB 5.25" 77-track 16-sector SSDD, hard sectored | 🦖 | | SMAKY6 |
| [`tids990`](doc/disk-tids990.md) | Texas Instruments DS990: 1126kB 8" DSSD | 🦖 | 🦖 | |
| [`tiki`](doc/disk-tiki.md) | Tiki 100: CP/M | | | CPMFS |
| [`victor9k`](doc/disk-victor9k.md) | Victor 9000 / Sirius One: 1224kB 5.25" DSDD GCR | 🦖 | 🦖 | |
| [`zilogmcz`](doc/disk-zilogmcz.md) | Zilog MCZ: 320kB 8" 77-track SSSD hard-sectored | 🦖 | | |
| [`zilogmcz`](doc/disk-zilogmcz.md) | Zilog MCZ: 320kB 8" 77-track SSSD hard-sectored | 🦖 | | ZDOS |
{: .datatable }
<!-- FORMATSEND -->

View File

@@ -51,26 +51,6 @@ static void write_bits(
}
}
void bindump(std::ostream& stream, std::vector<bool>& buffer)
{
size_t pos = 0;
while ((pos < buffer.size()) and (pos < 520))
{
stream << fmt::format("{:5d} : ", pos);
for (int i = 0; i < 40; i++)
{
if ((pos + i) < buffer.size())
stream << fmt::format("{:01b}", (buffer[pos + i]));
else
stream << "-- ";
if ((((pos + i + 1) % 8) == 0) and i != 0)
stream << " ";
}
stream << std::endl;
pos += 40;
}
}
static std::vector<bool> encode_data(uint8_t input)
{
/*

View File

@@ -59,6 +59,70 @@ uint8_t mzosChecksum(const Bytes& bytes)
return checksum;
}
static uint8_t b(uint32_t field, uint8_t pos)
{
return (field >> pos) & 1;
}
static uint8_t eccNextBit(uint32_t ecc, uint8_t data_bit)
{
// This is 0x81932080 which is 0x0104C981 with reversed bits
return b(ecc, 7) ^ b(ecc, 13) ^ b(ecc, 16) ^ b(ecc, 17) ^ b(ecc, 20)
^ b(ecc, 23) ^ b(ecc, 24) ^ b(ecc, 31) ^ data_bit;
}
uint32_t vectorGraphicEcc(const Bytes& bytes)
{
uint32_t e = 0;
Bytes payloadBytes = bytes.slice(0, bytes.size()-4);
ByteReader payload(payloadBytes);
while (!payload.eof()) {
uint8_t byte = payload.read_8();
for (int i = 0; i < 8; i++) {
e = (e << 1) | eccNextBit(e, byte >> 7);
byte <<= 1;
}
}
Bytes trailerBytes = bytes.slice(bytes.size()-4);
ByteReader trailer(trailerBytes);
uint32_t res = e;
while (!trailer.eof()) {
uint8_t byte = trailer.read_8();
for (int i = 0; i < 8; i++) {
res = (res << 1) | eccNextBit(e, byte >> 7);
e <<= 1;
byte <<= 1;
}
}
return res;
}
/* Fixes bytes when possible, returning true if changed. */
static bool vectorGraphicEccFix(Bytes& bytes, uint32_t syndrome)
{
uint32_t ecc = syndrome;
int pos = (MICROPOLIS_ENCODED_SECTOR_SIZE-5)*8+7;
bool aligned = false;
while ((ecc & 0xff000000) == 0) {
pos += 8;
ecc <<= 8;
}
for (; pos >= 0; pos--) {
bool bit = ecc & 1;
ecc >>= 1;
if (bit)
ecc ^= 0x808264c0;
if ((ecc & 0xff07ffff) == 0)
aligned = true;
if (aligned && pos % 8 == 0)
break;
}
if (pos < 0)
return false;
bytes[pos/8] ^= ecc >> 16;
return true;
}
class MicropolisDecoder : public Decoder
{
public:
@@ -85,9 +149,10 @@ public:
/* Discard a possible partial sector at the end of the track.
* This partial sector could be mistaken for a conflicted sector, if
* whatever data read happens to match the checksum of 0, which is
* rare, but has been observed on some disks.
* rare, but has been observed on some disks. There's 570uS of slack in
* each sector, after accounting for preamble, data, and postamble.
*/
if (now > (getFluxmapDuration() - 12.5e6))
if (now > (getFluxmapDuration() - 12.0e6))
{
seekToIndexMark();
return 0;
@@ -114,9 +179,10 @@ public:
_sector->headerStartTime = tell().ns();
/* seekToPattern() can skip past the index hole, if this happens
* too close to the end of the Fluxmap, discard the sector.
* too close to the end of the Fluxmap, discard the sector. The
* preamble was expected to be 640uS long.
*/
if (_sector->headerStartTime > (getFluxmapDuration() - 12.5e6))
if (_sector->headerStartTime > (getFluxmapDuration() - 11.3e6))
{
return 0;
}
@@ -130,6 +196,17 @@ public:
auto rawbits = readRawBits(MICROPOLIS_ENCODED_SECTOR_SIZE * 16);
auto bytes =
decodeFmMfm(rawbits).slice(0, MICROPOLIS_ENCODED_SECTOR_SIZE);
bool eccPresent = bytes[274] == 0xaa;
uint32_t ecc = 0;
if (_config.ecc_type() == MicropolisDecoderProto::VECTOR && eccPresent) {
ecc = vectorGraphicEcc(bytes.slice(0, 274));
if (ecc != 0) {
vectorGraphicEccFix(bytes, ecc);
ecc = vectorGraphicEcc(bytes.slice(0, 274));
}
}
ByteReader br(bytes);
int syncByte = br.read_8(); /* sync */
@@ -191,8 +268,10 @@ public:
_sector->data = bytes;
else
error("Sector output size may only be 256 or 275");
_sector->status =
(wantChecksum == gotChecksum) ? Sector::OK : Sector::BAD_CHECKSUM;
if (wantChecksum == gotChecksum && (!eccPresent || ecc == 0))
_sector->status = Sector::OK;
else
_sector->status = Sector::BAD_CHECKSUM;
}
private:

View File

@@ -8,7 +8,8 @@
static void write_sector(std::vector<bool>& bits,
unsigned& cursor,
const std::shared_ptr<const Sector>& sector)
const std::shared_ptr<const Sector>& sector,
MicropolisEncoderProto::EccType eccType)
{
if ((sector->data.size() != 256) &&
(sector->data.size() != MICROPOLIS_ENCODED_SECTOR_SIZE))
@@ -45,8 +46,15 @@ static void write_sector(std::vector<bool>& bits,
writer.write_8(0); /* Padding */
writer += sector->data;
writer.write_8(micropolisChecksum(sectorData.slice(1)));
for (int i = 0; i < 5; i++)
writer.write_8(0); /* 4 byte ECC and ECC not present flag */
uint8_t eccPresent = 0;
uint32_t ecc = 0;
if (eccType == MicropolisEncoderProto::VECTOR) {
eccPresent = 0xaa;
ecc = vectorGraphicEcc(sectorData + Bytes(4));
}
writer.write_be32(ecc);
writer.write_8(eccPresent);
}
for (uint8_t b : sectorData)
fullSector->push_back(b);
@@ -86,18 +94,32 @@ public:
(_config.rotational_period_ms() * 1e3) / _config.clock_period_us();
std::vector<bool> bits(bitsPerRevolution);
std::vector<unsigned> indexes;
unsigned prev_cursor = 0;
unsigned cursor = 0;
for (const auto& sectorData : sectors)
write_sector(bits, cursor, sectorData);
for (const auto& sectorData : sectors) {
indexes.push_back(cursor);
prev_cursor = cursor;
write_sector(bits, cursor, sectorData, _config.ecc_type());
}
indexes.push_back(prev_cursor + (cursor - prev_cursor)/2);
indexes.push_back(cursor);
if (cursor != bits.size())
error("track data mismatched length");
std::unique_ptr<Fluxmap> fluxmap(new Fluxmap);
fluxmap->appendBits(bits,
calculatePhysicalClockPeriod(_config.clock_period_us() * 1e3,
_config.rotational_period_ms() * 1e6));
nanoseconds_t clockPeriod = calculatePhysicalClockPeriod(
_config.clock_period_us() * 1e3,
_config.rotational_period_ms() * 1e6);
auto pos = bits.begin();
for (int i = 1; i < indexes.size(); i++) {
auto end = bits.begin() + indexes[i];
fluxmap->appendBits(std::vector<bool>(pos, end), clockPeriod);
fluxmap->appendIndex();
pos = end;
}
return fluxmap;
}

View File

@@ -17,5 +17,6 @@ extern std::unique_ptr<Encoder> createMicropolisEncoder(
const EncoderProto& config);
extern uint8_t micropolisChecksum(const Bytes& bytes);
extern uint32_t vectorGraphicEcc(const Bytes& bytes);
#endif

View File

@@ -8,17 +8,30 @@ message MicropolisDecoderProto {
MICROPOLIS = 1;
MZOS = 2;
}
enum EccType {
NONE = 0;
VECTOR = 1;
}
optional int32 sector_output_size = 1 [default = 256,
(help) = "How much of the raw sector should be saved. Must be 256 or 275"];
optional ChecksumType checksum_type = 2 [default = AUTO,
(help) = "Checksum type to use: AUTO, MICROPOLIS, MZOS"];
optional EccType ecc_type = 3 [default = NONE,
(help) = "ECC type to use: NONE, VECTOR"];
}
message MicropolisEncoderProto {
enum EccType {
NONE = 0;
VECTOR = 1;
}
optional double clock_period_us = 1
[ default = 2.0, (help) = "clock rate on the real device" ];
optional double rotational_period_ms = 2
[ default = 200.0, (help) = "rotational period on the real device" ];
optional EccType ecc_type = 3 [default = NONE,
(help) = "ECC type to use for IMG data: NONE, VECTOR"];
}

View File

@@ -33,7 +33,7 @@ LIBUSBP_SRCS += \
dep/libusbp/src/mac/list_mac.c \
dep/libusbp/src/mac/serial_port_mac.c \
else
else ifeq ($(shell uname),Linux)
LIBUSBP_CFLAGS += $(shell pkg-config --cflags libudev)
LIBUSBP_LDFLAGS += $(shell pkg-config --libs libudev)
@@ -48,6 +48,11 @@ LIBUSBP_SRCS += \
dep/libusbp/src/linux/udev_linux.c \
dep/libusbp/src/linux/usbfd_linux.c \
else
LIBUSBP_SRCS += \
dep/libusbp/src/dummy.c
endif
LIBUSBP_OBJS = $(patsubst %.c, $(OBJDIR)/%.o, $(LIBUSBP_SRCS))

103
dep/libusbp/src/dummy.c Normal file
View File

@@ -0,0 +1,103 @@
// This file contains failing place-holders to make things compile
// on otherwise unsupported platforms.
#include <libusbp_internal.h>
struct libusbp_device
{
char* syspath;
char* serial_number; // may be NULL
uint16_t product_id;
uint16_t vendor_id;
uint16_t revision;
};
static libusbp_error* fail()
{
return error_create("USB hardware is not supported on this platform");
}
libusbp_error* libusbp_device_copy(
const libusbp_device* source, libusbp_device** dest)
{
return fail();
}
libusbp_error* libusbp_generic_interface_create(const libusbp_device* device,
uint8_t interface_number,
bool composite __attribute__((unused)),
libusbp_generic_interface** gi)
{
return fail();
}
libusbp_error* libusbp_generic_handle_open(
const libusbp_generic_interface* gi, libusbp_generic_handle** handle)
{
return fail();
}
void libusbp_device_free(libusbp_device* device) {}
void libusbp_generic_handle_close(libusbp_generic_handle* handle) {}
void libusbp_generic_interface_free(libusbp_generic_interface* gi) {}
libusbp_error* libusbp_device_get_vendor_id(
const libusbp_device* device, uint16_t* vendor_id)
{
return fail();
}
libusbp_error* libusbp_device_get_product_id(
const libusbp_device* device, uint16_t* product_id)
{
return fail();
}
libusbp_error* libusbp_device_get_serial_number(
const libusbp_device* device, char** serial_number)
{
return fail();
}
libusbp_error* libusbp_write_pipe(libusbp_generic_handle* handle,
uint8_t pipe_id,
const void* data,
size_t size,
size_t* transferred)
{
return fail();
}
libusbp_error* libusbp_read_pipe(libusbp_generic_handle* handle,
uint8_t pipe_id,
void* data,
size_t size,
size_t* transferred)
{
return fail();
}
libusbp_error* libusbp_serial_port_create(const libusbp_device* device,
uint8_t interface_number,
bool composite,
libusbp_serial_port** port)
{
return fail();
}
libusbp_error* libusbp_serial_port_get_name(
const libusbp_serial_port* port, char** name)
{
return fail();
}
void libusbp_serial_port_free(libusbp_serial_port* port) {}
libusbp_error* libusbp_list_connected_devices(
libusbp_device*** device_list, size_t* device_count)
{
return fail();
}

View File

@@ -15,6 +15,7 @@ encoding scheme.
- Format variants:
- `264`: 264kB 3.5" 66-track SSDD; HP9121 format
- `608`: 608kB 3.5" 76-track DSDD; HP9122 format
- `616`: 616kB 3.5" 77-track DSDD
- `770`: 770kB 3.5" 77-track DSDD
@@ -23,12 +24,19 @@ encoding scheme.
To read:
- `fluxengine read hplif --264 -s drive:0 -o hplif.img`
- `fluxengine read hplif --608 -s drive:0 -o hplif.img`
- `fluxengine read hplif --616 -s drive:0 -o hplif.img`
- `fluxengine read hplif --770 -s drive:0 -o hplif.img`
To write:
- `fluxengine write hplif --264 -d drive:0 -i hplif.img`
- `fluxengine write hplif --608 -d drive:0 -i hplif.img`
- `fluxengine write hplif --616 -d drive:0 -i hplif.img`
- `fluxengine write hplif --770 -d drive:0 -i hplif.img`
## References
* [A summary of the Hewlett Packard floppy disk
formats](http://www.bitsavers.org/pdf/hp/disc/912x/HP_Flexible_Disk_Formats.pdf)

View File

@@ -22,13 +22,25 @@ pinout as a 96tpi PC 5.25" drive. In use they should be identical.
While most operating systems use the standard Micropolis checksum, Vector
Graphic MZOS uses a unique checksum. The decoder will automatically detect
the checksum type in use; however, a specific checksum type may be forced
using the `--decoder.micropolis.checksum_type=n` where the type is one of:
using the `--decoder.micropolis.checksum_type=TYPE` where TYPE is one of:
| Type | Description |
|------|-----------------------------------------|
| 0 | Automatically detect |
| 1 | Standard Micropolis (MDOS, CP/M, OASIS) |
| 2 | Vector Graphic MZOS |
| Checksum | Description |
|------------|-----------------------------------------|
| AUTO | Automatically detect |
| MICROPOLIS | Standard Micropolis (MDOS, CP/M, OASIS) |
| MZOS | Vector Graphic MZOS |
Later versions of the Micropolis format supported ECC, especially in
controllers with HDD support. The ECC can detect and correct errors. However,
it is unclear what ECC algorithm was used by each vendor. ECC is disabled by
default, but available for checking and correcting using
`--decoder.micropolis.ecc_type=TYPE` and for writing from IMG files using
`--encoder.micropolis.ecc_type=TYPE`, where TYPE is one of:
| ECC | Description |
|--------|------------------------------------------|
| NONE | No ECC processing enabled |
| VECTOR | Vector Graphic Dual-Mode Disk Controller |
The [CP/M BIOS](https://www.seasip.info/Cpm/bios.html) defined SELDSK, SETTRK,
and SETSEC, but no function to select the head/side. Double-sided floppies

View File

@@ -9,13 +9,29 @@ drive, used for saving MIDI sequences and samples.
Weirdly, it seems to use precisely the same format as the Brother word
processors: a thoroughly non-IBM-compatible custom GCR system.
FluxEngine pretends to support this, but it has had almost no testing, the only
disk image I have seen for it was mostly corrupt, and very little is known
about the format, so I have no idea whether it's correct or not.
FluxEngine supports both reading and writing D20 disks, as well as basic support
for the filesystem, allowing files to be read from and written to D20 disks.
Note that the D20 was never intended to support arbitrary files on its disks and
is very likely to crash if you put unexpected files on a disk. In addition,
while the file format itself is currently unknown, there is a header at the top
of the file containing what appears to be the name shown in the D20 file
browser, so the name by which you see it is not necessarily the filename.
A word of warning --- just like the Brother word processors, the D20 floppy
drive isn't very well aligned. The drive itself uses quarter-stepping to
automatically adapt to whatever alignment the disk was formatted with. This
means that trying to read such a disk on a PC drive, which does _not_ have
adjustable alignment, may not work very well. In these situations it is possible
to adjust the alignment of most modern drives, but this is a somewhat risky
process and may result in permanently wrecking the drive alignment.
Please [get in touch](https://github.com/davidgiven/fluxengine/issues/new) if
you know anything about it.
Many thanks to trondl [on the VCF
forums](https://forum.vcfed.org/index.php?threads/roland-d-20-decoding-the-mysterious-floppy-format.1243226/)
for assistance with this!
## Options
(no options)
@@ -26,3 +42,7 @@ To read:
- `fluxengine read rolandd20 -s drive:0 -o rolandd20.img`
To write:
- `fluxengine write rolandd20 -d drive:0 -i rolandd20.img`

View File

@@ -20,11 +20,8 @@ bytes per sector --- 128 bytes of user payload plus two two-byte metadata
words used to construct linked lists of sectors for storing files. These
stored 320kB each.
FluxEngine has experimental read support for these disks, based on a single
Catweasel flux file I've been able to obtain, which only contained 70 tracks.
I haven't been able to try this for real. If anyone has any of these disks,
an 8-inch drive, a FluxEngine and the appropriate adapter, please [get in
touch](https://github.com/davidgiven/fluxengine/issues/new)...
FluxEngine has read support for these, including support for RIO's ZDOS file
system.
## Options

View File

@@ -26,6 +26,7 @@ The following file systems are supported so far.
| Macintosh HFS | Y | Y | Only AppleDouble files may be written |
| pSOS' PHILE | Y | | Probably unreliable due to lack of documentation |
| Smaky 6 | Y | | |
| Zilog MCZ RIO's ZDOS | Y | | |
{: .datatable }
Please not that Atari disks do _not_ use standard FatFS, and the library I'm

View File

@@ -25,7 +25,7 @@ SetCompressor /solid lzma
GreaseWeazle hardware. It also allows manipulation of flux files and disk \
images, so it's useful without any hardware.$\r$\n\
$\r$\n\
This wizard will install WordGrinder on your computer.$\r$\n\
This wizard will install FluxEngine on your computer.$\r$\n\
$\r$\n\
$_CLICK"
@@ -130,7 +130,7 @@ SectionEnd
Section "Desktop Shortcut"
SetOutPath "$DOCUMENTS"
CreateShortCut "$DESKTOP\WordGrinder.lnk" "$INSTDIR\fluxengine-gui.exe" "" "$INSTDIR\fluxengine-gui.exe" 0
CreateShortCut "$DESKTOP\FluxEngine.lnk" "$INSTDIR\fluxengine-gui.exe" "" "$INSTDIR\fluxengine-gui.exe" 0
SectionEnd
;--------------------------------

View File

@@ -77,14 +77,17 @@ LIBFLUXENGINE_SRCS = \
lib/vfs/cbmfs.cc \
lib/vfs/cpmfs.cc \
lib/vfs/fatfs.cc \
lib/vfs/lif.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 \
lib/vfs/lif.cc \
lib/vfs/machfs.cc \
lib/vfs/microdos.cc \
lib/vfs/philefs.cc \
lib/vfs/prodos.cc \
lib/vfs/roland.cc \
lib/vfs/smaky6fs.cc \
lib/vfs/vfs.cc \
lib/vfs/zdos.cc \
LIBFLUXENGINE_OBJS = $(patsubst %.cc, $(OBJDIR)/%.o, $(LIBFLUXENGINE_SRCS))
OBJS += $(LIBFLUXENGINE_OBJS)

View File

@@ -362,6 +362,14 @@ ByteWriter& ByteWriter::operator+=(std::istream& stream)
return *this;
}
ByteWriter& ByteWriter::pad(unsigned count, uint8_t b)
{
while (count--)
this->write_8(b);
return *this;
}
void BitWriter::push(uint32_t bits, size_t size)
{
bits <<= 32 - size;

View File

@@ -345,6 +345,8 @@ public:
return *this += stream;
}
ByteWriter& pad(unsigned count, uint8_t byte = 0);
private:
Bytes& _bytes;
};

View File

@@ -19,4 +19,36 @@ enum IndexMode {
INDEXMODE_360 = 2;
}
enum FluxSourceSinkType {
FLUXTYPE_NOT_SET = 0;
FLUXTYPE_A2R = 1;
FLUXTYPE_AU = 2;
FLUXTYPE_CWF = 3;
FLUXTYPE_DRIVE = 4;
FLUXTYPE_ERASE = 5;
FLUXTYPE_FLUX = 6;
FLUXTYPE_FLX = 7;
FLUXTYPE_KRYOFLUX = 8;
FLUXTYPE_SCP = 9;
FLUXTYPE_TEST_PATTERN = 10;
FLUXTYPE_VCD = 11;
}
enum ImageReaderWriterType {
IMAGETYPE_NOT_SET = 0;
IMAGETYPE_D64 = 1;
IMAGETYPE_D88 = 2;
IMAGETYPE_DIM = 3;
IMAGETYPE_DISKCOPY = 4;
IMAGETYPE_FDI = 5;
IMAGETYPE_IMD = 6;
IMAGETYPE_IMG = 7;
IMAGETYPE_JV3 = 8;
IMAGETYPE_LDBS = 9;
IMAGETYPE_NFD = 10;
IMAGETYPE_NSI = 11;
IMAGETYPE_RAW = 12;
IMAGETYPE_TD0 = 13;
}

View File

@@ -11,10 +11,153 @@
#include "lib/decoders/decoders.h"
#include <fstream>
#include <google/protobuf/text_format.h>
#include <regex>
static Config config;
enum ConstructorMode
{
MODE_RO,
MODE_WO,
MODE_RW
};
struct ImageConstructor
{
std::string extension;
ImageReaderWriterType type;
ConstructorMode mode;
};
static const std::vector<FluxConstructor> fluxConstructors = {
{/* The .flux format must be first. */
.name = "FluxEngine (.flux)",
.pattern = std::regex("^(.*\\.flux)$"),
.source =
[](auto& s, auto* proto)
{
proto->set_type(FLUXTYPE_FLUX);
proto->mutable_fl2()->set_filename(s);
}, .sink =
[](auto& s, auto* proto)
{
proto->set_type(FLUXTYPE_FLUX);
proto->mutable_fl2()->set_filename(s);
}},
{
.name = "Supercard Pro (.scp)",
.pattern = std::regex("^(.*\\.scp)$"),
.source =
[](auto& s, auto* proto)
{
proto->set_type(FLUXTYPE_SCP);
proto->mutable_scp()->set_filename(s);
}, .sink =
[](auto& s, auto* proto)
{
proto->set_type(FLUXTYPE_SCP);
proto->mutable_scp()->set_filename(s);
}, },
{.name = "AppleSauce (.a2r)",
.pattern = std::regex("^(.*\\.a2r)$"),
.source =
[](auto& s, auto* proto)
{
proto->set_type(FLUXTYPE_A2R);
proto->mutable_a2r()->set_filename(s);
}, .sink =
[](auto& s, auto* proto)
{
proto->set_type(FLUXTYPE_A2R);
proto->mutable_a2r()->set_filename(s);
}},
{.name = "CatWeazle (.cwf)",
.pattern = std::regex("^(.*\\.cwf)$"),
.source =
[](auto& s, auto* proto)
{
proto->set_type(FLUXTYPE_CWF);
proto->mutable_cwf()->set_filename(s);
}},
{.pattern = std::regex("^erase:$"),
.source =
[](auto& s, auto* proto)
{
proto->set_type(FLUXTYPE_ERASE);
}},
{.name = "KryoFlux directory",
.pattern = std::regex("^kryoflux:(.*)$"),
.source =
[](auto& s, auto* proto)
{
proto->set_type(FLUXTYPE_KRYOFLUX);
proto->mutable_kryoflux()->set_directory(s);
}},
{.pattern = std::regex("^testpattern:(.*)"),
.source =
[](auto& s, auto* proto)
{
proto->set_type(FLUXTYPE_TEST_PATTERN);
}},
{.pattern = std::regex("^drive:(.*)"),
.source =
[](auto& s, auto* proto)
{
proto->set_type(FLUXTYPE_DRIVE);
globalConfig().overrides()->mutable_drive()->set_drive(
std::stoi(s));
}, .sink =
[](auto& s, auto* proto)
{
proto->set_type(FLUXTYPE_DRIVE);
globalConfig().overrides()->mutable_drive()->set_drive(
std::stoi(s));
}},
{.name = "FluxCopy directory",
.pattern = std::regex("^flx:(.*)$"),
.source =
[](auto& s, auto* proto)
{
proto->set_type(FLUXTYPE_FLX);
proto->mutable_flx()->set_directory(s);
}},
{.name = "Value Change Dump directory",
.pattern = std::regex("^vcd:(.*)$"),
.sink =
[](auto& s, auto* proto)
{
proto->set_type(FLUXTYPE_VCD);
proto->mutable_vcd()->set_directory(s);
}},
{.name = "Audio file directory",
.pattern = std::regex("^au:(.*)$"),
.sink =
[](auto& s, auto* proto)
{
proto->set_type(FLUXTYPE_AU);
proto->mutable_au()->set_directory(s);
}},
};
static const std::vector<ImageConstructor> imageConstructors = {
{".adf", IMAGETYPE_IMG, MODE_RW},
{".d64", IMAGETYPE_D64, MODE_RW},
{".d81", IMAGETYPE_IMG, MODE_RW},
{".d88", IMAGETYPE_D88, MODE_RW},
{".dim", IMAGETYPE_DIM, MODE_RO},
{".diskcopy", IMAGETYPE_DISKCOPY, MODE_RW},
{".dsk", IMAGETYPE_IMG, MODE_RW},
{".fdi", IMAGETYPE_FDI, MODE_RO},
{".imd", IMAGETYPE_IMD, MODE_RW},
{".img", IMAGETYPE_IMG, MODE_RW},
{".jv3", IMAGETYPE_JV3, MODE_RO},
{".nfd", IMAGETYPE_NFD, MODE_RO},
{".nsi", IMAGETYPE_NSI, MODE_RW},
{".st", IMAGETYPE_IMG, MODE_RW},
{".td0", IMAGETYPE_TD0, MODE_RO},
{".vgi", IMAGETYPE_IMG, MODE_RW},
{".xdf", IMAGETYPE_IMG, MODE_RW},
};
Config& globalConfig()
{
return config;
@@ -29,7 +172,7 @@ ConfigProto* Config::combined()
/* First apply any standalone options. */
std::set<std::string> options = _appliedOptions;
std::set<const OptionRequirementProto*> requirements;
std::set<const OptionPrerequisiteProto*> prereqs;
for (const auto& option : _baseConfig.option())
{
if (options.find(option.name()) != options.end())
@@ -257,7 +400,7 @@ const OptionProto& Config::findOption(const std::string& optionName)
void Config::checkOptionValid(const OptionProto& option)
{
for (const auto& req : option.requires())
for (const auto& req : option.prerequisite())
{
bool matched = false;
try
@@ -334,72 +477,17 @@ void Config::clearOptions()
invalidate();
}
static void setFluxSourceImpl(std::string filename, FluxSourceProto* proto)
static void setFluxSourceImpl(
const std::string& filename, FluxSourceProto* proto)
{
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("^(.*\\.a2r)$"),
[](auto& s, auto* proto)
{
proto->set_type(FluxSourceProto::A2R);
proto->mutable_a2r()->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);
globalConfig().overrides()->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)
for (const auto& it : fluxConstructors)
{
std::smatch match;
if (std::regex_match(filename, match, it.first))
if (std::regex_match(filename, match, it.pattern))
{
it.second(match[1], proto);
if (!it.source)
throw new InapplicableValueException();
it.source(match[1], proto);
return;
}
}
@@ -412,56 +500,16 @@ void Config::setFluxSource(std::string filename)
setFluxSourceImpl(filename, overrides()->mutable_flux_source());
}
static void setFluxSinkImpl(std::string filename, FluxSinkProto* proto)
static void setFluxSinkImpl(const std::string& filename, FluxSinkProto* proto)
{
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);
globalConfig().overrides()->mutable_drive()->set_drive(
std::stoi(s));
}},
};
for (const auto& it : formats)
for (const auto& it : fluxConstructors)
{
std::smatch match;
if (std::regex_match(filename, match, it.first))
if (std::regex_match(filename, match, it.pattern))
{
it.second(match[1], proto);
if (!it.sink)
throw new InapplicableValueException();
it.sink(match[1], proto);
return;
}
}
@@ -487,34 +535,14 @@ void Config::setVerificationFluxSource(std::string filename)
void Config::setImageReader(std::string filename)
{
static const std::map<std::string, std::function<void(ImageReaderProto*)>>
formats = {
// clang-format off
{".adf", [](auto* proto) { proto->set_type(ImageReaderProto::IMG); }},
{".d64", [](auto* proto) { proto->set_type(ImageReaderProto::D64); }},
{".d81", [](auto* proto) { proto->set_type(ImageReaderProto::IMG); }},
{".d88", [](auto* proto) { proto->set_type(ImageReaderProto::D88); }},
{".dim", [](auto* proto) { proto->set_type(ImageReaderProto::DIM); }},
{".diskcopy", [](auto* proto) { proto->set_type(ImageReaderProto::DISKCOPY); }},
{".dsk", [](auto* proto) { proto->set_type(ImageReaderProto::IMG); }},
{".fdi", [](auto* proto) { proto->set_type(ImageReaderProto::FDI); }},
{".imd", [](auto* proto) { proto->set_type(ImageReaderProto::IMD); }},
{".img", [](auto* proto) { proto->set_type(ImageReaderProto::IMG); }},
{".jv3", [](auto* proto) { proto->set_type(ImageReaderProto::JV3); }},
{".nfd", [](auto* proto) { proto->set_type(ImageReaderProto::NFD); }},
{".nsi", [](auto* proto) { proto->set_type(ImageReaderProto::NSI); }},
{".st", [](auto* proto) { proto->set_type(ImageReaderProto::IMG); }},
{".td0", [](auto* proto) { proto->set_type(ImageReaderProto::TD0); }},
{".vgi", [](auto* proto) { proto->set_type(ImageReaderProto::IMG); }},
{".xdf", [](auto* proto) { proto->set_type(ImageReaderProto::IMG); }},
// clang-format on
};
for (const auto& it : formats)
for (const auto& it : imageConstructors)
{
if (endsWith(filename, it.first))
if (endsWith(filename, it.extension))
{
it.second(overrides()->mutable_image_reader());
if (it.mode == MODE_WO)
throw new InapplicableValueException();
overrides()->mutable_image_reader()->set_type(it.type);
overrides()->mutable_image_reader()->set_filename(filename);
return;
}
@@ -525,31 +553,14 @@ void Config::setImageReader(std::string filename)
void Config::setImageWriter(std::string filename)
{
static const std::map<std::string, std::function<void(ImageWriterProto*)>>
formats = {
// clang-format off
{".adf", [](auto* proto) { proto->set_type(ImageWriterProto::IMG); }},
{".d64", [](auto* proto) { proto->set_type(ImageWriterProto::D64); }},
{".d81", [](auto* proto) { proto->set_type(ImageWriterProto::IMG); }},
{".d88", [](auto* proto) { proto->set_type(ImageWriterProto::D88); }},
{".diskcopy", [](auto* proto) { proto->set_type(ImageWriterProto::DISKCOPY); }},
{".dsk", [](auto* proto) { proto->set_type(ImageWriterProto::IMG); }},
{".img", [](auto* proto) { proto->set_type(ImageWriterProto::IMG); }},
{".imd", [](auto* proto) { proto->set_type(ImageWriterProto::IMD); }},
{".ldbs", [](auto* proto) { proto->set_type(ImageWriterProto::LDBS); }},
{".nsi", [](auto* proto) { proto->set_type(ImageWriterProto::NSI); }},
{".raw", [](auto* proto) { proto->set_type(ImageWriterProto::RAW); }},
{".st", [](auto* proto) { proto->set_type(ImageWriterProto::IMG); }},
{".vgi", [](auto* proto) { proto->set_type(ImageWriterProto::IMG); }},
{".xdf", [](auto* proto) { proto->set_type(ImageWriterProto::IMG); }},
// clang-format on
};
for (const auto& it : formats)
for (const auto& it : imageConstructors)
{
if (endsWith(filename, it.first))
if (endsWith(filename, it.extension))
{
it.second(overrides()->mutable_image_writer());
if (it.mode == MODE_RO)
throw new InapplicableValueException();
overrides()->mutable_image_writer()->set_type(it.type);
overrides()->mutable_image_writer()->set_filename(filename);
return;
}
@@ -560,7 +571,7 @@ void Config::setImageWriter(std::string filename)
bool Config::hasFluxSource()
{
return (*this)->flux_source().type() != FluxSourceProto::NOT_SET;
return (*this)->flux_source().type() != FLUXTYPE_NOT_SET;
}
std::shared_ptr<FluxSource>& Config::getFluxSource()
@@ -578,7 +589,7 @@ std::shared_ptr<FluxSource>& Config::getFluxSource()
bool Config::hasVerificationFluxSource() const
{
return _verificationFluxSourceProto.type() != FluxSourceProto::NOT_SET;
return _verificationFluxSourceProto.type() != FLUXTYPE_NOT_SET;
}
std::shared_ptr<FluxSource>& Config::getVerificationFluxSource()
@@ -596,7 +607,7 @@ std::shared_ptr<FluxSource>& Config::getVerificationFluxSource()
bool Config::hasImageReader()
{
return (*this)->image_reader().type() != ImageReaderProto::NOT_SET;
return (*this)->image_reader().type() != IMAGETYPE_NOT_SET;
}
std::shared_ptr<ImageReader>& Config::getImageReader()
@@ -614,7 +625,7 @@ std::shared_ptr<ImageReader>& Config::getImageReader()
bool Config::hasFluxSink()
{
return (*this)->flux_sink().type() != FluxSinkProto::NOT_SET;
return (*this)->flux_sink().type() != FLUXTYPE_NOT_SET;
}
std::unique_ptr<FluxSink> Config::getFluxSink()
@@ -627,7 +638,7 @@ std::unique_ptr<FluxSink> Config::getFluxSink()
bool Config::hasImageWriter()
{
return (*this)->image_writer().type() != ImageWriterProto::NOT_SET;
return (*this)->image_writer().type() != IMAGETYPE_NOT_SET;
}
std::unique_ptr<ImageWriter> Config::getImageWriter()
@@ -671,3 +682,8 @@ std::shared_ptr<Decoder>& Config::getDecoder()
}
return _decoder;
}
const std::vector<FluxConstructor>& Config::getFluxFormats()
{
return fluxConstructors;
}

View File

@@ -4,6 +4,7 @@
#include <google/protobuf/message.h>
#include "lib/config.pb.h"
#include "lib/common.pb.h"
class ConfigProto;
class OptionProto;
@@ -46,6 +47,22 @@ public:
}
};
class InapplicableValueException : public ErrorException
{
public:
InapplicableValueException():
ErrorException("selected format cannot be used here")
{}
};
struct FluxConstructor
{
std::string name;
std::regex pattern;
std::function<void(const std::string& filename, FluxSourceProto*)> source;
std::function<void(const std::string& filename, FluxSinkProto*)> sink;
};
class Config
{
public:
@@ -146,6 +163,10 @@ public:
bool hasImageWriter();
std::unique_ptr<ImageWriter> getImageWriter();
public:
static const std::vector<FluxConstructor>& getFluxFormats();
static std::vector<std::string> getImageFormats();
private:
ConfigProto _baseConfig;
ConfigProto _overridesConfig;

View File

@@ -50,7 +50,7 @@ message ConfigProto
repeated OptionGroupProto option_group = 20;
}
message OptionRequirementProto
message OptionPrerequisiteProto
{
optional string key = 1 [ (help) = "path to config value" ];
repeated string value = 2 [ (help) = "list of required values" ];
@@ -65,7 +65,7 @@ message OptionProto
[ (help) = "message to display when option is in use" ];
optional bool set_by_default = 6
[ (help) = "this option is applied by default", default = false ];
repeated OptionRequirementProto requires = 7
repeated OptionPrerequisiteProto prerequisite = 7
[ (help) = "prerequisites for this option" ];
optional ConfigProto config = 4

View File

@@ -98,6 +98,9 @@ Fluxmap& Fluxmap::appendBits(const std::vector<bool>& bits, nanoseconds_t clock)
appendPulse();
}
}
unsigned delta = (now - duration()) / NS_PER_TICK;
if (delta)
appendInterval(delta);
return *this;
}

View File

@@ -30,8 +30,8 @@ static void upgradeFluxFile(FluxFileProto& proto)
error(
"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);
(int)proto.version(),
(int)FluxFileVersion::VERSION_2);
}
FluxFileProto loadFl2File(const std::string filename)

View File

@@ -10,26 +10,25 @@ std::unique_ptr<FluxSink> FluxSink::create(const FluxSinkProto& config)
{
switch (config.type())
{
case FluxSinkProto::DRIVE:
case FLUXTYPE_DRIVE:
return createHardwareFluxSink(config.drive());
case FluxSinkProto::A2R:
case FLUXTYPE_A2R:
return createA2RFluxSink(config.a2r());
case FluxSinkProto::AU:
case FLUXTYPE_AU:
return createAuFluxSink(config.au());
case FluxSinkProto::VCD:
case FLUXTYPE_VCD:
return createVcdFluxSink(config.vcd());
case FluxSinkProto::SCP:
case FLUXTYPE_SCP:
return createScpFluxSink(config.scp());
case FluxSinkProto::FLUX:
case FLUXTYPE_FLUX:
return createFl2FluxSink(config.fl2());
default:
error("bad output disk config");
return std::unique_ptr<FluxSink>();
}
}

View File

@@ -29,17 +29,8 @@ message Fl2FluxSinkProto {
// Next: 10
message FluxSinkProto {
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 FluxSourceSinkType type = 9
[default = FLUXTYPE_NOT_SET, (help) = "flux sink type"];
optional HardwareFluxSinkProto drive = 2;
optional A2RFluxSinkProto a2r = 8;

View File

@@ -180,7 +180,7 @@ public:
trackdataWriter += fluxdata;
}
operator std::string() const
operator std::string() const override
{
return fmt::format("scp({})", _config.filename());
}

View File

@@ -64,7 +64,7 @@ public:
of << "\n";
}
operator std::string() const
operator std::string() const override
{
return fmt::format("vcd({})", _config.directory());
}

View File

@@ -142,7 +142,7 @@ public:
}
}
void recalibrate() {}
void recalibrate() override {}
private:
Bytes findChunk(Bytes id)

View File

@@ -15,7 +15,7 @@ public:
return std::unique_ptr<const Fluxmap>();
}
void recalibrate() {}
void recalibrate() override {}
};
std::unique_ptr<FluxSource> FluxSource::createEraseFluxSource(

View File

@@ -39,8 +39,8 @@ public:
_extraConfig.mutable_drive()->set_rotational_period_ms(
_proto.rotational_period_ms());
if (_proto.has_tpi())
_extraConfig.mutable_drive()->set_tpi(_proto.tpi());
if (_proto.has_tpi())
_extraConfig.mutable_drive()->set_tpi(_proto.tpi());
}
public:
@@ -55,7 +55,7 @@ public:
return std::make_unique<EmptyFluxSourceIterator>();
}
void recalibrate() {}
void recalibrate() override {}
private:
void check_for_error(std::ifstream& ifs)

View File

@@ -10,35 +10,34 @@ std::unique_ptr<FluxSource> FluxSource::create(const FluxSourceProto& config)
{
switch (config.type())
{
case FluxSourceProto::DRIVE:
case FLUXTYPE_DRIVE:
return createHardwareFluxSource(config.drive());
case FluxSourceProto::ERASE:
case FLUXTYPE_ERASE:
return createEraseFluxSource(config.erase());
case FluxSourceProto::KRYOFLUX:
case FLUXTYPE_KRYOFLUX:
return createKryofluxFluxSource(config.kryoflux());
case FluxSourceProto::TEST_PATTERN:
case FLUXTYPE_TEST_PATTERN:
return createTestPatternFluxSource(config.test_pattern());
case FluxSourceProto::SCP:
case FLUXTYPE_SCP:
return createScpFluxSource(config.scp());
case FluxSourceProto::A2R:
case FLUXTYPE_A2R:
return createA2rFluxSource(config.a2r());
case FluxSourceProto::CWF:
case FLUXTYPE_CWF:
return createCwfFluxSource(config.cwf());
case FluxSourceProto::FLUX:
case FLUXTYPE_FLUX:
return createFl2FluxSource(config.fl2());
case FluxSourceProto::FLX:
case FLUXTYPE_FLX:
return createFlxFluxSource(config.flx());
default:
error("bad input disk configuration");
return std::unique_ptr<FluxSource>();
}
}

View File

@@ -41,20 +41,8 @@ message FlxFluxSourceProto {
// NEXT: 12
message FluxSourceProto {
enum FluxSourceType {
NOT_SET = 0;
DRIVE = 1;
TEST_PATTERN = 2;
ERASE = 3;
KRYOFLUX = 4;
SCP = 5;
CWF = 6;
FLUX = 7;
FLX = 8;
A2R = 9;
}
optional FluxSourceType type = 9 [default = NOT_SET, (help) = "flux source type"];
optional FluxSourceSinkType type = 9
[default = FLUXTYPE_NOT_SET, (help) = "flux source type"];
optional A2rFluxSourceProto a2r = 11;
optional CwfFluxSourceProto cwf = 7;

View File

@@ -19,7 +19,7 @@ public:
return readFlxBytes(Bytes::readFromFile(path));
}
void recalibrate() {}
void recalibrate() override {}
private:
const std::string _path;

View File

@@ -5,6 +5,7 @@
#include <fstream>
#include <sys/types.h>
#include <dirent.h>
#include <filesystem>
#define MCLK_HZ (((18432000.0 * 73.0) / 14.0) / 2.0)
#define SCLK_HZ (MCLK_HZ / 2)
@@ -23,10 +24,18 @@ static bool has_suffix(const std::string& haystack, const std::string& needle)
}
std::unique_ptr<Fluxmap> readStream(
const std::string& dir, unsigned track, unsigned side)
std::string dir, unsigned track, unsigned side)
{
std::string suffix = fmt::format("{:02}.{}.raw", track, side);
FILE* fp = fopen(dir.c_str(), "r");
if (fp)
{
fclose(fp);
int i = dir.find_last_of("/\\");
dir = dir.substr(0, i);
}
DIR* dirp = opendir(dir.c_str());
if (!dirp)
error("cannot access path '{}'", dir);

View File

@@ -2,7 +2,7 @@
#define STREAM_H
extern std::unique_ptr<Fluxmap> readStream(
const std::string& dir, unsigned track, unsigned side);
std::string dir, unsigned track, unsigned side);
extern std::unique_ptr<Fluxmap> readStream(const std::string& path);
extern std::unique_ptr<Fluxmap> readStream(const Bytes& bytes);

View File

@@ -128,7 +128,7 @@ public:
return fluxmap;
}
void recalibrate() {}
void recalibrate() override {}
private:
void check_for_error()

View File

@@ -15,6 +15,7 @@
#include <climits>
#include <variant>
#include <optional>
#include <regex>
#include <fmt/format.h>
#if defined(_WIN32) || defined(__WIN32__)

View File

@@ -15,37 +15,37 @@ std::unique_ptr<ImageReader> ImageReader::create(const ImageReaderProto& config)
{
switch (config.type())
{
case ImageReaderProto::DIM:
case IMAGETYPE_DIM:
return ImageReader::createDimImageReader(config);
case ImageReaderProto::D88:
case IMAGETYPE_D88:
return ImageReader::createD88ImageReader(config);
case ImageReaderProto::FDI:
case IMAGETYPE_FDI:
return ImageReader::createFdiImageReader(config);
case ImageReaderProto::IMD:
case IMAGETYPE_IMD:
return ImageReader::createIMDImageReader(config);
case ImageReaderProto::IMG:
case IMAGETYPE_IMG:
return ImageReader::createImgImageReader(config);
case ImageReaderProto::DISKCOPY:
case IMAGETYPE_DISKCOPY:
return ImageReader::createDiskCopyImageReader(config);
case ImageReaderProto::JV3:
case IMAGETYPE_JV3:
return ImageReader::createJv3ImageReader(config);
case ImageReaderProto::D64:
case IMAGETYPE_D64:
return ImageReader::createD64ImageReader(config);
case ImageReaderProto::NFD:
case IMAGETYPE_NFD:
return ImageReader::createNFDImageReader(config);
case ImageReaderProto::NSI:
case IMAGETYPE_NSI:
return ImageReader::createNsiImageReader(config);
case ImageReaderProto::TD0:
case IMAGETYPE_TD0:
return ImageReader::createTd0ImageReader(config);
default:

View File

@@ -24,22 +24,8 @@ message ImageReaderProto
default = false
];
enum ImageReaderType {
NOT_SET = 0;
IMG = 1;
DISKCOPY = 2;
IMD = 3;
JV3 = 4;
D64 = 5;
NSI = 6;
TD0 = 7;
DIM = 8;
FDI = 9;
D88 = 10;
NFD = 11;
}
optional ImageReaderType type = 14 [default = NOT_SET, (help) = "input image type"];
optional ImageReaderWriterType type = 14
[default = IMAGETYPE_NOT_SET, (help) = "input image type"];
optional ImgInputOutputProto img = 2;
optional DiskCopyInputProto diskcopy = 3;

View File

@@ -15,28 +15,28 @@ std::unique_ptr<ImageWriter> ImageWriter::create(const ImageWriterProto& config)
{
switch (config.type())
{
case ImageWriterProto::IMG:
case IMAGETYPE_IMG:
return ImageWriter::createImgImageWriter(config);
case ImageWriterProto::D64:
case IMAGETYPE_D64:
return ImageWriter::createD64ImageWriter(config);
case ImageWriterProto::LDBS:
case IMAGETYPE_LDBS:
return ImageWriter::createLDBSImageWriter(config);
case ImageWriterProto::DISKCOPY:
case IMAGETYPE_DISKCOPY:
return ImageWriter::createDiskCopyImageWriter(config);
case ImageWriterProto::NSI:
case IMAGETYPE_NSI:
return ImageWriter::createNsiImageWriter(config);
case ImageWriterProto::RAW:
case IMAGETYPE_RAW:
return ImageWriter::createRawImageWriter(config);
case ImageWriterProto::D88:
case IMAGETYPE_D88:
return ImageWriter::createD88ImageWriter(config);
case ImageWriterProto::IMD:
case IMAGETYPE_IMD:
return ImageWriter::createImdImageWriter(config);
default:

View File

@@ -66,25 +66,14 @@ message ImdOutputProto
// 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
];
optional ImageWriterType type = 11 [ default = NOT_SET, (help) = "image writer type" ];
optional ImageReaderWriterType type = 11
[ default = IMAGETYPE_NOT_SET, (help) = "image writer type" ];
optional ImgInputOutputProto img = 2;
optional D64OutputProto d64 = 3;

View File

@@ -273,6 +273,8 @@ public:
case ImdOutputProto::RATE_DD:
RATE = 2000;
break;
case ImdOutputProto::RATE_GUESS:
break;
}
}
header.ModeValue =

View File

@@ -119,6 +119,7 @@ static ProtoField resolveProtoPath(
switch (field->label())
{
case google::protobuf::FieldDescriptor::LABEL_OPTIONAL:
case google::protobuf::FieldDescriptor::LABEL_REQUIRED:
if (!create && !reflection->HasField(*message, field))
throw ProtoPathNotFoundException(fmt::format(
"could not find config field '{}'", field->name()));
@@ -139,7 +140,7 @@ static ProtoField resolveProtoPath(
break;
default:
error("bad proto label {}", field->label());
error("bad proto label for field '{}' in '{}'", item, path);
}
descriptor = message->GetDescriptor();

View File

@@ -2,6 +2,7 @@
#define PROTO_H
#include <google/protobuf/message.h>
#include "lib/common.pb.h"
#include "lib/config.pb.h"
class ProtoPathNotFoundException : public ErrorException

View File

@@ -456,6 +456,9 @@ std::shared_ptr<TrackFlux> readAndDecodeTrack(FluxSource& fluxSource,
auto trackFlux = std::make_shared<TrackFlux>();
trackFlux->trackInfo = trackInfo;
if (fluxSource.isHardware())
measureDiskRotation();
FluxSourceIteratorHolder fluxSourceIteratorHolder(fluxSource);
int retriesRemaining = globalConfig()->decoder().retries();
for (;;)
@@ -498,8 +501,6 @@ std::shared_ptr<const DiskFlux> readDiskCommand(
auto diskflux = std::make_shared<DiskFlux>();
log(BeginOperationLogMessage{"Reading and decoding disk"});
if (fluxSource.isHardware())
measureDiskRotation();
auto locations = Layout::computeLocations();
unsigned index = 0;
for (auto& trackInfo : locations)

View File

@@ -90,6 +90,16 @@ struct Sector : public LogicalLocation
}
};
template <>
struct fmt::formatter<Sector::Status> : formatter<string_view>
{
auto format(Sector::Status status, format_context& ctx) const
{
return formatter<string_view>::format(
Sector::statusToString(status), ctx);
}
};
extern bool sectorPointerSortPredicate(const std::shared_ptr<const Sector>& lhs,
const std::shared_ptr<const Sector>& rhs);
extern bool sectorPointerEqualsPredicate(

View File

@@ -76,7 +76,7 @@ public:
"your FluxEngine firmware is at version {} but the client is "
"for version {}; please upgrade",
version,
FLUXENGINE_PROTOCOL_VERSION);
(int) FLUXENGINE_PROTOCOL_VERSION);
}
private:

View File

@@ -26,32 +26,39 @@ static const std::string get_serial_number(const libusbp::device& device)
std::vector<std::shared_ptr<CandidateDevice>> findUsbDevices()
{
std::vector<std::shared_ptr<CandidateDevice>> candidates;
for (const auto& it : libusbp::list_connected_devices())
try
{
auto candidate = std::make_unique<CandidateDevice>();
candidate->device = it;
uint32_t id = (it.get_vendor_id() << 16) | it.get_product_id();
if (VALID_DEVICES.find(id) != VALID_DEVICES.end())
std::vector<std::shared_ptr<CandidateDevice>> candidates;
for (const auto& it : libusbp::list_connected_devices())
{
candidate->id = id;
candidate->serial = get_serial_number(it);
auto candidate = std::make_unique<CandidateDevice>();
candidate->device = it;
if (id == GREASEWEAZLE_ID)
uint32_t id = (it.get_vendor_id() << 16) | it.get_product_id();
if (VALID_DEVICES.find(id) != VALID_DEVICES.end())
{
libusbp::serial_port port(candidate->device);
candidate->serialPort = port.get_name();
candidate->type = DEVICE_GREASEWEAZLE;
candidate->id = id;
candidate->serial = get_serial_number(it);
if (id == GREASEWEAZLE_ID)
{
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));
}
else if (id == FLUXENGINE_ID)
candidate->type = DEVICE_FLUXENGINE;
candidates.push_back(std::move(candidate));
}
}
return candidates;
return candidates;
}
catch (const libusbp::error& e)
{
error("USB error: {}", e.message());
}
}
std::string getDeviceName(DeviceType type)

View File

@@ -6,7 +6,7 @@
bool emergencyStop = false;
static const char* WHITESPACE = " \t\n\r\f\v";
static std::string WHITESPACE(" \t\n\r\f\v\0", 7);
static const char* SEPARATORS = "/\\";
void ErrorException::print() const
@@ -197,6 +197,17 @@ bool doesFileExist(const std::string& filename)
return f.good();
}
int countSetBits(uint32_t word)
{
int b = 0;
while (word)
{
b += word & 1;
word >>= 1;
}
return b;
}
uint32_t unbcd(uint32_t bcd)
{
uint32_t dec = 0;

View File

@@ -19,6 +19,7 @@ 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);
extern int countSetBits(uint32_t word);
extern uint32_t unbcd(uint32_t bcd);
template <class K, class V>

View File

@@ -56,7 +56,7 @@ public:
{
}
uint32_t capabilities() const
uint32_t capabilities() const override
{
return OP_GETFSDATA | OP_CREATE | OP_LIST | OP_GETFILE | OP_PUTFILE |
OP_GETDIRENT | OP_DELETE | OP_MOVE | OP_CREATEDIR;
@@ -229,7 +229,7 @@ public:
throw CannotWriteException();
}
void createDirectory(const Path& path)
void createDirectory(const Path& path) override
{
AdfMount m(this);
if (path.empty())

View File

@@ -50,7 +50,7 @@ public:
{
}
uint32_t capabilities() const
uint32_t capabilities() const override
{
return OP_LIST | OP_GETDIRENT | OP_GETFSDATA | OP_GETFILE;
}

View File

@@ -232,7 +232,7 @@ public:
{
}
uint32_t capabilities() const
uint32_t capabilities() const override
{
return OP_GETFSDATA | OP_LIST | OP_GETFILE | OP_PUTFILE | OP_GETDIRENT |
OP_DELETE;

View File

@@ -195,7 +195,7 @@ public:
{
}
uint32_t capabilities() const
uint32_t capabilities() const override
{
return OP_GETFSDATA | OP_LIST | OP_GETFILE | OP_GETDIRENT;
}

View File

@@ -81,7 +81,7 @@ public:
{
}
uint32_t capabilities() const
uint32_t capabilities() const override
{
return OP_GETFSDATA | OP_LIST | OP_GETFILE | OP_GETDIRENT;
}

View File

@@ -36,7 +36,7 @@ public:
{
}
uint32_t capabilities() const
uint32_t capabilities() const override
{
return OP_GETFSDATA | OP_CREATE | OP_LIST | OP_GETFILE | OP_PUTFILE |
OP_GETDIRENT | OP_MOVE | OP_CREATEDIR | OP_DELETE;
@@ -199,7 +199,7 @@ public:
throwError(res);
}
void createDirectory(const Path& path)
void createDirectory(const Path& path) override
{
mount();
auto pathStr = path.to_str();
@@ -296,7 +296,7 @@ private:
default:
throw FilesystemException(
fmt::format("unknown fatfs error {}", res));
fmt::format("unknown fatfs error {}", (int)res));
}
}

View File

@@ -44,7 +44,7 @@ public:
return _changedSectors.put(track, side, sectorId);
}
virtual bool isReadOnly()
virtual bool isReadOnly() override
{
return (_fluxSink == nullptr);
}

View File

@@ -20,19 +20,19 @@ public:
public:
std::shared_ptr<const Sector> get(
unsigned track, unsigned side, unsigned sectorId)
unsigned track, unsigned side, unsigned sectorId) override
{
return _image->get(track, side, sectorId);
}
std::shared_ptr<Sector> put(
unsigned track, unsigned side, unsigned sectorId)
unsigned track, unsigned side, unsigned sectorId) override
{
_changed = true;
return _image->put(track, side, sectorId);
}
virtual bool isReadOnly()
virtual bool isReadOnly() override
{
return (_writer == nullptr);
}

View File

@@ -214,6 +214,7 @@ private:
_directoryBlock = rbr.read_be32();
rbr.skip(4);
_directorySize = rbr.read_be32();
rbr.skip(4);
unsigned tracks = rbr.read_be32();
unsigned heads = rbr.read_be32();
unsigned sectors = rbr.read_be32();

View File

@@ -23,7 +23,7 @@ public:
{
}
uint32_t capabilities() const
uint32_t capabilities() const override
{
return OP_GETFSDATA | OP_CREATE | OP_LIST | OP_GETFILE | OP_PUTFILE |
OP_GETDIRENT | OP_MOVE | OP_CREATEDIR | OP_DELETE;

223
lib/vfs/microdos.cc Normal file
View File

@@ -0,0 +1,223 @@
#include "lib/globals.h"
#include "lib/vfs/vfs.h"
#include "lib/config.pb.h"
#include "lib/utils.h"
#include <fmt/format.h>
/* See https://www.hp9845.net/9845/projects/hpdir/#lif_filesystem for
* a description. */
static void trimZeros(std::string s)
{
s.erase(std::remove(s.begin(), s.end(), 0), s.end());
}
class MicrodosFilesystem : public Filesystem
{
struct SDW
{
unsigned start;
unsigned length;
};
class MicrodosDirent : public Dirent
{
public:
MicrodosDirent(MicrodosFilesystem& fs, Bytes& bytes)
{
file_type = TYPE_FILE;
ByteReader br(bytes);
auto stem = trimWhitespace(br.read(6));
auto ext = trimWhitespace(br.read(3));
filename = fmt::format("{}.{}", stem, ext);
br.skip(1);
ssn = br.read_be16();
attr = br.read_8();
Bytes rib = fs.getLogicalSector(ssn);
ByteReader rbr(rib);
for (int i = 0; i < 57; i++)
{
unsigned w = rbr.read_be16();
if (w & 0x8000)
{
/* Last. */
sectors = w & 0x7fff;
break;
}
else
{
/* Each record except the last is 24 bits long. */
w = (w << 8) | rbr.read_8();
sdws.emplace_back(SDW{w & 0xffff, (w >> 16) + 1});
}
}
rbr.seek(500);
lastSectorBytes = rbr.read_be16();
loadSectors = rbr.read_be16();
loadAddress = rbr.read_be16();
startAddress = rbr.read_be16();
length = sectors * 512;
mode = "";
path = {filename};
attributes[Filesystem::FILENAME] = filename;
attributes[Filesystem::LENGTH] = std::to_string(length);
attributes[Filesystem::FILE_TYPE] = "file";
attributes[Filesystem::MODE] = mode;
attributes["microdos.ssn"] = std::to_string(ssn);
attributes["microdos.attr"] = fmt::format("0x{:x}", attr);
attributes["microdos.sdw_count"] = std::to_string(sdws.size());
attributes["microdos.total_sectors"] = std::to_string(sectors);
attributes["microdos.lastSectorBytes"] =
std::to_string(lastSectorBytes);
attributes["microdos.loadSectors"] = std::to_string(loadSectors);
attributes["microdos.loadAddress"] =
fmt::format("0x{:x}", loadAddress);
attributes["microdos.startAddress"] =
fmt::format("0x{:x}", startAddress);
}
public:
unsigned ssn;
unsigned attr;
std::vector<SDW> sdws;
unsigned sectors;
unsigned lastSectorBytes;
unsigned loadSectors;
unsigned loadAddress;
unsigned startAddress;
};
public:
MicrodosFilesystem(
const MicrodosProto& 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::map<std::string, std::string> attributes;
attributes[VOLUME_NAME] = _volumeLabel;
attributes[TOTAL_BLOCKS] = std::to_string(_totalBlocks);
attributes[USED_BLOCKS] = std::to_string(_usedBlocks);
attributes[BLOCK_SIZE] = "512";
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);
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& sdw : dirent->sdws)
bw += getLogicalSector(sdw.start, sdw.length);
return data.slice(512);
}
private:
void mount()
{
_rootBlock = getLogicalSector(0);
_catBlock = getLogicalSector(9);
Bytes directory = getLogicalSector(1, 8);
ByteReader rbr(_rootBlock);
rbr.seek(20);
_volumeLabel = trimWhitespace(rbr.read(44));
_dirents.clear();
ByteReader dbr(directory);
while (!dbr.eof())
{
Bytes direntBytes = dbr.read(16);
if ((direntBytes[0] != 0) && (direntBytes[0] != 0xff))
{
auto dirent =
std::make_unique<MicrodosDirent>(*this, direntBytes);
_dirents.push_back(std::move(dirent));
}
}
ByteReader cbr(_catBlock);
_totalBlocks = 630;
_usedBlocks = 0;
for (int i = 0; i < _totalBlocks / 8; i++)
{
uint8_t b = cbr.read_8();
_usedBlocks += countSetBits(b);
}
}
std::shared_ptr<MicrodosDirent> findFile(const std::string filename)
{
for (const auto& dirent : _dirents)
{
if (dirent->filename == filename)
return dirent;
}
throw FileNotFoundException();
}
private:
const MicrodosProto& _config;
Bytes _rootBlock;
Bytes _catBlock;
std::string _volumeLabel;
unsigned _totalBlocks;
unsigned _usedBlocks;
std::vector<std::shared_ptr<MicrodosDirent>> _dirents;
};
std::unique_ptr<Filesystem> Filesystem::createMicrodosFilesystem(
const FilesystemProto& config, std::shared_ptr<SectorInterface> sectors)
{
return std::make_unique<MicrodosFilesystem>(config.microdos(), sectors);
}

View File

@@ -10,14 +10,14 @@
* 0d 2a
* 0e 79
* 0f 6d
* 10 07 0x07c10c19, creation timestamp
* 10 07 0x07c10c19, creation timestamp; year
* 11 c1 ^
* 12 0c ^
* 13 19 ^
* 14 2f
* 12 0c month
* 13 19 day
* 14 2f time? minutes
* 15 00
* 16 00
* 17 18
* 16 00 hours
* 17 18 seconds
* 18 03 0x320, number of blocks on the disk
* 19 20 ^
* 1a 00 0x0010, first data block?
@@ -88,11 +88,6 @@
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
@@ -125,6 +120,15 @@ class PhileFilesystem : public Filesystem
this->filename = filename;
path = {filename};
attributes[Filesystem::CTIME] =
fmt::format("{:04}-{:02}-{:02}T{:02}:{:02}:{:02}",
filedes.reader().seek(4).read_be16(),
filedes[6],
filedes[7] + 1,
filedes[10] & 0x1f,
filedes[8],
filedes[11]);
attributes[Filesystem::FILENAME] = filename;
attributes[Filesystem::LENGTH] = std::to_string(length);
attributes[Filesystem::FILE_TYPE] = "file";
@@ -160,7 +164,7 @@ public:
{
}
uint32_t capabilities() const
uint32_t capabilities() const override
{
return OP_GETFSDATA | OP_LIST | OP_GETFILE | OP_GETDIRENT;
}
@@ -175,7 +179,7 @@ public:
mount();
std::string volumename = _rootBlock.reader().read(0x0c);
trimZeros(volumename);
volumename = trimWhitespace(volumename);
std::map<std::string, std::string> attributes;
attributes[VOLUME_NAME] = volumename;
@@ -246,7 +250,7 @@ private:
{
uint16_t fileno = br.read_be16();
std::string filename = br.read(14);
trimZeros(filename);
filename = trimWhitespace(filename);
if (fileno)
{

View File

@@ -140,7 +140,7 @@ public:
{
}
uint32_t capabilities() const
uint32_t capabilities() const override
{
return OP_LIST | OP_GETDIRENT | OP_GETFILE | OP_GETFSDATA;
}

427
lib/vfs/roland.cc Normal file
View File

@@ -0,0 +1,427 @@
#include "lib/globals.h"
#include "lib/vfs/vfs.h"
#include "lib/config.pb.h"
#include "lib/utils.h"
#include <regex>
static std::string unmangleFilename(const std::string& mangled)
{
std::string extension = mangled.substr(10);
extension.erase(extension.find_last_not_of("_") + 1);
std::string root = mangled.substr(0, 10);
root.erase(root.find_last_not_of("_") + 1);
if (!extension.empty())
return root + "." + extension;
return root;
}
static std::string mangleFilename(const std::string& human)
{
int dot = human.rfind('.');
std::string extension =
(dot == std::string::npos) ? "" : human.substr(dot + 1);
std::string root =
(dot == std::string::npos) ? human : human.substr(0, dot);
if (extension.empty())
extension = "___";
if (extension.size() > 3)
throw BadPathException("Invalid filename: extension too long");
if (root.size() > 10)
throw BadPathException("Invalid filename: root too long");
root = (root + std::string(10, '_')).substr(0, 10);
std::string mangled = root + extension;
static const std::regex checker("[A-Z0-9_$.]*");
if (!std::regex_match(mangled, checker))
throw BadPathException(
"Invalid filename: unsupported characters (remember to use "
"uppercase)");
return mangled;
}
class RolandFsFilesystem : public Filesystem
{
private:
class RolandDirent : public Dirent
{
public:
RolandDirent(const std::string& filename)
{
file_type = TYPE_FILE;
rename(filename);
length = 0;
attributes[Filesystem::FILENAME] = filename;
attributes[Filesystem::LENGTH] = std::to_string(length);
attributes[Filesystem::FILE_TYPE] = "file";
attributes[Filesystem::MODE] = "";
}
void rename(const std::string& name)
{
filename = name;
path = {filename};
}
void putBlock(RolandFsFilesystem* fs, uint8_t offset, uint8_t block)
{
if (blocks.size() <= offset)
blocks.resize(offset+1);
blocks[offset] = block;
length = (offset+1) * fs->_blockSectors * fs->_sectorSize;
attributes[Filesystem::LENGTH] = std::to_string(length);
}
void putBlocks(RolandFsFilesystem* fs, uint8_t offset, Bytes& dirent)
{
for (int i = 0; i < 16; i++)
{
uint8_t blocknumber = dirent[16 + i];
if (!blocknumber)
break;
putBlock(fs, offset+i, blocknumber);
}
}
public:
std::vector<int> blocks;
};
public:
RolandFsFilesystem(
const RolandFsProto& config, std::shared_ptr<SectorInterface> sectors):
Filesystem(sectors),
_config(config)
{
}
uint32_t capabilities() const override
{
return OP_CREATE | OP_GETFSDATA | OP_LIST | OP_GETFILE | OP_PUTFILE |
OP_GETDIRENT | OP_DELETE | OP_MOVE;
}
std::map<std::string, std::string> getMetadata() override
{
mount();
int usedBlocks = 0;
for (bool b : _allocationBitmap)
usedBlocks += b;
std::map<std::string, std::string> attributes;
attributes[VOLUME_NAME] = "";
attributes[TOTAL_BLOCKS] = std::to_string(_filesystemBlocks);
attributes[USED_BLOCKS] = std::to_string(usedBlocks);
attributes[BLOCK_SIZE] = std::to_string(_config.block_size());
return attributes;
}
FilesystemStatus check() override
{
return FS_OK;
}
void create(bool quick, const std::string& volumeName) override
{
if (!quick)
eraseEverythingOnDisk();
init();
_allocationBitmap[0] = true;
rewriteDirectory();
}
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);
return result;
}
std::shared_ptr<Dirent> getDirent(const Path& path) override
{
mount();
if (path.size() != 1)
throw BadPathException();
return findFile(path.front());
}
Bytes getFile(const Path& path) override
{
mount();
if (path.size() != 1)
throw BadPathException();
Bytes data;
ByteWriter bw(data);
auto f = findFile(path.front());
for (uint8_t b : f->blocks)
bw += getRolandBlock(b);
return data;
}
void putFile(const Path& path, const Bytes& bytes) override
{
mount();
if (path.size() != 1)
throw BadPathException();
if (findFileOrReturnNull(path.front()))
throw BadPathException("File exists");
int blocks = bytes.size() / _config.block_size();
auto de = std::make_shared<RolandDirent>(
RolandDirent(mangleFilename(path.front())));
ByteReader br(bytes);
int offset = 0;
while (!br.eof())
{
Bytes data =
br.read(_config.block_size()).slice(0, _config.block_size());
int block = allocateBlock();
de->putBlock(this, offset, block);
putRolandBlock(block, data);
offset++;
}
_dirents.push_back(de);
rewriteDirectory();
}
void deleteFile(const Path& path) override
{
mount();
if (path.size() != 1)
throw BadPathException();
auto de = findFile(path.front());
for (uint8_t b : de->blocks)
freeBlock(b);
for (auto it = _dirents.begin(); it != _dirents.end(); it++)
{
if (*it == de)
{
_dirents.erase(it);
break;
}
}
rewriteDirectory();
}
void moveFile(const Path& oldName, const Path& newName) override
{
mount();
if ((oldName.size() != 1) || (newName.size() != 1))
throw BadPathException();
if (findFileOrReturnNull(newName.front()))
throw BadPathException("File exists");
auto de = findFile(oldName.front());
de->rename(mangleFilename(newName.front()));
rewriteDirectory();
}
private:
void init()
{
_directoryLba = getOffsetOfSector(_config.directory_track(), 0, 0);
_sectorSize = getLogicalSectorSize(0, 0);
_blockSectors = _config.block_size() / _sectorSize;
_filesystemBlocks = getLogicalSectorCount() / _blockSectors;
_midBlock = (getLogicalSectorCount() - _directoryLba) / _blockSectors;
_dirents.clear();
_allocationBitmap.clear();
_allocationBitmap.resize(_filesystemBlocks);
}
void rewriteDirectory()
{
Bytes directory;
ByteWriter bw(directory);
bw.write_8(0);
bw.append("ROLAND-GCRDOS");
bw.write_le16(0x4e);
bw.pad(16);
for (auto& de : _dirents)
{
int blockIndex = 0;
for (;;)
{
if (bw.pos == 0xa00)
throw DiskFullException();
if ((blockIndex % 16) == 0)
{
bw.write_8(0);
int len = de->filename.size();
bw.append(de->filename);
bw.pad(13 - len, '_');
bw.write_8(0);
bw.write_8(blockIndex / 16);
}
if (blockIndex == de->blocks.size())
break;
bw.write_8(de->blocks[blockIndex]);
blockIndex++;
}
bw.pad(16 - (blockIndex % 16), 0);
}
while (bw.pos != 0xa00)
{
bw.write_8(0xe5);
bw.pad(13, ' ');
bw.write_le16(0);
bw.pad(16);
}
for (bool b : _allocationBitmap)
bw.write_8(b ? 0xff : 0x00);
putRolandBlock(0, directory.slice(0, _config.block_size()));
}
void mount()
{
init();
Bytes directory = getRolandBlock(0);
ByteReader br(directory);
br.seek(1);
if (br.read(13) != "ROLAND-GCRDOS")
throw BadFilesystemException();
br.seek(32);
std::map<std::string, std::shared_ptr<RolandDirent>> files;
for (int i = 0; i < _config.directory_entries(); i++)
{
Bytes direntBytes = br.read(32);
if (direntBytes[0] == 0)
{
int extent = direntBytes[15];
std::string filename =
unmangleFilename(direntBytes.slice(1, 13));
std::shared_ptr<RolandDirent> de;
auto it = files.find(filename);
if (it == files.end())
{
files[filename] = de =
std::make_shared<RolandDirent>(filename);
_dirents.push_back(de);
}
else
de = it->second;
de->putBlocks(this, extent*16, direntBytes);
}
}
br.seek(0xa00);
for (int i = 0; i < _filesystemBlocks; i++)
_allocationBitmap[i] = br.read_8();
}
std::shared_ptr<RolandDirent> findFileOrReturnNull(
const std::string filename)
{
for (const auto& dirent : _dirents)
{
if (dirent->filename == filename)
return dirent;
}
return nullptr;
}
std::shared_ptr<RolandDirent> findFile(const std::string filename)
{
std::shared_ptr<RolandDirent> de = findFileOrReturnNull(filename);
if (!de)
throw FileNotFoundException();
return de;
}
int allocateBlock()
{
for (int i = 0; i < _filesystemBlocks; i++)
if (!_allocationBitmap[i])
{
_allocationBitmap[i] = true;
return i;
}
throw DiskFullException();
}
void freeBlock(int block)
{
if (block >= _filesystemBlocks)
throw BadFilesystemException();
if (!_allocationBitmap[block])
throw BadFilesystemException();
_allocationBitmap[block] = false;
}
unsigned blockToLogicalSectorNumber(int block)
{
int track;
if (block < _midBlock)
track = _config.directory_track() + block;
else
track = _config.directory_track() - (1 + block - _midBlock);
return track * _blockSectors;
}
Bytes getRolandBlock(int number)
{
return getLogicalSector(
blockToLogicalSectorNumber(number), _blockSectors);
}
void putRolandBlock(int number, const Bytes& bytes)
{
assert(bytes.size() == _config.block_size());
putLogicalSector(blockToLogicalSectorNumber(number), bytes);
}
private:
const RolandFsProto& _config;
unsigned _sectorSize;
unsigned _blockSectors;
unsigned _midBlock;
unsigned _directoryLba;
unsigned _filesystemBlocks;
std::vector<std::shared_ptr<RolandDirent>> _dirents;
std::vector<bool> _allocationBitmap;
};
std::unique_ptr<Filesystem> Filesystem::createRolandFsFilesystem(
const FilesystemProto& config, std::shared_ptr<SectorInterface> sectors)
{
return std::make_unique<RolandFsFilesystem>(config.roland(), sectors);
}

View File

@@ -11,6 +11,9 @@ class Encoder;
class SectorInterface
{
public:
virtual ~SectorInterface() {}
public:
virtual std::shared_ptr<const Sector> get(
unsigned track, unsigned side, unsigned sectorId) = 0;

View File

@@ -139,7 +139,7 @@ public:
{
}
uint32_t capabilities() const
uint32_t capabilities() const override
{
return OP_LIST | OP_GETFILE | OP_GETFSDATA | OP_GETDIRENT;
}

View File

@@ -216,6 +216,15 @@ std::unique_ptr<Filesystem> Filesystem::createFilesystem(
case FilesystemProto::LIF:
return Filesystem::createLifFilesystem(config, image);
case FilesystemProto::MICRODOS:
return Filesystem::createMicrodosFilesystem(config, image);
case FilesystemProto::ZDOS:
return Filesystem::createZDosFilesystem(config, image);
case FilesystemProto::ROLAND:
return Filesystem::createRolandFsFilesystem(config, image);
default:
error("no filesystem configured");
return std::unique_ptr<Filesystem>();
@@ -236,7 +245,7 @@ std::unique_ptr<Filesystem> Filesystem::createFilesystemFromConfig()
fluxSource = globalConfig().getFluxSource();
decoder = globalConfig().getDecoder();
}
if (globalConfig()->flux_sink().type() == FluxSinkProto::DRIVE)
if (globalConfig()->flux_sink().type() == FLUXTYPE_DRIVE)
{
fluxSink = globalConfig().getFluxSink();
encoder = globalConfig().getEncoder();
@@ -273,8 +282,8 @@ Bytes Filesystem::getLogicalSector(uint32_t number, uint32_t count)
{
if ((number + count) > _locations.size())
throw BadFilesystemException(
fmt::format("invalid filesystem: sector {} is out of bounds",
number + count - 1));
fmt::format("invalid filesystem: sector {} is out of bounds ({} maximum)",
number + count - 1, _locations.size()));
Bytes data;
ByteWriter bw(data);

View File

@@ -101,6 +101,14 @@ public:
DiskFullException(const std::string& msg): CannotWriteException(msg) {}
};
class ReadErrorException : public FilesystemException
{
public:
ReadErrorException(): FilesystemException("Fatal read error") {}
ReadErrorException(const std::string& msg): FilesystemException(msg) {}
};
class ReadOnlyFilesystemException : public FilesystemException
{
public:
@@ -135,6 +143,7 @@ public:
static constexpr const char* LENGTH = "length";
static constexpr const char* MODE = "mode";
static constexpr const char* FILE_TYPE = "file_type";
static constexpr const char* CTIME = "ctime";
static constexpr const char* VOLUME_NAME = "volume_name";
static constexpr const char* TOTAL_BLOCKS = "total_blocks";
@@ -256,6 +265,12 @@ public:
const FilesystemProto& config, std::shared_ptr<SectorInterface> image);
static std::unique_ptr<Filesystem> createLifFilesystem(
const FilesystemProto& config, std::shared_ptr<SectorInterface> image);
static std::unique_ptr<Filesystem> createMicrodosFilesystem(
const FilesystemProto& config, std::shared_ptr<SectorInterface> image);
static std::unique_ptr<Filesystem> createZDosFilesystem(
const FilesystemProto& config, std::shared_ptr<SectorInterface> image);
static std::unique_ptr<Filesystem> createRolandFsFilesystem(
const FilesystemProto& config, std::shared_ptr<SectorInterface> image);
static std::unique_ptr<Filesystem> createFilesystem(
const FilesystemProto& config, std::shared_ptr<SectorInterface> image);

View File

@@ -88,7 +88,32 @@ message LifProto
[ default = 256, (help) = "LIF filesystem block size" ];
}
// NEXT_TAG: 15
message MicrodosProto {}
// NEXT_TAG: 16
message ZDosProto
{
message Location
{
optional uint32 track = 1 [ (help) = "track number" ];
optional uint32 sector = 3 [ (help) = "sector ID" ];
}
optional Location filesystem_start = 1
[ (help) = "position of the filesystem superblock" ];
}
message RolandFsProto
{
optional uint32 directory_track = 1
[ (help) = "position of the directory", default = 39 ];
optional uint32 block_size = 2
[ (help) = "filesystem block size", default = 3072 ];
optional uint32 directory_entries = 3
[ (help) = "number of directory entries", default = 79 ];
}
// NEXT_TAG: 18
message FilesystemProto
{
enum FilesystemType
@@ -106,6 +131,9 @@ message FilesystemProto
APPLEDOS = 10;
PHILE = 11;
LIF = 12;
MICRODOS = 13;
ZDOS = 14;
ROLAND = 15;
}
optional FilesystemType type = 10
@@ -123,6 +151,9 @@ message FilesystemProto
optional Smaky6FsProto smaky6 = 11;
optional PhileProto phile = 13;
optional LifProto lif = 14;
optional MicrodosProto microdos = 15;
optional ZDosProto zdos = 16;
optional RolandFsProto roland = 17;
optional SectorListProto sector_order = 9
[ (help) = "specify the filesystem order of sectors" ];

325
lib/vfs/zdos.cc Normal file
View File

@@ -0,0 +1,325 @@
#include "lib/globals.h"
#include "lib/vfs/vfs.h"
#include "lib/config.pb.h"
#include "lib/layout.h"
#include <iomanip>
/* See
* https://oldcomputers.dyndns.org/public/pub/rechner/zilog/zds/manuals/z80-rio_os_userman.pdf,
* page 116. */
enum
{
ZDOS_TYPE_DATA = 0x10,
ZDOS_TYPE_ASCII = 0x20,
ZDOS_TYPE_DIRECTORY = 0x40,
ZDOS_TYPE_PROCEDURE = 0x80
};
enum
{
ZDOS_MODE_FORCE = 1 << 2,
ZDOS_MODE_RANDOM = 1 << 3,
ZDOS_MODE_SECRET = 1 << 4,
ZDOS_MODE_LOCKED = 1 << 5,
ZDOS_MODE_ERASEPROTECT = 1 << 6,
ZDOS_MODE_WRITEPROTECT = 1 << 7
};
static const std::map<uint8_t, std::string> fileTypeMap = {
{0, "INVALID" },
{ZDOS_TYPE_DATA, "DATA" },
{ZDOS_TYPE_ASCII, "ASCII" },
{ZDOS_TYPE_DIRECTORY, "DIRECTORY"},
{ZDOS_TYPE_PROCEDURE, "PROCEDURE"}
};
static std::string convertTime(std::string zdosTime)
{
/* Due to a bug in std::get_time, we can't parse the string directly --- a
* pattern of %y%m%d causes the first four digits of the string to become
* the year. So we need to reform the string. */
zdosTime = fmt::format("{}-{}-{}",
zdosTime.substr(0, 2),
zdosTime.substr(2, 2),
zdosTime.substr(4, 2));
std::tm tm = {};
std::stringstream(zdosTime) >> std::get_time(&tm, "%y-%m-%d");
std::stringstream ss;
ss << std::put_time(&tm, "%FT%T%z");
return ss.str();
}
class ZDosFilesystem : public Filesystem
{
class ZDosDescriptor
{
public:
ZDosDescriptor(ZDosFilesystem* zfs, int block): zfs(zfs)
{
Bytes bytes = zfs->getLogicalSector(block);
ByteReader br(bytes);
br.seek(8);
firstRecord = zfs->readBlockNumber(br);
br.seek(12);
type = br.read_8();
recordCount = br.read_le16();
recordSize = br.read_le16();
br.seek(19);
properties = br.read_8();
startAddress = br.read_le16();
lastRecordSize = br.read_le16();
br.seek(24);
ctime = br.read(8);
mtime = br.read(8);
rewind();
}
void rewind()
{
currentRecord = firstRecord;
eof = false;
}
Bytes readRecord()
{
assert(!eof);
int count = recordSize / 0x80;
Bytes result;
ByteWriter bw(result);
while (count--)
{
Bytes sector = zfs->getLogicalSector(currentRecord);
ByteReader br(sector);
bw += br.read(0x80);
br.skip(2);
int sectorId = br.read_8();
int track = br.read_8();
currentRecord = zfs->toBlockNumber(sectorId, track);
if (sectorId == 0xff)
eof = true;
}
return result;
}
public:
ZDosFilesystem* zfs;
uint16_t firstRecord;
uint8_t type;
uint16_t recordCount;
uint16_t recordSize;
uint8_t properties;
uint16_t startAddress;
uint16_t lastRecordSize;
std::string ctime;
std::string mtime;
uint16_t currentRecord;
bool eof;
};
class ZDosDirent : public Dirent
{
public:
ZDosDirent(ZDosFilesystem* zfs,
const std::string& filename,
int descriptorBlock):
zfs(zfs),
descriptorBlock(descriptorBlock),
zd(zfs, descriptorBlock)
{
file_type = TYPE_FILE;
this->filename = filename;
length = (zd.recordCount - 1) * zd.recordSize + zd.lastRecordSize;
mode = "";
if (zd.properties & ZDOS_MODE_FORCE)
mode += 'F';
if (zd.properties & ZDOS_MODE_RANDOM)
mode += 'R';
if (zd.properties & ZDOS_MODE_SECRET)
mode += 'S';
if (zd.properties & ZDOS_MODE_LOCKED)
mode += 'L';
if (zd.properties & ZDOS_MODE_ERASEPROTECT)
mode += 'E';
if (zd.properties & ZDOS_MODE_WRITEPROTECT)
mode += 'W';
path = {filename};
attributes[Filesystem::FILENAME] = filename;
attributes[Filesystem::LENGTH] = std::to_string(length);
attributes[Filesystem::FILE_TYPE] = "file";
attributes[Filesystem::MODE] = mode;
attributes["zdos.descriptor_record"] =
std::to_string(descriptorBlock);
attributes["zdos.first_record"] = std::to_string(zd.firstRecord);
attributes["zdos.record_size"] = std::to_string(zd.recordSize);
attributes["zdos.record_count"] = std::to_string(zd.recordCount);
attributes["zdos.last_record_size"] =
std::to_string(zd.lastRecordSize);
attributes["zdos.start_address"] =
fmt::format("0x{:04x}", zd.startAddress);
attributes["zdos.type"] = fileTypeMap.at(zd.type & 0xf0);
attributes["zdos.ctime"] = convertTime(zd.ctime);
attributes["zdos.mtime"] = convertTime(zd.mtime);
}
public:
ZDosFilesystem* zfs;
int descriptorBlock;
ZDosDescriptor zd;
};
public:
ZDosFilesystem(
const ZDosProto& 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::map<std::string, std::string> attributes;
attributes[VOLUME_NAME] = "";
attributes[TOTAL_BLOCKS] = std::to_string(_totalBlocks);
attributes[USED_BLOCKS] = std::to_string(_usedBlocks);
attributes[BLOCK_SIZE] = "128";
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);
return result;
}
Bytes getFile(const Path& path) override
{
mount();
if (path.size() != 1)
throw BadPathException();
auto dirent = findFile(path.front());
dirent->zd.rewind();
Bytes bytes;
ByteWriter bw(bytes);
while (!dirent->zd.eof)
{
bw += dirent->zd.readRecord();
}
return bytes.slice(0, dirent->length);
}
private:
void mount()
{
_sectorsPerTrack = Layout::getLayoutOfTrack(0, 0)->numSectors;
int rootBlock = toBlockNumber(_config.filesystem_start().sector(),
_config.filesystem_start().track());
ZDosDescriptor zd(this, rootBlock);
if (zd.type != ZDOS_TYPE_DIRECTORY)
throw BadFilesystemException();
_totalBlocks = getLogicalSectorCount();
_usedBlocks = (zd.recordCount * zd.recordSize) / 0x80 + 1;
while (!zd.eof)
{
Bytes bytes = zd.readRecord();
ByteReader br(bytes);
for (;;)
{
int len = br.read_8();
if (len == 0xff)
break;
std::string filename = br.read(len & 0x7f);
int descriptorBlock = readBlockNumber(br);
auto dirent = std::make_unique<ZDosDirent>(
this, filename, descriptorBlock);
_usedBlocks +=
(dirent->zd.recordCount * dirent->zd.recordSize) / 0x80 + 1;
_dirents.push_back(std::move(dirent));
}
}
}
std::shared_ptr<ZDosDirent> findFile(const std::string filename)
{
for (const auto& dirent : _dirents)
{
if (dirent->filename == filename)
return dirent;
}
throw FileNotFoundException();
}
int toBlockNumber(int sectorId, int track)
{
return track * _sectorsPerTrack + sectorId;
}
int readBlockNumber(ByteReader& br)
{
int sectorId = br.read_8();
int track = br.read_8();
return toBlockNumber(sectorId, track);
}
private:
const ZDosProto& _config;
unsigned _sectorsPerTrack;
unsigned _totalBlocks;
unsigned _usedBlocks;
std::vector<std::shared_ptr<ZDosDirent>> _dirents;
};
std::unique_ptr<Filesystem> Filesystem::createZDosFilesystem(
const FilesystemProto& config, std::shared_ptr<SectorInterface> sectors)
{
return std::make_unique<ZDosFilesystem>(config.zdos(), sectors);
}

View File

@@ -14,9 +14,12 @@ static std::string supportStatus(SupportStatus status)
case SupportStatus::UNICORN:
return "🦄";
case SupportStatus::UNSUPPORTED:
return "";
}
return "";
return "";
}
int main(int argc, const char* argv[])

View File

@@ -247,10 +247,10 @@ static void draw_x_graticules(Agg2D& painter,
int mainAnalyseDriveResponse(int argc, const char* argv[])
{
globalConfig().overrides()->mutable_flux_source()->set_type(
FluxSourceProto::DRIVE);
FLUXTYPE_DRIVE);
flags.parseFlagsWithConfigFiles(argc, argv, {});
if (globalConfig()->flux_sink().type() != FluxSinkProto::DRIVE)
if (globalConfig()->flux_sink().type() != FLUXTYPE_DRIVE)
error("this only makes sense with a real disk drive");
usbSetDrive(globalConfig()->drive().drive(),

View File

@@ -132,7 +132,7 @@ static nanoseconds_t guessClock(const Fluxmap& fluxmap)
int mainInspect(int argc, const char* argv[])
{
globalConfig().overrides()->mutable_flux_source()->set_type(
FluxSourceProto::DRIVE);
FLUXTYPE_DRIVE);
flags.parseFlagsWithConfigFiles(argc, argv, {});
auto& fluxSource = globalConfig().getFluxSource();

View File

@@ -52,12 +52,13 @@ int mainLs(int argc, const char* argv[])
uint32_t total = 0;
for (const auto& dirent : files)
{
fmt::print("{} {:{}} {:6} {}\n",
fmt::print("{} {:{}} {:6} {:4} {}\n",
fileTypeChar(dirent->file_type),
quote(dirent->filename),
maxlen + 2,
dirent->length,
dirent->mode);
dirent->mode,
dirent->attributes[Filesystem::CTIME]);
total += dirent->length;
}
fmt::print("({} files, {} bytes)\n", files.size(), total);

View File

@@ -57,10 +57,10 @@ int mainRawRead(int argc, const char* argv[])
if (argc == 1)
showProfiles("rawread", formats);
globalConfig().overrides()->mutable_flux_source()->set_type(
FluxSourceProto::DRIVE);
FLUXTYPE_DRIVE);
flags.parseFlagsWithConfigFiles(argc, argv, formats);
if (globalConfig()->flux_sink().type() == FluxSinkProto::DRIVE)
if (globalConfig()->flux_sink().type() == FLUXTYPE_DRIVE)
error("you can't use rawread to write to hardware");
std::shared_ptr<FluxSource> fluxSource = globalConfig().getFluxSource();

View File

@@ -49,7 +49,7 @@ static ActionFlag eraseFlag({"--erase"},
[]()
{
globalConfig().overrides()->mutable_flux_source()->set_type(
FluxSourceProto::ERASE);
FLUXTYPE_ERASE);
});
int mainRawWrite(int argc, const char* argv[])
@@ -60,10 +60,10 @@ int mainRawWrite(int argc, const char* argv[])
if (argc == 1)
showProfiles("rawwrite", formats);
globalConfig().overrides()->mutable_flux_sink()->set_type(
FluxSinkProto::DRIVE);
FLUXTYPE_DRIVE);
flags.parseFlagsWithConfigFiles(argc, argv, formats);
if (globalConfig()->flux_source().type() == FluxSourceProto::DRIVE)
if (globalConfig()->flux_source().type() == FLUXTYPE_DRIVE)
error("you can't use rawwrite to read from hardware");
auto& fluxSource = globalConfig().getFluxSource();

View File

@@ -61,10 +61,10 @@ int mainRead(int argc, const char* argv[])
{
if (argc == 1)
showProfiles("read", formats);
globalConfig().set("flux_source.type", "DRIVE");
globalConfig().set("flux_source.type", "FLUXTYPE_DRIVE");
flags.parseFlagsWithConfigFiles(argc, argv, formats);
if (globalConfig()->decoder().copy_flux_to().type() == FluxSinkProto::DRIVE)
if (globalConfig()->decoder().copy_flux_to().type() == FLUXTYPE_DRIVE)
error("you cannot copy flux to a hardware device");
auto& fluxSource = globalConfig().getFluxSource();

View File

@@ -19,7 +19,7 @@ int mainRpm(int argc, const char* argv[])
{
flags.parseFlagsWithConfigFiles(argc, argv, {});
if (globalConfig()->flux_source().type() != FluxSourceProto::DRIVE)
if (globalConfig()->flux_source().type() != FLUXTYPE_DRIVE)
error("this only makes sense with a real disk drive");
usbSetDrive(globalConfig()->drive().drive(),

View File

@@ -23,7 +23,7 @@ int mainSeek(int argc, const char* argv[])
{
flags.parseFlagsWithConfigFiles(argc, argv, {});
if (globalConfig()->flux_source().type() != FluxSourceProto::DRIVE)
if (globalConfig()->flux_source().type() != FLUXTYPE_DRIVE)
error("this only makes sense with a real disk drive");
usbSetDrive(globalConfig()->drive().drive(),

View File

@@ -14,6 +14,18 @@
#include <google/protobuf/text_format.h>
#include <fstream>
static void ignoreInapplicableValueExceptions(std::function<void(void)> cb)
{
try
{
cb();
}
catch (const InapplicableValueException* e)
{
/* swallow */
}
}
FlagGroup fileFlags;
static StringFlag image({"-i", "--image"},
@@ -21,8 +33,16 @@ static StringFlag image({"-i", "--image"},
"",
[](const auto& value)
{
globalConfig().setImageReader(value);
globalConfig().setImageWriter(value);
ignoreInapplicableValueExceptions(
[&]()
{
globalConfig().setImageReader(value);
});
ignoreInapplicableValueExceptions(
[&]()
{
globalConfig().setImageWriter(value);
});
});
static StringFlag flux({"-f", "--flux"},
@@ -30,6 +50,14 @@ static StringFlag flux({"-f", "--flux"},
"",
[](const auto& value)
{
globalConfig().setFluxSource(value);
globalConfig().setFluxSink(value);
ignoreInapplicableValueExceptions(
[&]()
{
globalConfig().setFluxSink(value);
});
ignoreInapplicableValueExceptions(
[&]()
{
globalConfig().setFluxSource(value);
});
});

View File

@@ -22,7 +22,7 @@ they might require nudging as the side order can't be reliably autodetected.
image_writer {
filename: "acornadfs.img"
type: IMG
type: IMAGETYPE_IMG
}
decoder {

View File

@@ -25,12 +25,12 @@ documentation:
image_reader {
filename: "acorndfs.img"
type: IMG
type: IMAGETYPE_IMG
}
image_writer {
filename: "acorndfs.img"
type: IMG
type: IMAGETYPE_IMG
}
layout {

View File

@@ -43,7 +43,7 @@ documentation:
image_writer {
filename: "aeslanier.img"
type: IMG
type: IMAGETYPE_IMG
}
decoder {

View File

@@ -28,7 +28,7 @@ documentation:
image_writer {
filename: "agat.img"
type: IMG
type: IMAGETYPE_IMG
}
layout {

View File

@@ -31,12 +31,12 @@ documentation:
image_reader {
filename: "amiga.adf"
type: IMG
type: IMAGETYPE_IMG
}
image_writer {
filename: "amiga.adf"
type: IMG
type: IMAGETYPE_IMG
}
layout {

View File

@@ -44,7 +44,7 @@ documentation:
image_writer {
filename: "ampro.img"
type: IMG
type: IMAGETYPE_IMG
}
layout {

View File

@@ -19,12 +19,12 @@ on what was available at the time, with the same format on both.
image_reader {
filename: "bk800.img"
type: IMG
type: IMAGETYPE_IMG
}
image_writer {
filename: "bk800.img"
type: IMG
type: IMAGETYPE_IMG
}
layout {

View File

@@ -116,12 +116,12 @@ file system supports this.
image_reader {
filename: "brother.img"
type: IMG
type: IMAGETYPE_IMG
}
image_writer {
filename: "brother.img"
type: IMG
type: IMAGETYPE_IMG
}
encoder {

View File

@@ -21,6 +21,7 @@ FORMATS = \
icl30 \
mac \
micropolis \
ms2000 \
mx \
n88basic \
northstar \

View File

@@ -54,12 +54,12 @@ documentation:
image_reader {
filename: "commodore.d64"
type: D64
type: IMAGETYPE_D64
}
image_writer {
filename: "commodore.d64"
type: D64
type: IMAGETYPE_D64
}
filesystem {

View File

@@ -37,7 +37,7 @@ documentation:
image_writer {
filename: "eco1.img"
type: IMG
type: IMAGETYPE_IMG
}
layout {

View File

@@ -11,7 +11,7 @@ format itself is yet another IBM scheme variant.
image_writer {
filename: "epsonpf10.img"
type: IMG
type: IMAGETYPE_IMG
}
layout {

View File

@@ -42,7 +42,7 @@ There's amazingly little information about these things.
image_writer {
filename: "f85.img"
type: IMG
type: IMAGETYPE_IMG
}
decoder {

View File

@@ -43,7 +43,7 @@ documentation:
image_writer {
filename: "fb100.img"
type: IMG
type: IMAGETYPE_IMG
}
decoder {

View File

@@ -14,18 +14,26 @@ Floppy-disk wise, they're yet more variations of the standard IBM floppy
encoding scheme.
>>>
documentation:
<<<
## References
* [A summary of the Hewlett Packard floppy disk
formats](http://www.bitsavers.org/pdf/hp/disc/912x/HP_Flexible_Disk_Formats.pdf)
>>>
drive {
high_density: false
}
image_reader {
filename: "hplif.img"
type: IMG
type: IMAGETYPE_IMG
}
image_writer {
filename: "hplif.img"
type: IMG
type: IMAGETYPE_IMG
}
decoder {
@@ -55,10 +63,6 @@ option_group {
layoutdata {
sector_size: 256
physical {
sector: 0
sector: 4
sector: 8
sector: 12
sector: 1
sector: 5
sector: 9
@@ -71,6 +75,56 @@ option_group {
sector: 7
sector: 11
sector: 15
sector: 4
sector: 8
sector: 12
sector: 16
}
}
}
encoder {
ibm {
trackdata {
emit_iam: false
target_rotational_period_ms: 200
target_clock_period_us: 4
gap0: 80
gap2: 22
gap3: 44
}
}
}
}
}
option {
name: "608"
comment: '608kB 3.5" 76-track DSDD; HP9122 format'
config {
layout {
tracks: 76
sides: 2
layoutdata {
sector_size: 256
physical {
sector: 1
sector: 5
sector: 9
sector: 13
sector: 2
sector: 6
sector: 10
sector: 14
sector: 3
sector: 7
sector: 11
sector: 15
sector: 4
sector: 8
sector: 12
sector: 16
}
}
}

View File

@@ -84,12 +84,12 @@ versa, so it shouldn't matter.
image_reader {
filename: "ibm.img"
type: IMG
type: IMAGETYPE_IMG
}
image_writer {
filename: "ibm.img"
type: IMG
type: IMAGETYPE_IMG
}
decoder {

View File

@@ -11,7 +11,7 @@ track! Other than that it's another IBM scheme variation.
image_writer {
filename: "icl30.img"
type: IMG
type: IMAGETYPE_IMG
}
layout {

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