diff options
author | Michael Zapf <Michael.Zapf@mizapf.de> | 2018-08-03 22:01:39 +0200 |
---|---|---|
committer | Michael Zapf <Michael.Zapf@mizapf.de> | 2018-08-03 22:01:39 +0200 |
commit | 801935badbc9cec92690559942edb23a19711234 (patch) | |
tree | 0772ed19953be6aae27f3d2a1397c2f2882d988b | |
parent | 9768491e279f766401e06c06c9ae148c7d615a81 (diff) |
ti99: Fixed long-standing TDF bug; added support for 16-sector formats
-rw-r--r-- | src/lib/formats/ti99_dsk.cpp | 1576 | ||||
-rw-r--r-- | src/lib/formats/ti99_dsk.h | 30 |
2 files changed, 971 insertions, 635 deletions
diff --git a/src/lib/formats/ti99_dsk.cpp b/src/lib/formats/ti99_dsk.cpp index 1b198295947..394bb85f1ce 100644 --- a/src/lib/formats/ti99_dsk.cpp +++ b/src/lib/formats/ti99_dsk.cpp @@ -1,44 +1,49 @@ // license:BSD-3-Clause // copyright-holders:Michael Zapf /********************************************************************* - * - * TI-99 family disk images - * - * used by TI-99/4, TI-99/4A, TI-99/8, SGCPU ("TI-99/4P"), and Geneve - * - * Sector Dump Format, aka "v9t9" format - * customized wd177x_format - * no track data - * - * Track Dump Format, aka "pc99" format - * contains all track information, but no clock patterns - * - * Both formats allow for a broad range of medium sizes. All sectors are 256 - * bytes long. The most common formats are 9 sectors per track, single-sided, - * 40 tracks, which yields 90 KiB of sector data (known as SSSD), and 18 - * sectors per track, double-sided, and 40 tracks, which is 360 KiB (known as - * DSDD). There are rare occurances of 8/16 sectors/track - * (prototypical TI double-density controller) and 35 track media. Newer - * controllers and ROMs allow for up to 36 sectors per track and 80 tracks on - * both sides, which is 1,44 MiB (DSHD80). - * - * Double stepping - * -------------- - * When using a 40-track disk in an 80-track drive, it seems as if each - * track appears twice in sequence (0,0,1,1,2,2,...,39,39). - * The system will write to each second track and leave the others untouched. - * When we write back that image, there is no simple way to know that - * the 80 tracks are in fact doubled 40 tracks. We will actually write - * all 80 tracks, doubling the size of the image file. This disk image will - * now become unusable in a 40-track drive - which is exactly what happens in reality. - * - * Michael Zapf, July 2015 - * - ********************************************************************/ + + TI-99 family disk images + + used by TI-99/4, TI-99/4A, TI-99/8, SGCPU ("TI-99/4P"), and Geneve + + Sector Dump Format, aka "v9t9" format + customized wd177x_format + no track data + + Track Dump Format, aka "pc99" format + contains all track information, but no clock patterns + + Both formats allow for a broad range of medium sizes. All sectors are 256 + bytes long. The most common formats are 9 sectors per track, single-sided, + 40 tracks, which yields 90 KiB of sector data (known as SSSD), and 18 + sectors per track, double-sided, and 40 tracks, which is 360 KiB (known as + DSDD). There are rare occurances of 16 sectors/track + (prototypical TI double-density controller) and 35 track media. Newer + controllers and ROMs allow for up to 36 sectors per track and 80 tracks on + both sides, which is 1,44 MiB (DSHD80). + + Double stepping + -------------- + When using a 40-track disk in an 80-track drive, it seems as if each + track appears twice in sequence (0,0,1,1,2,2,...,39,39). + The system will write to each second track and leave the others untouched. + When we write back that image, there is no simple way to know that + the 80 tracks are in fact doubled 40 tracks. We will actually write + all 80 tracks, doubling the size of the image file. This disk image will + now become unusable in a 40-track drive - which is exactly what happens in reality. + + Michael Zapf, July 2015 + Updated August 2018 + + TODO: + - Support for 77 track images for HX5102 + +********************************************************************/ #include <string.h> #include <time.h> #include <assert.h> +#include <iomanip> #include "emu.h" // osd_printf_* (in osdcore.h) #include "imageutl.h" @@ -46,46 +51,25 @@ #define SECTOR_SIZE 256 -// Debugging -#define TRACE 0 +#undef LOG_OUTPUT_FUNC +#define LOG_OUTPUT_FUNC osd_printf_info + +#define LOG_WARN (1U<<1) // Warnings +#define LOG_HEADER (1U<<2) // Header +#define LOG_SHIFT (1U<<3) // Shift register +#define LOG_INTERLEAVE (1U<<4) // Interleave information +#define LOG_INFO (1U<<5) // Disk info +#define LOG_DETAIL (1U<<6) // Details +#define LOG_TRACK (1U<<7) // Track output + +#define VERBOSE ( LOG_WARN ) + +#include "logmacro.h" // ==================================================== // Common methods for both formats. // ==================================================== -/* - Find out whether there are IDAMs and DAMs (without having clock bits). - As said above, let's not spend too much effort allowing format deviations. - If the image does not exactly adhere to the format, give up. -*/ -bool ti99_floppy_format::check_for_address_marks(uint8_t* trackdata, int encoding) -{ - int i=0; - - if (encoding==floppy_image::FM) - { - // Check 5 sectors of track 0 - while (i < 5) - { - if (trackdata[16 + 6 + i*334] != 0xfe) break; - if (trackdata[16 + 30 + i*334] != 0xfb && trackdata[16 + 30 + i*334] != 0xf8) break; - i++; - } - } - else - { - // Try MFM - i = 0; - while (i < 5) - { - if (trackdata[40 + 13 + i*340] != 0xfe) break; - if (trackdata[40 + 57 + i*340] != 0xfb && trackdata[40 + 57 + i*340] != 0xf8) break; - i++; - } - } - return (i==5); -} - int ti99_floppy_format::get_encoding(int cell_size) { return (cell_size==4000)? floppy_image::FM : floppy_image::MFM; @@ -100,18 +84,30 @@ bool ti99_floppy_format::load(io_generic *io, uint32_t form_factor, floppy_image int sector_count = 0; int heads = 0; int log_track_count = 0; - determine_sizes(io, cell_size, sector_count, heads, log_track_count); + int file_size = io_generic_size(io); - if (cell_size == 0) return false; + bool img_high_tpi = false; + bool drive_high_tpi = false; + bool skip_track = false; + int track_count, head_count; - // Be ready to hold a track of up to 36 sectors (with gaps and marks) - uint8_t trackdata[13000]; + // Get information about cell size, sector count, and logical track count + // (the track count as defined by the file system) + determine_sizes(io, cell_size, sector_count, heads, log_track_count); - int maxtrack, maxheads; - image->get_maximal_geometry(maxtrack, maxheads); + image->get_maximal_geometry(track_count, head_count); + drive_high_tpi = (track_count > 44); - int file_size = io_generic_size(io); - int track_size = get_track_size(cell_size, sector_count); + // Depends on the image format (SDF or TDF) + int track_size = get_track_size(sector_count); + + // Adding support for a variant of the sector image format which adds 768 bytes + // as a bad sector map. Only applies for SDF; TDF will not yield that result + if (((file_size - 768) % (SECTOR_SIZE*360)) == 0) + { + LOGMASKED(LOG_INFO, "[ti99_dsk] Stripping map of bad sectors at image end\n"); + file_size -= 768; + } // Problem: If the disk is improperly formatted, the track count will be // wrong. For instance, a disk could be reformatted to single-side. @@ -119,379 +115,426 @@ bool ti99_floppy_format::load(io_generic *io, uint32_t form_factor, floppy_image if ((heads==1) && (file_size > track_size*40)) heads = 2; - int phys_track_count = file_size / (track_size*heads); + // TI formats are known as having a constant track size, so the physical + // track count on the medium can easily be calculated. + int img_track_count = file_size / (track_size*heads); + img_high_tpi = (img_track_count > 44); // Some disks are known to have an incomplete header. // PASCAL disks have a track count of 0. - if (log_track_count==0) log_track_count = phys_track_count; + if (log_track_count == 0) log_track_count = img_track_count; - if (TRACE) osd_printf_info("ti99_dsk: logical tracks = %d, physical tracks = %d\n", log_track_count, phys_track_count); + LOGMASKED(LOG_INFO, "[ti99_dsk] logical tracks = %d, image tracks = %d, drive track_count = %d\n", log_track_count, img_track_count, track_count); - if (phys_track_count > maxtrack) + // If the floppy disk has 80 tracks but the drive has 40 tracks, force + // the floppy to 40 tracks (will make it unreadable) + if (img_high_tpi && !drive_high_tpi) { - osd_printf_error("ti99_dsk: Floppy disk has too many tracks for this drive.\n"); - return false; + LOGMASKED(LOG_WARN, "[ti99_dsk] Floppy disk has too many tracks for this drive, will skip every second track.\n"); + img_high_tpi = false; + skip_track = true; // only read the even numbered tracks } // Is this the first time that this disk is read in an 80-track drive? - bool double_step = ((log_track_count * 2) <= maxtrack); - bool first_time_double = ((phys_track_count * 2) <= maxtrack); + bool double_step = (drive_high_tpi && log_track_count <= 44); + bool first_time_double = (drive_high_tpi && !img_high_tpi); - if (first_time_double) - { - osd_printf_warning("ti99_dsk: 40-track image in an 80-track drive. On save, image size will double.\n"); - } + if (double_step) + LOGMASKED(LOG_INFO, "[ti99_dsk] Applying double stepping\n"); - int acttrack; + if (first_time_double) + LOGMASKED(LOG_WARN, "[ti99_dsk] 40-track image in an 80-track drive. On save, image size will double.\n"); + int trackid; // Read the image + uint8_t sectordata[36*SECTOR_SIZE]; + int sectornmb[36]; + int sectoroff[36]; + for (int head=0; head < heads; head++) { - for (int track=0; track < phys_track_count; track++) + for (int track=0; track < img_track_count; track++) { - if (double_step && !first_time_double) acttrack = track/2; - else acttrack = track; + LOGMASKED(LOG_TRACK, "[ti99_dsk] Reading track %d, head %d\n", track, head); + if (double_step && !first_time_double) trackid = track/2; + else trackid = track; - load_track(io, trackdata, head, track, acttrack, sector_count, phys_track_count, cell_size); + load_track(io, sectordata, sectornmb, sectoroff, head, track, sector_count, img_track_count); - if (first_time_double) + if (double_step) { - // Create two tracks from each medium track. This reflects the - // fact that the drive head will find the same data after - // a single step - if (get_encoding(cell_size)==floppy_image::FM) + if (first_time_double) { - generate_track_fm(track*2, head, cell_size, trackdata, image); - generate_track_fm(track*2+1, head, cell_size, trackdata, image); + // Create two tracks from each medium track. This reflects the + // fact that the drive head will find the same data after + // a single step + if (sector_count < 10) + { + generate_fm_track_from_sectors(image, sectordata, sector_count, sectornmb, sectoroff, track*2, trackid, head); + generate_fm_track_from_sectors(image, sectordata, sector_count, sectornmb, sectoroff, track*2+1, trackid, head); + } + else + { + generate_mfm_track_from_sectors(image, sectordata, sector_count, sectornmb, sectoroff, track*2, trackid, head); + generate_mfm_track_from_sectors(image, sectordata, sector_count, sectornmb, sectoroff, track*2+1, trackid, head); + } } else { - generate_track_mfm(track*2, head, cell_size, trackdata, image); - generate_track_mfm(track*2+1, head, cell_size, trackdata, image); + // Reading 80 track image for double stepping + if (sector_count < 10) + generate_fm_track_from_sectors(image, sectordata, sector_count, sectornmb, sectoroff, track, trackid, head); + else + generate_mfm_track_from_sectors(image, sectordata, sector_count, sectornmb, sectoroff, track, trackid, head); } } else { + // No double stepping now, but maybe the image was created under double stepping + int drivetrack = (skip_track)? track / 2 : track; + // Normal tracks - if (get_encoding(cell_size)==floppy_image::FM) - generate_track_fm(track, head, cell_size, trackdata, image); + if (sector_count < 10) + generate_fm_track_from_sectors(image, sectordata, sector_count, sectornmb, sectoroff, drivetrack, drivetrack, head); else - generate_track_mfm(track, head, cell_size, trackdata, image); + generate_mfm_track_from_sectors(image, sectordata, sector_count, sectornmb, sectoroff, drivetrack, drivetrack, head); } + if (skip_track) track++; } } return true; } +/* + Save all tracks to the image file. +*/ bool ti99_floppy_format::save(io_generic *io, floppy_image *image) { int act_track_size = 0; uint8_t bitstream[500000/8]; - uint8_t trackdata[9216]; // max size + uint8_t sectordata[9216]; // max size (36*256) int cellsizes[] = { 2000, 4000, 1000, 2000 }; // Do we use double-stepping? // If our image was loaded into a 80-track drive, we will always write 80 tracks. - int maxtrack, maxheads; - image->get_maximal_geometry(maxtrack, maxheads); + int track_count, head_count; + image->get_maximal_geometry(track_count, head_count); - if (maxtrack > 80) maxtrack = 80; + if (track_count > 80) track_count = 80; else { - if (maxtrack > 35 && maxtrack < 80) maxtrack = 40; - else maxtrack = 35; + if (track_count > 35 && track_count < 80) track_count = 40; + else track_count = 35; } int attempt = 0; int sector[36]; - int maxsect = 18; - bool write = true; + int seccount = 0; - // We expect a bitstream of length 50000 for FM and 100000 for MFM - for(int head=0; head < 2; head++) + int head = 0; + int cell_size = cellsizes[0]; + int encoding = get_encoding(cell_size); + int expected_sectors = (encoding==floppy_image::MFM)? 16:9; + + while (head < 2) { - int track = 0; - while (track < maxtrack) + for (int track = 0; track < track_count; track++) { - int cell_size = cellsizes[attempt]; - int encoding = get_encoding(cell_size); - int track_size = get_track_size(cell_size, 36); // max number of sectors - - // Retrieve the cells from the flux sequence + // Retrieve the cells from the flux sequence. This also delivers the actual track size. generate_bitstream_from_track(track, head, cell_size, bitstream, act_track_size, image); // Maybe the track has become longer due to moving splices if (act_track_size > 200000000/cell_size) act_track_size = 200000000/cell_size; - int marks = decode_bitstream(bitstream, trackdata, sector, act_track_size, encoding, (encoding==floppy_image::FM)? 0xff:0x4e, track_size); + LOGMASKED(LOG_DETAIL, "[ti99_dsk] Getting sectors from track %d, head %d\n", track, head); + seccount = get_sectors(bitstream, act_track_size, encoding, track, head, expected_sectors, sectordata, sector); + LOGMASKED(LOG_DETAIL, "[ti99_dsk] Seccount = %d\n", seccount); - if (track==0) + // We may have more sectors in MFM (18). This is OK in track 0; otherwise we need to restart the process. + if (track==0 && head==0) { - if (head==0) + if (seccount==18 && expected_sectors==16) { - // Find the highest sector in the track - // This is only needed for the SDF format - int i=35; - while (i>=0 && sector[i]==-1) i--; + LOGMASKED(LOG_INFO, "[ti99_dsk] Using full 18 sectors/track format for MFM\n"); + expected_sectors=18; + } + } + else + { + if (seccount==18 && expected_sectors==16) + { + // This happens when we assumed 16 sectors/track up to here + // and now the track uses 18 tracks. We need to start over, + // assuming 18 sectors/track, and adding empty sectors + // where necessary + head = -1; + LOGMASKED(LOG_WARN, "[ti99_dsk] Tracks with variable sector count; assuming 18 sectors/track\n"); + break; + } + } - if (i>18) maxsect = 36; - else + if (seccount < 3 && track == 0) + { + if (head == 0) + { + // Bad cell size. + LOGMASKED(LOG_DETAIL, "[ti99_dsk] Decoding with cell size %d failed.\n", cell_size); + attempt++; + if (attempt == 4) { - if (i>16) maxsect = 18; - else - { - if (i>9) maxsect = 16; - else maxsect = 9; - } + LOGMASKED(LOG_INFO, "[ti99_dsk] Unsupported cell size or unformatted medium.\n"); + return false; } - if (TRACE) osd_printf_info("ti99_dsk: Sectors/track: %d\n", maxsect); - - // We try different cell sizes until we find a fitting size. - // If this fails, we fall back to a size of 2000 ns - // The criterion for a successful decoding is that we find at least - // 6 ID or data address marks on the track. It is highly unlikely - // to find so many marks with a wrong cell size. - if (marks < 6 && attempt < 3) + else { - if (TRACE) osd_printf_verbose("ti99_dsk: Decoding with cell size %d failed.\n", cell_size); - attempt++; - write = false; + // Pick a new cell size + cell_size = cellsizes[attempt]; + encoding = get_encoding(cell_size); + expected_sectors = (encoding==floppy_image::MFM)? 16:9; } - else write = true; + head = -1; + break; } else { - if (marks < 6) + // head 0 already done, but head 1 failing + if (min_heads()==1) { - // There are no address marks on track 0, head 1. - if (min_heads()==1) - { - if (TRACE) osd_printf_info("ti99_dsk: We don't have a second side and the format allows for single-sided recording.\n"); - return true; - } - else - { - osd_printf_warning("ti99_dsk: No second side, but this format requires two-sided recording. Saving empty tracks.\n"); - } + LOGMASKED(LOG_INFO, "[ti99_dsk] Saving as single-sided\n"); + break; } - } - } - - if (write) - { - if (TRACE) - { - if (head == 0 && track == 0) + else { - if (marks >=6) { if (TRACE) osd_printf_info("ti99_dsk: Decoding with cell size %d successful.\n", cell_size); } - else osd_printf_info("ti99_dsk: No address marks found on track 0. Assuming FM format.\n"); + LOGMASKED(LOG_WARN, "[ti99_dsk] Saving empty tracks on second side\n"); } } - // Save to the file - write_track(io, trackdata, sector, track, head, maxsect, maxtrack, track_size); - track++; } + + // Save to the file + write_track(io, sectordata, sector, track, head, seccount, track_count); } + head++; } - return true; } -void ti99_floppy_format::generate_track_fm(int track, int head, int cell_size, uint8_t* trackdata, floppy_image *image) +enum +{ + GAP0 = 0, GAP1, GAP2, GAP3, GAPBYTE, SYNC +}; + +/* + Build a new track from sector contents. +*/ +void ti99_floppy_format::generate_fm_track_from_sectors(floppy_image *image, uint8_t *sectordata, int sector_count, int *sectornmb, int *secoffset, int track, int trackid, int head) { - int track_size_cells = 200000000/cell_size; std::vector<uint32_t> buffer; - // The TDF has a long track lead-out that makes the track length sum up - // to 3253; this is too long for the number of cells in the real track. - // This was either an error when that format was defined, - // or it is due to the fact that when reading a track via - // the controller, after the track has been read, the controller still - // delivers some FF values until it times out. + const int param[6] = { 10, 26, 11, 28, 0xff, 6 }; - // Accordingly, we limit the track size to cell_number / 16, - // which means 3125 for FM - // This also means that Gap 4 (lead-out) is not 231 bytes long but only 103 bytes + // cell size in ns + const int cell_size = 4000; + const int cell_count = 200000000/cell_size; - int track_size = track_size_cells / 16; + int sectorpartlen = 2*param[SYNC] + 10 + param[GAP2] + SECTOR_SIZE + param[GAP3]; + int tracklen = param[GAP1] + sector_count * sectorpartlen; + if (param[GAP0]!=0) tracklen += param[GAP0] + param[SYNC] + 1; + // The remaining bytes are filled with the gap byte as gap 4. + int gap4 = cell_count/16 - tracklen; - short crc1, crc2, found_crc; - int start = 16; + short crc = 0; - if (check_for_address_marks(trackdata, floppy_image::FM)==false) - { - if (head==0 && track==0) osd_printf_warning("ti99_dsk: Cannot find FM address marks on track %d, head %d; likely broken or unformatted.\n", track, head); - return; - } + // Create Gap 0 and Index AM + for (int i=0; i < param[GAP0]; i++) + fm_w(buffer, 8, param[GAPBYTE], cell_size); + + for (int i=0; i < param[SYNC]; i++) + fm_w(buffer, 8, 0x00, cell_size); + + raw_w(buffer, 16, 0xf77a, cell_size); // IXAM - // Found a track; we now know where the address marks are: - // (start is positioned at the pre-id gap) - // IDAM: start + 6 + n*334 - // DAM: start + 30 + n*334 - // and the CRCs are at - // CRC1: start + 11 + n*334 - // CRC2: start + 287 + n*334 - // If the CRCs are F7F7, we recalculate them. - for (int i=0; i < track_size; i++) + // and Gap 1 + for (int i=0; i < param[GAP1]; i++) + fm_w(buffer, 8, param[GAPBYTE], cell_size); + + // For each sector + for (int j=0; j < sector_count; j++) { - if (((i-start-6)%334==0) && (i < start + 9*334)) + // If the sector is not available, create an empty space of zeros + if (sectornmb[j]==-1) { - // IDAM - raw_w(buffer, 16, 0xf57e, cell_size); + for (int i=0; i < sectorpartlen; i++) + raw_w(buffer, 16, 0x0000, cell_size); } else { - if (((i-start-30)%334==0) && (i < start + 9*334)) + // Sync + for (int i=0; i < param[SYNC]; i++) + fm_w(buffer, 8, 0x00, cell_size); + raw_w(buffer, 16, 0xf57e, cell_size); // IDAM + + crc = ccitt_crc16_one(0xffff, 0xfe); + + // Header + fm_w(buffer, 8, trackid, cell_size); + crc = ccitt_crc16_one(crc, trackid); + fm_w(buffer, 8, head, cell_size); + crc = ccitt_crc16_one(crc, head); + fm_w(buffer, 8, sectornmb[j], cell_size); + crc = ccitt_crc16_one(crc, sectornmb[j]); + fm_w(buffer, 8, 0x01, cell_size); + crc = ccitt_crc16_one(crc, 0x01); + // CRC + fm_w(buffer, 8, (crc>>8)&0xff, cell_size); + fm_w(buffer, 8, crc&0xff, cell_size); + + // Gap 2 + for (int i=0; i < param[GAP2]; i++) + fm_w(buffer, 8, param[GAPBYTE], cell_size); + // Sync + for (int i=0; i < param[SYNC]; i++) + fm_w(buffer, 8, 0x00, cell_size); + raw_w(buffer, 16, 0xf56f, cell_size); // DAM + crc = ccitt_crc16_one(0xffff, 0xfb); + + // Sector contents + for (int k=0; k < SECTOR_SIZE; k++) { - // DAM - // FB (1111010101101111) = normal data, F8 (1111010101101010)= deleted data - raw_w(buffer, 16, (trackdata[i]==0xf8)? 0xf56a : 0xf56f, cell_size); - } - else - { - if (((i-start-11)%334==0) && (i < start + 9*334)) - { - // CRC1 - crc1 = ccitt_crc16(0xffff, &trackdata[i-5], 5); - found_crc = (trackdata[i]<<8 | trackdata[i+1]); - if ((found_crc & 0xffff) == 0xf7f7) - { - // PC99 format: no real CRC; replace it - // Also, when converting from SDF we let this method create the proper CRC. - // osd_printf_verbose("Warning: PC99 format using pseudo CRC1; replace by %04x\n", crc1); - trackdata[i] = (crc1 >> 8) & 0xff; - trackdata[i+1] = crc1 & 0xff; - found_crc = crc1; - } - if (crc1 != found_crc) - { - osd_printf_error("ti99_dsk: Warning: CRC1 does not match (track=%d, head=%d). Found = %04x, calc = %04x\n", track, head, found_crc& 0xffff, crc1& 0xffff); - } - } - else - { - if (((i-start-287)%334==0) && (i < start + 9*334)) - { - // CRC2 - crc2 = ccitt_crc16(0xffff, &trackdata[i-SECTOR_SIZE-1], SECTOR_SIZE+1); - found_crc = (trackdata[i]<<8 | trackdata[i+1]); - if ((found_crc & 0xffff) == 0xf7f7) - { - // PC99 format: no real CRC; replace it - // osd_printf_verbose("Warning: PC99 format using pseudo CRC2; replace by %04x\n", crc2); - trackdata[i] = (crc2 >> 8) & 0xff; - trackdata[i+1] = crc2 & 0xff; - found_crc = crc2; - } - if (crc2 != found_crc) - { - osd_printf_error("ti99_dsk: Warning: CRC2 does not match (track=%d, head=%d). Found = %04x, calc = %04x\n", track, head, found_crc& 0xffff, crc2& 0xffff); - } - } - } - fm_w(buffer, 8, trackdata[i], cell_size); + fm_w(buffer, 8, sectordata[secoffset[j]+k], cell_size); + crc = ccitt_crc16_one(crc, sectordata[secoffset[j]+k]); } + // CRC + fm_w(buffer, 8, (crc>>8)&0xff, cell_size); + fm_w(buffer, 8, crc&0xff, cell_size); + + // Gap 3 + for (int i=0; i < param[GAP3]; i++) + fm_w(buffer, 8, param[GAPBYTE], cell_size); } } + // and fill the remaining bytes with gap bytes + for (int i=0; i < gap4; i++) + fm_w(buffer, 8, param[GAPBYTE], cell_size); + generate_track_from_levels(track, head, buffer, 0, image); } -void ti99_floppy_format::generate_track_mfm(int track, int head, int cell_size, uint8_t* trackdata, floppy_image *image) +void ti99_floppy_format::generate_mfm_track_from_sectors(floppy_image *image, uint8_t *sectordata, int sector_count, int *sectornmb, int *secoffset, int track, int trackid, int head) { - int track_size_cells = 200000000/cell_size; + // MFM16: (80,50,22,50,4e,12) + // MFM18/36: (10,30,22,21,4e,12) + // A1: 0x4489 01 00 01 00 10 *00 10 01 + // C2: 0x5224 01 01 00 10 *00 10 01 00 std::vector<uint32_t> buffer; - // See above - // We limit the track size to cell_number / 16, which means 6250 for MFM - // Here, Gap 4 is actually only 90 bytes long - // (not 712 as specified in the TDF format) - int track_size = track_size_cells / 16; + const int mfm16param[7] = { 80, 50, 22, 50, 0x4e, 12 }; + const int mfm18param[7] = { 10, 30, 22, 21, 0x4e, 12 }; + const int *param = mfm18param; - short crc1, crc2, found_crc; - int start = 40; + // cell size in ns + int cell_size = 2000; - if (check_for_address_marks(trackdata, floppy_image::MFM)==false) + if (sector_count < 30) { - if (track==0 && head==0) osd_printf_error("ti99_dsk: Cannot find MFM address marks on track %d, head %d; likely broken or unformatted.\n", track, head); - return; + cell_size = 2000; + if (sector_count == 16) param = mfm16param; } + else cell_size = 1000; + + int cell_count = 200000000/cell_size; + + int sectorpartlen = 2*param[SYNC] + 16 + param[GAP2] + SECTOR_SIZE + param[GAP3]; + int tracklen = param[GAP1] + sector_count * sectorpartlen; + if (param[GAP0]!=0) tracklen += param[GAP0] + param[SYNC] + 4; + // The remaining bytes are filled with the gap byte as gap 4. + int gap4 = cell_count/16 - tracklen; + + short crc = 0; + + // Create Gap 0 and Index AM + for (int i=0; i < param[GAP0]; i++) + mfm_w(buffer, 8, param[GAPBYTE], cell_size); - // Found a track; we now know where the address marks are: - // (start is positioned at the pre-id gap) - // IDAM: start + 10 + n*340 (starting at first a1) - // DAM: start + 54 + n*340 (starting at first a1) - // and the CRCs are at - // CRC1: start + 18 + n*340 - // CRC2: start + 314 + n*334 + for (int i=0; i < param[SYNC]; i++) + mfm_w(buffer, 8, 0x00, cell_size); - for (int i=0; i < track_size; i++) + for (int i=0; i < 3; i++) + raw_w(buffer, 16, 0x5224, cell_size); + mfm_w(buffer, 8, 0xfc, cell_size); + + // and Gap 1 + for (int i=0; i < param[GAP1]; i++) + mfm_w(buffer, 8, param[GAPBYTE], cell_size); + + // For each sector + for (int j=0; j < sector_count; j++) { - if (((i-start-10)%340==0) && (i < start + 18*340)) + // If the sector is not available, create an empty space of zeros + if (sectornmb[j]==-1) { - // IDAM - for (int j=0; j < 3; j++) - raw_w(buffer, 16, 0x4489, cell_size); // 3 times A1 - i += 2; + for (int i=0; i < sectorpartlen; i++) + raw_w(buffer, 16, 0x0000, cell_size); } else { - if (((i-start-54)%340==0) && (i < start + 18*340)) + // Sync + for (int i=0; i < param[SYNC]; i++) + mfm_w(buffer, 8, 0x00, cell_size); + // IDAM + for (int i=0; i < 3; i++) + raw_w(buffer, 16, 0x4489, cell_size); + mfm_w(buffer, 8, 0xfe, cell_size); + + crc = 0xb230; // pre-init including IDAM + + // Header + mfm_w(buffer, 8, trackid, cell_size); + crc = ccitt_crc16_one(crc, trackid); + mfm_w(buffer, 8, head, cell_size); + crc = ccitt_crc16_one(crc, head); + mfm_w(buffer, 8, sectornmb[j], cell_size); + crc = ccitt_crc16_one(crc, sectornmb[j]); + mfm_w(buffer, 8, 0x01, cell_size); + crc = ccitt_crc16_one(crc, 0x01); + // CRC + mfm_w(buffer, 8, (crc>>8)&0xff, cell_size); + mfm_w(buffer, 8, crc&0xff, cell_size); + + // Gap 2 + for (int i=0; i < param[GAP2]; i++) + mfm_w(buffer, 8, param[GAPBYTE], cell_size); + // Sync + for (int i=0; i < param[SYNC]; i++) + mfm_w(buffer, 8, 0x00, cell_size); + + // DAM + for (int i=0; i < 3; i++) + raw_w(buffer, 16, 0x4489, cell_size); + mfm_w(buffer, 8, 0xfb, cell_size); + crc = 0xe295; // pre-init including DAM + + // Sector contents + for (int k=0; k < SECTOR_SIZE; k++) { - // DAM - for (int j=0; j < 3; j++) - raw_w(buffer, 16, 0x4489, cell_size); // 3 times A1 - i += 2; - } - else - { - if (((i-start-18)%340==0) && (i < start + 18*340)) - { - // CRC1 - // The CRC also covers the three A1 bytes! - crc1 = ccitt_crc16(0xffff, &trackdata[i-8], 8); - found_crc = (trackdata[i]<<8 | trackdata[i+1]); - if ((found_crc & 0xffff) == 0xf7f7) - { - // PC99 format: pseudo CRC; replace it - // osd_printf_verbose("Warning: PC99 format using pseudo CRC1; replace by %04x\n", crc1); - trackdata[i] = (crc1 >> 8) & 0xff; - trackdata[i+1] = crc1 & 0xff; - found_crc = crc1; - } - if (crc1 != found_crc) - { - osd_printf_error("ti99_dsk: Warning: CRC1 does not match (track=%d, head=%d). Found = %04x, calc = %04x\n", track, head, found_crc & 0xffff, crc1& 0xffff); - } - } - else - { - if ((i > 340) && ((i-start-314)%340==0) && (i < start + 18*340)) - { - // CRC2 - crc2 = ccitt_crc16(0xffff, &trackdata[i-SECTOR_SIZE-4], SECTOR_SIZE+4); - found_crc = (trackdata[i]<<8 | trackdata[i+1]); - if ((found_crc & 0xffff) == 0xf7f7) - { - // PC99 format: pseudo CRC; replace it - // osd_printf_verbose("Warning: PC99 format using pseudo CRC2; replace by %04x\n", crc2); - trackdata[i] = (crc2 >> 8) & 0xff; - trackdata[i+1] = crc2 & 0xff; - found_crc = crc2; - } - if (crc2 != found_crc) - { - osd_printf_error("ti99_dsk: Warning: CRC2 does not match (track=%d, head=%d). Found = %04x, calc = %04x\n", track, head, found_crc& 0xffff, crc2& 0xffff); - } - } - } - mfm_w(buffer, 8, trackdata[i], cell_size); + mfm_w(buffer, 8, sectordata[secoffset[j]+k], cell_size); + crc = ccitt_crc16_one(crc, sectordata[secoffset[j]+k]); } + // CRC + mfm_w(buffer, 8, (crc>>8)&0xff, cell_size); + mfm_w(buffer, 8, crc&0xff, cell_size); + + // Gap 3 + for (int i=0; i < param[GAP3]; i++) + mfm_w(buffer, 8, param[GAPBYTE], cell_size); } } + // and fill the remaining bytes with gap bytes + for (int i=0; i < gap4; i++) + mfm_w(buffer, 8, param[GAPBYTE], cell_size); generate_track_from_levels(track, head, buffer, 0, image); } @@ -500,44 +543,57 @@ void ti99_floppy_format::generate_track_mfm(int track, int head, int cell_size, enum { FMIDAM, + A1IDAM1, + A1IDAM2, + A1IDAM3, MFMIDAM, + MFMIDENT, HEADER, FMDAM, + A1DAM1, + A1DAM2, + A1DAM3, MFMDAM, DATA }; /* - Decodes the bitstream into a TDF track image. - Returns the number of detected marks. + Retrieve all sectors from the track, given as a bitstream. + The sectors are assumed to have a length of 256 byte, and their sequence + is stored in the secnumber array. */ -int ti99_floppy_format::decode_bitstream(const uint8_t *bitstream, uint8_t *trackdata, int* sector, int cell_count, int encoding, uint8_t gapbytes, int track_size) +int ti99_floppy_format::get_sectors(const uint8_t *bitstream, int cell_count, int encoding, int track, int head, int sectors, uint8_t *sectordata, int *secnumber) { - int databytes = 0; - int a1count = 0; + int bitpos = 0; int lastpos = 0; - int headerbytes = 0; - int curpos = 0; - uint8_t curbyte = 0; + int spos = 0; + int seccount = 0; uint16_t shift_reg = 0; - int tpos = 0; - int pos = 0; - int state; - int marks = 0; - int current_sector = 0; - - // Init track - memset(trackdata, 0x00, track_size); + int bytepos = 0; + uint8_t databyte = 0; + uint8_t curbyte = 0; + int rep = 0; + int cursect = 0; - for (int i=0; i < 36; i++) sector[i] = -1; + // An array of flags (max 32), set for each found sector + uint32_t foundsectors = 0; - state = (encoding==floppy_image::MFM)? MFMIDAM : FMIDAM; + int maxsect = (encoding==floppy_image::MFM)? 18 : 9; + int first = (encoding==floppy_image::MFM)? A1IDAM1 : FMIDAM; + int state = first; - while (pos < cell_count && tpos < track_size) + while (bitpos < cell_count) { + LOGMASKED(LOG_SHIFT, "[ti99_dsk] shift = %04x\n", shift_reg); shift_reg = (shift_reg << 1) & 0xffff; - if ((pos & 0x07)==0) curbyte = bitstream[curpos++]; + if ((bitpos & 0x07)==0) curbyte = bitstream[bytepos++]; if ((curbyte & 0x80) != 0) shift_reg |= 1; + + if (((bitpos - lastpos) & 1)==0) + { + databyte = (databyte << 1) & 0xff; + databyte |= shift_reg & 1; + } curbyte <<= 1; switch (state) @@ -545,157 +601,273 @@ int ti99_floppy_format::decode_bitstream(const uint8_t *bitstream, uint8_t *trac case FMIDAM: if (shift_reg == 0xf57e) { - marks++; - // Found a header - headerbytes = 5; - // Create GAP0 at the beginning or GAP3 later - if (tpos==0) - tpos += 16; - else - for (int i=0; i < 45; i++) trackdata[tpos++] = gapbytes; - - // IDAM sync bytes - tpos += 6; - trackdata[tpos++] = 0xfe; + // IDAM found + LOGMASKED(LOG_DETAIL, "[ti99_dsk] IDAM found at %05x\n", bitpos); state = HEADER; - lastpos = pos; + lastpos = bitpos; + rep = 0; } break; - - case MFMIDAM: - // Count three subsequent A1 + case A1IDAM1: if (shift_reg == 0x4489) { - if (lastpos > 0) + state = A1IDAM2; + lastpos = bitpos; + } + break; + case A1IDAM2: + case A1IDAM3: + if (bitpos - lastpos == 16) + { + if (shift_reg == 0x4489) { - if (pos - lastpos == 16) a1count++; - else a1count = 1; + state = (state==A1IDAM2)? A1IDAM3 : MFMIDENT; } - else a1count = 1; - - lastpos = pos; + else + { + LOGMASKED(LOG_WARN, "[ti99_dsk] Error: Missing A1, not a header at %05x\n", bitpos); + state = A1IDAM1; + } + lastpos = bitpos; } - if (a1count == 3) + break; + + case MFMIDENT: + if (bitpos - lastpos == 16) { - marks++; - // Found a header - headerbytes = 6; - // Create GAP0 at the beginning or GAP3 later - if (tpos==0) - for (int i=0; i < 40; i++) trackdata[tpos++] = gapbytes; + if (databyte != 0xfe) + { + LOGMASKED(LOG_WARN, "[ti99_dsk] Error: Ident (found) = %02x, expected = fe; skipping header\n", databyte); + state = A1IDAM1; + } else - for (int i=0; i < 24; i++) trackdata[tpos++] = gapbytes; - - // IDAM sync bytes - tpos += 10; - state = HEADER; - trackdata[tpos++] = 0xa1; - trackdata[tpos++] = 0xa1; - trackdata[tpos++] = 0xa1; + { + rep = 0; + state = HEADER; + } + lastpos = bitpos; } break; - case HEADER: - if (pos - lastpos == 16) + // Get the header, store the sector number, and report any + // unexpected contents in the other fields. When invalid, skip + // that header. + if (bitpos - lastpos == 16) { - // Transfer header bytes - trackdata[tpos] = get_data_from_encoding(shift_reg); + LOGMASKED(LOG_HEADER, "[ti99_dsk] Header byte: %02x\n", databyte); - if (headerbytes == 3) current_sector = trackdata[tpos]; - tpos++; - - if (headerbytes == 0) + switch (rep) { - state = (encoding==floppy_image::MFM)? MFMDAM : FMDAM; - a1count = 0; + case 0: + if (databyte > 79) + { + // Illegal cylinder, ignore this header. + LOGMASKED(LOG_WARN, "[ti99_dsk] Error: Track (found) = %d, must be less than 80; skipping header.\n", databyte); + state = (encoding==floppy_image::MFM)? A1IDAM1 : FMIDAM; + break; + } + // Consider double stepping + if (databyte != track && databyte != track/2) + LOGMASKED(LOG_WARN, "[ti99_dsk] Warning: Track (found) = %d, assuming %d\n", databyte, track); + break; + case 1: + if (databyte > 2) + { + // Illegal head, ignore this header. + LOGMASKED(LOG_WARN, "[ti99_dsk] Error: Head (found) = %d, must be less than 2; skipping header.\n", databyte); + state = (encoding==floppy_image::MFM)? A1IDAM1 : FMIDAM; + break; + } + if (databyte != head) + LOGMASKED(LOG_WARN, "[ti99_dsk] Warning: Head (found) = %d, assuming = %d\n", databyte, head); + break; + case 2: + if (databyte >= maxsect) + { + // Illegal sector, ignore this header. + LOGMASKED(LOG_WARN, "[ti99_dsk] Error: Sector number (found) = %d, must be less than %d; skipping header\n", databyte, maxsect); + state = (encoding==floppy_image::MFM)? A1IDAM1 : FMIDAM; + } + else cursect = databyte; + + if ((foundsectors & (1 << cursect)) != 0) + { + // Sector already appeared; discard it + LOGMASKED(LOG_WARN, "[ti99_dsk] Error: Sector number (found) = %d already appeared; skipping header\n", databyte); + state = (encoding==floppy_image::MFM)? A1IDAM1 : FMIDAM; + } + break; + case 3: + if (databyte != 1) + { + // Illegal sector, ignore this header. + LOGMASKED(LOG_WARN, "[ti99_dsk] Error: Sector size (found) = %d, must be 1; skipping header\n", databyte); + state = (encoding==floppy_image::MFM)? A1IDAM1 : FMIDAM; + } + else + { + state = (encoding==floppy_image::MFM)? A1DAM1 : FMDAM; + } + rep = -1; + break; } - else headerbytes--; - lastpos = pos; + rep++; + lastpos = bitpos; } break; - case FMDAM: + if (bitpos - lastpos > 500) + { + LOGMASKED(LOG_WARN, "[ti99_dsk] Error: DAM too far away, skipping header\n"); + state = FMIDAM; + break; + } + if (shift_reg == 0xf57e) + { + LOGMASKED(LOG_WARN, "[ti99_dsk] Error: Expected DAM, found next IDAM; skipping header\n"); + state = HEADER; // continue with reading the header + lastpos = bitpos; + break; + } if (shift_reg == 0xf56a || shift_reg == 0xf56f) { - if (pos - lastpos > 400) + // DAM found + LOGMASKED(LOG_DETAIL, "[ti99_dsk] DAM found: %04x\n", bitpos); + state = DATA; + rep = 0; + lastpos = bitpos; + secnumber[seccount++] = cursect; + // Keep track of the found sectors + foundsectors |= (1 << cursect); + } + break; + case A1DAM1: + if (bitpos - lastpos > 1000) + { + LOGMASKED(LOG_WARN, "[ti99_dsk] Error: A1/DAM too far away, skipping header\n"); + state = A1IDAM1; + break; + } + if (shift_reg == 0x4489) + { + state = A1DAM2; + lastpos = bitpos; + } + break; + case A1DAM2: + case A1DAM3: + if (bitpos - lastpos == 16) + { + if (shift_reg == 0x4489) { - // Too far apart - state = FMIDAM; - // Abort this sector, skip to the next - tpos += 321; - a1count = 1; - break; + state = (state==A1DAM2)? A1DAM3 : MFMDAM; } - marks++; - // Add GAP2 and DAM sync - for (int i=0; i < 11; i++) trackdata[tpos++] = 0xff; - tpos += 6; - state = DATA; - databytes = 257; - trackdata[tpos++] = (shift_reg==0xf56a)? 0xf8 : 0xfb; - lastpos = pos; - sector[current_sector] = tpos; + else + { + LOGMASKED(LOG_WARN, "[ti99_dsk] Error: Missing A1, not a data field at %05x\n", bitpos); + state = A1IDAM1; + } + lastpos = bitpos; } break; case MFMDAM: - // Count three subsequent A1 - if (shift_reg == 0x4489) + if (bitpos - lastpos == 16) { - if (pos - lastpos > 560) + if (databyte == 0xfe) { - // Too far apart - state = MFMIDAM; - // Abort this sector, skip to the next - tpos += 320; - a1count = 1; + LOGMASKED(LOG_WARN, "[ti99_dsk] Error: Expected DAM, found next IDAM; skipping header\n"); + state = HEADER; // continue with reading the header + lastpos = bitpos; break; } - if (lastpos > 0) + if (databyte != 0xf8 && databyte != 0xfb) { - if (pos - lastpos == 16) a1count++; - else a1count = 1; + LOGMASKED(LOG_WARN, "[ti99_dsk] Error: DAM (found) = %02x, expected = f8 or fb; skipping header\n", databyte); + state = HEADER; // continue with reading the header } - else a1count = 1; - - lastpos = pos; - } - if (a1count == 3) - { - marks++; - // Add GAP2 and DAM sync - for (int i=0; i < 22; i++) trackdata[tpos++] = gapbytes; - tpos += 12; - trackdata[tpos++] = 0xa1; - trackdata[tpos++] = 0xa1; - trackdata[tpos++] = 0xa1; - state = DATA; - databytes = 258; - sector[current_sector] = tpos+1; + else + { + state = DATA; + secnumber[seccount++] = cursect; + // Keep track of the found sectors + foundsectors |= (1 << cursect); + } + rep = 0; + lastpos = bitpos; } break; case DATA: - if (pos - lastpos == 16) + if (bitpos - lastpos == 16) { - // Ident byte - trackdata[tpos++] = get_data_from_encoding(shift_reg); - if (databytes > 0) databytes--; + if (rep < 256) + { + sectordata[spos++] = databyte; + } else { - state = (encoding==floppy_image::MFM)? MFMIDAM : FMIDAM; - a1count = 0; + state = first; } - lastpos = pos; + rep++; + lastpos = bitpos; } break; } - pos++; + bitpos++; + } + + // We start with 9 sectors (FM) and 16 sectors (MFM) + // If we have 18 sectors in MFM, and we have track 0/head 0, then accept + // it, and switch to expected = 18. + // If we have expected=16 and find sectors past sector 15, + // and track!=0 or head!=0, then stop + // the creation and restart with expected=18. + + // We now add all remaining, unfound sectors to complete the track + // If all sectors were found, this loop is skipped + int j = 0; + int actual_sectors = seccount; + LOGMASKED(LOG_DETAIL, "[ti99_dsk] Sector map: %08x\n", foundsectors); + + // Don't try to fill the first track + if (track==0 && head==0 && seccount<3) + return seccount; + + if (seccount < sectors) + { + LOGMASKED(LOG_INFO, "[ti99_dsk] %d missing sectors in track %d, head %d, adding as empty\n", sectors-seccount, track, head); + } + + for (int i = seccount; i < sectors; i++) + { + // Search the next missing sector number. All sectors that were + // found have a 1 at their bit position in foundsectors. + while ((foundsectors & 1)!=0 && j < sectors) + { + foundsectors >>= 1; + j++; + } + LOGMASKED(LOG_INFO, "[ti99_dsk] add empty sector %d\n", j); + // Add the sector + secnumber[seccount++] = j; + foundsectors >>= 1; + j++; + // Create an empty sector + for (int k=0; k < 256; k++) + sectordata[spos++] = 0xe5; } - return marks; + + LOGMASKED(LOG_INTERLEAVE, "[ti99_dsk] End of track reached, sectors = %d\n", seccount); + LOGMASKED(LOG_INTERLEAVE, "[ti99_dsk] Sector sequence: "); + for (int i=0; i < seccount; i++) + LOGMASKED(LOG_INTERLEAVE, "%d ", secnumber[i]); + LOGMASKED(LOG_INTERLEAVE, "\n"); + return actual_sectors; } + uint8_t ti99_floppy_format::get_data_from_encoding(uint16_t raw) { return (raw & 0x4000 ? 0x80 : 0x00) | @@ -758,36 +930,26 @@ int ti99_sdf_format::identify(io_generic *io, uint32_t form_factor) // Adding support for another sector image format which adds 768 bytes // as a bad sector map - if ((file_size / SECTOR_SIZE) % 10 == 3) - { - if (TRACE) osd_printf_info("ti99_dsk: Stripping map of bad sectors at image end\n"); - file_size -= SECTOR_SIZE*3; - } - - // Formats with 9 or 18 sectors per track (multiples of SSSD) - // 40-track formats: SSSD/9/40 (1), DSSD/9/40 (2), SSDD/18/40 (2), DSDD/18/40 (4) - // 80-track formats: SSSD/9/80 (2), DSSD/9/80 (4), SSDD/18/80 (4), DSDD/18/80,(8) - // High density: DSQD/36/80 (16) (experimental) + if (((file_size - 768) % (SECTOR_SIZE*360)) == 0) + file_size -= 768; // All formats apply for 5.25" and 3.5" form factors - - // Default formats (when the geometry cannot be determined from the VIB) - // (1) -> SSSD - // (2) -> DSSD - // (4) -> DSDD - // (8) -> DSDD80 - // (16) -> DSQD - - if ((file_size % 92160)==0) + switch (file_size) { - int multiple = file_size/92160; - if ((multiple == 1) || (multiple == 2) || (multiple == 4) || (multiple == 8) || (multiple == 16)) vote = 50; + case 92160: // SSSD + case 163840: // SSDD16 + case 184320: // DSSD + case 327680: // DSDD16 + case 368640: // DSDD + case 737280: // DSDD80 + case 1474560: // DSQD + vote = 50; + break; + default: + vote = 0; + break; } - // Formats with 16 sectors per track (rare, SSDD/16/40, DSDD/16/40). - if (file_size == 163840) vote = 50; - if (file_size == 327680) vote = 50; - if (vote > 0) { // Read first sector (Volume Information Block) @@ -797,15 +959,15 @@ int ti99_sdf_format::identify(io_generic *io, uint32_t form_factor) // Check from contents if ((vib.id[0]=='D')&&(vib.id[1]=='S')&&(vib.id[2]=='K')) { - if (TRACE) osd_printf_info("ti99_dsk: Found formatted SDF disk medium\n"); + LOGMASKED(LOG_INFO, "[ti99_dsk] Found formatted SDF disk medium\n"); vote = 100; } else { - if (TRACE) osd_printf_info("ti99_dsk: No valid VIB found; disk may be unformatted\n"); + LOGMASKED(LOG_INFO, "[ti99_dsk] No valid VIB found; disk may be unformatted\n"); } } - else if (TRACE) osd_printf_info("ti99_dsk: Disk image is not a SDF image\n"); + else LOGMASKED(LOG_INFO, "[ti99_dsk] Disk image is not a SDF image\n"); return vote; } @@ -820,8 +982,9 @@ void ti99_sdf_format::determine_sizes(io_generic *io, int& cell_size, int& secto bool have_vib = false; - // See above - if ((file_size / SECTOR_SIZE) % 10 == 3) file_size -= SECTOR_SIZE*3; + // Remove the bad sector map + if (((file_size - 768) % (SECTOR_SIZE*360)) == 0) + file_size -= 768; // Read first sector io_generic_read(io, &vib, 0, sizeof(ti99vib)); @@ -841,7 +1004,7 @@ void ti99_sdf_format::determine_sizes(io_generic *io, int& cell_size, int& secto if (vib.density < 4) cell_size = 2000; else cell_size = 1000; } - if (TRACE) osd_printf_info("ti99_dsk: VIB says that this disk is %s density with %d sectors per track, %d tracks, and %d heads\n", (cell_size==4000)? "single": ((cell_size==2000)? "double" : "high"), sector_count, vib.tracksperside, heads); + LOGMASKED(LOG_INFO, "[ti99_dsk] VIB says that this disk is %s density with %d sectors per track, %d tracks, and %d heads\n", (cell_size==4000)? "single": ((cell_size==2000)? "double" : "high"), sector_count, vib.tracksperside, heads); have_vib = true; tracks = vib.tracksperside; } @@ -881,169 +1044,119 @@ void ti99_sdf_format::determine_sizes(io_generic *io, int& cell_size, int& secto { if (sector_count == 16 && sector_count1 == 18) { - osd_printf_warning("ti99_dsk: Warning: Invalid 16-sector format. Assuming 18 sectors.\n"); + LOGMASKED(LOG_WARN, "[ti99_dsk] Warning: Invalid 16-sector format. Assuming 18 sectors.\n"); sector_count = 18; } else { if (heads == 2 && ((cell_size1 != cell_size) || (sector_count1 != sector_count))) - osd_printf_warning("ti99_dsk: Warning: Disk image size does not correspond with format information in VIB.\n"); + LOGMASKED(LOG_WARN, "[ti99_dsk] Warning: Disk image size does not correspond with format information in VIB.\n"); } } else { - heads = (file_size < 100000)? 1 : 2; // for SSSD + heads = (file_size < 180000)? 1 : 2; // for SSSD, SSDD16 cell_size = cell_size1; sector_count = sector_count1; } } -int ti99_sdf_format::get_track_size(int cell_size, int sector_count) +int ti99_sdf_format::get_track_size(int sector_count) { return sector_count * SECTOR_SIZE; } -/* - Load a SDF image track. Essentially, we want to end up in the same - kind of track image as with the TDF, so the easiest thing is to produce - a TDF image track from the sectors and process them as if it came from TDF. - - acttrack is the actual track when double stepping is used and changes - every two physical tracks. -*/ -void ti99_sdf_format::load_track(io_generic *io, uint8_t *trackdata, int head, int track, int acttrack, int sectorcount, int trackcount, int cellsize) +void ti99_sdf_format::load_track(io_generic *io, uint8_t *sectordata, int *sector, int *secoffset, int head, int track, int sectorcount, int trackcount) { - bool fm = (cellsize==4000); - int tracksize = sectorcount * SECTOR_SIZE; - // Calculate the track offset from the beginning of the image file - int logicaltrack = head * trackcount; - logicaltrack += ((head&1)==0)? track : (trackcount - 1 - track); - - // Interleave and skew - int interleave = fm? 4 : 5; - int skew = fm? 6 : 0; + int logicaltrack = (head==0)? track : (2*trackcount - track - 1); + int position = logicaltrack * get_track_size(sectorcount); - int secsize = fm? 334 : 340; - int position = 0; - int count = 0; + io_generic_read(io, sectordata, position, sectorcount*SECTOR_SIZE); - memset(trackdata, 0x00, 9216); + // Interleave and skew + int interleave = 7; + if (sectorcount == 18) interleave = 11; + else if (sectorcount == 16) interleave = 9; - int secno = 0; - secno = (acttrack * skew) % sectorcount; + int skew = (sectorcount<10)? 6 : 0; - // Gap 1 - int gap1 = fm? 16 : 40; - for (int i=0; i < gap1; i++) trackdata[position+i] = fm? 0x00 : 0x4e; + int secno = (track * skew) % sectorcount; + // Construct the sector sequence for (int i=0; i < sectorcount; i++) { - position = secno * secsize + gap1; - // Sync - count = fm? 6 : 10; - while (count-- > 0) trackdata[position++] = 0x00; - - if (!fm) + sector[i] = secno; + secoffset[i] = secno*SECTOR_SIZE; + secno = (secno + interleave) % sectorcount; + } + if (track==0 && head==0) + { + dumpbytes(sectordata, sectorcount*SECTOR_SIZE); + LOGMASKED(LOG_TRACK, "[ti99_dsk] Sectors = "); + for (int i=0; i < sectorcount; i++) { - trackdata[position++] = 0xa1; - trackdata[position++] = 0xa1; - trackdata[position++] = 0xa1; + LOGMASKED(LOG_TRACK, "%02d ", sector[i]); } - trackdata[position++] = 0xfe; // IDAM / ident - - // Header - trackdata[position++] = acttrack; - trackdata[position++] = head; - trackdata[position++] = i; - trackdata[position++] = 1; - - trackdata[position++] = 0xf7; - trackdata[position++] = 0xf7; - - // Gap 2 - count = fm? 11 : 22; - while (count-- > 0) trackdata[position++] = fm? 0xff : 0x4e; - - // Sync - count = fm? 6 : 12; - while (count-- > 0) trackdata[position++] = 0x00; - - if (!fm) + LOGMASKED(LOG_TRACK, "\n[ti99_dsk] Offset = "); + for (int i=0; i < sectorcount; i++) { - trackdata[position++] = 0xa1; - trackdata[position++] = 0xa1; - trackdata[position++] = 0xa1; + LOGMASKED(LOG_TRACK, "%04x ", secoffset[i]); } - trackdata[position++] = 0xfb; - - io_generic_read(io, trackdata + position, logicaltrack * tracksize + i*SECTOR_SIZE, SECTOR_SIZE); - - position += SECTOR_SIZE; - trackdata[position++] = 0xf7; - trackdata[position++] = 0xf7; - - // Gap 3 - count = fm? 45 : 24; - while (count-- > 0) trackdata[position++] = fm? 0xff : 0x4e; - secno = (secno + interleave) % sectorcount; + LOGMASKED(LOG_TRACK, "\n"); } - - // Gap 4 - count = fm? 231 : 712; - position = sectorcount * secsize + gap1; - while (count-- > 0) trackdata[position++] = fm? 0xff : 0x4e; - - // if (head==0 && track==0) showtrack(trackdata, 9216); } /* For debugging. Outputs the byte array in a xxd-like way. */ -void ti99_floppy_format::showtrack(uint8_t* trackdata, int length) +void ti99_floppy_format::dumpbytes(uint8_t* trackdata, int length) { for (int i=0; i < length; i+=16) { - osd_printf_verbose("%07x: ", i); - for (int j=0; j < 16; j++) - { - osd_printf_verbose("%02x", trackdata[i+j]); - if ((j&1)==1) osd_printf_verbose(" "); - } - osd_printf_verbose(" "); - for (int j=0; j < 16; j++) - { - if (trackdata[i+j] >= 32 && trackdata[i+j]<128) osd_printf_verbose("%c", trackdata[i+j]); - else osd_printf_verbose("."); - } - osd_printf_verbose("\n"); + LOGMASKED(LOG_TRACK, "[ti99_dsk] %s\n", dumpline(trackdata+i, i).c_str()); } } +std::string ti99_floppy_format::dumpline(uint8_t* line, int address) const +{ + std::ostringstream stream; + stream << std::hex << std::setfill('0') << std::setw(6) << unsigned(address); + + for (int i=0; i < 16; i++) + { + stream << " " << std::setw(2) << (int)line[i]; + } + stream << " " ; + for (int i=0; i < 16; i++) + { + char ch = (line[i] >= 32 && line[i] < 127)? (char)line[i] : '.'; + stream << std::setw(1) << ch; + } + return stream.str(); +} + /* Write the data to the disk. We have a list of sector positions, so we - just need to go through that list and save each sector in the track data. + just need to go through that list and save each sector in the sector data. */ -void ti99_sdf_format::write_track(io_generic *io, uint8_t *trackdata, int *sector, int track, int head, int maxsect, int maxtrack, int numbytes) +void ti99_sdf_format::write_track(io_generic *io, uint8_t *sectordata, int *sector, int track, int head, int sector_count, int track_count) { uint8_t* buf; - // For creating an empty space where the sector did not exist - uint8_t empty[256]; - memset(empty, 0, 256); - int logicaltrack = head * maxtrack; - logicaltrack += ((head&1)==0)? track : (maxtrack - 1 - track); - int trackoffset = logicaltrack * maxsect * SECTOR_SIZE; + int logicaltrack = head * track_count; + logicaltrack += ((head&1)==0)? track : (track_count - 1 - track); + int trackoffset = logicaltrack * sector_count * SECTOR_SIZE; -// if (head==0 && track==0) showtrack(trackdata, 9216); - - for (int i=0; i < maxsect; i++) { - if (sector[i]==-1) buf = empty; - else buf = trackdata + sector[i]; - io_generic_write(io, buf, trackoffset + i * SECTOR_SIZE, SECTOR_SIZE); + for (int i=0; i < sector_count; i++) + { + buf = sectordata + i * SECTOR_SIZE; + LOGMASKED(LOG_DETAIL, "[ti99_dsk] Writing sector %d (offset %06x)\n", sector[i], sector[i] * SECTOR_SIZE); + io_generic_write(io, buf, trackoffset + sector[i] * SECTOR_SIZE, SECTOR_SIZE); } } + const floppy_format_type FLOPPY_TI99_SDF_FORMAT = &floppy_image_format_creator<ti99_sdf_format>; /* @@ -1078,6 +1191,9 @@ const floppy_format_type FLOPPY_TI99_SDF_FORMAT = &floppy_image_format_creator<t modified in an 80-track drive. This will automatically cause the creation of an 80-track image. + DSSD80: 520480 bytes + DSDD80: 1099520 bytes + Tracks are stored from outside to inside of head 0, then outside to inside of head 1: (Head,Track): (0,0) (0,1) (0,2) ... (0,38) (0,39) (1,0) (1,1) ... (1,39) @@ -1102,70 +1218,290 @@ const char *ti99_tdf_format::extensions() const */ int ti99_tdf_format::identify(io_generic *io, uint32_t form_factor) { - uint64_t file_size = io_generic_size(io); int vote = 0; - uint8_t trackdata[2000]; + uint8_t fulltrack[6872]; + + int cell_size = 0; + int sector_count = 0; + int heads = 0; + int tracks = 0; + int sectorinterval = 0; + int headsectoff = 0; + int pos = 0; + determine_sizes(io, cell_size, sector_count, heads, tracks); // Do we have a plausible image size? From the size alone we only give a 50 percent vote. - if ((file_size == 260240) || (file_size == 549760) || (file_size == 1099520)) + if (sector_count != 0) vote = 50; if (vote > 0) { - if (TRACE) osd_printf_error("ti99_dsk: Image looks like a TDF image\n"); + LOGMASKED(LOG_INFO, "[ti99_dsk] Image file length matches TDF\n"); + + // Fetch track 0 + io_generic_read(io, fulltrack, 0, get_track_size(sector_count)); + + if (sector_count == 9) + { + headsectoff = 24; + sectorinterval = 334; + } + else + { + headsectoff = 44; + sectorinterval = (sector_count==16)? 368 : 340; + } + + while (fulltrack[pos] != 0xfe && pos < 70) pos++; - // Get some bytes from track 0 - io_generic_read(io, trackdata, 0, 2000); + bool failed = false; + // Find the IDAMs and DAMs on track 0 + for (int i=0; i < sector_count; i++) + { + int idam = fulltrack[pos+i*sectorinterval]; + int dam = fulltrack[pos+headsectoff+i*sectorinterval]; + if ((idam != 0xfe) || ((dam != 0xfb) && (dam != 0xf8))) + { + LOGMASKED(LOG_WARN, "[ti99_dsk] Failed at %04x or %04x\n", pos+i*sectorinterval, pos+headsectoff+i*sectorinterval); + failed = true; + break; + } + } - if (check_for_address_marks(trackdata, floppy_image::FM)==true || check_for_address_marks(trackdata, floppy_image::MFM)==true) vote=100; + if (failed) + { + LOGMASKED(LOG_WARN, "[ti99_dsk] Image does not comply with TDF; may be broken or unformatted\n"); + } else { - if (TRACE) osd_printf_error("ti99_dsk: Image does not comply with TDF; may be broken or unformatted\n"); + LOGMASKED(LOG_INFO, "[ti99_dsk] Image format complies with TDF\n"); + vote = 100; } } - else if (TRACE) osd_printf_error("ti99_dsk: Disk image is not a TDF image\n"); + else LOGMASKED(LOG_INFO, "[ti99_dsk] Disk image is not a TDF image\n"); return vote; } /* - Find the proper format for a given image file. We determine the cell size, - but we do not care about the sector size (only needed by the SDF converter). + Find the proper format for a given image file. */ void ti99_tdf_format::determine_sizes(io_generic *io, int& cell_size, int& sector_count, int& heads, int& tracks) { uint64_t file_size = io_generic_size(io); heads = 2; // TDF only supports two-sided recordings - - if (file_size % get_track_size(2000, 0)==0) cell_size = 2000; - else if (file_size % get_track_size(4000, 0)==0) cell_size = 4000; - - // We could check for the content, but if we find that the content is - // FM and the size implies MFM, the calculated track count will be wrong. + tracks = 40; + LOGMASKED(LOG_INFO, "[ti99_dsk] Image size = %ld\n", file_size); + switch (file_size) + { + case 260240: // used in PC99 + cell_size = 4000; + sector_count = 9; + break; + case 491520: // 320K HX5102 + cell_size = 2000; + sector_count = 16; + break; + case 549760: // used in PC99 + cell_size = 2000; + sector_count = 18; + break; + case 520480: + cell_size = 4000; + sector_count = 9; + break; + case 983040: + cell_size = 2000; + sector_count = 16; + tracks = 80; + break; + case 1099520: + cell_size = 2000; + sector_count = 18; + tracks = 80; + break; + case 2007040: + cell_size = 1000; + sector_count = 18; + tracks = 80; + break; + default: + cell_size = 0; + sector_count = 0; + break; + } } /* - For TDF this just amounts to loading the track from the image file. - acttrack is not used here, since the file contains the track data. + Load a track. Although TDF provides us with a near-precise image of the + track, we just copy the sectors, store the sequence, and recreate the + track from scratch. TDF is not as flexible as it suggests, it does not + allow different gap lengths and so on. */ -void ti99_tdf_format::load_track(io_generic *io, uint8_t *trackdata, int head, int track, int acttrack, int sectorcount, int trackcount, int cellsize) +void ti99_tdf_format::load_track(io_generic *io, uint8_t *sectordata, int *sector, int *secoffset, int head, int track, int sectorcount, int trackcount) { - int offset = ((trackcount * head) + track) * get_track_size(cellsize, 0); - io_generic_read(io, trackdata, offset, get_track_size(cellsize, 0)); + uint8_t fulltrack[6872]; // space for a full TDF track + + // Read beginning of track 0. We need this to get the first gap, according + // to the format + io_generic_read(io, fulltrack, 0, 100); + + int offset = 0; + int tracksize = get_track_size(sectorcount); + int sectorinterval = 0; + int headsectoff = 0; + int pos = 0; + int img_sect = 0; + + if (sectorcount == 9) + { + headsectoff = 25; + sectorinterval = 334; + } + else + { + headsectoff = 45; + sectorinterval = (sectorcount==16)? 368 : 340; + } + + // Initialize the sequence lists + for (int i=0; i < sectorcount; i++) + { + sector[i] = -1; + secoffset[0] = 0; + } + + // We already verified that this is TDF, but the disk may be unformatted + while (fulltrack[pos] != 0xfe && pos < tracksize) pos++; + + if (pos==tracksize) + { + // No sectors found + LOGMASKED(LOG_WARN, "[ti99_dsk] No sectors on track %d, head %d\n", track, head); + return; + } + + offset = pos; + + int base = (head * trackcount + track) * tracksize; + int position = 0; + io_generic_read(io, fulltrack, base, tracksize); + + for (int i=0; i < sectorcount; i++) + { + position = i*sectorinterval + offset; + if (position >= 6872) + { + LOGMASKED(LOG_WARN, "[ti99_dsk] No more sectors on track %d, head %d\n", track, head); + break; // no more sectors + } + if (fulltrack[position] != 0xfe) + { + LOGMASKED(LOG_WARN, "[ti99_dsk] Invalid header (no FE) on track %d, head %d\n", track, head); + continue; // no valid header + } + img_sect = fulltrack[position+3]; + if (img_sect >= 18) + { + LOGMASKED(LOG_WARN, "[ti99_dsk] Invalid sector number (%d) on track %d, head %d\n", img_sect, track, head); + continue; // invalid sector number + } + sector[i] = img_sect; + secoffset[i] = i*SECTOR_SIZE; + position += headsectoff; + memcpy(sectordata + i*SECTOR_SIZE, fulltrack+position, SECTOR_SIZE); + } + + // dumpbytes(sectordata, sectorcount*SECTOR_SIZE); + LOGMASKED(LOG_TRACK, "[ti99_dsk] Sectors = "); + for (int i=0; i < sectorcount; i++) + { + LOGMASKED(LOG_TRACK, "%02d ", sector[i]); + } + LOGMASKED(LOG_TRACK, "\n[ti99_dsk] Offset = "); + for (int i=0; i < sectorcount; i++) + { + LOGMASKED(LOG_TRACK, "%04x ", secoffset[i]); + } + LOGMASKED(LOG_TRACK, "\n"); } /* - Also here, we just need to write the complete track on the image file. + Rebuild TDF track image + The TDF format is a bit more restrictive than it could be. The gaps need + to be of the exact size and contain the expected bytes. So we only + need the sector contents and the sector sequence, which are passed via + sectordata, sector, and sector_count. */ -void ti99_tdf_format::write_track(io_generic *io, uint8_t *trackdata, int *sector, int track, int head, int maxsect, int maxtrack, int numbytes) +void ti99_tdf_format::write_track(io_generic *io, uint8_t *sectordata, int *sector, int track, int head, int sector_count, int track_count) { - int offset = ((maxtrack * head) + track) * numbytes; - io_generic_write(io, trackdata, offset, numbytes); + uint8_t trackdata[14000]; + int offset = ((track_count * head) + track) * get_track_size(sector_count); + int a1 = 0; + + enum + { + WGAP1 = 0, WGAP2, WGAP3, WGAP4, WGAP1BYTE, WGAPBYTE, WSYNC1, WSYNC2 + }; + + const int fm9param[8] = { 16, 11, 45, 231, 0x00, 0xff, 6, 6 }; + const int mfm16param[8] = { 50, 22, 50, 206, 0x4e, 0x4e, 12, 12 }; + const int mfm18param[8] = { 40, 22, 24, 712, 0x4e, 0x4e, 10, 12 }; + const int mfm36param[8] = { 40, 22, 24, 264, 0x4e, 0x4e, 10, 12 }; + + const int *param = fm9param; + + if (sector_count == 16) + param = mfm16param; + else if (sector_count == 18) + param = mfm18param; + else if (sector_count == 36) + param = mfm36param; + + LOGMASKED(LOG_TRACK, "[ti99_dsk] Writing track %d, head %d\n", track, head); + + if (sector_count >= 16) a1 = 3; + + // Create track + int pos = 0; + for (int i=0; i < param[WGAP1]; i++) trackdata[pos++] = param[WGAP1BYTE]; + for (int j=0; j < sector_count; j++) + { + for (int i=0; i < param[WSYNC1]; i++) trackdata[pos++] = 0x00; + for (int i=0; i < a1; i++) trackdata[pos++] = 0xa1; + trackdata[pos++] = 0xfe; + trackdata[pos++] = track; + trackdata[pos++] = head; + trackdata[pos++] = sector[j]; + trackdata[pos++] = 0x01; + trackdata[pos++] = 0xf7; // PC99 does not accept real CRC values + trackdata[pos++] = 0xf7; + for (int i=0; i < param[WGAP2]; i++) trackdata[pos++] = param[WGAPBYTE]; + for (int i=0; i < param[WSYNC2]; i++) trackdata[pos++] = 0x00; + for (int i=0; i < a1; i++) trackdata[pos++] = 0xa1; + trackdata[pos++] = 0xfb; + memcpy(trackdata + pos, sectordata + j * SECTOR_SIZE, SECTOR_SIZE); + pos +=256; + trackdata[pos++] = 0xf7; + trackdata[pos++] = 0xf7; + for (int i=0; i < param[WGAP3]; i++) trackdata[pos++] = param[WGAPBYTE]; + } + for (int i=0; i < param[WGAP4]; i++) trackdata[pos++] = param[WGAPBYTE]; + io_generic_write(io, trackdata, offset, get_track_size(sector_count)); } -int ti99_tdf_format::get_track_size(int cell_size, int sector_count) +int ti99_tdf_format::get_track_size(int sector_count) { - if (cell_size == 4000) return 3253; - if (cell_size == 2000) return 6872; + switch (sector_count) + { + case 9: + return 3253; + case 16: + return 6144; + case 18: + return 6872; + case 36: + return 12544; + } return 0; } diff --git a/src/lib/formats/ti99_dsk.h b/src/lib/formats/ti99_dsk.h index d596f4bb17c..d67f38acaae 100644 --- a/src/lib/formats/ti99_dsk.h +++ b/src/lib/formats/ti99_dsk.h @@ -14,7 +14,7 @@ #define TI99_DSK_H #include "flopimg.h" -#include "wd177x_dsk.h" +#include <sstream> class ti99_floppy_format : public floppy_image_format_t { @@ -26,23 +26,23 @@ public: protected: int decode_bitstream(const uint8_t *bitstream, uint8_t *trackdata, int *sector, int cell_count, int encoding, uint8_t gapbytes, int track_size); uint8_t get_data_from_encoding(uint16_t raw); + int get_sectors(const uint8_t *bitstream, int cell_count, int encoding, int track, int head, int sectors, uint8_t *sectordata, int *secnumber); virtual int min_heads() =0; virtual void determine_sizes(io_generic *io, int& cell_size, int& sector_count, int& heads, int& tracks) =0; - virtual int get_track_size(int cell_size, int sector_count) =0; - virtual void load_track(io_generic *io, uint8_t *trackdata, int head, int track, int acttrack, int sectorcount, int trackcount, int cellsize) =0; - virtual void write_track(io_generic *io, uint8_t *trackdata, int *sector, int track, int head, int maxsect, int maxtrack, int numbytes) =0; + virtual int get_track_size(int sector_count) =0; + virtual void load_track(io_generic *io, uint8_t *sectordata, int *sector, int *secoffset, int head, int track, int sectorcount, int trackcount) =0; + virtual void write_track(io_generic *io, uint8_t *sectordata, int *sector, int track, int head, int sector_count, int track_count) =0; int get_encoding(int cell_size); - void generate_track_fm(int track, int head, int cell_size, uint8_t* trackdata, floppy_image *image); - void generate_track_mfm(int track, int head, int cell_size, uint8_t* trackdata, floppy_image *image); - - bool check_for_address_marks(uint8_t* trackdata, int encoding); + void generate_fm_track_from_sectors(floppy_image *image, uint8_t *sectordata, int sector_count, int *sector, int *secoffset, int track, int trackid, int head); + void generate_mfm_track_from_sectors(floppy_image *image, uint8_t *sectordata, int sector_count, int *sector, int *secoffset, int track, int trackid, int head); // Debugging - void showtrack(uint8_t* trackdata, int length); + void dumpbytes(uint8_t* trackdata, int length); + std::string dumpline(uint8_t* line, int address) const; }; /* @@ -58,9 +58,9 @@ public: private: void determine_sizes(io_generic *io, int& cell_size, int& sector_count, int& heads, int& tracks) override; - int get_track_size(int cell_size, int sector_count) override; - void write_track(io_generic *io, uint8_t *trackdata, int *sector, int track, int head, int maxsect, int maxtrack, int numbytes) override; - void load_track(io_generic *io, uint8_t *trackdata, int head, int track, int acttrack, int sectorcount, int trackcount, int cellsize) override; + int get_track_size(int sector_count) override; + void write_track(io_generic *io, uint8_t *sectordata, int *sector, int track, int head, int sector_count, int track_count) override; + void load_track(io_generic *io, uint8_t *sectordata, int *sector, int *secoffset, int head, int track, int sector_count, int track_count) override; // This format supports single-sided images int min_heads() override { return 1; } @@ -97,9 +97,9 @@ public: private: void determine_sizes(io_generic *io, int& cell_size, int& sector_count, int& heads, int& tracks) override; - void load_track(io_generic *io, uint8_t *trackdata, int head, int track, int acttrack, int sectorcount, int trackcount, int cellsize) override; - void write_track(io_generic *io, uint8_t *trackdata, int *sector, int track, int head, int maxsect, int maxtrack, int numbytes) override; - int get_track_size(int cell_size, int sector_count) override; + void load_track(io_generic *io, uint8_t *sectordata, int *sector, int *secoffset, int head, int track, int sectorcount, int trackcount) override; + void write_track(io_generic *io, uint8_t *sectordata, int *sector, int track, int head, int sector_count, int track_count) override; + int get_track_size(int sector_count) override; // This format only supports double-sided images int min_heads() override { return 2; } |