Extend the Sonic 1 sprite mappings and art limit
From Sonic Retro
(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.
Contents
Extending Sonic's animation limit
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).
Extending Sonic's map/sprite limit
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
Extending Sonic's art limit
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.
Extending animation limit (for all other objects)
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!
|Extend the Sonic 1 sprite mappings and art limit]]