64kramsystem / 64kramsystem/ghidra-vice-connector

Sprite data viewer: render active sprite frames from live memory

Aperta
#31 0 commenti 0 reazioni 0 assegnatari Vedi su GitHub
priority: medium
Lingua principale
Python
Stelle
1
Fork
0
Metriche di merge delle PR
Nessuna PR unita negli ultimi 30g

Descrizione

## 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)

Guida per i contributori

Nessuna guida per i contributori indicizzata per questo repository

Direzione di ricerca

La issue specifica di aggiungere un nuovo metodo `save_sprite_sheet` a `src/main/py/src/vice/methods.py` e funzioni helper in `src/main/py/src/vice/commands.py`. Inizia leggendo il codice esistente in questi file per comprendere la struttura e l’API `memory_get`. Il piano di implementazione include pseudocodice dettagliato per leggere i registri VIC-II, calcolare gli indirizzi ed eseguire il rendering degli sprite. Esegui il test eseguendo il nuovo metodo nell’ambiente del connettore Ghidra-VICE e verifica il file PNG di output in `/tmp/vice_sprites.png`. Il lavoro è completo quando lo sprite sheet viene generato correttamente dalla memoria VICE in tempo reale.

Scritto dal modello di indicizzazione a partire dal testo della issue.

Valutazione

Ambito
desktop-dev, tooling
Tipo di issue
Funzionalità
Difficoltà
3/5
Tempo stimato
1-2 giorni
Stato di attività
Ferma
Chiarezza
Specificata chiaramente
Idoneità per principianti
65/100

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.