summaryrefslogtreecommitdiffstatshomepage
path: root/src/lib/formats/trs80_dsk.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib/formats/trs80_dsk.cpp')
-rw-r--r--src/lib/formats/trs80_dsk.cpp430
1 files changed, 414 insertions, 16 deletions
diff --git a/src/lib/formats/trs80_dsk.cpp b/src/lib/formats/trs80_dsk.cpp
index 988c253a9b4..f670273a5af 100644
--- a/src/lib/formats/trs80_dsk.cpp
+++ b/src/lib/formats/trs80_dsk.cpp
@@ -1,45 +1,101 @@
-// license:GPL-2.0+
+// license:BSD-3-Clause
// copyright-holders:Dirk Best
/***************************************************************************
- TRS-80
+TRS-80
- JV1 disk image format
+JV1/3 disk image formats
- Used by Jeff Vavasour's TRS-80 Emulators
+Used by Jeff Vavasour's TRS-80 Emulators
- TODO:
- - Gap sizes unverified
+Description of JV1:
+ This format has no header or identifying characteristics. It is
+ simply a string of sectors one after the other in order. There's
+ no DAM or inter-sector bytes. The expected format is 10 sectors
+ per track, 256 bytes per sector, single-sided, single-density.
+ There's no limit on the number of tracks. It's up to the emulation
+ to decide how to represent things. This format is intended only
+ for the TRS-80 Model 1 with Level 2 basic. As it happens, the
+ format is readable by the Model 3, but it's probably not a good
+ idea to attempt writing on it.
+ BUGS: If you try to create a JV1 disk, MAME will crash.
+
+Description of JV3:
+ There's no header, but the format is obvious to the practised eye.
+ Firstly there's a 3 byte descriptor per sector, occurs 2901 times.
+ This is followed by a byte to indicate if the disk is read-only. The
+ total size of this area is 0x2200 bytes. The sector descriptors can
+ be in any order. Unused descriptors are FF FF (FC | size). These
+ can be intermixed with valid descriptors.
+ Byte 1 - track number (0xFF indicates unused entry).
+ Byte 2 - sector number.
+ Byte 3 - flag byte. The bits are assigned as follows:
+ Bits 0,1 - size in multiples of 128 bytes xor 1 for used tracks, or
+ xor 2 for unused tracks.
+ 128 bytes: 1 (used), 2 (unused)
+ 256 bytes: 0 (used), 3 (unused)
+ 512 bytes: 3 (used), 0 (unused)
+ 1024 bytes: 2 (used), 1 (unused)
+ bit 2: non-ibm flag. Some FDCs, such as the 1771, can have sector
+ sizes from 16 to 1024 in any multiple of 16. MAME doesn't
+ support this, and the format itself gives no indication of the
+ size. So, it's impossible to support it.
+ bit 3: sector has a CRC error.
+ bit 4: side (0 = side 0, 1 = side 1)
+ bits 5,6: DAM code:
+ 0xF8 (deleted): 0x60 (single-density), 0x20 (double-density)
+ 0xF9 (undefined): 0x40 (single-density), invalid/unused (double-density)
+ 0xFA (undefined): 0x20 (single-density), invalid/unused (double-density)
+ 0xFB (normal) : 0x00 (single and double-density)
+ bit 7: density (1 = dden/MFM). MAME doesn't support different densities in
+ a track, so it's defaulted to single-density unless at least 1 sector is
+ marked as double-density.
+ After 0x2200 comes the data. There's no DAM bytes or inter-sector bytes
+ included. If the last part of the descriptors is all unused, there's no
+ need for those sectors to be in the image. If 2901 entries is not enough,
+ the format allows another set of descriptors and data. However no TRS-80
+ disks need this extra area, so it's unsupported. This format is intended
+ only for the TRS-80 Models 3 and 4. Save/Create is currently unsupported.
+
+ What we support: up to 2 sides, 96 tracks, 18 sectors per track.
+ We support all the flags except the non-ibm flag.
+ We do not support multiple sets of descriptors and data.
+ We do not support multiple densities per track.
***************************************************************************/
#include "trs80_dsk.h"
-trs80_format::trs80_format() : wd177x_format(formats)
+#include "ioprocs.h"
+
+#include "osdcore.h" // osd_printf_*
+
+
+jv1_format::jv1_format() : wd177x_format(formats)
{
}
-const char *trs80_format::name() const
+const char *jv1_format::name() const noexcept
{
- return "trs80";
+ return "jv1";
}
-const char *trs80_format::description() const
+const char *jv1_format::description() const noexcept
{
return "TRS-80 JV1 disk image";
}
-const char *trs80_format::extensions() const
+const char *jv1_format::extensions() const noexcept
{
- return "dsk";
+ return "jv1,dsk";
}
-int trs80_format::get_track_dam_fm(const format &f, int head, int track)
+int jv1_format::get_track_dam_fm(const format &f, int head, int track) const
{
return (track == 17 && head == 0) ? FM_DDAM : FM_DAM;
}
-const trs80_format::format trs80_format::formats[] =
+const jv1_format::format jv1_format::formats[] =
{
{
floppy_image::FF_525, floppy_image::SSSD, floppy_image::FM,
@@ -50,10 +106,352 @@ const trs80_format::format trs80_format::formats[] =
4000, 10, 40, 1, 256, {}, 0, {}, 14, 11, 12
},
{
- floppy_image::FF_525, floppy_image::SSDD, floppy_image::FM,
+ floppy_image::FF_525, floppy_image::SSQD, floppy_image::FM,
4000, 10, 80, 1, 256, {}, 0, {}, 14, 11, 12
},
{}
};
-const floppy_format_type FLOPPY_TRS80_FORMAT = &floppy_image_format_creator<trs80_format>;
+const jv1_format FLOPPY_JV1_FORMAT;
+
+
+#define MAX_SECTORS 19
+#define MAX_TRACKS 96
+
+jv3_format::jv3_format()
+{
+}
+
+const char *jv3_format::name() const noexcept
+{
+ return "jv3";
+}
+
+const char *jv3_format::description() const noexcept
+{
+ return "TRS-80 JV3 disk image";
+}
+
+const char *jv3_format::extensions() const noexcept
+{
+ return "jv3,dsk";
+}
+
+int jv3_format::identify(util::random_read &io, uint32_t form_factor, const std::vector<uint32_t> &variants) const
+{
+ uint64_t image_size;
+ if (io.length(image_size))
+ return 0;
+
+ if (image_size < 0x2200)
+ return 0; // too small, silent return
+
+ auto const [err, data, actual] = read_at(io, 0, image_size);
+ if (err || (actual != image_size))
+ return 0;
+ const uint32_t entries = 2901;
+ const uint32_t header_size = entries *3 +1;
+
+ // check the readonly flag
+ if ((data[0x21ff] != 0) && (data[0x21ff] != 0xff))
+ return 0; // flag is incorrect, silent return
+
+ // Check for DMK
+ uint8_t testbyte = 0;
+ for (int i = 5; i < 16; i++)
+ testbyte |= data[i];
+ if (!testbyte)
+ return 0; // DMK, silent return
+
+ // Check for other disk formats
+ if (data[1] >= MAX_SECTORS)
+ return 0; // some other format, silent return
+
+ // Full validation of the disk
+ bool sector_array[2][MAX_TRACKS][MAX_SECTORS];
+ for (uint8_t side = 0; side < 2; side++)
+ for (uint8_t track = 0; track < MAX_TRACKS; track++)
+ for (uint8_t sector = 0; sector < MAX_SECTORS; sector++)
+ sector_array[side][track][sector] = false; // fill_n won't compile
+
+ uint32_t data_ptr = header_size, last_data = header_size, size = 0;
+
+ // printf errors may indicate it's another format, and so are ignorable.
+ for (uint32_t sect = 0; sect < entries; sect++)
+ {
+ uint8_t track = data[sect*3];
+ uint8_t sector = data[sect*3+1];
+ uint8_t flag_size = data[sect*3+2] & 3;
+ uint8_t flag_side = (data[sect*3+2] & 0x10) ? 1 : 0;
+
+ if (track < 0xff)
+ {
+ // validate so we don't overrun the array
+ if (track >= MAX_TRACKS)
+ {
+ osd_printf_info("jv3_format::identify - track %d exceeds maximum allowed (%d)\n",track,MAX_TRACKS-1);
+ return 0;
+ }
+
+ if (sector >= MAX_SECTORS)
+ {
+ osd_printf_info("jv3_format::identify - sector %d exceeds maximum allowed (%d)\n",sector,MAX_SECTORS-1);
+ return 0;
+ }
+
+ // check if sector already exists
+ if (sector_array[flag_side][track][sector])
+ {
+ osd_printf_info("jv3_format::identify - side %d track %d sector %d is duplicated\n",flag_side,track,sector);
+ return 0;
+ }
+
+ sector_array[flag_side][track][sector] = true;
+
+ size = 128 << (flag_size ^1);
+ data_ptr += size;
+ last_data = data_ptr;
+ }
+ else
+ {
+ size = 128 << (flag_size ^2);
+ data_ptr += size;
+ }
+ }
+
+ // Is all data in the image? (unused tracks at the end are optional)
+ if (last_data > image_size)
+ {
+ osd_printf_info("jv3_format::identify - disk is missing some data. Expected 0x%X, Actual = 0x%X\n",last_data,image_size);
+ return 0;
+ }
+
+ return FIFID_STRUCT;
+}
+
+bool jv3_format::load(util::random_read &io, uint32_t form_factor, const std::vector<uint32_t> &variants, floppy_image &image) const
+{
+ // disk has already been validated in every way except if it exceeds drive tracks, we do that below
+ osd_printf_info("Disk detected as JV3\n");
+ int drive_tracks, drive_sides;
+ image.get_maximal_geometry(drive_tracks, drive_sides);
+ uint64_t image_size;
+ if (io.length(image_size))
+ return false;
+ auto const [err, data, actual] = read_at(io, 0, image_size);
+ if (err || (actual != image_size))
+ return 0;
+ const uint32_t entries = 2901;
+ const uint32_t header_size = entries *3 +1;
+ bool is_dd = false, is_ds = false;
+
+ for (uint8_t curr_side = 0; curr_side < 2; curr_side++)
+ {
+ for (uint8_t curr_track = 0; curr_track < MAX_TRACKS; curr_track++)
+ {
+ // Find out how many sectors are in this track
+ uint8_t max_sect = 0, min_sect = 0xff;
+ for (uint32_t sect = 0; sect < entries; sect++)
+ {
+ uint8_t track = data[sect*3];
+ uint8_t sector = data[sect*3+1];
+ uint8_t flags = data[sect*3+2];
+ uint8_t side = (flags & 0x10) ? 1 : 0;
+ if ((track == curr_track) && (side == curr_side))
+ {
+ if (sector > max_sect)
+ max_sect = sector;
+ if (sector < min_sect)
+ min_sect = sector;
+ }
+ }
+
+ // If there's any data for this track process it
+ if (max_sect >= min_sect)
+ {
+ // Protect against oversized disk
+ if ((curr_track >= drive_tracks) || (curr_side >= drive_sides))
+ {
+ osd_printf_error("Disk exceeds drive capabilities\n");
+ return false;
+ }
+
+ max_sect++; // now it is the max possible number of sectors on this track
+
+ desc_pc_sector sectors[MAX_SECTORS];
+ // set defaults in case some sectors are missing from the image
+ for (uint8_t i = 0; i < max_sect; i++)
+ {
+ sectors[i].track = curr_track;
+ sectors[i].head = curr_side;
+ sectors[i].sector = i;
+ sectors[i].actual_size = 256;
+ sectors[i].size = 1;
+ sectors[i].deleted = true;
+ sectors[i].bad_crc = false;
+ sectors[i].data = 0;
+ }
+
+ uint32_t data_ptr = header_size;
+ bool ddensity = false;
+ // Search for sectors on this track & side
+ for (uint32_t sect = 0; sect < entries; sect++)
+ {
+ uint8_t track = data[sect*3];
+ uint8_t sector = data[sect*3+1];
+ uint8_t flags = data[sect*3+2];
+ uint8_t side = (flags & 0x10) ? 1 : 0;
+ uint8_t size = (flags & 0x03);
+ if (track == 0xff)
+ size ^= 2;
+ else
+ size ^= 1;
+ uint16_t sector_size = 128 << size;
+ if ((track == curr_track) && (side == curr_side))
+ {
+ // process the sector
+ sectors[sector].track = curr_track;
+ sectors[sector].head = curr_side;
+ sectors[sector].sector = sector;
+ sectors[sector].actual_size = sector_size;
+ sectors[sector].size = size;
+ uint8_t dam = flags & 0xe0;
+ sectors[sector].deleted = ((dam == 0x60) || (dam == 0xa0)) ? true : false;
+ sectors[sector].bad_crc = (flags & 0x08) ? true : false;
+ sectors[sector].data = &data[data_ptr];
+ if (flags & 0x80)
+ ddensity = true;
+ }
+ data_ptr += sector_size;
+ }
+
+ //osd_printf_verbose("Side %d, Track %d, %s density\n",curr_side,curr_track,ddensity ? "Double" : "Single");
+ if (ddensity)
+ {
+ is_dd = true;
+ build_wd_track_mfm(curr_track, curr_side, image, 100000, max_sect, sectors, 32, 31, 22);
+ }
+ else
+ build_wd_track_fm(curr_track, curr_side, image, 50000, max_sect, sectors, 14, 12, 11);
+
+ if (curr_side)
+ is_ds = true;
+ }
+ }
+ }
+ if (is_dd)
+ {
+ if (is_ds)
+ image.set_variant(floppy_image::DSDD);
+ else
+ image.set_variant(floppy_image::SSDD);
+ }
+ else
+ {
+ if (is_ds)
+ image.set_variant(floppy_image::DSSD);
+ else
+ image.set_variant(floppy_image::SSSD);
+ }
+ return true;
+}
+
+bool jv3_format::save(util::random_read_write &io, const std::vector<uint32_t> &variants, const floppy_image &image) const
+{
+ int track_count, head_count;
+ image.get_actual_geometry(track_count, head_count);
+
+ if (track_count)
+ {
+ // If the disk already exists, find out if it's writable
+ uint64_t image_size;
+ if (!io.length(image_size) && (image_size >= 0x2200))
+ {
+ uint8_t flag;
+ auto const [err, actual] = read_at(io, 0x21ff, &flag, 1);
+ if (err || (1 != actual)) // TODO: should we save over the top if we couldn’t read the read-only flag?
+ return false;
+ if (flag == 0)
+ return false; // disk is read-only
+ }
+ }
+
+ uint32_t data_ptr = 0x2200, sect_ptr = 0;
+
+
+ // prepare default header, which we will partially overwrite later
+ uint8_t header[0x2200];
+ std::fill_n(header, std::size(header), 0xFF);
+
+ if (!track_count)
+ {
+ track_count = 35;
+ head_count = 1;
+ }
+
+ // write disk data
+ for (uint8_t head = 0; head < head_count; head++)
+ {
+ for (uint8_t track = 0; track < track_count; track++)
+ {
+ auto bitstream = generate_bitstream_from_track(track, head, 2000, image);
+ auto sectors = extract_sectors_from_bitstream_mfm_pc(bitstream);
+ bool ddensity = true;
+ if (sectors.empty())
+ {
+ bitstream = generate_bitstream_from_track(track, head, 4000, image);
+ sectors = extract_sectors_from_bitstream_fm_pc(bitstream);
+ ddensity = false;
+ }
+
+ if (sectors.empty())
+ {
+ // write dummy data. If we write nothing at all, mame hangs
+ uint8_t dummy[256];
+ std::fill_n(dummy, std::size(dummy), 0xE5);
+ for (uint8_t i = 0; i < 10; i++)
+ {
+ header[sect_ptr++] = track;
+ header[sect_ptr++] = i;
+ header[sect_ptr++] = head ? 0x10 : 0;
+ /*auto const [err, actual] =*/ write_at(io, data_ptr, &dummy[0], 256); // FIXME: check for errors
+ data_ptr += 256;
+ }
+ }
+ else
+ {
+ // How many sectors?
+ uint8_t count = 0;
+ for (const auto &s : sectors)
+ if (!s.empty())
+ count++;
+
+ // Save them
+ for (uint8_t i = 0; i < count; i++)
+ {
+ header[sect_ptr++] = track;
+ header[sect_ptr++] = i;
+ uint8_t flags = (ddensity) ? 0x80 : 0;
+ flags |= (sectors[i].size() >> 8) ^1;
+ flags |= head ? 0x10 : 0;
+ header[sect_ptr++] = flags;
+ /*auto const [err, actual] =*/ write_at(io, data_ptr, sectors[i].data(), sectors[i].size()); // FIXME: check for errors
+ data_ptr += sectors[i].size();
+ }
+ }
+ }
+ }
+ // Save the header
+ /*auto const [err, actual] =*/ write_at(io, 0, header, 0x2200); // FIXME: check for errors
+
+ return true;
+}
+
+
+bool jv3_format::supports_save() const noexcept
+{
+ return true;
+}
+
+const jv3_format FLOPPY_JV3_FORMAT;
+