Sonic and Sega Retro Message Board: Sonic 1 Level Select Code Input Detection? - Sonic and Sega Retro Message Board

Jump to content

Hey there, Guest!  (Log In · Register) Help
Loading News Feed...
 

Sonic 1 Level Select Code Input Detection? Exploring various methods of input pattern matching.

#1 User is offline Dr. Ivo 

  Posted 05 May 2009 - 12:12 PM

  • Professional Reverse Engineer
  • Posts: 42
  • Joined: 04-February 04
  • Gender:Male
  • Location:Philadelphia, PA
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?
This post has been edited by Dr. Ivo: 05 May 2009 - 12:13 PM

#2 User is online GerbilSoft 

Posted 05 May 2009 - 01:42 PM

  • RickRotate'd.
  • Posts: 1961
  • Joined: 11-January 03
  • Gender:Male
  • Location:USA
  • Project:Gens/GS
  • Wiki edits:158
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 User is offline Dr. Ivo 

Posted 05 May 2009 - 02:10 PM

  • Professional Reverse Engineer
  • Posts: 42
  • Joined: 04-February 04
  • Gender:Male
  • Location:Philadelphia, PA
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 User is online GerbilSoft 

Posted 05 May 2009 - 02:43 PM

  • RickRotate'd.
  • Posts: 1961
  • Joined: 11-January 03
  • Gender:Male
  • Location:USA
  • Project:Gens/GS
  • Wiki edits:158

View PostDr. Ivo, on May 5 2009, 03:10 PM, said:

Is this special case included?

I believe it is, but I can't test it at the moment.

#5 User is offline SMTP 

Posted 05 May 2009 - 02:53 PM

  • Posts: 2144
  • Joined: 27-April 04
  • Gender:Male
  • Location:Ohio
  • Wiki edits:59

View PostDr. Ivo, on May 5 2009, 01:12 PM, said:

- 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.



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

#6 User is offline Andlabs 

Posted 06 May 2009 - 08:55 PM

  • 「いっきまーす」
  • Posts: 2175
  • Joined: 11-July 08
  • Gender:Male
  • Project:Writing my own MD/Genesis sound driver :D
  • Wiki edits:7,061
Why did none of you just look at the fucking disassembly? This is the old Hivebrain one:

Title_RegionJ:		; XREF: Title_ChkRegion
		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.

Title_EnterCheat:	; XREF: Title_ChkRegion
		move.w	($FFFFFFE4).w,d0
		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.

		move.b	($FFFFF605).w,d0; get button press
		andi.b	#$F,d0; read only up/down/left/right buttons
		cmp.b	(a0),d0; does button press match the cheat code?
		bne.s	loc_3210; if not, branch
		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. :-)
This post has been edited by Andlabs: 06 May 2009 - 08:57 PM

#7 User is offline SMTP 

Posted 06 May 2009 - 09:06 PM

  • Posts: 2144
  • Joined: 27-April 04
  • Gender:Male
  • Location:Ohio
  • Wiki edits:59
Yea, which is exactly just like

Quote

- 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.


As I told him.

#8 User is offline Andlabs 

Posted 06 May 2009 - 09:08 PM

  • 「いっきまーす」
  • Posts: 2175
  • Joined: 11-July 08
  • Gender:Male
  • Project:Writing my own MD/Genesis sound driver :D
  • Wiki edits:7,061

View PostSMTP, on May 6 2009, 10:06 PM, said:

As I told him.


View PostSMTP, on May 5 2009, 03:53 PM, said:

when I use Kmod.


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 User is offline Dr. Ivo 

Posted 06 May 2009 - 09:55 PM

  • Professional Reverse Engineer
  • Posts: 42
  • Joined: 04-February 04
  • Gender:Male
  • Location:Philadelphia, PA
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:

; loc_9746:

; a0 = cheat 1 code address
; a1 = cheat memory to set to 01,01
; a2 = cheat 2 code address
; d2 = false (7 emeralds)  true (15 continues)
CheckCheats:; This is called from 2 places: the options screen and the level select screen
	move.w	(Correct_cheat_entries).w,d0; Get the number of correct sound IDs entered so far
	adda.w	d0,a0	; Skip to the next entry
	move.w	(Sound_test_sound).w,d0; Get the current sound test sound
	cmp.b	(a0),d0	; Compare it to the cheat
	bne.s	+	; If they're different, branch
	addq.w	#1,(Correct_cheat_entries).w; Add 1 to the number of correct entries
	tst.b	1(a0)	; Is the next entry 0?
	bne.s	++	; If not, branch
	move.w	#$101,(a1); Enable the cheat
	move.b	#SndID_Ring,d0; Play the ring sound
	bsr.w	JmpTo_PlaySound
+
	move.w	#0,(Correct_cheat_entries).w; Clear the number of correct entries
+
	move.w	(Correct_cheat_entries_2).w,d0; Do the same procedure with the other cheat
	adda.w	d0,a2
	move.w	(Sound_test_sound).w,d0
	cmp.b	(a2),d0
	bne.s	++
	addq.w	#1,(Correct_cheat_entries_2).w
	tst.b	1(a2)
	bne.s	+++; rts
	tst.w	d2	; Test this to determine which cheat to enable
	bne.s	+	; If not 0, branch
	move.b	#$F,(Continue_count).w; Give 15 continues
; The next line causes the bug where the OOZ music plays until reset.
; Remove "&$7F" to fix the bug.
	move.b	#SndID_ContinueJingle&$7F,d0; Play the continue jingle
	bsr.w	JmpTo_PlayMusic
	bra.s	++
; ===========================================================================
+
	move.w	#7,(Got_Emerald).w; Give 7 emeralds to the player
	move.b	#MusID_Emerald,d0; Play the emerald jingle
	bsr.w	JmpTo_PlayMusic
+
	move.w	#0,(Correct_cheat_entries_2).w; Clear the number of correct entries
+
	rts
; ===========================================================================
level_select_cheat:	dc.b $19, $65,   9, $17,   0
continues_cheat:	dc.b   1,   1,   2,   4,   0; byte_97B7
debug_cheat:		dc.b   1,   9,   9,   2,   1,   1,   2,   4,   0
super_sonic_cheat:	dc.b   4,   1,   2,   6,   0; byte_97C5

This post has been edited by Dr. Ivo: 07 May 2009 - 01:49 PM

#10 User is offline Uberham 

Posted 08 May 2009 - 11:04 AM

  • King Of Oblivion
  • Posts: 991
  • Joined: 23-February 08
  • Gender:Male
  • Location:Sheffield England
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 User is offline tokumaru 

Posted 08 May 2009 - 02:06 PM

  • Posts: 588
  • Joined: 17-June 07
  • Gender:Male
  • Location:Rio de Janeiro
  • Project:Platformer for the NES

View PostUberham, on May 8 2009, 01:04 PM, said:

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"

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 User is offline JoseTB 

Posted 08 May 2009 - 02:38 PM

  • Posts: 623
  • Joined: 01-June 04
  • Location:Spain
  • Wiki edits:4

View PostDr. Ivo, on May 7 2009, 04:55 AM, said:

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.


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 User is offline Overlord 

Posted 08 May 2009 - 04:08 PM

  • Что, вы ожидали что-то остроумное?
  • Posts: 13607
  • Joined: 12-January 03
  • Gender:Male
  • Location:Berkshire, England
  • Project:VGDB
  • Wiki edits:3,204

View PostJoseTB, on May 8 2009, 08:38 PM, said:

View PostDr. Ivo, on May 7 2009, 04:55 AM, said:

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.


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.

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 User is offline Dr. Ivo 

Posted 08 May 2009 - 04:08 PM

  • Professional Reverse Engineer
  • Posts: 42
  • Joined: 04-February 04
  • Gender:Male
  • Location:Philadelphia, PA
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?
This post has been edited by Dr. Ivo: 08 May 2009 - 04:25 PM

#15 User is offline tokumaru 

Posted 08 May 2009 - 07:17 PM

  • Posts: 588
  • Joined: 17-June 07
  • Gender:Male
  • Location:Rio de Janeiro
  • Project:Platformer for the NES

View PostDr. Ivo, on May 8 2009, 06:08 PM, said:

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

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.

  • 2 Pages +
  • 1
  • 2
    Locked
    Locked Forum

1 User(s) are reading this topic
0 members, 1 guests, 0 anonymous users