diff --git a/.github/workflows/ccpp.yml b/.github/workflows/ccpp.yml index 8a6b889d..b68038bd 100644 --- a/.github/workflows/ccpp.yml +++ b/.github/workflows/ccpp.yml @@ -62,7 +62,7 @@ jobs: - name: zip run: | - zip -9 fluxengine.zip fluxengine.exe fluxengine-gui.exe upgrade-flux-file.exe brother120tool.exe brother240tool.exe FluxEngine.cydsn/CortexM3/ARM_GCC_541/Release/FluxEngine.hex fluxengine-installer.exe + zip -9 fluxengine-windows.zip fluxengine.exe fluxengine-gui.exe upgrade-flux-file.exe brother120tool.exe brother240tool.exe FluxEngine.cydsn/CortexM3/ARM_GCC_541/Release/FluxEngine.hex fluxengine-installer.exe - name: Upload build artifacts uses: actions/upload-artifact@v2 diff --git a/README.md b/README.md index f98b6966..137a5fef 100644 --- a/README.md +++ b/README.md @@ -115,7 +115,8 @@ people who've had it work). | [Elektronika BK](doc/disk-bd.md) | 🦄 | 🦄 | Soviet Union PDP-11 clone | | [Macintosh 400kB/800kB](doc/disk-macintosh.md) | 🦄 | 🦄 | | | [NEC PC-98](doc/disk-ibm.md) | 🦄 | 🦄 | trimode drive not required | -| [Sharp X68000](doc/disk-ibm.md) | 🦄 | 🦄 | | +| [pSOS](doc/disk-ibm.md) | 🦄 | 🦖* | yet another IBM scheme | +| [Sharp X68000](doc/disk-ibm.md) | 🦄 | 🦄 | pSOS PHILE file system | | [Smaky 6](doc/disk-smaky6.md) | 🦖 | | 5.25" hard sectored | | [TRS-80](doc/disk-trs80.md) | 🦖 | 🦖* | a minor variation of the IBM scheme | {: .datatable } diff --git a/arch/ibm/decoder.cc b/arch/ibm/decoder.cc index 4fbaa2d5..cd6709f6 100644 --- a/arch/ibm/decoder.cc +++ b/arch/ibm/decoder.cc @@ -147,6 +147,7 @@ public: _sector->logicalSide = br.read_8(); _sector->logicalSector = br.read_8(); _currentSectorSize = 1 << (br.read_8() + 7); + uint16_t gotCrc = crc16(CCITT_POLY, bytes.slice(0, br.pos)); uint16_t wantCrc = br.read_be16(); if (wantCrc == gotCrc) @@ -206,6 +207,18 @@ public: uint16_t wantCrc = br.read_be16(); _sector->status = (wantCrc == gotCrc) ? Sector::OK : Sector::BAD_CHECKSUM; + + auto layout = Layout::getLayoutOfTrack( + _sector->logicalTrack, _sector->logicalSide); + if (_currentSectorSize != layout->sectorSize) + std::cerr << fmt::format( + "Warning: configured sector size for t{}.h{}.s{} is {} bytes " + "but that seen on disk is {} bytes\n", + _sector->logicalTrack, + _sector->logicalSide, + _sector->logicalSector, + layout->sectorSize, + _currentSectorSize); } private: diff --git a/doc/using.md b/doc/using.md index 7ea81f55..a18ef5a9 100644 --- a/doc/using.md +++ b/doc/using.md @@ -155,6 +155,14 @@ more common tools. encoding. You can specify a profile if you want to write a subset of the disk. + - `fluxengine merge -s -s -d -c -h -B` Reads flux (possibly from a disk) and does various analyses of it to try diff --git a/lib/build.mk b/lib/build.mk index 43e73e22..e14e6b7d 100644 --- a/lib/build.mk +++ b/lib/build.mk @@ -9,6 +9,7 @@ LIBFLUXENGINE_SRCS = \ lib/decoders/fmmfm.cc \ lib/encoders/encoders.cc \ lib/environment.cc \ + lib/fl2.cc \ lib/flags.cc \ lib/fluxmap.cc \ lib/fluxsink/a2rfluxsink.cc \ @@ -78,6 +79,7 @@ LIBFLUXENGINE_SRCS = \ 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 \ diff --git a/lib/drive.proto b/lib/drive.proto index 509959fe..e60a04b6 100644 --- a/lib/drive.proto +++ b/lib/drive.proto @@ -35,6 +35,15 @@ message DriveProto optional int32 tpi = 11 [ default = 96, (help) = "TPI of drive" ]; optional double rotational_period_ms = 12 [ default = 0, (help) = "Rotational period of the drive in milliseconds (0 to autodetect)"]; + + enum ErrorBehaviour { + NOTHING = 0; + JIGGLE = 1; + RECALIBRATE = 2; + } + + optional ErrorBehaviour error_behaviour = 15 + [ default = JIGGLE, (help) = "what to do when an error occurs during reads" ]; } // vim: ts=4 sw=4 et diff --git a/lib/fl2.cc b/lib/fl2.cc new file mode 100644 index 00000000..bf50430c --- /dev/null +++ b/lib/fl2.cc @@ -0,0 +1,69 @@ +#include "globals.h" +#include "proto.h" +#include "fluxmap.h" +#include "fmt/format.h" +#include "lib/fl2.pb.h" +#include + +static void upgradeFluxFile(FluxFileProto& proto) +{ + if (proto.version() == FluxFileVersion::VERSION_1) + { + /* Change a flux datastream with multiple segments separated by F_DESYNC + * into multiple flux segments. */ + + for (auto& track : *proto.mutable_track()) + { + if (track.flux_size() != 0) + { + Fluxmap oldFlux(track.flux(0)); + + track.clear_flux(); + for (const auto& flux : oldFlux.split()) + track.add_flux(flux->rawBytes()); + } + } + + proto.set_version(FluxFileVersion::VERSION_2); + } + if (proto.version() > FluxFileVersion::VERSION_2) + Error() << fmt::format( + "this is a version {} flux file, but this build of the client can " + "only handle up to version {} --- please upgrade", + proto.version(), + FluxFileVersion::VERSION_2); +} + +FluxFileProto loadFl2File(const std::string filename) +{ + std::ifstream ifs(filename, std::ios::in | std::ios::binary); + if (!ifs.is_open()) + Error() << fmt::format( + "cannot open input file '{}': {}", filename, strerror(errno)); + + char buffer[16]; + ifs.read(buffer, sizeof(buffer)); + if (strncmp(buffer, "SQLite format 3", 16) == 0) + Error() << "this flux file is too old; please use the " + "upgrade-flux-file tool to upgrade it"; + + FluxFileProto proto; + ifs.seekg(0); + if (!proto.ParseFromIstream(&ifs)) + Error() << fmt::format("unable to read input file '{}'", filename); + upgradeFluxFile(proto); + return proto; +} + +void saveFl2File(const std::string filename, FluxFileProto& proto) +{ + proto.set_magic(FluxMagic::MAGIC); + proto.set_version(FluxFileVersion::VERSION_2); + + std::ofstream of(filename, std::ios::out | std::ios::binary); + if (!proto.SerializeToOstream(&of)) + Error() << fmt::format("unable to write output file '{}'", filename); + of.close(); + if (of.fail()) + Error() << "FL2 write I/O error: " << strerror(errno); +} diff --git a/lib/fl2.h b/lib/fl2.h new file mode 100644 index 00000000..0ea4ecfd --- /dev/null +++ b/lib/fl2.h @@ -0,0 +1,10 @@ +#ifndef FL2_H +#define FL2_H + +class FluxFileProto; + +extern FluxFileProto loadFl2File(const std::string filename); +extern void saveFl2File(const std::string filename, FluxFileProto& proto); + +#endif + diff --git a/lib/fluxsink/fl2fluxsink.cc b/lib/fluxsink/fl2fluxsink.cc index 2cb270a3..d6dbbee0 100644 --- a/lib/fluxsink/fl2fluxsink.cc +++ b/lib/fluxsink/fl2fluxsink.cc @@ -9,73 +9,69 @@ #include "proto.h" #include "fmt/format.h" #include "lib/fl2.pb.h" +#include "fl2.h" #include #include #include +#include class Fl2FluxSink : public FluxSink { public: - Fl2FluxSink(const Fl2FluxSinkProto& lconfig): - Fl2FluxSink(lconfig.filename()) - { - } + Fl2FluxSink(const Fl2FluxSinkProto& lconfig): + Fl2FluxSink(lconfig.filename()) + { + } - Fl2FluxSink(const std::string& filename): - _filename(filename), - _of(_filename, std::ios::out | std::ios::binary) - { - if (!_of.is_open()) - Error() << "cannot open output file"; - } + Fl2FluxSink(const std::string& filename): _filename(filename) + { + std::ofstream of(filename); + if (!of.is_open()) + Error() << "cannot open output file"; + of.close(); + std::filesystem::remove(filename); + } - ~Fl2FluxSink() - { - FluxFileProto proto; - proto.set_magic(FluxMagic::MAGIC); - proto.set_version(FluxFileVersion::VERSION_2); - for (const auto& e : _data) - { - auto track = proto.add_track(); - track->set_track(e.first.first); - track->set_head(e.first.second); - for (const auto& fluxBytes : e.second) - track->add_flux(fluxBytes); - } + ~Fl2FluxSink() + { + FluxFileProto proto; + for (const auto& e : _data) + { + auto track = proto.add_track(); + track->set_track(e.first.first); + track->set_head(e.first.second); + for (const auto& fluxBytes : e.second) + track->add_flux(fluxBytes); + } - if (!proto.SerializeToOstream(&_of)) - Error() << "unable to write output file"; - _of.close(); - if (_of.fail()) - Error() << "FL2 write I/O error: " << strerror(errno); - } + saveFl2File(_filename, proto); + } public: - void writeFlux(int track, int head, const Fluxmap& fluxmap) override - { - auto& vector = _data[std::make_pair(track, head)]; - vector.push_back(fluxmap.rawBytes()); - } + void writeFlux(int track, int head, const Fluxmap& fluxmap) override + { + auto& vector = _data[std::make_pair(track, head)]; + vector.push_back(fluxmap.rawBytes()); + } - operator std::string () const override - { - return fmt::format("fl2({})", _filename); - } + operator std::string() const override + { + return fmt::format("fl2({})", _filename); + } private: - std::string _filename; - std::ofstream _of; - std::map, std::vector> _data; + std::string _filename; + std::map, std::vector> _data; }; -std::unique_ptr FluxSink::createFl2FluxSink(const Fl2FluxSinkProto& config) +std::unique_ptr FluxSink::createFl2FluxSink( + const Fl2FluxSinkProto& config) { return std::unique_ptr(new Fl2FluxSink(config)); } -std::unique_ptr FluxSink::createFl2FluxSink(const std::string& filename) +std::unique_ptr FluxSink::createFl2FluxSink( + const std::string& filename) { return std::unique_ptr(new Fl2FluxSink(filename)); } - - diff --git a/lib/fluxsource/fl2fluxsource.cc b/lib/fluxsource/fl2fluxsource.cc index 0785c345..0f21bfeb 100644 --- a/lib/fluxsource/fl2fluxsource.cc +++ b/lib/fluxsource/fl2fluxsource.cc @@ -4,6 +4,7 @@ #include "lib/fl2.pb.h" #include "fluxsource/fluxsource.h" #include "proto.h" +#include "fl2.h" #include "fmt/format.h" #include "fluxmap.h" #include @@ -11,38 +12,36 @@ class Fl2FluxSourceIterator : public FluxSourceIterator { public: - Fl2FluxSourceIterator(const TrackFluxProto& proto): - _proto(proto) - {} + Fl2FluxSourceIterator(const TrackFluxProto& proto): _proto(proto) {} - bool hasNext() const override - { - return _count < _proto.flux_size(); - } + bool hasNext() const override + { + return _count < _proto.flux_size(); + } - std::unique_ptr next() override - { - auto bytes = _proto.flux(_count); - _count++; - return std::make_unique(bytes); - } + std::unique_ptr next() override + { + auto bytes = _proto.flux(_count); + _count++; + return std::make_unique(bytes); + } private: - const TrackFluxProto& _proto; - int _count = 0; + const TrackFluxProto& _proto; + int _count = 0; }; class EmptyFluxSourceIterator : public FluxSourceIterator { - bool hasNext() const override - { - return false; - } + bool hasNext() const override + { + return false; + } - std::unique_ptr next() override - { - Error() << "no flux to read"; - } + std::unique_ptr next() override + { + Error() << "no flux to read"; + } }; class Fl2FluxSource : public FluxSource @@ -50,15 +49,7 @@ class Fl2FluxSource : public FluxSource public: Fl2FluxSource(const Fl2FluxSourceProto& config): _config(config) { - std::ifstream ifs(_config.filename(), std::ios::in | std::ios::binary); - if (!ifs.is_open()) - Error() << fmt::format("cannot open input file '{}': {}", - _config.filename(), - strerror(errno)); - - if (!_proto.ParseFromIstream(&ifs)) - Error() << "unable to read input file"; - upgradeFluxFile(); + _proto = loadFl2File(_config.filename()); } public: @@ -67,7 +58,7 @@ public: for (const auto& trackFlux : _proto.track()) { if ((trackFlux.track() == track) && (trackFlux.head() == head)) - return std::make_unique(trackFlux); + return std::make_unique(trackFlux); } return std::make_unique(); @@ -82,31 +73,6 @@ private: Error() << fmt::format("FL2 read I/O error: {}", strerror(errno)); } - void upgradeFluxFile() - { - if (_proto.version() == FluxFileVersion::VERSION_1) - { - /* Change a flux datastream with multiple segments separated by F_DESYNC into multiple - * flux segments. */ - - for (auto& track : *_proto.mutable_track()) - { - if (track.flux_size() != 0) - { - Fluxmap oldFlux(track.flux(0)); - - track.clear_flux(); - for (const auto& flux : oldFlux.split()) - track.add_flux(flux->rawBytes()); - } - } - - _proto.set_version(FluxFileVersion::VERSION_2); - } - if (_proto.version() > FluxFileVersion::VERSION_2) - Error() << fmt::format("this is a version {} flux file, but this build of the client can only handle up to version {} --- please upgrade", _proto.version(), FluxFileVersion::VERSION_2); - } - private: const Fl2FluxSourceProto& _config; FluxFileProto _proto; @@ -115,12 +81,5 @@ private: std::unique_ptr FluxSource::createFl2FluxSource( const Fl2FluxSourceProto& config) { - char buffer[16]; - std::ifstream(config.filename(), std::ios::in | std::ios::binary) - .read(buffer, 16); - if (strncmp(buffer, "SQLite format 3", 16) == 0) - Error() << "this flux file is too old; please use the " - "upgrade-flux-file tool to upgrade it"; - return std::unique_ptr(new Fl2FluxSource(config)); } diff --git a/lib/fluxsource/fluxsource.h b/lib/fluxsource/fluxsource.h index 811952b3..a7dc04ad 100644 --- a/lib/fluxsource/fluxsource.h +++ b/lib/fluxsource/fluxsource.h @@ -19,10 +19,10 @@ class FlxFluxSourceProto; class FluxSourceIterator { public: - virtual ~FluxSourceIterator() {} + virtual ~FluxSourceIterator() {} - virtual bool hasNext() const = 0; - virtual std::unique_ptr next() = 0; + virtual bool hasNext() const = 0; + virtual std::unique_ptr next() = 0; }; class FluxSource @@ -31,33 +31,48 @@ public: virtual ~FluxSource() {} private: - static std::unique_ptr createCwfFluxSource(const CwfFluxSourceProto& config); - static std::unique_ptr createEraseFluxSource(const EraseFluxSourceProto& config); - static std::unique_ptr createFl2FluxSource(const Fl2FluxSourceProto& config); - static std::unique_ptr createFlxFluxSource(const FlxFluxSourceProto& config); - static std::unique_ptr createHardwareFluxSource(const HardwareFluxSourceProto& config); - static std::unique_ptr createKryofluxFluxSource(const KryofluxFluxSourceProto& config); - static std::unique_ptr createScpFluxSource(const ScpFluxSourceProto& config); - static std::unique_ptr createTestPatternFluxSource(const TestPatternFluxSourceProto& config); + static std::unique_ptr createCwfFluxSource( + const CwfFluxSourceProto& config); + static std::unique_ptr createEraseFluxSource( + const EraseFluxSourceProto& config); + static std::unique_ptr createFl2FluxSource( + const Fl2FluxSourceProto& config); + static std::unique_ptr createFlxFluxSource( + const FlxFluxSourceProto& config); + static std::unique_ptr createHardwareFluxSource( + const HardwareFluxSourceProto& config); + static std::unique_ptr createKryofluxFluxSource( + const KryofluxFluxSourceProto& config); + static std::unique_ptr createScpFluxSource( + const ScpFluxSourceProto& config); + static std::unique_ptr createTestPatternFluxSource( + const TestPatternFluxSourceProto& config); public: - static std::unique_ptr createMemoryFluxSource(const DiskFlux& flux); + static std::unique_ptr createMemoryFluxSource( + const DiskFlux& flux); static std::unique_ptr create(const FluxSourceProto& spec); - static void updateConfigForFilename(FluxSourceProto* proto, const std::string& filename); + static void updateConfigForFilename( + FluxSourceProto* proto, const std::string& filename); public: - virtual std::unique_ptr readFlux(int track, int side) = 0; + virtual std::unique_ptr readFlux( + int track, int side) = 0; virtual void recalibrate() {} - virtual bool isHardware() { return false; } + virtual void seek(int track) {} + virtual bool isHardware() + { + return false; + } }; class TrivialFluxSource : public FluxSource { public: std::unique_ptr readFlux(int track, int side); - virtual std::unique_ptr readSingleFlux(int track, int side) = 0; + virtual std::unique_ptr readSingleFlux( + int track, int side) = 0; }; #endif - diff --git a/lib/fluxsource/hardwarefluxsource.cc b/lib/fluxsource/hardwarefluxsource.cc index 165f7ed7..02f268f5 100644 --- a/lib/fluxsource/hardwarefluxsource.cc +++ b/lib/fluxsource/hardwarefluxsource.cc @@ -30,7 +30,9 @@ private: std::unique_ptr next() { - usbSetDrive(config.drive().drive(), config.drive().high_density(), config.drive().index_mode()); + usbSetDrive(config.drive().drive(), + config.drive().high_density(), + config.drive().index_mode()); usbSeek(_track); Bytes data = usbRead(_head, @@ -51,7 +53,7 @@ private: public: HardwareFluxSource(const HardwareFluxSourceProto& conf): _config(conf) { - measureDiskRotation(_oneRevolution, _hardSectorThreshold); + measureDiskRotation(_oneRevolution, _hardSectorThreshold); } ~HardwareFluxSource() {} @@ -59,8 +61,7 @@ public: public: std::unique_ptr readFlux(int track, int head) override { - return std::make_unique( - *this, track, head); + return std::make_unique(*this, track, head); } void recalibrate() override @@ -68,6 +69,11 @@ public: usbRecalibrate(); } + void seek(int track) override + { + usbSeek(track); + } + bool isHardware() override { return true; diff --git a/lib/readerwriter.cc b/lib/readerwriter.cc index 80b041d0..20e6966d 100644 --- a/lib/readerwriter.cc +++ b/lib/readerwriter.cc @@ -189,6 +189,26 @@ BadSectorsState combineRecordAndSectors(TrackFlux& trackFlux, return HAS_NO_BAD_SECTORS; } +static void adjustTrackOnError(FluxSource& fluxSource, int baseTrack) +{ + switch (config.drive().error_behaviour()) + { + case DriveProto::NOTHING: + break; + + case DriveProto::RECALIBRATE: + fluxSource.recalibrate(); + break; + + case DriveProto::JIGGLE: + if (baseTrack > 0) + fluxSource.seek(baseTrack - 1); + else + fluxSource.seek(baseTrack + 1); + break; + } +} + ReadResult readGroup(FluxSourceIteratorHolder& fluxSourceIteratorHolder, std::shared_ptr& trackInfo, TrackFlux& trackFlux, @@ -343,6 +363,7 @@ void writeTracksAndVerify(FluxSink& fluxSink, if (result != GOOD_READ) { + adjustTrackOnError(fluxSource, trackInfo->physicalTrack); Logger() << "bad read"; return false; } @@ -453,9 +474,13 @@ std::shared_ptr readAndDecodeTrack(FluxSource& fluxSource, break; } - Logger() << fmt::format( - "retrying; {} retries remaining", retriesRemaining); - retriesRemaining--; + if (fluxSource.isHardware()) + { + adjustTrackOnError(fluxSource, trackInfo->physicalTrack); + Logger() << fmt::format( + "retrying; {} retries remaining", retriesRemaining); + retriesRemaining--; + } } return trackFlux; @@ -584,7 +609,8 @@ void rawReadDiskCommand(FluxSource& fluxsource, FluxSink& fluxsink) unsigned index = 0; for (auto& trackInfo : locations) { - Logger() << OperationProgressLogMessage{index * 100 / (int)locations.size()}; + Logger() << OperationProgressLogMessage{ + index * 100 / (int)locations.size()}; index++; testForEmergencyStop(); diff --git a/lib/vfs/philefs.cc b/lib/vfs/philefs.cc new file mode 100644 index 00000000..6038f5a4 --- /dev/null +++ b/lib/vfs/philefs.cc @@ -0,0 +1,296 @@ +#include "lib/globals.h" +#include "lib/vfs/vfs.h" +#include "lib/config.pb.h" +#include "lib/utils.h" +#include + +/* Root block: + * + * 00-0b volume name + * 0c 01 + * 0d 2a + * 0e 79 + * 0f 6d + * 10 07 0x07c10c19, creation timestamp + * 11 c1 ^ + * 12 0c ^ + * 13 19 ^ + * 14 2f + * 15 00 + * 16 00 + * 17 18 + * 18 03 0x320, number of blocks on the disk + * 19 20 ^ + * 1a 00 0x0010, first data block? + * 1b 10 ^ + * 1c 00 0x0010, address of bitmap in HCS + * 1d 10 ^ + * 1e 00 0x0011, address of FLIST in HCS + * 1f 11 ^ + * 20 00 0x0017, address of last block of FLIST? + * 21 17 ^ + * 22 00 + * 23 6b + * 24 00 + * 25 20 + * + * 14 files + * file id 3 is not used + * directory at 0xc00 + * 0x4000, block 0x10, volume bitmap + * 0x4400, block 0x11, flist, 7 blocks long? + * file descriptors seem to be 64 bytes + * + * File descriptor, 64 bytes: + * 00 file type + * 0e+04 length in bytes + * 14... spans + * word: start block + * word: number of blocks + * +00000C00 00 01 42 49 54 4D 41 50 2E 53 59 53 00 00 00 00 ..BITMAP.SYS.... + +00008040 41 00 00 00 07 C1 0C 19 1E 00 00 18 00 02 00 00 A............... +00008050 04 00 00 01 00 10 00 01 00 00 00 00 00 00 00 00 ................ +00008060 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ +00008070 00 00 00 00 00 00 00 00 00 00 00 01 00 01 01 00 ................ + +00000C10 00 02 46 4C 49 53 54 2E 53 59 53 00 00 00 00 00 ..FLIST.SYS..... + +00008080 41 00 00 00 07 C1 0C 19 1E 00 00 18 00 02 00 00 A............... +00008090 1C 00 00 01 00 11 00 07 00 00 00 00 00 00 00 00 ................ +000080A0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ +000080B0 00 00 00 00 00 00 00 00 00 00 00 07 00 07 01 00 ................ + +00000C20 00 04 53 4B 45 4C 00 00 00 00 00 00 00 00 00 00 ..SKEL.......... + +00008100 01 00 00 03 07 C1 0C 19 19 00 00 19 00 02 00 00 ................ +00008110 55 00 00 01 00 20 00 16 00 00 00 00 00 00 00 00 U.... .......... +00008120 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ +00008130 00 00 00 00 00 00 00 00 00 00 00 16 00 16 01 00 ................ + + +00000C30 00 05 43 4F 44 45 00 00 00 00 00 00 00 00 00 00 ..CODE.......... + +00004540 01 00 00 03 07 C1 0C 19 26 00 00 1F 00 02 00 08 ........&....... +00004550 10 00 00 01 00 36 02 04 00 00 00 00 00 00 00 00 .....6.......... +00004560 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ +00004570 00 00 00 00 00 00 00 00 00 00 02 04 02 04 01 00 ................ + +00000C40 00 06 53 43 53 49 00 00 00 00 00 00 00 00 00 00 ..SCSI.......... +00000C50 00 07 41 4F 46 00 00 00 00 00 00 00 00 00 00 00 ..AOF........... +00000C60 00 08 4D 43 46 00 00 00 00 00 00 00 00 00 00 00 ..MCF........... +00000C70 00 09 53 59 53 54 45 4D 2E 53 43 46 00 00 00 00 ..SYSTEM.SCF.... +00000C80 00 0A 53 59 53 54 45 4D 2E 50 44 46 00 00 00 00 ..SYSTEM.PDF.... +00000C90 00 0B 43 4C 54 31 2E 43 4C 54 00 00 00 00 00 00 ..CLT1.CLT...... +00000CA0 00 0C 43 4C 54 32 2E 43 4C 54 00 00 00 00 00 00 ..CLT2.CLT...... +00000CB0 00 0D 43 4C 54 33 2E 43 4C 54 00 00 00 00 00 00 ..CLT3.CLT...... +00000CC0 00 0E 43 4C 54 34 2E 43 4C 54 00 00 00 00 00 00 ..CLT4.CLT...... +00000CD0 00 0F 47 52 45 59 2E 43 4C 54 00 00 00 00 00 00 ..GREY.CLT...... + */ + +static void trimZeros(std::string s) +{ + s.erase(std::remove(s.begin(), s.end(), 0), s.end()); +} + +class PhileFilesystem : public Filesystem +{ + struct Span + { + uint16_t startBlock; + uint16_t blockCount; + }; + + class PhileDirent : public Dirent + { + public: + PhileDirent( + int fileno, const std::string& filename, const Bytes& filedes): + _fileno(fileno) + { + file_type = TYPE_FILE; + + ByteReader br(filedes); + br.seek(0x0e); + length = br.read_be32(); + + { + std::stringstream ss; + ss << 'R'; + if (filedes[0] & 0x40) + ss << 'S'; + mode = ss.str(); + } + + this->filename = filename; + path = {filename}; + + attributes[Filesystem::FILENAME] = filename; + attributes[Filesystem::LENGTH] = std::to_string(length); + attributes[Filesystem::FILE_TYPE] = "file"; + attributes[Filesystem::MODE] = mode; + + int spans = br.read_be16(); + for (int i = 0; i < spans; i++) + { + Span span; + span.startBlock = br.read_be16(); + span.blockCount = br.read_be16(); + _spans.push_back(span); + } + + attributes["phile.spans"] = std::to_string(spans); + } + + const std::vector& spans() const + { + return _spans; + } + + private: + int _fileno; + std::vector _spans; + }; + +public: + PhileFilesystem( + const PhileProto& config, std::shared_ptr 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 getMetadata() override + { + mount(); + + std::string volumename = _rootBlock.reader().read(0x0c); + trimZeros(volumename); + + std::map attributes; + attributes[VOLUME_NAME] = volumename; + attributes[TOTAL_BLOCKS] = std::to_string(_totalBlocks); + attributes[USED_BLOCKS] = attributes[TOTAL_BLOCKS]; + attributes[BLOCK_SIZE] = std::to_string(_config.block_size()); + return attributes; + } + + std::shared_ptr getDirent(const Path& path) override + { + mount(); + if (path.size() != 1) + throw BadPathException(); + + return findFile(path.front()); + } + + std::vector> list(const Path& path) override + { + mount(); + if (!path.empty()) + throw FileNotFoundException(); + + std::vector> result; + for (auto& de : _dirents) + result.push_back(de.second); + return result; + } + + Bytes getFile(const Path& path) override + { + mount(); + if (path.size() != 1) + throw BadPathException(); + + auto dirent = findFile(path.front()); + + Bytes data; + ByteWriter bw(data); + for (const auto& span : dirent->spans()) + bw += getPsosBlock(span.startBlock, span.blockCount); + + data.resize(dirent->length); + return data; + } + +private: + void mount() + { + _sectorSize = getLogicalSectorSize(); + _blockSectors = _config.block_size() / _sectorSize; + + _rootBlock = getPsosBlock(2, 1); + _bitmapBlockNumber = _rootBlock.reader().seek(0x1c).read_be16(); + _filedesBlockNumber = _rootBlock.reader().seek(0x1e).read_be16(); + _filedesLength = + _rootBlock.reader().seek(0x20).read_be16() - _filedesBlockNumber + 1; + _totalBlocks = _rootBlock.reader().seek(0x18).read_be16(); + + Bytes directoryBlock = getPsosBlock(3, 1); + Bytes filedesBlock = getPsosBlock(_filedesBlockNumber, _filedesLength); + + _dirents.clear(); + ByteReader br(directoryBlock); + ByteReader fr(filedesBlock); + while (!br.eof()) + { + uint16_t fileno = br.read_be16(); + std::string filename = br.read(14); + trimZeros(filename); + + if (fileno) + { + fr.seek(fileno * 64); + Bytes filedes = fr.read(64); + auto dirent = + std::make_unique(fileno, filename, filedes); + _dirents[fileno] = std::move(dirent); + } + } + } + + std::shared_ptr findFile(const std::string filename) + { + for (const auto& dirent : _dirents) + { + if (dirent.second->filename == filename) + return dirent.second; + } + + throw FileNotFoundException(); + } + + Bytes getPsosBlock(uint32_t number, uint32_t count = 1) + { + unsigned sector = number * _blockSectors; + return getLogicalSector(sector, _blockSectors * count); + } + +private: + const PhileProto& _config; + int _sectorSize; + int _blockSectors; + int _totalBlocks; + int _bitmapBlockNumber; + int _filedesBlockNumber; + int _filedesLength; + Bytes _rootBlock; + std::map> _dirents; +}; + +std::unique_ptr Filesystem::createPhileFilesystem( + const FilesystemProto& config, std::shared_ptr sectors) +{ + return std::make_unique(config.phile(), sectors); +} diff --git a/lib/vfs/vfs.cc b/lib/vfs/vfs.cc index c1337a5c..72128a73 100644 --- a/lib/vfs/vfs.cc +++ b/lib/vfs/vfs.cc @@ -209,6 +209,9 @@ std::unique_ptr Filesystem::createFilesystem( case FilesystemProto::SMAKY6: return Filesystem::createSmaky6Filesystem(config, image); + case FilesystemProto::PHILE: + return Filesystem::createPhileFilesystem(config, image); + default: Error() << "no filesystem configured"; return std::unique_ptr(); diff --git a/lib/vfs/vfs.h b/lib/vfs/vfs.h index 2024ab49..857aa7bf 100644 --- a/lib/vfs/vfs.h +++ b/lib/vfs/vfs.h @@ -252,6 +252,8 @@ public: const FilesystemProto& config, std::shared_ptr image); static std::unique_ptr createSmaky6Filesystem( const FilesystemProto& config, std::shared_ptr image); + static std::unique_ptr createPhileFilesystem( + const FilesystemProto& config, std::shared_ptr image); static std::unique_ptr createFilesystem( const FilesystemProto& config, std::shared_ptr image); diff --git a/lib/vfs/vfs.proto b/lib/vfs/vfs.proto index aa45e814..22bf997f 100644 --- a/lib/vfs/vfs.proto +++ b/lib/vfs/vfs.proto @@ -68,7 +68,14 @@ message AppledosProto { message Smaky6FsProto {} -// NEXT_TAG: 13 +message PhileProto { + optional uint32 block_size = 1 [ + default = 1024, + (help) = "Phile filesystem block size" + ]; +} + +// NEXT_TAG: 14 message FilesystemProto { enum FilesystemType { @@ -81,8 +88,9 @@ message FilesystemProto MACHFS = 6; CBMFS = 7; PRODOS = 8; - SMAKY6 = 9; - APPLEDOS = 10; + SMAKY6 = 9; + APPLEDOS = 10; + PHILE = 11; } optional FilesystemType type = 10 [default = NOT_SET, (help) = "filesystem type"]; @@ -95,8 +103,9 @@ message FilesystemProto optional MacHfsProto machfs = 6; optional CbmfsProto cbmfs = 7; optional ProdosProto prodos = 8; - optional AppledosProto appledos = 12; - optional Smaky6FsProto smaky6 = 11; + optional AppledosProto appledos = 12; + optional Smaky6FsProto smaky6 = 11; + optional PhileProto phile = 13; optional SectorListProto sector_order = 9 [(help) = "specify the filesystem order of sectors"]; } diff --git a/src/build.mk b/src/build.mk index 7cc2c7ba..0d3dadd0 100644 --- a/src/build.mk +++ b/src/build.mk @@ -9,6 +9,7 @@ FLUXENGINE_SRCS = \ src/fe-getfileinfo.cc \ src/fe-inspect.cc \ src/fe-ls.cc \ + src/fe-merge.cc \ src/fe-mkdir.cc \ src/fe-mv.cc \ src/fe-rm.cc \ diff --git a/src/fe-merge.cc b/src/fe-merge.cc new file mode 100644 index 00000000..470c10ab --- /dev/null +++ b/src/fe-merge.cc @@ -0,0 +1,65 @@ +#include "globals.h" +#include "flags.h" +#include "fluxmap.h" +#include "sector.h" +#include "proto.h" +#include "flux.h" +#include "fl2.h" +#include "fl2.pb.h" +#include "fmt/format.h" +#include "fluxengine.h" +#include + +static FlagGroup flags; + +static std::vector inputFluxFiles; + +static StringFlag sourceFlux({"-s", "--source"}, + "flux file to read from (repeatable)", + "", + [](const auto& value) + { + inputFluxFiles.push_back(value); + }); + +static StringFlag destFlux( + {"-d", "--dest"}, "destination flux file to write to", ""); + +int mainMerge(int argc, const char* argv[]) +{ + flags.parseFlags(argc, argv); + + if (inputFluxFiles.empty()) + Error() << "you must specify at least one input flux file (with -s)"; + if (destFlux.get() == "") + Error() << "you must specify an output flux file (with -d)"; + + std::map, TrackFluxProto> data; + for (const auto& s : inputFluxFiles) + { + fmt::print("Reading {}...\n", s); + FluxFileProto f = loadFl2File(s); + + for (auto& trackflux : f.track()) + { + auto key = std::make_pair(trackflux.track(), trackflux.head()); + auto i = data.find(key); + if (i == data.end()) + data[key] = trackflux; + else + { + for (auto flux : trackflux.flux()) + i->second.add_flux(flux); + } + } + } + + FluxFileProto proto; + for (auto& i : data) + *proto.add_track() = i.second; + + fmt::print("Writing {}...\n", destFlux.get()); + saveFl2File(destFlux.get(), proto); + + return 0; +} diff --git a/src/fluxengine.cc b/src/fluxengine.cc index cedbdb6a..94166988 100644 --- a/src/fluxengine.cc +++ b/src/fluxengine.cc @@ -12,6 +12,7 @@ extern command_cb mainGetFile; extern command_cb mainGetFileInfo; extern command_cb mainInspect; extern command_cb mainLs; +extern command_cb mainMerge; extern command_cb mainMkDir; extern command_cb mainMv; extern command_cb mainPutFile; @@ -44,6 +45,7 @@ static std::vector commands = { "format", mainFormat, "Format a disk and make a file system on it.", }, { "rawread", mainRawRead, "Reads raw flux from a disk. Warning: you can't use this to copy disks.", }, { "rawwrite", mainRawWrite, "Writes a flux file to a disk. Warning: you can't use this to copy disks.", }, + { "merge", mainMerge, "Merge together multiple flux files.", }, { "getdiskinfo", mainGetDiskInfo, "Read volume metadata off a disk (or image).", }, { "ls", mainLs, "Show files on disk (or image).", }, { "mv", mainMv, "Rename a file on a disk (or image).", }, diff --git a/src/formats/build.mk b/src/formats/build.mk index c1418734..92a6be07 100644 --- a/src/formats/build.mk +++ b/src/formats/build.mk @@ -63,6 +63,7 @@ FORMATS = \ northstar175 \ northstar350 \ northstar87 \ + psos800 \ rx50 \ shugart_drive \ smaky6 \ diff --git a/src/formats/psos800.textpb b/src/formats/psos800.textpb new file mode 100644 index 00000000..d71670ea --- /dev/null +++ b/src/formats/psos800.textpb @@ -0,0 +1,58 @@ +comment: 'pSOS generic 800kB DSDD with PHILE' + +drive { + high_density: false + rotational_period_ms: 200 +} + +image_reader { + filename: "pme.img" + type: IMG +} + +image_writer { + filename: "pme.img" + type: IMG +} + +layout { + tracks: 80 + sides: 2 + order: HCS + swap_sides: true + layoutdata { + sector_size: 1024 + physical { + sector: 1 + sector: 2 + sector: 3 + sector: 4 + sector: 5 + } + } +} + +encoder { + ibm { + trackdata { + target_rotational_period_ms: 200 + target_clock_period_us: 4 + gap0: 80 + gap2: 22 + gap3: 80 + } + } +} + +decoder { + ibm { + trackdata { + ignore_side_byte: true + } + } +} + +filesystem { + type: PHILE +} + diff --git a/src/gui/main.cc b/src/gui/main.cc index 324fdecb..8073312e 100644 --- a/src/gui/main.cc +++ b/src/gui/main.cc @@ -93,7 +93,7 @@ void FluxEngineApp::RunOnWorkerThread(std::function callback) std::cerr << "Cannot start new worker task as one is already running\n"; _callback = callback; - if (GetThread()) + if (GetThread() && GetThread()->IsRunning()) GetThread()->Wait(); emergencyStop = false; @@ -122,9 +122,16 @@ bool FluxEngineApp::IsWorkerThreadRunning() const void FluxEngineApp::OnExec(const ExecEvent& event) { - event.RunCallback(); - if (event.IsSynchronous()) - execSemaphore.Post(); + try + { + event.RunCallback(); + if (event.IsSynchronous()) + execSemaphore.Post(); + } + catch (std::exception& e) + { + std::cerr << "Unhandled exception: " << e.what() << "\n"; + } } void runOnUiThread(std::function callback) diff --git a/src/gui/mainwindow.cc b/src/gui/mainwindow.cc index 543a51b4..1881a7f6 100644 --- a/src/gui/mainwindow.cc +++ b/src/gui/mainwindow.cc @@ -481,6 +481,9 @@ public: visualiser->Clear(); _filesystemModel->Clear(Path()); _currentDisk = nullptr; + _filesystemCapabilities = 0; + _filesystemIsReadOnly = true; + _filesystemNeedsFlushing = false; _state = STATE_BROWSING_WORKING; UpdateState();