77

For those who are unfamiliar, the original NES Metroid was one of many NES games to use passwords as a means to save progress. Later, this password system was leveraged to create unusual effects in the game by inputting invalid passwords. One of these passwords was "ENGAGERIDLEYMOTHERFUCKER" which bricked some versions of the game, and would later go on to break NES emulators, and even brick the systems they ran on.

What causes this to happen?

10
  • 20
    How could an emulator brick the system it runs on? I once wrote an emulator that disabled memory protection for performance, but the worst thing that could happen was to wipe all user data. -- However, there might be some sensitive system that can be bricked by an application creating havoc. Do you have an example? Oct 23, 2021 at 19:13
  • 14
    @thebusybee Here's one example: In A UEFI World, "rm -rf /" Can Brick Your System. (TL;DR: Junky firmware that can be put into an unrecoverable state by manipulating the flash memory exposed to the OS in unexpected ways. On "everything is a file" platforms, rm -rf / is included in that.) Basically, any change to persistent storage has the potential to brick things if the boot ROM doesn't provide a means to recover.
    – ssokolow
    Oct 23, 2021 at 19:24
  • 53
    'Bricking' means the device is no longer functional and typically can't be restored (hard brick), i.e. as functional and useful as a brick after some event which bricked it. I think you mean 'crash' which just means a software error which is easily remedied by a reset/reboot. A 'soft brick' is not the same thing, as it is more along the lines of a device that is no longer functional, but there is a way to repair it by re-flashing the firmware, for example (think an iPhone getting restored via iTunes).
    – bjb
    Oct 23, 2021 at 23:09
  • 2
    @Will, hardly so in the 80s (or even 90s), at least not commonly.
    – Zeus
    Oct 25, 2021 at 1:50
  • 7
    Interesting...though, if you take a look at the comments on the video, even though holding down the power button doesn't work it looks like the system can be recovered by simply letting the battery die (or removing the battery). So, presumably the 3DS's emulator handles the 02 opcode by simply halting/crashing the emulator -- but, before 2016, it somehow managed to crash badly enough that the power button didn't work. I still wouldn't call it a brick, but it's still a pretty nasty crash.
    – NobodyNada
    Oct 25, 2021 at 19:00

2 Answers 2

202

Let's take a look at the code! A few seconds of Googling led me to a high-quality annotated disassembly by Kent Hansen and Nick Mikstas: https://www.metroid-database.com/source-code/

Whenever the user is on the password screen, the following routine runs every frame:

EnterPassword:
L9147:  JSR EraseAllSprites         ;($C1A3)Remove sprites from screen.
L914A:  LDA Joy1Change              ;
L914C:  AND #$10                    ;Check to see if START has been pressed.
L914E:  BEQ +                       ;If not, branch.
L9150:  JMP CheckPassword           ;($8C5E)Check if password is correct.
... (draw the password menu and handle user input)

If the user pressses the start button, we jump to the CheckPassword routine:

CheckPassword:
L8C5E:  JSR ConsolidatePassword     ;($8F60)Convert password characters to password bytes.
L8C61:  JSR ValidatePassword        ;($8DDE)Verify password is correct.
L8C64:  BCS +                       ;Branch if incorrect password.
L8C66:  JMP InitializeGame          ;($92D4)Preliminary housekeeping before game starts.
... (handle incorrect password)

ConsolidatePassword converts the password from characters into bytes, and stores it in memory at address $6988. The password in question is decoded into the byte sequence 39 74 0A 40 E6 D2 35 53 A2 59 87 51 39 B3 DE 31 43 9B. The last two bytes are special: the 43 indicates that the password should be deobfuscated by rotating it to the right by 0x43 bits, and the 9B is a checksum. ValidatePassword performs the rotation, and computes the checksum. The password then becomes the sequence 12 CC 3A 89 CD 9E F1 89 CB A0 52 07 36 91 AA 9D 43, which (by pure coincidence) happens to add up to 0x9B, meaning the checksum matches and the game treats it as a valid password rather than displaying the error screen.

We then jump to the InitializeGame routine:

InitializeGame:
L92D4:  JSR ClearRAM_33_DF          ;($C1D4)Clear RAM.
L92D7:  JSR ClearSamusStats         ;($C578)Reset Samus stats for a new game.
L92DA:  JSR LoadPasswordData        ;($8D12)Load data from password.
...
L931A:  LDA InArea                  ;Load area Samus is to start in.
    L931C:  AND #$0F                ;
L931E:  TAY                         ;
L931F:  LDA BankTable,Y             ;Change to proper memory page.
L9322:  STA SwitchPending           ;
L9324:  RTS 

LoadPasswordData initializes game variables using the password bytes:

LoadPasswordData:
L8D12:  LDA NARPASSWORD             ;If invincible Samus active, skip-->
L8D15:  BNE +++                     ;further password processing.
L8D17:  JSR LoadUniqueItems         ;($8BD4)Load unique items from password.
L8D1A:  JSR LoadTanksAndMissiles    ;($8D3D)Calculate number of missiles from password.
L8D1D:  LDY #$00                    ;
L8D1F:  LDA PasswordByte08          ;If MSB in PasswordByte08 is set,-->
L8D22:  AND #$80                    ;Samus is not wearing her suit.
L8D24:  BEQ +                       ;           
L8D26:  INY                         ;
L8D27:* STY JustInBailey            ;
L8D2A:  LDA PasswordByte08          ;Extract first 5 bits from PasswordByte08-->
L8D2D:  AND #$3F                    ;and use it to determine starting area.
L8D2F:  STA InArea                  ;
L8D31:  LDY #$03                    ;
L8D33:* LDA PasswordByte0B,Y        ;Load Samus' age.
L8D36:  STA SamusAge,Y              ;
L8D39:  DEY                         ;
L8D3A:  BPL -                       ;Loop to load all 3 age bytes.
L8D3C:* RTS                         ;

Since this isn't actually a "real", meaningful password, this routine just ends up loading a bunch of nonsensical, meaningless values. The one that actually causes the game to crash is the InArea, which comes from the low 5 bits of byte 8 of the password, or 0xB.

We return to InitializeGame, which eventually looks up the area number in a table to determine which memory bank to load. Here's the table:

LCA30:  .byte $02                   ;Brinstar.
LCA31:  .byte $03                   ;Norfair.
LCA32:  .byte $05                   ;Kraid hideout.
LCA33:  .byte $04                   ;Tourian.
LCA34:  .byte $06                   ;Ridley hideout.

But note that this table has only 5 entries! An area of 0xB (decimal 11) causes us to read well past the end of the table and into the next routine. The byte we read happens to be 0x84, which is not a valid bank number.

A while later, we perform the actual bankswitch:

SwitchOK:
LC4E8:  lda #$00                    ;Reset(so that the bank switch won't be performed-->
LC4EA:  sta SwitchPending           ;every succeeding frame too).
LC4EC:  dey                         ;Y now contains the bank to switch to.
LC4ED:  sty CurrentBank             ;

ROMSwitch:
LC4EF:  tya                         ;
LC4F0:  sta $00                     ;Bank to switch to is stored at location $00.
LC4F2:  lda SwitchUpperBits         ;Load upper two bits for Reg 3 (they should always be 0).
LC4F4:  and #$18                    ;Extract bits 3 and 4 and add them to the current-->
LC4F6:  ora $00                     ;bank to switch to.
LC4F8:  sta SwitchUpperBits         ;Store any new bits set in 3 or 4(there should be none).

;Loads the lower memory page with the bank specified in A.

MMCWriteReg3:
LC4FA:  sta MMC1Reg3                ;Write bit 0 of ROM bank #.
LC4FD:  lsr                         ;
LC4FE:  sta MMC1Reg3                ;Write bit 1 of ROM bank #.
LC501:  lsr                         ;
LC502:  sta MMC1Reg3                ;Write bit 2 of ROM bank #.
LC505:  lsr                         ;
LC506:  sta MMC1Reg3                ;Write bit 3 of ROM bank #.
LC509:  lsr                         ;
LC50A:  sta MMC1Reg3                ;Write bit 4 of ROM bank #.
LC50D:  lda $00                     ;Restore A with current bank number before exiting.
LC50F:* rts                         ;

The MMC1 was a slightly unusual mapper, in that it is accessed via a serial interface which accepts one bit at a time. See the NESDev Wiki's documentation:

Unlike almost all other mappers, the MMC1 is configured through a serial port in order to reduce pin count. CPU $8000-$FFFF is connected to a common shift register. Writing a value with bit 7 set ($80 through $FF) to any address in $8000-$FFFF clears the shift register to its initial state. To change a register's value, the CPU writes five times with bit 7 clear and a bit of the desired value in bit 0. On the first four writes, the MMC1 shifts bit 0 into a shift register. On the fifth write, the MMC1 copies bit 0 and the shift register contents into an internal register selected by bits 14 and 13 of the address, and then it clears the shift register.

In other words: each time we write to an MMC register, bit 7 indicates whether we want to clear the shift register or write to it, and if we want to write then bit 0 indicates the bit we want to write. Bits 1-6 are ignored

However, when we try to switch banks to a value that's way out of range (0x83, because of the dey instruction), the first write has bit 7 set and actually resets the shift register rather than writing. The remaining 4 writes write to the shift register but do not trigger a bankswitch, since the MMC1 is waiting for a 5th write.

Later on, in the NMI handler, the game attempts to configure the PPU's nametable layout:

SetPPUMirror:
LC4B6:  lsr                         ;
LC4B7:  lsr                         ;Move bit 3 to bit 0 position.
LC4B8:  lsr                         ;
LC4B9:  and #$01                    ;Remove all other bits.
LC4BB:  sta $00                     ;Store at address $00.
LC4BD:  lda MMCReg0Cntrl            ;
LC4BF:  and #$FE                    ;Load MMCReg0Cntrl and remove bit 0.
LC4C1:  ora $00                     ;Replace bit 0 with stored bit at $00.
LC4C3:  sta MMCReg0Cntrl            ;
LC4C5:  sta MMC1Reg0                ;
LC4C8:  lsr                         ;
LC4C9:  sta MMC1Reg0                ;
LC4Cc:  lsr                         ;
LC4CD:  sta MMC1Reg0                ;
LC4D0:  lsr                         ;Load new configuration data serially-->
LC4D1:  sta MMC1Reg0                ;into MMC1Reg0.
LC4D4:  lsr                         ;
LC4D5:  sta MMC1Reg0                ;
LC4D8:  rts                         ;

This routine attempts to update the MMC1 control register, which is documented on the NESDev wiki:

4bit0
-----
CPPMM
|||||
|||++- Mirroring (0: one-screen, lower bank; 1: one-screen, upper bank;
|||               2: vertical; 3: horizontal)
|++--- PRG ROM bank mode (0, 1: switch 32 KB at $8000, ignoring low bit of bank number;
|                         2: fix first bank at $8000 and switch 16 KB bank at $C000;
|                         3: fix last bank at $C000 and switch 16 KB bank at $8000)
+----- CHR ROM bank mode (0: switch 8 KB at a time; 1: switch two separate 4 KB banks)

However, the shift register still contains 4 leftover bits from the failed bankswitch; it's contents (in binary) are x0001. The first write to the MMC1 register fills in the 5th bit (the x) and causes all 5 bits to immediately be written to the control register. This causes the MMC1 to suddenly switch into 32-KiB bank mode.

Normally, the game runs with the MMC1 in 16-KiB mode, mapping area-specific routines and data (in this case, for the title screen) to addresses $8000-BFFF and the game engine to $C000-$FFFF. However, when the MMC1 is switched into 32-KiB mode, the game engine is suddenly replaced with the area data for Brinstar. The address we are currently executing, $C4C8, is suddenly replaced with what appears to be unrelated enemy AI routines:

L84C2:  LDY EnXRoomPos,X
L84C5:  BNE $84DA
L84C7:  LDA $49
L84C9:  CMP #$02
L84CB:  BCC $84DA
L84CD:  LDA $FD
L84CF:  BEQ $84D4

We've ended up misaligned in the instruction stream: note that one instruction starts at address C4C7, and the next starts at C4C9; but we're trying to execute the instruction starting at C4C8. The processor ends up interpreting the LDA operand (memory address $49) as an opcode, which happens to correspond to the instruction EOR #i. This doesn't do anything interesting, but it means that we're still misaligned and now attempting to execute address C4CA (the #$02 operand to the CMP). Opcode 02 is invalid; if executed, it just causes the CPU to freeze until the console is reset.


There's no way this crash could possibly cause any permanent damage to the cartridge or console. The 02 opcode itself could not cause any harm; it just locks up the CPU until the user presses the reset button or cycles the power. Besides, the Metroid NES cartridge included no persistent storage so there's no way to "brick" it. There were (to my knowledge) no other "versions" of Metroid released on the NES, so it's not possible that I investigated the wrong version.

As far as emulators: an accurate emulator should behave identically to the hardware, so the password will cause the game to crash on an emulator just as it would on hardware. But it's virtually impossible for the crash to actually have any effect on the system running; it would take an extraordinarily badly-written emulator to actually cause harm to the system it's running on if it encounters an invalid opcode.

14
  • 17
    As to the last line, it's not just badly written, but an OS that allows the emulator to somehow operate in non-protected mode and write beyond what it is suppose to. You'll have to do something really weird like run as su / admin before it does real damage, or it somehow uses an exploit to perform access elevation to become su / admin.
    – Nelson
    Oct 24, 2021 at 4:08
  • 14
    @Nelson that said it is entirely possible for an emulator that emulates a frozen system and therefore eats all input for the emulated system, gets bugged and also ignores all input for itself, and refuses to exit fullscreen mode thus appearing to freeze the entire host system. It takes one person to forget CTRL+ALT+DEL and start a myth. Oct 24, 2021 at 10:35
  • 15
    Thanks for this trip down the rabbit hole. Well done!
    – Brian H
    Oct 24, 2021 at 18:43
  • 4
    @MichaelW. The authors seem to have assumed that checksum validation was sufficient; clearly they were mistaken.
    – zwol
    Oct 25, 2021 at 16:16
  • 5
    @MichaelW. A basic checksum is enough to protect against typos, which is about all that's necessary. There isn't much point in wasting your limited code space (and programmer time!) trying to use a cryptographically secure scheme, the worst that happens if someone tries to guess passwords is an unintended cheat or a crash. The checksum used on credit card numbers isn't much different.
    – Bob
    Oct 25, 2021 at 23:41
35

Fundamentally these 'passwords' aren't really passwords as such, but map back to a string of bits that control the state of the game - so it's more of a string representation of the current state of the game than a password.

There is an amazingly good description of how this works available here that covers the full process of mapping the provided 'password' to the string of bits that controls the state of the game.

The actual words in the 'password' don't have any specific meaning or relationship to the end result - it's simply a string of letters that in this case happens to form words.

As the passed string is basically forcing the game into a set state, it's possible for it to be put into a state that the game manufacturer never intended it to be in - including states that simply make no sense in the context of the game. This can then lead to triggering bugs in the game that occur due to the invalid state that the writers never intended to occur (and thus obviously never handled/tested), resulting in crashes.

2
  • 3
    It's reasonable that there should be a crash, but do we know how the crash leads to permanently disabling the cartridge and/or console (or whether that can in fact happen)? Oct 23, 2021 at 18:45
  • 3
    If you prefer it in video rather than password.txt form, Bisqwit also has a Metroid episode in his series of videos where he explains reverse-engineered video game passwords and he even explains what happens with that particular password (with the help of a less coarse but functionally equivalent one), though he only gets one of the possible outcomes.
    – ssokolow
    Oct 23, 2021 at 19:17

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .