Merge pull request #667 from davidgiven/options

Overhaul the options system.
This commit is contained in:
David Given
2023-05-06 00:18:41 +01:00
committed by GitHub
91 changed files with 2868 additions and 2842 deletions

View File

@@ -177,38 +177,40 @@ endef
$(call do-encodedecodetest,agat840)
$(call do-encodedecodetest,amiga)
$(call do-encodedecodetest,appleii140)
$(call do-encodedecodetest,atarist360)
$(call do-encodedecodetest,atarist370)
$(call do-encodedecodetest,atarist400)
$(call do-encodedecodetest,atarist410)
$(call do-encodedecodetest,atarist720)
$(call do-encodedecodetest,atarist740)
$(call do-encodedecodetest,atarist800)
$(call do-encodedecodetest,atarist820)
$(call do-encodedecodetest,apple2,,--140)
$(call do-encodedecodetest,atarist,,--360)
$(call do-encodedecodetest,atarist,,--370)
$(call do-encodedecodetest,atarist,,--400)
$(call do-encodedecodetest,atarist,,--410)
$(call do-encodedecodetest,atarist,,--720)
$(call do-encodedecodetest,atarist,,--740)
$(call do-encodedecodetest,atarist,,--800)
$(call do-encodedecodetest,atarist,,--820)
$(call do-encodedecodetest,bk800)
$(call do-encodedecodetest,brother120)
$(call do-encodedecodetest,brother240)
$(call do-encodedecodetest,commodore1541,scripts/commodore1541_test.textpb,--35)
$(call do-encodedecodetest,commodore1541,scripts/commodore1541_test.textpb,--40)
$(call do-encodedecodetest,brother,,--120)
$(call do-encodedecodetest,brother,,--240)
$(call do-encodedecodetest,commodore1541,scripts/commodore1541_test.textpb,--171)
$(call do-encodedecodetest,commodore1541,scripts/commodore1541_test.textpb,--192)
$(call do-encodedecodetest,commodore1581)
$(call do-encodedecodetest,cmd_fd2000)
$(call do-encodedecodetest,hp9121)
$(call do-encodedecodetest,ibm1200)
$(call do-encodedecodetest,ibm1232)
$(call do-encodedecodetest,ibm1440)
$(call do-encodedecodetest,ibm180)
$(call do-encodedecodetest,ibm160)
$(call do-encodedecodetest,ibm320)
$(call do-encodedecodetest,ibm360)
$(call do-encodedecodetest,ibm720)
$(call do-encodedecodetest,mac400,scripts/mac400_test.textpb)
$(call do-encodedecodetest,mac800,scripts/mac800_test.textpb)
$(call do-encodedecodetest,hplif,,--264)
$(call do-encodedecodetest,hplif,,--616)
$(call do-encodedecodetest,hplif,,--770)
$(call do-encodedecodetest,ibm,,--1200)
$(call do-encodedecodetest,ibm,,--1232)
$(call do-encodedecodetest,ibm,,--1440)
$(call do-encodedecodetest,ibm,,--180)
$(call do-encodedecodetest,ibm,,--160)
$(call do-encodedecodetest,ibm,,--320)
$(call do-encodedecodetest,ibm,,--360)
$(call do-encodedecodetest,ibm,,--720)
$(call do-encodedecodetest,mac,scripts/mac400_test.textpb,--400)
$(call do-encodedecodetest,mac,scripts/mac800_test.textpb,--800)
$(call do-encodedecodetest,n88basic)
$(call do-encodedecodetest,rx50)
$(call do-encodedecodetest,tids990)
$(call do-encodedecodetest,victor9k_ss)
$(call do-encodedecodetest,victor9k_ds)
$(call do-encodedecodetest,victor9k,,--612)
$(call do-encodedecodetest,victor9k,,--1224)
$(OBJDIR)/%.a:
@mkdir -p $(dir $@)

View File

@@ -19,13 +19,15 @@ Reading discs
Just do:
```
fluxengine read ampro400
fluxengine read ampro <format>
```
You should end up with an `ampro.img` which is 409600. If you have a
double-sided disk, use `ampro800`, which will give you a file819200 bytes long.
These is an alias for `fluxengine read ibm` with preconfigured parameters. You
can pass this straight into [cpmtools](http://www.moria.de/~michael/cpmtools/):
...where `<format>` is one of `--400` for a 40-track disk or `--800` for an
80-track disk. These are an alias for `fluxengine read ibm` with preconfigured
parameters.
FluxEngine has direct filesystem support for these disks, or you can pass the
disk images into [cpmtools](http://www.moria.de/~michael/cpmtools/):
```
$ cpmls -f ampdsdd ampro.img

View File

@@ -30,7 +30,8 @@ FluxEngine can remap the sectors from physical to logical using modifiers. If
you don't specify a remapping modifier, you get the sectors in the order they
appear on the disk.
If you don't want an image in physical sector order, specify one of these options:
If you don't want an image in physical sector order, specify one of these
options:
- `--appledos` Selects AppleDOS sector translation
- `--prodos` Selects ProDOS sector translation
@@ -40,15 +41,21 @@ These options also select the appropriate file system; FluxEngine has read-only
support for all of these. For example:
```
fluxengine ls appleii140 --appledos -f image.flux
fluxengine ls apple2 --appledos -f image.flux
```
In addition, some third-party systems use 80-track double sides drives, with
the same underlying disk format. These are supported with the `appleii640`
profile. The complication here is that the AppleDOS filesystem only supports up
the same underlying disk format. The full list of formats supported is:
- `--140` 35-track single-sided (the normal Apple II format)
- `--640` 80-track double-sided
`--140` is the default.
The complication here is that the AppleDOS filesystem only supports up
to 50 tracks, so it needs tweaking to support larger disks. It treats the
second side of the disk as a completely different volume. To access these
files, use `--appledos --side1`.
files, use `--640 --appledos --side1`.
[^1]: CP/M disks use the ProDOS translation for the first three tracks and a
different translation for all the tracks thereafter.
@@ -65,12 +72,12 @@ Reading discs
Just do:
```
fluxengine read appleii140
fluxengine read apple2
```
(or `appleii640`)
(or `apple2 --640`)
You should end up with an `appleii140.img` which is 143360 bytes long. It will
You should end up with an `apple2.img` which is 143360 bytes long. It will
be in physical sector ordering if you don't specify a file system format as
described above.
@@ -79,7 +86,7 @@ Writing discs
Just do:
```
fluxengine write appleii140 -i appleii140.img
fluxengine write apple2 -i apple2.img
```
The image will be expected to be in physical sector ordering if you don't

View File

@@ -33,29 +33,29 @@ FluxEngine can also write Atari ST scheme disks.
The syntax is:
fluxengine write <format> -i input.st
fluxengine write atarist -i input.st <format>
Available formats
-----------------
`<format>` can be one of these:
- `atarist360`: a 360kB 3.5" disk, with 80 cylinders, 1 side, and 9 sectors
per track.
- `atarist370`: a 370kB 3.5" disk, with 82 cylinders, 1 side, and 9 sectors
per track.
- `atarist400`: a 400kB 3.5" disk, with 80 cylinders, 1 side, and 10 sectors
per track.
- `atarist410`: a 410kB 3.5" disk, with 82 cylinders, 1 side, and 10 sectors
per track.
- `atarist720`: a 720kB 3.5" disk, with 80 cylinders, 2 sides, and 9 sectors
per track.
- `atarist740`: a 740kB 3.5" disk, with 82 cylinders, 2 sides, and 9 sectors
per track.
- `atarist800`: a 800kB 3.5" disk, with 80 cylinders, 2 sides, and 10 sectors
per track.
- `atarist820`: a 820kB 3.5" disk, with 82 cylinders, 2 sides, and 10 sectors
per track.
- `--360`: a 360kB 3.5" disk, with 80 cylinders, 1 side, and 9 sectors per
track.
- `--370`: a 370kB 3.5" disk, with 82 cylinders, 1 side, and 9 sectors per
track.
- `--400`: a 400kB 3.5" disk, with 80 cylinders, 1 side, and 10 sectors per
track.
- `--410`: a 410kB 3.5" disk, with 82 cylinders, 1 side, and 10 sectors per
track.
- `--720`: a 720kB 3.5" disk, with 80 cylinders, 2 sides, and 9 sectors per
track.
- `--740`: a 740kB 3.5" disk, with 82 cylinders, 2 sides, and 9 sectors per
track.
- `--800`: a 800kB 3.5" disk, with 80 cylinders, 2 sides, and 10 sectors per
track.
- `--820`: a 820kB 3.5" disk, with 82 cylinders, 2 sides, and 10 sectors per
track.
See [the IBM format documentation](disk-ibm.md) for more information. Note that
only some PC 3.5" floppy disk drives are capable of seeking to the 82nd track.

View File

@@ -38,10 +38,10 @@ Reading disks
Just do:
```
fluxengine read `<format>`
fluxengine read brother `<format>`
```
... where `<format>` can be `brother120` or `brother240`. You should end up
... where `<format>` can be `--120` or `--240`. You should end up
with a `brother.img` which is either 119808 or 239616 bytes long.
Writing disks
@@ -53,7 +53,7 @@ Just do:
fluxengine write `<format>` -i brother.img
```
...where `<format>` can be `brother120` or `brother240`.
...where `<format>` can be `--120` or `--240`.
Dealing with misaligned disks
-----------------------------
@@ -102,7 +102,8 @@ record.) The sector order is 05a3816b4927, which gives a sector skew of 5.
High level format
-----------------
Once decoded, you end up with a file system image.
Once decoded, you end up with a file system image. FluxEngine supports direct
filesystem access for both kinds of disks.
### 120kB disks

View File

@@ -33,29 +33,28 @@ Reading 1541 disks
Just do:
```
fluxengine read commodore1541 -o commodore1541.d64
fluxengine read commodore -o commodore.d64
```
You should end up with an `commodore1541.d64` file which is 174848 bytes long.
You should end up with an `commodore.d64` file which is 174848 bytes long.
You can load this straight into a Commodore 64 emulator such as
[VICE](http://vice-emu.sourceforge.net/).
If you have a 40-track disk, add `--40`.
If you have a 40-track disk, add `--196`.
**Big warning!** Commodore 64 disk images are complicated due to the way the
tracks are different sizes and the odd sector size, so you need the special D64
or LDBS output formats to represent them sensibly. Don't use IMG unless you
know what you're doing.
or LDBS output formats to represent them sensibly.
Writing 1541 disks
------------------
Just do:
```
fluxengine write commodore1541 -i file.d64
fluxengine write commodore -i file.d64
```
If you have a 40-track disk, add `--40`.
If you have a 40-track disk, add `--196`.
Note that only standard Commodore 64 BAM file systems can be written this way,
as the disk ID in the BAM has to be copied to every sector on the disk.

View File

@@ -38,15 +38,16 @@ Reading disks
Just do:
fluxengine read `<format>`
fluxengine read ibm `<format>`
...and you'll end up with a `<format>.img` file. This should work on most PC
disks (including FM 360kB disks, 3.5" 1440kB disks, 5.25" 1200kB disks, etc.)
The size of the disk image will vary depending on the format.
...and you'll end up with an `ibm.img` file. You'll need to specify which
format to use; this can be one of `--160`, `--180`, `--320`, `--360`, `--720`,
`--1200`, `--1232` or `--1400` depending. The size of the disk image will vary
depending on the format.
The common PC formats are `ibm720` and `ibm1440`, but there are _many_ others,
The common PC formats are `--720` and `--1440`, but there are _many_ others,
and there's too many configuration options to usefully list. Use `fluxengine
write` to list all formats, and try `fluxengine write ibm1440 --config` to see
write` to list all formats, and try `fluxengine write ibm --1440 --config` to see
a sample configuration.
Configuration options you'll want include:
@@ -84,16 +85,13 @@ makes things slightly awkward. Preconfigured profiles are available.
The syntax is:
fluxengine write <format> -i input.img <options>
fluxengine write ibm <format> -i input.img <options>
The common PC formats are `ibm720` and `ibm1440`, but there are _many_ others,
and there's too many configuration options to usefully list. Use `fluxengine
write` to list all formats, and try `fluxengine write ibm1440 --config` to see
a sample configuration.
See above for the formats.
Some image formats, such as DIM, specify the image format, For these you can
specify the `ibm` format and FluxEngine will automatically determine the
correct format to use.
specify the `--auto` format (which is the default) and FluxEngine will
automatically determine the correct format to use.
Mixed-format disks
------------------
@@ -131,11 +129,7 @@ drives, feature "tri-mode" support which in addition to normal 300rpm modes,
can change their speed to read and write 360rpm DD and HD disks.
Neither the FluxEngine or Greaseweazle hardware can currently command a
tri-mode drive to spin at 360rpm, however an older 360rpm-only drive will work
to read these formats.
tri-mode drive to spin at 360rpm. However on both devices the FluxEngine
software is capable of both reading and writing 300rpm disks at 360rpm and vice
versa, so it shouldn't matter.
Alternately, the FluxEngine software can rescale the flux pulses to enable
reading and writing these formats with a plain 300rpm drive. To do this,
specify the following two additional options:
--flux_source.rescale=1.2 --flux_sink.rescale=1.2

View File

@@ -37,20 +37,19 @@ Reading discs
Just do:
```
fluxengine read <format> -o mac.dsk
fluxengine read mac <format> -o mac.dsk
```
...where `<format>` can be `mac400` or `mac800`.
...where `<format>` can be `--400` or `--800`.
You should end up with a `mac.dsk` file containing a raw sector image
(equivalent to `.img`).
**Big warning!** Mac disk images are complicated due to the way the tracks are
different sizes and the odd sector size. What you get above is a triangular
disk image, which contains all the 512-byte user data areas concatenated
together in filesystem order. It does not contain the twelve bytes of metadata.
If you want these as well, specify that you want 524 byte sectors with
`--output.image.img.trackdata.sector_size=512`. The metadata will follow the
The Mac disk format contains an extra twelve bytes of data per sector which can
be used for filesystem metadata. In practice, this was never used by anyone,
and so the default is to omit these. If you want them, specify that you want
524 byte sectors with `--layout.layoutdata.sector_size=524`. The metadata will
follow the
512 bytes of user data.
FluxEngine also supports DiskCopy 4.2 disk images, which may be a better option
@@ -64,16 +63,13 @@ Writing discs
Just do:
```
fluxengine write <format> -i mac.dsk
fluxengine write mac <format> -i mac.dsk
```
...where `<format>` can be `mac400` or `mac800`.
...where `<format>` can be `400` or `800`.
It'll read the image file and write it out.
The same warning as above applies --- you can use normal `.dsk` files but it's
problematic. Consider using DiskCopy 4.2 files instead.
Useful references
-----------------

View File

@@ -23,10 +23,10 @@ Reading disks
Based on your floppy drive, just do one of:
```
fluxengine read micropolis143 # single-sided Mod I
fluxengine read micropolis287 # double-sided Mod I
fluxengine read micropolis315 # single-sided Mod II
fluxengine read micropolis630 # double-sided Mod II
fluxengine read micropolis --143 # single-sided Mod I
fluxengine read micropolis --287 # double-sided Mod I
fluxengine read micropolis --315 # single-sided Mod II
fluxengine read micropolis --630 # double-sided Mod II
```
You should end up with a `micropolis.img` of the corresponding size. The image
@@ -45,10 +45,10 @@ It's also possible to output to VGI, which retains OS-specific "user data" and
machine-specific ECC. Add `--vgi` to the command line after the chosen
Micropolis profile:
```
fluxengine read micropolis143 --vgi # single-sided Mod I
fluxengine read micropolis287 --vgi # double-sided Mod I
fluxengine read micropolis315 --vgi # single-sided Mod II
fluxengine read micropolis630 --vgi # double-sided Mod II
fluxengine read micropolis --143 --vgi # single-sided Mod I
fluxengine read micropolis --287 --vgi # double-sided Mod I
fluxengine read micropolis --315 --vgi # single-sided Mod II
fluxengine read micropolis --630 --vgi # double-sided Mod II
```
You should end up with a `micropolis.vgi` instead. The format is well-defined
@@ -72,15 +72,15 @@ Writing disks
Just do one of:
```
fluxengine write micropolis143 # single-sided Mod I
fluxengine write micropolis287 # double-sided Mod I
fluxengine write micropolis315 # single-sided Mod II
fluxengine write micropolis630 # double-sided Mod II
fluxengine write micropolis --143 # single-sided Mod I
fluxengine write micropolis --287 # double-sided Mod I
fluxengine write micropolis --315 # single-sided Mod II
fluxengine write micropolis --630 # double-sided Mod II
fluxengine write micropolis143 --vgi # single-sided Mod I
fluxengine write micropolis287 --vgi # double-sided Mod I
fluxengine write micropolis315 --vgi # single-sided Mod II
fluxengine write micropolis630 --vgi # double-sided Mod II
fluxengine write micropolis --143 --vgi # single-sided Mod I
fluxengine write micropolis --287 --vgi # double-sided Mod I
fluxengine write micropolis --315 --vgi # single-sided Mod II
fluxengine write micropolis --630 --vgi # double-sided Mod II
```
Useful references

View File

@@ -9,7 +9,7 @@ clone of the PDP-11. The MX board was an early floppy drive controller board
for it.
<div style="text-align: center">
<a href="http://www.leningrad.su/museum/show_big.php?n=1006"><img src="dvk3m.jpg" style="max-width: 60%" alt="A Durango F85, held precariously"></a>
<a href="http://www.leningrad.su/museum/show_big.php?n=1006"><img src="dvk3m.jpg" style="max-width: 60%" alt="A DVK computer"></a>
</div>
The MX format is interesting in that it has to be read a track at a time. The
@@ -42,15 +42,17 @@ Reading discs
-------------
```
fluxengine read mx440
fluxengine read mx
```
You should end up with an `mx.img` which will vary in length depending on the format. The default is double-sided 80-track. For the other formats, use:
You should end up with an `mx.img` which will vary in length depending on the
format. The default is double-sided 80-track. For the other formats, add one of
the following options:
* single-sided 40-track: `mx110`
* double-sided 40-track: `mx220_ds`
* single-sided 80-track: `mx220_ss`
* double-sided 80-track: `mx440`
* single-sided 40-track: `--110`
* double-sided 40-track: `--220ds`
* single-sided 80-track: `--220ss`
* double-sided 80-track: `--440`
Useful references

View File

@@ -21,12 +21,10 @@ equivalent to .img images.
Reading disks
-------------
You must use a 48-TPI (40-track) 300RPM 5.25" floppy drive.
To read a double-sided North Star floppy, run:
```
fluxengine read <format>
fluxengine read northstar <format>
```
...where `<format>` is one of the formats listed below.
@@ -37,13 +35,10 @@ disk type.
Writing disks
-------------
You must use a 48-TPI (40-track) 300RPM 5.25" floppy drive and make
sure that the drive's spindle speed is adjusted to exactly 300RPM.
To write a double-sided North Star floppy, run:
```
fluxengine write <format> -i image_to_write.nsi
fluxengine write northstar <format> -i image_to_write.nsi
```
...where `<format>` is one of the formats listed below.
@@ -55,9 +50,9 @@ The following formats are supported:
| Format name | Disk Type | File Size (bytes) |
| -------------- | ----------------------------------- | ----------------- |
| `northstar87` | Single-Sided, Single-Density (SSSD) | 89,600 |
| `northstar175` | Single-Sided, Double-Density (SSDD) | 179,200 |
| `northstar350` | Double-Sided, Double-Density (DSDD) | 358,400 |
| `--87` | Single-Sided, Single-Density (SSSD) | 89,600 |
| `--175` | Single-Sided, Double-Density (SSDD) | 179,200 |
| `--350` | Double-Sided, Double-Density (DSDD) | 358,400 |
Useful references
-----------------

View File

@@ -8,7 +8,7 @@ sector GCR disks, with a variable-speed drive and a varying number of sectors
per track --- from 19 to 12. Disks can be double-sided, meaning that they can
store 1224kB per disk, which was almost unheard of back then. Because the way
that the tracks on head 1 are offset from head 0 (this happens with all disks),
the speed zone allocation on head 1 differ from head 0...
the speed zone allocation on head 1 differs from head 0...
| Zone | Head 0 tracks | Head 1 tracks | Sectors | Original period (ms) |
|:----:|:-------------:|:-------------:|:-------:|:--------------------:|
@@ -40,18 +40,12 @@ Reading discs
Just do:
```
fluxengine read <format>
fluxengine read victor9k <format>
```
...where `<format>` can be `victor9k_ss` or `victor9k_ds`.
For `victor9k_ss` you should end up with an `victor9k.img` which is 627200 bytes long.
For `victor9k_ds` you should end up with an `victor9k.img` which is 1224192 bytes long.
**Big warning!** The image is triangular, where each track occupies a different
amount of space. Victor disk images are complicated due to the way the tracks
are different sizes and the odd sector size.
...where `<format>` can be `--612` for a single-sided disk or `--1224` for a
double-sided disk.
Writing discs
-------------
@@ -59,11 +53,10 @@ Writing discs
Just do:
```
fluxengine read victor9k_ss -i victor9k.img
fluxengine write victor9k <format> -i victor9k.img
```
**Big warning!** This uses the same triangular disk image that reading uses.
`<format>` is as above.
Useful references
-----------------

View File

@@ -77,6 +77,7 @@ 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 \

View File

@@ -12,7 +12,7 @@ import "lib/drive.proto";
import "lib/common.proto";
import "lib/layout.proto";
// NEXT_TAG: 21
// NEXT_TAG: 23
message ConfigProto
{
optional string comment = 8;
@@ -39,18 +39,27 @@ message ConfigProto
optional FilesystemProto filesystem = 17;
repeated OptionProto option = 20;
repeated OptionGroupProto option_group = 22;
}
// NEXT_TAG: 7
message OptionProto
{
optional string name = 1 [ (help) = "option name" ];
optional string comment = 2 [ (help) = "help text for option" ];
optional string message = 3
[ (help) = "message to display when option is in use" ];
optional string exclusivity_group = 5 [
(help) =
"options with the same group cannot be selected at the same time"
];
optional bool set_by_default = 6 [
(help) = "this option is applied by default",
default = false
];
optional ConfigProto config = 4
[ (help) = "option data", (recurse) = false ];
}
message OptionGroupProto
{
optional string comment = 1 [ (help) = "help text for option group" ];
repeated OptionProto option = 2;
}

View File

@@ -1,364 +1,424 @@
#include "globals.h"
#include "flags.h"
#include "proto.h"
#include "utils.h"
#include "logger.h"
#include "fmt/format.h"
#include <google/protobuf/text_format.h>
#include <regex>
#include <fstream>
static FlagGroup* currentFlagGroup;
static std::vector<Flag*> all_flags;
static std::map<const std::string, Flag*> flags_by_name;
static void doHelp();
static void doShowConfig();
static void doDoc();
static FlagGroup helpGroup;
static ActionFlag helpFlag = ActionFlag({"--help"}, "Shows the help.", doHelp);
static ActionFlag showConfigFlag = ActionFlag({"--config", "-C"},
"Shows the currently set configuration and halts.",
doShowConfig);
static ActionFlag docFlag = ActionFlag(
{"--doc"}, "Shows the available configuration options and halts.", doDoc);
FlagGroup::FlagGroup()
{
currentFlagGroup = this;
}
FlagGroup::FlagGroup(std::initializer_list<FlagGroup*> groups): _groups(groups)
{
currentFlagGroup = this;
}
void FlagGroup::addFlag(Flag* flag)
{
_flags.push_back(flag);
}
static bool setFallbackFlag(
const std::string& key, const std::string& value, bool uses_that)
{
if (beginsWith(key, "--"))
{
std::string path = key.substr(2);
if (key.find('.') != std::string::npos)
{
ProtoField protoField = resolveProtoPath(&config, path);
setProtoFieldFromString(protoField, value);
return uses_that;
}
else
{
if (FlagGroup::applyOption(path))
return false;
}
}
Error() << "unrecognised flag; try --help";
}
bool FlagGroup::applyOption(const std::string& option)
{
for (const auto& configs : config.option())
{
if (option == configs.name())
{
if (configs.config().option_size() > 0)
Error() << fmt::format(
"option '{}' has an option inside it, which isn't "
"allowed",
option);
if (configs.config().include_size() > 0)
Error() << fmt::format(
"option '{}' is trying to include something, which "
"isn't allowed",
option);
Logger() << fmt::format("OPTION: {}", configs.message());
config.MergeFrom(configs.config());
return true;
}
}
return false;
}
std::vector<std::string> FlagGroup::parseFlagsWithFilenames(int argc,
const char* argv[],
std::function<bool(const std::string&)> callback)
{
if (_initialised)
throw std::runtime_error("called parseFlags() twice");
/* Recursively accumulate a list of all flags. */
all_flags.clear();
flags_by_name.clear();
std::function<void(FlagGroup*)> recurse;
recurse = [&](FlagGroup* group)
{
if (group->_initialised)
return;
for (FlagGroup* subgroup : group->_groups)
recurse(subgroup);
for (Flag* flag : group->_flags)
{
for (const auto& name : flag->names())
{
if (flags_by_name.find(name) != flags_by_name.end())
Error() << "two flags use the name '" << name << "'";
flags_by_name[name] = flag;
}
all_flags.push_back(flag);
}
group->_initialised = true;
};
recurse(this);
recurse(&helpGroup);
/* Now actually parse them. */
std::vector<std::string> filenames;
int index = 1;
while (index < argc)
{
std::string thisarg = argv[index];
std::string thatarg = (index < (argc - 1)) ? argv[index + 1] : "";
std::string key;
std::string value;
bool usesthat = false;
if (thisarg.size() == 0)
{
/* Ignore this argument. */
}
else if (thisarg[0] != '-')
{
/* This is a filename. Pass it to the callback, and if not consumed
* queue it. */
if (!callback(thisarg))
filenames.push_back(thisarg);
}
else
{
/* This is a flag. */
if ((thisarg.size() > 1) && (thisarg[1] == '-'))
{
/* Long option. */
auto equals = thisarg.rfind('=');
if (equals != std::string::npos)
{
key = thisarg.substr(0, equals);
value = thisarg.substr(equals + 1);
}
else
{
key = thisarg;
value = thatarg;
usesthat = true;
}
}
else
{
/* Short option. */
if (thisarg.size() > 2)
{
key = thisarg.substr(0, 2);
value = thisarg.substr(2);
}
else
{
key = thisarg;
value = thatarg;
usesthat = true;
}
}
auto flag = flags_by_name.find(key);
if (flag == flags_by_name.end())
{
if (setFallbackFlag(key, value, usesthat))
index++;
}
else
{
flag->second->set(value);
if (usesthat && flag->second->hasArgument())
index++;
}
}
index++;
}
return filenames;
}
void FlagGroup::parseFlags(int argc,
const char* argv[],
std::function<bool(const std::string&)> callback)
{
auto filenames = parseFlagsWithFilenames(argc, argv, callback);
if (!filenames.empty())
Error() << "non-option parameter " << *filenames.begin()
<< " seen (try --help)";
}
void FlagGroup::parseFlagsWithConfigFiles(int argc,
const char* argv[],
const std::map<std::string, std::string>& configFiles)
{
parseFlags(argc,
argv,
[&](const auto& filename)
{
parseConfigFile(filename, configFiles);
return true;
});
}
ConfigProto FlagGroup::parseSingleConfigFile(const std::string& filename,
const std::map<std::string, std::string>& configFiles)
{
const auto& it = configFiles.find(filename);
if (it != configFiles.end())
{
ConfigProto config;
if (!config.ParseFromString(it->second))
Error() << "couldn't load built-in config proto";
return config;
}
else
{
std::ifstream f(filename, std::ios::out);
if (f.fail())
Error() << fmt::format(
"Cannot open '{}': {}", filename, strerror(errno));
std::ostringstream ss;
ss << f.rdbuf();
ConfigProto config;
if (!google::protobuf::TextFormat::MergeFromString(ss.str(), &config))
Error() << "couldn't load external config proto";
return config;
}
}
void FlagGroup::parseConfigFile(const std::string& filename,
const std::map<std::string, std::string>& configFiles)
{
auto newConfig = parseSingleConfigFile(filename, configFiles);
/* The includes need to be merged _first_. */
for (const auto& include : newConfig.include())
{
auto included = parseSingleConfigFile(include, configFiles);
if (included.include_size() != 0)
Error() << "only one level of config file includes are supported "
"(so far, if you need this, complain)";
config.MergeFrom(included);
}
config.MergeFrom(newConfig);
}
void FlagGroup::checkInitialised() const
{
if (!_initialised)
throw std::runtime_error("Attempt to access uninitialised flag");
}
Flag::Flag(const std::vector<std::string>& names, const std::string helptext):
_group(*currentFlagGroup),
_names(names),
_helptext(helptext)
{
if (!currentFlagGroup)
Error() << "no flag group defined for " << *names.begin();
_group.addFlag(this);
}
void BoolFlag::set(const std::string& value)
{
if ((value == "true") || (value == "y"))
_value = true;
else if ((value == "false") || (value == "n"))
_value = false;
else
Error() << "can't parse '" << value << "'; try 'true' or 'false'";
_callback(_value);
_isSet = true;
}
const std::string HexIntFlag::defaultValueAsString() const
{
return fmt::format("0x{:x}", _defaultValue);
}
static void doHelp()
{
std::cout << "FluxEngine options:\n";
std::cout
<< "Note: options are processed left to right and order matters!\n";
for (auto flag : all_flags)
{
std::cout << " ";
bool firstname = true;
for (auto name : flag->names())
{
if (!firstname)
std::cout << ", ";
std::cout << name;
firstname = false;
}
if (flag->hasArgument())
std::cout << " <default: \"" << flag->defaultValueAsString()
<< "\">";
std::cout << ": " << flag->helptext() << std::endl;
}
exit(0);
}
static void doShowConfig()
{
std::string s;
google::protobuf::TextFormat::PrintToString(config, &s);
std::cout << s << '\n';
exit(0);
}
static void doDoc()
{
const auto fields = findAllProtoFields(&config);
for (const auto field : fields)
{
const std::string& path = field.first;
const google::protobuf::FieldDescriptor* f = field.second;
if (f->type() == google::protobuf::FieldDescriptor::TYPE_MESSAGE)
continue;
std::string helpText = f->options().GetExtension(help);
std::cout << fmt::format("{}: {}\n", path, helpText);
}
exit(0);
}
#include "globals.h"
#include "flags.h"
#include "proto.h"
#include "utils.h"
#include "logger.h"
#include "fmt/format.h"
#include <google/protobuf/text_format.h>
#include <regex>
#include <fstream>
static FlagGroup* currentFlagGroup;
static std::vector<Flag*> all_flags;
static std::map<const std::string, Flag*> flags_by_name;
static void doHelp();
static void doShowConfig();
static void doDoc();
static FlagGroup helpGroup;
static ActionFlag helpFlag = ActionFlag({"--help"}, "Shows the help.", doHelp);
static ActionFlag showConfigFlag = ActionFlag({"--config", "-C"},
"Shows the currently set configuration and halts.",
doShowConfig);
static ActionFlag docFlag = ActionFlag(
{"--doc"}, "Shows the available configuration options and halts.", doDoc);
FlagGroup::FlagGroup()
{
currentFlagGroup = this;
}
FlagGroup::FlagGroup(std::initializer_list<FlagGroup*> groups): _groups(groups)
{
currentFlagGroup = this;
}
void FlagGroup::addFlag(Flag* flag)
{
_flags.push_back(flag);
}
void FlagGroup::applyOption(const OptionProto& option)
{
if (option.config().option_size() > 0)
Error() << fmt::format(
"option '{}' has an option inside it, which isn't "
"allowed",
option.name());
if (option.config().option_group_size() > 0)
Error() << fmt::format(
"option '{}' has an option group inside it, which isn't "
"allowed",
option.name());
if (option.config().include_size() > 0)
Error() << fmt::format(
"option '{}' is trying to include something, which "
"isn't allowed",
option.name());
Logger() << fmt::format("OPTION: {}",
option.has_message() ? option.message() : option.comment());
config.MergeFrom(option.config());
}
bool FlagGroup::applyOption(const std::string& optionName)
{
auto searchOptionList = [&](auto& optionList)
{
for (const auto& option : optionList)
{
if (optionName == option.name())
{
applyOption(option);
return true;
}
}
return false;
};
if (searchOptionList(config.option()))
return true;
for (const auto& optionGroup : config.option_group())
{
if (searchOptionList(optionGroup.option()))
return true;
}
return false;
}
std::vector<std::string> FlagGroup::parseFlagsWithFilenames(int argc,
const char* argv[],
std::function<bool(const std::string&)> callback)
{
if (_initialised)
throw std::runtime_error("called parseFlags() twice");
/* Recursively accumulate a list of all flags. */
all_flags.clear();
flags_by_name.clear();
std::function<void(FlagGroup*)> recurse;
recurse = [&](FlagGroup* group)
{
if (group->_initialised)
return;
for (FlagGroup* subgroup : group->_groups)
recurse(subgroup);
for (Flag* flag : group->_flags)
{
for (const auto& name : flag->names())
{
if (flags_by_name.find(name) != flags_by_name.end())
Error() << "two flags use the name '" << name << "'";
flags_by_name[name] = flag;
}
all_flags.push_back(flag);
}
group->_initialised = true;
};
recurse(this);
recurse(&helpGroup);
/* Now actually parse them. */
std::set<std::string> options;
std::vector<std::pair<std::string, std::string>> overrides;
std::vector<std::string> filenames;
int index = 1;
while (index < argc)
{
std::string thisarg = argv[index];
std::string thatarg = (index < (argc - 1)) ? argv[index + 1] : "";
std::string key;
std::string value;
bool usesthat = false;
if (thisarg.size() == 0)
{
/* Ignore this argument. */
}
else if (thisarg[0] != '-')
{
/* This is a filename. Pass it to the callback, and if not consumed
* queue it. */
if (!callback(thisarg))
filenames.push_back(thisarg);
}
else
{
/* This is a flag. */
if ((thisarg.size() > 1) && (thisarg[1] == '-'))
{
/* Long option. */
auto equals = thisarg.rfind('=');
if (equals != std::string::npos)
{
key = thisarg.substr(0, equals);
value = thisarg.substr(equals + 1);
}
else
{
key = thisarg;
value = thatarg;
usesthat = true;
}
}
else
{
/* Short option. */
if (thisarg.size() > 2)
{
key = thisarg.substr(0, 2);
value = thisarg.substr(2);
}
else
{
key = thisarg;
value = thatarg;
usesthat = true;
}
}
auto flag = flags_by_name.find(key);
if (flag == flags_by_name.end())
{
if (beginsWith(key, "--"))
{
std::string path = key.substr(2);
if (key.find('.') != std::string::npos)
{
overrides.push_back(std::make_pair(path, value));
index += usesthat;
}
else
options.insert(path);
}
else
Error() << "unrecognised flag; try --help";
}
else
{
flag->second->set(value);
if (usesthat && flag->second->hasArgument())
index++;
}
}
index++;
}
/* Apply any default options in groups. */
for (auto& group : config.option_group())
{
const OptionProto* defaultOption = &*group.option().begin();
bool isSet = false;
for (auto& option : group.option())
{
if (options.find(option.name()) != options.end())
{
defaultOption = &option;
options.erase(option.name());
}
}
FlagGroup::applyOption(*defaultOption);
}
/* Next, any standalone options. */
for (auto& option : config.option())
{
if (options.find(option.name()) != options.end())
{
FlagGroup::applyOption(option);
options.erase(option.name());
}
}
if (!options.empty())
Error() << fmt::format(
"--{} is not a known flag or format option; try --help",
*options.begin());
/* Now apply any value overrides (in order). */
for (auto [k, v] : overrides)
{
ProtoField protoField = resolveProtoPath(&config, k);
setProtoFieldFromString(protoField, v);
}
return filenames;
}
void FlagGroup::parseFlags(int argc,
const char* argv[],
std::function<bool(const std::string&)> callback)
{
auto filenames = parseFlagsWithFilenames(argc, argv, callback);
if (!filenames.empty())
Error() << "non-option parameter " << *filenames.begin()
<< " seen (try --help)";
}
void FlagGroup::parseFlagsWithConfigFiles(int argc,
const char* argv[],
const std::map<std::string, std::string>& configFiles)
{
parseFlags(argc,
argv,
[&](const auto& filename)
{
parseConfigFile(filename, configFiles);
return true;
});
}
ConfigProto FlagGroup::parseSingleConfigFile(const std::string& filename,
const std::map<std::string, std::string>& configFiles)
{
const auto& it = configFiles.find(filename);
if (it != configFiles.end())
{
ConfigProto config;
if (!config.ParseFromString(it->second))
Error() << "couldn't load built-in config proto";
return config;
}
else
{
std::ifstream f(filename, std::ios::out);
if (f.fail())
Error() << fmt::format(
"Cannot open '{}': {}", filename, strerror(errno));
std::ostringstream ss;
ss << f.rdbuf();
ConfigProto config;
if (!google::protobuf::TextFormat::MergeFromString(ss.str(), &config))
Error() << "couldn't load external config proto";
return config;
}
}
void FlagGroup::parseConfigFile(const std::string& filename,
const std::map<std::string, std::string>& configFiles)
{
auto newConfig = parseSingleConfigFile(filename, configFiles);
/* The includes need to be merged _first_. */
for (const auto& include : newConfig.include())
{
auto included = parseSingleConfigFile(include, configFiles);
if (included.include_size() != 0)
Error() << "only one level of config file includes are supported "
"(so far, if you need this, complain)";
config.MergeFrom(included);
}
config.MergeFrom(newConfig);
}
void FlagGroup::checkInitialised() const
{
if (!_initialised)
throw std::runtime_error("Attempt to access uninitialised flag");
}
Flag::Flag(const std::vector<std::string>& names, const std::string helptext):
_group(*currentFlagGroup),
_names(names),
_helptext(helptext)
{
if (!currentFlagGroup)
Error() << "no flag group defined for " << *names.begin();
_group.addFlag(this);
}
void BoolFlag::set(const std::string& value)
{
if ((value == "true") || (value == "y"))
_value = true;
else if ((value == "false") || (value == "n"))
_value = false;
else
Error() << "can't parse '" << value << "'; try 'true' or 'false'";
_callback(_value);
_isSet = true;
}
const std::string HexIntFlag::defaultValueAsString() const
{
return fmt::format("0x{:x}", _defaultValue);
}
static void doHelp()
{
std::cout << "FluxEngine options:\n";
std::cout
<< "Note: options are processed left to right and order matters!\n";
for (auto flag : all_flags)
{
std::cout << " ";
bool firstname = true;
for (auto name : flag->names())
{
if (!firstname)
std::cout << ", ";
std::cout << name;
firstname = false;
}
if (flag->hasArgument())
std::cout << " <default: \"" << flag->defaultValueAsString()
<< "\">";
std::cout << ": " << flag->helptext() << std::endl;
}
exit(0);
}
static void doShowConfig()
{
std::string s;
google::protobuf::TextFormat::PrintToString(config, &s);
std::cout << s << '\n';
exit(0);
}
static void doDoc()
{
const auto fields = findAllProtoFields(&config);
for (const auto field : fields)
{
const std::string& path = field.first;
const google::protobuf::FieldDescriptor* f = field.second;
if (f->type() == google::protobuf::FieldDescriptor::TYPE_MESSAGE)
continue;
std::string helpText = f->options().GetExtension(help);
std::cout << fmt::format("{}: {}\n", path, helpText);
}
exit(0);
}

View File

@@ -1,327 +1,329 @@
#ifndef FLAGS_H
#define FLAGS_H
class DataSpec;
class Flag;
class ConfigProto;
class FlagGroup
{
public:
FlagGroup();
FlagGroup(std::initializer_list<FlagGroup*> groups);
public:
void parseFlags(
int argc,
const char* argv[],
std::function<bool(const std::string&)> callback =
[](const auto&)
{
return false;
});
std::vector<std::string> parseFlagsWithFilenames(
int argc,
const char* argv[],
std::function<bool(const std::string&)> callback =
[](const auto&)
{
return false;
});
void parseFlagsWithConfigFiles(int argc,
const char* argv[],
const std::map<std::string, std::string>& configFiles);
/* Load one config file (or internal config file), without expanding
* includes. */
static ConfigProto parseSingleConfigFile(const std::string& filename,
const std::map<std::string, std::string>& configFiles);
/* Load a top-level config file (or internal config file), expanding
* includes. */
static void parseConfigFile(const std::string& filename,
const std::map<std::string, std::string>& configFiles);
/* Modify the current config to engage the named option. */
static bool applyOption(const std::string& option);
void addFlag(Flag* flag);
void checkInitialised() const;
private:
bool _initialised = false;
const std::vector<FlagGroup*> _groups;
std::vector<Flag*> _flags;
};
class Flag
{
public:
Flag(const std::vector<std::string>& names, const std::string helptext);
virtual ~Flag(){};
void checkInitialised() const
{
_group.checkInitialised();
}
const std::string& name() const
{
return _names[0];
}
const std::vector<std::string> names() const
{
return _names;
}
const std::string& helptext() const
{
return _helptext;
}
virtual bool hasArgument() const = 0;
virtual const std::string defaultValueAsString() const = 0;
virtual void set(const std::string& value) = 0;
private:
FlagGroup& _group;
const std::vector<std::string> _names;
const std::string _helptext;
};
class ActionFlag : Flag
{
public:
ActionFlag(const std::vector<std::string>& names,
const std::string helptext,
std::function<void(void)> callback):
Flag(names, helptext),
_callback(callback)
{
}
bool hasArgument() const
{
return false;
}
const std::string defaultValueAsString() const
{
return "";
}
void set(const std::string& value)
{
_callback();
}
private:
const std::function<void(void)> _callback;
};
class SettableFlag : public Flag
{
public:
SettableFlag(
const std::vector<std::string>& names, const std::string helptext):
Flag(names, helptext)
{
}
operator bool() const
{
checkInitialised();
return _value;
}
bool hasArgument() const
{
return false;
}
const std::string defaultValueAsString() const
{
return "false";
}
void set(const std::string& value)
{
_value = true;
}
private:
bool _value = false;
};
template <typename T>
class ValueFlag : public Flag
{
public:
ValueFlag(
const std::vector<std::string>& names,
const std::string helptext,
const T defaultValue,
std::function<void(const T&)> callback =
[](const T&)
{
}):
Flag(names, helptext),
_defaultValue(defaultValue),
_value(defaultValue),
_callback(callback)
{
}
const T& get() const
{
checkInitialised();
return _value;
}
operator const T&() const
{
return get();
}
bool isSet() const
{
return _isSet;
}
void setDefaultValue(T value)
{
_value = _defaultValue = value;
}
bool hasArgument() const
{
return true;
}
protected:
T _defaultValue;
T _value;
bool _isSet = false;
std::function<void(const T&)> _callback;
};
class StringFlag : public ValueFlag<std::string>
{
public:
StringFlag(
const std::vector<std::string>& names,
const std::string helptext,
const std::string defaultValue = "",
std::function<void(const std::string&)> callback =
[](const std::string&)
{
}):
ValueFlag(names, helptext, defaultValue, callback)
{
}
const std::string defaultValueAsString() const
{
return _defaultValue;
}
void set(const std::string& value)
{
_value = value;
_callback(_value);
_isSet = true;
}
};
class IntFlag : public ValueFlag<int>
{
public:
IntFlag(
const std::vector<std::string>& names,
const std::string helptext,
int defaultValue = 0,
std::function<void(const int&)> callback =
[](const int&)
{
}):
ValueFlag(names, helptext, defaultValue, callback)
{
}
const std::string defaultValueAsString() const
{
return std::to_string(_defaultValue);
}
void set(const std::string& value)
{
_value = std::stoi(value);
_callback(_value);
_isSet = true;
}
};
class HexIntFlag : public IntFlag
{
public:
HexIntFlag(
const std::vector<std::string>& names,
const std::string helptext,
int defaultValue = 0,
std::function<void(const int&)> callback =
[](const int&)
{
}):
IntFlag(names, helptext, defaultValue, callback)
{
}
const std::string defaultValueAsString() const;
};
class DoubleFlag : public ValueFlag<double>
{
public:
DoubleFlag(
const std::vector<std::string>& names,
const std::string helptext,
double defaultValue = 1.0,
std::function<void(const double&)> callback =
[](const double&)
{
}):
ValueFlag(names, helptext, defaultValue, callback)
{
}
const std::string defaultValueAsString() const
{
return std::to_string(_defaultValue);
}
void set(const std::string& value)
{
_value = std::stod(value);
_callback(_value);
_isSet = true;
}
};
class BoolFlag : public ValueFlag<bool>
{
public:
BoolFlag(
const std::vector<std::string>& names,
const std::string helptext,
bool defaultValue = false,
std::function<void(const bool&)> callback =
[](const bool&)
{
}):
ValueFlag(names, helptext, defaultValue, callback)
{
}
const std::string defaultValueAsString() const
{
return _defaultValue ? "true" : "false";
}
void set(const std::string& value);
};
#endif
#ifndef FLAGS_H
#define FLAGS_H
class DataSpec;
class Flag;
class ConfigProto;
class OptionProto;
class FlagGroup
{
public:
FlagGroup();
FlagGroup(std::initializer_list<FlagGroup*> groups);
public:
void parseFlags(
int argc,
const char* argv[],
std::function<bool(const std::string&)> callback =
[](const auto&)
{
return false;
});
std::vector<std::string> parseFlagsWithFilenames(
int argc,
const char* argv[],
std::function<bool(const std::string&)> callback =
[](const auto&)
{
return false;
});
void parseFlagsWithConfigFiles(int argc,
const char* argv[],
const std::map<std::string, std::string>& configFiles);
/* Load one config file (or internal config file), without expanding
* includes. */
static ConfigProto parseSingleConfigFile(const std::string& filename,
const std::map<std::string, std::string>& configFiles);
/* Load a top-level config file (or internal config file), expanding
* includes. */
static void parseConfigFile(const std::string& filename,
const std::map<std::string, std::string>& configFiles);
/* Modify the current config to engage the named option. */
static void applyOption(const OptionProto& option);
static bool applyOption(const std::string& option);
void addFlag(Flag* flag);
void checkInitialised() const;
private:
bool _initialised = false;
const std::vector<FlagGroup*> _groups;
std::vector<Flag*> _flags;
};
class Flag
{
public:
Flag(const std::vector<std::string>& names, const std::string helptext);
virtual ~Flag(){};
void checkInitialised() const
{
_group.checkInitialised();
}
const std::string& name() const
{
return _names[0];
}
const std::vector<std::string> names() const
{
return _names;
}
const std::string& helptext() const
{
return _helptext;
}
virtual bool hasArgument() const = 0;
virtual const std::string defaultValueAsString() const = 0;
virtual void set(const std::string& value) = 0;
private:
FlagGroup& _group;
const std::vector<std::string> _names;
const std::string _helptext;
};
class ActionFlag : Flag
{
public:
ActionFlag(const std::vector<std::string>& names,
const std::string helptext,
std::function<void(void)> callback):
Flag(names, helptext),
_callback(callback)
{
}
bool hasArgument() const
{
return false;
}
const std::string defaultValueAsString() const
{
return "";
}
void set(const std::string& value)
{
_callback();
}
private:
const std::function<void(void)> _callback;
};
class SettableFlag : public Flag
{
public:
SettableFlag(
const std::vector<std::string>& names, const std::string helptext):
Flag(names, helptext)
{
}
operator bool() const
{
checkInitialised();
return _value;
}
bool hasArgument() const
{
return false;
}
const std::string defaultValueAsString() const
{
return "false";
}
void set(const std::string& value)
{
_value = true;
}
private:
bool _value = false;
};
template <typename T>
class ValueFlag : public Flag
{
public:
ValueFlag(
const std::vector<std::string>& names,
const std::string helptext,
const T defaultValue,
std::function<void(const T&)> callback =
[](const T&)
{
}):
Flag(names, helptext),
_defaultValue(defaultValue),
_value(defaultValue),
_callback(callback)
{
}
const T& get() const
{
checkInitialised();
return _value;
}
operator const T&() const
{
return get();
}
bool isSet() const
{
return _isSet;
}
void setDefaultValue(T value)
{
_value = _defaultValue = value;
}
bool hasArgument() const
{
return true;
}
protected:
T _defaultValue;
T _value;
bool _isSet = false;
std::function<void(const T&)> _callback;
};
class StringFlag : public ValueFlag<std::string>
{
public:
StringFlag(
const std::vector<std::string>& names,
const std::string helptext,
const std::string defaultValue = "",
std::function<void(const std::string&)> callback =
[](const std::string&)
{
}):
ValueFlag(names, helptext, defaultValue, callback)
{
}
const std::string defaultValueAsString() const
{
return _defaultValue;
}
void set(const std::string& value)
{
_value = value;
_callback(_value);
_isSet = true;
}
};
class IntFlag : public ValueFlag<int>
{
public:
IntFlag(
const std::vector<std::string>& names,
const std::string helptext,
int defaultValue = 0,
std::function<void(const int&)> callback =
[](const int&)
{
}):
ValueFlag(names, helptext, defaultValue, callback)
{
}
const std::string defaultValueAsString() const
{
return std::to_string(_defaultValue);
}
void set(const std::string& value)
{
_value = std::stoi(value);
_callback(_value);
_isSet = true;
}
};
class HexIntFlag : public IntFlag
{
public:
HexIntFlag(
const std::vector<std::string>& names,
const std::string helptext,
int defaultValue = 0,
std::function<void(const int&)> callback =
[](const int&)
{
}):
IntFlag(names, helptext, defaultValue, callback)
{
}
const std::string defaultValueAsString() const;
};
class DoubleFlag : public ValueFlag<double>
{
public:
DoubleFlag(
const std::vector<std::string>& names,
const std::string helptext,
double defaultValue = 1.0,
std::function<void(const double&)> callback =
[](const double&)
{
}):
ValueFlag(names, helptext, defaultValue, callback)
{
}
const std::string defaultValueAsString() const
{
return std::to_string(_defaultValue);
}
void set(const std::string& value)
{
_value = std::stod(value);
_callback(_value);
_isSet = true;
}
};
class BoolFlag : public ValueFlag<bool>
{
public:
BoolFlag(
const std::vector<std::string>& names,
const std::string helptext,
bool defaultValue = false,
std::function<void(const bool&)> callback =
[](const bool&)
{
}):
ValueFlag(names, helptext, defaultValue, callback)
{
}
const std::string defaultValueAsString() const
{
return _defaultValue ? "true" : "false";
}
void set(const std::string& value);
};
#endif

200
lib/vfs/lif.cc Normal file
View File

@@ -0,0 +1,200 @@
#include "lib/globals.h"
#include "lib/vfs/vfs.h"
#include "lib/config.pb.h"
#include "lib/utils.h"
#include <fmt/format.h>
/* See https://www.hp9845.net/9845/projects/hpdir/#lif_filesystem for
* a description. */
static void trimZeros(std::string s)
{
s.erase(std::remove(s.begin(), s.end(), 0), s.end());
}
class LifFilesystem : public Filesystem
{
class LifDirent : public Dirent
{
public:
LifDirent(const LifProto& config, Bytes& bytes)
{
file_type = TYPE_FILE;
ByteReader br(bytes);
filename = trimWhitespace(br.read(10));
uint16_t type = br.read_be16();
location = br.read_be32();
length = br.read_be32() * config.block_size();
mode = fmt::format("{:04x}", type);
path = {filename};
attributes[Filesystem::FILENAME] = filename;
attributes[Filesystem::LENGTH] = std::to_string(length);
attributes[Filesystem::FILE_TYPE] = "file";
attributes[Filesystem::MODE] = mode;
}
public:
uint32_t location;
};
public:
LifFilesystem(
const LifProto& config, std::shared_ptr<SectorInterface> sectors):
Filesystem(sectors),
_config(config)
{
}
uint32_t capabilities() const
{
return OP_GETFSDATA | OP_LIST | OP_GETFILE | OP_GETDIRENT;
}
FilesystemStatus check() override
{
return FS_OK;
}
std::map<std::string, std::string> getMetadata() override
{
mount();
std::map<std::string, std::string> attributes;
attributes[VOLUME_NAME] = _volumeLabel;
attributes[TOTAL_BLOCKS] = std::to_string(_totalBlocks);
attributes[USED_BLOCKS] = std::to_string(_usedBlocks);
attributes[BLOCK_SIZE] = std::to_string(_config.block_size());
attributes["lif.directory_block"] = std::to_string(_directoryBlock);
attributes["lif.directory_size"] = std::to_string(_directorySize);
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());
return getLifBlock(
dirent->location, dirent->length / _config.block_size());
}
private:
void mount()
{
_sectorSize = getLogicalSectorSize();
_sectorsPerBlock = _sectorSize / _config.block_size();
_rootBlock = getLifBlock(0);
ByteReader rbr(_rootBlock);
if (rbr.read_be16() != 0x8000)
throw BadFilesystemException();
_volumeLabel = trimWhitespace(rbr.read(6));
_directoryBlock = rbr.read_be32();
rbr.skip(4);
_directorySize = rbr.read_be32();
unsigned tracks = rbr.read_be32();
unsigned heads = rbr.read_be32();
unsigned sectors = rbr.read_be32();
_usedBlocks = 1 + _directorySize;
Bytes directory = getLifBlock(_directoryBlock, _directorySize);
_dirents.clear();
ByteReader br(directory);
while (!br.eof())
{
Bytes direntBytes = br.read(32);
if (direntBytes[0] != 0xff)
{
auto dirent = std::make_unique<LifDirent>(_config, direntBytes);
_usedBlocks += dirent->length / _config.block_size();
_dirents.push_back(std::move(dirent));
}
}
_totalBlocks = std::max(tracks * heads * sectors, _usedBlocks);
}
std::shared_ptr<LifDirent> findFile(const std::string filename)
{
for (const auto& dirent : _dirents)
{
if (dirent->filename == filename)
return dirent;
}
throw FileNotFoundException();
}
Bytes getLifBlock(uint32_t number, uint32_t count)
{
Bytes b;
ByteWriter bw(b);
while (count)
{
bw += getLifBlock(number);
number++;
count--;
}
return b;
}
Bytes getLifBlock(uint32_t number)
{
/* LIF uses 256-byte blocks, but the underlying format can have much
* bigger sectors. */
Bytes sector = getLogicalSector(number / _sectorsPerBlock);
unsigned offset = number % _sectorsPerBlock;
return sector.slice(
offset * _config.block_size(), _config.block_size());
}
private:
const LifProto& _config;
int _sectorSize;
int _sectorsPerBlock;
Bytes _rootBlock;
std::string _volumeLabel;
unsigned _directoryBlock;
unsigned _directorySize;
unsigned _totalBlocks;
unsigned _usedBlocks;
std::vector<std::shared_ptr<LifDirent>> _dirents;
};
std::unique_ptr<Filesystem> Filesystem::createLifFilesystem(
const FilesystemProto& config, std::shared_ptr<SectorInterface> sectors)
{
return std::make_unique<LifFilesystem>(config.lif(), sectors);
}

View File

@@ -233,8 +233,8 @@ private:
_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;
_filedesLength = _rootBlock.reader().seek(0x20).read_be16() -
_filedesBlockNumber + 1;
_totalBlocks = _rootBlock.reader().seek(0x18).read_be16();
Bytes directoryBlock = getPsosBlock(3, 1);

View File

@@ -212,6 +212,9 @@ std::unique_ptr<Filesystem> Filesystem::createFilesystem(
case FilesystemProto::PHILE:
return Filesystem::createPhileFilesystem(config, image);
case FilesystemProto::LIF:
return Filesystem::createLifFilesystem(config, image);
default:
Error() << "no filesystem configured";
return std::unique_ptr<Filesystem>();

View File

@@ -254,6 +254,8 @@ public:
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> createLifFilesystem(
const FilesystemProto& config, std::shared_ptr<SectorInterface> image);
static std::unique_ptr<Filesystem> createFilesystem(
const FilesystemProto& config, std::shared_ptr<SectorInterface> image);

View File

@@ -59,26 +59,33 @@ message CbmfsProto
message ProdosProto {}
message AppledosProto {
optional uint32 filesystem_offset_sectors = 1 [
default = 0,
(help) = "offset the entire offset up the disk this many sectors"
];
message AppledosProto
{
optional uint32 filesystem_offset_sectors = 1 [
default = 0,
(help) = "offset the entire offset up the disk this many sectors"
];
}
message Smaky6FsProto {}
message PhileProto {
optional uint32 block_size = 1 [
default = 1024,
(help) = "Phile filesystem block size"
];
message PhileProto
{
optional uint32 block_size = 1
[ default = 1024, (help) = "Phile filesystem block size" ];
}
// NEXT_TAG: 14
message LifProto
{
optional uint32 block_size = 1
[ default = 256, (help) = "LIF filesystem block size" ];
}
// NEXT_TAG: 15
message FilesystemProto
{
enum FilesystemType {
enum FilesystemType
{
NOT_SET = 0;
ACORNDFS = 1;
BROTHER120 = 2;
@@ -88,12 +95,14 @@ message FilesystemProto
MACHFS = 6;
CBMFS = 7;
PRODOS = 8;
SMAKY6 = 9;
APPLEDOS = 10;
PHILE = 11;
SMAKY6 = 9;
APPLEDOS = 10;
PHILE = 11;
LIF = 12;
}
optional FilesystemType type = 10 [default = NOT_SET, (help) = "filesystem type"];
optional FilesystemType type = 10
[ default = NOT_SET, (help) = "filesystem type" ];
optional AcornDfsProto acorndfs = 1;
optional Brother120FsProto brother120 = 2;
@@ -103,9 +112,11 @@ message FilesystemProto
optional MacHfsProto machfs = 6;
optional CbmfsProto cbmfs = 7;
optional ProdosProto prodos = 8;
optional AppledosProto appledos = 12;
optional Smaky6FsProto smaky6 = 11;
optional PhileProto phile = 13;
optional SectorListProto sector_order = 9 [(help) = "specify the filesystem order of sectors"];
optional AppledosProto appledos = 12;
optional Smaky6FsProto smaky6 = 11;
optional PhileProto phile = 13;
optional LifProto lif = 14;
optional SectorListProto sector_order = 9
[ (help) = "specify the filesystem order of sectors" ];
}

View File

@@ -1,26 +0,0 @@
comment: 'Acorn ADFS generic settings (32-bit formats)'
is_extension: true
image_writer {
filename: "acornadfs.img"
type: IMG
}
layout {
tracks: 80
sides: 2
layoutdata {
sector_size: 1024
physical {
start_sector: 0
}
}
}
decoder {
ibm {
trackdata {
ignore_side_byte: true
}
}
}

View File

@@ -1,25 +0,0 @@
comment: 'Acorn ADFS generic settings (8-bit formats)'
is_extension: true
image_writer {
filename: "acornadfs.img"
type: IMG
}
layout {
layoutdata {
sector_size: 256
physical {
start_sector: 0
count: 16
}
}
}
decoder {
ibm {
trackdata {
ignore_side_byte: true
}
}
}

View File

@@ -1,202 +0,0 @@
comment: 'Apple II generic settings'
is_extension: true
drive {
high_density: false
}
decoder {
apple2 {}
}
encoder {
apple2 {}
}
option {
name: "nofs"
comment: "use physical CHS sector order and no file system"
exclusivity_group: "format"
}
option {
name: "appledos"
comment: "use AppleDOS soft sector skew and file system"
message: "compensating for AppleDOS soft sector skew"
exclusivity_group: "format"
config {
image_reader {
filesystem_sector_order: true
}
image_writer {
filesystem_sector_order: true
}
filesystem {
type: APPLEDOS
}
layout {
layoutdata {
filesystem {
sector: 0
sector: 13
sector: 11
sector: 9
sector: 7
sector: 5
sector: 3
sector: 1
sector: 14
sector: 12
sector: 10
sector: 8
sector: 6
sector: 4
sector: 2
sector: 15
}
}
}
}
}
option {
name: "prodos"
comment: "use ProDOS soft sector skew and filesystem"
message: "compensating for ProDOS soft sector skew"
exclusivity_group: "format"
config {
image_reader {
filesystem_sector_order: true
}
image_writer {
filesystem_sector_order: true
}
filesystem {
type: PRODOS
}
layout {
layoutdata {
filesystem {
sector: 0
sector: 2
sector: 4
sector: 6
sector: 8
sector: 10
sector: 12
sector: 14
sector: 1
sector: 3
sector: 5
sector: 7
sector: 9
sector: 11
sector: 13
sector: 15
}
}
}
}
}
option {
name: "cpm"
comment: "use CP/M soft sector skew and filesystem"
message: "compensating for CP/M soft sector skew"
exclusivity_group: "format"
config {
image_reader {
filesystem_sector_order: true
}
image_writer {
filesystem_sector_order: true
}
filesystem {
type: CPMFS
cpmfs {
filesystem_start {
track: 3
}
block_size: 4096
dir_entries: 128
}
}
decoder {
apple2 {
side_one_track_offset: 80
}
}
encoder {
apple2 {
side_one_track_offset: 80
}
}
layout {
layoutdata {
# The boot tracks use ProDOS translation.
track: 0
up_to_track: 2
filesystem {
sector: 0
sector: 2
sector: 4
sector: 6
sector: 8
sector: 10
sector: 12
sector: 14
sector: 1
sector: 3
sector: 5
sector: 7
sector: 9
sector: 11
sector: 13
sector: 15
}
}
layoutdata {
# The data tracks use their own, special translation.
track: 3
up_to_track: 79
filesystem {
sector: 0
sector: 3
sector: 6
sector: 9
sector: 12
sector: 15
sector: 2
sector: 5
sector: 8
sector: 11
sector: 14
sector: 1
sector: 4
sector: 7
sector: 10
sector: 13
}
}
}
}
}

View File

@@ -1,23 +0,0 @@
comment: 'Common Atari definitions'
is_extension: true
encoder {
ibm {
trackdata {
target_rotational_period_ms: 200
target_clock_period_us: 4
emit_iam: false
gap0: 80
gap2: 22
gap3: 34
}
}
}
decoder {
ibm {
trackdata {
ignore_sector: 66
}
}
}

View File

@@ -1,64 +0,0 @@
comment: 'Common Micropolis definitions'
is_extension: true
drive {
hard_sector_count: 16
}
image_reader {
filename: "micropolis.img"
type: IMG
}
image_writer {
filename: "micropolis.img"
type: IMG
}
layout {
layoutdata {
sector_size: 256
physical {
start_sector: 0
count: 16
}
}
}
encoder {
micropolis {}
}
decoder {
micropolis {}
}
option {
name: "vgi"
comment: "Read/write VGI format images"
message: "using VGI format"
config {
image_reader {
filename: "micropolis.vgi"
type: IMG
}
image_writer {
filename: "micropolis.vgi"
type: IMG
}
layout {
layoutdata {
sector_size: 275
}
}
decoder {
micropolis {
sector_output_size: 275
}
}
}
}

View File

@@ -1,22 +0,0 @@
comment: 'DVK MX common settings'
is_extension: true
image_writer {
filename: "mx.img"
type: IMG
}
layout {
layoutdata {
sector_size: 256
physical {
start_sector: 0
count: 11
}
}
}
decoder {
mx {}
}

View File

@@ -1,35 +0,0 @@
comment: 'Northstar generic settings'
is_extension: true
image_reader {
filename: "northstar.nsi"
type: NSI
}
image_writer {
filename: "northstar.nsi"
type: NSI
}
layout {
layoutdata {
physical {
start_sector: 0
count: 10
}
}
}
drive {
hard_sector_count: 10
sync_with_index: true
}
encoder {
northstar {}
}
decoder {
northstar {}
}

View File

@@ -0,0 +1,121 @@
comment: 'Acorn ADFS family (ro)'
image_writer {
filename: "acornadfs.img"
type: IMG
}
decoder {
ibm {
trackdata {
ignore_side_byte: true
}
}
}
option_group {
comment: "Format variant"
option {
name: "160"
comment: '160kB 3.5" or 5.25" 40-track SSDD; S format'
config {
tpi: 48
layout {
tracks: 40
sides: 1
layoutdata {
sector_size: 256
physical {
start_sector: 0
count: 16
}
}
}
}
}
option {
name: "320"
comment: '320kB 3.5" or 5.25" 80-track SSDD; M format'
config {
tpi: 96
layout {
tracks: 80
sides: 1
layoutdata {
sector_size: 256
physical {
start_sector: 0
count: 16
}
}
}
}
}
option {
name: "640"
comment: '640kB 3.5" or 5.25" 80-track DSDD; L format'
config {
tpi: 96
layout {
tracks: 80
sides: 2
layoutdata {
sector_size: 256
physical {
start_sector: 0
count: 16
}
}
}
}
}
option {
name: "800"
comment: '800kB 3.5" 80-track DSDD; D and E formats'
config {
tpi: 96
layout {
tracks: 80
sides: 2
layoutdata {
sector_size: 1024
physical {
start_sector: 0
count: 5
}
}
}
}
}
option {
name: "1600"
comment: '1600kB 3.5" 80-track DSHD; F formats'
set_by_default: true
config {
tpi: 96
layout {
tracks: 80
sides: 2
layoutdata {
sector_size: 1024
physical {
start_sector: 0
count: 10
}
}
}
}
}
}

View File

@@ -1,9 +0,0 @@
comment: 'Acorn ADFS S-format 160kB 3.5" or 5.25" 40-track SS'
include: '_acornadfs8'
layout {
tracks: 40
sides: 1
}
tpi: 48

View File

@@ -1,10 +0,0 @@
comment: 'Acorn ADFS F-format 1600kB 3.5" 80-track DS'
include: '_acornadfs32'
layout {
layoutdata {
physical {
count: 10
}
}
}

View File

@@ -1,7 +0,0 @@
comment: 'Acorn ADFS M-format 320kB 3.5" or 5.25" 80-track SS'
include: '_acornadfs8'
layout {
tracks: 80
sides: 1
}

View File

@@ -1,7 +0,0 @@
comment: 'Acorn ADFS L-format 640kB 3.5" or 5.25" 80-track DS'
include: '_acornadfs8'
layout {
tracks: 80
sides: 2
}

View File

@@ -1,10 +0,0 @@
comment: 'Acorn ADFS D and E-format 800kB 3.5" 80-track DS'
include: '_acornadfs32'
layout {
layoutdata {
physical {
count: 5
}
}
}

View File

@@ -1,4 +1,4 @@
comment: 'Acorn DFS 100kB/200kB 3.5" or 5.25" 40- or 80-track SS (ro)'
comment: 'Acorn DFS fmaily'
image_reader {
filename: "acorndfs.img"
@@ -11,7 +11,6 @@ image_writer {
}
layout {
tracks: 80
sides: 1
layoutdata {
sector_size: 256
@@ -47,3 +46,30 @@ filesystem {
type: ACORNDFS
}
option_group {
comment: "Format variant"
option {
name: "100"
comment: '100kB 40-track SSSD'
config {
layout {
tracks: 40
}
}
}
option {
name: "200"
comment: '200kB 80-track SSSD'
set_by_default: true
config {
layout {
tracks: 80
}
}
}
}

58
src/formats/ampro.textpb Normal file
View File

@@ -0,0 +1,58 @@
comment: 'Ampro 5.25" family'
image_writer {
filename: "ampro.img"
type: IMG
}
layout {
sides: 2
layoutdata {
sector_size: 1024
physical {
start_sector: 17
count: 5
}
}
}
decoder {
ibm {}
}
filesystem {
type: CPMFS
cpmfs {
filesystem_start {
track: 1
}
block_size: 2048
dir_entries: 128
}
}
option_group {
comment: "Format variant"
option {
name: "400"
comment: "400kB 40-track DSDD"
config {
layout {
tracks: 40
}
}
}
option {
name: "800"
comment: "800kB 80-track DSDD"
config {
layout {
tracks: 80
}
}
}
}

View File

@@ -1,22 +0,0 @@
comment: 'Ampro 400kB/800kB 5.25" 40/80 track SSDD/DSDD (ro)'
image_writer {
filename: "ampro.img"
type: IMG
}
layout {
tracks: 80
sides: 1
layoutdata {
sector_size: 1024
physical {
start_sector: 17
count: 5
}
}
}
decoder {
ibm {}
}

View File

@@ -1,22 +0,0 @@
comment: 'Ampro 400kB/800kB 5.25" 40/80 track SSDD/DSDD (ro)'
image_writer {
filename: "ampro.img"
type: IMG
}
layout {
tracks: 80
sides: 2
layoutdata {
sector_size: 1024
physical {
start_sector: 17
count: 5
}
}
}
decoder {
ibm {}
}

254
src/formats/apple2.textpb Normal file
View File

@@ -0,0 +1,254 @@
comment: 'Apple II family'
decoder {
apple2 {}
}
encoder {
apple2 {}
}
option_group {
comment: "Format variant"
option {
name: "140"
comment: '140kB 5.25" 35-track SS'
set_by_default: true
config {
layout {
tracks: 35
sides: 1
layoutdata {
sector_size: 256
physical {
start_sector: 0
count: 16
}
}
}
tpi: 48
}
}
option {
name: "640"
comment: '640kB 5.25" 80-track DS'
config {
layout {
tracks: 80
sides: 2
order: HCS
layoutdata {
sector_size: 256
physical {
start_sector: 0
count: 16
}
}
}
tpi: 96
}
}
}
option {
name: "side1"
comment: "for AppleDOS file system access, read the volume on side 1 of a disk"
config {
filesystem {
appledos {
filesystem_offset_sectors: 0x500
}
}
}
}
option_group {
comment: "Filesystem and sector skew"
option {
name: "nofs"
comment: "use physical CHS sector order and no file system"
}
option {
name: "appledos"
comment: "use AppleDOS soft sector skew and file system"
config {
image_reader {
filesystem_sector_order: true
}
image_writer {
filesystem_sector_order: true
}
filesystem {
type: APPLEDOS
}
layout {
layoutdata {
filesystem {
sector: 0
sector: 13
sector: 11
sector: 9
sector: 7
sector: 5
sector: 3
sector: 1
sector: 14
sector: 12
sector: 10
sector: 8
sector: 6
sector: 4
sector: 2
sector: 15
}
}
}
}
}
option {
name: "prodos"
comment: "use ProDOS soft sector skew and filesystem"
set_by_default: true
config {
image_reader {
filesystem_sector_order: true
}
image_writer {
filesystem_sector_order: true
}
filesystem {
type: PRODOS
}
layout {
layoutdata {
filesystem {
sector: 0
sector: 2
sector: 4
sector: 6
sector: 8
sector: 10
sector: 12
sector: 14
sector: 1
sector: 3
sector: 5
sector: 7
sector: 9
sector: 11
sector: 13
sector: 15
}
}
}
}
}
option {
name: "cpm"
comment: "use CP/M soft sector skew and filesystem"
config {
image_reader {
filesystem_sector_order: true
}
image_writer {
filesystem_sector_order: true
}
filesystem {
type: CPMFS
cpmfs {
filesystem_start {
track: 3
}
block_size: 4096
dir_entries: 128
}
}
decoder {
apple2 {
side_one_track_offset: 80
}
}
encoder {
apple2 {
side_one_track_offset: 80
}
}
layout {
layoutdata {
# The boot tracks use ProDOS translation.
track: 0
up_to_track: 2
filesystem {
sector: 0
sector: 2
sector: 4
sector: 6
sector: 8
sector: 10
sector: 12
sector: 14
sector: 1
sector: 3
sector: 5
sector: 7
sector: 9
sector: 11
sector: 13
sector: 15
}
}
layoutdata {
# The data tracks use their own, special translation.
track: 3
up_to_track: 79
filesystem {
sector: 0
sector: 3
sector: 6
sector: 9
sector: 12
sector: 15
sector: 2
sector: 5
sector: 8
sector: 11
sector: 14
sector: 1
sector: 4
sector: 7
sector: 10
sector: 13
}
}
}
}
}
}

View File

@@ -1,27 +0,0 @@
comment: 'Apple II 140kB 5.25" 35 track SSDD'
include: '_apple2'
image_reader {
filename: "appleii140.img"
type: IMG
}
image_writer {
filename: "appleii140.img"
type: IMG
}
layout {
tracks: 35
sides: 1
layoutdata {
sector_size: 256
physical {
start_sector: 0
count: 16
}
}
}
tpi: 48

View File

@@ -1,42 +0,0 @@
comment: 'Apple II 640kB 5.25" 80 track DSDD'
include: '_apple2'
image_reader {
filename: "appleii640.img"
type: IMG
}
image_writer {
filename: "appleii640.img"
type: IMG
}
layout {
tracks: 80
sides: 2
order: HCS
layoutdata {
sector_size: 256
physical {
start_sector: 0
count: 16
}
}
}
tpi: 96
option {
name: "side1"
comment: "read the volume on side 1 of a disk (AppleDOS only)"
message: "accessing volume on side 1"
config {
filesystem {
appledos {
filesystem_offset_sectors: 0x500
}
}
}
}

180
src/formats/atarist.textpb Normal file
View File

@@ -0,0 +1,180 @@
comment: 'Atari ST family'
encoder {
ibm {
trackdata {
target_rotational_period_ms: 200
target_clock_period_us: 4
emit_iam: false
gap0: 80
gap2: 22
gap3: 34
}
}
}
decoder {
ibm {
trackdata {
ignore_sector: 66
}
}
}
option_group {
comment: "Format variant"
option {
name: "360"
comment: '360kB 3.5" 80-track 9-sector SSDD'
config {
layout {
tracks: 80
sides: 1
layoutdata {
sector_size: 512
physical {
start_sector: 1
count: 9
}
}
}
}
}
option {
name: "370"
comment: '370kB 3.5" 82-track 9-sector SSDD'
config {
layout {
tracks: 82
sides: 1
layoutdata {
sector_size: 512
physical {
start_sector: 1
count: 9
}
}
}
}
}
option {
name: "400"
comment: '400kB 3.5" 80-track 10-sector SSDD'
config {
layout {
tracks: 80
sides: 1
layoutdata {
sector_size: 512
physical {
start_sector: 1
count: 10
}
}
}
}
}
option {
name: "410"
comment: '410kB 3.5" 82-track 10-sector SSDD'
config {
layout {
tracks: 82
sides: 1
layoutdata {
sector_size: 512
physical {
start_sector: 1
count: 10
}
}
}
}
}
option {
name: "720"
comment: '720kB 3.5" 80-track 9-sector DSDD'
set_by_default: true
config {
layout {
tracks: 80
sides: 2
layoutdata {
sector_size: 512
physical {
start_sector: 1
count: 9
}
}
}
}
}
option {
name: "740"
comment: '740kB 3.5" 82-track 9-sector DSDD'
config {
layout {
tracks: 82
sides: 2
layoutdata {
sector_size: 512
physical {
start_sector: 1
count: 9
}
}
}
}
}
option {
name: "800"
comment: '800kB 3.5" 80-track 10-sector DSDD'
config {
layout {
tracks: 80
sides: 2
layoutdata {
sector_size: 512
physical {
start_sector: 1
count: 10
}
}
}
}
}
option {
name: "820"
comment: '820kB 3.5" 82-track 10-sector DSDD'
config {
layout {
tracks: 82
sides: 2
layoutdata {
sector_size: 512
physical {
start_sector: 1
count: 10
}
}
}
}
}
}

View File

@@ -1,25 +0,0 @@
comment: 'Atari ST 360kB 3.5" 80-track 9-sector SSDD'
include: '_atari'
image_reader {
filename: "atarist360.st"
type: IMG
}
image_writer {
filename: "atarist360.st"
type: IMG
}
layout {
tracks: 80
sides: 1
layoutdata {
sector_size: 512
physical {
start_sector: 1
count: 9
}
}
}

View File

@@ -1,25 +0,0 @@
comment: 'Atari ST 370kB 3.5" 82-track 9-sector SSDD'
include: '_atari'
image_reader {
filename: "atarist370.st"
type: IMG
}
image_writer {
filename: "atarist370.st"
type: IMG
}
layout {
tracks: 82
sides: 1
layoutdata {
sector_size: 512
physical {
start_sector: 1
count: 9
}
}
}

View File

@@ -1,25 +0,0 @@
comment: 'Atari ST 400kB 3.5" 80-track 10-sector SSDD'
include: '_atari'
image_reader {
filename: "atarist400.st"
type: IMG
}
image_writer {
filename: "atarist400.st"
type: IMG
}
layout {
tracks: 80
sides: 1
layoutdata {
sector_size: 512
physical {
start_sector: 1
count: 10
}
}
}

View File

@@ -1,25 +0,0 @@
comment: 'Atari ST 410kB 3.5" 82-track 10-sector SSDD'
include: '_atari'
image_reader {
filename: "atarist410.st"
type: IMG
}
image_writer {
filename: "atarist410.st"
type: IMG
}
layout {
tracks: 82
sides: 1
layoutdata {
sector_size: 512
physical {
start_sector: 1
count: 10
}
}
}

View File

@@ -1,25 +0,0 @@
comment: 'Atari ST 720kB 3.5" 80-track 9-sector DSDD'
include: '_atari'
image_reader {
filename: "atarist720.st"
type: IMG
}
image_writer {
filename: "atarist720.st"
type: IMG
}
layout {
tracks: 80
sides: 2
layoutdata {
sector_size: 512
physical {
start_sector: 1
count: 9
}
}
}

View File

@@ -1,25 +0,0 @@
comment: 'Atari ST 740kB 3.5" 82-track 9-sector DSDD'
include: '_atari'
image_reader {
filename: "atarist740.st"
type: IMG
}
image_writer {
filename: "atarist740.st"
type: IMG
}
layout {
tracks: 82
sides: 2
layoutdata {
sector_size: 512
physical {
start_sector: 1
count: 9
}
}
}

View File

@@ -1,26 +0,0 @@
comment: 'Atari ST 800kB 3.5" 80-track 10-sector DSDD'
include: '_atari'
image_reader {
filename: "atarist800.st"
type: IMG
}
image_writer {
filename: "atarist800.st"
type: IMG
}
layout {
tracks: 80
sides: 2
layoutdata {
sector_size: 512
physical {
start_sector: 1
count: 10
}
}
}

View File

@@ -1,26 +0,0 @@
comment: 'Atari ST 820kB 3.5" 82-track 10-sector DSDD'
include: '_atari'
image_reader {
filename: "atarist820.st"
type: IMG
}
image_writer {
filename: "atarist820.st"
type: IMG
}
layout {
tracks: 82
sides: 2
layoutdata {
sector_size: 512
physical {
start_sector: 1
count: 10
}
}
}

View File

@@ -0,0 +1,91 @@
comment: 'Brother GCR family'
image_reader {
filename: "brother.img"
type: IMG
}
image_writer {
filename: "brother.img"
type: IMG
}
encoder {
brother {}
}
decoder {
brother {}
}
option_group {
comment: "Format variant"
option {
name: "120"
comment: '120kB 3.5" 39-track SS GCR'
config {
layout {
tracks: 39
sides: 1
layoutdata {
sector_size: 256
physical {
start_sector: 0
count: 12
skew: 5
}
}
}
encoder {
brother {
format: BROTHER120
}
}
drive {
head_bias: 0
group_offset: 1
}
filesystem {
type: BROTHER120
}
tpi: 48
}
}
option {
name: "240"
comment: '240kB 3.5" 78-track SS GCR'
config {
layout {
tracks: 78
sides: 1
layoutdata {
sector_size: 256
physical {
start_sector: 0
count: 12
skew: 5
}
}
}
drive {
head_bias: 3
}
filesystem {
type: FATFS
}
tpi: 96
}
}
}

View File

@@ -1,46 +0,0 @@
comment: 'Brother 120kB 3.5" 39-track SS GCR'
image_reader {
filename: "brother120.img"
type: IMG
}
image_writer {
filename: "brother120.img"
type: IMG
}
layout {
tracks: 39
sides: 1
layoutdata {
sector_size: 256
physical {
start_sector: 0
count: 12
skew: 5
}
}
}
encoder {
brother {
format: BROTHER120
}
}
decoder {
brother {}
}
drive {
head_bias: 0
group_offset: 1
}
tpi: 48
filesystem {
type: BROTHER120
}

View File

@@ -1,41 +0,0 @@
comment: 'Brother 240kB 3.5" 78-track SS GCR'
image_reader {
filename: "brother240.img"
type: IMG
}
image_writer {
filename: "brother240.img"
type: IMG
}
layout {
tracks: 78
sides: 1
layoutdata {
sector_size: 256
physical {
start_sector: 0
count: 12
skew: 5
}
}
}
encoder {
brother {}
}
decoder {
brother {}
}
drive {
head_bias: 3
}
filesystem {
type: FATFS
}

View File

@@ -1,37 +1,16 @@
FORMATS = \
_acornadfs8 \
_acornadfs32 \
_apple2 \
_atari \
_micropolis \
_northstar \
_mx \
40track_drive \
acornadfs160 \
acornadfs320 \
acornadfs640 \
acornadfs800 \
acornadfs1600 \
acornadfs \
acorndfs \
aeslanier \
agat840 \
amiga \
ampro400 \
ampro800 \
ampro \
apple2_drive \
appleii140 \
appleii640 \
atarist360 \
atarist370 \
atarist400 \
atarist410 \
atarist720 \
atarist740 \
atarist800 \
atarist820 \
apple2 \
atarist \
bk800 \
brother120 \
brother240 \
brother \
commodore1541 \
commodore1581 \
cmd_fd2000 \
@@ -39,44 +18,22 @@ FORMATS = \
epsonpf10 \
f85 \
fb100 \
hp9121 \
hplif770 \
hplif \
ibm \
ibm1200 \
ibm1232 \
ibm1440 \
ibm180 \
ibm160 \
ibm360 \
ibm320 \
ibm720 \
icl30 \
mac400 \
mac800 \
micropolis143 \
micropolis287 \
micropolis315 \
micropolis630 \
mx110 \
mx220_ds \
mx220_ss \
mx440 \
mac \
micropolis \
mx \
n88basic \
northstar175 \
northstar350 \
northstar87 \
northstar \
psos800 \
rolandd20 \
rx50 \
shugart_drive \
smaky6 \
tids990 \
tiki90 \
tiki200 \
tiki400 \
tiki800 \
victor9k_ds \
victor9k_ss \
tiki \
victor9k \
zilogmcz \
$(OBJDIR)/src/formats/format_%.o: $(OBJDIR)/src/formats/format_%.cc

View File

@@ -64,27 +64,30 @@ filesystem {
type: CBMFS
}
option {
name: "35"
comment: "35-track variant (default)"
message: "using 35-track variant"
config {
layout {
tracks: 35
option_group {
comment: "Format family"
option {
name: "171"
comment: "35-track variant"
set_by_default: true
config {
layout {
tracks: 35
}
}
}
option {
name: "192"
comment: "40-track variant"
config {
layout {
tracks: 40
}
}
}
}
option {
name: "40"
comment: "40-track variant"
message: "using 40-track variant"
config {
layout {
tracks: 40
}
}
}

View File

@@ -1,49 +0,0 @@
comment: 'Hewlett-Packard 9121 264kB 3.5" SSDD'
image_reader {
filename: "hp9121.img"
type: IMG
}
layout {
tracks: 66
sides: 1
layoutdata {
sector_size: 256
physical {
sector: 0
sector: 4
sector: 8
sector: 12
sector: 1
sector: 5
sector: 9
sector: 13
sector: 2
sector: 6
sector: 10
sector: 14
sector: 3
sector: 7
sector: 11
sector: 15
}
}
}
encoder {
ibm {
trackdata {
emit_iam: false
target_rotational_period_ms: 200
target_clock_period_us: 4
gap0: 80
gap2: 22
gap3: 44
}
}
}
decoder {
ibm {}
}

143
src/formats/hplif.textpb Normal file
View File

@@ -0,0 +1,143 @@
comment: 'Hewlett-Packard LIF family'
drive {
high_density: false
}
image_reader {
filename: "hplif.img"
type: IMG
}
image_writer {
filename: "hplif.img"
type: IMG
}
decoder {
ibm {
}
}
filesystem {
type: LIF
}
tpi: 96
option_group {
comment: "Format family"
option {
name: "264"
comment: '264kB 3.5" 66-track SSDD; HP9121 format'
config {
layout {
tracks: 66
sides: 1
layoutdata {
sector_size: 256
physical {
sector: 0
sector: 4
sector: 8
sector: 12
sector: 1
sector: 5
sector: 9
sector: 13
sector: 2
sector: 6
sector: 10
sector: 14
sector: 3
sector: 7
sector: 11
sector: 15
}
}
}
encoder {
ibm {
trackdata {
emit_iam: false
target_rotational_period_ms: 200
target_clock_period_us: 4
gap0: 80
gap2: 22
gap3: 44
}
}
}
}
}
option {
name: "616"
comment: '616kB 3.5" 77-track DSDD'
config {
layout {
tracks: 77
sides: 2
layoutdata {
sector_size: 256
physical {
start_sector: 1
count: 16
}
}
}
encoder {
ibm {
trackdata {
target_rotational_period_ms: 200
target_clock_period_us: 4
emit_iam: false
gap0: 80
gap2: 22
gap3: 40
}
}
}
}
}
option {
name: "770"
comment: '770kB 3.5" 77-track DSDD'
config {
layout {
tracks: 77
sides: 2
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
}
}
}
}
}
}

View File

@@ -1,48 +0,0 @@
comment: 'Hewlett-Packard LIF 770kB 3.5" DSDD'
drive {
high_density: false
rotational_period_ms: 200
}
image_reader {
filename: "hplif770.img"
type: IMG
}
image_writer {
filename: "hplif770.img"
type: IMG
}
layout {
tracks: 77
sides: 2
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 {
}
}

View File

@@ -1,4 +1,9 @@
comment: 'PC 3.5"/5.25" autodetect double sided format'
comment: 'Generic PC 3.5"/5.25" family'
image_reader {
filename: "ibm.img"
type: IMG
}
image_writer {
filename: "ibm.img"
@@ -9,9 +14,255 @@ decoder {
ibm {}
}
heads {
start: 0
end: 1
filesystem {
type: FATFS
}
tpi: 96
option_group {
comment: "Format variant"
option {
name: "auto"
comment: 'try to autodetect the format (unreliable)'
set_by_default: true
config {}
}
option {
name: "160"
comment: '160kB 5.25" 40-track 8-sector SSDD'
config {
layout {
tracks: 40
sides: 1
layoutdata {
sector_size: 512
physical {
start_sector: 1
count: 8
}
}
}
encoder {
ibm {
trackdata {
target_rotational_period_ms: 167
target_clock_period_us: 3.333
}
}
}
tpi: 48
}
}
option {
name: "180"
comment: '180kB 5.25" 40-track 9-sector SSDD'
config {
layout {
tracks: 40
sides: 1
layoutdata {
sector_size: 512
physical {
start_sector: 1
count: 9
}
}
}
encoder {
ibm {
trackdata {
target_rotational_period_ms: 167
target_clock_period_us: 3.333
}
}
}
tpi: 48
}
}
option {
name: "320"
comment: '320kB 5.25" 40-track 8-sector DSDD'
config {
layout {
tracks: 40
sides: 2
layoutdata {
sector_size: 512
physical {
start_sector: 1
count: 8
}
}
}
encoder {
ibm {
trackdata {
target_rotational_period_ms: 167
target_clock_period_us: 3.333
}
}
}
tpi: 48
}
}
option {
name: "360"
comment: '360kB 5.25" 40-track 9-sector DSDD'
config {
layout {
tracks: 40
sides: 2
layoutdata {
sector_size: 512
physical {
start_sector: 1
count: 9
}
}
}
encoder {
ibm {
trackdata {
target_rotational_period_ms: 167
target_clock_period_us: 3.333
}
}
}
tpi: 48
}
}
option {
name: "720"
comment: '720kB 5.25"/3.5" 80-track 9-sector DSDD'
config {
layout {
tracks: 80
sides: 2
layoutdata {
sector_size: 512
physical {
start_sector: 1
count: 9
}
}
}
encoder {
ibm {
trackdata {
# This also works on 166ms drives (producing a physical clock of
# 3.33us).
target_rotational_period_ms: 200
target_clock_period_us: 4
}
}
}
}
}
option {
name: "1200"
comment: '1200kB 5.25" 80-track 15-sector DSHD'
config {
layout {
tracks: 80
sides: 2
layoutdata {
sector_size: 512
physical {
start_sector: 1
count: 15
}
}
}
encoder {
ibm {
trackdata {
target_rotational_period_ms: 167
target_clock_period_us: 2
}
}
}
}
}
option {
name: "1232"
comment: '1232kB 5.25"/3.5" 77-track 8-sector DSHD'
config {
layout {
tracks: 77
sides: 2
layoutdata {
sector_size: 1024
physical {
start_sector: 1
count: 8
}
}
}
encoder {
ibm {
trackdata {
target_rotational_period_ms: 167
target_clock_period_us: 2
}
}
}
}
}
option {
name: "1440"
comment: '1440kB 3.5" 80-track 18-sector DSHD'
config {
layout {
tracks: 80
sides: 2
layoutdata {
sector_size: 512
physical {
start_sector: 1
count: 18
}
}
}
encoder {
ibm {
trackdata {
target_rotational_period_ms: 200
target_clock_period_us: 2
}
}
}
}
}
}

View File

@@ -1,46 +0,0 @@
comment: 'PC 1200kB 5.25" 80-track 15-sector DSHD'
image_reader {
filename: "ibm1200.img"
type: IMG
}
image_writer {
filename: "ibm1200.img"
type: IMG
}
layout {
tracks: 80
sides: 2
layoutdata {
sector_size: 512
physical {
start_sector: 1
count: 15
}
}
}
encoder {
ibm {
trackdata {
target_rotational_period_ms: 167
target_clock_period_us: 2
}
}
}
decoder {
ibm {}
}
drive {
high_density: true
}
filesystem {
type: FATFS
}

View File

@@ -1,46 +0,0 @@
comment: 'Japanese PC 1232kB 5.25"/3.5" 77-track 8-sector DSHD'
image_reader {
filename: "ibm1232.img"
type: IMG
}
image_writer {
filename: "ibm1232.img"
type: IMG
}
layout {
tracks: 77
sides: 2
layoutdata {
sector_size: 1024
physical {
start_sector: 1
count: 8
}
}
}
encoder {
ibm {
trackdata {
target_rotational_period_ms: 167
target_clock_period_us: 2
}
}
}
decoder {
ibm {}
}
drive {
high_density: true
}
filesystem {
type: FATFS
}

View File

@@ -1,41 +0,0 @@
comment: 'PC 1440kB 3.5" 80-track 18-sector DSHD'
image_reader {
filename: "ibm1440.img"
type: IMG
}
image_writer {
filename: "ibm1440.img"
type: IMG
}
layout {
tracks: 80
sides: 2
layoutdata {
sector_size: 512
physical {
start_sector: 1
count: 18
}
}
}
encoder {
ibm {
trackdata {
target_rotational_period_ms: 200
target_clock_period_us: 2
}
}
}
decoder {
ibm {}
}
filesystem {
type: FATFS
}

View File

@@ -1,48 +0,0 @@
comment: 'PC 160kB 5.25" 40-track 8-sector SSDD'
image_reader {
filename: "ibm160.img"
type: IMG
}
image_writer {
filename: "ibm160.img"
type: IMG
}
layout {
tracks: 40
sides: 1
layoutdata {
sector_size: 512
physical {
start_sector: 1
count: 8
}
}
}
encoder {
ibm {
trackdata {
target_rotational_period_ms: 167
target_clock_period_us: 3.333
}
}
}
decoder {
ibm {}
}
drive {
high_density: false
}
tpi: 48
filesystem {
type: FATFS
}

View File

@@ -1,48 +0,0 @@
comment: 'PC 180kB 5.25" 40-track 9-sector SSDD'
image_reader {
filename: "ibm180.img"
type: IMG
}
image_writer {
filename: "ibm180.img"
type: IMG
}
layout {
tracks: 40
sides: 1
layoutdata {
sector_size: 512
physical {
start_sector: 1
count: 9
}
}
}
encoder {
ibm {
trackdata {
target_rotational_period_ms: 167
target_clock_period_us: 3.333
}
}
}
decoder {
ibm {}
}
drive {
high_density: false
}
tpi: 48
filesystem {
type: FATFS
}

View File

@@ -1,44 +0,0 @@
comment: 'PC 320kB 5.25" 40-track 8-sector DSDD'
image_reader {
filename: "ibm320.img"
type: IMG
}
image_writer {
filename: "ibm320.img"
type: IMG
}
layout {
tracks: 40
sides: 2
layoutdata {
sector_size: 512
physical {
start_sector: 1
count: 8
}
}
}
encoder {
ibm {
trackdata {
target_rotational_period_ms: 167
target_clock_period_us: 3.333
}
}
}
decoder {
ibm {}
}
tpi: 48
filesystem {
type: FATFS
}

View File

@@ -1,44 +0,0 @@
comment: 'PC 360kB 5.25" 40-track 9-sector DSDD'
image_reader {
filename: "ibm360.img"
type: IMG
}
image_writer {
filename: "ibm360.img"
type: IMG
}
layout {
tracks: 40
sides: 2
layoutdata {
sector_size: 512
physical {
start_sector: 1
count: 9
}
}
}
encoder {
ibm {
trackdata {
target_rotational_period_ms: 167
target_clock_period_us: 3.333
}
}
}
decoder {
ibm {}
}
tpi: 48
filesystem {
type: FATFS
}

View File

@@ -1,47 +0,0 @@
comment: 'PC 720kB 5.25"/3.5" 80-track 9-sector DSDD'
image_reader {
filename: "ibm720.img"
type: IMG
}
image_writer {
filename: "ibm720.img"
type: IMG
}
layout {
tracks: 80
sides: 2
layoutdata {
sector_size: 512
physical {
start_sector: 1
count: 9
}
}
}
encoder {
ibm {
trackdata {
# This also works on 166ms drives (producing a physical clock of
# 3.33us).
target_rotational_period_ms: 200
target_clock_period_us: 4
}
}
}
decoder {
ibm {}
}
drive {
high_density: false
}
filesystem {
type: FATFS
}

View File

@@ -1,12 +1,12 @@
comment: 'Macintosh 800kB 3.5" DSDD GCR'
comment: 'Macintosh GCR family'
image_reader {
filename: "mac800.dsk"
filename: "mac.dsk"
type: IMG
}
image_writer {
filename: "mac800.dsk"
filename: "mac.dsk"
type: IMG
}
@@ -64,8 +64,47 @@ decoder {
macintosh {}
}
filesystem {
type: MACHFS
option_group {
comment: "Format variant"
option {
name: "400"
comment: "400kB 80-track SSDD"
config {
layout {
sides: 1
}
}
}
option {
name: "800"
comment: "800kB 80-track DSDD"
set_by_default: true
config {
layout {
sides: 2
}
filesystem {
type: MACHFS
}
}
}
}
option {
name: "metadata"
comment: "read/write 524 byte sectors"
config {
layout {
layoutdata {
sector_size: 524
}
}
}
}

View File

@@ -1,66 +0,0 @@
comment: 'Macintosh 400kB 3.5" SSDD GCR'
image_reader {
filename: "mac400.dsk"
type: IMG
}
image_writer {
filename: "mac400.dsk"
type: IMG
}
layout {
tracks: 80
sides: 1
layoutdata {
sector_size: 512
physical {
start_sector: 0
skew: 6
}
}
layoutdata {
track: 0
up_to_track: 15
physical {
count: 12
}
}
layoutdata {
track: 16
up_to_track: 31
physical {
count: 11
}
}
layoutdata {
track: 32
up_to_track: 47
physical {
count: 10
}
}
layoutdata {
track: 48
up_to_track: 63
physical {
count: 9
}
}
layoutdata {
track: 64
up_to_track: 79
physical {
count: 8
}
}
}
encoder {
macintosh {}
}
decoder {
macintosh {}
}

View File

@@ -0,0 +1,113 @@
comment: ' Micropolis format family'
drive {
hard_sector_count: 16
}
image_reader {
filename: "micropolis.img"
type: IMG
}
image_writer {
filename: "micropolis.img"
type: IMG
}
layout {
layoutdata {
sector_size: 256
physical {
start_sector: 0
count: 16
}
}
}
encoder {
micropolis {}
}
decoder {
micropolis {}
}
option {
name: "vgi"
comment: "Read/write VGI format images with 275 bytes per sector"
config {
image_reader {
filename: "micropolis.vgi"
type: IMG
}
image_writer {
filename: "micropolis.vgi"
type: IMG
}
layout {
layoutdata {
sector_size: 275
}
}
decoder {
micropolis {
sector_output_size: 275
}
}
}
}
option_group {
option {
name: "143"
comment: '143kB 5.25" SSDD hard-sectored; Micropolis MetaFloppy Mod I'
config {
layout {
tracks: 35
sides: 1
}
}
}
option {
name: "287"
comment: '287kB 5.25" DSDD hard-sectored; Micropolis MetaFloppy Mod I'
config {
layout {
tracks: 35
sides: 2
}
}
}
option {
name: "315"
comment: '315kB 5.25" SSDD hard-sectored; Micropolis MetaFloppy Mod II'
config {
layout {
tracks: 77
sides: 1
}
}
}
option {
name: "630"
comment: '630kB 5.25" DSDD hard-sectored; Micropolis MetaFloppy Mod II'
config {
layout {
tracks: 77
sides: 2
}
}
}
}

View File

@@ -1,8 +0,0 @@
comment: 'Micropolis MetaFloppy Mod I 143kB 5.25" SSDD hard-sectored'
include: '_micropolis'
layout {
tracks: 35
sides: 1
}

View File

@@ -1,8 +0,0 @@
comment: 'Micropolis MetaFloppy Mod I 287kB 5.25" DSDD hard-sectored'
include: '_micropolis'
layout {
tracks: 35
sides: 2
}

View File

@@ -1,8 +0,0 @@
comment: 'Micropolis MetaFloppy Mod II 315kB 5.25" SSDD hard-sectored'
include: '_micropolis'
layout {
tracks: 77
sides: 1
}

View File

@@ -1,8 +0,0 @@
comment: 'Micropolis MetaFloppy Mod II 630kB 5.25" DSDD hard-sectored'
include: '_micropolis'
layout {
tracks: 77
sides: 2
}

76
src/formats/mx.textpb Normal file
View File

@@ -0,0 +1,76 @@
comment: 'DVK MX family'
image_writer {
filename: "mx.img"
type: IMG
}
layout {
layoutdata {
sector_size: 256
physical {
start_sector: 0
count: 11
}
}
}
decoder {
mx {}
}
tpi: 48
option_group {
comment: "Format family"
option {
name: "110"
comment: '110kB 5.25" 40-track SSSD'
config {
layout {
tracks: 40
sides: 1
}
}
}
option {
name: "220ds"
comment: '220kB 5.25" 40-track DSSD'
config {
layout {
tracks: 40
sides: 2
}
}
}
option {
name: "220ss"
comment: '220kB 5.25" 80-track SSSD'
config {
layout {
tracks: 80
sides: 1
}
}
}
option {
name: "440"
comment: '440kB 5.25" 80-track DSSD'
set_by_default: true
config {
layout {
tracks: 80
sides: 2
}
}
}
}

View File

@@ -1,11 +0,0 @@
comment: 'DVK MX 110kB 5.25" (ro)'
include: '_mx'
layout {
tracks: 40
sides: 1
}
tpi: 48

View File

@@ -1,11 +0,0 @@
comment: 'DVK MX 220kB DS 40-track 5.25" (ro)'
include: '_mx'
layout {
tracks: 40
sides: 2
}
tpi: 48

View File

@@ -1,8 +0,0 @@
comment: 'DVK MX 220kB SS 80-track 5.25" (ro)'
include: '_mx'
layout {
tracks: 80
sides: 1
}

View File

@@ -1,8 +0,0 @@
comment: 'DVK MX 440kB 5.25" (ro)'
include: '_mx'
layout {
tracks: 80
sides: 2
}

View File

@@ -0,0 +1,85 @@
comment: 'Northstar family'
image_reader {
filename: "northstar.nsi"
type: NSI
}
image_writer {
filename: "northstar.nsi"
type: NSI
}
layout {
layoutdata {
physical {
start_sector: 0
count: 10
}
}
}
drive {
hard_sector_count: 10
sync_with_index: true
}
encoder {
northstar {}
}
decoder {
northstar {}
}
tpi: 48
option_group {
comment: "Format variant"
option {
name: "87"
comment: '87.5kB 5.25" 35-track SSSD hard-sectored'
config {
layout {
tracks: 40
sides: 1
layoutdata {
sector_size: 256
}
}
}
}
option {
name: "175"
comment: '175kB 5.25" 40-track SSDD hard-sectored'
config {
layout {
tracks: 40
sides: 1
layoutdata {
sector_size: 512
}
}
}
}
option {
name: "350"
comment: '350kB 5.25" 40-track DSDD hard-sectored'
set_by_default: true
config {
layout {
tracks: 40
sides: 2
layoutdata {
sector_size: 512
}
}
}
}
}

View File

@@ -1,12 +0,0 @@
comment: 'Northstar 175kB 5.25" 35-track SSDD hard-sectored'
include: '_northstar'
layout {
tracks: 40
sides: 1
layoutdata {
sector_size: 512
}
}

View File

@@ -1,13 +0,0 @@
comment: 'Northstar 350kB 5.25" 35-track SSDD hard-sectored'
include: '_northstar'
layout {
tracks: 40
sides: 2
layoutdata {
sector_size: 512
}
}

View File

@@ -1,13 +0,0 @@
comment: 'Northstar 87.5kB 5.25" 35-track SSSD hard-sectored'
include: '_northstar'
layout {
tracks: 40
sides: 1
layoutdata {
sector_size: 256
}
}

144
src/formats/tiki.textpb Normal file
View File

@@ -0,0 +1,144 @@
comment: 'Tiki 100 family'
image_writer {
filename: "tiki.img"
type: IMG
}
decoder {
ibm {}
}
tpi: 48
option_group {
comment: "Format variant"
option {
name: "90"
comment: '90kB 40-track 18-sector SSSD'
config {
layout {
tracks: 40
sides: 1
layoutdata {
side: 0
sector_size: 128
physical {
start_sector: 1
count: 18
}
}
}
filesystem {
type: CPMFS
cpmfs {
filesystem_start {
track: 3
}
block_size: 1024
dir_entries: 32
}
}
}
}
option {
name: "200"
comment: '200kB 40-track 10-sector SSSD'
config {
layout {
tracks: 40
sides: 1
layoutdata {
side: 0
sector_size: 512
physical {
start_sector: 1
count: 10
}
}
}
filesystem {
type: CPMFS
cpmfs {
filesystem_start {
track: 2
}
block_size: 1024
dir_entries: 64
}
}
}
}
option {
name: "400"
comment: '400kB 40-track 10-sector DSSD'
config {
layout {
tracks: 40
sides: 2
layoutdata {
sector_size: 512
physical {
start_sector: 1
count: 10
}
}
}
filesystem {
type: CPMFS
cpmfs {
filesystem_start {
side: 0
track: 1
}
block_size: 2048
dir_entries: 128
}
}
}
}
option {
name: "800"
comment: '800kB 80-track 10-sector DSSD'
config {
layout {
tracks: 80
sides: 2
layoutdata {
sector_size: 512
physical {
start_sector: 1
count: 10
}
}
}
filesystem {
type: CPMFS
cpmfs {
filesystem_start {
side: 0
track: 1
}
block_size: 2048
dir_entries: 128
}
}
tpi: 96
}
}
}

View File

@@ -1,38 +0,0 @@
comment: 'Tiki 100 200kB 40-track 10-sector SSSD (ro)'
image_writer {
filename: "tiki200.img"
type: IMG
}
layout {
tracks: 40
sides: 1
layoutdata {
side: 0
sector_size: 512
physical {
start_sector: 1
count: 10
}
}
}
decoder {
ibm {}
}
filesystem {
type: CPMFS
cpmfs {
filesystem_start {
track: 2
}
block_size: 1024
dir_entries: 64
}
}
tpi: 48

View File

@@ -1,38 +0,0 @@
comment: 'Tiki 100 400kB 40-track 10-sector DSSD (ro)'
image_writer {
filename: "tiki400.img"
type: IMG
}
layout {
tracks: 40
sides: 2
layoutdata {
sector_size: 512
physical {
start_sector: 1
count: 10
}
}
}
decoder {
ibm {}
}
filesystem {
type: CPMFS
cpmfs {
filesystem_start {
side: 0
track: 1
}
block_size: 2048
dir_entries: 128
}
}
tpi: 48

View File

@@ -1,38 +0,0 @@
comment: 'Tiki 100 800kB 80-track 10-sector DSSD (ro)'
image_writer {
filename: "tiki900.img"
type: IMG
}
layout {
tracks: 80
sides: 2
layoutdata {
sector_size: 512
physical {
start_sector: 1
count: 10
}
}
}
decoder {
ibm {}
}
filesystem {
type: CPMFS
cpmfs {
filesystem_start {
side: 0
track: 1
}
block_size: 2048
dir_entries: 128
}
}
tpi: 96

View File

@@ -1,38 +0,0 @@
comment: 'Tiki 100 90kB 40-track 18-sector SSSD (ro)'
image_writer {
filename: "tiki90.img"
type: IMG
}
layout {
tracks: 40
sides: 1
layoutdata {
side: 0
sector_size: 128
physical {
start_sector: 1
count: 18
}
}
}
decoder {
ibm {}
}
filesystem {
type: CPMFS
cpmfs {
filesystem_start {
track: 3
}
block_size: 1024
dir_entries: 32
}
}
tpi: 48

View File

@@ -1,12 +1,12 @@
comment: 'Victor 9000 / Sirius One 1224kB DSHD GCR variable sector)'
comment: 'Victor 9000 / Sirius One family'
image_reader {
filename: "victor9k_ds.img"
filename: "victor9k.img"
type: IMG
}
image_writer {
filename: "victor9k_ds.img"
filename: "victor9k.img"
type: IMG
}
@@ -261,3 +261,30 @@ encoder {
decoder {
victor9k {}
}
option_group {
comment: "Format family"
option {
name: "612"
comment: '612kB 80-track DSHD GCR'
config {
layout {
sides: 1
}
}
}
option {
name: "1224"
comment: '1224kB 80-track DSHD GCR'
config {
layout {
sides: 2
}
}
}
}

View File

@@ -1,188 +0,0 @@
comment: 'Victor 9000 / Sirius One 612kB SSHD GCR variable sector)'
image_reader {
filename: "victor9k.img"
type: IMG
}
image_writer {
filename: "victor9k.img"
type: IMG
}
layout {
tracks: 80
sides: 1
layoutdata {
sector_size: 512
physical {
start_sector: 0
}
}
layoutdata {
track: 0
up_to_track: 3
physical {
count: 19
}
}
layoutdata {
track: 4
up_to_track: 15
physical {
count: 18
}
filesystem {
start_sector: 0
count: 18
skew: 4
}
}
layoutdata {
track: 16
up_to_track: 26
physical {
count: 17
}
filesystem {
start_sector: 0
count: 17
skew: 4
}
}
layoutdata {
track: 27
up_to_track: 37
physical {
count: 16
}
filesystem {
start_sector: 0
count: 16
skew: 4
}
}
layoutdata {
track: 38
up_to_track: 47
physical {
count: 15
}
filesystem {
start_sector: 0
count: 15
skew: 4
}
}
layoutdata {
track: 48
up_to_track: 59
physical {
count: 14
}
filesystem {
start_sector: 0
count: 14
skew: 4
}
}
layoutdata {
track: 60
up_to_track: 70
physical {
count: 13
}
filesystem {
start_sector: 0
count: 13
skew: 4
}
}
layoutdata {
track: 71
up_to_track: 79
physical {
count: 12
}
filesystem {
start_sector: 0
count: 12
skew: 4
}
}
}
encoder {
victor9k {
trackdata {
clock_period_us: 2.1367 # 468kHz
post_index_gap_us: 500.0
pre_header_sync_bits: 150
post_header_gap_bits: 60
pre_data_sync_bits: 40
post_data_gap_bits: 300
}
trackdata {
head: 0
min_track: 0
max_track: 3
rotational_period_ms: 237.9
}
trackdata {
head: 0
min_track: 4
max_track: 15
rotational_period_ms: 224.5
}
trackdata {
head: 0
min_track: 16
max_track: 26
rotational_period_ms: 212.2
}
trackdata {
head: 0
min_track: 27
max_track: 37
rotational_period_ms: 199.9
}
trackdata {
head: 0
min_track: 38
max_track: 47
rotational_period_ms: 187.6
}
trackdata {
head: 0
min_track: 48
max_track: 59
rotational_period_ms: 175.3
}
trackdata {
head: 0
min_track: 60
max_track: 70
rotational_period_ms: 163.0
}
trackdata {
head: 0
min_track: 71
max_track: 79
rotational_period_ms: 149.6
}
}
}
decoder {
victor9k {}
}
filesystem {
cpmfs {
filesystem_start {
track: 5
}
block_size: 2048
dir_entries: 128
}
}

View File

@@ -577,89 +577,85 @@ private:
_formatNames[formatChoice->GetSelection()];
FlagGroup::parseConfigFile(formatName, formats);
std::set<std::string> exclusivityGroups;
for (auto& option : config.option())
for (auto& group : config.option_group())
{
if (option.has_exclusivity_group())
exclusivityGroups.insert(option.exclusivity_group());
}
if (config.option().empty())
sizer->Add(new wxStaticText(formatOptionsContainer,
wxID_ANY,
"(no options for this format)"));
else
{
/* Add grouped radiobuttons for anything in an exclusivity
* group. */
group.comment() + ":"));
for (auto& group : exclusivityGroups)
bool first = true;
bool valueSet = false;
wxRadioButton* defaultButton = nullptr;
for (auto& option : group.option())
{
bool first = true;
for (auto& option : config.option())
{
if (option.exclusivity_group() != group)
continue;
auto* rb = new wxRadioButton(formatOptionsContainer,
wxID_ANY,
option.comment());
auto key =
std::make_pair(formatName, option.name());
sizer->Add(rb);
rb->Bind(wxEVT_RADIOBUTTON,
[=](wxCommandEvent& e)
{
for (auto& option : config.option())
{
if (option.exclusivity_group() == group)
_formatOptions.erase(std::make_pair(
formatName, option.name()));
}
_formatOptions.insert(key);
OnControlsChanged(e);
});
if (_formatOptions.find(key) !=
_formatOptions.end())
rb->SetValue(true);
if (first)
rb->SetExtraStyle(wxRB_GROUP);
first = false;
}
}
/* Anything that's _not_ in a group gets a checkbox. */
for (auto& option : config.option())
{
if (option.has_exclusivity_group())
continue;
auto* choice = new wxCheckBox(
formatOptionsContainer, wxID_ANY, option.comment());
auto* rb = new wxRadioButton(formatOptionsContainer,
wxID_ANY,
option.comment(),
wxDefaultPosition,
wxDefaultSize,
first ? wxRB_GROUP : 0);
auto key = std::make_pair(formatName, option.name());
sizer->Add(choice);
sizer->Add(rb);
if (_formatOptions.find(key) != _formatOptions.end())
choice->SetValue(true);
choice->Bind(wxEVT_CHECKBOX,
rb->Bind(wxEVT_RADIOBUTTON,
[=](wxCommandEvent& e)
{
if (choice->GetValue())
_formatOptions.insert(key);
else
_formatOptions.erase(key);
for (auto& option : group.option())
{
_formatOptions.erase(std::make_pair(
formatName, option.name()));
}
_formatOptions.insert(key);
OnControlsChanged(e);
});
if (_formatOptions.find(key) != _formatOptions.end())
{
rb->SetValue(true);
valueSet = true;
}
if (option.set_by_default() || !defaultButton)
defaultButton = rb;
first = false;
}
if (!valueSet && defaultButton)
defaultButton->SetValue(true);
}
/* Anything that's _not_ in a group gets a checkbox. */
for (auto& option : config.option())
{
auto* choice = new wxCheckBox(
formatOptionsContainer, wxID_ANY, option.comment());
auto key = std::make_pair(formatName, option.name());
sizer->Add(choice);
choice->SetValue(
(_formatOptions.find(key) != _formatOptions.end()) ||
option.set_by_default());
choice->Bind(wxEVT_CHECKBOX,
[=](wxCommandEvent& e)
{
if (choice->GetValue())
_formatOptions.insert(key);
else
_formatOptions.erase(key);
OnControlsChanged(e);
});
}
if (config.option().empty() && config.option_group().empty())
sizer->Add(new wxStaticText(formatOptionsContainer,
wxID_ANY,
"(no options for this format)"));
}
formatOptionsContainer->SetSizerAndFit(sizer);