Skip to content

Commit a846712

Browse files
committed
Merge remote-tracking branch 'upstream/master'
2 parents 7a05c05 + f8a198e commit a846712

7 files changed

Lines changed: 1910 additions & 0 deletions

File tree

support.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99

1010
// Apple 2 support
1111
#include "support/a2/dsk2nib_lib.h"
12+
// Apple IIgs disk integration
13+
#include "support/a2/iigs_disk.h"
1214

1315
// Archie support
1416
#include "support/archie/archie.h"

support/a2/IIGS_DISK_SUPPORT.md

Lines changed: 451 additions & 0 deletions
Large diffs are not rendered by default.

support/a2/iigs_disk.cpp

Lines changed: 333 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,333 @@
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+
}

support/a2/iigs_disk.h

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
// MiSTer integration glue for the Apple IIgs core disk handling.
2+
//
3+
// This is the bridge between the pure codec (iigs_fmt.{h,cpp}) and MiSTer's
4+
// SD-block plumbing in user_io.cpp. It is active ONLY for the "Apple-IIgs"
5+
// core; every other core (including the Apple II NIB flow via SD_TYPE_A2) is
6+
// left completely untouched.
7+
//
8+
// Slots (Apple-IIgs.sv, VDNUM=4): S0,S1 = hard disk; S2 = 3.5"; S3 = 5.25".
9+
10+
#ifndef IIGS_DISK_H
11+
#define IIGS_DISK_H
12+
13+
#include <stdint.h>
14+
// Relies on fileTYPE being defined already (file_io.h), matching dsk2nib_lib.h:
15+
// this header is reached via support.h, which is included after file_io.h.
16+
17+
// sd_type value used to route IIgs slots into iigs_read/iigs_write. Continues
18+
// the series after SD_TYPE_A2 (=2) defined in user_io.cpp.
19+
#define SD_TYPE_IIGS 3
20+
21+
// iigs_mount return codes.
22+
#define IIGS_PASSTHRU 0 // not handled — serve normally (raw .po/.hdv/native .woz)
23+
#define IIGS_HANDLED 1 // set up for iigs_read/iigs_write; size/sd_type adjusted
24+
#define IIGS_REJECT (-1) // wrong disk for this slot — caller aborts mount (OSD shown)
25+
26+
// True if the running core is "Apple-IIgs".
27+
int iigs_is_core(void);
28+
29+
// Called from user_io_file_mount after the image is opened, for the IIgs core.
30+
// May convert a floppy image to WOZ (in memory) or set a header offset for a
31+
// 2MG/DC42 hard disk, validate the image against the slot (strict, with OSD
32+
// guidance on mismatch), and adjust f->size to what the core should see.
33+
// *out_writable is set to the read/write policy for HANDLED mounts.
34+
int iigs_mount(int index, const char *name, fileTYPE *f, int *out_writable);
35+
36+
// Release any per-slot resources (converted WOZ buffer). Safe to call anytime.
37+
void iigs_unmount(int index);
38+
39+
// Block dispatch, called from user_io.cpp like a2_readDSK/a2_writeDSK.
40+
void iigs_read(int disk, fileTYPE *f, uint64_t lba, int ack);
41+
void iigs_write(int disk, fileTYPE *f, uint64_t lba, int ack);
42+
43+
#endif // IIGS_DISK_H

0 commit comments

Comments
 (0)