Port Knuckles into Sonic 2
From Sonic Retro
Revision as of 12:45, 27 May 2018 by MainMemory (talk | contribs) (Learn proper grammar and how to link to files on the wiki, also give better directions than "line 4531". And this whole bit is pointless without the rest of the code to make it use that art.)
(Original guide by MainMemory)
Note: this guide is written with the Sonic 2 GitHub disassembly in mind.
Contents
- 1 Getting the Knuckles object and putting it in Sonic 2
- 2 Importing Knuckles' art and mappings
- 3 Getting Knuckles ingame
- 4 Importing Knuckles' palettes
- 5 Fixing bugs
- 5.1 Monitors don't break when gliding
- 5.2 Knuckles gets hurt gliding into enemies
- 5.3 Knuckles doesn't behave properly when standing on objects
- 5.4 Knuckles doesn't bounce off bosses when gliding
- 5.5 Dust doesn't appear when Knuckles is sliding
- 5.6 Knuckles' height is incorrect when breathing a bubble
- 5.7 Knuckles' sprite is incorrect when exiting debug mode
- 5.8 Knuckles starts gliding during the WFZ end scene
- 5.9 Knuckles doesn't stop gliding on OOZ fans
- 5.10 Knuckles doesn't stop gliding on WFZ propellers
- 5.11 Knuckles doesn't stop gliding in WFZ wind tunnels
- 5.12 The level doesn't reset properly after dying
- 6 Importing graphics from KiS2
- 7 Ending Sequence
- 8 Adding Knuckles' sound effects from S3K
Getting the Knuckles object and putting it in Sonic 2
First you will need s2k.asm from the Knuckles in Sonic 2 disassembly.
Open s2k.asm, select everything from Obj01: (line 27324) to the end of ArtConvTable (line 30585), and copy it into a new asm file called Knuckles.asm in your S2 disasm folder. This is so we don't accidentally change something in our Sonic 2 disassembly while we're fixing Knuckles.
Open Knuckles.asm and do a Find and Replace operation to replace every instance of "Obj01" (without quotes) with "ObjXX", where XX is the object ID you want Knuckles to use. The following IDs are unused in Sonic 2: 4C, 4D, 4E, 4F, 62, D0, D1, and everything from DD to FF. Use that ID whenever I refer to ObjXX.
Next, do the following replacements: "CheckGameOver" to "ObjXX_CheckGameOver", "JmpTo_KillCharacter" to "JmpToK_KillCharacter", "Player_CheckFloor" to "Sonic_CheckFloor", "CheckCeilingDist" to "Sonic_CheckCeiling".
Find the line
bsr.w Adjust2PArtPointer2_Useless
and change it to:
jsr Adjust2PArtPointer
Now you'll need to copy some more code from s2k.asm. Specifically, sub_3192E6, loc_318FE8, sub_318FF6, loc_319208 and loc_3193D2. Because they're all small routines, I'll paste them here:
; =============== S U B R O U T I N E =======================================
; Doesn't exist in S2
sub_3192E6: ; ...
move.b $17(a0),d0
ext.w d0
sub.w d0,d2
eor.w #$F,d2
lea ($FFFFF768).w,a4
move.w #-$10,a3
move.w #$800,d6
bsr.w FindFloor
move.b #$80,d2
loc_319306:
bra.w loc_318FE8
; End of function sub_3192E6
; START OF FUNCTION CHUNK FOR CheckRightWallDist
loc_318FE8: ; ...
move.b ($FFFFF768).w,d3
btst #0,d3
beq.s return_318FF4
move.b d2,d3
return_318FF4: ; ...
rts
; END OF FUNCTION CHUNK FOR CheckRightWallDist
; =============== S U B R O U T I N E =======================================
sub_318FF6: ; ...
move.b $17(a0),d0
ext.w d0
add.w d0,d2
lea ($FFFFF768).w,a4
move.w #$10,a3
move.w #0,d6
bsr.w FindFloor
move.b #0,d2
bra.s loc_318FE8
; End of function sub_318FF6
; ---------------------------------------------------------------------------
; This doesn't exist in S2...
; START OF FUNCTION CHUNK FOR sub_315C22
loc_319208: ; ...
move.b $17(a0),d0
ext.w d0
add.w d0,d3
lea ($FFFFF768).w,a4
move.w #$10,a3
move.w #0,d6
bsr.w FindWall
move.b #$C0,d2
bra.w loc_318FE8
; END OF FUNCTION CHUNK FOR sub_315C22
; ---------------------------------------------------------------------------
; START OF FUNCTION CHUNK FOR sub_315C22
loc_3193D2: ; ...
move.b $17(a0),d0
ext.w d0
sub.w d0,d3
eor.w #$F,d3
lea ($FFFFF768).w,a4
move.w #$FFF0,a3
move.w #$400,d6
bsr.w FindWall
move.b #$40,d2
bra.w loc_318FE8
; END OF FUNCTION CHUNK FOR sub_315C22
Put this code at the end of Knuckles' asm.
Now we're ready to put the code in our S2 disassembly, so open s2.asm. You can place Knuckles' code wherever you want, but the place that seems to cause the fewest errors is right before Obj79. You can either add the line:
include "Knuckles.asm"
or just copy and paste his code in.
When you do that, run build.bat in the S2 disassembly. It should tell you there are 14 errors and to check s2.log, so that's what we'll do. Everything in the log will be in this format:
> > >s2.asm(line#): error: jump distance too big
> > > bsr.w label
What you need to do is go to each line and change the bsr.w to jsr. There is one line that's a bra.w instead, change that to jmp.
Importing Knuckles' art and mappings
If you try to build now, you will see three "symbol undefined" errors: SK_Map_Knuckles, SK_PLC_Knuckles and SK_ArtUnc_Knux. So let's fix those.
For SK_Map_Knuckles, you need to take Knuckles' sprite mappings from S&K and convert them to Sonic 2 format. There are two ways to do this:
- Using SonMapEd, set the Game Format setting to "Sonic 3 & Knuckles", load the mappings file, change the Game Format setting to "Sonic 2", and save the mappings as Knuckles.asm in the mappings/sprite folder of your S2 disasm.
- Using MappingsConverter, load the mappings with input set to Game: "Sonic 3 & Knuckles", Format: ASM and output set to Game: "Sonic 2", Format: ASM, and save the mappings as Knuckles.asm in the mappings/sprite folder of your S2 disasm.
- If you're using the MapMacros branch of the S2 disassembly as a base, you can also set the output format to "ASM with Macros".
SK_Map_Knuckles: include "mappings/sprite/Knuckles.asm"
For SK_PLC_Knuckles, you can simply take Knuckles' DPLCs from S&K and save them as Knuckles.asm in the mappings/spriteDPLC folder of your S2 disasm, since the DPLC format is the same for S3K's players and Sonic 2.
SK_PLC_Knuckles: include "mappings/spriteDPLC/Knuckles.asm"
For SK_ArtUnc_Knux, take Knuckles' art from S&K and save it as Knuckles.bin in the art/uncompressed folder of your S2 disasm.
align $20 SK_ArtUnc_Knux: BINCLUDE "art/uncompressed/Knuckles.bin"
Optimizing Knuckles' art loading
This step is optional, unless you use the Sonic 2 Clone Driver (v2), but is recommended for performance's sake.
You may have noticed that Knuckles' art file is the same file as S3K, even though the two games' palettes are organized differently. This is possible because Knuckles' code dynamically recolors all his sprites in LoadKnucklesDynPLC_Part2. This saves some space in the KiS2 ROM, but for a Sonic 2 hack, it's just wasting CPU, so we'll remove it.
Select everything under the line bmi.w return_31753E in LoadKnucklesDynPLC_Part2 up to (but not including) return_31753E, and delete it. Replace it with this code, modified from LoadSonicDynPLC_Part2:
move.w #tiles_to_bytes(ArtTile_ArtUnc_Sonic),d4 ; loc_1B86E: KPLC_ReadEntry: moveq #0,d1 move.w (a2)+,d1 move.w d1,d3 lsr.w #8,d3 andi.w #$F0,d3 addi.w #$10,d3 andi.w #$FFF,d1 lsl.l #5,d1 addi.l #SK_ArtUnc_Knux,d1 move.w d4,d2 add.w d3,d4 add.w d3,d4 jsr (QueueDMATransfer).l dbf d5,KPLC_ReadEntry ; repeat for number of entries
Then you can delete ArtConvTable and the comments above it.
Now you just need Knuckles' art converted to Sonic 2's palette, and replace Knuckles' art file (art/uncompressed/Knuckles.bin) with that.
Getting Knuckles ingame
At this point, you should be getting no errors when building, but you still can't play as Knuckles, because nothing references his code!
Go to Obj_Index in s2.asm, find the line corresponding to the object ID you gave Knuckles' code, and replace it with this:
ObjPtr_Knuckles: dc.l ObjXX ; KnucklesNow, go to s2.constants.asm, find the end of the ObjID list, and add
ObjID_Knuckles = id(ObjPtr_Knuckles)
Back in s2.asm, under InitPlayers, copy the first two lines under InitPlayers_Alone and paste them under InitPlayers_TailsAlone, changing the label after the bne to InitPlayers_KnucklesAlone. Then add the following lines under the rts at the end of InitPlayers_TailsAlone:
InitPlayers_KnucklesAlone: move.b #ObjID_Knuckles,(MainCharacter+id).w ; load ObjXX Knuckles object at $FFFFB000 move.b #ObjID_SpindashDust,(Sonic_Dust+id).w ; load Obj08 Knuckles' spindash dust/splash object at $FFFFD100 rts
Next go to OptionScreen_Choices and change the 3 to a 4 in the first line. This changes how many characters are available in the character select.
dc.l TextOptScr_KnucklesAloneThen go to TextOptScr_TailsAlone and add the line
TextOptScr_KnucklesAlone: menutxt "KNUCKLES ALONE "
Now build the game, run it, and choose Knuckles in the options menu, and you'll see... Blue Knuckles!
Importing Knuckles' palettes
Here are the palette files, put them in the art/palettes folder.
Add the following lines under Pal_Result for the palette data:
Pal_Knux: palette Knuckles.bin,SonicAndTails2.bin ; "Sonic and Miles" background palette (also usually the primary palette line) Pal_CPZ_K_U: palette CPZ Knux underwater.bin ; Chemical Plant Zone underwater palette Pal_ARZ_K_U: palette ARZ Knux underwater.bin ; Aquatic Ruin Zone underwater palette
Add these lines under PalPtr_Result to add them to the palette list:
PalPtr_Knux: palptr Pal_Knux, 0 PalPtr_CPZ_K_U: palptr Pal_CPZ_K_U, 0 PalPtr_ARZ_K_U: palptr Pal_ARZ_K_U, 0
In s2.constants.asm, find the end of the PalID list, and add the lines
PalID_Knux = id(PalPtr_Knux) ; 28 PalID_CPZ_K_U = id(PalPtr_CPZ_K_U) ; 29 PalID_ARZ_K_U = id(PalPtr_ARZ_K_U) ; 30
Now find Level_LoadPal in s2.asm, and replace everything until Level_WaterPal with
Level_LoadPal: moveq #PalID_BGND,d0 cmpi.w #3,(Player_mode).w ; are you playing as Knuckles? blt.s + ; if not, branch moveq #PalID_Knux,d0 ; load Knuckles' palette index + bsr.w PalLoad_Now ; load Sonic's palette line tst.b (Water_flag).w ; does level have water? beq.s Level_GetBgm ; if not, branch moveq #PalID_HPZ_U,d0 ; palette number $15 cmpi.b #hidden_palace_zone,(Current_Zone).w beq.s Level_WaterPal ; branch if level is HPZ moveq #PalID_CPZ_U,d0 ; palette number $16 cmpi.b #chemical_plant_zone,(Current_Zone).w bne.s Level_PalNotCPZ ; branch if level is not CPZ cmpi.w #3,(Player_mode).w ; are you playing as Knuckles? blt.s Level_WaterPal ; if not, branch moveq #PalID_CPZ_K_U,d0 bra.s Level_WaterPal ; branch Level_PalNotCPZ: moveq #PalID_ARZ_U,d0 ; palette number $17 cmpi.w #3,(Player_mode).w ; are you playing as Knuckles? blt.s Level_WaterPal ; if not, branch moveq #PalID_ARZ_K_U,d0
Super Knuckles
In s2k.asm, select everything from PalCycle_SuperKnuckles (line 3173) to Pal_KnucklesReds (line 3227) and paste it into s2.asm just before Pal_FadeTo. Then, under PalCycle_SuperSonic, add the lines
cmpi.w #3,(Player_mode).w ; are we Knuckles? beq.w PalCycle_SuperKnuckles ; if we are, run the Super Knuckles cycle instead
Fixing bugs
Monitors don't break when gliding
In SolidObject_Monitor_Sonic, just under bne.s Obj26_ChkOverEdge, add the following lines:
cmpi.b #ObjID_Knuckles,id(a1) beq.s SolidObject_Monitor_Knuckles
Then, under the rts, add
SolidObject_Monitor_Knuckles: cmp.b #1,$21(a1) beq.s + cmp.b #3,$21(a1) beq.s + cmpi.b #AniIDSonAni_Roll,anim(a1) bne.w SolidObject_cont + rts
Then, under loc_3F768, replace everything from cmpi.b #2,anim(a0) up to return_3F78A with
cmpi.b #2,anim(a0) beq.s Break_Monitor cmpi.b #ObjID_Knuckles,id(a0) bne.s return_3F78A cmp.b #1,$21(a0) beq.s Break_Monitor cmp.b #3,$21(a0) bne.s return_3F78A Break_Monitor: neg.w y_vel(a0) ; reverse Sonic's y-motion move.b #4,routine(a1) move.w a0,parent(a1)
Knuckles gets hurt gliding into enemies
Under Touch_Enemy, add the following lines:
cmpi.b #ObjID_Knuckles,id(a0) bne.s Touch_NotKnuckles cmp.b #1,$21(a0) beq.s + cmp.b #3,$21(a0) beq.s + Touch_NotKnuckles:
Knuckles doesn't behave properly when standing on objects
Under Sonic_ResetOnFloor_Part2, add the following lines:
cmpi.b #ObjID_Knuckles,id(a0) ; is this object ID Knuckles? beq.w Knuckles_ResetOnFloor_Part2 ; if it is, branch to the Knuckles version of this code
Knuckles doesn't bounce off bosses when gliding
Under Touch_Enemy, just before neg.w x_vel(a0) add the following lines:
cmpi.b #ObjID_Knuckles,id(a0) bne.s + cmp.b #1,$21(a0) bne.s + move.b #2,$21(a0) move.b #$21,anim(a0) +
Then add the same lines just before the neg.w x_vel(a0) under Touch_Enemy_Part2.
Dust doesn't appear when Knuckles is sliding
Change Obj08_CheckSkid to this:
Obj08_CheckSkid: movea.w parent(a0),a2 ; a2=character moveq #$10,d1 ; move y offset to d1 cmpi.b #AniIDSonAni_Stop,anim(a2) ; SonAni_Stop beq.s Obj08_SkidDust moveq #6,d1 ; move different y offset to d1 cmpi.b #ObjID_Knuckles,id(a2) ; playing as Knuckles? bne.s + cmpi.b #3,$21(a2) ; check for sliding beq.s Obj08_SkidDust + move.b #2,routine(a0) move.b #0,objoff_32(a0) rtsThen, under Obj08_SkidDust, change the line
addi.w #$10,y_pos(a1)to
add.w d1,y_pos(a1)
Knuckles' height is incorrect when breathing a bubble
Under loc_1FB0C, find the lines
cmpi.b #1,(a1) bne.s loc_1FBA8
and replace them with
cmpi.b #ObjID_Knuckles,id(a1) beq.s + cmpi.b #1,(a1) bne.s loc_1FBA8 +
Knuckles' sprite is incorrect when exiting debug mode
In loc_41C56, add the following lines under move.l #Mapunc_Sonic,mappings(a1):
cmpi.b #ObjID_Knuckles,id(a1) bne.s + move.l #SK_Map_Knuckles,mappings(a1) +
Knuckles starts gliding during the WFZ end scene
Under ObjB2_Jump_to_plane, find the linemove.w #((button_right_mask|button_A_mask)<<8)|button_right_mask|button_A_mask,(Ctrl_1_Logical).wand replace it with
move.w #(button_right_mask|button_A_mask)<<8,(Ctrl_1_Logical).w
Then, just above loc_3AB8A, add
addq.b #2,routine_secondary(a0) bra.s loc_3AB8A ObjB2_Jumping_to_ship: cmpi.w #$447,objoff_2A(a0) bcc.s loc_3AB8A move.w #button_A_mask<<8,(Ctrl_1_Logical).wUnder ObjB2_Main_WFZ_states, just before the last item, add
offsetTableEntry.w ObjB2_Jumping_to_ship
Knuckles doesn't stop gliding on OOZ fans
Under loc_2A990 go to the + label and insert the following lines:
cmpi.b #ObjID_Knuckles,id(a1) bne.s + clr.b $21(a1) +
Knuckles doesn't stop gliding on WFZ propellers
Under ObjB5_CheckPlayer go to the + label and insert the following lines:
cmpi.b #ObjID_Knuckles,id(a1) bne.s ObjB5_NotKnuckles clr.b $21(a1) ObjB5_NotKnuckles:
Knuckles doesn't stop gliding in WFZ wind tunnels
Under WindTunnel find the line bset #1,status(a1) and insert the following lines:
cmpi.b #ObjID_Knuckles,id(a1) bne.s + clr.b $21(a1) +
The level doesn't reset properly after dying
Under ObjXX_ResetLevel_Part2, change the linetst.w ($FFFFFFDC).wto
tst.w (Two_player_mode).w
Importing graphics from KiS2
Importing and PLC setup
In s2k.asm, copy everything from ArtNem_MiniKnuckles (line 75253) up to ArtNem_TitleSprites_1 (line 76054), and paste it anywhere you want in s2.asm, like after ArtNem_MiniTails.
Under PlrList_ResultsTails_End, add the lines
;--------------------------------------------------------------------------------------- ; Pattern load queue ; Knuckles life counter ;--------------------------------------------------------------------------------------- PlrList_KnucklesLife: plrlistheader plreq ArtTile_ArtNem_life_counter, ArtNem_KTELife PlrList_KnucklesLife_End ;--------------------------------------------------------------------------------------- ; PATTERN LOAD REQUEST LIST ; Standard 2 - loaded for every level ;--------------------------------------------------------------------------------------- PlrList_Std2Knuckles: plrlistheader plreq ArtTile_ArtNem_Checkpoint, ArtNem_Checkpoint plreq ArtTile_ArtNem_Powerups, ArtNem_Powerups plreq ArtTile_ArtNem_Powerups+$2C, ArtNem_MonitorIconsMod plreq ArtTile_ArtNem_Shield, ArtNem_InvincibilityShield PlrList_Std2Knuckles_End ;--------------------------------------------------------------------------------------- ; Pattern load queue ; Knuckles end of level results screen ;--------------------------------------------------------------------------------------- PlrList_ResultsKnuckles: plrlistheader plreq ArtTile_ArtNem_TitleCard, ArtNem_TitleCard plreq ArtTile_ArtNem_ResultsText, ArtNem_ResultsText plreq ArtTile_ArtNem_MiniCharacter, ArtNem_MiniKnuckles plreq ArtTile_ArtNem_Perfect, ArtNem_Perfect PlrList_ResultsKnuckles_End ;--------------------------------------------------------------------------------------- ; Pattern load queue ; End of level signpost ;--------------------------------------------------------------------------------------- PlrList_SignpostKnuckles: plrlistheader plreq ArtTile_ArtNem_Signpost, ArtNem_Signpost plreq ArtTile_ArtNem_Signpost+$22, ArtNem_Signpost_KnucklesPatch PlrList_SignpostKnuckles_End
Then under PLCptr_ResultsTails, add
PLCptr_KnucklesLife: offsetTableEntry.w PlrList_KnucklesLife PLCptr_Std2Knuckles: offsetTableEntry.w PlrList_Std2Knuckles PLCptr_ResultsKnuckles: offsetTableEntry.w PlrList_ResultsKnuckles PLCptr_SignpostKnuckles: offsetTableEntry.w PlrList_SignpostKnuckles
In s2.constants.asm, under PLCID_ResultsTails add the lines
PLCID_KnucklesLife = id(PLCptr_KnucklesLife) PLCID_Std2Knuckles = id(PLCptr_Std2Knuckles) PLCID_ResultsKnuckles = id(PLCptr_ResultsKnuckles) PLCID_SignpostKnuckles = id(PLCptr_SignpostKnuckles)
Loading the PLCs
Now that the art is in the PLC list, we need to tell the game to load it.
Under Level, find the line moveq #PLCID_Std2,d0 and replace everything from there up to Level_ClrRam with
bsr.w Level_SetPlayerMode moveq #PLCID_Std2,d0 cmpi.w #3,(Player_mode).w ; are we Knuckles? bne.s + ; if not, branch moveq #PLCID_Std2Knuckles,d0 ; load Knuckles' standard art + bsr.w LoadPLC moveq #PLCID_Miles1up,d0 tst.w (Two_player_mode).w bne.s + cmpi.w #2,(Player_mode).w bne.s Level_NoTails addq.w #PLCID_MilesLife-PLCID_Miles1up,d0 + tst.b (Graphics_Flags).w bpl.s + addq.w #PLCID_Tails1up-PLCID_Miles1up,d0 + bsr.w LoadPLC Level_NoTails: cmpi.w #3,(Player_mode).w bne.s Level_ClrRam moveq #PLCID_KnucklesLife,d0 bsr.w LoadPLC
In CheckLoadSignpostArt find the line moveq #PLCID_Signpost,d0 and under it, add
cmpi.w #3,(Player_mode).w bne.w LoadPLC2 ; load signpost art moveq #PLCID_SignpostKnuckles,d0
Additionally change the bne.s + under tst.w (Two_player_mode).w to bne.s ++
Then under Load_EndOfAct, insert the following just before jsr (LoadPLC2).l:
cmpi.w #3,(Player_mode).w bne.s + moveq #PLCID_ResultsKnuckles,d0 +
Life Icon mappings
In KiS2, Knuckles' initials in the life counter use palette line 1, but S2 uses line 2. To fix this, take this file and extract it to the mappings/sprite folder.
HUD_MapUnc_Knuckles: BINCLUDE "mappings/sprite/hud_k.bin"
Next go to BuildHUD, find the line lea (HUD_MapUnc_40A9A).l,a1 and add this below it:
cmpi.w #3,(Player_mode).w bne.s + lea (HUD_MapUnc_Knuckles).l,a1 +
Score Tally
To get the score tally to display Knuckles' name, add the following lines just before Obj6F_MapUnc_14ED0:
Map_Obj3A_Knuckles: offsetTable offsetTableEntry.w word_311BF6 offsetTableEntry.w word_14D1C offsetTableEntry.w word_14D5E offsetTableEntry.w word_14DA0 offsetTableEntry.w word_14DDA offsetTableEntry.w word_14BC8 offsetTableEntry.w word_14BEA offsetTableEntry.w word_14BF4 offsetTableEntry.w word_14BFE offsetTableEntry.w word_14DF4 offsetTableEntry.w word_14E1E offsetTableEntry.w word_14E50 offsetTableEntry.w word_14E82 offsetTableEntry.w word_14E8C offsetTableEntry.w word_14E96 word_311BF6: dc.w $B dc.w 5, $85C6, $82E3, $FF88 dc.w 5, $8584, $82C2, $FF98 dc.w 5, $85D8, $82EC, $FFA8 dc.w 5, $85B4, $82DA, $FFB8 dc.w 5, $85C6, $82E3, $FFC8 dc.w 5, $85C2, $82E1, $FFD8 dc.w 5, $8580, $82C0, $FFE8 dc.w 5, $85D0, $82E8, $FFF8 dc.w 5, $85B8, $82DC, $10 dc.w 5, $8588, $82C4, $20 dc.w 5, $85D4, $82EA, $2F
Then, in loc_140CE find the line move.l #Obj3A_MapUnc_14CBC,mappings(a1) and add the following under it:
cmpi.w #3,(Player_mode).w bne.s + move.l #Map_Obj3A_Knuckles,mappings(a1) +
Under the last results_screen_object in byte_14380, add
byte_14380_K: results_screen_object $28, $138, $B8, 2, 0 results_screen_object $200, $100, $CA, 4, 3 results_screen_object $240, $140, $CA, 6, 4 results_screen_object $278, $178, $BE, 8, 6 results_screen_object $350, $120, $120, 4, 9 results_screen_object $320, $120, $F0, 4, $A results_screen_object $330, $120, $100, 4, $B results_screen_object $340, $120, $110, $16, $E
Then, in loc_140AC find lea byte_14380(pc),a2 and under it add
cmpi.w #3,(Player_mode).w bne.s + lea byte_14380_K(pc),a2 +
Download this file and put it in the art/nemesis folder. Then, under ArtNem_TitleCard2 add the lines
even ArtNem_KnucklesK: BINCLUDE "art/nemesis/S2KnuxK.bin"Finally under PlrList_ResultsKnuckles (remember that?) add the line
plreq ArtTile_ArtNem_ResultsText+$16, ArtNem_KnucklesK
at the end of the list.
Title Card background
To make Knuckles' title card background green instead of red, go to LoadTitleCard0, and under bsr.w JmpTo2_NemDec add
cmpi.w #3,(Player_option).w bne.s LoadTitleCard_Art2 move.l #vdpComm(tiles_to_bytes(ArtTile_ArtNem_TitleCard+$5A),VRAM,WRITE),(VDP_control_port).l moveq #$F,d0 loc_312364: move.l #$44444444,(VDP_data_port).l dbf d0,loc_312364 LoadTitleCard_Art2:
CNZ slot machines
To get Casino Night Zone's slot machines to show Knuckles instead of Sonic, go to SlotMachine_GetPixelRow and add
cmpi.w #3,(Player_mode).w beq.s sub_325964
Then, under the rts, paste this routine from KiS2:
sub_325964: ; ... move.w d3,d0 lsr.w #8,d0 and.w #7,d0 move.b (a3,d0.w),d0 and.w #7,d0 beq.s loc_32598C ror.w #7,d0 lea (ArtUnc_CNZSlotPics).l,a2 add.w d0,a2 move.w d3,d0 and.w #$F8,d0 lsr.w #1,d0 add.w d0,a2 rts ; --------------------------------------------------------------------------- loc_32598C: ; ... lea (byte_33B1F0).l,a2 move.w d3,d0 and.w #$F8,d0 lsr.w #1,d0 add.w d0,a2 rts ; End of function sub_325964
Ending Sequence
Note: This section is not finished.
Under EndingSequence find the line move.w d0,(Ending_Routine).w and just above it add
cmpi.w #3,(Player_mode).w bne.s + moveq #6,d0 cmpi.b #7,(Emerald_count).w bne.s + addq.w #2,d0 +
Then under EndingSequence_LoadCharacterArt_Characters add the lines
offsetTableEntry.w EndingSequence_LoadCharacterArt_Knuckles ; 6 offsetTableEntry.w EndingSequence_LoadCharacterArt_Knuckles ; 8
at the end of the table.
Under the bra.w at the end of EndingSequence_LoadCharacterArt_Tails, add the lines
EndingSequence_LoadCharacterArt_Knuckles: move.l #vdpComm(tiles_to_bytes(ArtTile_EndingCharacter),VRAM,WRITE),(VDP_control_port).l lea (ArtNem_EndingKnuckles).l,a0 bra.w JmpTo_NemDec
Under EndingSequence_LoadFlickyArt_Flickies add the lines
offsetTableEntry.w EndingSequence_LoadFlickyArt_Bird ; 6 offsetTableEntry.w EndingSequence_LoadFlickyArt_Eagle ; 8
at the end of the table.
Under ObjCA, change the bit above st (Super_Sonic_flag).w to
addq.w #1,objoff_32(a0) cmpi.w #8,(Ending_Routine).w beq.s + cmpi.w #2,(Ending_Routine).w beq.s + bra.s ++ +
Under ObjCA_State5_States add the lines
offsetTableEntry.w loc_A2E0k ; 6 offsetTableEntry.w loc_A2EEk ; 8
at the end of the table.
Under the rts at the end of loc_A2F2, add the lines
loc_A2E0k: moveq #$10,d0 - move.b #ObjID_Knuckles,id(a1) ; load Sonic object move.b #$81,obj_control(a1) rts ; =========================================================================== loc_A2EEk: moveq #$12,d0 bra.s -
Under PaletteChangerDataIndex add the lines
offsetTableEntry.w off_310678 ;$10 offsetTableEntry.w off_310690 ;$12
at the end of the table.
Under off_133E0, add the lines
off_310678: C9PalInfo loc_1344C,Pal_3090BC, 0,$1F,4,7 off_310690: C9PalInfo loc_1344C,Pal_3090BC+$C0,0,$F,4,7
Then in s2k.asm, take everything from Pal_3090BC (line 11400) up to dword_30919C (line 11406) and paste it in s2.asm under Pal_AD3E.
Under loc_A30A find the line cmpi.w #2,(Ending_Routine).w and replace everything from there to move.b #AniIDSonAni_Walk,anim(a1) with:
cmpi.w #8,(Ending_Routine).w beq.s + cmpi.w #2,(Ending_Routine).w beq.s + bra.s ++ +
Under loc_A4B6, find the line move.b #7,mapping_frame(a0) and just above it, add
cmpi.w #8,(Ending_Routine).w beq.s +
Under off_A534, add the lines
offsetTableEntry.w loc_A53Ak ; 6 offsetTableEntry.w loc_A55C ; 8
at the end of the table.
Under the bra.s - at the end of loc_A582, add:
loc_A53Ak: move.w y_pos(a0),d0 subi.w #$1C,d0 move.w d0,y_pos(a1) move.w x_pos(a0),x_pos(a1) move.w #$500,anim(a1) move.w #$100,anim_frame_duration(a1) rts
Under off_A5FC, add the lines
offsetTableEntry.w byte_A602 ; 6 offsetTableEntry.w byte_A61E ; 8
at the end of the table.
Adding Knuckles' sound effects from S3K
You may notice that Knuckles is missing some sound effects for when he grabs a wall, lands from a glide, and slides along the ground. Since KiS2 uses S2's sound driver directly, they couldn't add these sounds, but you can.
Download this file and extract its contents to the sound folder of your disassembly.
Just above MusicPoint2 add the lines:
SonicDriverVer = 2 include "sound/_smps2asm_inc.asm"
Under SoundIndex, add the following lines anywhere before SndPtr__End:
SndPtr_WallGrab: rom_ptr_z80 Snd_WallGrab SndPtr_Land: rom_ptr_z80 Snd_Land SndPtr_Slide: rom_ptr_z80 Snd_Slide
Their position within the list determones their priority: anything below them will be interrupted by these sounds, and anything above them can interrupt them. I personally put them just above SndPtr_Ring.
Then after the end of Sound70, above the if line, add
Snd_WallGrab: include "sound/wallgrab.asm" Snd_Land: include "sound/land.asm" Snd_Slide: include "sound/slide.asm"
If you try to build now, you will likely get an error telling you the sound bank is too large. In that case, you can go above SoundIndex and add
align $8000 soundBankStart := *
This will put the sound effects in their own sound bank.
In s2.constants.asm anywhere between SndID__First and SndID__End (it doesn't matter where) add
SndID_WallGrab = id(SndPtr_WallGrab) SndID_Land = id(SndPtr_Land) SndID_Slide = id(SndPtr_Slide)
Now you're ready to get Knuckles to play the sounds.
Go to loc_315804 and replace the rts with:
move.w #SndID_WallGrab,d0 jmp PlaySound
Just above return_315900 add:
move.w #SndID_Land,d0 jmp PlaySound
Then under loc_315958, just above the rts add:
move.b (Timer_frames+1).w,d0 andi.b #7,d0 bne.s + moveq #SndID_Slide,d0 jmp PlaySound +
Now depending on where you placed the new sounds, you may have broken some of Knuckles' code which uses numbers rather than SndID constants.
To fix this you can search for every occurrence of PlaySound, and change the move.w line just above it to use a SndID constant.
Luckily for you, the SndID definitions in s2.constants.asm all have their original values as comments, so if you find
move.w #$A4,d0 jsr PlaySound
you can search the SndID list to find
SndID_Skidding = id(SndPtr_Skidding) ; A4
and you'll replace the $A4 with SndID_Skidding.
Once you do all this and test, you will notice that the sliding sound is not working. This is because a slight change to the driver is needed to get it to work. So open up s2.sounddriver.asm and locate this:
; zloc_285: ;zGetFrequency zFMSetFreq: ; 'a' holds a note to get frequency for sub 80h jr z,zFMDoRest ; If this is a rest, jump to zFMDoRest add a,(ix+5) ; Add current channel key offset (coord flag E9) add a,a ; Offset into Frequency table... add a,zFrequencies&0FFh ld (zloc_292+2),a ; store into the instruction after zloc_292 (self-modifying code) ; ld d,a ; adc a,(zFrequencies&0FF00h)>>8 ; sub d ; ld (zloc_292+3),a ; this is how you could store the high byte of the pointer too (unnecessary if it's in the right range) zloc_292:
There are no issues if the comments are not there; in any event, you want to edit it to match this:
; zloc_285: ;zGetFrequency zFMSetFreq: ; 'a' holds a note to get frequency for sub 80h jr z,zFMDoRest ; If this is a rest, jump to zFMDoRest add a,(ix+5) ; Add current channel key offset (coord flag E9) add a,a ; Offset into Frequency table... add a,zFrequencies&0FFh ld (zloc_292+2),a ; store into the instruction after zloc_292 (self-modifying code) ld d,a adc a,(zFrequencies&0FF00h)>>8 sub d ld (zloc_292+3),a ; this is how you could store the high byte of the pointer too (unnecessary if it's in the right range) zloc_292:
After you are done, find this:
;zloc_460 zPSGSetFreq: sub 81h ; a = a-$81 (zero-based index from lowest note) jr c,+ ; If carry (only time that happens if 80h because of earlier logic) this is a rest! add a,(ix+5) ; Add current channel key offset (coord flag E9) add a,a ; Multiply note value by 2 add a,zPSGFrequencies&0FFh ; Point to proper place in table ld (zloc_46D+2),a ; store into the instruction after zloc_46D (self-modifying code) zloc_46D:
and change it to this:
;zloc_460 zPSGSetFreq: sub 81h ; a = a-$81 (zero-based index from lowest note) jr c,+ ; If carry (only time that happens if 80h because of earlier logic) this is a rest! add a,(ix+5) ; Add current channel key offset (coord flag E9) add a,a ; Multiply note value by 2 add a,zPSGFrequencies&0FFh ; Point to proper place in table ld (zloc_46D+2),a ; store into the instruction after zloc_46D (self-modifying code) ld d,a adc a,(zPSGFrequencies&0FF00h)>>8 sub d ld (zloc_46D+3),a ; this is how you could store the high byte of the pointer too (unnecessary if it's in the right range) zloc_46D:
We will be moving the zFrequencies and zPSGFrequencies tables to they don't get in the way, and these changes load the high byte of the pointer too. So now time to move the tables: find this:
; This the note -> frequency setting lookup ; the same array is found at $729CE in Sonic 1, and at $C9C44 in Ristar ; zword_359: ensure1byteoffset 8Ch zPSGFrequencies: dw 356h, 326h, 2F9h, 2CEh, 2A5h, 280h, 25Ch, 23Ah dw 21Ah, 1FBh, 1DFh, 1C4h, 1ABh, 193h, 17Dh, 167h dw 153h, 140h, 12Eh, 11Dh, 10Dh, 0FEh, 0EFh, 0E2h dw 0D6h, 0C9h, 0BEh, 0B4h, 0A9h, 0A0h, 97h, 8Fh dw 87h, 7Fh, 78h, 71h, 6Bh, 65h, 5Fh, 5Ah dw 55h, 50h, 4Bh, 47h, 43h, 40h, 3Ch, 39h dw 36h, 33h, 30h, 2Dh, 2Bh, 28h, 26h, 24h dw 22h, 20h, 1Fh, 1Dh, 1Bh, 1Ah, 18h, 17h dw 16h, 15h, 13h, 12h, 11h, 0 ; ---------------------------------------------------------------------------
and delete it; then find this:
; lookup table of FM note frequencies for instruments and sound effects ensure1byteoffset 0C0h ; zbyte_534: zFrequencies: dw 025Eh,0284h,02ABh,02D3h,02FEh,032Dh,035Ch,038Fh,03C5h,03FFh,043Ch,047Ch dw 0A5Eh,0A84h,0AABh,0AD3h,0AFEh,0B2Dh,0B5Ch,0B8Fh,0BC5h,0BFFh,0C3Ch,0C7Ch dw 125Eh,1284h,12ABh,12D3h,12FEh,132Dh,135Ch,138Fh,13C5h,13FFh,143Ch,147Ch dw 1A5Eh,1A84h,1AABh,1AD3h,1AFEh,1B2Dh,1B5Ch,1B8Fh,1BC5h,1BFFh,1C3Ch,1C7Ch dw 225Eh,2284h,22ABh,22D3h,22FEh,232Dh,235Ch,238Fh,23C5h,23FFh,243Ch,247Ch dw 2A5Eh,2A84h,2AABh,2AD3h,2AFEh,2B2Dh,2B5Ch,2B8Fh,2BC5h,2BFFh,2C3Ch,2C7Ch dw 325Eh,3284h,32ABh,32D3h,32FEh,332Dh,335Ch,338Fh,33C5h,33FFh,343Ch,347Ch dw 3A5Eh,3A84h,3AABh,3AD3h,3AFEh,3B2Dh,3B5Ch,3B8Fh,3BC5h,3BFFh,3C3Ch,3C7Ch ; 96 entries
and delete it too; now find this:
; --------------------------------------------------------------------------- ;zbyte_FD8h zSFXPriority:
and edit it to match this:
; --------------------------------------------------------------------------- ; This the note -> frequency setting lookup ; the same array is found at $729CE in Sonic 1, and at $C9C44 in Ristar ; zword_359: zPSGFrequencies: dw 356h, 326h, 2F9h, 2CEh, 2A5h, 280h, 25Ch, 23Ah dw 21Ah, 1FBh, 1DFh, 1C4h, 1ABh, 193h, 17Dh, 167h dw 153h, 140h, 12Eh, 11Dh, 10Dh, 0FEh, 0EFh, 0E2h dw 0D6h, 0C9h, 0BEh, 0B4h, 0A9h, 0A0h, 97h, 8Fh dw 87h, 7Fh, 78h, 71h, 6Bh, 65h, 5Fh, 5Ah dw 55h, 50h, 4Bh, 47h, 43h, 40h, 3Ch, 39h dw 36h, 33h, 30h, 2Dh, 2Bh, 28h, 26h, 24h dw 22h, 20h, 1Fh, 1Dh, 1Bh, 1Ah, 18h, 17h dw 16h, 15h, 13h, 12h, 11h, 0, 0, 0 ; lookup table of FM note frequencies for instruments and sound effects ; zbyte_534: zFrequencies: dw 025Eh,0284h,02ABh,02D3h,02FEh,032Dh,035Ch,038Fh,03C5h,03FFh,043Ch,047Ch dw 0A5Eh,0A84h,0AABh,0AD3h,0AFEh,0B2Dh,0B5Ch,0B8Fh,0BC5h,0BFFh,0C3Ch,0C7Ch dw 125Eh,1284h,12ABh,12D3h,12FEh,132Dh,135Ch,138Fh,13C5h,13FFh,143Ch,147Ch dw 1A5Eh,1A84h,1AABh,1AD3h,1AFEh,1B2Dh,1B5Ch,1B8Fh,1BC5h,1BFFh,1C3Ch,1C7Ch dw 225Eh,2284h,22ABh,22D3h,22FEh,232Dh,235Ch,238Fh,23C5h,23FFh,243Ch,247Ch dw 2A5Eh,2A84h,2AABh,2AD3h,2AFEh,2B2Dh,2B5Ch,2B8Fh,2BC5h,2BFFh,2C3Ch,2C7Ch dw 325Eh,3284h,32ABh,32D3h,32FEh,332Dh,335Ch,338Fh,33C5h,33FFh,343Ch,347Ch dw 3A5Eh,3A84h,3AABh,3AD3h,3AFEh,3B2Dh,3B5Ch,3B8Fh,3BC5h,3BFFh,3C3Ch,3C7Ch ; 96 entries ; --------------------------------------------------------------------------- ;zbyte_FD8h zSFXPriority:
If you compare it with the old tables, you will see that zPSGFrequencies gained a couple of new entries; it is because of these new entries that we had to move all this stuff around. Also, the sliding sound uses these entries, which do not exist in the Sonic 2 driver; so it should now be fixed.