RockNES savestate format http://rocknes.web.fc2.com/ ------------------------ ©1998-2021 Zepper CONTACT ME! - Website: http://rocknes.web.fc2.com - e-mail: fx3rnes@hotmail.com - ICQ: 12292568 ------------------------ Last update: 03/15/2021 ------------------------ * Missing mapper context information. 1. Introduction =============== - RockNES uses its own format to save progress in any game. Files have the extension RSx, and 'x' is an hexanumber from 0h to Fh (RS0, RS1, RS2... RSE, RSF), so you can save up to 15 files for each game. - File header is present. First 4 bytes should be 52|53|58|07 (RSX7). Last version is 7. 2. File format ============== - The header is composed 8 bytes: the string RSX7 (4 bytes long) followed by the file size (4 bytes long too). - Each data block starts with a string of 3 bytes (tagged blocks). They're saved in the following order, but this is not required for file loading. *JOYPADS [IOD = Input and Output Device] *CPU *PPU *APU (+VRC6 if present) *MAPPER *PPU extended save *NSF (if present) ---------------------------------------------------- [TAG id] - 4 bytes long, data starts in the byte 5 ---------------------------------------------------- TAG | HEXA DATA | DESCRIPTION ======+============+=============== IOD0 | 0x494F4400 | JOYPAD player 1 data .strobe flag .joy bits (last data on the bus) .ctr_joypad (joypad data) .number of reads (up to 8) TAG | HEXA DATA | DESCRIPTION ======+============+=============== IOD1 | 0x494F4401 | JOYPAD player 2 data .joy bits (last data on the bus) .ctr_joypad (joypad data) .number of reads (up to 8) TAG | HEXA DATA | DESCRIPTION ======+============+=============== CPU0 | 0x43505500 | CPU context 1 byte for each register, in order: A, X, Y, P, S .low byte of PC .high byte of PC .pending IRQ .pending NMI .IRQ request .NMI request - If there's PRG ROM data mapped into any 8K banks, the page number is saved (1 byte) Usually, the last 8k is always saved. TAG | HEXA DATA | DESCRIPTION ======+============+=============== PRG3 | 0x43505503 | PRG ROM bank at $6000-$7FFF PRG4 | 0x43505504 | PRG ROM bank at $8000-$9FFF PRG5 | 0x43505505 | PRG ROM bank at $A000-$BFFF PRG6 | 0x43505506 | PRG ROM bank at $C000-$DFFF PRG7 | 0x43505507 | PRG ROM bank at $E000-$FFFF TAG | HEXA DATA | DESCRIPTION ======+============+=============== RAM0 | 0x52414D00 | RAM data at $0000-$07FF WRM0 | 0x57524D00 | RAM data at $6000-$7FFF (always $00 if there's PRG ROM) TAG | HEXA DATA | DESCRIPTION ======+============+=============== PPU0 | 0x50505500 | PPU context .OAM primary $100 bytes long .last value written to PPU $2000 .last value written to PPU $2001 .last value latched at PPU $2007 (reads) .low byte of PPU address (aka loopy_v) .high byte of PPU address (aka loopy_v) .low byte of temp PPU address (aka loopy_t) .high byte of temp PPU address (aka loopy_t) .PPU fine X scroll (low 3 bits of $2005, first write) .PPU toggle (controls writes to $2005/6) .PPU $2003 sprite address TAG | HEXA DATA | DESCRIPTION ======+============+=============== APU0 | 0x41505500 | APU context (sound) The last value written to the registers, saved in this order: .$4015 ^ $FF .$4017 .$4000,$4001,$4002,$4003,$4004,$4005,$4006,$4007 .$4008,$4009,$400A,$400B,$400C,$400D,$400E,$400F .$4010,$4011,$4012,$4013 SQUARE WAVE CONTEXT (2 BLOCKS) .write flag set to TRUE on writes to $4003,$4007,$400B,$400F .envelope divider .low byte of envelope counter .high byte of envelope counter .sweep reload flag .sweep count .low byte of frequency .high byte of frequency .duty cycle counter .active time left counter TRIANGLE CONTEXT .width_lut .low byte of frequency .high byte of frequency .low byte of frequency cache .high byte of frequency cache .write flag set to TRUE on writes to $4003,$4007,$400B,$400F .duty counter .active time left counter NOISE CONTEXT .write flag set to TRUE on writes to $4003,$4007,$400B,$400F .envelope divider .low byte of envelope counter .high byte of envelope counter .active time left counter .low byte of shift register .high byte of shift register .low byte of frequency .high byte of frequency DMC CONTEXT (4 bytes, low->high byte order) .frequency .address .length +Other stuff (1 byte each) .counter .silent flag .is_empty flag .sample buffer .shift register .steal cycles .queue .APU cycle counter (4 bytes) TAG | HEXA DATA | DESCRIPTION ======+============+=============== VRC6 | 0x56524336 | VRC6 registers (1 byte each) .last value written to each register, in order: $9000,$9001,$9002 $A000,$A001,$A002 $B000,$B001,$B002 TAG | HEXA DATA | DESCRIPTION ======+============+=============== MMC0 | 0x4D4D4300 | Mapper context *TO BE WRITTEN* TAG | HEXA DATA | DESCRIPTION ======+============+=============== PTB0 | 0x50544200 | PPU timing context .OAM secondary (32 bytes long) .low byte of the current scanline .high byte of the current scanline .PPU $2002 (reads) .OAM secondary address (while evaluating) .low byte of PPU cycle .high byte of PPU cycle .PPU even/odd flag .VBlank timing flag TAG | HEXA DATA | DESCRIPTION ======+============+=============== NSF0 | 0x4E534600 | NSF context (if available) .current song number .bankswitch values (8 bytes long) .NSF memory (size $9000) - $0000-$7FFF is mapped to CPU $8000-$FFFF (8K) - $8000-$8FFF is used by the emulator to store 32 bytes of a 6502 NSF player. .$000C: low byte of init address .$000D: high byte of init address .$000E: low byte of play address .$000F: high byte of play address //EOF