|
| 1 | +// MiSTer integration glue for the Apple IIgs core. See iigs_disk.h. |
| 2 | + |
| 3 | +#include <stdio.h> |
| 4 | +#include <stdlib.h> |
| 5 | +#include <string.h> |
| 6 | +#include <strings.h> |
| 7 | + |
| 8 | +#include "../../file_io.h" |
| 9 | +#include "../../user_io.h" |
| 10 | +#include "../../hardware.h" |
| 11 | +#include "../../menu.h" |
| 12 | + |
| 13 | +#include "iigs_fmt.h" |
| 14 | +#include "iigs_disk.h" |
| 15 | + |
| 16 | +// Per-slot serving state (only used for SD_TYPE_IIGS slots). |
| 17 | +// mode 0 = hard disk, raw blocks at hdr_off (2MG/DC42) |
| 18 | +// mode 1 = converted floppy, served from the in-memory woz buffer (read-only) |
| 19 | +static int g_mode[16] = {}; |
| 20 | +static int64_t g_hdr_off[16] = {}; |
| 21 | +static uint8_t *g_woz[16] = {}; |
| 22 | +static size_t g_woz_sz[16] = {}; |
| 23 | +// Converted-floppy write-back descriptor (mode 1): |
| 24 | +static int g_wb_ok[16] = {}; // write-back supported (and file writable) |
| 25 | +static int g_wb_kind[16] = {}; // 1 = 3.5", 2 = 5.25" |
| 26 | +static int64_t g_wb_off[16] = {}; // header offset within the source file |
| 27 | +static int g_wb_order[16] = {}; // 5.25 source order: 0 = DOS, 1 = ProDOS |
| 28 | + |
| 29 | +// IIgs slot kinds: 0,1 = hard disk; 2 = 3.5"; 3 = 5.25". -1 = not an IIgs slot. |
| 30 | +static int slot_kind(int index) |
| 31 | +{ |
| 32 | + if (index == 0 || index == 1) return 0; // HDD |
| 33 | + if (index == 2) return 1; // 3.5" |
| 34 | + if (index == 3) return 2; // 5.25" |
| 35 | + return -1; |
| 36 | +} |
| 37 | + |
| 38 | +static int eqi(const char *a, const char *b) { return a && b && !strcasecmp(a, b); } |
| 39 | + |
| 40 | +int iigs_is_core(void) |
| 41 | +{ |
| 42 | + const char *n = user_io_get_core_name(); |
| 43 | + return n && !strcasecmp(n, "Apple-IIgs"); |
| 44 | +} |
| 45 | + |
| 46 | +static void reject(const char *msg) |
| 47 | +{ |
| 48 | + printf("IIgs: rejecting mount: %s\n", msg); |
| 49 | + InfoMessage(msg, 5000, "Wrong disk for slot"); |
| 50 | +} |
| 51 | + |
| 52 | +void iigs_unmount(int index) |
| 53 | +{ |
| 54 | + if (index < 0 || index >= 16) return; |
| 55 | + if (g_woz[index]) { free(g_woz[index]); g_woz[index] = NULL; } |
| 56 | + g_woz_sz[index] = 0; |
| 57 | + g_hdr_off[index] = 0; |
| 58 | + g_mode[index] = 0; |
| 59 | + g_wb_ok[index] = 0; |
| 60 | + g_wb_kind[index] = 0; |
| 61 | + g_wb_off[index] = 0; |
| 62 | + g_wb_order[index] = 0; |
| 63 | +} |
| 64 | + |
| 65 | +// Read the entire open image into a freshly malloc'd buffer (caller frees). |
| 66 | +static uint8_t *read_all(fileTYPE *f, size_t *out_len) |
| 67 | +{ |
| 68 | + size_t n = (size_t)f->size; |
| 69 | + uint8_t *b = (uint8_t *)malloc(n ? n : 1); |
| 70 | + if (!b) return NULL; |
| 71 | + FileSeek(f, 0, SEEK_SET); |
| 72 | + size_t got = 0; |
| 73 | + while (got < n) { |
| 74 | + int r = FileReadAdv(f, b + got, n - got); |
| 75 | + if (r <= 0) break; |
| 76 | + got += (size_t)r; |
| 77 | + } |
| 78 | + FileSeek(f, 0, SEEK_SET); |
| 79 | + if (got != n) { free(b); return NULL; } |
| 80 | + *out_len = n; |
| 81 | + return b; |
| 82 | +} |
| 83 | + |
| 84 | +// Build a converted WOZ for a floppy slot. Returns malloc'd buffer + size, or |
| 85 | +// NULL on failure. kind: 1 = 3.5", 2 = 5.25". |
| 86 | +static uint8_t *build_woz(int kind, const char *ext, const uint8_t *raw, size_t raw_len, size_t *out_sz) |
| 87 | +{ |
| 88 | + // Resolve the payload (strip 2MG/DC42 header) and its order. |
| 89 | + const uint8_t *pay = raw; |
| 90 | + size_t pay_len = raw_len; |
| 91 | + int order_prodos = 0, order_nib = 0; |
| 92 | + |
| 93 | + TwoMG m; |
| 94 | + DC42 d; |
| 95 | + if (twomg_parse(raw, raw_len, &m)) { |
| 96 | + pay = raw + m.data_offset; |
| 97 | + pay_len = m.data_len; |
| 98 | + order_prodos = (m.format == 1); |
| 99 | + order_nib = (m.format == 2); |
| 100 | + } else if (dc42_parse(raw, raw_len, &d)) { |
| 101 | + pay = raw + 84; |
| 102 | + pay_len = d.data_size; |
| 103 | + order_prodos = 1; |
| 104 | + } else if (raw_len == A2_NIB_IMAGE_SIZE) { |
| 105 | + order_nib = 1; |
| 106 | + } else if (eqi(ext, "po")) { |
| 107 | + order_prodos = 1; |
| 108 | + } // else .do/.dsk => DOS order |
| 109 | + |
| 110 | + if (kind == 1) { |
| 111 | + // 3.5": need an 800K ProDOS image |
| 112 | + if (pay_len != A2_35_IMAGE_SIZE || order_nib) return NULL; |
| 113 | + size_t cap = 2 * 1024 * 1024; |
| 114 | + uint8_t *woz = (uint8_t *)malloc(cap); |
| 115 | + if (!woz) return NULL; |
| 116 | + size_t n = a2_po_to_woz35(woz, cap, pay); |
| 117 | + if (!n) { free(woz); return NULL; } |
| 118 | + *out_sz = n; |
| 119 | + return woz; |
| 120 | + } |
| 121 | + |
| 122 | + // 5.25": produce a 140K DOS-order image, then encode to WOZ |
| 123 | + static uint8_t dsk[A2_525_IMAGE_SIZE]; |
| 124 | + if (order_nib) { |
| 125 | + if (pay_len != A2_NIB_IMAGE_SIZE) return NULL; |
| 126 | + if (!a2_nib_to_dsk(dsk, pay)) return NULL; |
| 127 | + } else { |
| 128 | + if (pay_len != A2_525_IMAGE_SIZE) return NULL; |
| 129 | + if (order_prodos) a2_prodos_to_dos(dsk, pay); |
| 130 | + else memcpy(dsk, pay, A2_525_IMAGE_SIZE); |
| 131 | + } |
| 132 | + size_t cap = 512 * 1024; |
| 133 | + uint8_t *woz = (uint8_t *)malloc(cap); |
| 134 | + if (!woz) return NULL; |
| 135 | + size_t n = a2_dsk_to_woz525(woz, cap, dsk); |
| 136 | + if (!n) { free(woz); return NULL; } |
| 137 | + *out_sz = n; |
| 138 | + return woz; |
| 139 | +} |
| 140 | + |
| 141 | +int iigs_mount(int index, const char *name, fileTYPE *f, int *out_writable) |
| 142 | +{ |
| 143 | + if (!iigs_is_core()) return IIGS_PASSTHRU; |
| 144 | + int kind = slot_kind(index); |
| 145 | + if (kind < 0) return IIGS_PASSTHRU; |
| 146 | + |
| 147 | + iigs_unmount(index); |
| 148 | + |
| 149 | + uint8_t head[128]; |
| 150 | + memset(head, 0, sizeof(head)); |
| 151 | + size_t hn = f->size < (int)sizeof(head) ? (size_t)f->size : sizeof(head); |
| 152 | + FileSeek(f, 0, SEEK_SET); |
| 153 | + FileReadAdv(f, head, hn); |
| 154 | + FileSeek(f, 0, SEEK_SET); |
| 155 | + |
| 156 | + const char *dot = strrchr(name, '.'); |
| 157 | + const char *ext = dot ? dot + 1 : NULL; |
| 158 | + int wt = woz_disk_type(head, f->size); |
| 159 | + int is_nib = (f->size == A2_NIB_IMAGE_SIZE); |
| 160 | + TwoMG m; |
| 161 | + int is_2mg = twomg_parse(head, f->size, &m); |
| 162 | + int is_dc42 = dc42_probe(head, f->size); |
| 163 | + |
| 164 | + if (kind == 0) { |
| 165 | + // ----- hard-disk slot: permissive (any ProDOS-order block image) ----- |
| 166 | + if (wt > 0) { reject("Hard-disk slot needs a ProDOS block image, not a WOZ floppy."); return IIGS_REJECT; } |
| 167 | + if (is_nib) { reject("Hard-disk slot needs a ProDOS block image, not a .nib floppy."); return IIGS_REJECT; } |
| 168 | + if (is_2mg && m.format != 1) { reject("This 2MG is DOS/NIB order; not a ProDOS hard-disk image."); return IIGS_REJECT; } |
| 169 | + if (eqi(ext, "do")) { reject("DOS-order disk — load it in a 5.25\" drive, not a hard disk."); return IIGS_REJECT; } |
| 170 | + |
| 171 | + int64_t off = 0; |
| 172 | + if (is_2mg) off = m.data_offset; |
| 173 | + else if (is_dc42) off = 84; |
| 174 | + if (off == 0) return IIGS_PASSTHRU; // raw .po/.hdv: generic path serves it |
| 175 | + |
| 176 | + g_mode[index] = 0; |
| 177 | + g_hdr_off[index] = off; |
| 178 | + if (f->size > off) f->size -= off; // core sees the payload size |
| 179 | + *out_writable = FileCanWrite(name); |
| 180 | + printf("IIgs: HDD slot %d, header offset %lld, payload %lld bytes\n", |
| 181 | + index, (long long)off, (long long)f->size); |
| 182 | + return IIGS_HANDLED; |
| 183 | + } |
| 184 | + |
| 185 | + // ----- floppy slot (kind 1 = 3.5", kind 2 = 5.25") ----- |
| 186 | + if (wt > 0) { |
| 187 | + int want = (kind == 1) ? 2 : 1; |
| 188 | + if (wt != want) { |
| 189 | + reject(kind == 1 ? "That's a 5.25\" WOZ — use the 5.25\" drive." |
| 190 | + : "That's a 3.5\" WOZ — use the 3.5\" drive."); |
| 191 | + return IIGS_REJECT; |
| 192 | + } |
| 193 | + |
| 194 | + // A zip-backed file is a forward-only decompression stream: every |
| 195 | + // backward seek re-inflates from the start, which is far too slow for |
| 196 | + // the WOZ controller's random track access and breaks flux-timing |
| 197 | + // copy protection (e.g. Karateka). Load the WOZ verbatim into RAM and |
| 198 | + // serve from there — byte-for-byte identical, no conversion, so the |
| 199 | + // protection is preserved. Regular files keep fast random access via |
| 200 | + // the passthrough path below. |
| 201 | + if (f->zip) { |
| 202 | + size_t n = 0; |
| 203 | + uint8_t *buf = read_all(f, &n); |
| 204 | + if (!buf) { reject("Could not read the WOZ from the archive."); return IIGS_REJECT; } |
| 205 | + g_mode[index] = 1; |
| 206 | + g_woz[index] = buf; |
| 207 | + g_woz_sz[index] = n; |
| 208 | + g_wb_ok[index] = 0; // read-only: cannot write back into a zip |
| 209 | + f->size = (int64_t)n; |
| 210 | + *out_writable = 0; |
| 211 | + printf("IIgs: native WOZ from archive on slot %d -> RAM (%zu bytes), read-only\n", index, n); |
| 212 | + return IIGS_HANDLED; |
| 213 | + } |
| 214 | + |
| 215 | + return IIGS_PASSTHRU; // native WOZ on a real file: serve directly |
| 216 | + } |
| 217 | + |
| 218 | + // Need to convert. Validate geometry up front for a clear message. |
| 219 | + DiskClass cls = iigs_classify(head, f->size, ext); |
| 220 | + int want_cls = (kind == 1) ? DC_FLOPPY_35 : DC_FLOPPY_525; |
| 221 | + if (cls != want_cls) { |
| 222 | + reject(kind == 1 ? "3.5\" drive needs an 800K disk image." |
| 223 | + : "5.25\" drive needs a 140K disk image."); |
| 224 | + return IIGS_REJECT; |
| 225 | + } |
| 226 | + |
| 227 | + size_t raw_len = 0; |
| 228 | + uint8_t *raw = read_all(f, &raw_len); |
| 229 | + if (!raw) { reject("Could not read the disk image."); return IIGS_REJECT; } |
| 230 | + |
| 231 | + // Determine the write-back descriptor before consuming `raw`. |
| 232 | + int wb_off = 0, wb_order = (kind == 1) ? 1 : 0, wb_ok = 0; |
| 233 | + TwoMG mm; |
| 234 | + if (twomg_parse(raw, raw_len, &mm)) { |
| 235 | + wb_off = (int)mm.data_offset; |
| 236 | + if (mm.format == 2) wb_ok = 0; // NIB payload: read-only |
| 237 | + else { wb_ok = 1; if (kind == 2) wb_order = (mm.format == 1); } |
| 238 | + } else if (dc42_probe(raw, raw_len)) { |
| 239 | + wb_ok = 0; // DC42: read-only (stale checksum) |
| 240 | + } else if (raw_len == A2_NIB_IMAGE_SIZE) { |
| 241 | + wb_ok = 0; // .nib: read-only (v1) |
| 242 | + } else if (kind == 2 && eqi(ext, "po")) { |
| 243 | + wb_ok = 1; wb_order = 1; // ProDOS-order 140K |
| 244 | + } else { |
| 245 | + wb_ok = 1; if (kind == 2) wb_order = 0; // raw .po(800K) / .do / .dsk |
| 246 | + } |
| 247 | + if (!FileCanWrite(name)) wb_ok = 0; |
| 248 | + |
| 249 | + size_t woz_sz = 0; |
| 250 | + uint8_t *woz = build_woz(kind, ext, raw, raw_len, &woz_sz); |
| 251 | + free(raw); |
| 252 | + if (!woz) { reject("Could not convert this disk to WOZ."); return IIGS_REJECT; } |
| 253 | + |
| 254 | + g_mode[index] = 1; |
| 255 | + g_woz[index] = woz; |
| 256 | + g_woz_sz[index] = woz_sz; |
| 257 | + g_wb_ok[index] = wb_ok; |
| 258 | + g_wb_kind[index] = kind; |
| 259 | + g_wb_off[index] = wb_off; |
| 260 | + g_wb_order[index] = wb_order; |
| 261 | + f->size = (int64_t)woz_sz; // core sees the WOZ size |
| 262 | + *out_writable = wb_ok; // writable only if write-back is supported |
| 263 | + printf("IIgs: floppy slot %d converted to WOZ (%zu bytes), %s\n", |
| 264 | + index, woz_sz, wb_ok ? "read-write (write-back)" : "read-only"); |
| 265 | + return IIGS_HANDLED; |
| 266 | +} |
| 267 | + |
| 268 | +void iigs_read(int disk, fileTYPE *f, uint64_t lba, int ack) |
| 269 | +{ |
| 270 | + uint8_t chunk[512]; |
| 271 | + if (g_mode[disk] == 1) { |
| 272 | + uint64_t off = lba * 512; |
| 273 | + if (g_woz[disk] && off + 512 <= g_woz_sz[disk]) memcpy(chunk, g_woz[disk] + off, 512); |
| 274 | + else memset(chunk, 0, 512); |
| 275 | + } else { |
| 276 | + if (!(FileSeek(f, (int64_t)(lba * 512 + g_hdr_off[disk]), SEEK_SET) && |
| 277 | + FileReadAdv(f, chunk, 512) > 0)) |
| 278 | + memset(chunk, 0, 512); |
| 279 | + } |
| 280 | + EnableIO(); |
| 281 | + spi_w(UIO_SECTOR_RD | ack); |
| 282 | + spi_block_write(chunk, user_io_get_width(), 512); |
| 283 | + DisableIO(); |
| 284 | +} |
| 285 | + |
| 286 | +void iigs_write(int disk, fileTYPE *f, uint64_t lba, int ack) |
| 287 | +{ |
| 288 | + uint8_t chunk[512]; |
| 289 | + EnableIO(); |
| 290 | + spi_w(UIO_SECTOR_WR | ack); |
| 291 | + spi_block_read(chunk, user_io_get_width(), 512); |
| 292 | + DisableIO(); |
| 293 | + |
| 294 | + if (g_mode[disk] == 0) { |
| 295 | + // Hard disk: write straight back to the file at the header offset. |
| 296 | + if (FileSeek(f, (int64_t)(lba * 512 + g_hdr_off[disk]), SEEK_SET)) |
| 297 | + FileWriteAdv(f, chunk, 512); |
| 298 | + return; |
| 299 | + } |
| 300 | + |
| 301 | + // Converted floppy: update the in-memory WOZ, then (if write-back is |
| 302 | + // supported) decode the affected track and persist it to the source image. |
| 303 | + uint64_t off = lba * 512; |
| 304 | + if (g_woz[disk] && off + 512 <= g_woz_sz[disk]) memcpy(g_woz[disk] + off, chunk, 512); |
| 305 | + if (!g_wb_ok[disk]) return; |
| 306 | + |
| 307 | + int t = a2_woz_track_for_lba(g_woz[disk], g_woz_sz[disk], (uint32_t)lba); |
| 308 | + if (t < 0) return; // header block (TMAP/TRKS dir) — nothing to persist |
| 309 | + |
| 310 | + if (g_wb_kind[disk] == 1) { |
| 311 | + // 3.5": decode the track's ProDOS blocks, write that block range back. |
| 312 | + static uint8_t po[A2_35_IMAGE_SIZE]; |
| 313 | + int base = 0, cnt = 0; |
| 314 | + if (a2_woz35_decode_track(g_woz[disk], g_woz_sz[disk], t, po, &base, &cnt) > 0) { |
| 315 | + if (FileSeek(f, g_wb_off[disk] + (int64_t)base * 512, SEEK_SET)) |
| 316 | + FileWriteAdv(f, po + (size_t)base * 512, (size_t)cnt * 512); |
| 317 | + } |
| 318 | + } else { |
| 319 | + // 5.25": decode the DOS-order track; re-skew to ProDOS if the source is .po. |
| 320 | + static uint8_t dsk[A2_525_IMAGE_SIZE]; |
| 321 | + if (a2_woz525_decode_track(g_woz[disk], g_woz_sz[disk], t, dsk) > 0) { |
| 322 | + static const int D2P[16] = { 0, 8, 1, 9, 2, 10, 3, 11, 4, 12, 5, 13, 6, 14, 7, 15 }; |
| 323 | + uint8_t out[A2_TRACK_SIZE]; |
| 324 | + const uint8_t *src = dsk + (size_t)t * A2_TRACK_SIZE; |
| 325 | + if (g_wb_order[disk] == 1) |
| 326 | + for (int s = 0; s < 16; s++) memcpy(out + D2P[s] * 256, src + s * 256, 256); |
| 327 | + else |
| 328 | + memcpy(out, src, A2_TRACK_SIZE); |
| 329 | + if (FileSeek(f, g_wb_off[disk] + (int64_t)t * A2_TRACK_SIZE, SEEK_SET)) |
| 330 | + FileWriteAdv(f, out, A2_TRACK_SIZE); |
| 331 | + } |
| 332 | + } |
| 333 | +} |
0 commit comments