|
|
NES Library |
|
|
|
2022-09-22 |
|
|
|
Last edit: 2022-09-22 |
|
|
|
--------------------- |
|
|
|
|
|
|
|
I wanted to learn more about how the NES works, so I consulted the resources on |
|
|
 |
Nesdev Wiki (https://www.nesdev.org) |
|
|
|
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 (https://github.com) |
|
|
|
|
|
|
|
## Tilesets dumping |
|
|
|
|
|
|
|
A NES program include a 16 bytes header, we can represent it like this: |
|
|
|
|
|
|
|
```rust |
|
|
|
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 |
|
|
|
```rust |
|
|
|
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 |
|
|
|
```rust |
|
|
|
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: |
|
|
|
|
|
|
|
```asm6502 |
|
|
|
; 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 |
|
|
|
|
|
|
|
- *The Game Genie is a enhancement cart for the NES designed by Camerica and distributed by Galoob and Cameric* |
|
|
|
- *it provides a simple interface to enter up to three cheat codes* |
|
|
|
|
|
|
|
*Source: |
|
|
 |
NesDev Wiki (https://www.nesdev.org) |
|
|
|
* |
|
|
|
|
|
|
|
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: |
|
|
|
|
|
|
|
```text |
|
|
|
$ nes-utils-cli --code SZEPSVSE |
|
|
|
Address 0x1e05 |
|
|
|
Value 0xad |
|
|
|
Compare value 0x8d |
|
|
|
``` |
|
|
|
|
|
|
|
## Links |
|
|
|
|
|
|
 |
https://github.com/theobori/nes-utils (https://github.com) |
|
|
|
|
|