64kramsystem / 64kramsystem/ghidra-vice-connector

Sprite data viewer: render active sprite frames from live memory

Abierto
#31 0 comentarios 0 reacciones 0 asignados Ver en GitHub
priority: medium
Lenguaje dominante
Python
Estrellas
1
Forks
0
Métricas de merge de PR
Sin PR fusionados en 30 d

Descripción

## Summary

Read sprite pointers and data blocks from live VICE memory and render them as annotated bitmaps, saved to PNG files. This lets you identify which memory addresses hold sprite graphics during a game session — a core challenge when the 64KB space mixes code, sprite frames, and other data.

Source: RetroDebugger (Sprite View with scaling), Regenerator 2000 (inline sprite rendering).

---

## How C64 sprites work

The VIC-II supports 8 hardware sprites. Their data addresses are controlled by **sprite pointers**: 8 bytes at `screen_base + $03F8`–`screen_base + $03FF`. Each pointer byte × 64 = address of the 63-byte sprite data block.

**Screen base address** = `(VIC_VMCSB >> 4) * $0400`, where `VIC_VMCSB` is the byte at `$D018`. The VIC bank (which 16KB window) is determined by CIA2 Port A (`$DD00`) bits 0–1: `bank = 3 - (CIA2_PRA & 0x03)`, so `vic_base = bank * $4000`.

**Sprite data block:** 63 bytes = 3 bytes × 21 rows = 24×21 pixels (hires) or 12×21 pixels (multicolor).

**Multicolor flag:** bit N of `$D01C` = sprite N is multicolor. Multicolor sprites use 4 colors: `$D025` (color 1), `$D026` (color 2), `$D027+N` (sprite color), transparent.

---

## Implementation plan

### 1. `util.py` — no new BMP commands needed

All data is read via the existing `memory_get()`. Use `side_effects=False` and the I/O bank for chip registers (see issue #21 for bank discovery pattern).

### 2. `methods.py` — new method

```python
@REGISTRY.method(display='Save Sprite Sheet')
def save_sprite_sheet(process: C64):
"""
Read all 8 sprite data blocks and save them as a PNG sprite sheet.
Output: /tmp/vice_sprites.png — 8 sprites in a row, 2x scaled.
"""
vice = commands.STATE.require_vice()

# 1. Read VIC-II registers (I/O bank)
io_bank = _get_io_bank(vice) # shared helper — see issue #21
vic = vice.memory_get(0xD000, 0xD02E, bank_id=io_bank, side_effects=False)
vmcsb = vic[0xD018 - 0xD000]
spena = vic[0xD015 - 0xD000]
spmc = vic[0xD01C - 0xD000]
mc0 = vic[0xD025 - 0xD000] & 0x0F # multicolor 0
mc1 = vic[0xD026 - 0xD000] & 0x0F # multicolor 1
sp_cols = [vic[0xD027 + i - 0xD000] & 0x0F for i in range(8)]

# 2. Determine VIC bank and screen base
cia2 = vice.memory_get(0xDD00, 0xDD00, bank_id=io_bank, side_effects=False)
vic_bank = (3 - (cia2[0] & 0x03)) * 0x4000
screen_base = vic_bank + ((vmcsb >> 4) & 0x0F) * 0x0400

# 3. Read sprite pointers and data
ptrs = vice.memory_get(screen_base + 0x03F8, screen_base + 0x03FF)
sprites = []
for i, ptr in enumerate(ptrs):
addr = vic_bank + ptr * 64
data = vice.memory_get(addr, addr + 62)
sprites.append({'data': data, 'multicolor': bool(spmc & (1 << i)),
'color': sp_cols[i], 'enabled': bool(spena & (1 << i)),
'ptr': ptr, 'addr': addr})

# 4. Render and save PNG
_render_sprite_sheet(sprites, mc0, mc1, path='/tmp/vice_sprites.png')
for i, s in enumerate(sprites):
status = 'ON' if s['enabled'] else 'off'
mode = 'MC' if s['multicolor'] else 'HI'
log.info(f' Sprite {i}: ptr=0x{s["ptr"]:02X} addr=0x{s["addr"]:04X} {mode} {status}')
log.info('Sprite sheet saved to /tmp/vice_sprites.png')
```

### 3. Rendering helper

```python
C64_PALETTE = [ # standard C64 16-color palette (RGB)
(0,0,0),(255,255,255),(136,0,0),(170,255,238),(204,68,204),(0,204,85),
(0,0,170),(238,238,119),(221,136,85),(102,68,0),(255,119,119),(51,51,51),
(119,119,119),(170,255,102),(0,136,255),(187,187,187),
]

def _render_sprite_sheet(sprites, mc0, mc1, path, scale=3):
# Each sprite: 24×21 hires or 12×21 multicolor
# Rendered at scale×scale pixels per C64 pixel
# 8 sprites side by side with 4px gap
import zlib, struct as s, pathlib
W_SP = 24 * scale
H_SP = 21 * scale
GAP = 4
W = 8 * W_SP + 7 * GAP
H = H_SP
pixels = bytearray(W * H * 3)
for i, sp in enumerate(sprites):
ox = i * (W_SP + GAP)
for row in range(21):
b0, b1, b2 = sp['data'][row*3], sp['data'][row*3+1], sp['data'][row*3+2]
bits = (b0 << 16) | (b1 << 8) | b2
if sp['multicolor']:
for col in range(12):
pair = (bits >> (22 - col*2)) & 0x03
color = [None, C64_PALETTE[mc0], C64_PALETTE[sp['color']],
C64_PALETTE[mc1]][pair]
if color is None: continue
for dy in range(scale):
for dx in range(scale * 2): # MC pixels are 2× wide
px = ox + col * scale * 2 + dx
py = row * scale + dy
pixels[(py * W + px) * 3:(py * W + px) * 3 + 3] = color
else:
for col in range(24):
if not (bits >> (23 - col)) & 1: continue
color = C64_PALETTE[sp['color']]
for dy in range(scale):
for dx in range(scale):
px = ox + col * scale + dx
py = row * scale + dy
pixels[(py * W + px) * 3:(py * W + px) * 3 + 3] = color

_write_png(path, W, H, bytes(pixels)) # shared PNG writer (see issue #23)
```

---

## Files to change

- `src/main/py/src/vice/methods.py` — add `save_sprite_sheet`
- `src/main/py/src/vice/commands.py` — add `_get_io_bank()` shared helper (also used by issue #21), `_render_sprite_sheet()`, `_write_png()` (shared with issue #23)

Guía de contribución

No hay ninguna guía de contribución indexada para este repositorio

Evaluación

Este issue todavía no se ha evaluado.

Recibe los nuevos issues en tu correo

Un resumen breve de issues de GitHub para principiantes.