Submission #8831: TaoTao's NES Kaettekita! Gunjin Shougi: Nanya Sore!? "game end glitch" in 00:00.56

(Link to video)
Nintendo Entertainment System
(Submitted: Kaettekita! Gunjin Shogi: Nanya Sore!?)
game end glitch
BizHawk 2.9.1
1881 (Cycle Count 3017358)
3347.1956261073415
504
PowerOn
Kaettekita! Gunjin Shougi - Nanya Sore! (J).nes
Submitted by TaoTao on 1/7/2024 10:21:52 PM
Submission Comments

Game objectives

  • Emulator used: BizHawk 2.9.1 (SubNesHawk core)
  • Aims for fastest time
  • Corrupts memory
This run beats NES "Kaettekita! Gunjin Shogi: Nanya Sore!?" under a second with the DPCM exploit.
Note: I don't know whether this movie syncs on a real console, but I think theoretically you can beat this game like this way on a real console.
Note: There are no sound in the ending, possibly due to uninitialized RAM state (I will describe the details later). I also made another movie for zero-initialized RAM, where the game plays sound correctly (encode). I don't know much about initial RAM state, sorry.

About the game

"Kaettekita! Gunjin Shogi: Nanya Sore!?" is the only Gunjin Shogi game for NES. There are five opponents, but all of them seem to have the same AI. When you win three times against each of them, the last opponent will appear. When you beat the last opponent, the ending starts.

Comments

DPCM exploit

In this game, you can easily cause a stack overflow by continuing to stall the input processing routine with subframe inputs. And, this game doesn't repurpose the stack page for other variables. So, the remaining problem is a setup for a credit warp.
The ending routine is located at $E9B0. The goal is let the program counter jump there. It is possible by taking some detours, though it is difficult to directly write the address as a return address.
First, when a NMI occurs, the stack grows as shown below (the NMI handler pushes A, X, Y registers in this order):
+---+---+---+---+----------+
| Y | X | A | P | rti_addr |
+---+---+---+---+----------+
And, when the input processing routine stalls, there are more stuffs on the stack:
+------+------+---------------+------+------+---+---+---+---+----------+
| 0xCA | 0xFD | player2_input | 0x54 | 0xFF | Y | X | A | P | rti_addr |
+------+------+---------------+------+------+---+---+---+---+----------+
So, when the stack overflows, you can let the program counter jump to $xxFD by overwriting the stack as below:
+------+----------------------+
|  P   |       rti_addr       |
+------+----------------------+
             ^
             |
+------+------+---------------+
| 0xCA | 0xFD | player2_input |
+------+------+---------------+
I let the program counter jump to $FD by setting player2_input to 0. $FD is the mirror variable of PPU_SCROLL x ($2005), and it's value is 0. This is brk instruction. On IRQ/BRK, this game simply returns with rti . So, the program counter proceeds to $FF (note that brk instruction skips the next byte).
$FF is the mirror variable of PPU_CTRL ($2000), and it's value is 0x90. This is bcc instruction. Its operand is fetched from $0100, and you can manipulate this while you are corrupting the stack. I write 0xF4 to $0100. So, the program counter branches to $F5 (note that the carry flag is false, because I copied 0xCA to the status register on the stack).
$F5-$F8 is the input buffer. $F5-$F6 contains the edge inputs of player1/player2, and $F7-F8 contains the raw inputs of player1/player2. I write [0x20, 0xB0, 0xE9] here. This is jsr $E9B0 . (This is possible because 0x20 is a "subset" of 0xE9, considering a byte as a bitset.) The game executes this code, and the program counter jumps to the ending routine. By the way, the ending routine never returns, so you don't have to worry about after the jsr instruction.
When you stall the input processing routine once, the stack will grow at most by 11 bytes. But, you can manipulate the consumed amount of the stack by adjusting the timing of NMI, using player2 inputs. (This game compares the inputs of player1/player2 separately, and the consumed CPU cycle differs slightly for each.)

Uninitialized RAM

On boot, This game doesn't initialize RAM so much, and it reads some uninitialized RAM (even without DPCM exploit). In this run, I think the game reads the uninitialized RAM below:
addrdescription
$17Some counter. Incremented by NMI handler without initialization.
$23OAM DMA flag.
$2AInput buffer. This will have no problem.
$2BInput buffer.
$F5Input buffer.
$F6Input buffer.
$F7Input buffer. Read as "previous input" without initialization.
$F8Input buffer. Read as "previous input" without initialization.
$075DVariable for audio driver, probably.
$075EVariable for audio driver, probably.
Especially, $075D and $075E will be problematic. Probably these are variables for the audio driver, and they have initial value 0xFF on NesHawk. I think this is the reason of no sound in the ending.

Memory map

addrtypedescription
$F5u8player1 edge input
$F6u8player2 edge input
$F7u8player1 raw input
$F8u8player2 raw input
$FCu8mirror variable of PPU_SCROLL y ($2005)
$FDu8mirror variable of PPU_SCROLL x ($2005)
$FEu8mirror variable of PPU_MASK ($2001)
$FFu8mirror variable of PPU_CTRL ($2000)
$E9B0codeending routine
$FDBAcodeinput processing routine
$FF00codeNMI handler
Inputs are in ABSTUDLR format.

Trivia

This game has a cheat which jumps to the ending. But it's slower than the DPCM exploit.
This game has weird bugs. Sometimes an enemy piece appears from nothing, and sometimes even an ally piece changes into an enemy piece. (example)

nymx: Claiming for judging.
nymx: This looks really good, just like your last submission. I didn't say this the last time, but this is proof that the emulator core is very accurate, as it mimics the DPCM hardware problem. I look forward to more of these runs. Great job.
Accepting.

despoa: Processing...
Last Edited by despoa on 1/26/2024 2:38 AM
Page History Latest diff List referrers