This commit is contained in:
David Given
2022-02-06 22:14:11 +01:00
14 changed files with 312 additions and 25 deletions

View File

@@ -6,6 +6,7 @@
#include "micropolis.h"
#include "bytes.h"
#include "fmt/format.h"
#include "lib/decoders/decoders.pb.h"
/* The sector has a preamble of MFM 0x00s and uses 0xFF as a sync pattern. */
static const FluxPattern SECTOR_SYNC_PATTERN(32, 0xaaaa5555);
@@ -28,7 +29,8 @@ class MicropolisDecoder : public AbstractDecoder
{
public:
MicropolisDecoder(const DecoderProto& config):
AbstractDecoder(config)
AbstractDecoder(config),
_config(config.micropolis())
{}
RecordType advanceToNextRecord()
@@ -59,13 +61,21 @@ public:
return;
br.read(10); /* OS data or padding */
_sector->data = br.read(256);
auto data = br.read(256);
uint8_t wantChecksum = br.read_8();
uint8_t gotChecksum = micropolisChecksum(bytes.slice(1, 2+266));
br.read(5); /* 4 byte ECC and ECC-present flag */
if (_config.sector_output_size() == 256)
_sector->data = data;
else if (_config.sector_output_size() == MICROPOLIS_ENCODED_SECTOR_SIZE)
_sector->data = bytes;
else
Error() << "Sector output size may only be 256 or 275";
_sector->status = (wantChecksum == gotChecksum) ? Sector::OK : Sector::BAD_CHECKSUM;
}
private:
const MicropolisDecoderProto& _config;
};
std::unique_ptr<AbstractDecoder> createMicropolisDecoder(const DecoderProto& config)

View File

@@ -19,7 +19,15 @@ static void write_sector(std::vector<bool>& bits, unsigned& cursor, const std::s
fullSector->push_back(0);
Bytes sectorData;
if (sector->data.size() == MICROPOLIS_ENCODED_SECTOR_SIZE)
{
if (sector->data[0] != 0xFF)
Error() << "275 byte sector doesn't start with sync byte 0xFF. Corrupted sector";
uint8_t wantChecksum = sector->data[1+2+266];
uint8_t gotChecksum = micropolisChecksum(sector->data.slice(1, 2+266));
if (wantChecksum != gotChecksum)
std::cerr << "Warning: checksum incorrect. Sector: " << sector->logicalSector << std::endl;
sectorData = sector->data;
}
else
{
ByteWriter writer(sectorData);

View File

@@ -1,5 +1,11 @@
syntax = "proto2";
message MicropolisDecoderProto {}
import "lib/common.proto";
message MicropolisDecoderProto {
optional int32 sector_output_size = 1 [default = 256,
(help) = "How much of the raw sector should be saved. Must be 256 or 275"];
}
message MicropolisEncoderProto {}

View File

@@ -1,10 +1,11 @@
Disk: Micropolis
================
Micropolis MetaFloppy disks use MFM and hard sectors. They were 100 TPI and
stored 315k per side. Each of the 16 sectors contains 266 bytes of "user data,"
allowing 10 bytes of metadata for use by the operating system. Micropolis DOS
(MDOS) used the metadata bytes, but CP/M did not.
Micropolis MetaFloppy disks use MFM and hard sectors. Mod I was 48 TPI and
stored 143k per side. Mod II was 100 TPI and stored 315k per side. Each of the
16 sectors contains 266 bytes of "user data," allowing 10 bytes of metadata for
use by the operating system. Micropolis DOS (MDOS) used the metadata bytes, but
CP/M did not.
Some later systems were Micropolis-compatible and so were also 100 TPI, like
the Vector Graphic Dual-Mode Disk Controller which was paired with a Tandon
@@ -19,31 +20,55 @@ pinout as a 96tpi PC 5.25" drive. In use they should be identical.
Reading disks
-------------
Just do:
Based on your floppy drive, just do one of:
```
fluxengine read micropolis
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
```
You should end up with a `micropolis.img` which is 630784 bytes long (for a
normal DD disk). The image is written in CHS order, but HCS is generally used
by CP/M tools so the image needs to be post-processed. For only half-full disks
or single-sided disks, you can use `--heads 0` to read only one side of the
disk which works around the problem.
You should end up with a `micropolis.img` of the corresponding size. The image
is written in CHS order, but HCS is generally used by CP/M tools so
double-sided disk images may need to be post-processed. Half-full double-sided
disks can be read as single-sided disks to work around the problem.
The [CP/M BIOS](https://www.seasip.info/Cpm/bios.html) defined SELDSK, SETTRK,
and SETSEC, but no function to select the head/side. Double-sided floppies
could be represented as having either twice the number of sectors, for CHS, or
twice the number of tracks, HCS; the second side's tracks logically followed
the first side (e.g., tracks 77-153). Micropolis disks tended to be the latter.
twice the number of tracks, HCS; the second side's tracks in opposite order
logically followed the first side (e.g., tracks 77-153). Micropolis disks
tended to be the latter.
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
```
You should end up with a `micropolis.vgi` instead. The format is well-defined
for double-sided disks so post-processing is not necessary.
Writing disks
-------------
Just do:
Just do one of:
```
fluxengine write micropolis -i micropolis.img
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 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
```
Useful references

View File

@@ -9,9 +9,10 @@ import "lib/fluxsink/fluxsink.proto";
import "lib/usb/usb.proto";
import "lib/common.proto";
// NEXT_TAG: 13
// NEXT_TAG: 14
message ConfigProto {
optional string comment = 8;
optional bool is_extension = 13;
optional ImageReaderProto image_reader = 12;
optional ImageWriterProto image_writer = 9;

View File

@@ -71,6 +71,7 @@ void ImageReader::updateConfigForFilename(ImageReaderProto* proto, const std::st
{".nfd", [&]() { proto->mutable_nfd(); }},
{".nsi", [&]() { proto->mutable_nsi(); }},
{".td0", [&]() { proto->mutable_td0(); }},
{".vgi", [&]() { proto->mutable_img(); }},
{".xdf", [&]() { proto->mutable_img(); }},
};

View File

@@ -51,6 +51,7 @@ void ImageWriter::updateConfigForFilename(ImageWriterProto* proto, const std::st
{".nsi", [&]() { proto->mutable_nsi(); }},
{".raw", [&]() { proto->mutable_raw(); }},
{".st", [&]() { proto->mutable_img(); }},
{".vgi", [&]() { proto->mutable_img(); }},
{".xdf", [&]() { proto->mutable_img(); }},
};

View File

@@ -518,13 +518,17 @@ FORMATS="\
ibm720_525 \
mac400 \
mac800 \
micropolis \
micropolis143 \
micropolis287 \
micropolis315 \
micropolis630 \
mx \
n88basic \
northstar175 \
northstar350 \
northstar87 \
tids990 \
vgi \
victor9k_ss \
victor9k_ds \
zilogmcz \

View File

@@ -101,7 +101,7 @@ static void globalHelp()
void showProfiles(const std::string& command, const std::map<std::string, std::string>& profiles)
{
std::cout << "syntax: fluxengine " << command << " <profile> [<options>...]\n"
std::cout << "syntax: fluxengine " << command << " <profile> [<extensions...>] [<options>...]\n"
"Use --help for option help.\n"
"Available profiles include:\n";
@@ -110,10 +110,22 @@ void showProfiles(const std::string& command, const std::map<std::string, std::s
ConfigProto config;
if (!config.ParseFromString(it.second))
Error() << "couldn't load config proto";
std::cout << fmt::format(" {}: {}\n", it.first, config.comment());
if (!config.is_extension())
std::cout << fmt::format(" {}: {}\n", it.first, config.comment());
}
std::cout << "Or use a text file containing your own configuration.\n";
std::cout << "Available profile options include:\n";
for (const auto& it : profiles)
{
ConfigProto config;
if (!config.ParseFromString(it.second))
Error() << "couldn't load config proto";
if (config.is_extension())
std::cout << fmt::format(" {}: {}\n", it.first, config.comment());
}
std::cout << "Profiles and extensions may also be textpb files .\n";
exit(1);
}

View File

@@ -0,0 +1,61 @@
comment: 'Micropolis MetaFloppy Mod I 143kB 5.25" SSDD hard-sectored'
flux_source {
drive {
hard_sector_count: 16
}
}
flux_sink {
drive {
hard_sector_count: 16
}
}
image_reader {
filename: "micropolis.img"
img {
tracks: 35
sides: 1
trackdata {
sector_size: 256
sector_range {
start_sector: 0
sector_count: 16
}
}
}
}
image_writer {
filename: "micropolis.img"
img {
tracks: 35
trackdata {
sector_size: 256
sector_range {
start_sector: 0
sector_count: 16
}
}
}
}
encoder {
micropolis {}
}
decoder {
micropolis {}
}
cylinders {
start: 0
end: 34
}
heads {
start: 0
end: 0
}

View File

@@ -0,0 +1,61 @@
comment: 'Micropolis MetaFloppy Mod I 287kB 5.25" DSDD hard-sectored'
flux_source {
drive {
hard_sector_count: 16
}
}
flux_sink {
drive {
hard_sector_count: 16
}
}
image_reader {
filename: "micropolis.img"
img {
tracks: 35
sides: 2
trackdata {
sector_size: 256
sector_range {
start_sector: 0
sector_count: 16
}
}
}
}
image_writer {
filename: "micropolis.img"
img {
tracks: 35
trackdata {
sector_size: 256
sector_range {
start_sector: 0
sector_count: 16
}
}
}
}
encoder {
micropolis {}
}
decoder {
micropolis {}
}
cylinders {
start: 0
end: 34
}
heads {
start: 0
end: 1
}

View File

@@ -0,0 +1,61 @@
comment: 'Micropolis MetaFloppy Mod II 315kB 5.25" SSDD hard-sectored'
flux_source {
drive {
hard_sector_count: 16
}
}
flux_sink {
drive {
hard_sector_count: 16
}
}
image_reader {
filename: "micropolis.img"
img {
tracks: 77
sides: 1
trackdata {
sector_size: 256
sector_range {
start_sector: 0
sector_count: 16
}
}
}
}
image_writer {
filename: "micropolis.img"
img {
tracks: 77
trackdata {
sector_size: 256
sector_range {
start_sector: 0
sector_count: 16
}
}
}
}
encoder {
micropolis {}
}
decoder {
micropolis {}
}
cylinders {
start: 0
end: 76
}
heads {
start: 0
end: 0
}

View File

@@ -1,4 +1,4 @@
comment: 'Micropolis MetaFloppy 630kB 5.25" DSDD hard-sectored'
comment: 'Micropolis MetaFloppy Mod II 630kB 5.25" DSDD hard-sectored'
flux_source {
drive {
@@ -29,7 +29,16 @@ image_reader {
image_writer {
filename: "micropolis.img"
img {}
img {
tracks: 77
trackdata {
sector_size: 256
sector_range {
start_sector: 0
sector_count: 16
}
}
}
}
encoder {

27
src/formats/vgi.textpb Normal file
View File

@@ -0,0 +1,27 @@
comment: 'Adjust a Micropolis profile to use the VGI image format'
is_extension: true
image_reader {
filename: "micropolis.vgi"
img {
trackdata {
sector_size: 275
}
}
}
image_writer {
filename: "micropolis.vgi"
img {
trackdata {
sector_size: 275
}
}
}
decoder {
micropolis {
sector_output_size: 275
}
}