This page is a mirror of Tepples' nesdev forum mirror (URL TBD).
Last updated on Oct-18-2019 Download

Controller Input Codes

Controller Input Codes
by on (#91448)
Hey all! I sat down to try and figure out a basic way to do some controller codes that people could use to do things like the Konami code. I thought I'd share what I ended up using, and ask how some of you might've or have handled it. This is what I came up with:

EDIT: Here's a ROM with it working on the title screen.

Code:
code_count:    .res 1      ; Number of frames between each button press   
code_offset:   .res 1      ; Which byte to check in the code_check table

code_check:
   .byte up_punch, up_punch, down_punch, down_punch
   .byte left_punch, right_punch, left_punch, right_punch
   .byte b_punch, a_punch

test_code:
   ldx code_offset         ; Get the offset ready
   lda control_pad         ; Check buttons for only one
   eor control_old         ;  button press at a time
   and control_pad         ;
   and code_check, x       ; Check the button press against
   beq :+                  ;  the code_check table
      lda #$10             ; When buttons match, reset the
      sta code_count       ;  code count and increase the
      lda code_offset      ;  offset by one.
      clc                  ;
      adc #$01             ;
      sta code_offset      ;
      cmp #$0a             ; Compare accumulator to max
      bne :+               ;  offset in code_check table
         jsr PPU_off       ; Do successful code input stuff
                           ;  here. Be sure to jump over
                           ;  everything else to the end
                           ;  of the routine when you
                           ;  actually implement this.
:   lda code_count         ; If code_count is NOT zero...
    beq :+                 ;
      sec                  ;  ... then subtract it by one
      sbc #$01             ;  and return.
      sta code_count       ;
      rts                  ;
:   lda #$00               ; If code_count was zero then
    sta code_offset        ;  set code_offset back to zero,
    rts                    ;  which means the code input
                           ;  failed, and must start from
                           ;  the beginning.
[/url]

by on (#91452)
Not that it really matters too terribly much, but this:

Code:
      lda code_offset
      clc                  ;
      adc #$01             ;
      sta code_offset      ;
      cmp #$0a             ; Compare accumulator to max
      bne :+   


Can be this:

Code:
      inx
      stx code_offset      ;
      cpx #$0a             ; Compare accumulator to max
      bne :+   


I haven't tried the actual code, but from the look of it if you pressed every button at once it would always advance the counter.

Code:
test_code:
   ldx code_offset         ; Get the offset ready
   lda control_pad         ; All buttons pressed #%11111111
   eor control_old         ; No buttons were pressed last frames #%00000000
   and control_pad         ;#%11111111
   and code_check, x       ; Check the button press against
   beq :+


That last and should probably be a cmp.

Even then the code input sort of fails. I just have to hammer buttons while the code input counter is above zero, and as long I press the right one at some point, it works, order be damned.

Could be wrong here since I haven't tested it, though. That's just how I understand it's working.

Instead of using a timer, I'd wait for no buttons to be pressed before advancing to the next input. It seems you're trying to support multiple inputs (like up+A as a single part of the code) which is noble. In that case, I'd keep the timer for the case where the input partially matches, but count down for a much shorter time period (like 4 frames.) If the full input doesn't match by then, the code resets.

Edit: Though... I guess that has a similar problem. As long as one of the possible buttons is held down, you can hammer others and the code doesn't reset. I may edit this later with a 6502 try of my own.

by on (#91457)
The first post has a ROM included now if anyone would like to check it out.

EDIT: Oops! Forgot to reply to the inx thing you pointed out. Yeah, I think I had been doing something else when writing the code at first, and never thought to mess with that after it had started working.

by on (#91459)
I just TAS'd the rom you posted, and pressing all the buttons every other frame DOES make the code work. So does pressing random buttons in the middle of the code. The only sequence I tried was up, down, up, b, down, down, start, left, right, left, A, right, B, A, but any buttons can probably be pressed in between while the timer is counting down.

Here's my admittedly crazy (and untested) solution. It allows for multiple button inputs (even all eight at once), and fails if anything that's not part of an input is pressed at any time. It will also wait as long as possible for you to press the rest of the buttons in a multibutton input which I think is nicer than a timer.

Apologies in advance if it's hard to read. My coding style is basically garbage. It's also not super optimized, but it may work even though it's untested. Yell at me if it doesn't work, I would like to fix it if it doesn't.

I may be able to do something less hardcore if multiple button inputs aren't needed. I realized after the post you didn't care about multiple buttons at all. I just sorta read (right_punch) as something like right+A, when you really just had the Konami code verbatim.

Edit: Doesn't work! :lol: Figuring it out now.
Edit 2: It plays at least somewhat nice now. Further stress testing now.
Code:
;NoButtons = Contains whether we are currently waiting for no buttons to be checked
;Non Zero means we are.
;IntNoButtons = Contains whether the first check is waiting for no buttons to be checked
;Non Zero means it is
;code_offset = code_offset
;InputStarted = Used to check for no buttons being pressed after starting a complex input.
;Nonzero means complex input was started

up_punch = %00001000
down_punch = %00000100
left_punch = %00000010
right_punch = %00000001
a_punch = %10000000
b_punch = %01000000

code_check:
   .byte up_punch, up_punch, down_punch, down_punch
   .byte left_punch, right_punch, left_punch, right_punch
   .byte b_punch, a_punch
;code_check:
   .byte up_punch | a_punch, down_punch | b_punch, left_punch, right_punch
   .byte down_punch | left_punch, down_punch | right_punch, b_punch, a_punch
   .byte up_punch, right_punch
test_code:
   ldx code_offset;Because when code_offset is 0, old inputs may still be pressed from when the code was reset
   beq nobuttonstartcheck;We go to an additional check
truestart:
   lda NoButtons;If we're waiting for no buttons to be pressed
   bne ComplexCheck;Branch
   lda buttonsheld
   beq complexinputstartcheck
complexinputreentry:
   cmp code_check,x;If the current input matches
   beq SetNoButtons;The input we want perfectly, we are now waiting for no buttons to be pressed to advance the offset
   ;If not...
   lda code_check,x;We now reverse the buttons the current input requires
   eor #$FF;We do this, so we can check if any of THOSE buttons we don't want are pressed
   and buttonsheld;And reset code_offset if so
   
   bne resetcode;If any are pressed, we reset the code
   ;Else, we go through the loop on the next frame waiting for a perfect match to our input
   ;And set InputStarted so if the player releases all of the current "good" buttons
   ;the code will be reset
   lda buttonsheld
   beq code_rts
   lda #$FF
   sta <InputStarted
   
   rts
complexinputstartcheck:
   ldy InputStarted
   beq complexinputreentry
   bne resetcode
   
nobuttonstartcheck:
   lda IntNoButtons;If this variable is 0,
   beq truestart;we're safe to start the code as normal
   lda #$00
   sta NoButtons
   sta InputStarted
   lda buttonsheld;Otherwise we RTS if the current input is non zero
   bne code_rts
   sta IntNoButtons;or store zero in this variable so the code can safely continue on the next frame
code_rts:
   rts
   
SetNoButtons:
   lda #$FF
   sta NoButtons
   
   lda #$00
   sta InputStarted
   
   rts
   
ComplexCheck:
   lda code_check,x
   eor #$FF
   sta temp
   
   lda buttonsheld
   beq AdvanceCode;If there are no buttons held, we can move the next input
   and temp;If a button that was NOT part of our input is pressed
   bne resetcode;While other buttons related to the current input are, we reset the code.
   
   rts;If neither of those things are true, we're still waiting for the buttons to be released
   
AdvanceCode:
   inx
   cpx #$0A
   beq codesucceeded
   stx code_offset
   lda #$00
   sta NoButtons
   sta InputStarted
   
   rts
   
resetcode:
   lda #$00
   sta NoButtons
   sta code_offset
   sta InputStarted
   
   lda #$FF
   sta IntNoButtons
   
   rts
   
codesucceeded:
;Do code succeeded things
   
   jmp resetcode
   rts


Konami Test Rom: http://www.mediafire.com/?4y232b65qmr3071
Multi Button Test Rom: http://www.mediafire.com/download.php?v891d08t1va5da7

Multibutton's code is: up+A, down+B, left, right, down+left, (remember you gotta release all buttons before next input) down+right, B, A, Up, Right

The background turns blue on success.

I definitely should have chosen a code that didn't have something like the down+left, down+right sequence. Still pretty proud of how it works with wrong button inputs even when the current input requires multiple buttons. Roth's is better for rapid fire entry, though. Mine is too strict to enter the code quickly.

by on (#91460)
Similar to codes like this, games like Street Fighter and Mortal Kombat will keep a queue of player input for performing special moves. So it has other uses than adding secret codes. Personally I find long button sequence codes to be rather annoying. There are better ways to hide cheats or debug functions.

by on (#91461)
Kasumi wrote:
I just TAS'd the rom you posted, and pressing all the buttons every other frame DOES make the code work. So does pressing random buttons in the middle of the code. The only sequence I tried was up, down, up, b, down, down, start, left, right, left, A, right, B, A, but any buttons can probably be pressed in between while the timer is counting down.


Testing a basic code like this against TASing isn't really a concern of mine. The counter is fast enough that with normal player input on an NES it pans out just right, I think.

I don't have time right now to look over the code you posted, but I will be sure to check it out later. Thanks for the critique and the response!

by on (#91462)
MottZilla wrote:
Personally I find long button sequence codes to be rather annoying. There are better ways to hide cheats or debug functions.


Amen to this. I still get a chuckle remembering my friend with the notebook full of moves for the MK series. "Hold on, I've got that fatality somewhere...<flip,flip,flip>YOU LOSE."

by on (#91463)
Mortal Kombat also has a STUPIDLY short window for entering those fatalities.

Re: My CRAZY input code. It sorta works now (Version one did not), but it still has bugs. It's also not great for rapid fire entry. I'mma keep fixing it up, though. It's a fun exercise.

by on (#92367)
Here's what I did for my game:

Code:
In RAM:
EasterEgggSteps: .rs 1 ;1Byte RAM for Easter Egg current button check.
Data:
EasterEggButtonCombo: (Button values array here, one button at a time)

Program:
  LDA #$10
  BIT Settings ;Check to see if Easter Egg is already set
  BNE .AfterEasterEggCheck ;It was, skip everything.
  LDA ControllerData0NewlyPressed ;Get buttons newly pressed.
  BEQ .AfterEasterEggCheck ;No new button pressed, skip rest.
  LDX EasterEggSteps ;Current buttton check.
  CMP EasterEggButtonCombo,X Check array.
  BNE .ClearEasterEggSoFar ;Clear button check value, wrong key.
  INC EasterEggSteps ;Next button check next time.
  CPX #$(NumberOfButtonPressesNeeded) ;Check to see if last press done.
  BNE .AfterEasterEggCheck ;Nope, not yet.
  LDA Settings ;Settings byte for my game engine, this just sets a flag for my main program to do stuff with.
  ORA #$10
  STA Settings ;Store easter egg hit flag for my engine.
.ClearEasterEggSoFar:
  LDA #$00
  STA EasterEggSteps ;Clear steps, wrong code or easter egg activated.
.AfterEasterEggCheck:
  Code after check is done.