💾 Archived View for tilde.pink › ~nagi › nes-utilities.gmi captured on 2024-02-05 at 10:20:02. Gemini links have been rewritten to link to archived content

View Raw

More Information

⬅️ Previous capture (2023-11-04)

🚧 View Differences

-=-=-=-=-=-=-

NES Library

2022-09-22

I wanted to learn more about how the NES works, so I consulted the resources on

Nesdev Wiki

and I discovered that from an NES program (.nes file) you could do cool stuff like extracting some tilesets from the CHR ROM (Character Read-Only Memory) or a PRNG (Pseudorandom Number Generator) with the instructions interpreted by the NES.

CLI

I made an example binary that can run all the main features of the nes-utils library to make it more convenient to test.

https://github.com/theobori/nes-utils-cli

Tilesets dumping

A NES program include a 16 bytes header, we can represent it like this:

const NES_HEADER_FIELDS_ORDER: [(&str, usize, usize); 9] = {
    [
        ("magic", 0, 4),
        ("len_prg_rom", 4, 1),
        ("len_chr_rom", 5, 1),
        ("f6", 6, 1),
        ("f7", 7, 1),
        ("len_prg_ram", 8, 1),
        ("f9", 9, 1),
        ("f10", 10, 1),
        ("reserved", 11, 5)
    ]
};

In this example, the field magic is at position 0 and has a lenght of 4 bytes.

The CHR ROM is linked to the PPU (Picture Process Unit), it means if the CHR ROM length is superior to zero, it contains some graphics.

There are not all the tilesets of the game in the CHR, you can find 0x2000 * len_chr_rom bytes in it, with two banks of 0x1000 bytes, which makes two images of 8 kilobytes, there is one bank for one image.

So, let's take the example of Kirby's Adventure, below are the graphical data of the CHR rom:

There are only four colors because the rest is calculated at runtime. Below are the main parts of code that generate this 2 images:

Colors

type Rgb = (u8, u8, u8);

const BLACK_PIXEL: Rgb = (0, 0, 0);
const COLOR_SCHEME: [Rgb; 4] = [
    (0, 0, 0),
    (126, 126, 126),
    (189, 189, 189),
    (255, 255, 255)
];

fn bits_to_rgb(left: u8, right: u8) -> Rgb {
    let color = right << 1 | left;

    COLOR_SCHEME[color as usize]
}

Bytes to RGB

pub fn fill_with_bank(&mut self, bank: &[u8]) {
    let mut mem_x = 0;
    let mut mem_y = 0;

    for byte in (0..bank.len()).step_by(16) {
        for y in 0..8 {
            if mem_x >= NesImage::W {
                mem_y += NesImage::TILE_H;
                mem_x = 0;
            }

            let lower = bank[byte + y];
            let upper = bank[byte + y + 8];

            for bit in 0..8 {
                let pixel = bits_to_rgb(
                    lower >> (7 - bit) & 1,
                    upper >> (7 - bit) & 1
                );
                self.put_pixel(bit + mem_x, y + mem_y, pixel);
            }
        }
        mem_x += NesImage::TILE_W;
    }
}

Disassembling

The bytecodes are in the PGR ROM (Program Read-Only Memory) of size 0x4000 * len_chr_rom (value in the header) bytes.

So, for Kirby's Adventure, the assembler code should looks like:

; Mapped registers

SQ1_VOL equ $4000
SQ1_SWEEP equ $4001
PPUMASK equ $2001
SQ1_LO equ $4002
SQ1_HI equ $4003
SQ2_SWEEP equ $4005
...

; Header

hex 4e 45 53 1a
hex 20
hex 20
hex 43
hex 00
hex 00
hex 00
hex 00
hex 00 00 00 00 00

; PRG ROM

and ($0f, x)      ; 21 0f
slo $280f         ; 0f 0f 28
slo $2121         ; 0f 21 21
jsr $2020         ; 20 20 20
jsr $0221         ; 20 21 02
slo $200f         ; 0f 0f 20
and ($0f, x)      ; 21 0f
...

Game Genie code

NesDev Wiki

Obviously I didn't buy the enhancement cart, so I can't use the available codes. But on some emulators it is possible to access the memory and inject values.

So I made a feature that decodes the Game Genie codes.

For example for Kirby's Adventure, it is possible to have infinite energy with the following code:

$ nes-utils-cli --code SZEPSVSE    
Address 0x1e05
Value 0xad
Compare value 0x8d

Links

https://github.com/theobori/nes-utils