mirror of
https://github.com/davidgiven/fluxengine.git
synced 2025-10-31 11:17:01 -07:00
Merge branch 'master' into basis
This commit is contained in:
2
.github/workflows/ccpp.yml
vendored
2
.github/workflows/ccpp.yml
vendored
@@ -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
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -155,6 +155,14 @@ more common tools.
|
||||
encoding. You can specify a profile if you want to write a subset of the
|
||||
disk.
|
||||
|
||||
- `fluxengine merge -s <fluxfile> -s <fluxfile...> -d <fluxfile`
|
||||
|
||||
Merges data from multiple flux files together. This is useful if you have
|
||||
several reads from an unreliable disk where each read has a different set
|
||||
of good sectors. By merging the flux files, you get to combine all the
|
||||
data. Don't use this on reads of different disks, for obvious results! Note
|
||||
that this works on flux files, not on flux sources.
|
||||
|
||||
- `fluxengine inspect -s <flux source> -c <cylinder> -h <head> -B`
|
||||
|
||||
Reads flux (possibly from a disk) and does various analyses of it to try
|
||||
|
||||
@@ -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 \
|
||||
|
||||
@@ -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
|
||||
|
||||
69
lib/fl2.cc
Normal file
69
lib/fl2.cc
Normal file
@@ -0,0 +1,69 @@
|
||||
#include "globals.h"
|
||||
#include "proto.h"
|
||||
#include "fluxmap.h"
|
||||
#include "fmt/format.h"
|
||||
#include "lib/fl2.pb.h"
|
||||
#include <fstream>
|
||||
|
||||
static void upgradeFluxFile(FluxFileProto& proto)
|
||||
{
|
||||
if (proto.version() == FluxFileVersion::VERSION_1)
|
||||
{
|
||||
/* Change a flux datastream with multiple segments separated by F_DESYNC
|
||||
* into multiple flux segments. */
|
||||
|
||||
for (auto& track : *proto.mutable_track())
|
||||
{
|
||||
if (track.flux_size() != 0)
|
||||
{
|
||||
Fluxmap oldFlux(track.flux(0));
|
||||
|
||||
track.clear_flux();
|
||||
for (const auto& flux : oldFlux.split())
|
||||
track.add_flux(flux->rawBytes());
|
||||
}
|
||||
}
|
||||
|
||||
proto.set_version(FluxFileVersion::VERSION_2);
|
||||
}
|
||||
if (proto.version() > FluxFileVersion::VERSION_2)
|
||||
Error() << fmt::format(
|
||||
"this is a version {} flux file, but this build of the client can "
|
||||
"only handle up to version {} --- please upgrade",
|
||||
proto.version(),
|
||||
FluxFileVersion::VERSION_2);
|
||||
}
|
||||
|
||||
FluxFileProto loadFl2File(const std::string filename)
|
||||
{
|
||||
std::ifstream ifs(filename, std::ios::in | std::ios::binary);
|
||||
if (!ifs.is_open())
|
||||
Error() << fmt::format(
|
||||
"cannot open input file '{}': {}", filename, strerror(errno));
|
||||
|
||||
char buffer[16];
|
||||
ifs.read(buffer, sizeof(buffer));
|
||||
if (strncmp(buffer, "SQLite format 3", 16) == 0)
|
||||
Error() << "this flux file is too old; please use the "
|
||||
"upgrade-flux-file tool to upgrade it";
|
||||
|
||||
FluxFileProto proto;
|
||||
ifs.seekg(0);
|
||||
if (!proto.ParseFromIstream(&ifs))
|
||||
Error() << fmt::format("unable to read input file '{}'", filename);
|
||||
upgradeFluxFile(proto);
|
||||
return proto;
|
||||
}
|
||||
|
||||
void saveFl2File(const std::string filename, FluxFileProto& proto)
|
||||
{
|
||||
proto.set_magic(FluxMagic::MAGIC);
|
||||
proto.set_version(FluxFileVersion::VERSION_2);
|
||||
|
||||
std::ofstream of(filename, std::ios::out | std::ios::binary);
|
||||
if (!proto.SerializeToOstream(&of))
|
||||
Error() << fmt::format("unable to write output file '{}'", filename);
|
||||
of.close();
|
||||
if (of.fail())
|
||||
Error() << "FL2 write I/O error: " << strerror(errno);
|
||||
}
|
||||
10
lib/fl2.h
Normal file
10
lib/fl2.h
Normal file
@@ -0,0 +1,10 @@
|
||||
#ifndef FL2_H
|
||||
#define FL2_H
|
||||
|
||||
class FluxFileProto;
|
||||
|
||||
extern FluxFileProto loadFl2File(const std::string filename);
|
||||
extern void saveFl2File(const std::string filename, FluxFileProto& proto);
|
||||
|
||||
#endif
|
||||
|
||||
@@ -9,9 +9,11 @@
|
||||
#include "proto.h"
|
||||
#include "fmt/format.h"
|
||||
#include "lib/fl2.pb.h"
|
||||
#include "fl2.h"
|
||||
#include <fstream>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <filesystem>
|
||||
|
||||
class Fl2FluxSink : public FluxSink
|
||||
{
|
||||
@@ -21,19 +23,18 @@ public:
|
||||
{
|
||||
}
|
||||
|
||||
Fl2FluxSink(const std::string& filename):
|
||||
_filename(filename),
|
||||
_of(_filename, std::ios::out | std::ios::binary)
|
||||
Fl2FluxSink(const std::string& filename): _filename(filename)
|
||||
{
|
||||
if (!_of.is_open())
|
||||
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();
|
||||
@@ -43,11 +44,7 @@ public:
|
||||
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:
|
||||
@@ -64,18 +61,17 @@ public:
|
||||
|
||||
private:
|
||||
std::string _filename;
|
||||
std::ofstream _of;
|
||||
std::map<std::pair<unsigned, unsigned>, std::vector<Bytes>> _data;
|
||||
};
|
||||
|
||||
std::unique_ptr<FluxSink> FluxSink::createFl2FluxSink(const Fl2FluxSinkProto& config)
|
||||
std::unique_ptr<FluxSink> FluxSink::createFl2FluxSink(
|
||||
const Fl2FluxSinkProto& config)
|
||||
{
|
||||
return std::unique_ptr<FluxSink>(new Fl2FluxSink(config));
|
||||
}
|
||||
|
||||
std::unique_ptr<FluxSink> FluxSink::createFl2FluxSink(const std::string& filename)
|
||||
std::unique_ptr<FluxSink> FluxSink::createFl2FluxSink(
|
||||
const std::string& filename)
|
||||
{
|
||||
return std::unique_ptr<FluxSink>(new Fl2FluxSink(filename));
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include "lib/fl2.pb.h"
|
||||
#include "fluxsource/fluxsource.h"
|
||||
#include "proto.h"
|
||||
#include "fl2.h"
|
||||
#include "fmt/format.h"
|
||||
#include "fluxmap.h"
|
||||
#include <fstream>
|
||||
@@ -11,9 +12,7 @@
|
||||
class Fl2FluxSourceIterator : public FluxSourceIterator
|
||||
{
|
||||
public:
|
||||
Fl2FluxSourceIterator(const TrackFluxProto& proto):
|
||||
_proto(proto)
|
||||
{}
|
||||
Fl2FluxSourceIterator(const TrackFluxProto& proto): _proto(proto) {}
|
||||
|
||||
bool hasNext() const override
|
||||
{
|
||||
@@ -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:
|
||||
@@ -82,31 +73,6 @@ private:
|
||||
Error() << fmt::format("FL2 read I/O error: {}", strerror(errno));
|
||||
}
|
||||
|
||||
void upgradeFluxFile()
|
||||
{
|
||||
if (_proto.version() == FluxFileVersion::VERSION_1)
|
||||
{
|
||||
/* Change a flux datastream with multiple segments separated by F_DESYNC into multiple
|
||||
* flux segments. */
|
||||
|
||||
for (auto& track : *_proto.mutable_track())
|
||||
{
|
||||
if (track.flux_size() != 0)
|
||||
{
|
||||
Fluxmap oldFlux(track.flux(0));
|
||||
|
||||
track.clear_flux();
|
||||
for (const auto& flux : oldFlux.split())
|
||||
track.add_flux(flux->rawBytes());
|
||||
}
|
||||
}
|
||||
|
||||
_proto.set_version(FluxFileVersion::VERSION_2);
|
||||
}
|
||||
if (_proto.version() > FluxFileVersion::VERSION_2)
|
||||
Error() << fmt::format("this is a version {} flux file, but this build of the client can only handle up to version {} --- please upgrade", _proto.version(), FluxFileVersion::VERSION_2);
|
||||
}
|
||||
|
||||
private:
|
||||
const Fl2FluxSourceProto& _config;
|
||||
FluxFileProto _proto;
|
||||
@@ -115,12 +81,5 @@ private:
|
||||
std::unique_ptr<FluxSource> FluxSource::createFl2FluxSource(
|
||||
const Fl2FluxSourceProto& config)
|
||||
{
|
||||
char buffer[16];
|
||||
std::ifstream(config.filename(), std::ios::in | std::ios::binary)
|
||||
.read(buffer, 16);
|
||||
if (strncmp(buffer, "SQLite format 3", 16) == 0)
|
||||
Error() << "this flux file is too old; please use the "
|
||||
"upgrade-flux-file tool to upgrade it";
|
||||
|
||||
return std::unique_ptr<FluxSource>(new Fl2FluxSource(config));
|
||||
}
|
||||
|
||||
@@ -31,33 +31,48 @@ public:
|
||||
virtual ~FluxSource() {}
|
||||
|
||||
private:
|
||||
static std::unique_ptr<FluxSource> createCwfFluxSource(const CwfFluxSourceProto& config);
|
||||
static std::unique_ptr<FluxSource> createEraseFluxSource(const EraseFluxSourceProto& config);
|
||||
static std::unique_ptr<FluxSource> createFl2FluxSource(const Fl2FluxSourceProto& config);
|
||||
static std::unique_ptr<FluxSource> createFlxFluxSource(const FlxFluxSourceProto& config);
|
||||
static std::unique_ptr<FluxSource> createHardwareFluxSource(const HardwareFluxSourceProto& config);
|
||||
static std::unique_ptr<FluxSource> createKryofluxFluxSource(const KryofluxFluxSourceProto& config);
|
||||
static std::unique_ptr<FluxSource> createScpFluxSource(const ScpFluxSourceProto& config);
|
||||
static std::unique_ptr<FluxSource> createTestPatternFluxSource(const TestPatternFluxSourceProto& config);
|
||||
static std::unique_ptr<FluxSource> createCwfFluxSource(
|
||||
const CwfFluxSourceProto& config);
|
||||
static std::unique_ptr<FluxSource> createEraseFluxSource(
|
||||
const EraseFluxSourceProto& config);
|
||||
static std::unique_ptr<FluxSource> createFl2FluxSource(
|
||||
const Fl2FluxSourceProto& config);
|
||||
static std::unique_ptr<FluxSource> createFlxFluxSource(
|
||||
const FlxFluxSourceProto& config);
|
||||
static std::unique_ptr<FluxSource> createHardwareFluxSource(
|
||||
const HardwareFluxSourceProto& config);
|
||||
static std::unique_ptr<FluxSource> createKryofluxFluxSource(
|
||||
const KryofluxFluxSourceProto& config);
|
||||
static std::unique_ptr<FluxSource> createScpFluxSource(
|
||||
const ScpFluxSourceProto& config);
|
||||
static std::unique_ptr<FluxSource> createTestPatternFluxSource(
|
||||
const TestPatternFluxSourceProto& config);
|
||||
|
||||
public:
|
||||
static std::unique_ptr<FluxSource> createMemoryFluxSource(const DiskFlux& flux);
|
||||
static std::unique_ptr<FluxSource> createMemoryFluxSource(
|
||||
const DiskFlux& flux);
|
||||
|
||||
static std::unique_ptr<FluxSource> create(const FluxSourceProto& spec);
|
||||
static void updateConfigForFilename(FluxSourceProto* proto, const std::string& filename);
|
||||
static void updateConfigForFilename(
|
||||
FluxSourceProto* proto, const std::string& filename);
|
||||
|
||||
public:
|
||||
virtual std::unique_ptr<FluxSourceIterator> readFlux(int track, int side) = 0;
|
||||
virtual std::unique_ptr<FluxSourceIterator> readFlux(
|
||||
int track, int side) = 0;
|
||||
virtual void recalibrate() {}
|
||||
virtual bool isHardware() { return false; }
|
||||
virtual void seek(int track) {}
|
||||
virtual bool isHardware()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
class TrivialFluxSource : public FluxSource
|
||||
{
|
||||
public:
|
||||
std::unique_ptr<FluxSourceIterator> readFlux(int track, int side);
|
||||
virtual std::unique_ptr<const Fluxmap> readSingleFlux(int track, int side) = 0;
|
||||
virtual std::unique_ptr<const Fluxmap> readSingleFlux(
|
||||
int track, int side) = 0;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
@@ -30,7 +30,9 @@ private:
|
||||
|
||||
std::unique_ptr<const Fluxmap> next()
|
||||
{
|
||||
usbSetDrive(config.drive().drive(), config.drive().high_density(), config.drive().index_mode());
|
||||
usbSetDrive(config.drive().drive(),
|
||||
config.drive().high_density(),
|
||||
config.drive().index_mode());
|
||||
usbSeek(_track);
|
||||
|
||||
Bytes data = usbRead(_head,
|
||||
@@ -59,8 +61,7 @@ public:
|
||||
public:
|
||||
std::unique_ptr<FluxSourceIterator> readFlux(int track, int head) override
|
||||
{
|
||||
return std::make_unique<HardwareFluxSourceIterator>(
|
||||
*this, track, head);
|
||||
return std::make_unique<HardwareFluxSourceIterator>(*this, track, head);
|
||||
}
|
||||
|
||||
void recalibrate() override
|
||||
@@ -68,6 +69,11 @@ public:
|
||||
usbRecalibrate();
|
||||
}
|
||||
|
||||
void seek(int track) override
|
||||
{
|
||||
usbSeek(track);
|
||||
}
|
||||
|
||||
bool isHardware() override
|
||||
{
|
||||
return true;
|
||||
|
||||
@@ -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<const TrackInfo>& 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,10 +474,14 @@ std::shared_ptr<TrackFlux> readAndDecodeTrack(FluxSource& fluxSource,
|
||||
break;
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
296
lib/vfs/philefs.cc
Normal file
296
lib/vfs/philefs.cc
Normal file
@@ -0,0 +1,296 @@
|
||||
#include "lib/globals.h"
|
||||
#include "lib/vfs/vfs.h"
|
||||
#include "lib/config.pb.h"
|
||||
#include "lib/utils.h"
|
||||
#include <fmt/format.h>
|
||||
|
||||
/* Root block:
|
||||
*
|
||||
* 00-0b volume name
|
||||
* 0c 01
|
||||
* 0d 2a
|
||||
* 0e 79
|
||||
* 0f 6d
|
||||
* 10 07 0x07c10c19, creation timestamp
|
||||
* 11 c1 ^
|
||||
* 12 0c ^
|
||||
* 13 19 ^
|
||||
* 14 2f
|
||||
* 15 00
|
||||
* 16 00
|
||||
* 17 18
|
||||
* 18 03 0x320, number of blocks on the disk
|
||||
* 19 20 ^
|
||||
* 1a 00 0x0010, first data block?
|
||||
* 1b 10 ^
|
||||
* 1c 00 0x0010, address of bitmap in HCS
|
||||
* 1d 10 ^
|
||||
* 1e 00 0x0011, address of FLIST in HCS
|
||||
* 1f 11 ^
|
||||
* 20 00 0x0017, address of last block of FLIST?
|
||||
* 21 17 ^
|
||||
* 22 00
|
||||
* 23 6b
|
||||
* 24 00
|
||||
* 25 20
|
||||
*
|
||||
* 14 files
|
||||
* file id 3 is not used
|
||||
* directory at 0xc00
|
||||
* 0x4000, block 0x10, volume bitmap
|
||||
* 0x4400, block 0x11, flist, 7 blocks long?
|
||||
* file descriptors seem to be 64 bytes
|
||||
*
|
||||
* File descriptor, 64 bytes:
|
||||
* 00 file type
|
||||
* 0e+04 length in bytes
|
||||
* 14... spans
|
||||
* word: start block
|
||||
* word: number of blocks
|
||||
*
|
||||
00000C00 00 01 42 49 54 4D 41 50 2E 53 59 53 00 00 00 00 ..BITMAP.SYS....
|
||||
|
||||
00008040 41 00 00 00 07 C1 0C 19 1E 00 00 18 00 02 00 00 A...............
|
||||
00008050 04 00 00 01 00 10 00 01 00 00 00 00 00 00 00 00 ................
|
||||
00008060 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
|
||||
00008070 00 00 00 00 00 00 00 00 00 00 00 01 00 01 01 00 ................
|
||||
|
||||
00000C10 00 02 46 4C 49 53 54 2E 53 59 53 00 00 00 00 00 ..FLIST.SYS.....
|
||||
|
||||
00008080 41 00 00 00 07 C1 0C 19 1E 00 00 18 00 02 00 00 A...............
|
||||
00008090 1C 00 00 01 00 11 00 07 00 00 00 00 00 00 00 00 ................
|
||||
000080A0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
|
||||
000080B0 00 00 00 00 00 00 00 00 00 00 00 07 00 07 01 00 ................
|
||||
|
||||
00000C20 00 04 53 4B 45 4C 00 00 00 00 00 00 00 00 00 00 ..SKEL..........
|
||||
|
||||
00008100 01 00 00 03 07 C1 0C 19 19 00 00 19 00 02 00 00 ................
|
||||
00008110 55 00 00 01 00 20 00 16 00 00 00 00 00 00 00 00 U.... ..........
|
||||
00008120 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
|
||||
00008130 00 00 00 00 00 00 00 00 00 00 00 16 00 16 01 00 ................
|
||||
|
||||
|
||||
00000C30 00 05 43 4F 44 45 00 00 00 00 00 00 00 00 00 00 ..CODE..........
|
||||
|
||||
00004540 01 00 00 03 07 C1 0C 19 26 00 00 1F 00 02 00 08 ........&.......
|
||||
00004550 10 00 00 01 00 36 02 04 00 00 00 00 00 00 00 00 .....6..........
|
||||
00004560 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
|
||||
00004570 00 00 00 00 00 00 00 00 00 00 02 04 02 04 01 00 ................
|
||||
|
||||
00000C40 00 06 53 43 53 49 00 00 00 00 00 00 00 00 00 00 ..SCSI..........
|
||||
00000C50 00 07 41 4F 46 00 00 00 00 00 00 00 00 00 00 00 ..AOF...........
|
||||
00000C60 00 08 4D 43 46 00 00 00 00 00 00 00 00 00 00 00 ..MCF...........
|
||||
00000C70 00 09 53 59 53 54 45 4D 2E 53 43 46 00 00 00 00 ..SYSTEM.SCF....
|
||||
00000C80 00 0A 53 59 53 54 45 4D 2E 50 44 46 00 00 00 00 ..SYSTEM.PDF....
|
||||
00000C90 00 0B 43 4C 54 31 2E 43 4C 54 00 00 00 00 00 00 ..CLT1.CLT......
|
||||
00000CA0 00 0C 43 4C 54 32 2E 43 4C 54 00 00 00 00 00 00 ..CLT2.CLT......
|
||||
00000CB0 00 0D 43 4C 54 33 2E 43 4C 54 00 00 00 00 00 00 ..CLT3.CLT......
|
||||
00000CC0 00 0E 43 4C 54 34 2E 43 4C 54 00 00 00 00 00 00 ..CLT4.CLT......
|
||||
00000CD0 00 0F 47 52 45 59 2E 43 4C 54 00 00 00 00 00 00 ..GREY.CLT......
|
||||
*/
|
||||
|
||||
static void trimZeros(std::string s)
|
||||
{
|
||||
s.erase(std::remove(s.begin(), s.end(), 0), s.end());
|
||||
}
|
||||
|
||||
class PhileFilesystem : public Filesystem
|
||||
{
|
||||
struct Span
|
||||
{
|
||||
uint16_t startBlock;
|
||||
uint16_t blockCount;
|
||||
};
|
||||
|
||||
class PhileDirent : public Dirent
|
||||
{
|
||||
public:
|
||||
PhileDirent(
|
||||
int fileno, const std::string& filename, const Bytes& filedes):
|
||||
_fileno(fileno)
|
||||
{
|
||||
file_type = TYPE_FILE;
|
||||
|
||||
ByteReader br(filedes);
|
||||
br.seek(0x0e);
|
||||
length = br.read_be32();
|
||||
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << 'R';
|
||||
if (filedes[0] & 0x40)
|
||||
ss << 'S';
|
||||
mode = ss.str();
|
||||
}
|
||||
|
||||
this->filename = filename;
|
||||
path = {filename};
|
||||
|
||||
attributes[Filesystem::FILENAME] = filename;
|
||||
attributes[Filesystem::LENGTH] = std::to_string(length);
|
||||
attributes[Filesystem::FILE_TYPE] = "file";
|
||||
attributes[Filesystem::MODE] = mode;
|
||||
|
||||
int spans = br.read_be16();
|
||||
for (int i = 0; i < spans; i++)
|
||||
{
|
||||
Span span;
|
||||
span.startBlock = br.read_be16();
|
||||
span.blockCount = br.read_be16();
|
||||
_spans.push_back(span);
|
||||
}
|
||||
|
||||
attributes["phile.spans"] = std::to_string(spans);
|
||||
}
|
||||
|
||||
const std::vector<Span>& spans() const
|
||||
{
|
||||
return _spans;
|
||||
}
|
||||
|
||||
private:
|
||||
int _fileno;
|
||||
std::vector<Span> _spans;
|
||||
};
|
||||
|
||||
public:
|
||||
PhileFilesystem(
|
||||
const PhileProto& config, std::shared_ptr<SectorInterface> sectors):
|
||||
Filesystem(sectors),
|
||||
_config(config)
|
||||
{
|
||||
}
|
||||
|
||||
uint32_t capabilities() const
|
||||
{
|
||||
return OP_GETFSDATA | OP_LIST | OP_GETFILE | OP_GETDIRENT;
|
||||
}
|
||||
|
||||
FilesystemStatus check() override
|
||||
{
|
||||
return FS_OK;
|
||||
}
|
||||
|
||||
std::map<std::string, std::string> getMetadata() override
|
||||
{
|
||||
mount();
|
||||
|
||||
std::string volumename = _rootBlock.reader().read(0x0c);
|
||||
trimZeros(volumename);
|
||||
|
||||
std::map<std::string, std::string> attributes;
|
||||
attributes[VOLUME_NAME] = volumename;
|
||||
attributes[TOTAL_BLOCKS] = std::to_string(_totalBlocks);
|
||||
attributes[USED_BLOCKS] = attributes[TOTAL_BLOCKS];
|
||||
attributes[BLOCK_SIZE] = std::to_string(_config.block_size());
|
||||
return attributes;
|
||||
}
|
||||
|
||||
std::shared_ptr<Dirent> getDirent(const Path& path) override
|
||||
{
|
||||
mount();
|
||||
if (path.size() != 1)
|
||||
throw BadPathException();
|
||||
|
||||
return findFile(path.front());
|
||||
}
|
||||
|
||||
std::vector<std::shared_ptr<Dirent>> list(const Path& path) override
|
||||
{
|
||||
mount();
|
||||
if (!path.empty())
|
||||
throw FileNotFoundException();
|
||||
|
||||
std::vector<std::shared_ptr<Dirent>> result;
|
||||
for (auto& de : _dirents)
|
||||
result.push_back(de.second);
|
||||
return result;
|
||||
}
|
||||
|
||||
Bytes getFile(const Path& path) override
|
||||
{
|
||||
mount();
|
||||
if (path.size() != 1)
|
||||
throw BadPathException();
|
||||
|
||||
auto dirent = findFile(path.front());
|
||||
|
||||
Bytes data;
|
||||
ByteWriter bw(data);
|
||||
for (const auto& span : dirent->spans())
|
||||
bw += getPsosBlock(span.startBlock, span.blockCount);
|
||||
|
||||
data.resize(dirent->length);
|
||||
return data;
|
||||
}
|
||||
|
||||
private:
|
||||
void mount()
|
||||
{
|
||||
_sectorSize = getLogicalSectorSize();
|
||||
_blockSectors = _config.block_size() / _sectorSize;
|
||||
|
||||
_rootBlock = getPsosBlock(2, 1);
|
||||
_bitmapBlockNumber = _rootBlock.reader().seek(0x1c).read_be16();
|
||||
_filedesBlockNumber = _rootBlock.reader().seek(0x1e).read_be16();
|
||||
_filedesLength =
|
||||
_rootBlock.reader().seek(0x20).read_be16() - _filedesBlockNumber + 1;
|
||||
_totalBlocks = _rootBlock.reader().seek(0x18).read_be16();
|
||||
|
||||
Bytes directoryBlock = getPsosBlock(3, 1);
|
||||
Bytes filedesBlock = getPsosBlock(_filedesBlockNumber, _filedesLength);
|
||||
|
||||
_dirents.clear();
|
||||
ByteReader br(directoryBlock);
|
||||
ByteReader fr(filedesBlock);
|
||||
while (!br.eof())
|
||||
{
|
||||
uint16_t fileno = br.read_be16();
|
||||
std::string filename = br.read(14);
|
||||
trimZeros(filename);
|
||||
|
||||
if (fileno)
|
||||
{
|
||||
fr.seek(fileno * 64);
|
||||
Bytes filedes = fr.read(64);
|
||||
auto dirent =
|
||||
std::make_unique<PhileDirent>(fileno, filename, filedes);
|
||||
_dirents[fileno] = std::move(dirent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<PhileDirent> findFile(const std::string filename)
|
||||
{
|
||||
for (const auto& dirent : _dirents)
|
||||
{
|
||||
if (dirent.second->filename == filename)
|
||||
return dirent.second;
|
||||
}
|
||||
|
||||
throw FileNotFoundException();
|
||||
}
|
||||
|
||||
Bytes getPsosBlock(uint32_t number, uint32_t count = 1)
|
||||
{
|
||||
unsigned sector = number * _blockSectors;
|
||||
return getLogicalSector(sector, _blockSectors * count);
|
||||
}
|
||||
|
||||
private:
|
||||
const PhileProto& _config;
|
||||
int _sectorSize;
|
||||
int _blockSectors;
|
||||
int _totalBlocks;
|
||||
int _bitmapBlockNumber;
|
||||
int _filedesBlockNumber;
|
||||
int _filedesLength;
|
||||
Bytes _rootBlock;
|
||||
std::map<int, std::shared_ptr<PhileDirent>> _dirents;
|
||||
};
|
||||
|
||||
std::unique_ptr<Filesystem> Filesystem::createPhileFilesystem(
|
||||
const FilesystemProto& config, std::shared_ptr<SectorInterface> sectors)
|
||||
{
|
||||
return std::make_unique<PhileFilesystem>(config.phile(), sectors);
|
||||
}
|
||||
@@ -209,6 +209,9 @@ std::unique_ptr<Filesystem> 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<Filesystem>();
|
||||
|
||||
@@ -252,6 +252,8 @@ public:
|
||||
const FilesystemProto& config, std::shared_ptr<SectorInterface> image);
|
||||
static std::unique_ptr<Filesystem> createSmaky6Filesystem(
|
||||
const FilesystemProto& config, std::shared_ptr<SectorInterface> image);
|
||||
static std::unique_ptr<Filesystem> createPhileFilesystem(
|
||||
const FilesystemProto& config, std::shared_ptr<SectorInterface> image);
|
||||
|
||||
static std::unique_ptr<Filesystem> createFilesystem(
|
||||
const FilesystemProto& config, std::shared_ptr<SectorInterface> image);
|
||||
|
||||
@@ -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 {
|
||||
@@ -83,6 +90,7 @@ message FilesystemProto
|
||||
PRODOS = 8;
|
||||
SMAKY6 = 9;
|
||||
APPLEDOS = 10;
|
||||
PHILE = 11;
|
||||
}
|
||||
|
||||
optional FilesystemType type = 10 [default = NOT_SET, (help) = "filesystem type"];
|
||||
@@ -97,6 +105,7 @@ message FilesystemProto
|
||||
optional ProdosProto prodos = 8;
|
||||
optional AppledosProto appledos = 12;
|
||||
optional Smaky6FsProto smaky6 = 11;
|
||||
optional PhileProto phile = 13;
|
||||
|
||||
optional SectorListProto sector_order = 9 [(help) = "specify the filesystem order of sectors"];
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ FLUXENGINE_SRCS = \
|
||||
src/fe-getfileinfo.cc \
|
||||
src/fe-inspect.cc \
|
||||
src/fe-ls.cc \
|
||||
src/fe-merge.cc \
|
||||
src/fe-mkdir.cc \
|
||||
src/fe-mv.cc \
|
||||
src/fe-rm.cc \
|
||||
|
||||
65
src/fe-merge.cc
Normal file
65
src/fe-merge.cc
Normal file
@@ -0,0 +1,65 @@
|
||||
#include "globals.h"
|
||||
#include "flags.h"
|
||||
#include "fluxmap.h"
|
||||
#include "sector.h"
|
||||
#include "proto.h"
|
||||
#include "flux.h"
|
||||
#include "fl2.h"
|
||||
#include "fl2.pb.h"
|
||||
#include "fmt/format.h"
|
||||
#include "fluxengine.h"
|
||||
#include <fstream>
|
||||
|
||||
static FlagGroup flags;
|
||||
|
||||
static std::vector<std::string> inputFluxFiles;
|
||||
|
||||
static StringFlag sourceFlux({"-s", "--source"},
|
||||
"flux file to read from (repeatable)",
|
||||
"",
|
||||
[](const auto& value)
|
||||
{
|
||||
inputFluxFiles.push_back(value);
|
||||
});
|
||||
|
||||
static StringFlag destFlux(
|
||||
{"-d", "--dest"}, "destination flux file to write to", "");
|
||||
|
||||
int mainMerge(int argc, const char* argv[])
|
||||
{
|
||||
flags.parseFlags(argc, argv);
|
||||
|
||||
if (inputFluxFiles.empty())
|
||||
Error() << "you must specify at least one input flux file (with -s)";
|
||||
if (destFlux.get() == "")
|
||||
Error() << "you must specify an output flux file (with -d)";
|
||||
|
||||
std::map<std::pair<int, int>, TrackFluxProto> data;
|
||||
for (const auto& s : inputFluxFiles)
|
||||
{
|
||||
fmt::print("Reading {}...\n", s);
|
||||
FluxFileProto f = loadFl2File(s);
|
||||
|
||||
for (auto& trackflux : f.track())
|
||||
{
|
||||
auto key = std::make_pair(trackflux.track(), trackflux.head());
|
||||
auto i = data.find(key);
|
||||
if (i == data.end())
|
||||
data[key] = trackflux;
|
||||
else
|
||||
{
|
||||
for (auto flux : trackflux.flux())
|
||||
i->second.add_flux(flux);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
FluxFileProto proto;
|
||||
for (auto& i : data)
|
||||
*proto.add_track() = i.second;
|
||||
|
||||
fmt::print("Writing {}...\n", destFlux.get());
|
||||
saveFl2File(destFlux.get(), proto);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -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<Command> 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).", },
|
||||
|
||||
@@ -63,6 +63,7 @@ FORMATS = \
|
||||
northstar175 \
|
||||
northstar350 \
|
||||
northstar87 \
|
||||
psos800 \
|
||||
rx50 \
|
||||
shugart_drive \
|
||||
smaky6 \
|
||||
|
||||
58
src/formats/psos800.textpb
Normal file
58
src/formats/psos800.textpb
Normal file
@@ -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
|
||||
}
|
||||
|
||||
@@ -93,7 +93,7 @@ void FluxEngineApp::RunOnWorkerThread(std::function<void()> 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;
|
||||
@@ -121,11 +121,18 @@ bool FluxEngineApp::IsWorkerThreadRunning() const
|
||||
}
|
||||
|
||||
void FluxEngineApp::OnExec(const ExecEvent& event)
|
||||
{
|
||||
try
|
||||
{
|
||||
event.RunCallback();
|
||||
if (event.IsSynchronous())
|
||||
execSemaphore.Post();
|
||||
}
|
||||
catch (std::exception& e)
|
||||
{
|
||||
std::cerr << "Unhandled exception: " << e.what() << "\n";
|
||||
}
|
||||
}
|
||||
|
||||
void runOnUiThread(std::function<void()> callback)
|
||||
{
|
||||
|
||||
@@ -481,6 +481,9 @@ public:
|
||||
visualiser->Clear();
|
||||
_filesystemModel->Clear(Path());
|
||||
_currentDisk = nullptr;
|
||||
_filesystemCapabilities = 0;
|
||||
_filesystemIsReadOnly = true;
|
||||
_filesystemNeedsFlushing = false;
|
||||
|
||||
_state = STATE_BROWSING_WORKING;
|
||||
UpdateState();
|
||||
|
||||
Reference in New Issue
Block a user