Blah
Free supporter
- 1,924
- Posts
- 12
- Years
- Unknown Island
- Seen Feb 19, 2025
Surf and Special 0x12b Hack
(for Fire Red only)Also see:
Changing Waterfall: https://www.pokecommunity.com/posts/8496240/
Changing Fly: https://www.pokecommunity.com/posts/8505424/
Changing Flash: https://www.pokecommunity.com/posts/8505791/
Goal and achievement:
My goal during the recent days was to mimic the anime in terms of how trainers commute in the water. I decided to try and make it so that if you have a water type Pokemon you can surf, regardless of move pool. As such, below is a semi-guide/resource to functionality of how you can implement such a feature yourself! You will also find my (and Touched's) implementation of a special that checks your party for a certain type of Pokemon.
Thought process and reasoning:
I've been trying to figure out how to remove the badge and HM requirement for surfing. I found
The script and removed the move requirement, but it seems I still can't bring up the surf script even with the badge. It seemed to keep checking if a Pokemon has learned surf before allowing me to activate the script. I wasn't quite sure where the second check is taking place, so I kinda backtracked..and landed on quite the peculiar structure.
Structure so far (forgive my formatting):
Code:
0806D548 - start
- operations
0806D550 - tile behavior check (may be height check)
compare behavior 0x0 or 0x1 (waterfall vs surf ...I think)
B 0806D570 - if surf goes here, else keep going
BL to 0805C83C - nested for loop. Loops Pokemon and moves (using move decryption, this is for waterfall)
compare move found (searches for waterfall I think)
B 0806D570 - if move found goes here
B 0806D5E2 - if not found goes here (a routine exit)
at 0806D570
ldr r0, [0x806D59C]
B 0806E6D0 (flag checker, the gym flag folks)
Check to see if var is 0x1 (that is the gym flag is set)
if not set B 0806D5A4
else run function BL to 0805C83C - nested for loop. Loops Pokemon moves (this one is for SURF!)
@ 0806d586 - checks for the surf (real check!)
if surf found B 0805C8B0 - collision detection
check no collision, - Execute -
And then a bunch of things we don't really care about.
So now you ask, how do we remove the HM requirement?! It's simple, you remove the else condition in the check. I.e pad it with 0s.
The solution to removing HM Surf from the picture:
At 0x6D588 - 0x6D589 change 0C D1 to 00 00.
Now what this exactly does is remove the check (outside of the script) for surf. However, surf is still checked for in the script itself.
You need to edit the script to whatever it is you wanted the surf condition to be, OR, you can use the edited version of special 0x12b I will talk about shortly.
Introduction to the special:
Thanks to Jpan's research on specials I found out that special 0x12b was a special which would tell you if your party had a grass Pokemon. Quite simple, and to be honest, very useless. However this special did do a few things internally that are actually worth using. It ran through your entire party and checked your Pokemon's type (and the check supported dual types, that is Pokemon like Venasaur would be flagged as grass too). So if we can modify the special to work for any type we specify would that be useful? What if we were also able to return the slot of the Pokemon who was of that type? Yes, please :PThe original (starts at 080CA804):
Spoiler:
Code:
push {r4-r6, lr}
mov r5, #0x0 @R5 unused, and served as loop counter! I.e can serve as our slot counter later on
ldr r6, [$080CA850] (=$08254784)
mov r0, #0x64
add r1, r5, #0x0
mul r1, r0
ldr r0, [$080CA854] (=$02024284)
add r4, r1, r0
add r0, r4, #0x0
mov r1, #0x5
bl $0803FBE8
cmp r0, #0x0
beq $080CA858
add r0, r4, #0x0
mov r1, #0x2D
bl $0803FBE8
cmp r0, #0x0
bne $080CA858
add r0, r4, #0x0
mov r1, #0xB
BL $0803FBE8
lsl r0, r0, #0x10
lsr r0, r0, #0x10
lsl r1, r0, #0x03
sub r1, r1, r0
lsl r1, r1, #0x02
add r1, r1, r6
ldrb r0, [r1, #0x6]
cmp r0, #0xC @CHECK TYPE 1 IS GRASS
beq $0x80CA84C @sets r0 to 1
ldrb r0, [r1, #0x7]
cmp R0, #0xC @CHECK TYPE 2 IS GRASS
bne $080CA858 @next Pokemon, this is the looping part
mov r0, #0x1 @set r0, return value, to 1
b $080CA864
...
some more things here to do with transversing the Pokemon table
I haven't explained some of the parts about getting Pokemon from part and looking it up in the table, but if you have questions I can answer.
The part of the code which we may be interested mostly by is the check part. It checks for 0xC (for grass) at the Pokemon's Type 1 and Type 2 slot. If you want you can change this statically to match a type of your choosing by simply modifying 0xC to a type you wanted to check.
Types are defined like this:
Spoiler:
Code:
Normal 0x0
Fighting 0x1
Flying 0x2
Poison 0x3
Ground 0x4
Rock 0x5
Bug 0x6
Ghost 0x7
Steel 0x8
??? (egg) 0x9
Fire 0xA
Water 0xB
Grass 0xC
Electric 0xD
Psychic 0xE
Ice 0xF
Dragon 0x10
Dark 0x11
However, Touched and I made this routine for the check part :
A)
Spoiler:
Code:
.text
.align 2
.thumb_func
main:
ldr r2, .VAR
ldrb r2, [r2]
ldrb r0, [r1, #6] @ Type 1
cmp r0, r2
beq is_type
ldrb r0, [r1, #7] @ Type 2
cmp r0, r2
bne not_type
is_type:
mov r0, r5
pop {r4-r6}
pop {r1}
bx r1
not_type:
ldr r0, =(0x80CA858 + 1)
bx r0
.align 2
.VAR:
.word 0x020270B8 + (0x8000 * 2)
What it does is compares the type 1 and type 2 to a variable (in this case 0x8000). If a Pokemon of that type is found we return into 0x8000 the slot of that Pokemon from 0 to 5, returning a 6 if no Pokemon of that type was found. Note that the variable can be changed to another variable, though careful which you use (encrypted variables and 0x800D, 0x800E, 0x800F don't work since we'd never be able to check the last result without overwriting it and similar situations with the rest.
Inserting:
To insert this into your FR ROM, you need to assemble it into free space and call it.
We overwrite the line ldrb r0, [r1, #0x6] and whatever else we need with a bx.
That is:
Code:
ldr r0, = Routine +1
bx r0
To insert this, first assemble A). Put it into some free space (even ending). If you don't know how to assemble it's:
Code:
06 4A 12 78 88 79 90 42 02 D0 C8 79 90 42 03 D1 28 1C 70 BC 02 BC 08 47 01 48 00 47 B8 70 03 02 59 A8 0C 08
Now go to 0xCA840, and insert:
Code:
00 48 00 47
So if I inserted at 7266B0, then
7266B0 = 087266B0
087266B + 1 = 087266B1
087266B1 = B1 66 72 08 (you insert this after 00 48 00 47).
Finally we want to return 6, if there isn't a Pokemon in the party of the type we specified. Go to 0xCA862 and change 00 to 06. That's it!
Sample Script:
PKSV
Spoiler:
Code:
#dyn 0x740000
#org @start
'-----------------------------------
lock
faceplayer
setvar 0x8000 0xB 'Look for water Pokemon
special2 0x4001 0x12B
compare 0x4001 0x6
if < jump @typeFound
msgbox @noneFound ' You don't have one!
callstd MSG_LOCK
release
end
#org @typeFound
'-----------------------------------
storevar 0x0 0x4001 'put 0x4001 into buffer 0
msgbox @location ' You have one at \v\h...
callstd MSG_LOCK ' Built-in lock command
release
end
#org @noneFound
= You don't have one!
#org @location
= You have one at \v\h02
XSE:
Spoiler:
Code:
'-----------------------
#org 0x740549
lock
faceplayer
setvar 0x8000 0xC
special2 0x4001 0x12B
compare 0x4001 0x6
if 0x0 goto 0x874056A
msgbox 0x8740451 '"You don't have one!"
callstd 0x6
release
end
'-----------------------
#org 0x74056A
buffernumber 0x0 0x4001
msgbox 0x8740578 '"You have one at [buffer1]"
callstd 0x6
release
end
'---------
' Strings
'---------
#org 0x740451
= You don't have one!
#org 0x740578
= You have one at [buffer1]
Note: The Pokemon's slot number is stored in whichever variable you give it. You can feed it 0x8000 if you wanted to use only 1 variable.
Extras (Surf hack + Special in conjunction):
PKSV script
Spoiler:
Code:
#org 0x81A6AC8
'-----------------------------------
jump 0x8750001
#org 0x8750001
'-----------------------------------
special INIT_STEPCOUNT
compare LASTRESULT 0x2
if == jump 0x81A7AE0 ' Equal To
lockall
msgbox 0x81A556E ' The water is dyed a ...
callstd MSG_YESNO ' Yes/No message
compare LASTRESULT NO
if == jump 0x81A6B0B ' Equal To
setvar 0x8000 0xB
special2 0x4001 0x12B
compare 0x4001 0x6
if >= jump 0x81A6B0C ' Larger Than or Equal To
storepartypokemon 0x0 0x4001
setanimation 0x0 0x4001
msgbox 0x81A55A5 ' \v\h02 used SURF!
callstd MSG_NOCLOSE ' Non-closing message
doanimation 0x9
releaseall
end
#org 0x81A7AE0
'-----------------------------------
release
end
#org 0x81A6B0B
'-----------------------------------
releaseall
end
#org 0x81A6B0C
'-----------------------------------
end
#org 0x81A556E
= The water is dyed a deep blue[.]\nWould you like to SURF?
#org 0x81A55A5
= \v\h02 used SURF!
XSE:
Spoiler:
Code:
'-----------------------
#org 0x1A6AC8
goto 0x8750001
bufferitem 0x2 0x600
nop1
jumpram
releaseall
copyvarifnotzero 0x0 0x39
compare LASTRESULT 0x6
if 0x1 goto 0x81A6B0C
bufferpokemon2 0x0 LASTRESULT
setanimation 0x0 LASTRESULT
lockall
msgbox 0x81A556E '"The water is dyed a deep blue[.]\nW..."
callstd 0x5
compare LASTRESULT 0x0
if 0x1 goto 0x81A6B0B
msgbox 0x81A55A5 '"[buffer1] used SURF!"
callstd 0x4
doanimation 0x9
releaseall
end
'-----------------------
#org 0x750001
special 0x187
compare LASTRESULT 0x2
if 0x1 goto 0x81A7AE0
lockall
msgbox 0x81A556E '"The water is dyed a deep blue[.]\nW..."
callstd 0x5
compare LASTRESULT 0x0
if 0x1 goto 0x81A6B0B
setvar 0x8000 0xB
special2 0x4001 0x12B
compare 0x4001 0x6
if 0x4 goto 0x81A6B0C
bufferpokemon2 0x0 0x4001
setanimation 0x0 0x4001
msgbox 0x81A55A5 '"[buffer1] used SURF!"
callstd 0x4
doanimation 0x9
releaseall
end
'-----------------------
#org 0x1A6B0C
end
'-----------------------
#org 0x1A6B0B
releaseall
end
'-----------------------
#org 0x1A7AE0
release
end
'---------
' Strings
'---------
#org 0x1A556E
= The water is dyed a deep blue[.]\nWould you like to SURF?
#org 0x1A55A5
= [buffer1] used SURF!
End:
I hope you found the hacks/research, or at least the special useful :)If you use either, please give credit to Touched. I don't particularly require credit, but if you want, please do give :P
...
Last edited: