Actions

SCHG How-to

Difference between revisions of "Extend the Sonic 1 sprite mappings and art limit"

From Sonic Retro

m (category)
m (Text replacement - "\[\[Category:SCHG How-tos.*" to "")
 
(5 intermediate revisions by 3 users not shown)
Line 9: Line 9:
 
First thing's first, in routine "Sonic_Animate:" you'll find the following code:
 
First thing's first, in routine "Sonic_Animate:" you'll find the following code:
  
<asm>SAnim_Do2:
+
<syntaxhighlight lang="asm">SAnim_Do2:
 
moveq #0,d1
 
moveq #0,d1
 
move.b $1B(a0),d1 ; load current frame number
 
move.b $1B(a0),d1 ; load current frame number
Line 17: Line 17:
 
SAnim_Next:
 
SAnim_Next:
 
move.b d0,$1A(a0) ; load sprite number
 
move.b d0,$1A(a0) ; load sprite number
addq.b #1,$1B(a0) ; next frame number</asm>
+
addq.b #1,$1B(a0) ; next frame number</syntaxhighlight>
  
 
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:
 
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:
  
<asm>SAnim_Do2:
+
<syntaxhighlight lang="asm">SAnim_Do2:
 
moveq #0,d1
 
moveq #0,d1
 
move.b $1B(a0),d1 ; load current frame number
 
move.b $1B(a0),d1 ; load current frame number
 
move.b 1(a1,d1.w),d0 ; read sprite number from script
 
move.b 1(a1,d1.w),d0 ; read sprite number from script
bpl SAnim_Next ; MJ: if it's not negative, branch
 
 
cmp.b #$FD,d0 ; MJ: is it a flag from FD to FF?
 
cmp.b #$FD,d0 ; MJ: is it a flag from FD to FF?
bge SAnim_End_FF ; MJ: if so, branch to flag routines
+
bhs SAnim_End_FF ; MJ: if so, branch to flag routines
  
 
SAnim_Next:
 
SAnim_Next:
 
move.b d0,$1A(a0) ; load sprite number
 
move.b d0,$1A(a0) ; load sprite number
addq.b #1,$1B(a0) ; next frame number</asm>
+
addq.b #1,$1B(a0) ; next frame number</syntaxhighlight>
  
 
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.
 
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.
Line 41: Line 40:
 
The second thing that needs to be changed is the code that reads the mapping data offset in "BuildSprites:":
 
The second thing that needs to be changed is the code that reads the mapping data offset in "BuildSprites:":
  
<asm>loc_D700:
+
<syntaxhighlight lang="asm">loc_D700:
 
movea.l 4(a0),a1
 
movea.l 4(a0),a1
 
moveq #0,d1
 
moveq #0,d1
Line 51: Line 50:
 
move.b (a1)+,d1
 
move.b (a1)+,d1
 
subq.b #1,d1
 
subq.b #1,d1
bmi.s loc_D720</asm>
+
bmi.s loc_D720</syntaxhighlight>
  
 
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.
 
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.
Line 59: Line 58:
 
After the changes the routine should look like this:
 
After the changes the routine should look like this:
  
<asm>loc_D700:
+
<syntaxhighlight lang="asm">loc_D700:
 
movea.l 4(a0),a1
 
movea.l 4(a0),a1
 
moveq #0,d1
 
moveq #0,d1
Line 70: Line 69:
 
move.b (a1)+,d1
 
move.b (a1)+,d1
 
subq.b #1,d1
 
subq.b #1,d1
bmi.s loc_D720</asm>
+
bmi.s loc_D720</syntaxhighlight>
  
 
==Extending Sonic's art limit==
 
==Extending Sonic's art limit==
Line 76: Line 75:
 
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).
 
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:"
+
In routine "LoadSonicDynPLC:" ''(For HG disassembly users, this is "Sonic_LoadGfx:". You want to find the label "@readentry")''
  
<asm>SPLC_ReadEntry:
+
<syntaxhighlight lang="asm">SPLC_ReadEntry:
 
moveq #0,d2
 
moveq #0,d2
 
move.b (a2)+,d2
 
move.b (a2)+,d2
Line 87: Line 86:
 
lsl.w #5,d2
 
lsl.w #5,d2
 
lea (Art_Sonic).l,a1
 
lea (Art_Sonic).l,a1
adda.l d2,a1</asm>
+
adda.l d2,a1</syntaxhighlight>
  
 
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.
 
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.
Line 95: Line 94:
 
The routine should look like this when it's done:
 
The routine should look like this when it's done:
  
<asm>SPLC_ReadEntry:
+
<syntaxhighlight lang="asm">SPLC_ReadEntry:
 
moveq #0,d2
 
moveq #0,d2
 
move.b (a2)+,d2
 
move.b (a2)+,d2
Line 105: Line 104:
 
lsl.l #5,d2 ; MJ: shifting long-word instead of word (more than FFFF bytes)
 
lsl.l #5,d2 ; MJ: shifting long-word instead of word (more than FFFF bytes)
 
lea (Art_Sonic).l,a1
 
lea (Art_Sonic).l,a1
adda.l d2,a1</asm>
+
adda.l d2,a1</syntaxhighlight>
  
 
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.
 
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.
Line 115: Line 114:
 
So, at routine "AnimateSprite:" you'll see:
 
So, at routine "AnimateSprite:" you'll see:
  
<asm>Anim_Run:
+
<syntaxhighlight lang="asm">Anim_Run:
 
subq.b #1,$1E(a0) ; subtract 1 from frame duration
 
subq.b #1,$1E(a0) ; subtract 1 from frame duration
 
bpl.s Anim_Wait ; if time remains, branch
 
bpl.s Anim_Wait ; if time remains, branch
Line 126: Line 125:
 
bmi.s Anim_End_FF ; if animation is complete, branch
 
bmi.s Anim_End_FF ; if animation is complete, branch
  
Anim_Next:</asm>
+
Anim_Next:</syntaxhighlight>
  
 
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:
 
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:
  
<asm>Anim_Run:
+
<syntaxhighlight lang="asm">Anim_Run:
 
subq.b #1,$1E(a0) ; subtract 1 from frame duration
 
subq.b #1,$1E(a0) ; subtract 1 from frame duration
 
bpl.s Anim_Wait ; if time remains, branch
 
bpl.s Anim_Wait ; if time remains, branch
Line 139: Line 138:
 
move.b $1B(a0),d1 ; load current frame number
 
move.b $1B(a0),d1 ; load current frame number
 
move.b 1(a1,d1.w),d0 ; read sprite number from script
 
move.b 1(a1,d1.w),d0 ; read sprite number from script
bpl Anim_Next ; MJ: if it's not negative, branch
 
 
cmp.b #$FA,d0 ; MJ: is it a flag from FA to FF?
 
cmp.b #$FA,d0 ; MJ: is it a flag from FA to FF?
bge Anim_End_FF ; MJ: if so, branch to flag routines
+
bhs Anim_End_FF ; MJ: if so, branch to flag routines
  
Anim_Next:</asm>
+
Anim_Next:</syntaxhighlight>
  
 
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.
 
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.
Line 149: Line 147:
 
And there we have it, now objects can animate sprite mappings up to $79, have fun with the extended space!
 
And there we have it, now objects can animate sprite mappings up to $79, have fun with the extended space!
  
[[Category:SCHG How-tos|Extend the Sonic 1 sprite mappings and art limit]]
+
{{S1Howtos}}
 +
|Extend the Sonic 1 sprite mappings and art limit]]

Latest revision as of 10:51, 25 August 2018

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

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!

SCHG How-To Guide: Sonic the Hedgehog (16-bit)
Fixing Bugs
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 | Collide with Water After Being Hurt | 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
Adding Features
Add Spin Dash ( Part 1 / Part 2 / Part 3 / Part 4 ) | Add Eggman Monitor | Add Super Sonic | Add the Air Roll
Sound Features
Expand the Sound Index | Play Different Songs Per Act | Port Sonic 2 Final Sound Driver | Port Sonic 3's Sound Driver | Port Flamewing's Sonic 3 & Knuckles Sound Driver | Change The SEGA Sound
Extending the Game
Load Chunks From ROM | Add Extra Characters | Make an Alternative Title Screen | Use Dynamic Tilesets | Make GHZ Load Alternate Art | Make Ending 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
Miscellaneous
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)

|Extend the Sonic 1 sprite mappings and art limit]]