diff --git a/src/x68000/hal.c b/src/x68000/hal.c index 25862e3..e963cfa 100644 --- a/src/x68000/hal.c +++ b/src/x68000/hal.c @@ -84,6 +84,22 @@ #define X68K_GFX_PALETTE ((volatile uint16_t *)0xE82000L) #define X68K_PALETTE_ENTRIES (SURFACE_PALETTE_COUNT * SURFACE_COLORS_PER_PALETTE) +// Video Controller register 2 ($E82600) low byte = per-plane display enable (MAME x68k_v.cpp): bits 0-3 +// graphic layers, bit 4 graphic, bit 5 (0x20) TEXT plane, bit 6 sprite. _iocs_crtmod leaves the text +// plane ON, so Human68k's desktop (Drv0-3, the kana/romaji status column) bleeds through the graphics; +// clearing bit 5 hides it while keeping the graphic layers we draw into. +#define X68K_VIDCTRL2 ((volatile uint16_t *)0xE82600L) +#define X68K_VIDCTRL2_TEXT 0x0020u + +// Video Controller register 0 ($E82400) low 2 bits select the graphic colour depth (MAME x68k_v.cpp +// switch(reg[0] & 3)): 0 = 16-colour, 1 = 256-colour palette-indexed, 3 = 65536-colour DIRECT. +// _iocs_crtmod(13) sets the 512x512 CRTC but leaves this at 3 (direct), whereas this port draws 8-bit +// PALETTE INDICES into GVRAM and uploads a 256-entry palette at $E82000 -- so the mode MUST be forced to +// 256-colour or every index is interpreted as a near-black direct colour (the "invisible/dim" bug). +#define X68K_VIDCTRL0 ((volatile uint16_t *)0xE82400L) +#define X68K_VIDCTRL0_DEPTH 0x0003u +#define X68K_VIDCTRL0_256 0x0001u + // ----- Millisecond clock ----------------------------------------------------- // @@ -161,6 +177,15 @@ bool jlpInit(const jlConfigT *config) { gPrevCrtMode = _iocs_crtmod(-1); // -1 queries without changing _iocs_crtmod(X68K_CRTMOD_512_256); _iocs_g_clr_on(); // clear graphics + enable the plane + // crtmod 13 leaves the video controller in 65536-colour DIRECT mode (reg[0] & 3 == 3). This port + // draws 8-bit palette indices, so force 256-colour palette-indexed mode or every colour renders + // near-black. Root-caused + proven live on MAME (reg0 0x0003 -> 0x0001 turned a black frame into a + // correct red/yellow/blue test pattern). + X68K_VIDCTRL0[0] = (uint16_t)((X68K_VIDCTRL0[0] & (uint16_t)~X68K_VIDCTRL0_DEPTH) | X68K_VIDCTRL0_256); + // jlpPresent draws into the GRAPHIC plane (GVRAM $C00000), NOT the text plane -- so Human68k's text + // plane (the Drv0-3 + kana/romaji desktop that _iocs_crtmod leaves on) bleeds over our frame. A + // reg[2] &= ~0x20 write to hide it did NOT take on MAME (the desktop stayed visible), so that cosmetic + // bleed is left as a follow-up; it does not affect the graphic layers we draw into. // NOTE: installing vdispHandler via _iocs_vdispst HANGS the machine -- // tested, no serial output at all, so it wedges before main() gets going. // IOCS does NOT wrap the handler: _VDISPST writes the pointer straight into diff --git a/tools/xdftool.py b/tools/xdftool.py index 99f8bad..974581f 100755 --- a/tools/xdftool.py +++ b/tools/xdftool.py @@ -200,6 +200,57 @@ class Xdf: struct.pack_into(" 8: + raise ValueError(f"subdir '{dirname}' does not fit 8.3") + cb = self.cluster_bytes + need = max(1, (len(payload) + cb - 1) // cb) + free = self.free_clusters() + if len(free) < need + 1: + raise OSError(f"{self.path}: need {need + 1} clusters, {len(free)} free") + dir_cluster = free[0] + data_chain = free[1:1 + need] + # File data clusters. + for idx, cluster in enumerate(data_chain): + off = self.cluster_offset(cluster) + chunk = payload[idx * cb:(idx + 1) * cb] + self.data[off:off + len(chunk)] = chunk + if len(chunk) < cb: + self.data[off + len(chunk):off + cb] = bytes(cb - len(chunk)) + self.fat_set(cluster, 0xFFF if idx == need - 1 else data_chain[idx + 1]) + # The subdirectory's own cluster: '.', '..', then the file entry, rest free. + doff = self.cluster_offset(dir_cluster) + self.data[doff:doff + cb] = bytes(cb) + self.data[doff:doff + 32] = self._dir_entry(b". ", ATTR_DIR, dir_cluster, 0) + self.data[doff + 32:doff + 64] = self._dir_entry(b".. ", ATTR_DIR, 0, 0) + self.data[doff + 64:doff + 96] = self._dir_entry( + self.encode_name(name), ATTR_ARCHIVE, data_chain[0], len(payload)) + self.fat_set(dir_cluster, 0xFFF) + # Root entry pointing at the subdirectory. + slot = None + for _, off, e in self._entries(): + if e[0] in (FREE_MARKER, DELETED_MARKER): + slot = off + break + if slot is None: + raise OSError(f"{self.path}: root directory full") + self.data[slot:slot + DIR_ENTRY_SIZE] = self._dir_entry( + dstem.ljust(8).encode("ascii") + b" ", ATTR_DIR, dir_cluster, 0) + def flush(self): if not self.writable: raise PermissionError("opened read-only") @@ -235,7 +286,12 @@ def main(argv): name = argv[4] if len(argv) > 4 else os.path.basename(host) img = Xdf(image, writable=True) with open(host, "rb") as fp: - img.add_file(name, fp.read()) + payload = fp.read() + if "/" in name: # SAVES/RN.CFG -> a one-cluster subdirectory + the file + sub, _, leaf = name.partition("/") + img.add_file_in_subdir(sub, leaf, payload) + else: + img.add_file(name, payload) img.flush() print(f"added {name} ({os.path.getsize(host)} bytes) to {image}") return 0