• 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?".
  • 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.

Quick Research & Development Thread

4
Posts
8
Years
  • Age 28
  • Seen Sep 9, 2017
Changing the experience gain formula (Emerald)

** The purpose of this research isn't to learn the EXP calculation formula. We already know that. The purpose is to give offsets we can use to easily change the formula.

The exact point where the game yanks a Pokemon's experience yield is here:
Code:
0804A4A4 LDRB            R2, [R0,#pokemon_base.exp_yield]
At which point one of the following calculations occur:
Code:
if ( v0 )
        {
          v8 = (unsigned int)(battle_participants[unk_202420D].level
                            * pokemon_basestats[battle_participants[unk_202420D].species].exp_yield
                            / 7 << 16) >> 17;
          v9 = v8 / v4;
          *v1 = v9;
          if ( !(v9 << 16) )
            *v1 = 1;
          word_20243CC[27] = v8 / v0;
          if ( !(v8 / v0 << 16) )
            word_20243CC[27] = 1;
        }
else
        {
          v10 = (battle_participants[unk_202420D].level
               * pokemon_basestats[battle_participants[unk_202420D].species].exp_yield
               / 7 & 0xFFFF)
              / v4;
          *v1 = v10;
          if ( !(v10 << 16) )
            *v1 = 1;
          word_20243CC[27] = 0;
        }
Basically:
Code:
(exp_yield * level / 7) & 0xFFFF
Is always calculated, and then split according to how many Pokemon participated in the battle. This is that calculation in full:
Code:
ROM:0804A4A4 42 7A                                   LDRB            R2, [R0,#pokemon_base.exp_yield]
ROM:0804A4A6 2A 31                                   ADDS            R1, #0x2A @ '*'
ROM:0804A4A8 08 78                                   LDRB            R0, [R1]
ROM:0804A4AA 50 43                                   MULS            R0, R2
ROM:0804A4AC 07 21                                   MOVS            R1, #7
ROM:0804A4AE 9D F2 47 F8                             BL              __divsi3
ROM:0804A4B2 00 04                                   LSLS            R0, R0, #0x10
ROM:0804A4B4 01 0C                                   LSRS            R1, R0, #0x10
You can modify these opcodes however you like.
Before these opcodes:
  • R0 is pokemon_base, the struct with all the information about the species of Pokemon we just KO'd.
  • R1 contains knowledge about the specific Pokemon we battled, such as its level (offset 0x2A).
After these opcodes:
  • R1 must contain the total EXP to split between the battling Pokemon.
Me, I just wanted to cancel EXP growth. So instead of the first opcode I zeroed out R2, and the result is that you always gain 1 EXP. Because of those checks in the psuedo-code - if the game detects that you're about to gain 0 EXP it changes it to 1 EXP. You can change that code as well if you want, it's right here:
Code:
ROM:0804A4C8 00 04                                   LSLS            R0, R0, #0x10
ROM:0804A4CA 00 28                                   CMP             R0, #0
ROM:0804A4CC 01 D1                                   BNE             loc_804A4D2
ROM:0804A4CE 01 20                                   MOVS            R0, #1
ROM:0804A4D0 08 80                                   STRH            R0, [R1]
....and here:
Code:
ROM:0804A4DE 00 04                                   LSLS            R0, R0, #0x10
ROM:0804A4E0 00 28                                   CMP             R0, #0
ROM:0804A4E2 18 D1                                   BNE             loc_804A516
ROM:0804A4E4 01 20                                   MOVS            R0, #1
ROM:0804A4E6 28 80                                   STRH            R0, [R5]
ROM:0804A4E8 15 E0                                   B               loc_804A516
 
794
Posts
10
Years
Is that someone's attempt to recreate the source code? That's pretty rad. I guess the upshot in my post is that you can make changes directly to the engine. Even if you used a compiler you'd still need to know the offsets in the ROM.

Nah, recreating the source code is another thing. :P
I linked you to the github of my and one other guy project. We basically rewrote a huge part of the original battle engine in C and added stuff in later generations. For more info, see my signature.
 
4
Posts
8
Years
  • Age 28
  • Seen Sep 9, 2017
Changing global wild encounter levels (Emerald)

** Again, something I did for my own uses. Instead of changing the wild encounter tables for each route, you can make sweeping changes to all wild encounters by changing the code that uses those tables.

This is what the flow for generating wild encounters looks like (call order):
ROM:080B5288 walking_func
ROM:080B4F4C choose_wild_pokemon_encounter
ROM:080B4C74 choose_encounter_level

The first function performs some check for every step (or turn) the player takes. If the player's tile is encounter-eligible it enters the second function, which eventually calls the third function, "choose_encounter_level" which does just that. Let's look at some pseudo-code:

Code:
int __fastcall choose_encounter_level(int encounter_table)
{
  int encounter_table_loc; // r4@1
  unsigned int v2; // r0@1
  int level_min; // r7@2
  unsigned int level_max; // r6@2
  int rand; // r4@4
  int v6; // r2@4
  int v7; // r0@5

  encounter_table_loc = encounter_table;
  v2 = *(_BYTE *)(encounter_table + 1);
  if ( v2 < *(_BYTE *)encounter_table_loc )
  {
    level_min = *(_BYTE *)(encounter_table_loc + 1);
    level_max = *(_BYTE *)encounter_table_loc;
  }
  else
  {
    level_min = *(_BYTE *)encounter_table_loc;
    level_max = v2;
  }
  rand = (unsigned __int16)rng() % (signed int)((level_max - level_min + 1) & 0xFF) & 0xFF;
  if ( !pokemon_getattr(0x20244EC, 6, v6) )
  {
    v7 = (unsigned __int8)sub_806B6D8(0x20244EC);
    if ( v7 == 55 || v7 == 72 || v7 == 46 )
    {
      if ( !(rng() & 1) )
        return level_max;
      if ( rand )
        rand = (rand - 1) & 0xFF;
    }
  }
  return (level_min + rand) & 0xFF;
}

Only some of the stuff here I found important, mostly the computation for "rand". It's worth clarifying that "level_min" etc. are encounter-specific, and come straight out of the encounter tables. Here are the relevant opcodes:

Computing "rand"
Code:
ROM:080B4C8A F4 1B                                   SUBS            R4, R6, R7
ROM:080B4C8C 01 34                                   ADDS            R4, #1
ROM:080B4C8E 24 06                                   LSLS            R4, R4, #0x18
ROM:080B4C90 24 0E                                   LSRS            R4, R4, #0x18
ROM:080B4C92 BA F7 9B FC                             BL              rng
ROM:080B4C96 00 04                                   LSLS            R0, R0, #0x10
ROM:080B4C98 00 0C                                   LSRS            R0, R0, #0x10
ROM:080B4C9A 21 1C                                   MOVS            R1, R4
ROM:080B4C9C 32 F2 D8 FC                             BL              __modsi3
ROM:080B4CA0 00 06                                   LSLS            R0, R0, #0x18
ROM:080B4CA2 04 0E                                   LSRS            R4, R0, #0x18
On entering:
  • R6 is level_min
  • R7 is level_max
On leaving:
  • R4 is "rand"

Returning "rand" as the encounter level
Code:
ROM:080B4CEA 38 19                                   ADDS            R0, R7, R4
ROM:080B4CEC 00 06                                   LSLS            R0, R0, #0x18
ROM:080B4CEE 00 0E                                   LSRS            R0, R0, #0x18
  • R4 and R7 keep their values from the "rand" computation - "rand" and "level_min" respectively.
Use cases

So, what now? Well, say you wanted to make encounters a bit scarier - it's as easy as adding to R4 or R7. In my own experiments I replaced the useless left-shift/right-shift opcodes near the "rand" computation with something more useful:

Code:
ROM:080B4C8A                 SUBS            R4, R6, R7
ROM:080B4C8C                 ADDS            R4, #1
ROM:080B4C8E                 LDR             R5, =0x3005D8C
ROM:080B4C90                 LDR             R5, [R5,#4]
ROM:080B4C92                 BL              rng
ROM:080B4C96                 LDRB            R5, [R5,#0x14]
ROM:080B4C98                 NOP
ROM:080B4C9A                 MOVS            R1, R4
ROM:080B4C9C                 BL              __modsi3
ROM:080B4CA0                 LSRS            R5, R5, #3
ROM:080B4CA2                 ADDS            R4, R0, R5
Since R5 was unused, I borrowed it to add to "rand" the message frame value! You know, the value in the options that decides what pretty frame your menus have (I prefer the pink one with the bows!). Based on this research:
showthread.php?p=8615361#post8615361

The value's behind pointer 0x3005D90, offset 0x14. The reason I counted from 0x3005D8c is convenience - it's used a bit later at 080B4D50:
Code:
ROM:080B4D50 dword_80B4D50   DCD 0x3005D8C
 
17
Posts
9
Years
  • Age 30
  • Seen Sep 3, 2023
HM Flash Lightens Entire Screen [EM]

I got a little irked trying to train in the Victory Road basement by the Flash HM not clearing the entire screen. I'm playing the game on my phone, which has an AMOLED display. AMOLED displays are very susceptible to burn-in, so to avoid getting a nice big dark circle in the middle of my screen...

I modified the Flash brightness table to make it work like FR/LG and light up the entire screen:

At 0x54FE66, change 48 to C8.

This is kind of cheap because it makes navigating Granite Cave and Victory Road easier than intended, but whatever.

Would just like to point out that doing this causes a softlock in the Dewford Gym if you defeat Brawly after defeating all of his trainers.
 
325
Posts
9
Years
I just happened to stumble on this

Writing C0 46 C0 46 to 0x03DF40 will allow the Weedle in the Old Man's tutorial to be female or genderless. Obviously this is only for FR but hey something similar should be out there for Emerald.

Also at 0x07F88C, there is a
Code:
mov r1, #0xD
which is where it loads Weedle's species, so you can change that to whatever you want with hooks or whatever.
 

Criminon

Forever Spriting
265
Posts
11
Years
This stuff should help anyone wanting to check their party's hp.
0x20242DA is the address stored in RAM for the 1st pokemon in your party's HP.
0x20242DC is the max HP.
0x20242D4 is the status ailment a pokemon is currently suffering. (00 being nothing)

Using comparefarbytetobyte 0x20242DA 0xnumber you want to check
you can easily check a pokemon's hp.

using comparefarbytetobyte 0x20242DC 0xnumber you want to check
you can easily see if a pokemon has enough hp for something.

using writebytetooffset 0xstatus 0x20242D4
you can easily edit the status effecting them.

For the rest of your party, take these offsets and add 0x64 to them.

It's crazy how much stuff I've written that I have completely forgot. Here's to remembering your own findings.
 
239
Posts
8
Years
  • Age 31
  • Seen yesterday
I did a little investigation into trainerbattle 0x9:

Rather than nop-ing out of the Oak's text function, you can just change the reserved byte from 0x3 to 0x0 to prevent Oak's text from appearing during battle. So you could re-use trainerbattle 0x9 and still have a battle that uses the text. It would look like:
Code:
trainerbattle 0x9 0x(trianer_ID) 0x0 @win @lose      //Oak's text does NOT appear
trainerbattle 0x9 0x(trainer_ID) 0x3 @win @lose     //Oak's text appears


EDIT: setting the reserved byte to 0x0 instead of 0x3 removes the functionality allowing you to continue with a script even if you white out. I made an assembly routine to toggle the text on and off while keeping this functionality in this post, though.

Additionally, trainerbattle 0x9 writes 0x0 or 0x1 to variable 0x800D (lastresult) depending on if you won or lost the battle, respectively. This allows you to branch to separate scripts depending on the result of your trainer battle
 
Last edited:

Derlo

Tired....
135
Posts
16
Years
I did a little investigation into trainerbattle 0x9:

Rather than nop-ing out of the Oak's text function, you can just change the reserved byte from 0x3 to 0x0 to prevent Oak's text from appearing during battle. So you could re-use trainerbattle 0x9 and still have a battle that uses the text. It would look like:
Code:
trainerbattle 0x9 0x(trianer_ID) 0x0 @win @lose      //Oak's text does NOT appear
trainerbattle 0x9 0x(trainer_ID) 0x3 @win @lose     //Oak's text appears

Additionally, trainerbattle 0x9 writes 0x0 or 0x1 to variable 0x800D (lastresult) depending on if you won or lost the battle, respectively. This allows you to branch to separate scripts depending on the result of your trainer battle

well, I tried this, but don't work!
Doing what Knizz did, I get fix a bug in GBA emulator for android. when the OAK's battle start, the emu freezes.
 
239
Posts
8
Years
  • Age 31
  • Seen yesterday
well, I tried this, but don't work!
Doing what Knizz did, I get fix a bug in GBA emulator for android. when the OAK's battle start, the emu freezes.

I've never tried using knizz's method, so perhaps it's an either-or situation. Try restoring the bytes that you nop-ed out of and see if that works.
 

AkameTheBulbasaur

Akame Marukawa of Iyotono
409
Posts
10
Years
There was a conflict between JPAN's set trainer levels hack and Doesntknowhowtoplay's trainer EVs hack. I may have found a way to fix it so that one can have both in their hack at the same time.

I posted it in Doesnt's original thread here, but I thought I'd link it here too so more people could see it, because it may be something useful for a lot of people's hacks if it works for everyone the way it worked for me.

I haven't tested it on a clean ROM yet, but I think it should still work there too.
 

Vivacity

still learning
6
Posts
9
Years
  • Age 29
  • Seen Jul 15, 2017
Class-based AWESOMENESS

Well hot danggg, this is AWESOME! And totally came out of nowhere - thanks so much for pulling this off and releasing it for us all to enjoy!

(I totally think this is cool/complex enough to deserve its own thread, by the way... but hey, your call. ;))

also:

1) your Readme file is a radiant and beautiful thing and an absolute MODEL of clarity and conciseness; I sort of wish you'd write a tutorial just on how to write documentation, haha, it could not be easier to understand
2) your smart automatic double battle implementation is brilliant, by the way - what a subtle but giant improvement to the default engine

and finally:

Emerald?
No.
hee ;)
 
51
Posts
9
Years
  • Age 33
  • Seen Nov 18, 2023
Class-based Poke Balls [FR]

In Sun and Moon, NPC trainers started using different types of Poke Balls. Specifically, it's tied to their trainer class - for example, Elite Four members and Ace Trainers keep their Pokemon in Ultra Balls. Prior to this, NPC trainers only ever used basic Poke Balls.

I ported this to Fire Red. The repo is here, and I've also added it as an attachment. The readme explains how to use it.

Great! But I have a problem with the insertion :S
I open the prompt and write "src/build" but nothing...if I write "script/build" a pop-up ask me with how program I want to open this file...
How can I compile it???
Thanks :D
 
132
Posts
9
Years
  • Age 23
  • Seen Apr 14, 2024
(for Emerald) These offsets determine how many Pok?mon show up in the Hoenn dex:
xBC8FE - amount you want
xBC926 - amount you want
xC0890 - amount you want minus one
You can only have up to 255 in the regional dex.

I'm sure there's some limiters related to the Summary screen and the Save screen that I've possibly missed but I can't confirm it right now.

0x6E3E2 should be changed to the amount you want as well to fix the bug where pokemon beyond 202 wouldn't have their dex number displayed on the summary screen
 
239
Posts
8
Years
  • Age 31
  • Seen yesterday
I hadn't seen this anywhere but my apologies if it's already been posted elsewhere.

The sprite that shows the rival walking in place in the nickname screen is different than the overworld rival character. I have no idea why; but regardless, I went ahead and searched for the appropriate offsets for the nickname screen images/palette:

Code:
Pallette: 0xE98004
	-pointer at 0x3E23C8
Frames: (0x100 or 256 bytes each)					
	3E1980 = frame 1 (facing down), pointer at 0x3E23C0
	3E1A80 = frame 2 (facing up)
	3E1B80 = frame 3 (facing left)
	3E1C80 = frame 4 (walk down left)
	3E1D80 = frame 5 (walk down right)
	3E1E80 = frame 6 (walk up right)
	3E1F80 = frame 7 (walk up left)
	3E2080 = frame 8 (walk left 1)
	3E2180 = frame 9 (walk left 2)
 
232
Posts
12
Years
  • Seen Sep 10, 2019
I hadn't seen this anywhere but my apologies if it's already been posted elsewhere.

The sprite that shows the rival walking in place in the nickname screen is different than the overworld rival character. I have no idea why; but regardless, I went ahead and searched for the appropriate offsets for the nickname screen images/palette:

Code:
Pallette: 0xE98004
    -pointer at 0x3E23C8
Frames: (0x100 or 256 bytes each)                    
    3E1980 = frame 1 (facing down), pointer at 0x3E23C0
    3E1A80 = frame 2 (facing up)
    3E1B80 = frame 3 (facing left)
    3E1C80 = frame 4 (walk down left)
    3E1D80 = frame 5 (walk down right)
    3E1E80 = frame 6 (walk up right)
    3E1F80 = frame 7 (walk up left)
    3E2080 = frame 8 (walk left 1)
    3E2180 = frame 9 (walk left 2)
Good eyes!
 
788
Posts
17
Years
  • Age 29
  • Seen Apr 13, 2024
Choose Poké Mart Inventory Based on Badge Progress [FR]

This shouldn't need too much of an explanation. Gen IV prevented early-game Poké Marts from becoming completely useless by late-game by tying the Poké Mart's inventory to how many badges you have, rather than the location of the Mart. This is the same thing for Fire Red.

Basically, if your script contains pokemart 0, it will pick the appropriate item list (which you can customize). Any argument other than zero is still understood as a static item list.

Remove flag check for running [FR]

Code:
0x0805BA3A: 01 20 C0 46

Combine this with the Run Inside Buildings hack, and you'll get the same running mechanics as Gen VI and Gen VII (as in, run anywhere and start out with the ability to run).

Automatically unlock National Dex [FR]

I could've sworn I posted this at some point, but I couldn't find it. The function responsible for determining whether you have the national dex or not is at 0x0806E25C. By making it always return true, you'll always be considered to have the national dex. To be clear, this doesn't make the Pokédex active on the start menu, but (for example) national dex Pokémon will be able to evolve and their Pokédex number will show on the summary screen (instead of ???). It's the equivalent of immediately invoking special 0x16F at the beginning of the game.

Code:
0x0806E25C: 01 20 70 47

Port of Emerald's Pickup mechanics [FR]
To be clear, this does not include the in-battle effect (where a Pokémon with Pickup may pick up an item that was just used), as introduced in BW; it just makes the item list depend on the level of the Pokémon, like in Emerald.

Can't forget HM08 [FR]
FRLG didn't use HM08 (Dive), so the move it contains can be freely forgotten without the Move Deleter. To make it like the rest of the HMs (in that the move can't be forgotten):

Code:
0x08125AB6: 07

-----------------------------------------

Nisarg said:
I tried it and for me works perfect on insert2, but have to ask, are there any bugs so far?

I haven't experienced any bugs with it, but if you do, please let me know. Just open an issue on github.

tkim said:
Could you clarify, if I wanted to completely disable 'The Pokémon stopped evolving' due to not having the national dex, do I have to use all of the offsets provided, or just the '@ all three'?

Use all the offsets.

Also, I apologize if moving my response to this post (it was originally two posts down) notifies you again.
 
Last edited:
232
Posts
12
Years
  • Seen Sep 10, 2019
Disable "The Pokemon Stopped Evolving." [FR]

FR has a feature that if you attempt to evolve a Pokemon into something not in the Kanto Dex (and you don't have the National Dex), it will mysteriously stop evolving. You could just give the player the National Dex at the beginning of the game, but if you don't want to do that, you can easily disable this.

Basically, this will turn some conditional jumps into unconditional jumps, and remove some checks. No free space required.

Spoiler:

Could you clarify, if I wanted to completely disable 'The Pokemon stopped evolving' due to not having the national dex, do I have to use all of the offsets provided, or just the '@ all three'?
 
Back
Top