Action replay codes for nightmare difficulty in Saturn Doom


Gear Supporter
It doesn't appear to have been found already. There was a report recently on Kronos Discord by user WhiteySnakey that the nightmare difficulty could be unlocked in the PSX version of Doom, and he thought that since the Saturn version shares some similarities with the PSX one, that difficulty level could also be hidden inside.

So I gave it a shot with Kronos Cheat search feature and Yabause debugger, and got lucky.

But first thing first, to avoid screen flickers that can occur in Doom if many cheat codes are enabled (such as those ones for the US version), as well as reduce the CPU cycles used by the Action replay handler, I recommend to use a different master code than the default one. There's a specific master code to use for each region of Saturn Doom (the part that changes from the default code is in bold) :
  • US version master code :
    F603CD70 C305
    B6002800 0000
  • Japanese version master code :
    F603CDCC C305
    B6002800 0000
  • European version master code :
    F603CC2C C305
    B6002800 0000
And here are the Action replay codes to unlock the nightmare difficulty level in all 3 regions of Saturn Doom :
  • US version cheat code :
    1604A2AA 0004
    16026F52 0009
    1602712A 61D0
    160286E8 E104
  • Japanese version cheat code :
    1604A306 0004
    16026F6E 0009
    160271A6 622C
    16028754 E104
  • European version cheat code :
    1604A166 0004
    16026E1E 0009
    16027056 6084
    16028664 E104
Enabling this cheat locks the difficulty option in nightmare mode, and that option can no longer be changed.
Here is how the main menu looks in the Japanese version with the cheat enabled :
Capture d’écran (54).png

In game, monster placement is the same as in Ultra Violence, but they move faster, shoot at a faster rate and their projectiles move faster. Contrary to the PC version, monsters don't respawn (thankfully, it's hard enough).

I don't know if the game can be completed in that mode, I've only been through 4 levels before I got toasted.

Also note that when continuing a game with a password without the cheat, the difficulty is set by the password, however a password obtained with the cheat enabled and input with the cheat disabled will set the difficulty as "I am a wimp". So a password obtained in the nightmare difficulty must be input with the cheat enabled in order to continue the game with the same difficulty. And a password obtained in an easier difficulty can be used with the cheat enabled to continue the game in nightmare mode.

The cheat codes work in Kronos emulator without needing the master code. They should be input after the Sega logo, in Tools > Cheat Lists.

A tutorial on how to use the Cheat search in Kronos, made by @Benjamin Siskoo :

An Action replay code is obtained from the address found in Kronos Cheat search that way :
  • The 1st part of the code is the hexadecimal memory address found in Kronos for a 16 bit value, with the initial 0 of the address generally replaced by 1. There are other possibilities detailed here : ActionReplay - Yabause. However, note that prefixing a code with "0" or "D" can cause issues in the AR handler (all or part of the enabled codes can be ignored).
  • The 2nd part is the hexadecimal 16 bit value to be set at that memory address.
And now for some technical details about the codes :
  • The 1st code sets the number of the difficulty mode displayed by the main menu to 4 which corresponds to the nightmare difficulty (difficulty levels are numbered in order of increasing difficulty starting at 0, and the game initializes that value to 2, which is hurt me plenty). This value is increased or decreased when the difficulty option is modified, and when the player presses start at the main menu, this value sets a separate variable that contains the gameplay difficulty level .
  • The 2nd code forces the function that handles the change of the difficulty option on the main menu to consider the case of a press to the left, which should decrease the difficulty, as a press to the right. Since difficulty is above the maximum difficulty where option is modified by a press to the right (that is above hurt me plenty), this code prevents any change of the difficulty level through this option.
  • The 3rd code changes the least significant bytes of the start address of the table containing the difficulty level names so that the computation of the address of the current difficulty name points to the string "nightmare." (start_address + 16 * difficulty_number). That string is from the text displayed after the last of the ultimate Doom levels. Displaying that string is not only a neat way to let the player know that the AR code is enabled, but it also prevents a crash at the menu : it happens if the displayed difficulty is set to 4 without changing the table start address, as the game tries to display the passwords alphabet as the difficulty name (it follows the difficulty names table in memory) and fails because that string is too long.
  • The 4th code prevents the difficulty read from the passwords to change the displayed difficulty number and the gameplay difficulty. This is forcing the nightmare difficulty on any passwords input in the option menu or in the pause menu. The passwords can't save the nightmare difficulty number because they encode that number on only 2 bits, which you can see in the PSX reverse engineered code. That limits the encoded difficulty to 4 possible values that don't include the 5th one needed for nightmare (also explains that the passwords obtained in nightmare contain "I am a wimp" difficulty as the 2 lower bits of 4 are 0).
Regarding the master code change, the issue with the default master code is that the Action Replay handler is called before every interrupt handlers. In Doom, it's a problem because :
  • It delays the update of VDP1's frame buffer change or erasure flags that takes place in the vblank out interrupt handler. The higher the number of enabled AR codes, the longer it takes for the AR handler to execute, the more it delays VDP1's flags update. That update is timing sensitive with a tight limit, and it it occurs too late, the frame buffer switch is delayed by a screen frame, or an unwanted frame buffer erasure can be triggered and the screen flickers.
  • There are 3 interrupts that occur each screen frame in Doom (vblank out, SMPC's interrupt back, and vblank in), so the default master code calls the AR handler 3 times the vertical video frequency per second (180 calls per second in NTSC). That is overkill, as the AR handler doesn't need to set cheat values that often, and once per screen frame is enough. More than that is wasting CPU cycles that Saturn Doom desperately needs given how poorly it can perform.
That's why the new master code sets the call of the AR handler only at the beginning of the vblank in interrupt handler : it occurs once per screen frame and it's less timing sensitive than the other interrupt handlers as it has the whole vblank to carry its work (copy stuff to VDP2, and start the SMPC INTBACK command for input collection), more than enough for what Doom does with it.

The FXXXXXXX part of the master code that is changed replaces the value at address 0XXXXXXX with 0xC305 which is a "TRAPA #0x5" instruction that triggers the AR handler. The value at 0XXXXXXX must meet the following criteria :
  • Be an instruction actually executed by the main CPU, otherwise the AR handler will never be called.
  • Have no side effect on the program execution if it's not executed : the AR handler call will replace that instruction, so its disappearance mustn't change the program flow.
  • Not be a delay slot instruction (an instruction following a branch instruction and executed during the delay incurred by the branch), because the TRAPA instruction isn't compatible with that and will cause an illegal instruction exception that will freeze the console.
Last edited:
Modified OP to add a patch to prevent the password being incompletely displayed if it contains the replaced letter.

The nitty-gritty details of the patch of the password display routine :
060288D4 : 017C : mov.b @(r0, r7), r1 => 007C : mov.b @r7, r0 ; The password letter index can be loaded directly in r0 without doing extu.b because its value is positive and lower than 32 so its MSB is always 0 and mov.b already extends like extu.b. Makes the 2 following instructions useless so they can be used to put back the letter.
060288D6 : 611C : extu.b r1, r1 => E162 : mov #0x62 r1 ; Loads the original letter code.
060288D8 : 6013 : mov r1, r0 => 2310 : mov.b r1, @r3 ; r3 contains the address of the string "bcdfghjklmnpqrstvwxyz0123456789!". 0 will be inserted back automatically by the 1st Action replay code, which thankfully doesn't run simultaneously, before returning to the main menu.

Thanks to @XProger for reporting of the extu optimization in SegaXtreme Discord.
Last edited:
Updated OP with a better master code than the default one and a rework of the nightmare AR code :
  • Makes it shorter (4 codes instead of 5).
  • Fixes the difficulty not being forced on the pause menu passwords.
  • Displays "nightmare." as the difficulty name instead of nothing.
For those curious, the original code and all its mingling with the password input can still be viewed on the internet archive.
Binary code,

(U)DOOM Nightmare
File:    0.BIN
0x3A2AA  0004h
0x16F52  0009h
0x1712A  61D0h
0x186E8  E104h

(J)DOOM Nightmare
File:    0.BIN
0x3A306  0004h
0x16F6E  0009h
0x171A6  622Ch
0x18754  E104h

(E)DOOM Nightmare
File:    0.BIN
0x3A166  0004h
0x16E1E  0009h
0x17056  6084h
0x18664  E104h
Last edited:
Updated OP to fix the US version master code, which I had mistakenly set in the vblank out interrupt instead of vblank in. So it could still cause screen flickers.