mirror of
https://github.com/davidgiven/fluxengine.git
synced 2025-10-31 11:17:01 -07:00
Merge pull request #188 from davidgiven/jv3
Add support for reading TRS-80 JV3 images.
This commit is contained in:
@@ -32,3 +32,11 @@ If you've got a 40-track disk, use `-s :t=0-79x2`.
|
||||
|
||||
If you've got a single density disk, use `--read-fm=true`. (Double density is
|
||||
the default.)
|
||||
|
||||
|
||||
Useful references
|
||||
-----------------
|
||||
|
||||
- [The JV3 file format](https://www.tim-mann.org/trs80/dskspec.html):
|
||||
documents the most popular emulator disk image.
|
||||
|
||||
|
||||
19
doc/using.md
19
doc/using.md
@@ -136,6 +136,10 @@ exact format varies according to the extension:
|
||||
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).
|
||||
|
||||
- `.jv3`: a disk image format mainly used by the TRS-80. These images can be
|
||||
read, but not yet written. You only get the data; the density and DAM bits
|
||||
are ignored.
|
||||
|
||||
### High density disks
|
||||
|
||||
High density disks use a different magnetic medium to low and double density
|
||||
@@ -228,21 +232,16 @@ directory.
|
||||
format in a non-backwards-compatible way; this tool will upgrade flux files
|
||||
to the new format.
|
||||
|
||||
- `fluxengine convert`: converts flux files from various formats to various
|
||||
other formats. You can use this to convert Catweasel flux files to
|
||||
- `fluxengine convert`: converts files from various formats to various other
|
||||
formats. The main use of this is probably `fluxengine convert image`, which
|
||||
will convert a disk image from one format to another.
|
||||
|
||||
There are also subcommands for converting Catweasel flux files to
|
||||
FluxEngine's native format, FluxEngine flux files to various other formats
|
||||
useful for debugging (including VCD which can be loaded into
|
||||
[sigrok](http://sigrok.org)), and bidirectional conversion to and from
|
||||
Supercard Pro `.scp` format.
|
||||
|
||||
**Important SCP note:** import (`fluxengine convert scptoflux`) should be
|
||||
fairly robust, but export (`fluxengine convert fluxtoscp`) should only be
|
||||
done with great caution as FluxEngine files contain features which can't be
|
||||
represented very well in `.scp` format and they're probably pretty dubious.
|
||||
As ever, please [get in
|
||||
touch](https://github.com/davidgiven/fluxengine/issues/new) with any
|
||||
reports.
|
||||
|
||||
Commands which normally take `--source` or `--dest` get a sensible default if
|
||||
left unspecified. `fluxengine read ibm` on its own will read drive 0 and
|
||||
write an `ibm.img` file.
|
||||
|
||||
@@ -14,6 +14,8 @@ std::map<std::string, ImageReader::Constructor> ImageReader::formats =
|
||||
{".d81", ImageReader::createImgImageReader},
|
||||
{".img", ImageReader::createImgImageReader},
|
||||
{".ima", ImageReader::createImgImageReader},
|
||||
{".jv1", ImageReader::createImgImageReader},
|
||||
{".jv3", ImageReader::createJv3ImageReader},
|
||||
};
|
||||
|
||||
static bool ends_with(const std::string& value, const std::string& ending)
|
||||
@@ -45,7 +47,7 @@ std::unique_ptr<ImageReader> ImageReader::create(const ImageSpec& spec)
|
||||
void ImageReader::verifyImageSpec(const ImageSpec& spec)
|
||||
{
|
||||
if (!findConstructor(spec))
|
||||
Error() << "unrecognised image filename extension";
|
||||
Error() << "unrecognised input image filename extension";
|
||||
}
|
||||
|
||||
ImageReader::ImageReader(const ImageSpec& spec):
|
||||
|
||||
@@ -24,6 +24,7 @@ private:
|
||||
static std::map<std::string, Constructor> formats;
|
||||
|
||||
static std::unique_ptr<ImageReader> createImgImageReader(const ImageSpec& spec);
|
||||
static std::unique_ptr<ImageReader> createJv3ImageReader(const ImageSpec& spec);
|
||||
|
||||
static Constructor findConstructor(const ImageSpec& spec);
|
||||
|
||||
|
||||
141
lib/imagereader/jv3imagereader.cc
Normal file
141
lib/imagereader/jv3imagereader.cc
Normal file
@@ -0,0 +1,141 @@
|
||||
#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>
|
||||
|
||||
/* JV3 files are kinda weird. There's a fixed layout for up to 2901 sectors, which may appear
|
||||
* in any order, followed by the same again for more sectors. To find the second data block
|
||||
* you need to know the size of the first data block, which requires parsing it.
|
||||
*
|
||||
* https://www.tim-mann.org/trs80/dskspec.html
|
||||
*
|
||||
* typedef struct {
|
||||
* SectorHeader headers1[2901];
|
||||
* unsigned char writeprot;
|
||||
* unsigned char data1[];
|
||||
* SectorHeader headers2[2901];
|
||||
* unsigned char padding;
|
||||
* unsigned char data2[];
|
||||
* } JV3;
|
||||
*
|
||||
* typedef struct {
|
||||
* unsigned char track;
|
||||
* unsigned char sector;
|
||||
* unsigned char flags;
|
||||
* } SectorHeader;
|
||||
*/
|
||||
|
||||
struct SectorHeader
|
||||
{
|
||||
uint8_t track;
|
||||
uint8_t sector;
|
||||
uint8_t flags;
|
||||
};
|
||||
|
||||
#define JV3_DENSITY 0x80 /* 1=dden, 0=sden */
|
||||
#define JV3_DAM 0x60 /* data address mark code; see below */
|
||||
#define JV3_SIDE 0x10 /* 0=side 0, 1=side 1 */
|
||||
#define JV3_ERROR 0x08 /* 0=ok, 1=CRC error */
|
||||
#define JV3_NONIBM 0x04 /* 0=normal, 1=short */
|
||||
#define JV3_SIZE 0x03 /* in used sectors: 0=256,1=128,2=1024,3=512
|
||||
in free sectors: 0=512,1=1024,2=128,3=256 */
|
||||
|
||||
#define JV3_FREE 0xFF /* in track and sector fields of free sectors */
|
||||
#define JV3_FREEF 0xFC /* in flags field, or'd with size code */
|
||||
|
||||
static unsigned getSectorSize(uint8_t flags)
|
||||
{
|
||||
if ((flags & JV3_FREEF) == JV3_FREEF)
|
||||
{
|
||||
switch (flags & JV3_SIZE)
|
||||
{
|
||||
case 0: return 512;
|
||||
case 1: return 1024;
|
||||
case 2: return 128;
|
||||
case 3: return 256;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (flags & JV3_SIZE)
|
||||
{
|
||||
case 0: return 256;
|
||||
case 1: return 128;
|
||||
case 2: return 1024;
|
||||
case 3: return 512;
|
||||
}
|
||||
}
|
||||
Error() << "not reachable";
|
||||
}
|
||||
|
||||
class Jv3ImageReader : public ImageReader
|
||||
{
|
||||
public:
|
||||
Jv3ImageReader(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";
|
||||
|
||||
inputFile.seekg( 0, std::ios::end);
|
||||
unsigned inputFileSize = inputFile.tellg();
|
||||
unsigned headerPtr = 0;
|
||||
SectorSet sectors;
|
||||
for (;;)
|
||||
{
|
||||
unsigned dataPtr = headerPtr + 2901*3 + 1;
|
||||
if (dataPtr >= inputFileSize)
|
||||
break;
|
||||
|
||||
for (unsigned i=0; i<2901; i++)
|
||||
{
|
||||
SectorHeader header = {0, 0, 0xff};
|
||||
inputFile.seekg(headerPtr);
|
||||
inputFile.read((char*) &header, 3);
|
||||
unsigned sectorSize = getSectorSize(header.flags);
|
||||
if ((header.flags & JV3_FREEF) != JV3_FREEF)
|
||||
{
|
||||
Bytes data(sectorSize);
|
||||
inputFile.seekg(dataPtr);
|
||||
inputFile.read((char*) data.begin(), sectorSize);
|
||||
|
||||
unsigned head = !!(header.flags & JV3_SIDE);
|
||||
std::unique_ptr<Sector>& sector = sectors.get(header.track, head, header.sector);
|
||||
sector.reset(new Sector);
|
||||
sector->status = Sector::OK;
|
||||
sector->logicalTrack = sector->physicalTrack = header.track;
|
||||
sector->logicalSide = sector->physicalSide = head;
|
||||
sector->logicalSector = header.sector;
|
||||
sector->data = data;
|
||||
}
|
||||
|
||||
headerPtr += 3;
|
||||
dataPtr += sectorSize;
|
||||
}
|
||||
|
||||
/* dataPtr is now pointing at the beginning of the next chunk. */
|
||||
|
||||
headerPtr = dataPtr;
|
||||
}
|
||||
|
||||
return sectors;
|
||||
}
|
||||
};
|
||||
|
||||
std::unique_ptr<ImageReader> ImageReader::createJv3ImageReader(
|
||||
const ImageSpec& spec)
|
||||
{
|
||||
return std::unique_ptr<ImageReader>(new Jv3ImageReader(spec));
|
||||
}
|
||||
|
||||
|
||||
@@ -47,7 +47,7 @@ std::unique_ptr<ImageWriter> ImageWriter::create(const SectorSet& sectors, const
|
||||
void ImageWriter::verifyImageSpec(const ImageSpec& spec)
|
||||
{
|
||||
if (!findConstructor(spec))
|
||||
Error() << "unrecognised image filename extension";
|
||||
Error() << "unrecognised output image filename extension";
|
||||
}
|
||||
|
||||
ImageWriter::ImageWriter(const SectorSet& sectors, const ImageSpec& spec):
|
||||
|
||||
@@ -153,6 +153,7 @@ buildlibrary libfmt.a \
|
||||
buildlibrary libbackend.a \
|
||||
lib/imagereader/imagereader.cc \
|
||||
lib/imagereader/imgimagereader.cc \
|
||||
lib/imagereader/jv3imagereader.cc \
|
||||
lib/imagewriter/d64imagewriter.cc \
|
||||
lib/imagewriter/diskcopyimagewriter.cc \
|
||||
lib/imagewriter/imagewriter.cc \
|
||||
@@ -212,6 +213,7 @@ buildlibrary libfrontend.a \
|
||||
src/fe-fluxtoau.cc \
|
||||
src/fe-fluxtoscp.cc \
|
||||
src/fe-fluxtovcd.cc \
|
||||
src/fe-image.cc \
|
||||
src/fe-inspect.cc \
|
||||
src/fe-readadfs.cc \
|
||||
src/fe-readaeslanier.cc \
|
||||
|
||||
38
src/fe-image.cc
Normal file
38
src/fe-image.cc
Normal file
@@ -0,0 +1,38 @@
|
||||
#include "globals.h"
|
||||
#include "flags.h"
|
||||
#include "dataspec.h"
|
||||
#include "sector.h"
|
||||
#include "sectorset.h"
|
||||
#include "imagereader/imagereader.h"
|
||||
#include "imagewriter/imagewriter.h"
|
||||
#include "fmt/format.h"
|
||||
#include <fstream>
|
||||
|
||||
static FlagGroup flags { };
|
||||
|
||||
static void syntax()
|
||||
{
|
||||
std::cout << "Syntax: fluxengine convert image <srcspec> <destspec>\n";
|
||||
exit(0);
|
||||
}
|
||||
|
||||
int mainConvertImage(int argc, const char* argv[])
|
||||
{
|
||||
auto filenames = flags.parseFlagsWithFilenames(argc, argv);
|
||||
if (filenames.size() != 2)
|
||||
syntax();
|
||||
|
||||
DataSpec ids(filenames[0]);
|
||||
ImageSpec iis(ids);
|
||||
SectorSet sectors = ImageReader::create(iis)->readImage();
|
||||
|
||||
DataSpec ods(filenames[1]);
|
||||
ImageSpec ois(ods);
|
||||
auto writer = ImageWriter::create(sectors, ois);
|
||||
writer->adjustGeometry();
|
||||
writer->printMap();
|
||||
writer->writeImage();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ extern command_cb mainConvertCwfToFlux;
|
||||
extern command_cb mainConvertFluxToAu;
|
||||
extern command_cb mainConvertFluxToScp;
|
||||
extern command_cb mainConvertFluxToVcd;
|
||||
extern command_cb mainConvertImage;
|
||||
extern command_cb mainConvertScpToFlux;
|
||||
extern command_cb mainInspect;
|
||||
extern command_cb mainReadADFS;
|
||||
@@ -101,6 +102,7 @@ static std::vector<Command> convertables =
|
||||
{ "fluxtoau", mainConvertFluxToAu, "Converts (one track of a) flux file to an .au audio file.", },
|
||||
{ "fluxtoscp", mainConvertFluxToScp, "Converrt a flux file to a Supercard Pro file.", },
|
||||
{ "fluxtovcd", mainConvertFluxToVcd, "Converts (one track of a) flux file to a VCD file.", },
|
||||
{ "image", mainConvertImage, "Converts one disk image to another.", },
|
||||
};
|
||||
|
||||
static std::vector<Command> testables =
|
||||
|
||||
Reference in New Issue
Block a user