💾 Archived View for tilde.pink › ~nagi › posts › nes-utilities.gmi captured on 2023-07-22 at 17:30:59. Gemini links have been rewritten to link to archived content

View Raw

More Information

⬅️ Previous capture (2023-06-14)

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

NES library

The library contains useful features like a disassembler, graphic extraction (to PNG) from CHR ROM and a Game Genie encoder/decoder.

Rust - Nes

By Théo Bori, edited 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.

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

*Source*

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