mirror of
https://github.com/davidgiven/fluxengine.git
synced 2025-10-31 11:17:01 -07:00
Compare commits
11 Commits
FluxEngine
...
FluxEngine
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
35f8249c67 | ||
|
|
d1467a14b8 | ||
|
|
3e6b9eb74d | ||
|
|
ce2e8fb4b5 | ||
|
|
7eaa75c05d | ||
|
|
e86de4483a | ||
|
|
203a74713f | ||
|
|
59ed2a6793 | ||
|
|
a03283ce64 | ||
|
|
984cdaeb03 | ||
|
|
a1ed4a9171 |
@@ -121,7 +121,7 @@ uint8_t decode_side(uint8_t side)
|
||||
* bit 5) and also whether we're above track 0x3f (in bit 6).
|
||||
*/
|
||||
|
||||
return !!(side & 0x40);
|
||||
return !!(side & 0x20);
|
||||
}
|
||||
|
||||
AbstractDecoder::RecordType MacintoshDecoder::advanceToNextRecord()
|
||||
|
||||
@@ -45,22 +45,20 @@ Reading discs
|
||||
Just do:
|
||||
|
||||
```
|
||||
fluxengine read mac
|
||||
fluxengine read mac -o mac.diskcopy
|
||||
```
|
||||
|
||||
You should end up with an `mac.img` which is 1001888 bytes long (for a normal
|
||||
DD disk). If you want the single-sided variety, use `-s :s=0`.
|
||||
You should end up with a `mac.diskcopy` file which is compatible with DiskCopy
|
||||
4.2, which most Mac emulators support.
|
||||
|
||||
**Big warning!** The image may not work in an emulator. Mac disk images are
|
||||
complicated due to the way the tracks are different sizes and the odd sector
|
||||
size. FluxEngine chooses to store them in a simple 524 x 12 x 2 x 80 layout,
|
||||
with holes where missing sectors should be. This was easiest. If anyone can
|
||||
suggest a better way, please [get in
|
||||
touch](https://github.com/davidgiven/fluxengine/issues/new).
|
||||
|
||||
The 12 bytes of metadata _follow_ the 512 bytes of user payload in the sector
|
||||
image. If you don't want it, specify a geometry in the output file with a
|
||||
512-byte sectore size like `-o mac.img:c=80:h=1:s=12:b=512`.
|
||||
**Big warning!** Mac disk images are complicated due to the way the tracks are
|
||||
different sizes and the odd sector size. If you use a normal `.img` file, then
|
||||
FluxEngine will store them in a simple 524 x 12 x 2 x 80 layout, with holes
|
||||
where missing sectors should be; this was easiest, but is unlikely to work with
|
||||
most Mac emulators and other software. In these files, the The 12 bytes of
|
||||
metadata _follow_ the 512 bytes of user payload in the sector image. If you
|
||||
don't want it, specify a geometry in the output file with a 512-byte sectore
|
||||
size like `-o mac.img:c=80:h=1:s=12:b=512`.
|
||||
|
||||
Useful references
|
||||
-----------------
|
||||
@@ -74,3 +72,7 @@ Useful references
|
||||
|
||||
- [Les Disquettes et le drive Disk II](http://www.hackzapple.com/DISKII/DISKIITECH.HTM), an
|
||||
epicly detailed writeup of the Apple II disk format (which is closely related)
|
||||
|
||||
- [The DiskCopy 4.2
|
||||
format](https://www.discferret.com/wiki/Apple_DiskCopy_4.2), described on
|
||||
the DiskFerret website.
|
||||
|
||||
@@ -117,6 +117,10 @@ exact format varies according to the extension:
|
||||
FluxEngine's D64 support is currently limited to write only. It will work
|
||||
with up to 40 logical tracks.
|
||||
|
||||
- `.diskcopy`: a Macintosh DiskCopy 4.2 file. This is a special-purpose
|
||||
format due to the weird layout of Mac GCR disks, but it can also support
|
||||
720kB and 1440kB IBM disks (although there's no real benefit).
|
||||
|
||||
### High density disks
|
||||
|
||||
High density disks use a different magnetic medium to low and double density
|
||||
|
||||
13
lib/bytes.h
13
lib/bytes.h
@@ -1,6 +1,8 @@
|
||||
#ifndef BYTES_H
|
||||
#define BYTES_H
|
||||
|
||||
#include <string.h>
|
||||
|
||||
class ByteReader;
|
||||
class ByteWriter;
|
||||
|
||||
@@ -10,6 +12,7 @@ public:
|
||||
Bytes();
|
||||
Bytes(unsigned size);
|
||||
Bytes(const uint8_t* ptr, size_t len);
|
||||
Bytes(const std::string& data);
|
||||
Bytes(std::initializer_list<uint8_t> data);
|
||||
Bytes(std::shared_ptr<std::vector<uint8_t>> data);
|
||||
Bytes(std::shared_ptr<std::vector<uint8_t>> data, unsigned start, unsigned end);
|
||||
@@ -271,6 +274,16 @@ public:
|
||||
|
||||
ByteWriter& operator += (std::istream& stream);
|
||||
|
||||
ByteWriter& append(const char* data)
|
||||
{
|
||||
return *this += Bytes((const uint8_t*)data, strlen(data));
|
||||
}
|
||||
|
||||
ByteWriter& append(const std::string& data)
|
||||
{
|
||||
return *this += Bytes(data);
|
||||
}
|
||||
|
||||
ByteWriter& append(const Bytes data)
|
||||
{
|
||||
return *this += data;
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
#include <set>
|
||||
#include <cassert>
|
||||
|
||||
typedef int nanoseconds_t;
|
||||
typedef double nanoseconds_t;
|
||||
class Bytes;
|
||||
|
||||
extern double getCurrentTime();
|
||||
|
||||
169
lib/imagewriter/diskcopyimagewriter.cc
Normal file
169
lib/imagewriter/diskcopyimagewriter.cc
Normal file
@@ -0,0 +1,169 @@
|
||||
#include "globals.h"
|
||||
#include "flags.h"
|
||||
#include "dataspec.h"
|
||||
#include "sector.h"
|
||||
#include "sectorset.h"
|
||||
#include "imagewriter/imagewriter.h"
|
||||
#include "fmt/format.h"
|
||||
#include "ldbs.h"
|
||||
#include <algorithm>
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
|
||||
static const char LABEL[] = "FluxEngine image";
|
||||
|
||||
static void write_and_update_checksum(ByteWriter& bw, uint32_t& checksum, const Bytes& data)
|
||||
{
|
||||
ByteReader br(data);
|
||||
while (!br.eof())
|
||||
{
|
||||
uint32_t i = br.read_be16();
|
||||
checksum += i;
|
||||
checksum = (checksum >> 1) | (checksum << 31);
|
||||
bw.write_be16(i);
|
||||
}
|
||||
}
|
||||
|
||||
class DiskCopyImageWriter : public ImageWriter
|
||||
{
|
||||
public:
|
||||
DiskCopyImageWriter(const SectorSet& sectors, const ImageSpec& spec):
|
||||
ImageWriter(sectors, spec)
|
||||
{}
|
||||
|
||||
void writeImage()
|
||||
{
|
||||
bool mfm = false;
|
||||
|
||||
if (spec.bytes == 524)
|
||||
{
|
||||
/* GCR disk */
|
||||
}
|
||||
else if (spec.bytes == 512)
|
||||
{
|
||||
/* MFM disk */
|
||||
mfm = true;
|
||||
}
|
||||
else
|
||||
Error() << "this image is not compatible with the DiskCopy 4.2 format";
|
||||
|
||||
|
||||
std::cout << "writing DiskCopy 4.2 image\n"
|
||||
<< fmt::format("{} tracks, {} heads, {} sectors, {} bytes per sector; {}\n",
|
||||
spec.cylinders, spec.heads, spec.sectors, spec.bytes,
|
||||
mfm ? "MFM" : "GCR");
|
||||
|
||||
auto sectors_per_track = [&](int track) -> int
|
||||
{
|
||||
if (mfm)
|
||||
return spec.sectors;
|
||||
|
||||
if (track < 16)
|
||||
return 12;
|
||||
if (track < 32)
|
||||
return 11;
|
||||
if (track < 48)
|
||||
return 10;
|
||||
if (track < 64)
|
||||
return 9;
|
||||
return 8;
|
||||
};
|
||||
|
||||
Bytes image;
|
||||
ByteWriter bw(image);
|
||||
|
||||
/* Write the actual sectr data. */
|
||||
|
||||
uint32_t dataChecksum = 0;
|
||||
uint32_t tagChecksum = 0;
|
||||
uint32_t offset = 0x54;
|
||||
uint32_t sectorDataStart = offset;
|
||||
for (int track = 0; track < spec.cylinders; track++)
|
||||
{
|
||||
for (int head = 0; head < spec.heads; head++)
|
||||
{
|
||||
int sectorCount = sectors_per_track(track);
|
||||
for (int sectorId = 0; sectorId < sectorCount; sectorId++)
|
||||
{
|
||||
const auto& sector = sectors.get(track, head, sectorId);
|
||||
if (sector)
|
||||
{
|
||||
bw.seek(offset);
|
||||
write_and_update_checksum(bw, dataChecksum, sector->data.slice(0, 512));
|
||||
}
|
||||
offset += 512;
|
||||
}
|
||||
}
|
||||
}
|
||||
uint32_t sectorDataEnd = offset;
|
||||
if (!mfm)
|
||||
{
|
||||
for (int track = 0; track < spec.cylinders; track++)
|
||||
{
|
||||
for (int head = 0; head < spec.heads; head++)
|
||||
{
|
||||
int sectorCount = sectors_per_track(track);
|
||||
for (int sectorId = 0; sectorId < sectorCount; sectorId++)
|
||||
{
|
||||
const auto& sector = sectors.get(track, head, sectorId);
|
||||
if (sector)
|
||||
{
|
||||
bw.seek(offset);
|
||||
write_and_update_checksum(bw, tagChecksum, sector->data.slice(512, 12));
|
||||
}
|
||||
offset += 12;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
uint32_t tagDataEnd = offset;
|
||||
|
||||
/* Write the header. */
|
||||
|
||||
uint8_t encoding;
|
||||
uint8_t format;
|
||||
if (mfm)
|
||||
{
|
||||
format = 0x22;
|
||||
if (spec.sectors == 18)
|
||||
encoding = 3;
|
||||
else
|
||||
encoding = 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (spec.heads == 2)
|
||||
{
|
||||
encoding = 1;
|
||||
format = 0x22;
|
||||
}
|
||||
else
|
||||
{
|
||||
encoding = 0;
|
||||
format = 0x02;
|
||||
}
|
||||
}
|
||||
|
||||
bw.seek(0);
|
||||
bw.write_8(sizeof(LABEL));
|
||||
bw.append(LABEL);
|
||||
bw.seek(0x40);
|
||||
bw.write_be32(sectorDataEnd - sectorDataStart); /* data size */
|
||||
bw.write_be32(tagDataEnd - sectorDataEnd); /* tag size */
|
||||
bw.write_be32(dataChecksum); /* data checksum */
|
||||
bw.write_be32(tagChecksum); /* tag checksum */
|
||||
bw.write_8(encoding); /* encoding */
|
||||
bw.write_8(format); /* format byte */
|
||||
bw.write_be16(0x0100); /* magic number */
|
||||
|
||||
image.writeToFile(spec.filename);
|
||||
}
|
||||
};
|
||||
|
||||
std::unique_ptr<ImageWriter> ImageWriter::createDiskCopyImageWriter(
|
||||
const SectorSet& sectors, const ImageSpec& spec)
|
||||
{
|
||||
return std::unique_ptr<ImageWriter>(new DiskCopyImageWriter(sectors, spec));
|
||||
}
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ std::map<std::string, ImageWriter::Constructor> ImageWriter::formats =
|
||||
{".adf", ImageWriter::createImgImageWriter},
|
||||
{".d64", ImageWriter::createD64ImageWriter},
|
||||
{".d81", ImageWriter::createImgImageWriter},
|
||||
{".diskcopy", ImageWriter::createDiskCopyImageWriter},
|
||||
{".img", ImageWriter::createImgImageWriter},
|
||||
{".ldbs", ImageWriter::createLDBSImageWriter},
|
||||
};
|
||||
|
||||
@@ -29,6 +29,8 @@ private:
|
||||
const SectorSet& sectors, const ImageSpec& spec);
|
||||
static std::unique_ptr<ImageWriter> createD64ImageWriter(
|
||||
const SectorSet& sectors, const ImageSpec& spec);
|
||||
static std::unique_ptr<ImageWriter> createDiskCopyImageWriter(
|
||||
const SectorSet& sectors, const ImageSpec& spec);
|
||||
|
||||
static Constructor findConstructor(const ImageSpec& spec);
|
||||
|
||||
|
||||
@@ -107,7 +107,7 @@ void Track::readFluxmap()
|
||||
fluxmap = fluxsource->readFlux(physicalTrack, physicalSide);
|
||||
std::cout << fmt::format(
|
||||
"{0} ms in {1} bytes\n",
|
||||
int(fluxmap->duration()/1e6),
|
||||
fluxmap->duration()/1e6,
|
||||
fluxmap->bytes());
|
||||
if (outputFluxSink)
|
||||
outputFluxSink->writeFlux(physicalTrack, physicalSide, *fluxmap);
|
||||
|
||||
@@ -47,8 +47,8 @@ void visualiseSectorsToFile(const SectorSet& sectors, const std::string& filenam
|
||||
|
||||
auto drawArc = [&](const std::unique_ptr<Sector>& sector, nanoseconds_t start, nanoseconds_t end, const std::string& colour)
|
||||
{
|
||||
start %= period*1000000;
|
||||
end %= period*1000000;
|
||||
start = fmod(start, period*1000000.0);
|
||||
end = fmod(end, period*1000000.0);
|
||||
if (end < start)
|
||||
end += period*1000000;
|
||||
|
||||
|
||||
@@ -154,6 +154,7 @@ buildlibrary libbackend.a \
|
||||
lib/imagereader/imagereader.cc \
|
||||
lib/imagereader/imgimagereader.cc \
|
||||
lib/imagewriter/d64imagewriter.cc \
|
||||
lib/imagewriter/diskcopyimagewriter.cc \
|
||||
lib/imagewriter/imagewriter.cc \
|
||||
lib/imagewriter/imgimagewriter.cc \
|
||||
lib/imagewriter/ldbsimagewriter.cc \
|
||||
|
||||
@@ -18,6 +18,11 @@ static SettableFlag fortyTrackMode(
|
||||
"set 48 tpi mode; only every other physical track is emitted"
|
||||
);
|
||||
|
||||
static SettableFlag indexedMode(
|
||||
{ "--indexed", "-i" },
|
||||
"align data to track boundaries"
|
||||
);
|
||||
|
||||
static SettableFlag singleSided(
|
||||
{ "--single-sided", "-s" },
|
||||
"only emit side 0"
|
||||
@@ -45,14 +50,18 @@ static void write_le32(uint8_t dest[4], uint32_t v)
|
||||
dest[3] = v >> 24;
|
||||
}
|
||||
|
||||
static void appendChecksum(uint32_t& checksum, const Bytes& bytes)
|
||||
{
|
||||
ByteReader br(bytes);
|
||||
while (!br.eof())
|
||||
checksum += br.read_8();
|
||||
}
|
||||
|
||||
static int strackno(int track, int side)
|
||||
{
|
||||
if (fortyTrackMode)
|
||||
track /= 2;
|
||||
if (singleSided)
|
||||
return track;
|
||||
else
|
||||
return (track << 1) | side;
|
||||
return (track << 1) | side;
|
||||
}
|
||||
|
||||
int mainConvertFluxToScp(int argc, const char* argv[])
|
||||
@@ -90,7 +99,8 @@ int mainConvertFluxToScp(int argc, const char* argv[])
|
||||
fileheader.revolutions = 5;
|
||||
fileheader.start_track = 0;
|
||||
fileheader.end_track = maxStrack;
|
||||
fileheader.flags = SCP_FLAG_INDEXED | (fortyTrackMode ? 0 : SCP_FLAG_96TPI);
|
||||
fileheader.flags = (indexedMode ? SCP_FLAG_INDEXED : 0)
|
||||
| (fortyTrackMode ? 0 : SCP_FLAG_96TPI);
|
||||
fileheader.cell_width = 0;
|
||||
fileheader.heads = singleSided ? 1 : 0;
|
||||
|
||||
@@ -104,9 +114,15 @@ int mainConvertFluxToScp(int argc, const char* argv[])
|
||||
for (int side = 0; side <= maxside; side++)
|
||||
{
|
||||
int strack = strackno(track, side);
|
||||
std::cout << fmt::format("FE track {}.{}, SCP track {}: ", track, side, strack) << std::flush;
|
||||
std::cout << fmt::format("{}.{}: ", track, side) << std::flush;
|
||||
|
||||
auto fluxmap = sqlReadFlux(inputDb, track, side);
|
||||
if (fluxmap->bytes() == 0)
|
||||
{
|
||||
std::cout << "missing\n";
|
||||
continue;
|
||||
}
|
||||
|
||||
ScpTrack trackheader = {0};
|
||||
trackheader.track_id[0] = 'T';
|
||||
trackheader.track_id[1] = 'R';
|
||||
@@ -117,6 +133,9 @@ int mainConvertFluxToScp(int argc, const char* argv[])
|
||||
Bytes fluxdata;
|
||||
ByteWriter fluxdataWriter(fluxdata);
|
||||
|
||||
if (indexedMode)
|
||||
fmr.findEvent(F_BIT_INDEX);
|
||||
|
||||
int revolution = 0;
|
||||
unsigned revTicks = 0;
|
||||
unsigned totalTicks = 0;
|
||||
@@ -159,7 +178,7 @@ int mainConvertFluxToScp(int argc, const char* argv[])
|
||||
trackdataWriter += Bytes((uint8_t*)&trackheader, sizeof(trackheader));
|
||||
trackdataWriter += fluxdata;
|
||||
|
||||
std::cout << fmt::format("{} ms in {} bytes\n",
|
||||
std::cout << fmt::format("{:.3f} ms in {} bytes\n",
|
||||
totalTicks * MS_PER_TICK,
|
||||
fluxdata.size());
|
||||
}
|
||||
@@ -167,6 +186,13 @@ int mainConvertFluxToScp(int argc, const char* argv[])
|
||||
|
||||
sqlClose(inputDb);
|
||||
|
||||
uint32_t checksum = 0;
|
||||
appendChecksum(checksum,
|
||||
Bytes((const uint8_t*) &fileheader, sizeof(fileheader))
|
||||
.slice(0x10));
|
||||
appendChecksum(checksum, trackdata);
|
||||
write_le32(fileheader.checksum, checksum);
|
||||
|
||||
std::cout << "Writing output file...\n";
|
||||
std::ofstream of(filenames[1], std::ios::out | std::ios::binary);
|
||||
if (!of.is_open())
|
||||
|
||||
@@ -28,15 +28,11 @@ static void check_for_error()
|
||||
|
||||
static int trackno(int strack)
|
||||
{
|
||||
if (startSide == endSide)
|
||||
return strack;
|
||||
return strack >> 1;
|
||||
}
|
||||
|
||||
static int headno(int strack)
|
||||
{
|
||||
if (startSide == endSide)
|
||||
return startSide;
|
||||
return strack & 1;
|
||||
}
|
||||
|
||||
@@ -65,6 +61,8 @@ static void read_header()
|
||||
static void read_track(int strack)
|
||||
{
|
||||
uint32_t offset = Bytes(header.track[strack], 4).reader().read_le32();
|
||||
if (offset == 0)
|
||||
return;
|
||||
|
||||
ScpTrack trackheader;
|
||||
inputFile.seekg(offset, std::ios::beg);
|
||||
@@ -112,7 +110,7 @@ static void read_track(int strack)
|
||||
inputBytes += datalength*2;
|
||||
}
|
||||
|
||||
std::cout << fmt::format(" {} ms in {} input bytes and {} output bytes\n",
|
||||
std::cout << fmt::format(" {:.3f} ms in {} input bytes and {} output bytes\n",
|
||||
fluxmap.duration() / 1e6, inputBytes, fluxmap.bytes());
|
||||
sqlWriteFlux(outputDb, trackno(strack), headno(strack), fluxmap);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user