(Original guide by MarkeyJester)
If you have worked with editing Sonic's sprites for Sonic 1 then you may have noticed a few issues in-game when Sonic has more than a certain number of sprites. By default, Sonic 1 has a sprite map limit of $7F sprites in one set, and several parts of the game engine will prevent you from having more.
In this guide we look at expanding that size to something more generous.
First thing's first, in routine "Sonic_Animate:" you'll find the following code:
SAnim_Do2: moveq #0,d1 move.b $1B(a0),d1 ; load current frame number move.b 1(a1,d1.w),d0 ; read sprite number from script bmi.s SAnim_End_FF ; if animation is complete, branch SAnim_Next: move.b d0,$1A(a0) ; load sprite number addq.b #1,$1B(a0) ; next frame number
Now, the instruction with "bmi" is what is responsible for preventing the animation scripts of Sonic reaching passed $7F, "bmi" is an instruction that will branch if the value is negative, a negative value is from $80 to $FF which is no good to us, replace the code above, with this below:
SAnim_Do2: moveq #0,d1 move.b $1B(a0),d1 ; load current frame number move.b 1(a1,d1.w),d0 ; read sprite number from script cmp.b #$FD,d0 ; MJ: is it a flag from FD to FF? bhs SAnim_End_FF ; MJ: if so, branch to flag routines SAnim_Next: move.b d0,$1A(a0) ; load sprite number addq.b #1,$1B(a0) ; next frame number
This will ensure that values from $FD to $FF will branch to "SAnim_End_FF:" (where "bmi" used to branch to) and values from $00 to $FC will continue down to "SAnim_Next:", basically expaning the script reading from $00 - $7F to $00 - $FC.
(The reason we're not allowing it to go up to $FF is because values $FD, $FE and $FF are special flags for things like walking/running speed/rotation, etc).
The second thing that needs to be changed is the code that reads the mapping data offset in "BuildSprites:":
loc_D700: movea.l 4(a0),a1 moveq #0,d1 btst #5,d4 bne.s loc_D71C move.b $1A(a0),d1 add.b d1,d1 adda.w (a1,d1.w),a1 move.b (a1)+,d1 subq.b #1,d1 bmi.s loc_D720
What happens here is the map ID is loaded into d1 "move.b $1A(a0),d1", and it is then multiplied by 2 "add.b d1,d1", but this only multiplies a "byte" of data, so map ID's higher than $7F won't multiply properly (e.g. $80 x $02 = $100, only the the right two digits are read $00). So to fix this is very simple, the .b in "add.b d1,d1", change to .w for word. This will ensure values from $80 to $FC will multiply by 2 properly.
Because we changed that value from byte to word, we have caused a potential (and fatal) error that will cause the game to crash if a sprite map ID from $80 to $FC is set to show. What's causing this is the sprite counter, the line "move.b (a1)+,d1" will load the "counter" (the "counter" is used to set how many sprites to process in one map list), but it's loading a byte of data, this isn't a problem as that's what we want it to do, but our new "add.w d1,d1" has changed a "word" of data in d1, so the left side of the word is still in d1 and needs to be cleared, adding "moveq #$00,d1" above "move.b (a1)+,d1" will ensure that the register is cleared and ready to use.
After the changes the routine should look like this:
loc_D700: movea.l 4(a0),a1 moveq #0,d1 btst #5,d4 bne.s loc_D71C move.b $1A(a0),d1 add.w d1,d1 ; MJ: changed from byte to word (we want more than 7F sprites) adda.w (a1,d1.w),a1 moveq #$00,d1 ; MJ: clear d1 (because of our byte to word change) move.b (a1)+,d1 subq.b #1,d1 bmi.s loc_D720
OK, so the animation script reads up to $FC, the mappings can be read up to $FC too, however, having $FC sprites to use you'll no-doubt be using a lot of art, unfortunately Sonic 1 can only load art of Sonic's art offset from 0000 to FFFF (this is a maximum of $7FF tiles), we're going to expand it to 1FFFF (maximum of $FFF tiles).
In routine "LoadSonicDynPLC:" (For HG disassembly users, this is "Sonic_LoadGfx:". You want to find the label "@readentry")
SPLC_ReadEntry: moveq #0,d2 move.b (a2)+,d2 move.w d2,d0 lsr.b #4,d0 lsl.w #8,d2 move.b (a2)+,d2 lsl.w #5,d2 lea (Art_Sonic).l,a1 adda.l d2,a1
This section will load data from Sonic's DPLC getting the number of tiles to load, and the tile ID, the tile ID is multiplied by $20 to get the offset of Sonic's art, but, only a word is read so the offset may only go up to $FFFF, let's fix this by changing the .w in "lsl.w #5,d2" to .l, this will ensure that it muleitplies the value into long-word rather than just word.
However, d2 still contains the "number of tiles to load" counter on the far left nybble, so let's clear that nybble by putting "andi.w #$0FFF,d2" above "lsl.l #5,d2".
The routine should look like this when it's done:
SPLC_ReadEntry: moveq #0,d2 move.b (a2)+,d2 move.w d2,d0 lsr.b #4,d0 lsl.w #8,d2 move.b (a2)+,d2 andi.w #$0FFF,d2 ; MJ: clear the counter lsl.l #5,d2 ; MJ: shifting long-word instead of word (more than FFFF bytes) lea (Art_Sonic).l,a1 adda.l d2,a1
And that's it, that's all there is to it. You can now have Sonic sprites up to $FC, and have Sonic art the size of $1FFFF. Almost double the amount.
This next change is optional, we have only extended Sonic's limit but we haven't extended the limit of all the other objects in the game, seeing as we've already changed the code in "BuildSprites" to allow more than $7F sprite mappings globally, we may as well allow objects that use the "AnimateSprite:" routine to use more than $7F too.
So, at routine "AnimateSprite:" you'll see:
Anim_Run: subq.b #1,$1E(a0) ; subtract 1 from frame duration bpl.s Anim_Wait ; if time remains, branch add.w d0,d0 adda.w (a1,d0.w),a1 ; jump to appropriate animation script move.b (a1),$1E(a0) ; load frame duration moveq #0,d1 move.b $1B(a0),d1 ; load current frame number move.b 1(a1,d1.w),d0 ; read sprite number from script bmi.s Anim_End_FF ; if animation is complete, branch Anim_Next:
Just like we did in "Sonic_Animate:", we need to change that "bmi" code in the same way, for the very same reasons, there is only one difference however, we can only have the limit up to $F9 for objects as they have additional flags ($FA, $FB and $FC) for altering the routine counter, but it's still better than $7F, so let's change it:
Anim_Run: subq.b #1,$1E(a0) ; subtract 1 from frame duration bpl.s Anim_Wait ; if time remains, branch add.w d0,d0 adda.w (a1,d0.w),a1 ; jump to appropriate animation script move.b (a1),$1E(a0) ; load frame duration moveq #0,d1 move.b $1B(a0),d1 ; load current frame number move.b 1(a1,d1.w),d0 ; read sprite number from script cmp.b #$FA,d0 ; MJ: is it a flag from FA to FF? bhs Anim_End_FF ; MJ: if so, branch to flag routines Anim_Next:
As you can see, if the value is from $00 to $F9, it'll continue down to "Anim_Next:", whilst if the value is from $FA to $FF, it'll branch to "Anim_End_FF" to run through the flag routines.
And there we have it, now objects can animate sprite mappings up to $79, have fun with the extended space!
|SCHG How-To Guide: Sonic the Hedgehog (16-bit)|
|Fix Demo Playback | Fix a Race Condition with Pattern Load Cues | Fix the SEGA Sound | Display the Press Start Button Text | Fix the Level Select Menu | Fix the Hidden Points Bug | Fix Accidental Deletion of Scattered Rings | Fix Ring Timers | Fix the Walk-Jump Bug | Correct Drowning Bugs | Fix the Death Boundary Bug | Fix the Camera Follow Bug | Fix Song Restoration Bugs | Fix the HUD Blinking | Fix the Level Select Graphics Bug | Fix a remember sprite related bug|
|Changing Design Choices|
|Change Spike Behavior | Fix Special Stage Jumping Physics | Improve the Fade In\Fade Out Progression Routines | Fix Scattered Rings' Underwater Physics | Remove the Speed Cap | Port the REV01 Background Effects | Port Sonic 2's Level Art Loader | Retain Rings Between Acts | Add Sonic 2 (Simon Wai Prototype) Level Select | Improve ObjectMove Subroutines | Port Sonic 2 Level Select|
|Add Spin Dash ( Part 1 / Part 2 / Part 3 / Part 4 ) | Add Eggman Monitor | Add Super Sonic|
|Expand Music Index From $94 to $9F | Extend Music Slots | Play Different Songs Per Act | Expand Music Index to Start at $00 | Port Sonic 2 Final Sound Driver | Port Sonic 3's Sound Driver|
|Extending the Game|
|Load Chunks From ROM | Add Extra Characters | Make an Alternative Title Screen | Use Dynamic Tilesets | Make GHZ Load Alternate Art | Add a New Zone | Set Up the Goggle Monitor | Add New Moves | Add a Dynamic Collision System | Dynamic Special Stage Walls System | Extend Sprite Mappings and Art Limit | Enigma Credits | Use Dynamic Palettes|
|Convert the Hivebrain 2005 Disassembly to ASM68K|
|Split Disassembly Guides|
|Set Up a Split Disassembly | Basic Level Editing | Basic Art Editing | Basic ASM Editing (Spin Dash)|