don't click here

Sonic 1 Level Select Code Input Detection?

Discussion in 'Engineering & Reverse Engineering' started by Dr. Ivo, May 5, 2009.

Thread Status:
Not open for further replies.
  1. Dr. Ivo

    Dr. Ivo

    Professional Reverse Engineer Tech Member
    I do not have a disassembly of the game handy, and I wondered if someone might know the answer to this question offhand:

    By what means does the Sonic 1 "level select" code routine detect when the correct code is entered?



    There are a few reasonable approaches, including:

    - An input buffer of size CODE_LENGTH. When filled, a byte-by-byte comparison is made against the code definition.

    - A single counter acting as an index into the code definition. When the current input generated matches the next item in the code defition, the counter is incremented. When the counter exceeds the code length, the code has been entered.

    - A finite state machine that accounts for the replicated initial inputs (U,U) and will consider them as part of the correct sequence even if pressed an odd number of times. (Explanation below.)


    There are some problems with both of these.



    - A single counter with index will not handle 'initial repeat' cases. For example, UUUDDLRLR would fail. After pressing the second U, the routine would be expecting a D. If another U was pressed instead of the D, the counter would reset, and the user would be required to enter two more U's to complete a valid code entry.

    - A byte-by-byte comparison plus input buffer solves the problem stated above, but seems too heavyweight. This is how "Street Fighter" style games detect "special move" input.

    - A finite state machine would would be comparatively code-heavy, and changing the code definition would also require rewriting the state machine. Resistant to change. (While not a large concern in low-level programming, it would still prompt one to consider an alternate solution.)



    Which did method did Naka choose?
     
  2. GerbilSoft

    GerbilSoft

    RickRotate'd. Administrator
    2,971
    76
    28
    USA
    rom-properties
    With the various Sound Test codes in Sonic 2, the game simply increases a counter every time a "correct" sound is entered, and resets it to 0 if an "incorrect" sound is entered. I'd guess that the same method is used in Sonic 1.
     
  3. Dr. Ivo

    Dr. Ivo

    Professional Reverse Engineer Tech Member
    So, Sonic 2 uses the counter-based solution.

    That makes sense. However, with the pattern:

    19, 65, 09, 17

    If 19 is played, the counter is incremented from 0 to 1 and the next correct sound is thus 65. If I then replay sound 19, what does the code do? Resetting to the counter from 1 to 0 is incorrect, because I will then have to replay 19 to get it to expect 65 as the next correct entry. Code with logic as strict as you suggest would cause the 19 to need to have been played an odd number of times.

    Therefore, there must be a 'special case' included to check if the sound played is equivalent to the first sound in the correct sequence, and if so, the counter is reset to 1 instead of 0 - but only if our current code counter is 1. In other words, keep the counter at 1 so long as the first number in the sequence has been entered.

    Is this special case included?
     
  4. GerbilSoft

    GerbilSoft

    RickRotate'd. Administrator
    2,971
    76
    28
    USA
    rom-properties
    I believe it is, but I can't test it at the moment.
     
  5. SMTP

    SMTP

    Tech Member

    Sonic 1, S3K and S2&K uses this method. As far as I know, when I use Kmod.
     
  6. Andlabs

    Andlabs

    「いっきまーす」 Wiki Sysop
    2,175
    1
    0
    Writing my own MD/Genesis sound driver :D
    Why did none of you just look at the fucking disassembly? This is the old Hivebrain one:

    Code (Text):
    1. Title_RegionJ:      ; XREF: Title_ChkRegion
    2.         lea (LevelSelectCode_J).l,a0; load  J code
    I don't know why RegionJ, but this just stores the address of the first entry of the cheat code for the level select, which is stored as a series of button hit values, into register a0.

    Code (Text):
    1. Title_EnterCheat:   ; XREF: Title_ChkRegion
    2.         move.w  ($FFFFFFE4).w,d0
    3.         adda.w  d0,a0
    Address $FFFFFFE4 contains the index into the level select code array. This is 0-indexed; the memory address was probably reset somewhere above. Then you add this index to the base of the array; now (a0), which is the value at the memory address pointed to by a0, has the next button hit expected.

    Code (Text):
    1.         move.b  ($FFFFF605).w,d0; get button press
    2.         andi.b  #$F,d0; read only up/down/left/right buttons
    3.         cmp.b   (a0),d0; does button press match the cheat code?
    4.         bne.s   loc_3210; if not, branch
    5.         addq.w  #1,($FFFFFFE4).w; next  button press
    This part should be obvious.

    Everything else handles the C button hits used for the debug code, sets the flag, and plays the ring sound. My current knowledge of 68k assembly prevents me from explaining that. The disassembly is your friend. :-)
     
  7. SMTP

    SMTP

    Tech Member
    Yea, which is exactly just like

    As I told him.
     
  8. Andlabs

    Andlabs

    「いっきまーす」 Wiki Sysop
    2,175
    1
    0
    Writing my own MD/Genesis sound driver :D
    Huh. Kmod is good! I was originally going to post something like "Kmod can be wrong, or it can hide the inner details, or what not."
     
  9. Dr. Ivo

    Dr. Ivo

    Professional Reverse Engineer Tech Member
    I asked while I wasn't at my home computer. I've since found the code within an old SVN repository. The code does not have any special case to handle "premature invalidation"!

    With the incrementing index, if the code is expecting a DN press, yet an UP is recieved, the counter is reset to 0, meaning you have to press UP again. Therefore, U,U,U,D,L,R is valid, yet U,U,D,L,R is not valid.

    So, a spare UP invalidates a subsequent valid code entry. My curiousity was to see if this had been handled by a special case.


    The code from Sonic 2 has the same issue:

    Code (Text):
    1. ; loc_9746:
    2.  
    3. ; a0 = cheat 1 code address
    4. ; a1 = cheat memory to set to 01,01
    5. ; a2 = cheat 2 code address
    6. ; d2 = false (7 emeralds)  true (15 continues)
    7. CheckCheats:; This is called from 2 places: the options screen and the level select screen
    8.     move.w  (Correct_cheat_entries).w,d0; Get the number of correct sound IDs entered so far
    9.     adda.w  d0,a0   ; Skip to the next entry
    10.     move.w  (Sound_test_sound).w,d0; Get the current sound test sound
    11.     cmp.b   (a0),d0 ; Compare it to the cheat
    12.     bne.s   +   ; If they're different, branch
    13.     addq.w  #1,(Correct_cheat_entries).w; Add 1 to the number of correct entries
    14.     tst.b   1(a0)   ; Is the next entry 0?
    15.     bne.s   ++  ; If not, branch
    16.     move.w  #$101,(a1); Enable the cheat
    17.     move.b  #SndID_Ring,d0; Play the ring sound
    18.     bsr.w   JmpTo_PlaySound
    19. +
    20.     move.w  #0,(Correct_cheat_entries).w; Clear the number of correct entries
    21. +
    22.     move.w  (Correct_cheat_entries_2).w,d0; Do the same procedure with the other cheat
    23.     adda.w  d0,a2
    24.     move.w  (Sound_test_sound).w,d0
    25.     cmp.b   (a2),d0
    26.     bne.s   ++
    27.     addq.w  #1,(Correct_cheat_entries_2).w
    28.     tst.b   1(a2)
    29.     bne.s   +++; rts
    30.     tst.w   d2  ; Test this to determine which cheat to enable
    31.     bne.s   +   ; If not 0, branch
    32.     move.b  #$F,(Continue_count).w; Give 15 continues
    33. ; The next line causes the bug where the OOZ music plays until reset.
    34. ; Remove "&$7F" to fix the bug.
    35.     move.b  #SndID_ContinueJingle&$7F,d0; Play the continue jingle
    36.     bsr.w   JmpTo_PlayMusic
    37.     bra.s   ++
    38. ; ===========================================================================
    39. +
    40.     move.w  #7,(Got_Emerald).w; Give 7 emeralds to the player
    41.     move.b  #MusID_Emerald,d0; Play the emerald jingle
    42.     bsr.w   JmpTo_PlayMusic
    43. +
    44.     move.w  #0,(Correct_cheat_entries_2).w; Clear the number of correct entries
    45. +
    46.     rts
    47. ; ===========================================================================
    48. level_select_cheat: dc.b $19, $65,   9, $17,   0
    49. continues_cheat:    dc.b   1,   1,   2,   4,   0; byte_97B7
    50. debug_cheat:        dc.b   1,   9,   9,   2,   1,   1,   2,   4,   0
    51. super_sonic_cheat:  dc.b   4,   1,   2,   6,   0; byte_97C5
     
  10. Uberham

    Uberham

    King Of Oblivion Member
    I would've thought it would be as easy as "if up is pushed you then need a down, if down is then pushed you then need a left, if left is then pushed, you need a right, if right is then pushed then the code is valid, push A+START to unlock"
     
  11. This is easy for a human, not necessarily for a computer. Plus, specific logic for each cheat would need much more ROM space than the generic logic along with the lists with multiple cheats.

    But the actual behavior is not too far off from what you described... It's just that instead of hardcoded directions/buttons there is a pointer to a list indicating what to expect next. If the player gets it wrong, the pointer is moved back to the first element of the list.
     
  12. JoseTB

    JoseTB

    Tech Member
    716
    59
    28
    Makes sense, doesn't it? The game expects the player to input the cheat exactly as it is in a row, so that if you do a mistake, you have to start with the sequence again. I always thought this was intentional, anyway.

    Also worth to note that this approach was actually reused code from Ghouls 'n Ghosts, early Naka's work.
     
  13. Overlord

    Overlord

    Now playable in Smash Bros Ultimate Moderator
    19,218
    965
    93
    Long-term happiness
    Thing is, Dr Ivo's point is that the second U press, though yes a mistake is at the same time the first press in a new sequence - there should have been an exception coded for this case.
     
  14. Dr. Ivo

    Dr. Ivo

    Professional Reverse Engineer Tech Member
    JoseTB,

    If the player enters A, B, A, B, U, D, L, R -- it is counted as correct because the input stream "contains" the correct code. However, if the player enters A, B, A, B, U, U, D, L, R the correct code is missed, even though the input still contains the whole code in the correct sequence.

    It does force the correct sequence, but it also flat out breaks if the player enters the first element in the code a second time (but not a third time.) Too complex to be an intentionally implemented "rule." (Rule: Cheat code is considered valid as long as you've pressed an odd number of the first element in the code)

    This is the caveat I pointed out about this method (which is the simplest/fastest, but most broken.)

    Naka's job was to only to port Ghouls N' Ghosts from the arcade version. He was not the original programmer (there were three credited.) However, I do agree that this routine is probably his own.

    It's a little odd that it expects two addresses plus a flag. It would seem more reasonable to have the cheat code address references in the routine itself, and then "pass in" only a register to be used as a flag for which two to test and what the results should be. It looks to be either a last minute addition, or something that was modifed at the last minute.

    My main reason for posting this thread was to discover if there was a bufferless sequence validation routine that could genuinely "scan" a stream for the target sequence.

    Anyone know if the "Konami Code" breaks in this way?
     
  15. It is fairly easy to fix though: whenever an incorrect input is received and the pointer is sent back to the start of the sequence, check if the input matches the first element and move right on to the second. It shouldn't take more than 3 lines of assembly code.
     
  16. JoseTB

    JoseTB

    Tech Member
    716
    59
    28
    Okay, I missed this part, but you are completely right. I guess it would be easy to fix, probably they just didn't notice.

    I know, I think I used bad wording there, I was implying only the work on porting the game. I was under the impression he rewrote some things including that routine. But now that we mention it, it'd be interesting to check other versions of Ghouls 'n Ghosts to see if the cheat codes break the same way, and thus to find out if the code was actually written by Naka or ported as-is.
     
  17. The arcade version doesn't have this kind of cheat, does it? I think the routine is probably Naka's, but really, this is the most straightforward way to check for cheats, and I bet tons of other games have similar code.
     
  18. nineko

    nineko

    I am the Holy Cat Tech Member
    6,298
    475
    63
    italy
    When I added a cheat code to my hack, I made my own implementation. With my code you can enter the first number of the sequence as many times as you want.
     
Thread Status:
Not open for further replies.