Palette fix for the X68000
This commit is contained in:
parent
74bc0eca7a
commit
63e76f02e6
2 changed files with 82 additions and 1 deletions
|
|
@ -84,6 +84,22 @@
|
||||||
#define X68K_GFX_PALETTE ((volatile uint16_t *)0xE82000L)
|
#define X68K_GFX_PALETTE ((volatile uint16_t *)0xE82000L)
|
||||||
#define X68K_PALETTE_ENTRIES (SURFACE_PALETTE_COUNT * SURFACE_COLORS_PER_PALETTE)
|
#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 -----------------------------------------------------
|
// ----- Millisecond clock -----------------------------------------------------
|
||||||
//
|
//
|
||||||
|
|
@ -161,6 +177,15 @@ bool jlpInit(const jlConfigT *config) {
|
||||||
gPrevCrtMode = _iocs_crtmod(-1); // -1 queries without changing
|
gPrevCrtMode = _iocs_crtmod(-1); // -1 queries without changing
|
||||||
_iocs_crtmod(X68K_CRTMOD_512_256);
|
_iocs_crtmod(X68K_CRTMOD_512_256);
|
||||||
_iocs_g_clr_on(); // clear graphics + enable the plane
|
_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 --
|
// NOTE: installing vdispHandler via _iocs_vdispst HANGS the machine --
|
||||||
// tested, no serial output at all, so it wedges before main() gets going.
|
// 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
|
// IOCS does NOT wrap the handler: _VDISPST writes the pointer straight into
|
||||||
|
|
|
||||||
|
|
@ -200,6 +200,57 @@ class Xdf:
|
||||||
struct.pack_into("<I", entry, 28, len(payload))
|
struct.pack_into("<I", entry, 28, len(payload))
|
||||||
self.data[slot:slot + DIR_ENTRY_SIZE] = entry
|
self.data[slot:slot + DIR_ENTRY_SIZE] = entry
|
||||||
|
|
||||||
|
def _dir_entry(self, name11, attr, cluster, size):
|
||||||
|
entry = bytearray(DIR_ENTRY_SIZE)
|
||||||
|
entry[0:11] = name11
|
||||||
|
entry[11] = attr
|
||||||
|
struct.pack_into("<H", entry, 22, (12 << 11)) # 12:00:00
|
||||||
|
struct.pack_into("<H", entry, 24, ((2026 - 1980) << 9) | (1 << 5) | 1) # 2026-01-01
|
||||||
|
struct.pack_into("<H", entry, 26, cluster)
|
||||||
|
struct.pack_into("<I", entry, 28, size)
|
||||||
|
return entry
|
||||||
|
|
||||||
|
def add_file_in_subdir(self, dirname, name, payload):
|
||||||
|
# Create a one-cluster subdirectory in root (with . and .. entries) and write `name` into it, so a
|
||||||
|
# guest that reads e.g. SAVES/RN.CFG finds it. Fresh-template semantics: the subdir must not exist
|
||||||
|
# yet (the callers copy a clean image per run). Human68k FAT12 is otherwise ordinary FAT12.
|
||||||
|
dstem = dirname.upper()
|
||||||
|
if len(dstem) > 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):
|
def flush(self):
|
||||||
if not self.writable:
|
if not self.writable:
|
||||||
raise PermissionError("opened read-only")
|
raise PermissionError("opened read-only")
|
||||||
|
|
@ -235,7 +286,12 @@ def main(argv):
|
||||||
name = argv[4] if len(argv) > 4 else os.path.basename(host)
|
name = argv[4] if len(argv) > 4 else os.path.basename(host)
|
||||||
img = Xdf(image, writable=True)
|
img = Xdf(image, writable=True)
|
||||||
with open(host, "rb") as fp:
|
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()
|
img.flush()
|
||||||
print(f"added {name} ({os.path.getsize(host)} bytes) to {image}")
|
print(f"added {name} ({os.path.getsize(host)} bytes) to {image}")
|
||||||
return 0
|
return 0
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue