mirror of
https://github.com/davidgiven/fluxengine.git
synced 2025-10-31 11:17:01 -07:00
Compare commits
46 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1b859015ae | ||
|
|
294ac87503 | ||
|
|
c297adb0c7 | ||
|
|
446b965794 | ||
|
|
1927cc7fe1 | ||
|
|
4eca254daf | ||
|
|
c7d4fee3f6 | ||
|
|
a6f798ae5b | ||
|
|
c9ae836e52 | ||
|
|
e3ffa63f7f | ||
|
|
4ffc2cc1dc | ||
|
|
7f9ba14687 | ||
|
|
a24933e272 | ||
|
|
20bdacbecf | ||
|
|
1f5903a9a0 | ||
|
|
bb073b6bb3 | ||
|
|
516241f8f5 | ||
|
|
977b6831a0 | ||
|
|
c61effb54f | ||
|
|
346d989944 | ||
|
|
60a73c8d1e | ||
|
|
e52db4a837 | ||
|
|
4e317643bc | ||
|
|
5f520bf375 | ||
|
|
2efe521b3a | ||
|
|
5c21103646 | ||
|
|
9444696f37 | ||
|
|
082fe4e787 | ||
|
|
5e13cf23f9 | ||
|
|
8f98a1f557 | ||
|
|
5b21e8798b | ||
|
|
b9ef5b7db8 | ||
|
|
9867f8c302 | ||
|
|
315889faf6 | ||
|
|
798e8fee89 | ||
|
|
e1c49db329 | ||
|
|
dae9537472 | ||
|
|
1330d56cdd | ||
|
|
6ce3ce20d0 | ||
|
|
362c5ee9b0 | ||
|
|
0f34ce0278 | ||
|
|
0c27c7c4c8 | ||
|
|
9db6efe7a2 | ||
|
|
8b8a22d7fb | ||
|
|
0a70344bc1 | ||
|
|
e77d01911c |
5
.github/workflows/ccpp.yml
vendored
5
.github/workflows/ccpp.yml
vendored
@@ -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'
|
||||
|
||||
4
.github/workflows/release.yml
vendored
4
.github/workflows/release.yml
vendored
@@ -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
|
||||
|
||||
@@ -132,13 +132,13 @@ choices because they can store multiple types of file system.
|
||||
| [`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 -->
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
/*
|
||||
|
||||
BIN
doc/FluxEngine_eagle_pcb.zip
Normal file
BIN
doc/FluxEngine_eagle_pcb.zip
Normal file
Binary file not shown.
@@ -93,6 +93,22 @@ You're now looking at the _top_ of the board.
|
||||
row of header sockets allowing you to plug the board directly onto the floppy
|
||||
disk drive; for simplicity I'm leaving that as an exercise for the reader.)
|
||||
|
||||
### If you want to use a PCB
|
||||
|
||||
Alternatively, you can make an actual PCB!
|
||||
|
||||
<div style="text-align: center">
|
||||
<a href="pcb.png"><img src="pcb.png" style="width:80%" alt="the PCB schematic"></a>
|
||||
</div>
|
||||
|
||||
This is a passive breakout board designed to take a PSoC5 development board, a
|
||||
standard 34-way PC connector, and a 50-way 8" drive connector. It was
|
||||
contributed by a user --- thanks!
|
||||
|
||||
<a href="FluxEngine_eagle_pcb.zip">Download this to get it</a>. This package
|
||||
contains the layout in Eagle format, a printable PDF of the PCB layout, and
|
||||
gerbers suitable for sending off for manufacture.
|
||||
|
||||
### Grounding
|
||||
|
||||
You _also_ need to solder a wire between a handy GND pin on the board and
|
||||
@@ -185,10 +201,14 @@ generic libusb stuff and should build and run on Windows, Linux and OSX as
|
||||
well, although on Windows it'll need MSYS2 and mingw32. You'll need to
|
||||
install some support packages.
|
||||
|
||||
- For Linux (this is Ubuntu, but this should apply to Debian too):
|
||||
- For Linux with Ubuntu/Debian:
|
||||
`libusb-1.0-0-dev`, `libsqlite3-dev`, `zlib1g-dev`,
|
||||
`libudev-dev`, `protobuf-compiler`, `libwxgtk3.0-gtk3-dev`,
|
||||
`libfmt-dev`.
|
||||
- For Linux with Fedora/Red Hat:
|
||||
`git`, `make`, `gcc`, `gcc-c++`, `xxd`, `protobuf-compiler`,
|
||||
`protobuf-devel`, `fmt-devel`, `systemd-devel`, `wxGTK3-devel`,
|
||||
`libsqlite3x-devel`
|
||||
- For OSX with Homebrew: `libusb`, `pkg-config`, `sqlite`,
|
||||
`protobuf`, `truncate`, `wxwidgets`, `fmt`.
|
||||
- For Windows with MSYS2: `make`, `mingw-w64-i686-libusb`,
|
||||
|
||||
@@ -9,9 +9,13 @@ 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.
|
||||
|
||||
Please [get in touch](https://github.com/davidgiven/fluxengine/issues/new) if
|
||||
you know anything about it.
|
||||
@@ -26,3 +30,7 @@ To read:
|
||||
|
||||
- `fluxengine read rolandd20 -s drive:0 -o rolandd20.img`
|
||||
|
||||
To write:
|
||||
|
||||
- `fluxengine write rolandd20 -d drive:0 -i rolandd20.img`
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
BIN
doc/pcb.png
Normal file
BIN
doc/pcb.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 22 KiB |
@@ -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
|
||||
|
||||
;--------------------------------
|
||||
|
||||
14
lib/build.mk
14
lib/build.mk
@@ -77,14 +77,16 @@ 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/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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -345,6 +345,8 @@ public:
|
||||
return *this += stream;
|
||||
}
|
||||
|
||||
ByteWriter& pad(unsigned count, uint8_t byte = 0);
|
||||
|
||||
private:
|
||||
Bytes& _bytes;
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
346
lib/config.cc
346
lib/config.cc
@@ -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;
|
||||
}
|
||||
21
lib/config.h
21
lib/config.h
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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>();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -180,7 +180,7 @@ public:
|
||||
trackdataWriter += fluxdata;
|
||||
}
|
||||
|
||||
operator std::string() const
|
||||
operator std::string() const override
|
||||
{
|
||||
return fmt::format("scp({})", _config.filename());
|
||||
}
|
||||
|
||||
@@ -64,7 +64,7 @@ public:
|
||||
of << "\n";
|
||||
}
|
||||
|
||||
operator std::string() const
|
||||
operator std::string() const override
|
||||
{
|
||||
return fmt::format("vcd({})", _config.directory());
|
||||
}
|
||||
|
||||
@@ -142,7 +142,7 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
void recalibrate() {}
|
||||
void recalibrate() override {}
|
||||
|
||||
private:
|
||||
Bytes findChunk(Bytes id)
|
||||
|
||||
@@ -15,7 +15,7 @@ public:
|
||||
return std::unique_ptr<const Fluxmap>();
|
||||
}
|
||||
|
||||
void recalibrate() {}
|
||||
void recalibrate() override {}
|
||||
};
|
||||
|
||||
std::unique_ptr<FluxSource> FluxSource::createEraseFluxSource(
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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>();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -19,7 +19,7 @@ public:
|
||||
return readFlxBytes(Bytes::readFromFile(path));
|
||||
}
|
||||
|
||||
void recalibrate() {}
|
||||
void recalibrate() override {}
|
||||
|
||||
private:
|
||||
const std::string _path;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -128,7 +128,7 @@ public:
|
||||
return fluxmap;
|
||||
}
|
||||
|
||||
void recalibrate() {}
|
||||
void recalibrate() override {}
|
||||
|
||||
private:
|
||||
void check_for_error()
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
#include <climits>
|
||||
#include <variant>
|
||||
#include <optional>
|
||||
#include <regex>
|
||||
#include <fmt/format.h>
|
||||
|
||||
#if defined(_WIN32) || defined(__WIN32__)
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -273,6 +273,8 @@ public:
|
||||
case ImdOutputProto::RATE_DD:
|
||||
RATE = 2000;
|
||||
break;
|
||||
case ImdOutputProto::RATE_GUESS:
|
||||
break;
|
||||
}
|
||||
}
|
||||
header.ModeValue =
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
10
lib/sector.h
10
lib/sector.h
@@ -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(
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -50,7 +50,7 @@ public:
|
||||
{
|
||||
}
|
||||
|
||||
uint32_t capabilities() const
|
||||
uint32_t capabilities() const override
|
||||
{
|
||||
return OP_LIST | OP_GETDIRENT | OP_GETFSDATA | OP_GETFILE;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -195,7 +195,7 @@ public:
|
||||
{
|
||||
}
|
||||
|
||||
uint32_t capabilities() const
|
||||
uint32_t capabilities() const override
|
||||
{
|
||||
return OP_GETFSDATA | OP_LIST | OP_GETFILE | OP_GETDIRENT;
|
||||
}
|
||||
|
||||
@@ -81,7 +81,7 @@ public:
|
||||
{
|
||||
}
|
||||
|
||||
uint32_t capabilities() const
|
||||
uint32_t capabilities() const override
|
||||
{
|
||||
return OP_GETFSDATA | OP_LIST | OP_GETFILE | OP_GETDIRENT;
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -44,7 +44,7 @@ public:
|
||||
return _changedSectors.put(track, side, sectorId);
|
||||
}
|
||||
|
||||
virtual bool isReadOnly()
|
||||
virtual bool isReadOnly() override
|
||||
{
|
||||
return (_fluxSink == nullptr);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -160,7 +160,7 @@ public:
|
||||
{
|
||||
}
|
||||
|
||||
uint32_t capabilities() const
|
||||
uint32_t capabilities() const override
|
||||
{
|
||||
return OP_GETFSDATA | OP_LIST | OP_GETFILE | OP_GETDIRENT;
|
||||
}
|
||||
|
||||
@@ -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
427
lib/vfs/roland.cc
Normal 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);
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -139,7 +139,7 @@ public:
|
||||
{
|
||||
}
|
||||
|
||||
uint32_t capabilities() const
|
||||
uint32_t capabilities() const override
|
||||
{
|
||||
return OP_LIST | OP_GETFILE | OP_GETFSDATA | OP_GETDIRENT;
|
||||
}
|
||||
|
||||
@@ -216,6 +216,12 @@ std::unique_ptr<Filesystem> Filesystem::createFilesystem(
|
||||
case FilesystemProto::LIF:
|
||||
return Filesystem::createLifFilesystem(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 +242,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 +279,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);
|
||||
|
||||
@@ -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:
|
||||
@@ -256,6 +264,10 @@ 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> 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);
|
||||
|
||||
@@ -88,7 +88,29 @@ message LifProto
|
||||
[ default = 256, (help) = "LIF filesystem block size" ];
|
||||
}
|
||||
|
||||
// NEXT_TAG: 15
|
||||
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: 17
|
||||
message FilesystemProto
|
||||
{
|
||||
enum FilesystemType
|
||||
@@ -106,6 +128,8 @@ message FilesystemProto
|
||||
APPLEDOS = 10;
|
||||
PHILE = 11;
|
||||
LIF = 12;
|
||||
ZDOS = 13;
|
||||
ROLAND = 14;
|
||||
}
|
||||
|
||||
optional FilesystemType type = 10
|
||||
@@ -123,6 +147,8 @@ message FilesystemProto
|
||||
optional Smaky6FsProto smaky6 = 11;
|
||||
optional PhileProto phile = 13;
|
||||
optional LifProto lif = 14;
|
||||
optional ZDosProto zdos = 15;
|
||||
optional RolandFsProto roland = 16;
|
||||
|
||||
optional SectorListProto sector_order = 9
|
||||
[ (help) = "specify the filesystem order of sectors" ];
|
||||
|
||||
325
lib/vfs/zdos.cc
Normal file
325
lib/vfs/zdos.cc
Normal 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);
|
||||
}
|
||||
@@ -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[])
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -43,7 +43,7 @@ documentation:
|
||||
|
||||
image_writer {
|
||||
filename: "aeslanier.img"
|
||||
type: IMG
|
||||
type: IMAGETYPE_IMG
|
||||
}
|
||||
|
||||
decoder {
|
||||
|
||||
@@ -28,7 +28,7 @@ documentation:
|
||||
|
||||
image_writer {
|
||||
filename: "agat.img"
|
||||
type: IMG
|
||||
type: IMAGETYPE_IMG
|
||||
}
|
||||
|
||||
layout {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -44,7 +44,7 @@ documentation:
|
||||
|
||||
image_writer {
|
||||
filename: "ampro.img"
|
||||
type: IMG
|
||||
type: IMAGETYPE_IMG
|
||||
}
|
||||
|
||||
layout {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -37,7 +37,7 @@ documentation:
|
||||
|
||||
image_writer {
|
||||
filename: "eco1.img"
|
||||
type: IMG
|
||||
type: IMAGETYPE_IMG
|
||||
}
|
||||
|
||||
layout {
|
||||
|
||||
@@ -11,7 +11,7 @@ format itself is yet another IBM scheme variant.
|
||||
|
||||
image_writer {
|
||||
filename: "epsonpf10.img"
|
||||
type: IMG
|
||||
type: IMAGETYPE_IMG
|
||||
}
|
||||
|
||||
layout {
|
||||
|
||||
@@ -42,7 +42,7 @@ There's amazingly little information about these things.
|
||||
|
||||
image_writer {
|
||||
filename: "f85.img"
|
||||
type: IMG
|
||||
type: IMAGETYPE_IMG
|
||||
}
|
||||
|
||||
decoder {
|
||||
|
||||
@@ -43,7 +43,7 @@ documentation:
|
||||
|
||||
image_writer {
|
||||
filename: "fb100.img"
|
||||
type: IMG
|
||||
type: IMAGETYPE_IMG
|
||||
}
|
||||
|
||||
decoder {
|
||||
|
||||
@@ -20,12 +20,12 @@ drive {
|
||||
|
||||
image_reader {
|
||||
filename: "hplif.img"
|
||||
type: IMG
|
||||
type: IMAGETYPE_IMG
|
||||
}
|
||||
|
||||
image_writer {
|
||||
filename: "hplif.img"
|
||||
type: IMG
|
||||
type: IMAGETYPE_IMG
|
||||
}
|
||||
|
||||
decoder {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -61,12 +61,12 @@ documentation:
|
||||
|
||||
image_reader {
|
||||
filename: "mac.dsk"
|
||||
type: IMG
|
||||
type: IMAGETYPE_IMG
|
||||
}
|
||||
|
||||
image_writer {
|
||||
filename: "mac.dsk"
|
||||
type: IMG
|
||||
type: IMAGETYPE_IMG
|
||||
}
|
||||
|
||||
layout {
|
||||
|
||||
@@ -67,12 +67,12 @@ drive {
|
||||
|
||||
image_reader {
|
||||
filename: "micropolis.img"
|
||||
type: IMG
|
||||
type: IMAGETYPE_IMG
|
||||
}
|
||||
|
||||
image_writer {
|
||||
filename: "micropolis.img"
|
||||
type: IMG
|
||||
type: IMAGETYPE_IMG
|
||||
}
|
||||
|
||||
layout {
|
||||
@@ -100,12 +100,12 @@ option {
|
||||
config {
|
||||
image_reader {
|
||||
filename: "micropolis.vgi"
|
||||
type: IMG
|
||||
type: IMAGETYPE_IMG
|
||||
}
|
||||
|
||||
image_writer {
|
||||
filename: "micropolis.vgi"
|
||||
type: IMG
|
||||
type: IMAGETYPE_IMG
|
||||
}
|
||||
|
||||
layout {
|
||||
|
||||
@@ -57,7 +57,7 @@ documentation:
|
||||
|
||||
image_writer {
|
||||
filename: "mx.img"
|
||||
type: IMG
|
||||
type: IMAGETYPE_IMG
|
||||
}
|
||||
|
||||
layout {
|
||||
|
||||
@@ -19,12 +19,12 @@ drive {
|
||||
|
||||
image_reader {
|
||||
filename: "n88basic.img"
|
||||
type: IMG
|
||||
type: IMAGETYPE_IMG
|
||||
}
|
||||
|
||||
image_writer {
|
||||
filename: "n88basic.img"
|
||||
type: IMG
|
||||
type: IMAGETYPE_IMG
|
||||
}
|
||||
|
||||
layout {
|
||||
|
||||
@@ -35,12 +35,12 @@ documentation:
|
||||
|
||||
image_reader {
|
||||
filename: "northstar.nsi"
|
||||
type: NSI
|
||||
type: IMAGETYPE_NSI
|
||||
}
|
||||
|
||||
image_writer {
|
||||
filename: "northstar.nsi"
|
||||
type: NSI
|
||||
type: IMAGETYPE_NSI
|
||||
}
|
||||
|
||||
layout {
|
||||
|
||||
@@ -26,12 +26,12 @@ drive {
|
||||
|
||||
image_reader {
|
||||
filename: "pme.img"
|
||||
type: IMG
|
||||
type: IMAGETYPE_IMG
|
||||
}
|
||||
|
||||
image_writer {
|
||||
filename: "pme.img"
|
||||
type: IMG
|
||||
type: IMAGETYPE_IMG
|
||||
}
|
||||
|
||||
layout {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
shortname: 'Roland D20'
|
||||
comment: '3.5" electronic synthesiser disks'
|
||||
read_support_status: DINOSAUR
|
||||
read_support_status: UNICORN
|
||||
write_support_status: DINOSAUR
|
||||
|
||||
documentation:
|
||||
<<<
|
||||
@@ -10,17 +11,38 @@ 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!
|
||||
>>>
|
||||
|
||||
image_reader {
|
||||
filename: "rolandd20.img"
|
||||
type: IMAGETYPE_IMG
|
||||
}
|
||||
|
||||
image_writer {
|
||||
filename: "rolandd20.img"
|
||||
type: IMG
|
||||
type: IMAGETYPE_IMG
|
||||
}
|
||||
|
||||
layout {
|
||||
@@ -37,6 +59,10 @@ layout {
|
||||
}
|
||||
}
|
||||
|
||||
encoder {
|
||||
brother {}
|
||||
}
|
||||
|
||||
decoder {
|
||||
brother {}
|
||||
}
|
||||
@@ -45,3 +71,6 @@ drive {
|
||||
head_bias: 1
|
||||
}
|
||||
|
||||
filesystem {
|
||||
type: ROLAND
|
||||
}
|
||||
|
||||
@@ -16,12 +16,12 @@ drive {
|
||||
|
||||
image_reader {
|
||||
filename: "rx50.img"
|
||||
type: IMG
|
||||
type: IMAGETYPE_IMG
|
||||
}
|
||||
|
||||
image_writer {
|
||||
filename: "rx50.img"
|
||||
type: IMG
|
||||
type: IMAGETYPE_IMG
|
||||
}
|
||||
|
||||
layout {
|
||||
|
||||
@@ -29,7 +29,7 @@ documentation:
|
||||
|
||||
image_writer {
|
||||
filename: "smaky6.img"
|
||||
type: IMG
|
||||
type: IMAGETYPE_IMG
|
||||
}
|
||||
|
||||
layout {
|
||||
|
||||
@@ -31,12 +31,12 @@ documentation:
|
||||
|
||||
image_reader {
|
||||
filename: "tids990.img"
|
||||
type: IMG
|
||||
type: IMAGETYPE_IMG
|
||||
}
|
||||
|
||||
image_writer {
|
||||
filename: "tids990.img"
|
||||
type: IMG
|
||||
type: IMAGETYPE_IMG
|
||||
}
|
||||
|
||||
layout {
|
||||
|
||||
@@ -11,7 +11,7 @@ on the precise format.
|
||||
|
||||
image_writer {
|
||||
filename: "tiki.img"
|
||||
type: IMG
|
||||
type: IMAGETYPE_IMG
|
||||
}
|
||||
|
||||
decoder {
|
||||
|
||||
@@ -50,12 +50,12 @@ documentation:
|
||||
|
||||
image_reader {
|
||||
filename: "victor9k.img"
|
||||
type: IMG
|
||||
type: IMAGETYPE_IMG
|
||||
}
|
||||
|
||||
image_writer {
|
||||
filename: "victor9k.img"
|
||||
type: IMG
|
||||
type: IMAGETYPE_IMG
|
||||
}
|
||||
|
||||
layout {
|
||||
|
||||
@@ -21,11 +21,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.
|
||||
>>>
|
||||
|
||||
documentation:
|
||||
@@ -40,7 +37,7 @@ documentation:
|
||||
|
||||
image_writer {
|
||||
filename: "zilogmcz.img"
|
||||
type: IMG
|
||||
type: IMAGETYPE_IMG
|
||||
}
|
||||
|
||||
decoder {
|
||||
@@ -60,4 +57,13 @@ layout {
|
||||
}
|
||||
}
|
||||
|
||||
filesystem {
|
||||
type: ZDOS
|
||||
zdos {
|
||||
filesystem_start {
|
||||
track: 22
|
||||
sector: 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -483,6 +483,7 @@ private:
|
||||
return;
|
||||
|
||||
_filesystem->moveFile(oldPath, newPath);
|
||||
node->newname = "";
|
||||
|
||||
auto dirent = _filesystem->getDirent(newPath);
|
||||
runOnUiThread(
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user