From 5ee7f9e0fa4a5eed79ccd1544b362114276118b7 Mon Sep 17 00:00:00 2001 From: Alan Steremberg Date: Wed, 30 Jul 2025 22:46:23 -0700 Subject: [PATCH] Apple-II: added DSK read/write support (#1011) --- support.h | 3 + support/a2/dsk2nib_lib.cpp | 467 +++++++++++++++++++++++++++++++++++++ support/a2/dsk2nib_lib.h | 19 ++ user_io.cpp | 32 ++- 4 files changed, 510 insertions(+), 11 deletions(-) create mode 100644 support/a2/dsk2nib_lib.cpp create mode 100644 support/a2/dsk2nib_lib.h diff --git a/support.h b/support.h index c0685ba..abfc2c6 100644 --- a/support.h +++ b/support.h @@ -7,6 +7,9 @@ // SharpMz support #include "support/sharpmz/sharpmz.h" +// Apple 2 support +#include "support/a2/dsk2nib_lib.h" + // Archie support #include "support/archie/archie.h" diff --git a/support/a2/dsk2nib_lib.cpp b/support/a2/dsk2nib_lib.cpp new file mode 100644 index 0000000..9f8cdff --- /dev/null +++ b/support/a2/dsk2nib_lib.cpp @@ -0,0 +1,467 @@ +#include +#include +#include +#include + +#include "../../file_io.h" +#include "../../user_io.h" +#include "../../hardware.h" + + +#include "dsk2nib_lib.h" + +// Constants from original dsk2nib.c +#define TRACKS_PER_DISK 35 +#define SECTORS_PER_TRACK 16 +#define BYTES_PER_SECTOR 256 +#define BYTES_PER_TRACK 4096 +#define PRIMARY_BUF_LEN 256 +#define SECONDARY_BUF_LEN 86 +#define DATA_LEN (PRIMARY_BUF_LEN+SECONDARY_BUF_LEN) +#define PROLOG_LEN 3 +#define EPILOG_LEN 3 +#define GAP1_LEN 48 +#define GAP2_LEN 5 +#define BYTES_PER_NIB_SECTOR 416 +#define BYTES_PER_NIB_TRACK 6656 +#define DEFAULT_VOLUME 254 +#define GAP_BYTE 0xff + +// Structures from original +typedef struct { + uchar prolog[ PROLOG_LEN ]; + uchar volume[ 2 ]; + uchar track[ 2 ]; + uchar sector[ 2 ]; + uchar checksum[ 2 ]; + uchar epilog[ EPILOG_LEN ]; +} addr_t; + +typedef struct { + uchar prolog[ PROLOG_LEN ]; + uchar data[ DATA_LEN ]; + uchar data_checksum; + uchar epilog[ EPILOG_LEN ]; +} data_t; + +typedef struct { + uchar gap1[ GAP1_LEN ]; + addr_t addr; + uchar gap2[ GAP2_LEN ]; + data_t data; +} nib_sector_t; + +// Static data from original +static uchar addr_prolog[] = { 0xd5, 0xaa, 0x96 }; +static uchar addr_epilog[] = { 0xde, 0xaa, 0xeb }; +static uchar data_prolog[] = { 0xd5, 0xaa, 0xad }; +static uchar data_epilog[] = { 0xde, 0xaa, 0xeb }; +static int soft_interleave[ SECTORS_PER_TRACK ] = + { 0, 7, 0xE, 6, 0xD, 5, 0xC, 4, 0xB, 3, 0xA, 2, 9, 1, 8, 0xF }; +static int phys_interleave[ SECTORS_PER_TRACK ] = + { 0, 0xD, 0xB, 9, 7, 5, 3, 1, 0xE, 0xC, 0xA, 8, 6, 4, 2, 0xF }; + +// Translation table for 6+2 encoding +static uchar table[ 0x40 ] = { + 0x96, 0x97, 0x9a, 0x9b, 0x9d, 0x9e, 0x9f, 0xa6, + 0xa7, 0xab, 0xac, 0xad, 0xae, 0xaf, 0xb2, 0xb3, + 0xb4, 0xb5, 0xb6, 0xb7, 0xb9, 0xba, 0xbb, 0xbc, + 0xbd, 0xbe, 0xbf, 0xcb, 0xcd, 0xce, 0xcf, 0xd3, + 0xd6, 0xd7, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde, + 0xdf, 0xe5, 0xe6, 0xe7, 0xe9, 0xea, 0xeb, 0xec, + 0xed, 0xee, 0xef, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, + 0xf7, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff +}; + +// Helper functions from original +static uchar translate(uchar byte) { + return table[byte & 0x3f]; +} + +static void odd_even_encode(uchar a[], int i) { + a[0] = (i >> 1) & 0x55; + a[0] |= 0xaa; + a[1] = i & 0x55; + a[1] |= 0xaa; +} + +static void nibbilize(uchar *src, data_t *data_field) { + int i, index, section; + uchar pair; + uchar primary_buf[PRIMARY_BUF_LEN]; + uchar secondary_buf[SECONDARY_BUF_LEN]; + uchar *dest = data_field->data; + + // Clear buffers + memset(primary_buf, 0, PRIMARY_BUF_LEN); + memset(secondary_buf, 0, SECONDARY_BUF_LEN); + + // Nibbilize data into primary and secondary buffers + for (i = 0; i < PRIMARY_BUF_LEN; i++) { + primary_buf[i] = src[i] >> 2; + + index = i % SECONDARY_BUF_LEN; + section = i / SECONDARY_BUF_LEN; + pair = ((src[i]&2)>>1) | ((src[i]&1)<<1); // swap the low bits + secondary_buf[index] |= pair << (section*2); + } + + // XOR pairs of nibbilized bytes in correct order + index = 0; + dest[index++] = translate(secondary_buf[0]); + + for (i = 1; i < SECONDARY_BUF_LEN; i++) + dest[index++] = translate(secondary_buf[i] ^ secondary_buf[i-1]); + + dest[index++] = translate(primary_buf[0] ^ secondary_buf[SECONDARY_BUF_LEN-1]); + + for (i = 1; i < PRIMARY_BUF_LEN; i++) + dest[index++] = translate(primary_buf[i] ^ primary_buf[i-1]); + + data_field->data_checksum = translate(primary_buf[PRIMARY_BUF_LEN-1]); +} + + +// Convert NIB physical sector to logical sector using interleave tables +static int phys_to_logical_sector(int phys_sector) { + // Find which logical sector maps to this physical sector + for (int i = 0; i < SECTORS_PER_TRACK; i++) { + if (phys_interleave[i] == phys_sector) { + return i; + } + } + return 0; // fallback +} + +void a2_readDsk2Nib(fileTYPE*fd, uint64_t offset, uchar *byte) { + int nib_track = offset / BYTES_PER_NIB_TRACK; + uint64_t track_offset = offset % BYTES_PER_NIB_TRACK; + int volume = DEFAULT_VOLUME; + + // Bounds check + if (nib_track >= TRACKS_PER_DISK) { + memset(byte, 0, 512); + return; + } + + // Build entire NIB track in memory + uchar nib_track_data[BYTES_PER_NIB_TRACK]; + + // Process all 16 sectors in this track + for (int phys_sector = 0; phys_sector < SECTORS_PER_TRACK; phys_sector++) { + // Convert physical sector to logical sector + int logical_sector = phys_to_logical_sector(phys_sector); + + // Get corresponding DSK soft sector + int dsk_soft_sector = soft_interleave[logical_sector]; + + // Read DSK sector data + uchar dsk_sector[BYTES_PER_SECTOR]; + off_t dsk_offset = (off_t)nib_track * BYTES_PER_TRACK + (off_t)dsk_soft_sector * BYTES_PER_SECTOR; + + if (FileSeek(fd, dsk_offset, SEEK_SET)) + { + if (FileReadAdv(fd, dsk_sector, BYTES_PER_SECTOR)) + { + // good + } + else { + memset(dsk_sector, 0, BYTES_PER_SECTOR); + } + } else { + memset(dsk_sector, 0, BYTES_PER_SECTOR); + } + + + //if (lseek(fd, dsk_offset, SEEK_SET) == -1) { + //} else if (read(fd, dsk_sector, BYTES_PER_SECTOR) != BYTES_PER_SECTOR) { + // memset(dsk_sector, 0, BYTES_PER_SECTOR); + //} + + // Build NIB sector structure + nib_sector_t nib_sector; + + // Initialize gaps + memset(nib_sector.gap1, GAP_BYTE, GAP1_LEN); + memset(nib_sector.gap2, GAP_BYTE, GAP2_LEN); + + // Set address field + memcpy(nib_sector.addr.prolog, addr_prolog, 3); + memcpy(nib_sector.addr.epilog, addr_epilog, 3); + odd_even_encode(nib_sector.addr.volume, volume); + odd_even_encode(nib_sector.addr.track, nib_track); + odd_even_encode(nib_sector.addr.sector, logical_sector); + int csum = volume ^ nib_track ^ logical_sector; + odd_even_encode(nib_sector.addr.checksum, csum); + + // Set data field + memcpy(nib_sector.data.prolog, data_prolog, 3); + memcpy(nib_sector.data.epilog, data_epilog, 3); + nibbilize(dsk_sector, &nib_sector.data); + + // Copy this sector to the track buffer + memcpy(nib_track_data + phys_sector * BYTES_PER_NIB_SECTOR, &nib_sector, sizeof(nib_sector)); + } + + // Copy requested 512 bytes from the track + int bytes_to_copy = 512; + int available_bytes = BYTES_PER_NIB_TRACK - track_offset; + + if (available_bytes <= 0) { + memset(byte, 0, 512); + return; + } + + if (bytes_to_copy > available_bytes) { + bytes_to_copy = available_bytes; + } + + memcpy(byte, nib_track_data + track_offset, bytes_to_copy); + + // Fill remaining bytes with zeros if needed + if (bytes_to_copy < 512) { + memset(byte + bytes_to_copy, 0, 512 - bytes_to_copy); + } +} + +// Helper functions for NIB to DSK conversion +static uchar odd_even_decode(uchar byte1, uchar byte2) { + uchar byte; + byte = (byte1 << 1) & 0xaa; + byte |= byte2 & 0x55; + return byte; +} + +static uchar untranslate(uchar x) { + uchar *ptr; + int index; + if ((ptr = (uchar*)memchr(table, x, 0x40)) == NULL) { + return 0; // Invalid byte, return 0 instead of fatal error + } + index = ptr - table; + return index; +} + +// Parse a NIB sector from byte stream and extract DSK data +static int parse_nib_sector(uchar *nib_data, int data_len, uchar *dsk_sector, int *track, int *sector) { + int pos = 0; + int state = 0; + uchar primary_buf[PRIMARY_BUF_LEN]; + uchar secondary_buf[SECONDARY_BUF_LEN]; + uchar checksum; + int i; + + // State machine to parse NIB sector + while (pos < data_len) { + uchar byte = nib_data[pos++]; + + switch (state) { + case 0: // Looking for address prolog D5 + if (byte == 0xd5) state = 1; + break; + + case 1: // Looking for address prolog AA + if (byte == 0xaa) state = 2; + else state = 0; + break; + + case 2: // Looking for address prolog 96 + if (byte == 0x96) state = 3; + else state = 0; + break; + + case 3: // Read volume (first byte) + if (pos >= data_len) return 0; + odd_even_decode(byte, nib_data[pos++]); // Read volume but don't store + state = 4; + break; + + case 4: // Read track (first byte) + if (pos >= data_len) return 0; + *track = odd_even_decode(byte, nib_data[pos++]); + state = 5; + break; + + case 5: // Read sector (first byte) + if (pos >= data_len) return 0; + *sector = odd_even_decode(byte, nib_data[pos++]); + state = 6; + break; + + case 6: // Read checksum (first byte) + if (pos >= data_len) return 0; + pos++; // Skip checksum, we don't validate it + state = 7; + break; + + case 7: // Skip address epilog DE + if (byte == 0xde) state = 8; + break; + + case 8: // Skip address epilog AA + if (byte == 0xaa) state = 9; + else state = 7; + break; + + case 9: // Skip address epilog EB, look for data prolog D5 + if (byte == 0xd5) state = 10; + break; + + case 10: // Looking for data prolog AA + if (byte == 0xaa) state = 11; + else state = 9; + break; + + case 11: // Looking for data prolog AD + if (byte == 0xad) state = 12; + else state = 9; + break; + + case 12: // Process data field + // Read and decode the 342 data bytes plus checksum + checksum = untranslate(byte); + secondary_buf[0] = checksum; + + // Read secondary buffer (85 more bytes) + for (i = 1; i < SECONDARY_BUF_LEN; i++) { + if (pos >= data_len) return 0; + checksum ^= untranslate(nib_data[pos++]); + secondary_buf[i] = checksum; + } + + // Read primary buffer (256 bytes) + for (i = 0; i < PRIMARY_BUF_LEN; i++) { + if (pos >= data_len) return 0; + checksum ^= untranslate(nib_data[pos++]); + primary_buf[i] = checksum; + } + + // Read and validate checksum + if (pos >= data_len) return 0; + checksum ^= untranslate(nib_data[pos++]); + // Ignore checksum validation for now + + // Denibbilize - reconstruct the 256-byte sector + for (i = 0; i < PRIMARY_BUF_LEN; i++) { + int index = i % SECONDARY_BUF_LEN; + uchar bit0, bit1; + + switch (i / SECONDARY_BUF_LEN) { + case 0: + bit0 = (secondary_buf[index] & 2) > 0; + bit1 = (secondary_buf[index] & 1) > 0; + break; + case 1: + bit0 = (secondary_buf[index] & 8) > 0; + bit1 = (secondary_buf[index] & 4) > 0; + break; + case 2: + bit0 = (secondary_buf[index] & 0x20) > 0; + bit1 = (secondary_buf[index] & 0x10) > 0; + break; + default: + bit0 = bit1 = 0; + break; + } + dsk_sector[i] = (primary_buf[i] << 2) | (bit1 << 1) | bit0; + } + return 1; // Success + + default: + state = 0; + break; + } + } + return 0; // Failed to parse +} + +void a2_writeDSK(fileTYPE* idx, uint64_t lba, int ack) { + //printf("a2_writeDSK(lba:%lld ack:%d\n",lba,ack); + // Fetch sector data from FPGA ... + uchar chunk[512]; + EnableIO(); + spi_w(UIO_SECTOR_WR | ack); + spi_block_read(chunk, user_io_get_width(), 512); + DisableIO(); + + a2_writeNib2Dsk(idx, lba*512, chunk); +} + +void a2_readDSK(fileTYPE* idx, uint64_t lba, int ack) { + //printf("a2_readDSK(lba:%lld ack:%d\n",lba,ack); + uchar chunk[512]; + + a2_readDsk2Nib(idx, lba*512, chunk); + + //printf("%x %x %x %x %x\n",chunk[0],chunk[1],chunk[2],chunk[3],chunk[4]); + + EnableIO(); + spi_w(UIO_SECTOR_RD | ack); + spi_block_write(chunk, user_io_get_width(), 512); + DisableIO(); +} + + +void a2_writeNib2Dsk(fileTYPE*fd, uint64_t offset, uchar *byte) { + int nib_track = offset / BYTES_PER_NIB_TRACK; + uint64_t track_offset = offset % BYTES_PER_NIB_TRACK; + + // Bounds check + if (nib_track >= TRACKS_PER_DISK) { + return; + } + + // We need to accumulate a full track's worth of NIB data to properly decode + // This is a simplified approach - we'll try to parse sectors from the given 512 bytes + static uchar track_buffer[BYTES_PER_NIB_TRACK]; + static int current_track = -1; + static int bytes_accumulated = 0; + + // If this is a new track, reset the buffer + if (current_track != nib_track) { + current_track = nib_track; + bytes_accumulated = 0; + memset(track_buffer, 0, BYTES_PER_NIB_TRACK); + } + + // Copy the 512 bytes into our track buffer at the appropriate offset + int copy_len = 512; + if (track_offset + copy_len > BYTES_PER_NIB_TRACK) { + copy_len = BYTES_PER_NIB_TRACK - track_offset; + } + + if (copy_len > 0) { + memcpy(track_buffer + track_offset, byte, copy_len); + bytes_accumulated += copy_len; + } + + // Try to parse and write any complete sectors we can find + // Look for sectors in the accumulated data + int pos = 0; + while (pos < bytes_accumulated - 400) { // Need at least 400 bytes for a sector + uchar dsk_sector[BYTES_PER_SECTOR]; + int track_num, sector_num; + + if (parse_nib_sector(track_buffer + pos, bytes_accumulated - pos, dsk_sector, &track_num, §or_num)) { + // Successfully parsed a sector + if (track_num == nib_track && sector_num < SECTORS_PER_TRACK) { + // Map logical sector to soft sector using interleave + int soft_sector = soft_interleave[sector_num]; + + // Calculate DSK file offset + off_t dsk_offset = (off_t)track_num * BYTES_PER_TRACK + (off_t)soft_sector * BYTES_PER_SECTOR; + + // Write sector to DSK file + //if (lseek(fd, dsk_offset, SEEK_SET) != -1) { + if (FileSeek(fd,dsk_offset, SEEK_SET)) + // if (lseek(fd, dsk_offset, SEEK_SET) != -1) { + FileWriteAdv(fd, dsk_sector,BYTES_PER_SECTOR); + //write(fd, dsk_sector, BYTES_PER_SECTOR); + //} + } + pos += BYTES_PER_NIB_SECTOR; // Move to next sector + } else { + pos++; // Try next byte position + } + } +} diff --git a/support/a2/dsk2nib_lib.h b/support/a2/dsk2nib_lib.h new file mode 100644 index 0000000..214e001 --- /dev/null +++ b/support/a2/dsk2nib_lib.h @@ -0,0 +1,19 @@ +#ifndef DSK2NIB_LIB_H +#define DSK2NIB_LIB_H + +#include + +typedef unsigned char uchar; + +// Library function for on-demand DSK to NIB conversion +void a2_readDsk2Nib(fileTYPE*fd, uint64_t offset, uchar *byte); + +// Library function for writing NIB data back to DSK format +void a2_writeNib2Dsk(fileTYPE*fd, uint64_t offset, uchar *byte); + + +void a2_writeDSK(fileTYPE* idx, uint64_t lba, int ack); +void a2_readDSK(fileTYPE* idx, uint64_t lba, int ack); + + +#endif diff --git a/user_io.cpp b/user_io.cpp index 01cc54e..307991c 100644 --- a/user_io.cpp +++ b/user_io.cpp @@ -43,6 +43,11 @@ static char core_path[1024] = {}; static char rbf_path[1024] = {}; static fileTYPE sd_image[16] = {}; + +#define SD_TYPE_DEFAULT 0 +#define SD_TYPE_C64 1 +#define SD_TYPE_A2 2 + static int sd_type[16] = {}; static int sd_image_cangrow[16] = {}; static uint64_t buffer_lba[16] = { ULLONG_MAX,ULLONG_MAX,ULLONG_MAX,ULLONG_MAX, @@ -2007,15 +2012,9 @@ int user_io_file_mount(const char *name, unsigned char index, char pre, int pre_ int img_type = 0; // disk image type (for C128 core): bit 0=dual sided, 1=raw GCR supported, 2=raw MFM supported, 3=high density sd_image_cangrow[index] = (pre != 0); - sd_type[index] = 0; - + sd_type[index] = SD_TYPE_DEFAULT ; if (len) { - if (!strcasecmp(user_io_get_core_name(), "apple-ii")) - { - ret = dsk2nib(name, sd_image + index); - } - if (!ret) { if (x2trd_ext_supp(name)) @@ -2030,7 +2029,7 @@ int user_io_file_mount(const char *name, unsigned char index, char pre, int pre_ { img_type = c64_openGCR(name, sd_image + index, index); ret = img_type < 0 ? 0 : 1; - sd_type[index] = 1; + sd_type[index] = SD_TYPE_C64; if (!ret) FileClose(&sd_image[index]); if (ret && is_c128()) @@ -2052,13 +2051,18 @@ int user_io_file_mount(const char *name, unsigned char index, char pre, int pre_ { img_type = c64_openGCR(name, sd_image + index, index); ret = img_type < 0 ? 0 : 1; - sd_type[index] = 1; + sd_type[index] = SD_TYPE_C64; if(!ret) FileClose(&sd_image[index]); } else if (!strcasecmp(name + len - 4, ".d81")) { img_type = G64_SUPPORT_HD | G64_SUPPORT_DS; } + else if (!strcasecmp(name + len - 4, ".dsk") && ((!strcasecmp(user_io_get_core_name(), "apple-ii") || (!strcasecmp(user_io_get_core_name(), "TK2000") )) )) + { + printf("FOUND A2 DSK type\n"); + sd_type[index] = SD_TYPE_A2; + } } if (ret && is_c128()) @@ -3085,8 +3089,14 @@ void user_io_poll() blks = 1; } DisableIO(); - - if ((blks == G64_BLOCK_COUNT_1541+1 || blks == G64_BLOCK_COUNT_1571+1) && sd_type[disk]) + if ( sd_type[disk] == SD_TYPE_A2) + { + //if (op) printf("A2 %x %llu on %d\n", op,lba, disk); + if (op == 2) a2_writeDSK(&sd_image[disk], lba, ack); + else if (op & 1) a2_readDSK(&sd_image[disk], lba, ack); + else break; + } + else if ((blks == G64_BLOCK_COUNT_1541+1 || blks == G64_BLOCK_COUNT_1571+1) && sd_type[disk]==SD_TYPE_C64) { if (op == 2) c64_writeGCR(disk, lba, blks-1); else if (op & 1) c64_readGCR(disk, lba, blks-1);