Difference between revisions of "Set up the Goggle Monitor to work with it"
From Sonic Retro
m (Dammit >_>) |
Bendabest19 (talk | contribs) |
||
(20 intermediate revisions by 11 users not shown) | |||
Line 1: | Line 1: | ||
− | |||
− | == Quick guide == | + | (Original 3 sections of the Hivebrain guide by Selbi, the End of Level Air Reapply code by theocas, the Title Screen Air Reapply code by Bendabest19, final section of Hivebrain guide and the Github guide by Inferno Gear, Github guide based off oringial Hivebrain guide by Selbi, grammar fixes by makotoyuki.) |
+ | |||
+ | Did you know that there are 2 unused monitors in Sonic 1? The 'S' and 'Goggle' monitors. You can place and also destroy them, but nothing will happen. In the split, the code for the 'S' monitor is under "Obj2E_ChkS:" (all it has is just a check for the monitor subtype and a 'rts'), but there is no code for the Goggle monitor, which just branches to Obj2E_ChkS! If you want to have that monitor to do something, you need to set it up first. It's not too hard, so I will explain it to you step-by-step. | ||
+ | ==Hivebrain disassembly == | ||
+ | === Quick guide === | ||
I know there are many people who just want to rip off the code without learning because they think they have better things to do. For those people I took the time to make a simple copy/paste guide: | I know there are many people who just want to rip off the code without learning because they think they have better things to do. For those people I took the time to make a simple copy/paste guide: | ||
Search for ''Obj2E_ChkS:'' and replace everything from there with this: | Search for ''Obj2E_ChkS:'' and replace everything from there with this: | ||
− | <asm>Obj2E_ChkS: | + | <syntaxhighlight lang="asm">Obj2E_ChkS: |
cmpi.b #7,d0 ; does monitor contain 'S' | cmpi.b #7,d0 ; does monitor contain 'S' | ||
− | bne | + | bne Obj2E_ChkGoggles ; if not, branch to Goggle code |
nop | nop | ||
Obj2E_ChkGoggles: | Obj2E_ChkGoggles: | ||
cmpi.b #8,d0 ; does monitor contain Goggles? | cmpi.b #8,d0 ; does monitor contain Goggles? | ||
− | bne | + | bne Obj2E_ChkEnd ; if not, branch to ChkEnd |
− | nop</ | + | nop</syntaxhighlight> |
− | That was everything. If you are too dumb to add/ | + | That was everything. If you are too dumb to add/understand this, then you weren't reading the real guide. |
− | == Full guide == | + | === Full guide === |
− | This version tells you | + | This version tells you how to do this in detail, and very well explained. It's not more than what you can see in the quick version, but with comments and explanations you can learn something, so this one is recommended. |
First off, open ''sonic1.asm'' and go to the Routine for the monitor contents (Obj2E). Scroll down until you see this: | First off, open ''sonic1.asm'' and go to the Routine for the monitor contents (Obj2E). Scroll down until you see this: | ||
− | <asm> ... | + | <syntaxhighlight lang="asm"> ... |
Obj2E_RingSound: | Obj2E_RingSound: | ||
move.w #$B5,d0 | move.w #$B5,d0 | ||
Line 28: | Line 31: | ||
Obj2E_ChkS: | Obj2E_ChkS: | ||
cmpi.b #7,d0 ; does monitor contain 'S' | cmpi.b #7,d0 ; does monitor contain 'S' | ||
− | bne | + | bne Obj2E_ChkEnd |
nop | nop | ||
Line 38: | Line 41: | ||
subq.w #1,$1E(a0) | subq.w #1,$1E(a0) | ||
bmi.w DeleteObject | bmi.w DeleteObject | ||
− | rts</ | + | rts</syntaxhighlight> |
− | As you can see, there is no Goggle Code. But there is code that checks if the monitor contains 'S'. However, you can open [[SonED2]] and add a new monitor using the object editor. Scroll through the different monitors, and you will see, in Obj2E are the monitors in the same order as in SonED2. The Goggle monitor is the last monitor in this list. This means | + | As you can see, there is no Goggle Code. But there is code that checks if the monitor contains 'S'. However, you can open [[SonED2]] and add a new monitor using the object editor. Scroll through the different monitors, and you will see, in Obj2E are the monitors in the same order as in SonED2. The Goggle monitor is the last monitor in this list. This means that you will have to add your new code under the code for the S. |
Before Obj2E_ChkEnd add a new label. The most fitting one is ''Obj2E_ChkGoggles:'' in my opinion: | Before Obj2E_ChkEnd add a new label. The most fitting one is ''Obj2E_ChkGoggles:'' in my opinion: | ||
− | <asm>Obj2E_ChkS: | + | <syntaxhighlight lang="asm">Obj2E_ChkS: |
cmpi.b #7,d0 ; does monitor contain 'S' | cmpi.b #7,d0 ; does monitor contain 'S' | ||
− | bne | + | bne Obj2E_ChkEnd |
nop | nop | ||
Line 51: | Line 54: | ||
Obj2E_ChkEnd: | Obj2E_ChkEnd: | ||
− | rts ; 'S' and Goggles monitors do nothing</ | + | rts ; 'S' and Goggles monitors do nothing</syntaxhighlight> |
− | In ChkS is a code to branch to ChkEnd. Scroll up and you will see, there is in every monitor a code like this, but instead of branching to ChkEnd, they are branching to the next monitor code. So do the same with the S code. Change ''bne | + | In ChkS is a code to branch to ChkEnd. Scroll up and you will see, there is in every monitor a code like this, but instead of branching to ChkEnd, they are branching to the next monitor code. So do the same with the S code. Change ''bne Obj2E_ChkEnd'' to ''bne Obj2E_ChkGoggles'': |
− | <asm>Obj2E_ChkS: | + | <syntaxhighlight lang="asm">Obj2E_ChkS: |
cmpi.b #7,d0 ; does monitor contain 'S' | cmpi.b #7,d0 ; does monitor contain 'S' | ||
− | bne | + | bne Obj2E_ChkGoggles ; if not, branch to Goggle code |
nop | nop | ||
Line 62: | Line 65: | ||
Obj2E_ChkEnd: | Obj2E_ChkEnd: | ||
− | rts ; 'S' and Goggles monitors do nothing</ | + | rts ; 'S' and Goggles monitors do nothing</syntaxhighlight> |
− | Okay, we've set a few things up, but we are not done yet. Maybe you've noticed with the last step, those 2 lines in each monitor at the beginning (a 'cmpi.b' code and a 'bne | + | Okay, we've set a few things up, but we are not done yet. Maybe you've noticed with the last step, those 2 lines in each monitor at the beginning (a 'cmpi.b' code and a 'bne' code). They are there to check if that monitor was destroyed and if so, do the code under it, otherwise check for the next monitor. We need that for the goggle monitor as well, so copy any of these 2 lines to your ''Obj2E_ChkGoggles:'' label. I'm using the lines of ChkS: |
− | <asm>Obj2E_ChkS: | + | <syntaxhighlight lang="asm">Obj2E_ChkS: |
cmpi.b #7,d0 ; does monitor contain 'S' | cmpi.b #7,d0 ; does monitor contain 'S' | ||
− | bne | + | bne Obj2E_ChkGoggles ; if not, branch to Goggle code |
nop | nop | ||
Obj2E_ChkGoggles: | Obj2E_ChkGoggles: | ||
+ | bne Obj2E_ChkGoggles ; if not, branch to Goggle code | ||
+ | |||
cmpi.b #7,d0 ; does monitor contain 'S' | cmpi.b #7,d0 ; does monitor contain 'S' | ||
− | |||
− | |||
Obj2E_ChkEnd: | Obj2E_ChkEnd: | ||
− | rts ; 'S' and Goggles monitors do nothing</ | + | rts ; 'S' and Goggles monitors do nothing</syntaxhighlight> |
As you can see, this can't work, because they are checking for the same things! Look up again. Every monitor has a higher number in the cmpi.b command. The number in S was 7. So what is the next number? 8 of course! So change the 7 to 8 in your Goggle code: | As you can see, this can't work, because they are checking for the same things! Look up again. Every monitor has a higher number in the cmpi.b command. The number in S was 7. So what is the next number? 8 of course! So change the 7 to 8 in your Goggle code: | ||
− | <asm>Obj2E_ChkS: | + | <syntaxhighlight lang="asm">Obj2E_ChkS: |
cmpi.b #7,d0 ; does monitor contain 'S' | cmpi.b #7,d0 ; does monitor contain 'S' | ||
− | bne | + | bne Obj2E_ChkGoggles ; if not, branch to Goggle code |
nop | nop | ||
Obj2E_ChkGoggles: | Obj2E_ChkGoggles: | ||
cmpi.b #8,d0 ; does monitor contain 'S' | cmpi.b #8,d0 ; does monitor contain 'S' | ||
− | bne | + | bne Obj2E_ChkGoggles ; if not, branch to Goggle code |
Obj2E_ChkEnd: | Obj2E_ChkEnd: | ||
− | rts ; 'S' and Goggles monitors do nothing</ | + | rts ; 'S' and Goggles monitors do nothing</syntaxhighlight> |
− | 7: We are nearly done. The last thing we have to do is to change the bne | + | 7: We are nearly done. The last thing we have to do is to change the bne command in your Goggle code to ChkEnd. Like this: |
− | <asm>Obj2E_ChkS: | + | <syntaxhighlight lang="asm">Obj2E_ChkS: |
cmpi.b #7,d0 ; does monitor contain 'S' | cmpi.b #7,d0 ; does monitor contain 'S' | ||
− | bne | + | bne Obj2E_ChkGoggles ; if not, branch to Goggle code |
nop | nop | ||
Obj2E_ChkGoggles: | Obj2E_ChkGoggles: | ||
cmpi.b #8,d0 ; does monitor contain 'S' | cmpi.b #8,d0 ; does monitor contain 'S' | ||
− | bne | + | bne Obj2E_ChkEnd ; if not, branch to Goggle code |
Obj2E_ChkEnd: | Obj2E_ChkEnd: | ||
− | rts ; 'S' and Goggles monitors do nothing</ | + | rts ; 'S' and Goggles monitors do nothing</syntaxhighlight> |
− | Now we | + | Now we're done, and you can insert code to run when you destroy a Goggle monitor. But if you don't wanna add code for now, you have to add a ''nop'' under the bne command, otherwise you will get an '''Illegal zero length branch''' error when building. Also, you should change the comments, because they are really misleading. Here is my finished code: |
− | <asm>Obj2E_ChkS: | + | <syntaxhighlight lang="asm">Obj2E_ChkS: |
cmpi.b #7,d0 ; does monitor contain 'S' | cmpi.b #7,d0 ; does monitor contain 'S' | ||
− | bne | + | bne Obj2E_ChkGoggles ; if not, branch to Goggle code |
nop | nop | ||
Obj2E_ChkGoggles: | Obj2E_ChkGoggles: | ||
cmpi.b #8,d0 ; does monitor contain Goggles? | cmpi.b #8,d0 ; does monitor contain Goggles? | ||
− | bne | + | bne Obj2E_ChkEnd ; if not, branch to ChkEnd |
nop | nop | ||
Obj2E_ChkEnd: | Obj2E_ChkEnd: | ||
− | rts</ | + | rts</syntaxhighlight> |
− | == Example Code == | + | === Example Code === |
Although that code is perfect now, it still does nothing. Unless you already have plans to do something with it, you can use this very simple code, that gives infinite air in LZ. | Although that code is perfect now, it still does nothing. Unless you already have plans to do something with it, you can use this very simple code, that gives infinite air in LZ. | ||
Go to the Goggle code and add a line, which is moving $1 to an unused RAM adress: | Go to the Goggle code and add a line, which is moving $1 to an unused RAM adress: | ||
− | <asm> move.b #1,($FFFFFFA0).w ; move 1 to the goggle check</ | + | <syntaxhighlight lang="asm"> move.b #1,($FFFFFFA0).w ; move 1 to the goggle check</syntaxhighlight> |
It should look like this: | It should look like this: | ||
− | <asm> ... | + | <syntaxhighlight lang="asm"> ... |
nop | nop | ||
Obj2E_ChkGoggles: | Obj2E_ChkGoggles: | ||
cmpi.b #8,d0 ; does monitor contain Goggles? | cmpi.b #8,d0 ; does monitor contain Goggles? | ||
− | bne | + | bne Obj2E_ChkEnd ; if not, branch to ChkEnd |
− | move.b #1,($ | + | move.b #1,($FFFFFFA0).w ; move 1 to the goggle check |
Obj2E_ChkEnd: | Obj2E_ChkEnd: | ||
− | ...</ | + | ...</syntaxhighlight> |
Next go to to ''Obj0A_ReduceAir:'' and add these 2 lines right after the label: | Next go to to ''Obj0A_ReduceAir:'' and add these 2 lines right after the label: | ||
− | <asm> tst.b ($ | + | <syntaxhighlight lang="asm"> tst.b ($FFFFFFA0).w ; was a goggle monitor broken? |
− | bne. | + | bne Obj0A_GoMakeItem ; if yes, branch</syntaxhighlight> |
+ | |||
+ | It should look like this: | ||
+ | <syntaxhighlight lang="asm">Obj0A_ReduceAir: | ||
+ | tst.b ($FFFFFFA0).w ; was a goggle monitor broken? | ||
+ | bne Obj0A_GoMakeItem ; if yes, branch | ||
+ | ...</syntaxhighlight> | ||
+ | |||
+ | === Fixing the infinite air glitch === | ||
+ | The code now works perfectly, but now you have infinite air forever. There are many ways you could go about fixing this, but theocas on the SSRG version of this tutorial proposed moving $0 the ram that signifies that a goggle monitor has been hit. This works for our purposes, so let's use that. | ||
+ | Place the following code at the end of Obj0D_Touch, but before locret_EBBA: | ||
+ | <syntaxhighlight lang="asm"> ... | ||
+ | move.b #0,($FFFFFFA0).w ; move 0 to the goggle check | ||
+ | ...</syntaxhighlight> | ||
+ | Now, after touching the signpost of any level, $0 is moved to $FFFFFFA0, making the game go through the normal code for reducing Sonic's amount of air underwater, which means Sonic no longer has an infinite amount of air forever. | ||
+ | |||
+ | ==GitHub disassembly == | ||
+ | === Quick guide === | ||
+ | I know there are many people who just want to rip off the code without learning because they think they have better things to do. For those people I took the time to make a simple copy/paste guide: | ||
+ | |||
+ | In _incObj\2E Monitor Content Power-Up.asm, search for ''Pow_ChkS:'' and replace everything from there with this: | ||
+ | <syntaxhighlight lang="asm">Pow_ChkS: | ||
+ | cmpi.b #7,d0 ; does monitor contain 'S' | ||
+ | bne.s Pow_ChkGoggles ; if not, branch to Goggle code | ||
+ | nop | ||
+ | |||
+ | Pow_ChkGoggles: | ||
+ | cmpi.b #8,d0 ; does monitor contain Goggles? | ||
+ | bne.s Pow_ChkEnd ; if not, branch to Pow_ChkEnd | ||
+ | nop</syntaxhighlight> | ||
+ | That was everything. If you are too dumb to add/understand this, then you weren't reading the real guide. | ||
+ | |||
+ | === Full guide === | ||
+ | This version tells you how to do this in detail, and very well explained. It's not more than what you can see in the quick version, but with comments and explanations you can learn something, so this one is recommended. | ||
+ | |||
+ | First off, open ''_incObj\2E Monitor Content Power-Up.asm' and go to the Routine for the S monitor contents (Pow_ChkS). You should see this. | ||
+ | <syntaxhighlight lang="asm"> ... | ||
+ | Pow_RingSound: | ||
+ | music sfx_Ring,1,0,0 ; play ring sound | ||
+ | ; =========================================================================== | ||
+ | |||
+ | Pow_ChkS: | ||
+ | cmpi.b #7,d0 ; does monitor contain 'S' | ||
+ | bne.s Pow_ChkEnd | ||
+ | nop | ||
+ | |||
+ | Pow_ChkEnd: | ||
+ | rts ; 'S' and Goggles monitors do nothing | ||
+ | ; =========================================================================== | ||
+ | |||
+ | Pow_Delete: ; XREF: Obj2E_Index | ||
+ | subq.w #1,obTimeFrame(a0) | ||
+ | bmi.w DeleteObject ; delete after half a second | ||
+ | rts</syntaxhighlight> | ||
+ | |||
+ | As you can see, there is no Goggle Code. But there is code that checks if the monitor contains 'S'. However, you can open [[SonED2]] and add a new monitor using the object editor. Scroll through the different monitors, and you will see, in ''_incObj\2E Monitor Content Power-Up.asm' are the monitors in the same order as in SonED2. The Goggle monitor is the last monitor in this list. This means that you will have to add your new code under the code for the S. | ||
+ | |||
+ | Before Pow_ChkEnd add a new label. The most fitting one is ''Pow_ChkGoggles:'' in my opinion: | ||
+ | <syntaxhighlight lang="asm">Pow_ChkS: | ||
+ | cmpi.b #7,d0 ; does monitor contain 'S' | ||
+ | bne.s Pow_ChkEnd | ||
+ | nop | ||
+ | |||
+ | Pow_ChkGoggles: | ||
+ | |||
+ | Pow_ChkEnd: | ||
+ | rts ; 'S' and Goggles monitors do nothing</syntaxhighlight> | ||
+ | |||
+ | In ChkS is a code to branch to ChkEnd. Scroll up and you will see, there is in every monitor a code like this, but instead of branching to ChkEnd, they are branching to the next monitor code. So do the same with the S code. Change ''bne.s Pow_ChkEnd'' to ''bne.s Pow_ChkGoggles'': | ||
+ | <syntaxhighlight lang="asm">Pow_ChkS: | ||
+ | cmpi.b #7,d0 ; does monitor contain 'S' | ||
+ | bne.s Pow_ChkGoggles ; if not, branch to Goggle code | ||
+ | nop | ||
+ | |||
+ | Pow_ChkGoggles: | ||
+ | |||
+ | Pow_ChkEnd: | ||
+ | rts ; 'S' and Goggles monitors do nothing</syntaxhighlight> | ||
+ | |||
+ | Okay, we've set a few things up, but we are not done yet. Maybe you've noticed with the last step, those 2 lines in each monitor at the beginning (a 'cmpi.b' code and a 'bne.s' code). They are there to check if that monitor was destroyed and if so, do the code under it, otherwise check for the next monitor. We need that for the goggle monitor as well, so copy any of these 2 lines to your ''Pow_ChkGoggles:'' label. I'm using the lines of ChkS: | ||
+ | <syntaxhighlight lang="asm">Pow_ChkS: | ||
+ | cmpi.b #7,d0 ; does monitor contain 'S' | ||
+ | bne.s Pow_ChkGoggles ; if not, branch to Goggle code | ||
+ | nop | ||
+ | |||
+ | Pow_ChkGoggles: | ||
+ | bne.s Pow_ChkGoggles ; if not, branch to Goggle code | ||
+ | |||
+ | cmpi.b #7,d0 ; does monitor contain 'S' | ||
+ | Pow_ChkEnd: | ||
+ | rts ; 'S' and Goggles monitors do nothing</syntaxhighlight> | ||
+ | |||
+ | As you can see, this can't work, because they are checking for the same things! Look up again. Every monitor has a higher number in the cmpi.b command. The number in S was 7. So what is the next number? 8 of course! So change the 7 to 8 in your Goggle code: | ||
+ | <syntaxhighlight lang="asm">Pow_ChkS: | ||
+ | cmpi.b #7,d0 ; does monitor contain 'S' | ||
+ | bne.s Pow_ChkGoggles ; if not, branch to Goggle code | ||
+ | nop | ||
+ | |||
+ | Pow_ChkGoggles: | ||
+ | cmpi.b #8,d0 ; does monitor contain 'S' | ||
+ | bne.s Pow_ChkGoggles ; if not, branch to Goggle code | ||
+ | |||
+ | Pow_ChkEnd: | ||
+ | rts ; 'S' and Goggles monitors do nothing</syntaxhighlight> | ||
+ | |||
+ | 7: We are nearly done. The last thing we have to do is to change the bne command in your Goggle code to ChkEnd. Like this: | ||
+ | <syntaxhighlight lang="asm">Pow_ChkS: | ||
+ | cmpi.b #7,d0 ; does monitor contain 'S' | ||
+ | bne.s Pow_ChkGoggles ; if not, branch to Goggle code | ||
+ | nop | ||
+ | |||
+ | Pow_ChkGoggles: | ||
+ | cmpi.b #8,d0 ; does monitor contain 'S' | ||
+ | bne.s Pow_ChkEnd ; if not, branch to Goggle code | ||
+ | |||
+ | Pow_ChkEnd: | ||
+ | rts ; 'S' and Goggles monitors do nothing</syntaxhighlight> | ||
+ | |||
+ | Now we're done, and you can insert code to run when you destroy a Goggle monitor. But if you don't wanna add code for now, you have to add a ''nop'' under the bne command, otherwise you will get an '''Illegal zero length branch''' error when building. Also, you should change the comments, because they are really misleading. Here is my finished code: | ||
+ | <syntaxhighlight lang="asm">Pow_ChkS: | ||
+ | cmpi.b #7,d0 ; does monitor contain 'S' | ||
+ | bne.s Pow_ChkGoggles ; if not, branch to Goggle code | ||
+ | nop | ||
+ | |||
+ | Pow_ChkGoggles: | ||
+ | cmpi.b #8,d0 ; does monitor contain Goggles? | ||
+ | bne.s Pow_ChkEnd ; if not, branch to ChkEnd | ||
+ | nop | ||
+ | |||
+ | Pow_ChkEnd: | ||
+ | rts</syntaxhighlight> | ||
+ | |||
+ | === Example Code === | ||
+ | Although that code is perfect now, it still does nothing. Unless you already have plans to do something with it, you can use this very simple code, that gives infinite air in LZ. | ||
+ | |||
+ | ==== Preparation ==== | ||
+ | In Variables.asm, at the end of the file in a good spot, put the following piece of code. | ||
+ | <syntaxhighlight lang="asm">f_gogglecheck = ramaddr ( $FFFFFFA0 ) ; goggle flag</syntaxhighlight> | ||
+ | |||
+ | ==== Adding the code ==== | ||
+ | |||
+ | Go to the Goggle code and add a line, which is moving $1 to an unused RAM adress: | ||
+ | <syntaxhighlight lang="asm"> move.b #1,(f_gogglecheck).w ; move 1 to the goggle check</syntaxhighlight> | ||
It should look like this: | It should look like this: | ||
− | <asm> | + | <syntaxhighlight lang="asm"> ... |
− | tst.b ( | + | nop |
− | bne. | + | |
− | ...</asm> | + | Pow_ChkGoggles: |
+ | cmpi.b #8,d0 ; does monitor contain Goggles? | ||
+ | bne Pow_ChkEnd ; if not, branch to ChkEnd | ||
+ | move.b #1,(f_gogglecheck).w ; move 1 to the goggle check | ||
+ | |||
+ | Pow_ChkEnd: | ||
+ | ...</syntaxhighlight> | ||
+ | |||
+ | Next find _incObj\0A Drowning Countdown and find ''.reduceair / @reduceair'' and add these 2 lines right after the label: | ||
+ | <syntaxhighlight lang="asm"> tst.b (f_gogglecheck).w ; was a goggle monitor broken? | ||
+ | bne @gotomakenum ; if yes, branch</syntaxhighlight> | ||
+ | |||
+ | It should look like this: | ||
+ | <syntaxhighlight lang="asm">@reduceair | ||
+ | tst.b (f_gogglecheck).w ; was a goggle monitor broken? | ||
+ | bne @gotomakenum ; if yes, branch | ||
+ | ...</syntaxhighlight> | ||
+ | |||
+ | === Fixing the infinite air glitch === | ||
+ | The code now works perfectly, but now you have infinite air forever. There are many ways you could go about fixing this, but theocas on the SSRG version of this tutorial proposed moving $0 the ram that signifies that a goggle monitor has been hit. This works for our purposes, so let's use that. | ||
+ | Place the following code in _incObj\0D Signpost.asm and place it at the end of Sign_Touch, but before @notouch: | ||
+ | <syntaxhighlight lang="asm"> ... | ||
+ | move.b #0,(f_gogglecheck).w ; move 0 to the goggle check | ||
+ | ...</syntaxhighlight> | ||
+ | Now, after touching the signpost of any level, $0 is moved to f_gogglecheck/$FFFFFFA0, making the game go through the normal code for reducing Sonic's amount of air underwater, which means Sonic no longer has an infinite amount of air forever. | ||
+ | Also in sonic.asm place the previous code into the beginning of "Tit_MainLoop" This makes it so that if you get a game over or soft reset the game Sonic will not have an infinite amount of air. | ||
− | |||
− | + | {{S1Howtos}} | |
+ | |Set up the Goggle Monitor to work with it]] |
Latest revision as of 19:49, 8 August 2023
(Original 3 sections of the Hivebrain guide by Selbi, the End of Level Air Reapply code by theocas, the Title Screen Air Reapply code by Bendabest19, final section of Hivebrain guide and the Github guide by Inferno Gear, Github guide based off oringial Hivebrain guide by Selbi, grammar fixes by makotoyuki.)
Did you know that there are 2 unused monitors in Sonic 1? The 'S' and 'Goggle' monitors. You can place and also destroy them, but nothing will happen. In the split, the code for the 'S' monitor is under "Obj2E_ChkS:" (all it has is just a check for the monitor subtype and a 'rts'), but there is no code for the Goggle monitor, which just branches to Obj2E_ChkS! If you want to have that monitor to do something, you need to set it up first. It's not too hard, so I will explain it to you step-by-step.
Contents
Hivebrain disassembly
Quick guide
I know there are many people who just want to rip off the code without learning because they think they have better things to do. For those people I took the time to make a simple copy/paste guide:
Search for Obj2E_ChkS: and replace everything from there with this:
Obj2E_ChkS:
cmpi.b #7,d0 ; does monitor contain 'S'
bne Obj2E_ChkGoggles ; if not, branch to Goggle code
nop
Obj2E_ChkGoggles:
cmpi.b #8,d0 ; does monitor contain Goggles?
bne Obj2E_ChkEnd ; if not, branch to ChkEnd
nop
That was everything. If you are too dumb to add/understand this, then you weren't reading the real guide.
Full guide
This version tells you how to do this in detail, and very well explained. It's not more than what you can see in the quick version, but with comments and explanations you can learn something, so this one is recommended.
First off, open sonic1.asm and go to the Routine for the monitor contents (Obj2E). Scroll down until you see this:
...
Obj2E_RingSound:
move.w #$B5,d0
jmp (PlaySound).l ; play ring sound
; ===========================================================================
Obj2E_ChkS:
cmpi.b #7,d0 ; does monitor contain 'S'
bne Obj2E_ChkEnd
nop
Obj2E_ChkEnd:
rts ; 'S' and Goggles monitors do nothing
; ===========================================================================
Obj2E_Delete: ; XREF: Obj2E_Index
subq.w #1,$1E(a0)
bmi.w DeleteObject
rts
As you can see, there is no Goggle Code. But there is code that checks if the monitor contains 'S'. However, you can open SonED2 and add a new monitor using the object editor. Scroll through the different monitors, and you will see, in Obj2E are the monitors in the same order as in SonED2. The Goggle monitor is the last monitor in this list. This means that you will have to add your new code under the code for the S.
Before Obj2E_ChkEnd add a new label. The most fitting one is Obj2E_ChkGoggles: in my opinion:
Obj2E_ChkS:
cmpi.b #7,d0 ; does monitor contain 'S'
bne Obj2E_ChkEnd
nop
Obj2E_ChkGoggles:
Obj2E_ChkEnd:
rts ; 'S' and Goggles monitors do nothing
In ChkS is a code to branch to ChkEnd. Scroll up and you will see, there is in every monitor a code like this, but instead of branching to ChkEnd, they are branching to the next monitor code. So do the same with the S code. Change bne Obj2E_ChkEnd to bne Obj2E_ChkGoggles:
Obj2E_ChkS:
cmpi.b #7,d0 ; does monitor contain 'S'
bne Obj2E_ChkGoggles ; if not, branch to Goggle code
nop
Obj2E_ChkGoggles:
Obj2E_ChkEnd:
rts ; 'S' and Goggles monitors do nothing
Okay, we've set a few things up, but we are not done yet. Maybe you've noticed with the last step, those 2 lines in each monitor at the beginning (a 'cmpi.b' code and a 'bne' code). They are there to check if that monitor was destroyed and if so, do the code under it, otherwise check for the next monitor. We need that for the goggle monitor as well, so copy any of these 2 lines to your Obj2E_ChkGoggles: label. I'm using the lines of ChkS:
Obj2E_ChkS:
cmpi.b #7,d0 ; does monitor contain 'S'
bne Obj2E_ChkGoggles ; if not, branch to Goggle code
nop
Obj2E_ChkGoggles:
bne Obj2E_ChkGoggles ; if not, branch to Goggle code
cmpi.b #7,d0 ; does monitor contain 'S'
Obj2E_ChkEnd:
rts ; 'S' and Goggles monitors do nothing
As you can see, this can't work, because they are checking for the same things! Look up again. Every monitor has a higher number in the cmpi.b command. The number in S was 7. So what is the next number? 8 of course! So change the 7 to 8 in your Goggle code:
Obj2E_ChkS:
cmpi.b #7,d0 ; does monitor contain 'S'
bne Obj2E_ChkGoggles ; if not, branch to Goggle code
nop
Obj2E_ChkGoggles:
cmpi.b #8,d0 ; does monitor contain 'S'
bne Obj2E_ChkGoggles ; if not, branch to Goggle code
Obj2E_ChkEnd:
rts ; 'S' and Goggles monitors do nothing
7: We are nearly done. The last thing we have to do is to change the bne command in your Goggle code to ChkEnd. Like this:
Obj2E_ChkS:
cmpi.b #7,d0 ; does monitor contain 'S'
bne Obj2E_ChkGoggles ; if not, branch to Goggle code
nop
Obj2E_ChkGoggles:
cmpi.b #8,d0 ; does monitor contain 'S'
bne Obj2E_ChkEnd ; if not, branch to Goggle code
Obj2E_ChkEnd:
rts ; 'S' and Goggles monitors do nothing
Now we're done, and you can insert code to run when you destroy a Goggle monitor. But if you don't wanna add code for now, you have to add a nop under the bne command, otherwise you will get an Illegal zero length branch error when building. Also, you should change the comments, because they are really misleading. Here is my finished code:
Obj2E_ChkS:
cmpi.b #7,d0 ; does monitor contain 'S'
bne Obj2E_ChkGoggles ; if not, branch to Goggle code
nop
Obj2E_ChkGoggles:
cmpi.b #8,d0 ; does monitor contain Goggles?
bne Obj2E_ChkEnd ; if not, branch to ChkEnd
nop
Obj2E_ChkEnd:
rts
Example Code
Although that code is perfect now, it still does nothing. Unless you already have plans to do something with it, you can use this very simple code, that gives infinite air in LZ.
Go to the Goggle code and add a line, which is moving $1 to an unused RAM adress:
move.b #1,($FFFFFFA0).w ; move 1 to the goggle check
It should look like this:
...
nop
Obj2E_ChkGoggles:
cmpi.b #8,d0 ; does monitor contain Goggles?
bne Obj2E_ChkEnd ; if not, branch to ChkEnd
move.b #1,($FFFFFFA0).w ; move 1 to the goggle check
Obj2E_ChkEnd:
...
Next go to to Obj0A_ReduceAir: and add these 2 lines right after the label:
tst.b ($FFFFFFA0).w ; was a goggle monitor broken?
bne Obj0A_GoMakeItem ; if yes, branch
It should look like this:
Obj0A_ReduceAir:
tst.b ($FFFFFFA0).w ; was a goggle monitor broken?
bne Obj0A_GoMakeItem ; if yes, branch
...
Fixing the infinite air glitch
The code now works perfectly, but now you have infinite air forever. There are many ways you could go about fixing this, but theocas on the SSRG version of this tutorial proposed moving $0 the ram that signifies that a goggle monitor has been hit. This works for our purposes, so let's use that. Place the following code at the end of Obj0D_Touch, but before locret_EBBA:
...
move.b #0,($FFFFFFA0).w ; move 0 to the goggle check
...
Now, after touching the signpost of any level, $0 is moved to $FFFFFFA0, making the game go through the normal code for reducing Sonic's amount of air underwater, which means Sonic no longer has an infinite amount of air forever.
GitHub disassembly
Quick guide
I know there are many people who just want to rip off the code without learning because they think they have better things to do. For those people I took the time to make a simple copy/paste guide:
In _incObj\2E Monitor Content Power-Up.asm, search for Pow_ChkS: and replace everything from there with this:
Pow_ChkS:
cmpi.b #7,d0 ; does monitor contain 'S'
bne.s Pow_ChkGoggles ; if not, branch to Goggle code
nop
Pow_ChkGoggles:
cmpi.b #8,d0 ; does monitor contain Goggles?
bne.s Pow_ChkEnd ; if not, branch to Pow_ChkEnd
nop
That was everything. If you are too dumb to add/understand this, then you weren't reading the real guide.
Full guide
This version tells you how to do this in detail, and very well explained. It's not more than what you can see in the quick version, but with comments and explanations you can learn something, so this one is recommended.
First off, open _incObj\2E Monitor Content Power-Up.asm' and go to the Routine for the S monitor contents (Pow_ChkS). You should see this.
...
Pow_RingSound:
music sfx_Ring,1,0,0 ; play ring sound
; ===========================================================================
Pow_ChkS:
cmpi.b #7,d0 ; does monitor contain 'S'
bne.s Pow_ChkEnd
nop
Pow_ChkEnd:
rts ; 'S' and Goggles monitors do nothing
; ===========================================================================
Pow_Delete: ; XREF: Obj2E_Index
subq.w #1,obTimeFrame(a0)
bmi.w DeleteObject ; delete after half a second
rts
As you can see, there is no Goggle Code. But there is code that checks if the monitor contains 'S'. However, you can open SonED2 and add a new monitor using the object editor. Scroll through the different monitors, and you will see, in _incObj\2E Monitor Content Power-Up.asm' are the monitors in the same order as in SonED2. The Goggle monitor is the last monitor in this list. This means that you will have to add your new code under the code for the S.
Before Pow_ChkEnd add a new label. The most fitting one is Pow_ChkGoggles: in my opinion:
Pow_ChkS:
cmpi.b #7,d0 ; does monitor contain 'S'
bne.s Pow_ChkEnd
nop
Pow_ChkGoggles:
Pow_ChkEnd:
rts ; 'S' and Goggles monitors do nothing
In ChkS is a code to branch to ChkEnd. Scroll up and you will see, there is in every monitor a code like this, but instead of branching to ChkEnd, they are branching to the next monitor code. So do the same with the S code. Change bne.s Pow_ChkEnd to bne.s Pow_ChkGoggles:
Pow_ChkS:
cmpi.b #7,d0 ; does monitor contain 'S'
bne.s Pow_ChkGoggles ; if not, branch to Goggle code
nop
Pow_ChkGoggles:
Pow_ChkEnd:
rts ; 'S' and Goggles monitors do nothing
Okay, we've set a few things up, but we are not done yet. Maybe you've noticed with the last step, those 2 lines in each monitor at the beginning (a 'cmpi.b' code and a 'bne.s' code). They are there to check if that monitor was destroyed and if so, do the code under it, otherwise check for the next monitor. We need that for the goggle monitor as well, so copy any of these 2 lines to your Pow_ChkGoggles: label. I'm using the lines of ChkS:
Pow_ChkS:
cmpi.b #7,d0 ; does monitor contain 'S'
bne.s Pow_ChkGoggles ; if not, branch to Goggle code
nop
Pow_ChkGoggles:
bne.s Pow_ChkGoggles ; if not, branch to Goggle code
cmpi.b #7,d0 ; does monitor contain 'S'
Pow_ChkEnd:
rts ; 'S' and Goggles monitors do nothing
As you can see, this can't work, because they are checking for the same things! Look up again. Every monitor has a higher number in the cmpi.b command. The number in S was 7. So what is the next number? 8 of course! So change the 7 to 8 in your Goggle code:
Pow_ChkS:
cmpi.b #7,d0 ; does monitor contain 'S'
bne.s Pow_ChkGoggles ; if not, branch to Goggle code
nop
Pow_ChkGoggles:
cmpi.b #8,d0 ; does monitor contain 'S'
bne.s Pow_ChkGoggles ; if not, branch to Goggle code
Pow_ChkEnd:
rts ; 'S' and Goggles monitors do nothing
7: We are nearly done. The last thing we have to do is to change the bne command in your Goggle code to ChkEnd. Like this:
Pow_ChkS:
cmpi.b #7,d0 ; does monitor contain 'S'
bne.s Pow_ChkGoggles ; if not, branch to Goggle code
nop
Pow_ChkGoggles:
cmpi.b #8,d0 ; does monitor contain 'S'
bne.s Pow_ChkEnd ; if not, branch to Goggle code
Pow_ChkEnd:
rts ; 'S' and Goggles monitors do nothing
Now we're done, and you can insert code to run when you destroy a Goggle monitor. But if you don't wanna add code for now, you have to add a nop under the bne command, otherwise you will get an Illegal zero length branch error when building. Also, you should change the comments, because they are really misleading. Here is my finished code:
Pow_ChkS:
cmpi.b #7,d0 ; does monitor contain 'S'
bne.s Pow_ChkGoggles ; if not, branch to Goggle code
nop
Pow_ChkGoggles:
cmpi.b #8,d0 ; does monitor contain Goggles?
bne.s Pow_ChkEnd ; if not, branch to ChkEnd
nop
Pow_ChkEnd:
rts
Example Code
Although that code is perfect now, it still does nothing. Unless you already have plans to do something with it, you can use this very simple code, that gives infinite air in LZ.
Preparation
In Variables.asm, at the end of the file in a good spot, put the following piece of code.
f_gogglecheck = ramaddr ( $FFFFFFA0 ) ; goggle flag
Adding the code
Go to the Goggle code and add a line, which is moving $1 to an unused RAM adress:
move.b #1,(f_gogglecheck).w ; move 1 to the goggle check
It should look like this:
...
nop
Pow_ChkGoggles:
cmpi.b #8,d0 ; does monitor contain Goggles?
bne Pow_ChkEnd ; if not, branch to ChkEnd
move.b #1,(f_gogglecheck).w ; move 1 to the goggle check
Pow_ChkEnd:
...
Next find _incObj\0A Drowning Countdown and find .reduceair / @reduceair and add these 2 lines right after the label:
tst.b (f_gogglecheck).w ; was a goggle monitor broken?
bne @gotomakenum ; if yes, branch
It should look like this:
@reduceair
tst.b (f_gogglecheck).w ; was a goggle monitor broken?
bne @gotomakenum ; if yes, branch
...
Fixing the infinite air glitch
The code now works perfectly, but now you have infinite air forever. There are many ways you could go about fixing this, but theocas on the SSRG version of this tutorial proposed moving $0 the ram that signifies that a goggle monitor has been hit. This works for our purposes, so let's use that. Place the following code in _incObj\0D Signpost.asm and place it at the end of Sign_Touch, but before @notouch:
...
move.b #0,(f_gogglecheck).w ; move 0 to the goggle check
...
Now, after touching the signpost of any level, $0 is moved to f_gogglecheck/$FFFFFFA0, making the game go through the normal code for reducing Sonic's amount of air underwater, which means Sonic no longer has an infinite amount of air forever. Also in sonic.asm place the previous code into the beginning of "Tit_MainLoop" This makes it so that if you get a game over or soft reset the game Sonic will not have an infinite amount of air.
|Set up the Goggle Monitor to work with it]]