Sonic 3 history speedrun, it was rushed and got DLC to finish it; a common practice nowadays. That DLC, however, was forwards compatible with Sonic 2, with other games unlocking a seed-generated blue spheres level, aside for Sonic 1 which unlocked a near-endless game. I was researching exactly what Sonic & Knuckles needs for a locked-on games rom header, and found a flaw in how it detects Sonic 1, but only Sonic 1. This is the code detecting Sonic 1s serial numbers, annotated by me because S&Ks disassembly is a wild west of documentation Code: sub_4CCA6: lea (BlueSpheresSerialsText).l,a1 moveq #4-1,d1 ; loop 4 valid serial .loop lea (LockonSerialNumber).l,a0 ; load locked game serial location moveq #0,d3 ; temporarily set as valid moveq #13-1,d2 ; loop checking 13 bytes .loopserialcheck move.b (a1)+,d0 ; get byte from valid serial cmp.b (a0)+,d0 ; compare to locked game serial ;cmpm.b (a0)+,(a1)+ ; ahem beq.s + ; if the same, don't set serial as invalid moveq #1,d3 ; invalid + dbf d2,.loopserialcheck tst.b d3 ; is serial a match with one of the valid ones? beq.s .fullbs ; if so, unlock full blue sphere dbf d1,.loop move.b #0,(Blue_spheres_mode).w move.b #-1,(Blue_spheres_progress_flag).w bsr.s sub_4CD18 ; this calculates a seed based on the locked games header .rts rts .fullbs move.b #1,(Blue_spheres_mode).w tst.b (Blue_spheres_menu_flag).w bne.s .rts clr.b (Blue_spheres_progress_flag).w move.l #0,(Blue_spheres_current_level).w move.l #$10203,(Blue_spheres_current_stage).w rts ; End of function sub_4CCA6 ; --------------------------------------------------------------------------- BlueSpheresSerialsText: dc.b "GM 00001009-0" ; REV00 (non-JP serial) dc.b "GM 00004049-0" ; REV01 (JP serial) ; 2 serials ; --------------------------------------------------------------------------- What this code does is check 13 bytes in the locked games serial with a list of pre-defined valid serials, notably excluding the last 14th byte. The code, while not the best, gets the job done... except for the obvious issue of checking for four serials when it only defines two. Before we theorize why, what's after those valid serials anyway? Code: sub_4CD18: lea (LockonSerialNumber).l,a1 moveq #$A,d1 loc_4CD20: move.b (a1),d0 subi.b #$30,d0 beq.s loc_4CD2E cmpi.b #$A,d0 blo.s loc_4CD34 loc_4CD2E: addq.w #1,a1 dbf d1,loc_4CD20 ... The code calculating the blue spheres seed for non-Sonic 1 games. Here's the valid data in full: Code: dc.b $43,$F9,$00,$20,$01,$80,$72,$0A,$10,$11,$04,$00,$00 ; S&K arbitrary serial 1 dc.b $30,$67,$06,$0C,$00,$00,$0A,$65,$06,$52,$49,$51,$C9 ; S&K arbitrary serial 2 So, why? Look at the code detecting Sonic 2 and Sonic 3 (which is better documented by default) Code: DetermineWhichGame: lea (LockonSerialsText).l,a1 moveq #4-1,d1 ; 3 Sonic 2 headers, 1 Sonic 3 header $$compareSerials: lea (LockonSerialNumber).l,a0 moveq #0,d3 moveq #14-1,d2 $$compareChars: move.b (a1)+,d0 cmp.b (a0)+,d0 beq.s $$matchingChar moveq #1,d3 $$matchingChar: dbf d2,$$compareChars tst.b d3 beq.s S2orS3LockedOn dbf d1,$$compareSerials bra.s BlueSpheresStartup ; --------------------------------------------------------------------------- S2orS3LockedOn: tst.w d1 beq.w SonicAndKnucklesStartup move.b #1,(SRAM_access_flag).l jmp ($300000).l ; May be changed at a later date to become compatible with S2K disassembly ; --------------------------------------------------------------------------- LockonSerialsText: dc.b "GM 00001051-00" ; Sonic 2 REV00/1/2 dc.b "GM 00001051-01" dc.b "GM 00001051-02" dc.b "GM MK-1079 -00" ; Sonic 3 The code is near identical to Sonic 1s detection, just with a different outcome for succeeded and failing, checking all 14 bytes, and actually having four serials to check Basically, I believe it was a copy/paste job. Now how is this useful? Basically, if you decide you want your game to unlock full blue spheres when locked onto Sonic and Knuckles, you don't need Sonic 1s serial number to do it. This can be useful in the rare case that an emulator treats Sonic 1 differently, like say, emulating the exact speed of the games rom, HD graphic packs, overclocking by default, or outright preventing it from loading. For homebrew games, it's also probably best to distance yourself from any proprietary Sega data (hasn't stopped like half of them from doing so anyway, but whatever). Might just be me, but there's also partially a technical flex to it, using a mistake from some dev in the 90s for a neat feature
Speaking of Lock-On Technology, VAdaPEGA recently found out that Knuckles in Sonic 2 directly calls Sonic 2's sound driver loading function directly from the Sonic 2 cartridge before patching the sound driver to fix sound data addresses. Basically, this means that you can set up a fake Sonic 2 header and an entry point where Sonic 2's sound driver loading routine is at to effectively allow any ROM to have an "& Knuckles" mode. Though, you will need to still be aware of the limitations of how cartridge ROMs get mapped alongside Sonic & Knuckles. I talked with him and he has given me permission to write up a guide on this sometime. EDIT: Bah, there's issues with SRAM, so this might not be the most reliable method. There is an alternate method though. When Sonic & 3 Knuckles boots up, it first decompresses the Sega logo into RAM. You can take advantage of this by setting up Kosinski data at the same address as that, in which it will clear out most of RAM, and then replace the return address for the call to whatever you want it to be. Though, if you want a more optimal set of data (839 ($347) bytes vs. 73151 ($11DBF) bytes), here's this: Code: dc.w %0101010101010101 dc.b $00 ; Write "00" dc.b $FF, $F8, $F3 ; Copy "00" 244 times dc.b $FF, $F8, $FF ; Copy "00" 256 times dc.b $FF, $F8, $FF ; Copy "00" 256 times dc.b $FF, $F8, $FF ; Copy "00" 256 times dc.b $FF, $F8, $FF ; Copy "00" 256 times dc.b $FF, $F8, $FF ; Copy "00" 256 times dc.b $FF, $F8, $FF ; Copy "00" 256 times rept 30 dc.w %0101010101010101 dc.b $FF, $F8, $FF ; Copy "00" 256 times dc.b $FF, $F8, $FF ; Copy "00" 256 times dc.b $FF, $F8, $FF ; Copy "00" 256 times dc.b $FF, $F8, $FF ; Copy "00" 256 times dc.b $FF, $F8, $FF ; Copy "00" 256 times dc.b $FF, $F8, $FF ; Copy "00" 256 times dc.b $FF, $F8, $FF ; Copy "00" 256 times dc.b $FF, $F8, $FF ; Copy "00" 256 times endr ; (Replace with "endm" if using AS) dc.w %0101010111110101 dc.b $FF, $F8, $FF ; Copy "00" 256 times dc.b $FF, $F8, $FF ; Copy "00" 256 times dc.b $FF, $F8, $FF ; Copy "00" 256 times dc.b $FF, $F8, $FF ; Copy "00" 256 times dc.b $FF, $F8, $FF ; Copy "00" 256 times dc.b $FF, $F8, $FF ; Copy "00" 256 times dc.b $FF, $F8, $FF ; Copy "00" 256 times dc.b $00, $00 ; Write "00 00" dc.w %0010111100000000 dc.b $00 ; Write "00" dc.l CPU_EntryPoint_SNK ; Write return address dc.b $00, $F0, $00 ; End of data even
I have always figured there would be a way to exploit lock-on to grab control and allow custom lock on behavior. Happy to see people who actually know what they're doing exploring the concept, could be fun :3