Este relatório consolida práticas e ferramentas atuais para escrever um SKILL.md em pt-BR que permita a um agente autônomo realizar desassembly completo de ROMs NES (.nes): validação do arquivo, parsing do cabeçalho iNES/NES 2.0, extração de PRG/CHR, modelagem de mappers e bank switching, endereçamento/espelhamento (mirroring), vetores de interrupção (NMI/RESET/IRQ/BRK), heurísticas de código vs dados, recuperação incremental de símbolos/labels, produção de saídas ASM/HTML/JSON, e validação com emuladores e recompilação “bit-perfect” quando possível. O formato iNES e sua extensão NES 2.0 são a base para inferir a topologia do cartucho, tamanhos e mapper; o NES 2.0 reduz ambiguidade histórica (ex.: “DiskDude!”) e adiciona submapper e tamanhos mais expressivos. citeturn23view0turn23view1turn7search12
A recomendação prática para um agente autônomo é combinar: (1) parsing determinístico do header + extração de bancos; (2) desassembly estático guiado por vetores + cross-references; (3) evidência dinâmica (CDL/trace) obtida por emuladores com debugger para separar código/dados e reduzir falsos positivos; (4) iteração (refino de labels/ranges) com ferramentas que aceitam “info files”/configuração; e (5) verificação por recompilação e emulação. citeturn4search2turn10search0turn10search24turn22view0
A ROM NES em formato .nes encapsula (no mínimo) um header de 16 bytes e áreas de PRG-ROM (código/dados do lado CPU) e CHR-ROM/CHR-RAM (dados gráficos do lado PPU). No NES 2.0, o header continua sendo 16 bytes, com campos adicionais (mapper MSB/submapper e MSB de tamanhos), e regras explícitas para tamanhos em unidades de 16 KiB (PRG) e 8 KiB (CHR) quando não se usa a forma expoente-multiplicador. citeturn23view1turn27view0turn27view1turn27view2
No nível de hardware lógico, a CPU enxerga um espaço de endereçamento de 64 KiB com RAM interna espelhada, registradores da PPU espelhados, registradores APU/I/O, e a janela do cartucho (PRG e registradores de mapper). O salto inicial do programa é definido pelo Reset vector no fim do espaço mapeado, junto com os vetores de NMI e IRQ/BRK. citeturn26search0turn26search1turn6search14turn26search3
flowchart TB
A["CPU $0000-$07FF<br/>RAM interna (2 KiB)"] --> B["$0800-$1FFF<br/>espelhos da RAM"]
B --> C["$2000-$2007<br/>PPU regs (8)"]
C --> D["$2008-$3FFF<br/>espelhos PPU regs"]
D --> E["$4000-$4017<br/>APU + I/O (controles, DMC, etc.)"]
E --> F["$4020-$5FFF<br/>expansão/cartucho (varia)"]
F --> G["$6000-$7FFF<br/>PRG-RAM/Save (se presente)"]
G --> H["$8000-$FFFF<br/>PRG-ROM + regs mapper<br/>(bank-switched)"]
H --> V["$FFFA-$FFFF<br/>Vetores NMI/RESET/IRQ-BRK"]
Os vetores ficam em endereços fixos: $FFFA–$FFFB (NMI), $FFFC–$FFFD (RESET), $FFFE–$FFFF (IRQ/BRK). Um desassembly “end-to-end” quase sempre começa lendo esses ponteiros (little-endian) e iniciando uma varredura recursiva, com suporte de logs dinâmicos para distinguir código/dados. citeturn26search0turn26search1turn26search22
Para instruções e modos de endereçamento do 6502 (fundamentais para reconstruir fluxo e interpretar tabelas de ponteiros), referências primárias incluem o manual clássico da família MCS6500 e guias de endereçamento (ex.: Obelisk 6502). citeturn6search4turn6search5
O SKILL.md abaixo assume que o agente tem permissão para executar processos locais e escrever arquivos em um workspace (detalhado na seção de permissões). Ferramentas recomendadas (com alternativas) e requisitos típicos:
- Sistema operacional: Linux (Ubuntu/Debian/Fedora) recomendado; Windows via WSL2 é viável; macOS é viável (detalhes de instalação variam). Especificado: não (o usuário não fixou SO).
- Utilitários base:
python3,dd,xxd/hexdump,sha256sum(ou equivalentes). - Disassembly (CLI): retrodisasm (tracing, suporte a CDL, verificação “bit-perfect” com assembler) — oferece opção de
-verify,-cdle suporte experimental a mappers com banking. citeturn22view0 - Disassembly/iteração com “info file”: da65 (cc65) — faz disassembly 6502/65C02 e aceita arquivo de informações com labels/ranges/segmentos, útil para iterar e controlar organização/offsets (especialmente em ROMs com bancos). citeturn10search0turn10search24
- Framework interativo: Ghidra (core) + extensão GhidraNes (loader iNES + suporte a vários mappers) para análise e anotação; Ghidra é Apache 2.0 e, nas releases recentes, recomenda JDK 21. citeturn19search0turn19search4turn10search2turn19search2
- Emuladores para validação e logging: FCEUX (Code/Data Logger e NL files), Mesen 2 (debugger/labels/CDL; projeto GPLv3; site indica versão recente e requisitos por plataforma). citeturn4search2turn4search38turn5search0turn5search22
- RE genérico: radare2 (LGPLv3 no repositório; comandos de xrefs/graphs; pode ser usado em modo “raw binary” com arquitetura 6502, mas exigirá modelagem manual do mapeamento NES). citeturn19search1turn10search1turn10search14
Observação importante sobre a sugestão “udis86”: udis86 é um disassembler para x86/x86-64, não para 6502/NES; portanto, não é apropriado para desassembly de PRG-ROM NES (a menos que o objetivo seja outro binário x86). citeturn20search0turn20search1
O workflow recomendado evita incentivar aquisição/distribuição ilegal de ROMs. Programas de computador são tipicamente protegidos por direito autoral em múltiplas jurisdições, incluindo o entendimento internacional e a legislação brasileira específica de software; logo, desassemble apenas ROMs sob licença permissiva/domínio público, ou para as quais você tenha autorização/direitos. citeturn21search0turn21search3turn21search10
Quando o objetivo é publicar resultados, uma prática mais segura é distribuir metadados, scripts, “info files” (ex.: para da65), símbolos/labels e patches, em vez de redistribuir a ROM. O próprio ecossistema de ferramentas (ex.: da65) foi desenhado para aceitar arquivos auxiliares que permitem que “quem tem uma cópia legal” gere uma listagem bem formatada localmente. citeturn10search0turn10search18
Para referência de engenharia reversa sob perspectiva brasileira (não substitui aconselhamento jurídico), a discussão sobre permissibilidade/condições e potenciais efeitos de inovação aparece na literatura acadêmica nacional sobre engenharia reversa de software. citeturn24search29
O exemplo end-to-end deste relatório usa um ROM explicitamente CC0/Public Domain (vide seção de exemplo), evitando qualquer dependência de material comercial restrito. citeturn12view0
Este é o encadeamento técnico que o SKILL.md deve impor ao agente: do arquivo bruto até outputs verificáveis.
O agente deve:
- Validar “magic bytes”:
"NES\x1A"no início (iNES e NES 2.0 compartilham isso). citeturn23view1 - Detectar variante do header (iNES vs NES 2.0) usando a regra de identificação, e aplicar a recomendação de detecção (inclui tratamento de lixo em bytes 7–15 como “DiskDude!”). citeturn23view0turn23view1
- Interpretar campos essenciais:
- PRG-ROM size: em NES 2.0, bytes 4 e nibble MSB em byte 9 (em unidades de 16 KiB quando MSB nibble é 0..E);
- CHR-ROM size: byte 5 + nibble MSB no byte 9 (em unidades de 8 KiB quando MSB nibble é 0..E);
- Trainer: bit correspondente no byte de flags; se presente, sempre 512 bytes em NES 2.0 e fica entre header e PRG. citeturn27view2turn23view1
- Extrair mapper/submapper (NES 2.0) e mapper (iNES) e registrar mirroring/hardwired vs mapper-controlled quando aplicável. citeturn23view1turn26search6turn23view0
- Validar consistência do tamanho total do arquivo: header (16) + trainer (0/512) + PRG + CHR + (misc ROM se houver) deve ser ≤ tamanho real; se houver sobra, registrar como “misc/extra” e marcar como unspecified se não for interpretável. citeturn23view1turn27view2
Após parsing, o agente deve materializar:
prg.bin(PRG-ROM concatenado) echr.bin(CHR-ROM, se existir; se CHR-ROM for 0, tratar como CHR-RAM e marcarchr.bincomo unspecified). Em NES 2.0, não assumir 8 KiB de CHR-RAM se CHR-ROM for 0; CHR-RAM deve vir explicitamente do header. citeturn27view1turn26search14- Um
manifest.jsoncom: mapper, submapper (ounull), PRG/CHR sizes, flags (trainer/battery/mirroring), hash (sha256) e offsets/paths dos blobs.
O agente deve sempre:
- Identificar o banco PRG que contém $FFFA-$FFFF no mapeamento CPU lógico, ler os 3 vetores (NMI/RESET/IRQ-BRK) e criar labels iniciais para esses handlers (mesmo que sejam stubs). citeturn26search0turn26search1
- Tratar IRQ e BRK como compartilhando vetores no NES (IRQ/BRK vector). citeturn26search0turn26search1
- Rotular e fixar, desde o início, endereços de registradores PPU/APU e espelhos (para que análise de xrefs e heurísticas de I/O fiquem mais confiáveis). citeturn6search14turn26search3turn26search6
- Usar referências de modos de endereçamento 6502 para detectar acessos indiretos e tabelas de ponteiros (little-endian). citeturn6search5turn6search4
Em ROMs “flat” (sem seções), um disassembler puro não distingue código/dados; por isso, o workflow precisa do braço dinâmico:
- Gerar CDL (Code/Data Logger) jogando/executando paths do programa dentro de um emulador com instrumentação; CDL marca bytes executados (código) e bytes lidos (dados), reduzindo desassembly incorreto. citeturn4search2
- Usar disassembler que aceite CDL (ex.: retrodisasm suporta
-cdl), para guiar o traçado e produzir ASM mais limpo; isso também é útil para mapear transições por bank switching. citeturn22view0 - Se usar da65, iterar com “info file”: rodar, interpretar trechos, adicionar labels/ranges/segmentos, repetir. citeturn10search0turn10search24
O agente deve tratar o mapper como parte do “modelo de execução” e não apenas como metadado. Uma descrição de alto nível (em pt-BR) é: o mapper permite que a CPU/PPU acessem mais memória por troca de bancos; endereços são agrupados em bancos e o software escreve em registradores do cartucho para alternar o mapeamento. citeturn25view0turn8search10
Requisito do SKILL.md: para mappers comuns, gerar um “bank map” inicial e definir regras de tradução CPU address -> ROM offset dependentes do banco ativo. Para o conjunto pedido:
- Mapper 0 (NROM): sem bank switching; PRG 16 KiB pode ser espelhado para preencher $8000-$FFFF (NROM-128) e PRG 32 KiB ocupa direto. citeturn7search0turn26search6
- Mapper 1 (MMC1/SxROM): registradores serializados, PRG bank mode típico fixa o último banco em $C000-$FFFF e permite trocar 16 KiB em $8000-$BFFF; há reset do shift register via escrita com bit 7=1. citeturn7search1turn7search5turn9search30
- Mapper 2 (UxROM/UNROM): troca bancos de PRG em 16 KiB com região fixa (comum: último banco fixo em $C000-$FFFF) e escrita em $8000-$FFFF; atenção a bus conflicts em certas placas e técnicas de lookup table. citeturn7search6turn9search26
- Mapper 3 (CNROM): PRG tipicamente fixo (32 KiB) e bank switching de CHR em bancos de 8 KiB; pode ter bus conflicts dependendo de submapper/variante. citeturn8search0turn8search4
- Mapper 4 (MMC3): PRG/CHR com granularidade fina e suporte a IRQ via contador de scanline (variações de comportamento por fabricante/versão). É o caso que mais exige modelagem correta de writes em registradores e validação via emulador/trace. citeturn7search3turn7search20
- Mapper 7 (AxROM): troca PRG em 32 KiB na janela $8000-$FFFF e oferece mirroring single-screen selecionável; pode haver bus conflicts em revisões. citeturn8search1turn9search26turn8search9
- Mapper 9 (MMC2): usa latch automático de CHR (4 KiB) acionado por acessos PPU a tiles (ex.: padrões envolvendo FD/FE), além de seleção de PRG conforme a placa; exige análise conjunta CPU+PPU para entender quando ocorre troca. citeturn8search2turn8search13turn8search6
- Mapper 10 (MMC4): similar ao MMC2 no conceito de latch de CHR, com diferenças na comutação de PRG e suporte a SRAM em algumas configurações; novamente requer observar acessos PPU. citeturn8search3turn8search7
- Mapper 66 (GxROM/GNROM/MHROM): troca PRG em 32 KiB e CHR em 8 KiB via registrador; é um “discrete logic mapper” comum. citeturn9search0turn9search26turn9search3
Mirroring: o agente deve diferenciar “mirroring de registradores” (espelhos de RAM/PPU regs) de “nametable mirroring/layout” (PPU $2000-$2FFF), que pode ser fixo (horizontal/vertical), single-screen, quatro telas, ou controlado por mapper. citeturn26search6turn26search2turn23view1turn23view0
O agente deve:
- Confirmar se o ROM usa IRQ de mapper, APU ou nenhum; pelo menos, rotular o vetor IRQ/BRK e rastrear entradas por IRQ durante execução (trace/CDL). citeturn26search0turn7search3
- Usar emuladores de alta observabilidade (debugger + trace + CDL). FCEUX documenta CDL e naming de arquivos
.nlcom convenção por banco (importante em jogos com bank swapping). citeturn4search2turn4search38 - Em mappers com IRQ (especialmente MMC3), cruzar sintomas visuais de timing incorreto (p.ex. uso de scanline IRQ para split scrolling) e validar em mais de um emulador quando necessário. citeturn7search20turn5search22
A tabela abaixo prioriza ferramentas citadas em documentação técnica e repositórios oficiais, e destaca “mapper support” no contexto de desassembly NES (não emulação completa).
| Ferramenta | Categoria | Diferenciais relevantes | Licença | Linguagem / runtime | Manutenção (indício) | Suporte a mappers / NES |
|---|---|---|---|---|---|---|
| retrodisasm | Disassembler/tracing CLI | Traça fluxo (code vs data), aceita CDL, gera ASM compatível com vários assemblers, opção -verify para recompilar e checar bit-perfect; suporte experimental a mappers com banking |
Apache-2.0 | Go | Release recente (ex.: janeiro/2026 no repo) | “Experimental mapper support” declarado; melhor com CDL citeturn18view0turn22view0 |
| da65 (cc65) | Disassembler 6502/65C02 | Aceita “info file” com ranges/labels/segmentos; bom para iteração e controle de endereços/segmentação | (cc65: licença do projeto é informada na doc do cc65; detalhe exato do “zlib-like” não foi especificado neste relatório) | C (toolchain) | Estável; usado há anos | Não é “NES-aware” por padrão; depende do mapeamento/segmentos fornecidos pelo agente citeturn10search0turn10search19turn10search24 |
| Ghidra | Framework RE | Disassembly + graphs + scripting; útil para rotular e navegar xrefs; requer loader NES adequado | Apache-2.0 | Java; releases recentes recomendam JDK 21 | Releases contínuas (ex.: 12.0.x em 2025/2026) | “NES-aware” via extensões; sem loader, precisa importar como raw e mapear manualmente citeturn19search0turn19search2turn19search4 |
| GhidraNes | Extensão Ghidra | Loader iNES e suporte declarado a vários mappers (lista no README) | (licença não especificada neste resumo; verificar no repositório) | Java (plugin) | Ativo no GitHub | Suporte explícito (ex.: NROM, MMC1, UxROM, MMC4 etc.) citeturn10search2 |
| IDA Pro | Framework RE comercial | Forte em análise interativa; suporta MOS 6502/65C02 (módulo) | Proprietário | (não especificado) | Comercial/ativo | Importa “raw binary”; para NES exige mapeamento manual e/ou scripts/plugins citeturn19search25turn20search39 |
| radare2 | Framework RE | CLI com xrefs, graphs e scripting; útil para inspeção rápida e automação | LGPLv3 (principal) | C | Ativo | Requer configurar arquitetura e mapping manual (NES não é formato executável padrão) citeturn19search1turn10search14 |
| FCEUX | Emulador + Debugging | Code/Data Logger (gera .cdl), debugger, trace; .nl suporta labels por banco |
GPL (histórico de GPL desde 2002; detalhes variam por distribuição) | C++ | Mantido; docs disponíveis | Emulação de mappers (não coberto aqui); CDL é central para separar code/data citeturn4search2turn4search38turn5search1turn5search12 |
| Mesen 2 | Emulador + Debugging | Debugger avançado; projeto GPLv3; site publica versão e requisitos | GPLv3 | (não especificado aqui) | Versão publicada (ex.: 2.1.1 em 2025) | Emulação ampla; útil para validar mapper/IRQ e gerar evidências de execução citeturn5search0turn5search22 |
| udis86 | Disassembler library | x86/x86-64, não se aplica a 6502 | BSD-2-Clause | C | Ativo em forks/pacotes | Não aplicável para PRG-ROM NES citeturn20search0turn20search1 |
| nes-disasm (amaiorano) | Disassembler CLI | Disassembler NES CLI (projeto antigo; exemplo de output simples) | MIT | C++ | Release 2014 (indício de baixa atividade) | Mapper exibido, mas suporte efetivo a banking não é especificado aqui citeturn4search4 |
NESHLA: o termo foi solicitado, mas não foi possível, neste relatório, confirmar uma fonte primária clara descrevendo a ferramenta e seu papel no desassembly NES; portanto, detalhes e recomendações específicas ficam unspecified. (Se o objetivo era “NES Header”/analisador de header, o papel pode ser parcialmente substituído pelo parsing automatizado que o SKILL.md propõe, ou por loaders como GhidraNes). citeturn23view0turn10search2
flowchart TD
A["Entrada: ROM .nes"] --> B["Validar magic 'NES\\x1A' + tamanho"]
B --> C["Parse header (iNES/NES 2.0)"]
C --> D["Extrair PRG/CHR (+ trainer se houver)"]
D --> E["Identificar mapper/submapper + mirroring"]
E --> F["Construir bank map inicial (CPU/PPU)"]
F --> G["Ler vetores $FFFA/$FFFC/$FFFE e criar entrypoints"]
G --> H["Desassembly estático por banco (recursivo)"]
H --> I["Rodar emulador + gerar CDL/trace"]
I --> J["Re-desassembly guiado por CDL + ajustar labels"]
J --> K["Output: ASM + JSON + HTML anotado"]
K --> L["Verificação: recompilar e comparar hashes + emular"]
A ênfase em iterar com CDL se justifica porque o Code/Data Logger foi concebido justamente para compensar o fato de que desassemblers não distinguem código/dados em binários “flat”. citeturn4search2turn22view0
A seguir, artefatos automáveis que o SKILL.md final incorpora. Eles são deliberadamente “agnósticos” (não assumem um ROM específico).
Extração rápida com dd (iNES/NES 2.0 básico)
(Assume que o agente já calculou PRG_SIZE e CHR_SIZE em bytes e TRAINER=0/512.)
# Entradas
ROM="game.nes"
OUTDIR="out"
mkdir -p "$OUTDIR"
# Header (16 bytes)
dd if="$ROM" of="$OUTDIR/header.bin" bs=1 count=16 status=none
# Trainer (0 ou 512)
dd if="$ROM" of="$OUTDIR/trainer.bin" bs=1 skip=16 count="$TRAINER" status=none
# PRG
dd if="$ROM" of="$OUTDIR/prg.bin" bs=1 skip=$((16+TRAINER)) count="$PRG_SIZE" status=none
# CHR
dd if="$ROM" of="$OUTDIR/chr.bin" bs=1 skip=$((16+TRAINER+PRG_SIZE)) count="$CHR_SIZE" status=noneO tamanho e presença do trainer são especificados no header; em NES 2.0 o trainer, quando presente, tem 512 bytes e fica entre header e PRG. citeturn27view2turn23view1
Script Python: parse header + extrair + gerar manifest.json
(Projetado para ser incorporado no SKILL.md como “script canônico”.)
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import hashlib
import json
import os
import struct
from dataclasses import dataclass
from typing import Optional, Dict, Any
NES_MAGIC = b"NES\x1A"
@dataclass
class NesHeader:
format: str # "iNES" | "NES2.0" | "unknown"
prg_bytes: int
chr_bytes: int
trainer_bytes: int
mapper: int
submapper: Optional[int]
mirroring: str # "horizontal" | "vertical" | "four-screen" | "mapper-controlled" | "unspecified"
battery: bool
def sha256_file(path: str) -> str:
h = hashlib.sha256()
with open(path, "rb") as f:
for chunk in iter(lambda: f.read(1024 * 1024), b""):
h.update(chunk)
return h.hexdigest()
def parse_nes_header(h: bytes, rom_size: int) -> NesHeader:
if len(h) != 16:
raise ValueError("Header deve ter 16 bytes")
if h[0:4] != NES_MAGIC:
raise ValueError("Magic inválido: esperado 'NES\\x1A'")
flags6 = h[6]
flags7 = h[7]
is_nes20 = (flags7 & 0x0C) == 0x08 # NES 2.0 identifier (bit2=0, bit3=1)
trainer_bytes = 512 if (flags6 & 0x04) else 0
battery = bool(flags6 & 0x02)
# Mirroring (hard-wired bits); note: alguns mappers ignoram bit 0 e podem controlar via registrador
four_screen = bool(flags6 & 0x08)
if four_screen:
mirroring = "four-screen"
else:
mirroring = "vertical" if (flags6 & 0x01) == 0 else "horizontal"
prg_lsb = h[4]
chr_lsb = h[5]
if is_nes20:
# NES 2.0: mapper 12-bit + submapper 4-bit
mapper_low = (flags6 >> 4) & 0x0F
mapper_mid = (flags7 >> 4) & 0x0F
mapper_hi = h[8] & 0x0F
submapper = (h[8] >> 4) & 0x0F
mapper = mapper_low | (mapper_mid << 4) | (mapper_hi << 8)
size_msb = h[9]
prg_msb_nibble = size_msb & 0x0F
chr_msb_nibble = (size_msb >> 4) & 0x0F
# Forma simples (0..E): unidades 16 KiB (PRG) e 8 KiB (CHR)
# Forma exponencial (F): aqui marcamos como "unspecified" para automatização mínima.
if prg_msb_nibble <= 0x0E:
prg_units_16k = (prg_msb_nibble << 8) | prg_lsb
prg_bytes = prg_units_16k * 16 * 1024
else:
prg_bytes = -1 # unspecified (expo-mult)
if chr_msb_nibble <= 0x0E:
chr_units_8k = (chr_msb_nibble << 8) | chr_lsb
chr_bytes = chr_units_8k * 8 * 1024
else:
chr_bytes = -1 # unspecified (expo-mult)
fmt = "NES2.0"
else:
# iNES: mapper é composto de nibbles em flags6/flags7
mapper = ((flags6 >> 4) & 0x0F) | (flags7 & 0xF0)
submapper = None
prg_bytes = prg_lsb * 16 * 1024
chr_bytes = chr_lsb * 8 * 1024
fmt = "iNES"
# Heurística recomendada: se bytes 12-15 não forem zero e não for NES2.0,
# o header pode conter lixo ("DiskDude!") e o mapper pode estar corrompido.
# Aqui apenas marcamos para inspeção manual.
if h[12:16] != b"\x00\x00\x00\x00":
fmt = "iNES (warning: bytes12-15 nonzero)"
# Sanidade mínima: tamanho esperado não pode exceder rom_size
# (Não falha automaticamente porque pode haver Misc ROM em NES 2.0.)
return NesHeader(
format=fmt,
prg_bytes=prg_bytes,
chr_bytes=chr_bytes,
trainer_bytes=trainer_bytes,
mapper=mapper,
submapper=submapper,
mirroring=mirroring,
battery=battery,
)
def extract(rom_path: str, out_dir: str) -> Dict[str, Any]:
os.makedirs(out_dir, exist_ok=True)
with open(rom_path, "rb") as f:
data = f.read()
header = data[:16]
h = parse_nes_header(header, len(data))
sha256 = hashlib.sha256(data).hexdigest()
# Offsets
off = 16
trainer = data[off:off + h.trainer_bytes]
off += h.trainer_bytes
prg = b""
chr_ = b""
if h.prg_bytes >= 0:
prg = data[off:off + h.prg_bytes]
off += h.prg_bytes
else:
# unspecified sizes (expo-mult): não extraímos automaticamente
pass
if h.chr_bytes >= 0:
chr_ = data[off:off + h.chr_bytes]
off += h.chr_bytes
else:
pass
# Write outputs
paths = {}
with open(os.path.join(out_dir, "header.bin"), "wb") as f:
f.write(header)
paths["header"] = "header.bin"
if h.trainer_bytes:
with open(os.path.join(out_dir, "trainer.bin"), "wb") as f:
f.write(trainer)
paths["trainer"] = "trainer.bin"
if prg:
with open(os.path.join(out_dir, "prg.bin"), "wb") as f:
f.write(prg)
paths["prg"] = "prg.bin"
else:
paths["prg"] = None # unspecified
if chr_:
with open(os.path.join(out_dir, "chr.bin"), "wb") as f:
f.write(chr_)
paths["chr"] = "chr.bin"
else:
paths["chr"] = None # chr-rom ausente ou unspecified
manifest = {
"rom": os.path.basename(rom_path),
"sha256": sha256,
"header_format": h.format,
"mapper": h.mapper,
"submapper": h.submapper,
"prg_bytes": h.prg_bytes,
"chr_bytes": h.chr_bytes,
"trainer_bytes": h.trainer_bytes,
"battery": h.battery,
"mirroring": h.mirroring,
"paths": paths,
"trailing_bytes_after_chr": len(data) - off if off <= len(data) else None,
"note": "Valores -1/None indicam unspecified e exigem inspeção manual.",
}
with open(os.path.join(out_dir, "manifest.json"), "w", encoding="utf-8") as f:
json.dump(manifest, f, indent=2, ensure_ascii=False)
return manifest
if __name__ == "__main__":
import argparse
ap = argparse.ArgumentParser()
ap.add_argument("rom", help="Arquivo .nes (iNES ou NES 2.0)")
ap.add_argument("--out", default="out", help="Diretório de saída")
args = ap.parse_args()
m = extract(args.rom, args.out)
print(json.dumps(m, indent=2, ensure_ascii=False))Este script implementa a identificação NES 2.0 via (header[7] & 0x0C) == 0x08 e respeita o trainer de 512 bytes quando indicado, conforme a especificação NES 2.0; também segue a advertência do iNES sobre bytes 12–15 “lixo” que pode poluir o mapper (“DiskDude!”), marcando como warning. citeturn23view0turn23view1turn27view2
Disassembly automatizado com retrodisasm + verificação
# Disassembly básico (auto-detecta .nes)
retrodisasm -o out/game.asm game.nes
# Se você tem um CDL gerado por emulador:
retrodisasm -cdl game.cdl -o out/game.asm game.nes
# Verificação "bit-perfect" (requer assembler no PATH)
retrodisasm -verify -o out/game.asm game.nesO suporte a CDL e -verify é documentado no guia NES do retrodisasm, incluindo a necessidade de ter um assembler (ca65/asm6/nesasm) disponível. citeturn22view0
Disassembly com da65 (cc65) orientado a “info file”
(Exemplo mínimo de abordagem: mapear um PRG “raw” em base $8000 e iterar refinando ranges/labels; detalhes exatos de opções variam e devem ser consultados no guia do da65.)
# Exemplo conceitual:
# 1) gerar prg.bin via script de extração
# 2) criar um arquivo .info (ranges/labels/segments)
# 3) rodar da65 com info file para melhorar saída
da65 --helpA utilidade central do da65 é aceitar “user-supplied information” para melhorar resultados, e o uso iterativo (rodar -> entender -> adicionar labels -> repetir) é recomendado inclusive em discussões técnicas sobre desassembly NES. citeturn10search0turn10search24
Para um exemplo “end-to-end” sem riscos de copyright comercial, use o pacote “chase nes C example code”, publicado com CC0 1.0 e referência explícita a “Public Domain”, contendo “Example code and ROM”. citeturn12view0turn21search6turn21search15
Passo a passo (automatizável)
# 1) Baixar (URL em código por política de links)
curl -L -o chase.zip "https://archive.org/download/chase_nes_example_code/chase.zip"
unzip -l chase.zip
# 2) Extrair e localizar o .nes
unzip -o chase.zip -d chase_src
find chase_src -maxdepth 3 -type f \( -iname "*.nes" -o -iname "*.NES" \) -printO item no Internet Archive é rotulado como CC0 1.0 Universal e declara que inclui ROM e código. citeturn12view0
Parsing e extração
python3 nes_extract.py chase_src/<arquivo>.nes --out out_chase
cat out_chase/manifest.jsonDesassembly e verificação por recompilação
# Instale cc65 (para ter ca65/ld65) - método de instalação é SO-dependente (unspecified)
retrodisasm -verify -o out_chase/chase.asm chase_src/<arquivo>.nes
# Alternativamente, disassemble e recompile manualmente (exemplo do guia):
ca65 out_chase/chase.asm -o out_chase/chase.o
ld65 out_chase/chase.o -t nes -o out_chase/rebuilt.nes
# Compare hashes
sha256sum chase_src/<arquivo>.nes out_chase/rebuilt.nesO guia NES do retrodisasm explicita o fluxo de reassembly com ca65/ld65 e a opção -verify para automatizar a checagem “bit-perfect”. citeturn22view0turn10search19
Como validação comportamental (além de byte-equality), rode a ROM original e a recompilada em emuladores com debugger. Para separar código/dados e melhorar a qualidade do desassembly, use Code/Data Logger; o FCEUX descreve a motivação (disassembler não distingue código/dados) e o fluxo de logging/salvamento de .cdl. citeturn4search2turn5search12
Se o projeto tiver bank switching, considere também .nl (labels por banco) conforme o formato descrito na documentação do FCEUX, pois ajuda a manter coerência de símbolos entre bancos. citeturn4search38
A seguir estão (1) um esqueleto mínimo e (2) um SKILL.md completo e prático, pronto para copiar. O conteúdo reflete as especificações iNES/NES 2.0 (detecção, trainer, tamanhos), o papel central de vetores $FFFA/$FFFC/$FFFE, e o uso de CDL/trace para separar código/dados e lidar com bank switching. citeturn23view0turn23view1turn26search0turn4search2turn22view0
# SKILL: Desassembly NES (.nes)
## Objetivo
## Permissões e restrições
## Ferramentas
## Pipeline
## Mappers suportados
## Heurísticas e rotulagem
## Saídas e verificação# SKILL: Desassembly end-to-end de ROMs NES (.nes)
## Objetivo
Desassemblar uma ROM NES (.nes) de ponta a ponta, produzindo:
- ASM reassemblável (preferencialmente bit-identical quando possível)
- Artefatos de análise (JSON de metadados, símbolos/labels, mapas de bancos)
- Opcional: HTML anotado navegável
- Evidência de validação (hashes, logs, execução em emuladores)
## Permissões obrigatórias
Você DEVE ter:
- Permissão de leitura do arquivo .nes alvo
- Permissão de escrita em um diretório de trabalho (workspace) para gerar PRG/CHR/ASM/JSON/logs
- Permissão de executar ferramentas locais (python, disassembler, assembler, emulador)
- Permissão de rede: OPCIONAL (para baixar ROMs livremente licenciadas e consultar documentação)
Restrições:
- NÃO baixe nem distribua ROMs comerciais protegidas por copyright sem autorização.
- Prefira ROMs CC0/domínio público/open source para exemplos e testes.
## Notas legais/éticas
- Se a ROM não for explicitamente livre (CC0/MIT/etc.), trate como protegida por copyright e NÃO redistribua.
- É aceitável publicar: scripts, arquivos de símbolos, “info files”, patches, documentação e hashes; evite publicar a ROM.
- Se houver dúvida de licenciamento, marque como UNSPECIFIED e não prossiga com distribuição.
## Pré-requisitos (SO, ferramentas, libs)
SO recomendado: Linux (ou WSL2 no Windows). Outros: macOS (unspecified).
Ferramentas mínimas:
- python3 (>= 3.10 recomendado)
- dd, xxd/hexdump, sha256sum (ou equivalentes)
Ferramentas recomendadas (escolha 1 por categoria):
- Disassembler/tracing: retrodisasm (recomendado)
- Disassembler iterativo: da65 (cc65)
- RE interativo: Ghidra + extensão GhidraNes (ou IDA Pro)
- Emulador com debugger/logs: FCEUX e/ou Mesen 2
Bibliotecas Python:
- apenas stdlib (hashlib, struct, json). Extras são opcionais (unspecified).
## Pipeline end-to-end (passo a passo)
### 1) Preparar workspace
Criar árvore:
- work/
- input/ (roms)
- out/<romname>/
- header.bin
- trainer.bin (se houver)
- prg.bin
- chr.bin
- manifest.json
- disasm/
- labels/
- logs/
Sempre registre: data/hora, hash sha256 da ROM.
### 2) Validar arquivo .nes
- Ler 16 bytes iniciais.
- Verificar magic "NES\x1A". Se falhar: ABORTAR (não é .nes iNES/NES2.0).
- Calcular sha256 da ROM e salvar no manifest.
### 3) Detectar iNES vs NES 2.0
Regras:
- NES 2.0 se (header[7] & 0x0C) == 0x08.
- Caso não NES2.0 e bytes 12-15 não sejam 0, marcar WARNING (header possivelmente poluído, ex.: “DiskDude!”).
Registrar no manifest:
- header_format: "NES2.0" | "iNES" | "iNES (warning...)"
### 4) Parse do header (tamanhos, mapper, mirroring, trainer)
Extrair:
- trainer_flag, battery_flag
- mirroring hard-wired:
- se four-screen bit set => "four-screen"
- senão bit0 => "vertical" (1) ou "horizontal" (0)
- OBS: muitos mappers ignoram bit0 e controlam via registrador; tratar isso no passo de mapper.
- PRG size:
- NES2.0: se MSB nibble != 0xF => PRG=( (byte9_low<<8)|byte4 ) * 16KiB
- iNES: PRG=(byte4)*16KiB
- CHR size:
- NES2.0: se MSB nibble != 0xF => CHR=( (byte9_high<<8)|byte5 ) * 8KiB
- iNES: CHR=(byte5)*8KiB
Se qualquer tamanho estiver em forma expoente-multiplicador (nibble==0xF):
- Marque o tamanho como UNSPECIFIED e exija inspeção manual (não extraia automaticamente).
Mapper:
- NES2.0: mapper = low4 (byte6>>4) + mid4 (byte7>>4)<<4 + hi4 (byte8_low)<<8
submapper = (byte8>>4)
- iNES: mapper = (byte6>>4) | (byte7 & 0xF0)
Registrar mapper/submapper no manifest.
### 5) Extrair trainer/PRG/CHR
- trainer: 512 bytes se flag; salvar trainer.bin.
- PRG: salvar prg.bin (concatenação linear).
- CHR: salvar chr.bin (se CHR-ROM > 0).
Se CHR-ROM == 0:
- Trate como CHR-RAM. Não há chr.bin. Marque como UNSPECIFIED (a menos que NES2.0 declare CHR-RAM em outro campo).
### 6) Construir “bank map” inicial (por mapper)
Objetivo: definir como traduzir endereços CPU/PPU para offsets no PRG/CHR, incluindo bank switching.
Regra universal:
- Vetores estão em CPU $FFFA-$FFFF, logo o banco que ocupa $C000-$FFFF (ou a janela final equivalente) contém NMI/RESET/IRQ vectors.
Implementar suporte mínimo aos mappers:
- Mapper 0 (NROM):
- PRG 16KiB: espelhado em $8000-$BFFF e $C000-$FFFF
- PRG 32KiB: $8000-$FFFF linear
- Sem bank switching
- Mapper 1 (MMC1):
- Modelar registros (control, chr0, chr1, prg) e escrita serial (5 writes) + reset do shift via bit7=1
- Modo comum: último banco fixo em $C000-$FFFF; banco 16KiB em $8000-$BFFF comutável
- Mirroring pode ser controlado pelo mapper (não confiar em bit0 do header)
- Mapper 2 (UxROM/UNROM):
- Banco 16KiB comutável em $8000-$BFFF; último banco fixo em $C000-$FFFF (comum)
- Writes para selecionar banco (atenção a bus conflicts)
- Mapper 3 (CNROM):
- PRG tipicamente fixo (32KiB em $8000-$FFFF)
- CHR comutável em bancos de 8KiB
- Mapper 4 (MMC3):
- PRG em bancos de 8KiB em janelas (bank select + bank data)
- CHR em bancos de 1KiB/2KiB
- IRQ baseado em contador (scanline/A12); exige observar writes em registradores e eventos IRQ
- Mapper 7 (AxROM):
- PRG 32KiB comutável em $8000-$FFFF
- Mirroring single-screen selecionável pelo mapper
- Mapper 9 (MMC2):
- CHR troca por latch (bancos 4KiB), acionado por acessos PPU a tiles especiais (FD/FE)
- Necessário modelar CHR latches + bancos selecionados em registradores
- Mapper 10 (MMC4):
- Similar a MMC2 em latch CHR; diferenças em PRG banking
- Mapper 66 (GxROM/GNROM):
- PRG 32KiB comutável e CHR 8KiB comutável via registrador
Se mapper não estiver entre os suportados:
- Marcar mapper como UNSPECIFIED e seguir o procedimento de detecção/continuação (ver seção “Mapper desconhecido”).
### 7) Localizar vetores e definir entrypoints
- A partir do bank map: localizar os 6 bytes finais de vetores (NMI/RESET/IRQ-BRK).
- Ler little-endian e criar labels: NMI_Handler, Reset_Handler, IRQ_Handler.
- Colocar Reset_Handler como entrypoint principal de disassembly.
### 8) Desassembly (estático + dinâmico)
Estratégia:
A) Estático (primeira passada)
- Para cada banco PRG:
- Definir ORG lógico (ex.: $8000 ou $C000 conforme janela típica do mapper)
- Fazer disassembly recursivo a partir dos entrypoints que apontem para esse banco
- Marcar bytes não atingidos como DATA por padrão
B) Dinâmico (segunda passada, recomendado)
- Rodar ROM em emulador com debugger e gerar:
- CDL (code/data logger)
- opcional: trace log
- Reexecutar disassembler com CDL (ex.: retrodisasm -cdl)
- Atualizar fronteiras code/data e labels.
### 9) Heurísticas práticas (código vs dados)
Use TODAS, com prioridade:
1) CDL: se byte foi EXECUTADO => código.
2) Reachability: bytes atingidos por fluxo (JSR/JMP/branches) a partir de entrypoints => provável código.
3) Padrões de tabela:
- sequência de words little-endian apontando para faixa $8000-$FFFF => provável jump table/pointer table
- tabelas de tiles/atributos normalmente são acessadas via índices e copiadas para PPU; observe writes em $2006/$2007.
4) Acesso a registradores:
- writes frequentes em $2000-$2007 => rotinas PPU
- writes em $4000-$4017 => rotinas APU/I-O
5) Bytes “impossíveis” como opcodes válidos em sequência longa NÃO garantem código (6502 é denso). Use apenas como sinal fraco.
### 10) Estratégia de rotulagem e anotação
- Pré-carregar labels de hardware:
- PPU: $2000..$2007 (PPUCTRL, PPUMASK, PPUSTATUS, OAMADDR, OAMDATA, PPUSCROLL, PPUADDR, PPUDATA)
- APU/I-O: $4000..$4017 (pelo menos $4014 OAMDMA, $4016/$4017 inputs, $4015 status)
- Rotular RAM (zero page / stack / WRAM) conforme for identificado (ex.: “player_x”, “scroll_y”).
- Para cada banco: separar labels por namespace (ex.: Bank00_Reset, Bank03_CopyTiles).
- Anotar TODAS as writes em registradores de mapper (endereços $8000+ típicos) com nome do registrador e significado deduzido.
### 11) Output formats (ASM, HTML, JSON)
Gerar:
- ASM:
- compatível com ca65 (recomendado) ou asm6
- incluir CHR via .incbin chr.bin quando CHR-ROM existir
- organizar por bancos (1 arquivo por banco + arquivo “main” que inclui)
- JSON:
- manifest.json (header + offsets + hashes)
- symbols.json (lista de labels: nome, endereço CPU, banco, tipo, comentário)
- banks.json (mapeamento janela->banco e eventos de troca conhecidos)
- HTML anotado:
- Converter ASM para HTML com realce (ex.: pygments) OU exportar listing do framework interativo (Ghidra)
- Incluir links cruzados para labels/endereços.
### 12) Testes e validação
Validação mínima:
- Se o toolchain permitir: recompilar e comparar hash/bytes do .nes (bit-identical).
- Rodar ROM original e recompilada em emuladores (pelo menos 2 se for MMC3/IRQ sensível).
- Se houver divergência visual/IRQ: revisar modelagem de mapper/IRQ e repetir com mais cobertura CDL.
- Registrar resultados em out/<rom>/validation.txt (hashes, comandos, emuladores usados).
## Mapper desconhecido: como detectar e prosseguir
Se o header indicar mapper raro/desconhecido ou suspeito:
1) Verificar se header está “poluído” (bytes 12-15 != 0 e não NES2.0). Se sim, trate mapper como SUSPEITO.
2) Calcular hashes (sha256/sha1/crc32) de PRG/CHR e procurar em bancos de dados/metadata (exige rede; se não houver, marcar UNSPECIFIED).
3) Inspecionar dinamicamente:
- Rodar em emulador com breakpoints em writes $8000-$FFFF e logar padrões
- Identificar esquema de registradores (endereços e bits usados)
4) Tentar classificar por “assinatura”:
- Se troca PRG em 16KiB e último banco fixo: candidato UxROM/MMC1-like
- Se troca PRG 32KiB inteira: candidato AxROM/GxROM-like
- Se existir IRQ de scanline e writes em registradores tipo MMC3: candidato MMC3-like
5) Se não for possível classificar:
- ABORTAR desassembly “correto” e produzir apenas:
- extração PRG/CHR
- desassembly “raw” por banco estático sem banking (marcar como UNSPECIFIED e não confiável)
- relatório de evidência (logs de writes, traces)
## Critérios de “concluído”
Considere concluído quando:
- manifest.json completo (sem campos críticos UNSPECIFIED, exceto quando inevitável)
- mapeamento de bancos documentado
- vetores e handlers identificados e rotulados
- ASM compila e roda (idealmente bit-identical)
- labels essenciais de PPU/APU/I-O e registradores do mapper anotados
- validação registrada
## Referências rápidas (links)
- NESdev iNES e NES 2.0
- NESdev CPU memory map / CPU interrupts / PPU registers / APU registers
- retrodisasm docs/nes.md
- FCEUX Code/Data Logger e NL files format
- cc65 da65 users guideObservação final sobre campos “unspecified” no SKILL.md: o uso explícito de UNSPECIFIED (ou -1/None) é intencional para impedir que o agente “invente” valores quando o header usa notações avançadas (expo-mult no NES 2.0), quando o mapper é incerto, ou quando CHR-RAM/PRG-RAM não está expressa de forma confiável. A própria especificação iNES documenta cenários de header não confiável (ex.: bytes 7–15 contaminados) e recomenda mascarar ou rejeitar. citeturn23view0turn23view1turn27view0turn27view1