mirror of
https://github.com/davidgiven/fluxengine.git
synced 2025-10-31 11:17:01 -07:00
Add support for North Star hard-sectored floppies.
North Star Floppy disks use 10-sector hard sectored disks with either FM or MFM encoding. They may be single- or double-sided. Each of the 10 sectors contains 256 (FM) or 512 (MFM) bytes of data. The disk has 35 cylinders, with tracks 0-34 on side 0, and tracks 35-69 on side 1. Tracks on side 1 are numbered "backwards" in that track 35 corresponds to cylinder 34, side 1, and track 69 corresponds to cylinder 0, side 1. The North Star sector format does not include any head positioning information. As such, reads from North Star floppies need to be synchronized with the index pulse, in order to properly identify the sector being read. While there is a command line option: --sync-with-index, that does this, the North Star reader forces this behavior by default. MFM sectors have 32 bytes of 00's followed by two sync characters, specified in the North Star MDS manual as 0xFBFB. This is true for most disks; however, I found a few disks, including an original North Star DOS/BASIC v2.2.1 DQ disk, that uses 0xFBnn, where nn is an incrementing pattern. While searching for the start of a sector header, seekToPattern() ignores the sector pulses. If a sector header cannot be decoded for any reason, seekToPattern() will advance past one or more sector pulses. For this reason, the _hardSectorId is calulated after the sector header is found. Due to the nature of the track ordering on side 1, an .nsi image reader and writer are provided for double-sided disks. The .nsi image format supports both single- and double-sided disks; however, single-sided .nsi images are equivalent to .img images.
This commit is contained in:
175
arch/northstar/decoder.cc
Normal file
175
arch/northstar/decoder.cc
Normal file
@@ -0,0 +1,175 @@
|
||||
/* Decoder for North Star 10-sector hard-sectored disks.
|
||||
*
|
||||
* Supports both single- and double-density. For the sector format and
|
||||
* checksum algorithm, see pp. 33 of the North Star Double Density Controller
|
||||
* manual:
|
||||
*
|
||||
* http://bitsavers.org/pdf/northstar/boards/Northstar_MDS-A-D_1978.pdf
|
||||
*
|
||||
* North Star disks do not contain any track/head/sector information
|
||||
* encoded in the sector record. For this reason, we have to be absolutely
|
||||
* sure that the hardSectorId is correct.
|
||||
*/
|
||||
|
||||
#include "globals.h"
|
||||
#include "fluxmap.h"
|
||||
#include "decoders/fluxmapreader.h"
|
||||
#include "decoders/decoders.h"
|
||||
#include "sector.h"
|
||||
#include "northstar.h"
|
||||
#include "bytes.h"
|
||||
#include "fmt/format.h"
|
||||
|
||||
/*
|
||||
* MFM sectors have 32 bytes of 00's followed by two sync characters,
|
||||
* specified in the North Star MDS manual as 0xFBFB.
|
||||
*
|
||||
* This is true for most disks; however, I found a few disks, including an
|
||||
* original North Star DOS/BASIC v2.2.1 DQ disk) that uses 0xFBnn, where
|
||||
* nn is an incrementing pattern.
|
||||
*
|
||||
* 00 00 00 F B
|
||||
* 0000 0000 0000 0000 0000 0000 0101 0101 0100 0101
|
||||
* A A A A A A 5 5 4 5
|
||||
*/
|
||||
static const FluxPattern MFM_PATTERN(64, 0xAAAAAAAAAAAA5545LL);
|
||||
|
||||
/* FM sectors have 16 bytes of 00's followed by 0xFB.
|
||||
* 00 FB
|
||||
* 0000 0000 1111 1111 1110 1111
|
||||
* A A F F E F
|
||||
*/
|
||||
static const FluxPattern FM_PATTERN(64, 0xAAAAAAAAAAAAFFEFLL);
|
||||
|
||||
const FluxMatchers ANY_SECTOR_PATTERN(
|
||||
{
|
||||
&MFM_PATTERN,
|
||||
&FM_PATTERN,
|
||||
}
|
||||
);
|
||||
|
||||
/* Search for FM or MFM sector record */
|
||||
AbstractDecoder::RecordType NorthstarDecoder::advanceToNextRecord()
|
||||
{
|
||||
nanoseconds_t now = _fmr->tell().ns();
|
||||
|
||||
/* For all but the first sector, seek to the next sector pulse.
|
||||
* The first sector does not contain the sector pulse in the fluxmap.
|
||||
*/
|
||||
if (now != 0) {
|
||||
_fmr->seekToIndexMark();
|
||||
now = _fmr->tell().ns();
|
||||
}
|
||||
|
||||
/* Discard a possible partial sector at the end of the track.
|
||||
* This partial sector could be mistaken for a conflicted sector, if
|
||||
* whatever data read happens to match the checksum of 0, which is
|
||||
* rare, but has been observed on some disks.
|
||||
*/
|
||||
if (now > (_fmr->getDuration() - 21e6)) {
|
||||
_fmr->seekToIndexMark();
|
||||
return(UNKNOWN_RECORD);
|
||||
}
|
||||
|
||||
int msSinceIndex = std::round(now / 1e6);
|
||||
|
||||
const FluxMatcher* matcher = nullptr;
|
||||
|
||||
/* Note that the seekToPattern ignores the sector pulses, so if
|
||||
* a sector is not found for some reason, the seek will advance
|
||||
* past one or more sector pulses. For this reason, calculate
|
||||
* _hardSectorId after the sector header is found.
|
||||
*/
|
||||
_sector->clock = _fmr->seekToPattern(ANY_SECTOR_PATTERN, matcher);
|
||||
|
||||
int sectorFoundTimeRaw = std::round((_fmr->tell().ns()) / 1e6);
|
||||
int sectorFoundTime;
|
||||
|
||||
/* Round time to the nearest 20ms */
|
||||
if ((sectorFoundTimeRaw % 20) < 10) {
|
||||
sectorFoundTime = (sectorFoundTimeRaw / 20) * 20;
|
||||
}
|
||||
else {
|
||||
sectorFoundTime = ((sectorFoundTimeRaw + 20) / 20) * 20;
|
||||
}
|
||||
|
||||
/* Calculate the sector ID based on time since the index */
|
||||
_hardSectorId = (sectorFoundTime / 20) % 10;
|
||||
|
||||
// std::cout << fmt::format(
|
||||
// "Sector ID {}: hole at {}ms, sector start at {}ms",
|
||||
// _hardSectorId, msSinceIndex, sectorFoundTimeRaw) << std::endl;
|
||||
|
||||
if (matcher == &MFM_PATTERN) {
|
||||
_sectorType = SECTOR_TYPE_MFM;
|
||||
readRawBits(48);
|
||||
return SECTOR_RECORD;
|
||||
}
|
||||
|
||||
if (matcher == &FM_PATTERN) {
|
||||
_sectorType = SECTOR_TYPE_FM;
|
||||
readRawBits(48);
|
||||
return SECTOR_RECORD;
|
||||
}
|
||||
|
||||
return UNKNOWN_RECORD;
|
||||
}
|
||||
|
||||
/* Checksum is initially 0.
|
||||
* For each data byte, XOR with the current checksum.
|
||||
* Rotate checksum left, carrying bit 7 to bit 0.
|
||||
*/
|
||||
uint8_t northstarChecksum(const Bytes& bytes) {
|
||||
ByteReader br(bytes);
|
||||
uint8_t checksum = 0;
|
||||
|
||||
while (!br.eof()) {
|
||||
checksum ^= br.read_8();
|
||||
checksum = ((checksum << 1) | ((checksum >> 7)));
|
||||
}
|
||||
|
||||
return checksum;
|
||||
}
|
||||
|
||||
void NorthstarDecoder::decodeSectorRecord()
|
||||
{
|
||||
unsigned recordSize, payloadSize, headerSize;
|
||||
|
||||
if (_sectorType == SECTOR_TYPE_MFM) {
|
||||
recordSize = NORTHSTAR_ENCODED_SECTOR_SIZE_DD;
|
||||
payloadSize = NORTHSTAR_PAYLOAD_SIZE_DD;
|
||||
headerSize = NORTHSTAR_HEADER_SIZE_DD;
|
||||
}
|
||||
else {
|
||||
recordSize = NORTHSTAR_ENCODED_SECTOR_SIZE_SD;
|
||||
payloadSize = NORTHSTAR_PAYLOAD_SIZE_SD;
|
||||
headerSize = NORTHSTAR_HEADER_SIZE_SD;
|
||||
}
|
||||
|
||||
auto rawbits = readRawBits(recordSize * 16);
|
||||
auto bytes = decodeFmMfm(rawbits).slice(0, recordSize);
|
||||
ByteReader br(bytes);
|
||||
uint8_t sync_char;
|
||||
|
||||
_sector->logicalSide = _sector->physicalSide;
|
||||
_sector->logicalSector = _hardSectorId;
|
||||
_sector->logicalTrack = _sector->physicalTrack;
|
||||
|
||||
sync_char = br.read_8(); /* Sync char: 0xFB */
|
||||
if (_sectorType == SECTOR_TYPE_MFM) {
|
||||
sync_char = br.read_8();/* MFM second Sync char, usually 0xFB */
|
||||
}
|
||||
|
||||
_sector->data = br.read(payloadSize);
|
||||
|
||||
uint8_t wantChecksum = br.read_8();
|
||||
uint8_t gotChecksum = northstarChecksum(bytes.slice(headerSize, payloadSize));
|
||||
|
||||
_sector->status = (wantChecksum == gotChecksum) ? Sector::OK : Sector::BAD_CHECKSUM;
|
||||
}
|
||||
|
||||
std::set<unsigned> NorthstarDecoder::requiredSectors(Track& track) const
|
||||
{
|
||||
static std::set<unsigned> sectors = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
|
||||
return sectors;
|
||||
}
|
||||
132
arch/northstar/encoder.cc
Normal file
132
arch/northstar/encoder.cc
Normal file
@@ -0,0 +1,132 @@
|
||||
#include "globals.h"
|
||||
#include "northstar.h"
|
||||
#include "sectorset.h"
|
||||
|
||||
FlagGroup northstarEncoderFlags;
|
||||
|
||||
#define GAP_FILL_SIZE_SD 30
|
||||
#define PRE_HEADER_GAP_FILL_SIZE_SD 9
|
||||
#define GAP_FILL_SIZE_DD 62
|
||||
#define PRE_HEADER_GAP_FILL_SIZE_DD 16
|
||||
|
||||
#define GAP1_FILL_BYTE (0x4F)
|
||||
#define GAP2_FILL_BYTE (0x4F)
|
||||
|
||||
#define TOTAL_SECTOR_BYTES ()
|
||||
|
||||
static void write_sector(std::vector<bool>& bits, unsigned& cursor, const Sector* sector)
|
||||
{
|
||||
int preambleSize = 0;
|
||||
int encodedSectorSize = 0;
|
||||
int gapFillSize = 0;
|
||||
int preHeaderGapFillSize = 0;
|
||||
|
||||
bool doubleDensity;
|
||||
|
||||
switch (sector->data.size()) {
|
||||
case NORTHSTAR_PAYLOAD_SIZE_SD:
|
||||
preambleSize = NORTHSTAR_PREAMBLE_SIZE_SD;
|
||||
encodedSectorSize = PRE_HEADER_GAP_FILL_SIZE_SD + NORTHSTAR_ENCODED_SECTOR_SIZE_SD + GAP_FILL_SIZE_SD;
|
||||
gapFillSize = GAP_FILL_SIZE_SD;
|
||||
preHeaderGapFillSize = PRE_HEADER_GAP_FILL_SIZE_SD;
|
||||
doubleDensity = false;
|
||||
break;
|
||||
case NORTHSTAR_PAYLOAD_SIZE_DD:
|
||||
preambleSize = NORTHSTAR_PREAMBLE_SIZE_DD;
|
||||
encodedSectorSize = PRE_HEADER_GAP_FILL_SIZE_DD + NORTHSTAR_ENCODED_SECTOR_SIZE_DD + GAP_FILL_SIZE_DD;
|
||||
gapFillSize = GAP_FILL_SIZE_DD;
|
||||
preHeaderGapFillSize = PRE_HEADER_GAP_FILL_SIZE_DD;
|
||||
doubleDensity = true;
|
||||
break;
|
||||
default:
|
||||
Error() << "unsupported sector size --- you must pick 256 or 512";
|
||||
break;
|
||||
}
|
||||
|
||||
int fullSectorSize = preambleSize + encodedSectorSize;
|
||||
auto fullSector = std::make_shared<std::vector<uint8_t>>();
|
||||
fullSector->reserve(fullSectorSize);
|
||||
|
||||
/* sector gap after index pulse */
|
||||
for (int i = 0; i < preHeaderGapFillSize; i++)
|
||||
fullSector->push_back(GAP1_FILL_BYTE);
|
||||
|
||||
/* sector preamble */
|
||||
for (int i = 0; i < preambleSize; i++)
|
||||
fullSector->push_back(0);
|
||||
|
||||
Bytes sectorData;
|
||||
if (sector->data.size() == encodedSectorSize)
|
||||
sectorData = sector->data;
|
||||
else {
|
||||
ByteWriter writer(sectorData);
|
||||
writer.write_8(0xFB); /* sync character */
|
||||
if (doubleDensity == true) {
|
||||
writer.write_8(0xFB); /* Double-density has two sync characters */
|
||||
}
|
||||
writer += sector->data;
|
||||
if (doubleDensity == true) {
|
||||
writer.write_8(northstarChecksum(sectorData.slice(2)));
|
||||
} else {
|
||||
writer.write_8(northstarChecksum(sectorData.slice(1)));
|
||||
}
|
||||
}
|
||||
for (uint8_t b : sectorData)
|
||||
fullSector->push_back(b);
|
||||
|
||||
if (sector->logicalSector != 9) {
|
||||
/* sector postamble */
|
||||
for (int i = 0; i < gapFillSize; i++)
|
||||
fullSector->push_back(GAP2_FILL_BYTE);
|
||||
|
||||
if (fullSector->size() != fullSectorSize)
|
||||
Error() << "sector mismatched length (" << sector->data.size() << ") expected: " << fullSector->size() << " got " << fullSectorSize;
|
||||
} else {
|
||||
/* sector postamble */
|
||||
for (int i = 0; i < gapFillSize; i++)
|
||||
fullSector->push_back(GAP2_FILL_BYTE);
|
||||
}
|
||||
|
||||
bool lastBit = false;
|
||||
|
||||
if (doubleDensity == true) {
|
||||
encodeMfm(bits, cursor, fullSector, lastBit);
|
||||
}
|
||||
else {
|
||||
encodeFm(bits, cursor, fullSector);
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<Fluxmap> NorthstarEncoder::encode(
|
||||
int physicalTrack, int physicalSide, const SectorSet& allSectors)
|
||||
{
|
||||
int bitsPerRevolution = 100000;
|
||||
double clockRateUs = 4.00;
|
||||
|
||||
if ((physicalTrack < 0) || (physicalTrack >= 35))
|
||||
return std::unique_ptr<Fluxmap>();
|
||||
|
||||
const auto& sector = allSectors.get(physicalTrack, physicalSide, 0);
|
||||
|
||||
if (sector->data.size() == NORTHSTAR_PAYLOAD_SIZE_SD) {
|
||||
bitsPerRevolution /= 2; // FM
|
||||
} else {
|
||||
clockRateUs /= 2.00;
|
||||
}
|
||||
|
||||
std::vector<bool> bits(bitsPerRevolution);
|
||||
unsigned cursor = 0;
|
||||
|
||||
for (int sectorId = 0; sectorId < 10; sectorId++)
|
||||
{
|
||||
const auto& sectorData = allSectors.get(physicalTrack, physicalSide, sectorId);
|
||||
write_sector(bits, cursor, sectorData);
|
||||
}
|
||||
|
||||
if (cursor > bits.size())
|
||||
Error() << "track data overrun";
|
||||
|
||||
std::unique_ptr<Fluxmap> fluxmap(new Fluxmap);
|
||||
fluxmap->appendBits(bits, clockRateUs * 1e3);
|
||||
return fluxmap;
|
||||
}
|
||||
60
arch/northstar/northstar.h
Normal file
60
arch/northstar/northstar.h
Normal file
@@ -0,0 +1,60 @@
|
||||
#ifndef NORTHSTAR_H
|
||||
#define NORTHSTAR_H
|
||||
|
||||
/* Northstar floppies are 10-hard sectored disks with a sector format as follows:
|
||||
*
|
||||
* |----------------------------------|
|
||||
* | SYNC Byte | Payload | Checksum |
|
||||
* |------------+----------+----------|
|
||||
* | 1 (0xFB) | 256 (SD) | 1 |
|
||||
* | 2 (0xFBFB) | 512 (DD) | |
|
||||
* |----------------------------------|
|
||||
*
|
||||
*/
|
||||
|
||||
#include "decoders/decoders.h"
|
||||
#include "encoders/encoders.h"
|
||||
|
||||
#define NORTHSTAR_PREAMBLE_SIZE_SD (16)
|
||||
#define NORTHSTAR_PREAMBLE_SIZE_DD (32)
|
||||
#define NORTHSTAR_HEADER_SIZE_SD (1)
|
||||
#define NORTHSTAR_HEADER_SIZE_DD (2)
|
||||
#define NORTHSTAR_PAYLOAD_SIZE_SD (256)
|
||||
#define NORTHSTAR_PAYLOAD_SIZE_DD (512)
|
||||
#define NORTHSTAR_CHECKSUM_SIZE (1)
|
||||
#define NORTHSTAR_ENCODED_SECTOR_SIZE_SD (NORTHSTAR_HEADER_SIZE_SD + NORTHSTAR_PAYLOAD_SIZE_SD + NORTHSTAR_CHECKSUM_SIZE)
|
||||
#define NORTHSTAR_ENCODED_SECTOR_SIZE_DD (NORTHSTAR_HEADER_SIZE_DD + NORTHSTAR_PAYLOAD_SIZE_DD + NORTHSTAR_CHECKSUM_SIZE)
|
||||
|
||||
#define SECTOR_TYPE_MFM (0)
|
||||
#define SECTOR_TYPE_FM (1)
|
||||
|
||||
class NorthstarDecoder : public AbstractDecoder
|
||||
{
|
||||
public:
|
||||
NorthstarDecoder()
|
||||
{
|
||||
_sectorType = SECTOR_TYPE_MFM;
|
||||
}
|
||||
|
||||
virtual ~NorthstarDecoder() {}
|
||||
|
||||
RecordType advanceToNextRecord();
|
||||
void decodeSectorRecord();
|
||||
std::set<unsigned> requiredSectors(Track& track) const;
|
||||
|
||||
private:
|
||||
uint8_t _sectorType;
|
||||
uint8_t _hardSectorId;
|
||||
};
|
||||
|
||||
class NorthstarEncoder : public AbstractEncoder
|
||||
{
|
||||
public:
|
||||
virtual ~NorthstarEncoder() {}
|
||||
std::unique_ptr<Fluxmap> encode(int physicalTrack, int physicalSide, const SectorSet& allSectors);
|
||||
};
|
||||
|
||||
extern FlagGroup northstarEncoderFlags;
|
||||
extern uint8_t northstarChecksum(const Bytes& bytes);
|
||||
|
||||
#endif /* NORTHSTAR */
|
||||
72
doc/disk-northstar.md
Normal file
72
doc/disk-northstar.md
Normal file
@@ -0,0 +1,72 @@
|
||||
Disk: Northstar
|
||||
================
|
||||
|
||||
Northstar Floppy disks use 10-sector hard sectored disks with either FM or MFM
|
||||
encoding. They may be single- or double-sided. Each of the 10 sectors contains
|
||||
256 (FM) or 512 (MFM) bytes of data. The disk has 35 cylinders, with tracks 0-
|
||||
34 on side 0, and tracks 35-69 on side 1. Tracks on side 1 are numbered "back-
|
||||
wards" in that track 35 corresponds to cylinder 34, side 1, and track 69
|
||||
corresponds to cylinder 0, side 1.
|
||||
|
||||
The Northstar sector format does not include any head positioning information.
|
||||
As such, reads from Northstar floppies need to by synchronized with the index
|
||||
pulse, in order to properly identify the sector being read. This is handled
|
||||
automatically by FluxEngine.
|
||||
|
||||
Due to the nature of the track ordering on side 1, an .nsi image reader and
|
||||
writer are provided for double-sided disks. The .nsi image writer supports
|
||||
both single- and double-sided disks; however single-sided .nsi images are
|
||||
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 northstar
|
||||
```
|
||||
|
||||
To read a single-sided North Star floppy, run:
|
||||
|
||||
```
|
||||
fluxengine read northstar -s:0
|
||||
```
|
||||
|
||||
You should end up with a `northstar.nsi` with a file size dependent on the floppy
|
||||
disk type:
|
||||
|
||||
| Disk Type | File Size (bytes) |
|
||||
| ----------------------------------- | ------- |
|
||||
| Single-Sided, Single-Density (SSSD) | 89,600 |
|
||||
| Single-Sided, Double-Density (SSDD) | 179,200 |
|
||||
| Double-Sided, Double-Density (DSDD) | 358,400 |
|
||||
|
||||
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 northstar -i image_to_write.nsi
|
||||
```
|
||||
|
||||
To write a single-sided North Star floppy, run:
|
||||
|
||||
```
|
||||
fluxengine write northstar -i image_to_write.nsi -d:s=0
|
||||
```
|
||||
|
||||
|
||||
Useful references
|
||||
-----------------
|
||||
|
||||
- [MICRO-DISK SYSTEM MDS-A-D DOUBLE DENSITY Manual][northstar_mds].
|
||||
Page 33 documents sector format for single- and double-density.
|
||||
|
||||
[northstar_mds]: http://bitsavers.org/pdf/northstar/boards/Northstar_MDS-A-D_1978.pdf
|
||||
@@ -96,6 +96,11 @@ public:
|
||||
_pos = pos;
|
||||
}
|
||||
|
||||
int getDuration(void)
|
||||
{
|
||||
return (_fluxmap.duration());
|
||||
}
|
||||
|
||||
uint8_t getNextEvent(unsigned& ticks);
|
||||
unsigned findEvent(uint8_t bits);
|
||||
unsigned readInterval(nanoseconds_t clock); /* with debounce support */
|
||||
|
||||
@@ -18,6 +18,7 @@ std::map<std::string, ImageReader::Constructor> ImageReader::formats =
|
||||
{".ima", ImageReader::createImgImageReader},
|
||||
{".jv1", ImageReader::createImgImageReader},
|
||||
{".jv3", ImageReader::createJv3ImageReader},
|
||||
{".nsi", ImageReader::createNsiImageReader},
|
||||
{".st", ImageReader::createImgImageReader},
|
||||
{".imd", ImageReader::createIMDImageReader},
|
||||
{".IMD", ImageReader::createIMDImageReader},
|
||||
|
||||
@@ -27,6 +27,7 @@ private:
|
||||
static std::unique_ptr<ImageReader> createImgImageReader(const ImageSpec& spec);
|
||||
static std::unique_ptr<ImageReader> createJv3ImageReader(const ImageSpec& spec);
|
||||
static std::unique_ptr<ImageReader> createIMDImageReader(const ImageSpec& spec);
|
||||
static std::unique_ptr<ImageReader> createNsiImageReader(const ImageSpec& spec);
|
||||
|
||||
static Constructor findConstructor(const ImageSpec& spec);
|
||||
|
||||
|
||||
104
lib/imagereader/nsiimagereader.cc
Normal file
104
lib/imagereader/nsiimagereader.cc
Normal file
@@ -0,0 +1,104 @@
|
||||
/* Image reader for Northstar floppy disk images */
|
||||
|
||||
#include "globals.h"
|
||||
#include "flags.h"
|
||||
#include "dataspec.h"
|
||||
#include "sector.h"
|
||||
#include "sectorset.h"
|
||||
#include "imagereader/imagereader.h"
|
||||
#include "fmt/format.h"
|
||||
#include <algorithm>
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
|
||||
class NsiImageReader : public ImageReader
|
||||
{
|
||||
public:
|
||||
NsiImageReader(const ImageSpec& spec):
|
||||
ImageReader(spec)
|
||||
{}
|
||||
|
||||
SectorSet readImage()
|
||||
{
|
||||
std::ifstream inputFile(spec.filename, std::ios::in | std::ios::binary);
|
||||
if (!inputFile.is_open())
|
||||
Error() << "cannot open input file";
|
||||
|
||||
if ((spec.cylinders == 0) && (spec.heads == 0) && (spec.sectors == 0) && (spec.bytes == 0)) {
|
||||
const auto begin = inputFile.tellg();
|
||||
inputFile.seekg(0, std::ios::end);
|
||||
const auto end = inputFile.tellg();
|
||||
const auto fsize = (end - begin);
|
||||
|
||||
std::cout << "NSI: Autodetecting geometry based on file size: " << fsize << std::endl;
|
||||
|
||||
spec.cylinders = 35;
|
||||
spec.sectors = 10;
|
||||
|
||||
switch (fsize) {
|
||||
case 358400:
|
||||
spec.heads = 2;
|
||||
spec.bytes = 512;
|
||||
break;
|
||||
case 179200:
|
||||
spec.heads = 1;
|
||||
spec.bytes = 512;
|
||||
break;
|
||||
case 89600:
|
||||
spec.heads = 1;
|
||||
spec.bytes = 256;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
size_t trackSize = spec.sectors * spec.bytes;
|
||||
|
||||
std::cout << fmt::format("reading {} tracks, {} heads, {} sectors, {} bytes per sector, {} kB total",
|
||||
spec.cylinders, spec.heads,
|
||||
spec.sectors, spec.bytes,
|
||||
spec.cylinders * spec.heads * trackSize / 1024)
|
||||
<< std::endl;
|
||||
|
||||
SectorSet sectors;
|
||||
unsigned sectorFileOffset;
|
||||
|
||||
for (int head = 0; head < spec.heads; head++)
|
||||
{
|
||||
for (int track = 0; track < spec.cylinders; track++)
|
||||
{
|
||||
for (int sectorId = 0; sectorId < spec.sectors; sectorId++)
|
||||
{
|
||||
if (head == 0) { /* Head 0 is from track 0-34 */
|
||||
sectorFileOffset = track * trackSize + sectorId * spec.bytes;
|
||||
}
|
||||
else { /* Head 1 is from track 70-35 */
|
||||
sectorFileOffset = (trackSize * spec.cylinders) + /* Skip over side 0 */
|
||||
((spec.cylinders - track - 1) * trackSize) +
|
||||
(sectorId * spec.bytes); /* Sector offset from beginning of track. */
|
||||
}
|
||||
|
||||
inputFile.seekg(sectorFileOffset, std::ios::beg);
|
||||
|
||||
Bytes data(spec.bytes);
|
||||
inputFile.read((char*) data.begin(), spec.bytes);
|
||||
|
||||
std::unique_ptr<Sector>& sector = sectors.get(track, head, sectorId);
|
||||
sector.reset(new Sector);
|
||||
sector->status = Sector::OK;
|
||||
sector->logicalTrack = sector->physicalTrack = track;
|
||||
sector->logicalSide = sector->physicalSide = head;
|
||||
sector->logicalSector = sectorId;
|
||||
sector->data = data;
|
||||
}
|
||||
}
|
||||
}
|
||||
return sectors;
|
||||
}
|
||||
};
|
||||
|
||||
std::unique_ptr<ImageReader> ImageReader::createNsiImageReader(
|
||||
const ImageSpec& spec)
|
||||
{
|
||||
return std::unique_ptr<ImageReader>(new NsiImageReader(spec));
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ std::map<std::string, ImageWriter::Constructor> ImageWriter::formats =
|
||||
{".diskcopy", ImageWriter::createDiskCopyImageWriter},
|
||||
{".img", ImageWriter::createImgImageWriter},
|
||||
{".ldbs", ImageWriter::createLDBSImageWriter},
|
||||
{".nsi", ImageWriter::createNSIImageWriter},
|
||||
{".st", ImageWriter::createImgImageWriter},
|
||||
};
|
||||
|
||||
|
||||
@@ -31,6 +31,8 @@ private:
|
||||
const SectorSet& sectors, const ImageSpec& spec);
|
||||
static std::unique_ptr<ImageWriter> createDiskCopyImageWriter(
|
||||
const SectorSet& sectors, const ImageSpec& spec);
|
||||
static std::unique_ptr<ImageWriter> createNSIImageWriter(
|
||||
const SectorSet& sectors, const ImageSpec& spec);
|
||||
|
||||
static Constructor findConstructor(const ImageSpec& spec);
|
||||
|
||||
|
||||
78
lib/imagewriter/nsiimagewriter.cc
Normal file
78
lib/imagewriter/nsiimagewriter.cc
Normal file
@@ -0,0 +1,78 @@
|
||||
#include "globals.h"
|
||||
#include "flags.h"
|
||||
#include "dataspec.h"
|
||||
#include "sector.h"
|
||||
#include "sectorset.h"
|
||||
#include "imagewriter/imagewriter.h"
|
||||
#include "fmt/format.h"
|
||||
#include "decoders/decoders.h"
|
||||
#include "arch/northstar/northstar.h"
|
||||
#include <algorithm>
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
|
||||
class NSIImageWriter : public ImageWriter
|
||||
{
|
||||
public:
|
||||
NSIImageWriter(const SectorSet& sectors, const ImageSpec& spec):
|
||||
ImageWriter(sectors, spec)
|
||||
{}
|
||||
|
||||
void writeImage()
|
||||
{
|
||||
unsigned numCylinders = spec.cylinders;
|
||||
unsigned numHeads = spec.heads;
|
||||
unsigned numSectors = spec.sectors;
|
||||
unsigned numTracks = numCylinders * numHeads;
|
||||
unsigned numBytes = spec.bytes;
|
||||
int head;
|
||||
|
||||
size_t trackSize = numSectors * numBytes;
|
||||
|
||||
if ((numBytes != 256) && (numBytes != 512) && (numBytes != 257) && (numBytes != 513))
|
||||
Error() << "Sector size must be 256 or 512.";
|
||||
|
||||
if (numCylinders != 35)
|
||||
Error() << "Number of cylinders must be 35.";
|
||||
|
||||
std::cout << fmt::format("Writing {} cylinders, {} heads, {} sectors, {} ({} bytes/sector), {} kB total",
|
||||
numCylinders, numHeads,
|
||||
numSectors, numBytes == 256 ? "SD" : "DD", numBytes,
|
||||
numTracks * trackSize / 1024)
|
||||
<< std::endl;
|
||||
|
||||
std::ofstream outputFile(spec.filename, std::ios::out | std::ios::binary);
|
||||
if (!outputFile.is_open())
|
||||
Error() << "cannot open output file";
|
||||
|
||||
unsigned sectorFileOffset;
|
||||
|
||||
for (int track = 0; track < numCylinders * numHeads; track++)
|
||||
{
|
||||
head = (track < numCylinders) ? 0 : 1;
|
||||
for (int sectorId = 0; sectorId < numSectors; sectorId++)
|
||||
{
|
||||
const auto& sector = sectors.get(track % numCylinders, head, sectorId);
|
||||
if (sector)
|
||||
{
|
||||
if (head == 0) { /* Side 0 is from track 0-34 */
|
||||
sectorFileOffset = track * trackSize + sectorId * numBytes;
|
||||
}
|
||||
else { /* Side 1 is from track 70-35 */
|
||||
sectorFileOffset = (numBytes * numSectors * numCylinders) + /* Skip over side 0 */
|
||||
((numCylinders - 1) - (track % numCylinders)) * (numBytes * numSectors) +
|
||||
(sectorId * numBytes); /* Sector offset from beginning of track. */
|
||||
}
|
||||
outputFile.seekp(sectorFileOffset, std::ios::beg);
|
||||
sector->data.slice(0, numBytes).writeTo(outputFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
std::unique_ptr<ImageWriter> ImageWriter::createNSIImageWriter(
|
||||
const SectorSet& sectors, const ImageSpec& spec)
|
||||
{
|
||||
return std::unique_ptr<ImageWriter>(new NSIImageWriter(sectors, spec));
|
||||
}
|
||||
@@ -85,6 +85,11 @@ void setReaderHardSectorCount(int sectorCount)
|
||||
setHardwareFluxSourceHardSectorCount(sectorCount);
|
||||
}
|
||||
|
||||
void setReaderFluxSourceSynced(bool synced)
|
||||
{
|
||||
setHardwareFluxSourceSynced(synced);
|
||||
}
|
||||
|
||||
static void writeSectorsToFile(const SectorSet& sectors, const ImageSpec& spec)
|
||||
{
|
||||
std::unique_ptr<ImageWriter> writer(ImageWriter::create(sectors, spec));
|
||||
|
||||
@@ -14,6 +14,7 @@ extern void setReaderDefaultSource(const std::string& source);
|
||||
extern void setReaderDefaultOutput(const std::string& output);
|
||||
extern void setReaderRevolutions(int revolutions);
|
||||
extern void setReaderHardSectorCount(int sectorCount);
|
||||
extern void setReaderFluxSourceSynced(bool synced);
|
||||
|
||||
extern std::vector<std::unique_ptr<Track>> readTracks();
|
||||
|
||||
|
||||
@@ -162,11 +162,13 @@ buildlibrary libbackend.a \
|
||||
lib/imagereader/imgimagereader.cc \
|
||||
lib/imagereader/jv3imagereader.cc \
|
||||
lib/imagereader/imdimagereader.cc \
|
||||
lib/imagereader/nsiimagereader.cc \
|
||||
lib/imagewriter/d64imagewriter.cc \
|
||||
lib/imagewriter/diskcopyimagewriter.cc \
|
||||
lib/imagewriter/imagewriter.cc \
|
||||
lib/imagewriter/imgimagewriter.cc \
|
||||
lib/imagewriter/ldbsimagewriter.cc \
|
||||
lib/imagewriter/nsiimagewriter.cc \
|
||||
arch/aeslanier/decoder.cc \
|
||||
arch/amiga/decoder.cc \
|
||||
arch/amiga/encoder.cc \
|
||||
@@ -185,6 +187,8 @@ buildlibrary libbackend.a \
|
||||
arch/mx/decoder.cc \
|
||||
arch/tids990/decoder.cc \
|
||||
arch/tids990/encoder.cc \
|
||||
arch/northstar/decoder.cc \
|
||||
arch/northstar/encoder.cc \
|
||||
arch/victor9k/decoder.cc \
|
||||
arch/zilogmcz/decoder.cc \
|
||||
lib/bytes.cc \
|
||||
@@ -246,6 +250,7 @@ buildlibrary libfrontend.a \
|
||||
src/fe-readmicropolis.cc \
|
||||
src/fe-readmx.cc \
|
||||
src/fe-readtids990.cc \
|
||||
src/fe-readnorthstar.cc \
|
||||
src/fe-readvictor9k.cc \
|
||||
src/fe-readzilogmcz.cc \
|
||||
src/fe-rpm.cc \
|
||||
@@ -258,6 +263,7 @@ buildlibrary libfrontend.a \
|
||||
src/fe-writebrother.cc \
|
||||
src/fe-writeibm.cc \
|
||||
src/fe-writemac.cc \
|
||||
src/fe-writenorthstar.cc \
|
||||
src/fe-writetids990.cc \
|
||||
src/fe-writeflux.cc \
|
||||
src/fe-writetestpattern.cc \
|
||||
|
||||
27
src/fe-readnorthstar.cc
Normal file
27
src/fe-readnorthstar.cc
Normal file
@@ -0,0 +1,27 @@
|
||||
#include "globals.h"
|
||||
#include "flags.h"
|
||||
#include "reader.h"
|
||||
#include "fluxmap.h"
|
||||
#include "encoders/encoders.h"
|
||||
#include "decoders/decoders.h"
|
||||
#include "sector.h"
|
||||
#include "sectorset.h"
|
||||
#include "record.h"
|
||||
#include "northstar/northstar.h"
|
||||
#include "fmt/format.h"
|
||||
|
||||
static FlagGroup flags { &readerFlags };
|
||||
|
||||
int mainReadNorthstar(int argc, const char* argv[])
|
||||
{
|
||||
setReaderDefaultSource(":t=0-34");
|
||||
setReaderDefaultOutput("northstar.nsi");
|
||||
setReaderHardSectorCount(10);
|
||||
setReaderFluxSourceSynced(true);
|
||||
flags.parseFlags(argc, argv);
|
||||
|
||||
NorthstarDecoder decoder;
|
||||
readDiskCommand(decoder);
|
||||
return 0;
|
||||
}
|
||||
|
||||
20
src/fe-writenorthstar.cc
Normal file
20
src/fe-writenorthstar.cc
Normal file
@@ -0,0 +1,20 @@
|
||||
#include "globals.h"
|
||||
#include "flags.h"
|
||||
#include "decoders/decoders.h"
|
||||
#include "northstar/northstar.h"
|
||||
#include "writer.h"
|
||||
|
||||
static FlagGroup flags { &writerFlags, &northstarEncoderFlags };
|
||||
|
||||
int mainWriteNorthstar(int argc, const char* argv[])
|
||||
{
|
||||
setWriterDefaultDest(":t=0-34");
|
||||
setWriterHardSectorCount(10);
|
||||
flags.parseFlags(argc, argv);
|
||||
|
||||
NorthstarEncoder encoder;
|
||||
writeDiskCommand(encoder);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ extern command_cb mainReadIBM;
|
||||
extern command_cb mainReadMac;
|
||||
extern command_cb mainReadMicropolis;
|
||||
extern command_cb mainReadMx;
|
||||
extern command_cb mainReadNorthstar;
|
||||
extern command_cb mainReadTiDs990;
|
||||
extern command_cb mainReadVictor9K;
|
||||
extern command_cb mainReadZilogMCZ;
|
||||
@@ -39,6 +40,7 @@ extern command_cb mainWriteAmiga;
|
||||
extern command_cb mainWriteBrother;
|
||||
extern command_cb mainWriteIbm;
|
||||
extern command_cb mainWriteMac;
|
||||
extern command_cb mainWriteNorthstar;
|
||||
extern command_cb mainWriteTiDs990;
|
||||
extern command_cb mainWriteFlux;
|
||||
extern command_cb mainWriteTestPattern;
|
||||
@@ -89,6 +91,7 @@ static std::vector<Command> readables =
|
||||
{ "mac", mainReadMac, "Reads Apple Macintosh disks.", },
|
||||
{ "micropolis", mainReadMicropolis, "Reads Micropolis disks.", },
|
||||
{ "mx", mainReadMx, "Reads MX disks.", },
|
||||
{ "northstar", mainReadNorthstar, "Reads Northstar disks.", },
|
||||
{ "tids990", mainReadTiDs990, "Reads Texas Instruments DS990 disks.", },
|
||||
{ "victor9k", mainReadVictor9K, "Reads Victor 9000 disks.", },
|
||||
{ "zilogmcz", mainReadZilogMCZ, "Reads Zilog MCZ disks.", },
|
||||
@@ -100,6 +103,7 @@ static std::vector<Command> writeables =
|
||||
{ "brother", mainWriteBrother, "Writes 120kB and 240kB Brother word processor disks.", },
|
||||
{ "ibm", mainWriteIbm, "Writes the ubiquitous IBM format disks.", },
|
||||
{ "mac", mainWriteMac, "Writes Apple Macintosh disks.", },
|
||||
{ "northstar", mainWriteNorthstar,"Writes Northstar disks.", },
|
||||
{ "tids990", mainWriteTiDs990, "Writes Texas Instruments DS990 disks.", },
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user