Skip to content

Instantly share code, notes, and snippets.

@celsowm
Created March 10, 2026 20:36
Show Gist options
  • Select an option

  • Save celsowm/744ed9f43343916eb871d75f35f76a6a to your computer and use it in GitHub Desktop.

Select an option

Save celsowm/744ed9f43343916eb871d75f35f76a6a to your computer and use it in GitHub Desktop.
nes-skill.md

SKILL.md para desassembly end-to-end de ROMs NES (.nes) por agente autônomo

Resumo executivo

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. citeturn23view0turn23view1turn7search12

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. citeturn4search2turn10search0turn10search24turn22view0

Fundamentos técnicos indispensáveis

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. citeturn23view1turn27view0turn27view1turn27view2

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. citeturn26search0turn26search1turn6search14turn26search3

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"]
Loading

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. citeturn26search0turn26search1turn26search22

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). citeturn6search4turn6search5

Pré-requisitos e ambiente

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, -cdl e suporte experimental a mappers com banking. citeturn22view0
  • 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). citeturn10search0turn10search24
  • 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. citeturn19search0turn19search4turn10search2turn19search2
  • 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). citeturn4search2turn4search38turn5search0turn5search22
  • 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). citeturn19search1turn10search1turn10search14

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). citeturn20search0turn20search1

Permissões, legalidade e ética

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. citeturn21search0turn21search3turn21search10

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. citeturn10search0turn10search18

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. citeturn24search29

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. citeturn12view0

Workflow end-to-end

Este é o encadeamento técnico que o SKILL.md deve impor ao agente: do arquivo bruto até outputs verificáveis.

Validação de arquivo e parsing do cabeçalho

O agente deve:

  1. Validar “magic bytes”: "NES\x1A" no início (iNES e NES 2.0 compartilham isso). citeturn23view1
  2. 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!”). citeturn23view0turn23view1
  3. 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. citeturn27view2turn23view1
  1. Extrair mapper/submapper (NES 2.0) e mapper (iNES) e registrar mirroring/hardwired vs mapper-controlled quando aplicável. citeturn23view1turn26search6turn23view0
  2. 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. citeturn23view1turn27view2

Extração PRG/CHR e construção de um “mapa de bancos”

Após parsing, o agente deve materializar:

  • prg.bin (PRG-ROM concatenado) e chr.bin (CHR-ROM, se existir; se CHR-ROM for 0, tratar como CHR-RAM e marcar chr.bin como 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. citeturn27view1turn26search14
  • Um manifest.json com: mapper, submapper (ou null), PRG/CHR sizes, flags (trainer/battery/mirroring), hash (sha256) e offsets/paths dos blobs.

Vetores, endereçamento e pontos de entrada

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). citeturn26search0turn26search1
  • Tratar IRQ e BRK como compartilhando vetores no NES (IRQ/BRK vector). citeturn26search0turn26search1
  • 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). citeturn6search14turn26search3turn26search6
  • Usar referências de modos de endereçamento 6502 para detectar acessos indiretos e tabelas de ponteiros (little-endian). citeturn6search5turn6search4

Separação código vs dados e iteração

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. citeturn4search2
  • 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. citeturn22view0
  • Se usar da65, iterar com “info file”: rodar, interpretar trechos, adicionar labels/ranges/segmentos, repetir. citeturn10search0turn10search24

Mappers e bank switching

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. citeturn25view0turn8search10

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. citeturn7search0turn26search6
  • 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. citeturn7search1turn7search5turn9search30
  • 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. citeturn7search6turn9search26
  • 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. citeturn8search0turn8search4
  • 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. citeturn7search3turn7search20
  • 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. citeturn8search1turn9search26turn8search9
  • 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. citeturn8search2turn8search13turn8search6
  • 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. citeturn8search3turn8search7
  • Mapper 66 (GxROM/GNROM/MHROM): troca PRG em 32 KiB e CHR em 8 KiB via registrador; é um “discrete logic mapper” comum. citeturn9search0turn9search26turn9search3

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. citeturn26search6turn26search2turn23view1turn23view0

IRQ e validação por emulador

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). citeturn26search0turn7search3
  • Usar emuladores de alta observabilidade (debugger + trace + CDL). FCEUX documenta CDL e naming de arquivos .nl com convenção por banco (importante em jogos com bank swapping). citeturn4search2turn4search38
  • 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. citeturn7search20turn5search22

Ferramentas e automação

Comparativo de ferramentas

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 citeturn18view0turn22view0
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 citeturn10search0turn10search19turn10search24
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 citeturn19search0turn19search2turn19search4
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.) citeturn10search2
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 citeturn19search25turn20search39
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) citeturn19search1turn10search14
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 citeturn4search2turn4search38turn5search1turn5search12
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 citeturn5search0turn5search22
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 citeturn20search0turn20search1
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 citeturn4search4

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). citeturn23view0turn10search2

Diagramas de bank switching

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"]
Loading

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”. citeturn4search2turn22view0

Scripts e comandos (bash + Python)

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=none

O 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. citeturn27view2turn23view1

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. citeturn23view0turn23view1turn27view2

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.nes

O suporte a CDL e -verify é documentado no guia NES do retrodisasm, incluindo a necessidade de ter um assembler (ca65/asm6/nesasm) disponível. citeturn22view0

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 --help

A 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. citeturn10search0turn10search24

Exemplo público e validação

ROM de domínio público (CC0): “chase” (Shiru)

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”. citeturn12view0turn21search6turn21search15

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" \) -print

O item no Internet Archive é rotulado como CC0 1.0 Universal e declara que inclui ROM e código. citeturn12view0

Parsing e extração

python3 nes_extract.py chase_src/<arquivo>.nes --out out_chase
cat out_chase/manifest.json

Desassembly 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.nes

O guia NES do retrodisasm explicita o fluxo de reassembly com ca65/ld65 e a opção -verify para automatizar a checagem “bit-perfect”. citeturn22view0turn10search19

Validação por emulação e cross-check

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. citeturn4search2turn5search12

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. citeturn4search38

Modelo mínimo de SKILL.md

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. citeturn23view0turn23view1turn26search0turn4search2turn22view0

Estrutura mínima (sugestão)

# SKILL: Desassembly NES (.nes)

## Objetivo
## Permissões e restrições
## Ferramentas
## Pipeline
## Mappers suportados
## Heurísticas e rotulagem
## Saídas e verificação

SKILL.md completo (pt-BR)

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

Observaçã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. citeturn23view0turn23view1turn27view0turn27view1

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment