• Our software update is now concluded. You will need to reset your password to log in. In order to do this, you will have to click "Log in" in the top right corner and then "Forgot your password?".
  • Forum moderator applications are now open! Click here for details.
  • Welcome to PokéCommunity! Register now and join one of the best fan communities on the 'net to talk Pokémon and more! We are not affiliated with The Pokémon Company or Nintendo.

Pokémon Generation III ROM Hacking Tutorials [Video Series]

2
Posts
6
Years
  • Age 56
  • Seen Jul 25, 2017
Great work on the tutorial series, Anthroyd, it is very comprehensive.

I have a question. Is it possible to add another menu option to the START screen? I ask because I want to try to implement a Persona/Shin Megami Tensei style Fusion Mechanic and I don't know if that would be possible given the tools we have.
 

HuK

25
Posts
7
Years
  • Age 32
  • Seen Jun 9, 2019
I can't use OWM because my rom base is 1460 patched version
so F000000 has data on it....
WHAT SHOULD I DO ?!
 

metaloath

MetalOath
4
Posts
6
Years
Hello Anthroyd, I would like to thank you for the amazing quality of this series. This is what one might call a proper video tutorial series.
UPDATE: I have Figured this out, see the edit.
Everything between the two dashed lines below should be ignored if you are new to the scene, it is a wall of confusing nonsense.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Spoiler:
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Edit:
So as it turns out, I was attempting to modify a level script without understanding the scripts themselves or the User Interface under the Header tab in AdvMap.
I was trying to modify the "Map Script Offset" script in the advanced header view (CTRL+H when in the header tab), when I should have been working with individual level scripts in the normal header view.
For some reason, it was completely unclear to me how to navigate an individual Map's pre-existing scrips even though it turned out to be very simple.

Anyways, to see the level scripts of any given map, look under the Header Tab, in the Map Script box, Under the "Script No:" text. There is a white box with 0 in it and two tiny arrow buttons pointing up and down.
Using these buttons you can see all the map scripts attached to this map starting at script number 0, and going up. In my case, I've opened "PALLET TOWN (4.3)" which is Oak's lab.
Script No:0 is a Type 03 level script, and clicking on the up arrow button twice reveals two additional map scripts of types 04 and 02, script numbers 1 and 2, respectively.
The part which I wanted to edit was the text under the "Strings" section of the type 02 level script. You can see this part by copying the offset under "Script offset 2:", in my case (16923E),
and pasting it in XSE in the "Offset" box and clicking the decompile button (the one to the right of this box). Taking a look under the "Strings" section,
you can see the text that Oak says when you first enter the lab in the intro event where he gives you your first Pokemon.

In order to perform an edit, you must do some heavy editing to the original script before attempting anything new, in order to avoid damaging any original data.
Here is how i did it:
Spoiler:

Original Script:
Spoiler:

As you can see, I replaced all "#org 0x" with "#org @", and parts where functions are called like "applymovement MOVE_PLAYER 0x8" with "applymovement MOVE_PLAYER @".
Just be careful not to replace things like variables and such. In short, If you can't read and understand the script, you shouldn't be doing this yet.
So I basically changed all pointers into dynamic pointers and added "#dynamic 0x800000" to the beginning of the script such that it will compile as a completely new script.
This is done so that when you compile the script, It wouldn't overwrite the original script, which can result in some weird things happening in the game, potentially breaking it.
Now you may change whatever you want to change,
hit the compile button, and copy your new script's offset. Going back to AdvMap, go back to your level script, again, in my case the type 02 level script in Oak's lab,
and you replace the script's old offset with the new one (under "Script offset 2:" for type 02 level scripts) and hit save map scripts, and then save the changes to the rom (CTRL + S). And that should be it.
If I have made any mistakes explaining this, please do inform me.

Note: the rest of this event's scripts are normal Person events and scripts found under the events section of AdvMap.
 
Last edited:

Leader Jasmine

steely-eyed missile girl
2
Posts
6
Years
  • Age 30
  • Seen Apr 14, 2018
I'm no Anthroyd (phenomenal video series, by the way!). But I think I can help you out with what's going wrong here. :)

**

First of all, re-editing the script to dynamically allocate its pointers is exactly what you should be doing - so good job. Doing it any other way, as you detail in step 4a, will overwrite other parts of the code; someday I'll write a proper tutorial explaining how and why this happens, but for now, here's a quick illustration.

Imagine each script in the map stored, in a long chain, from beginning to end. So in 4.4, you have Oak's speeches, the Pokédexes, Gary's trash-talking and so on. Each of these scripts is a different length and consequently occupies a different number of bytes - but they're all stored back-to-back, one after the other.

That's the important part - there is no space left between scripts in a vanilla FireRed ROM. (That would be wasteful, after all.) When Game Freak created the ROM, their compiler automatically worked out how many bytes every script needed, and allocated exactly that amount of space to each one. And no more.

Hopefully, it should now be clear why editing Oak's text to a longer string will inevitably corrupt, overwrite and generally mess up other scripts in the ROM. Since there is no additional space for extra text bytes, XSE will dutifully compile your changes over the top of an existing script. If you're lucky, this will only corrupt other person or signpost scripts, which are relatively easily fixed. If you're unlucky, you'll start messing around with map scripts, ASM code and image data, which are much harder to put right.

So... is it impossible to extend dialog, or otherwise modify scripts? Of course not! We simply need to find somewhere else to store our new, modified script - a location currently unused by the ROM. Some free space, in other words. And we then need to tell the game to look there when executing the script, instead reading from the original location.

This is what you've done with your new script. By adding the pre-processing directive #dynamic 0x800000, you're telling the compiler that you want it to start looking for free space, beginning at the offset 0x800000. (This is typically used because it's an easily memorable location, not because it's technically the beginning of the free space in vanilla FireRed. If you'd like to find your own space, crack open a hex editor and start hunting for FF bytes at around 0x71A500. But I digress - and 0x800000 is a perfectly good place to start storing your scripts.)

Time for a quick terminology lesson. There are two kinds of offset you should be familiar with when scripting: dynamic, and static. Dynamic offsets begin with the prefix '@', while static offsets begin with the prefix '0x'. The difference is, essentially, that static offsets point to a pre-determined location in the ROM - for instance, 0x167F7E - while dynamic offsets are placeholder labels. In other words, they don't point anywhere... yet.

That's why static offsets are always numerical. Since existing scripts are stored somewhere specific in the ROM, every script you decompile will be labelled with numbers, instead of text. For example:
#org 0x168F7E
#org 0x168FE1
#org 0x168FF0

These numbers (well, offsets, really) are useful because they're the actual locations of each part of the decompiled script. If you open up a hex editor and 'Goto...' the offset 0x16923E, those bytes right there are the hex symbols corresponding to each of your scripting commands.

For example
, at 0x16923E:
Code:
69 C7 00 4F 04 00 B0 92 16 08 51 00 00 [...]
...A meaningless string of bytes? Not quite. Armed with a bit of scripting know-how, we can manually translate this!

Code:
0x69 = lockall
0xC7 = textcolor, with parameter 0x0
0x4F = applymovement, with person ID 0004, using movement data at 1692B0
0x51 = waitmovement, with parameter 0x0000

...Well, you get the picture. (If you're wondering where I got the byte values, try F1 for XSE's Command Help.)

That said, when we're trying to read or write a script in XSE-language, static (or numeric) offsets aren't as easy to understand as dynamic offsets.

Why? Because dynamic offsets can be anything you like - text, numbers, or a combination of both. So they can be memorable, and they can help you organise your code. For example:
#org @start
#org @text_1
#org @oakSpeaks01

This is one of the reasons it's preferable to use dynamic offsets when coding your scripts.

But the most important reason that you'll (almost) always want to use dynamic offsets in your custom code isn't just that it makes things easier to read. It's because any and all dynamic offsets will be automatically replaced by the compiler when you hit the button in XSE!

This sounds magic, but in fact, all that's happening is that the compiler is doing this:
1) Calculate length of script segment beginning at #org @dynamic_offset_01.
2) Search through the ROM for free space (FF bytes) of exactly that length.
3) Replace @dynamic_offset_01 with 0x(free space offset); for example, 0x800000.
4) Convert XSE commands into hex bytes.
5) Overwrite FF bytes with custom hex bytes.
6) Output list of offsets where custom code was stored, ready to be copied and pasted with AdvanceMap.

You could do this yourself with a copy of FSF, if you had the patience - going through each label, calculating the byte length of each section using the Command Help in XSE, and typing that into FSF manually. But it takes far too long to be practical - and sooner or later, you'd end up making a mistake. XSE's compiler is super-fast, however, and it doesn't screw up.

There's another, incredibly useful feature which only works with dynamic offsets, too. XSE's pre-processing directive #clean will automatically remove the last dynamically-compiled script from the ROM.

In other words, it'll replace everything it just compiled with FF bytes again. So, if you mess something up, you haven't wasted any space. You can just clean your old script off the ROM, and compile the modified one in its place!

tl;dr: static offsets are bad unless you know exactly what you're doing. Dynamic offsets are easier to understand, safer to use and can be erased at will.

**

...And all of that said, none of that is technically your problem here.

XSE is a great tool - but it never officially left beta. As such, there are a few quirks and bugs here and there. (Remarkably few, actually; it's one of the most solid hacking tools you'll ever come across. And paired with DavidJCobb's scripting reference, diego's Mega-Huge tutorial and Spherical Ice's level script documentation, you'll have essentially everything you need to code whatever you like.)

But XSE does *not* decompile level scripts properly every time. Which is what's going wrong here.

Here's the section of your code that's bugging out:

Code:
'---------------
#org @168FF0
#raw word 0x4055
#raw word 0x1
#raw pointer @16923E
#raw word 0x4055
But first, let's step through the code to see what's going on here (and what's going wrong).

...Incidentally, this is the first method I recommend for debugging a misbehaving script. Step through each line of code, commenting what it does as you go.

If you find yourself confused or unsure what a particular line is doing, chances are you've found your problem. (Or at least, you've found something to research, and you have a starting point for your bug hunt.)

So...

Code:
#dynamic 0x800000 // start looking for free space at 0x80000.
'---------------
#org @168F7E // this section of code is a header; it contains pointers to lists, one for each different type of level script
#raw 0x3 // this map contains type 3 level scripts
#raw pointer @168F8E // pointer to our type 3 level script handler
#raw 0x4 // this map contains type 4 level scripts
#raw pointer @168FE1 // pointer to our type 4 level script handler
#raw 0x2 // this map contains type 2 level scripts
#raw pointer @168FF0 // pointer to our type 2 level script handler
#raw 0x0 // end of map level scripts

'---------------
#org @168FE1 // list of type 4 level scripts
#raw word 0x4055 // which variable will the game check?
#raw word 0x1 // if the variable is 0x1, jump to and execute the following pointer
#raw pointer @168FEB // type 4 level script pointer
#raw word 0x0 // end of type 4 scripts

'---------------
#org @168FF0 // list of type 2 level scripts
#raw word 0x4055 // which variable will the game check?
#raw word 0x1 // if the variable is 0x1, jump to and execute the following pointer
#raw pointer @16923E // type 2 level script pointer
#raw word 0x4055 // which variable will the game check?

'---------------
#org @168F8E
[...]
And, you see, we've already found our problem. After checking for a variable, the code appears to stop abruptly - which will break the script execution, as you discovered.

Why does it stop? Because XSE has failed to decompile everything it found at the pointer 0x168FF0. (This isn't your fault and it's a tricky little bug to spot.) But if XSE won't decompile the whole script, what can we do?

Brute-force it, of course! Break out HxD and go to the offset 0x168FF0... and here's what you'll see.

Code:
55 40 01 00 3E 92 16 08 55 40 07 00 02 90 16 08 00 00
We can translate this fairly easily; the bytes are all raw, which means they correspond directly to the hex codes you'll find in your editor. And they've just been flipped:

Code:
#raw word 0x4055
#raw word 0x1
#raw pointer 0x816923E
#raw word 0x4055
#raw word 0x7
#raw pointer 0x8169002
#raw word 0x0
So now we have our full, decompiled script. (We know to stop at the 00 00 mark because the game terminates its lists of level scripts with two 00 bytes - a raw word of 0x0.)

Here's my version of your code, cleaned up a bit and with handier labels for your dynamic offsets. Compile this, stick it in the Map Script Offset field in Advance-Map, and you should be ready to roll!

Code:
#dynamic 0x800000
#include stdpoke.rbh
#include stditems.rbh
#include stdattacks.rbh
#include stdmove.rbh

'---------------
#org @level_scripts_master_header
#raw 0x3
#raw pointer @list_of_type_3_scripts
#raw 0x4
#raw pointer @list_of_type_4_scripts
#raw 0x2
#raw pointer @list_of_type_2_scripts
#raw 0x0

'---------------
#org @list_of_type_4_scripts
#raw word 0x4055
#raw word 0x1
#raw pointer @type_4_script_01
#raw word 0x0

'---------------
#org @list_of_type_2_scripts
#raw word 0x4055
#raw word 0x1
#raw pointer @type_2_script_01
#raw word 0x4055
#raw word 0x7
#raw pointer 0x8169002 // this is your @type_2_script_02. It's a little long to decompile here, but feel free to plug 169002 into XSE if you want a look!
#raw word 0x0

'---------------
#org @list_of_type_3_scripts
setflag 0x2CF
compare 0x4055 0x1
if 0x1 call @type_3_script_01
compare 0x4055 0x7
if 0x1 call @type_3_script_02
compare 0x4055 0x8
if 0x1 call @type_3_script_03
checkflag 0x247
if 0x1 call @type_3_script_04
end

'---------------
#org @type_4_script_01
spriteface 0xFF 0x2
end

'---------------
#org @type_2_script_01
lockall
textcolor 0x0
applymovement 0x4 @oak_m1
waitmovement 0x0
hidesprite 0x4
movesprite2 0x4 0x6 0x3
spritebehave 0x4 0x8
clearflag 0x2B
applymovement MOVE_PLAYER @player_m1
waitmovement 0x0
applymovement 0x8 @gary_m1
waitmovement 0x0
clearflag 0x4001
playsong2 0x0
fadedefault
msgbox @t1 MSG_KEEPOPEN '"[rival]: Gramps!\nI'm fed up with ..."
closeonkeypress
pause 0x3C
msgbox @t2 MSG_KEEPOPEN '"OAK: Hmm? [rival]?\nWhy are you he..."
closeonkeypress
pause 0x1E
applymovement 0x8 @gary_m2
waitmovement 0x0
msgbox @t3 MSG_KEEPOPEN '"[rival]: Hey! Gramps!\nWhat about ..."
msgbox @t4 MSG_KEEPOPEN '"OAK: Be patient, [rival],\nI'll gi..."
setvar 0x4055 0x2
releaseall
end

'---------------
#org @type_3_script_01
movesprite2 0x4 0x6 0xB
spritebehave 0x4 0x7
playsong2 0x12E
return

'---------------
#org @type_3_script_02
movesprite2 0x4 0x6 0xB
spritebehave 0x4 0x7
return

'---------------
#org @type_3_script_03
setvar 0x4055 0x9
return

'---------------
#org @type_3_script_04
setflag 0x24F
return


'---------
' Strings
'---------
#org @t1
= [rival]: Gramps!\nI'm fed up with waiting!

#org @t2
= OAK: Hmm? [rival]?\nWhy are you here already?\pI said for you to come back\nlater[.]\pAh, whatever! Just wait there.\pLook, [player]! Do you see that ball on\nthe table?\pIt's called a POKé BALL. It holds\na POKéMON inside.\pYou may have it! Go on, take it!

#org @t3
= [rival]: Hey! Gramps!\nWhat about me?

#org @t4
= OAK: Be patient, [rival],\nI'll give you one later.


'-----------
' Movements
'-----------
#org @oak_m1
#raw 0x11 'Step Up (Normal)
#raw 0x11 'Step Up (Normal)
#raw 0x11 'Step Up (Normal)
#raw 0x11 'Step Up (Normal)
#raw 0x11 'Step Up (Normal)
#raw 0x11 'Step Up (Normal)
#raw 0xFE 'End of Movements

#org @player_m1
#raw 0x11 'Step Up (Normal)
#raw 0x11 'Step Up (Normal)
#raw 0x11 'Step Up (Normal)
#raw 0x11 'Step Up (Normal)
#raw 0x11 'Step Up (Normal)
#raw 0x11 'Step Up (Normal)
#raw 0x11 'Step Up (Normal)
#raw 0x11 'Step Up (Normal)
#raw 0xFE 'End of Movements

#org @gary_m1
#raw 0x2E 'Face Up (Delayed)
#raw 0xFE 'End of Movements

#org @gary_m2
#raw 0x26 'Step on the Spot Up (Faster)
#raw 0x26 'Step on the Spot Up (Faster)
#raw 0xFE 'End of Movements
Any questions, please fire away! (I really should write that tutorial sometime...)
 
48
Posts
11
Years
  • Seen Jul 28, 2023
This series of videos has been a great help and I thank you for putting in the effort to make it. 10/10
 
15
Posts
6
Years
Might've been covered in a video and I missed it but, what about editing stuff like game corner prizes? I've found tools that take care of the majority of stuff that I'm working on but nothing on this particular thing. I thought it'd be similar to the mart but it's a bit more complicated.
 
11
Posts
5
Years
  • Age 25
  • Seen Nov 3, 2022
Replacing Tileset with Modern Tileset is too hard and this guide hasn't helped me at all in making a new hack rom . I would like to shot a positive comment, but I couldn't make an hack rom with this . It's just too hard. Like I can't put new tileset, can't modify the storyline etc. So annoying really. Also I already stucked in "assign double script to the NPC" , what? you can't , there's only 1 offset for NPC and it was not clear how to assign one more script to the NPC. Also when I make a new map, like new Pallet town I can see some pieces of buildings of other routes.. so many problems that made me bothered in making a new hack rom (was super hyped before realized it's so damn hard)
 

Anthroyd

Professor
211
Posts
7
Years
I'm updating this thread to alert that an update video has been completed and released. Thank you all for your support throughout the past two years. I really appreciate it.
 
Last edited:

TubbieRanger

UNprofessional ROMhacker
5
Posts
6
Years
  • Age 47
  • Seen Oct 9, 2019
I'm having the strangest problem, for some reason, when testing my game, SOME doors just don't work, even if the building and door weren't edited at all. Any idea what's happening? I've been using your tutorials.

EDIT: I fixed the problem, something I did changed the behavior bytes of some door tiles, if anyone knows why this happened, please tell me. I think it was either due to expanding tilesets or because I added a move to gastly's learnset.
 
Last edited:
8
Posts
5
Years
  • Age 30
  • Seen Nov 24, 2018
This has been a major help for my ROM hacking. I will continue to watch more videos. Thank you for doing this
 
15
Posts
4
Years
  • Age 31
  • Seen Sep 21, 2023
I would like to point out something regarding map editing! I haven't seen any content in your tutorials, but I hope other people can help me too! I'm trying to change the hero's avatar/Icon/protagonist head on the town map. In this case it's Fire Red. Using unLZ I was able to find the male and female offsets at number 188 and 189. I have replaced some of the offsets with my own avatars, but I can never get the right colors. It's always messed up. Could anyone out there help me out?

Tks
 
Back
Top