76

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?

Badasahog
  • 4,031
  • 3
  • 24
  • 61
  • 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? – the busybee Oct 23 '21 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 '21 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 '21 at 23:09
  • The game writers that use this kind of "password" system actually jump through great hoops to prevent the potential of vulgar or insensitive passwords being generated. It doesn't stop folks from entering them, but it prevents the game from generating them. So, from that aspect, this is unlikely to have ever been a valid password. – Will Hartung Oct 23 '21 at 23:10
  • 2
    @Will, hardly so in the 80s (or even 90s), at least not commonly. – Zeus Oct 25 '21 at 01:50
  • @bjb putting in the password on the 3DS virtual console would literally brick the system – Badasahog Oct 25 '21 at 13:57
  • 2
    @Badasahog Do you have a reliable source for that? I've seen many sources indicating that the password causes the emulator to crash, but also that it can be recovered with a system reboot. I haven't seen any firsthand claims of consoles actually being bricked, and that seems so vanishingly unlikely (see my answer) that I'm pretty sure it's just an urban legend. – NobodyNada Oct 25 '21 at 18:01
  • @NobodyNada https://youtu.be/WRydysMUOFc?t=246 – Badasahog Oct 25 '21 at 18:04
  • 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 '21 at 19:00
  • 1
    @Badasahog I would never take the claims of sensationalist gamer YouTubers at their word. Even your link only speaks of ‘soft bricking’, i.e. not permanent, and even that seems overstating it. It's just a lockup, fixable by normal power cycling. – user3840170 Aug 12 '22 at 07:03

2 Answers2

201

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.

NobodyNada
  • 5,464
  • 1
  • 23
  • 35
  • 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 '21 at 04: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. – John Dvorak Oct 24 '21 at 10:35
  • 15
    Thanks for this trip down the rabbit hole. Well done! – Brian H Oct 24 '21 at 18:43
  • Is ValidatePassword actually validating anything at all? You'd think a function like that is supposed to prevent exactly this issue. – Michael W. Oct 25 '21 at 15:27
  • @MichaelW. It validates the checksum. – HiddenWindshield Oct 25 '21 at 16:16
  • 4
    @MichaelW. The authors seem to have assumed that checksum validation was sufficient; clearly they were mistaken. – zwol Oct 25 '21 at 16:16
  • 1
    @zwol So all it's doing is counting bytes, checking against the total, and giving a thumbs up if the numbers match? Yikes. – Michael W. Oct 25 '21 at 18:12
  • 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 '21 at 23:41
  • tl;dr the game authors broke the cardinal rule of checksums: they are intended to be used to detect data integrity, not authenticity. This becomes more true the more simple the csum algorithm is. – Ian Kemp Oct 26 '21 at 09:41
  • 2
    @IanKemp I expect the devs fully intended to only check integrity and didn't care about authenticity. They're just trying to protect the user from simple typos. – TenMinJoe Oct 26 '21 at 09:55
  • 2
    With my software engineer hat on, this is another instance of the eternal argument over whether programs should spend CPU cycles and code space on bounds checks. A check for whether the "InArea" value was within the valid range would have prevented the crash, but when you're writing a game in assembly language for a console whose memory is so limited that you need to use bank-switching, well -- I can understand why the programmers didn't think those extra instructions were worth the space they would have needed. – zwol Oct 26 '21 at 15:01
  • But the fact that they failed to validate authenticity led to the unintended consequence of being able to crash the console with bad data. As a software developer myself, data validation is always worth it - it prevents so many problems downstream... – Ian Kemp Oct 27 '21 at 08:10
  • @IanKemp I agree that a bounds-check on InArea would have been the right thing to do by today's software development standards. But when you're in the 80's writing a rushed port of a videogame in 8-bit assembly...well presumably "behaves correctly if the user brute-forces invalid passwords" wasn't a design requirement, as the worst thing that could happen is that the game crashes. Besides, strange passwords add another layer of mystery to the game that would have fascinated kids at the time, and that we get to unravel 35 years later :) – NobodyNada Oct 27 '21 at 16:47
  • 3
    And while validating InArea would have been easy, what about the other fields in the password? I don't think there's much more potential for crashes, but there's definitely passwords that lead to unbeatable or inconsistent states (such as "Samus has collected morph ball but does not have the ability to morph", "Samus is trapped in Tourian with no missiles", or "Samus has both Ice and Wave Beam"). These would have been much less trivial to validate, which is probably why authenticating passwords was not a design requirement. – NobodyNada Oct 27 '21 at 16:53
  • As of January 2024 the given link to the disassembly is broken, metroid-database.com no longer exists. https://www.romhacking.net/documents/459/ is a working link – BenW Jan 24 '24 at 16:49
34

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.

Doc
  • 441
  • 4
  • 3
  • 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)? – Nate Eldredge Oct 23 '21 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 '21 at 19:17