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

Extending the Pokedex in Red Version

330
Posts
10
Years
  • Age 32
  • Seen Mar 29, 2024
Tutorial:
Extending the Pokedex in Red Version
====================================

This tutorial will walk you through the process of extending the Pokedex in Red Version. This is the method I used in my hack, Pokemon Maize Version. Unfortunately, there is one important trade-off with this method. This method will limit the Box size to 19 pokemon instead of the usual 20. If you don't think that's the worst thing in the world, then this tutorial is perfect for you. You'll need to understand pointers and general hex editing to follow this tutorial, as well. I'll be going into a good amount of detail to explain why things work and why we need to modify them in these ways.

Important References You Need:
- Giegue's Master Hacking Guide: http://hax.iimarck.us/topic/20/
- Red ROM Map: http://datacrystal.romhacking.net/wiki/Pokémon_Red_and_Blue:ROM_map
- Red RAM Map: http://datacrystal.romhacking.net/wiki/Pokémon_Red/Blue:RAM_map
- pokered disassembly (specifically, the file called "main.asm"): https://github.com/iimarckus/pokered

Tools You'll Need:
- TileMolester: http://www.romhacking.net/utilities/109/
- WoodPression: http://hax.iimarck.us/topic/613/

First, we need to do some dirty work before we can insert new pokemon. These 5 steps will walk you through the dirty work.

1. Repointing the BaseStats table
The BaseStats table holds data such as base stats, type, and catch rate for all pokemon in pokedex order. So, Bulbasaur's is first, followed by IvySaur, etc.. Each entry in the BaseStats table uses 0x1C bytes. Since there are 150 total pokemon (not including Mew because its BaseStats entry is located separately from the rest), we know the BaseStats table is a total of (150)*(0x1C) = 0x1068 bytes. The BaseStats table starts at address 0x383DE and ends at 0x39446, which is a total of 0x1068 bytes! When we add new pokemon to the pokedex, we'll be appending, or "inserting at the end", to the BaseStats table. However, there is no free space after the BaseStats table because the CryData begins directly after it. So, we'll solve this issue by moving the entire BaseStats table to a free bank in the ROM. Bank 0x3F is the last bank, and it's completely free, so we'll move the BaseStats table there. Bank 0x3F starts at address 0xFC000, so copy the 0x1068 bytes from address 0x383DE to 0xFC000. After you've copied them, fill in the old BaseStats table location with 0x00's. (Place 0x1068 total 0x00 bytes starting at 0x383DE.) We need that newly-free space for other important things!

Now that the BaseStats table has been moved to 0xFC000, we need to change the pointers elsewhere in the ROM. There are only 3 locations in the ROM that need to be changed for this:
- Change byte at 0x153b from 0E to 3F. (This is the Bank number of the BaseStats table. We moved it to Bank number 0x3F.)
- Change pointer at 0x1578 from DE 43 to 00 40 (This is the pointer to the start of the BaseStats table. 00 40 is the pointer to 0xFC000 because it's at very the start of the Bank.)
- Change pointer at 0x3AE49 from DE 43 to 00 40 (Same as previous change.)

We just completely moved the location of the BaseStats table. Run the game to see if everything still works exactly the same. If so, move on to the next step.


2. Add Sprite Bank Numbers to BaseStats Table Entries
Red Version has a strange way of looking up where the front and back sprites of each pokemon are located. The front sprite pointer is bytes 12 and 13 in the BaseStats entry for a pokemon, and the back sprite pointer is bytes 14 and 15. (You can see these in "main.asm" from the pokered disassembly.) However, how does the game know which Bank to find the sprite in? The answer is that there is a routine in the game located at 0x1627 (UncompressMonSprite) that uses the pokemon's internal id number to sort out which bank the sprites are located in. The internal id for each pokemon is listed in Giegue's Master Hacking Guide, which I put a link to at the top of this post. We're going to completely change this routine so that it doesn't use the internal id to determine the bank. Instead, we'll simply include the bank number in the BaseStats entry for each pokemon. The BaseStats entry for each Pokemon ends with a 0x00 that is unused. In other words, it's just padding. So, we'll actually use that last byte. Just a warning, this next part is a pain. We need to manually change the last byte in every pokemon's BaseStats table entry from 0x00 to the Bank number of its front and back sprite. So, for each pokemon, we look up its internal id, which can be found in Giegue's Master Hacking Guide. Then, we use the information below to determine which Bank its sprite is in. Once we know the Bank number, we replace the last byte in the BaseStats table entry with the Bank number.

Code:
Note "<=" means "less than or equal to"
id < 0x1F, bank 0x09
0x1F <= id < 0x4A, bank = 0x0A
0x4A <= id < 0x74, bank = 0x0B
0x74 <= id < 0x99, bank = 0x0C
0x99 <= id,            bank = 0x0D

Let's do Bulbasaur as an example. Using Giegue's Master Hacking Guide, we know that Bulbasaur's internal id is 0x99. Looking at the table above, we see than the Bank number is 0x0D. Now, we go to the last byte in Bulbasaur's BaseStats table entry, which is now located at 0xFC01B after part 1 of this tutorial. Finally, we change the byte at 0xFC01B from 0x00 to 0x0D.

Just for good measure, I'll do Ivysaur, too. Using Giegue's Master Hacking Guide, we know Ivysaur's internal id is 0x09. Using the table above, we know the Bank number is 0x09 because the id is less than 0x1F. Now go to the last byte of Ivysaur's BaseStats table entry, which is at 0xFC037, and change the byte from 0x00 to 0x09.

After you've done this for all of the pokemon in the BaseStats table (yes, all 150 of them...), we'll need to change the routine in the game at 0x1627 (UncompressMonSprite), so that it actually uses this new information we've included in the BaseStats table. Essentially, we'll include some assembly code that loads that byte.

Insert the following 6 bytes at address 0x1645: FA D3 D0 C3 FD 24. (FA D3 D0 says "load the last byte in the BaseStats table entry for the current pokemon". Then C3 FD 24 says to jump to the UncompressSpriteData routine.)

Yay, we're done with sprites! Run the game again to make sure everything is working exactly the same as before. The awesome thing about this solution is that you can put the graphics for new pokemon in any ROM Bank because you just place the Bank number in that pokemon's BaseStats table entry.


3. Repoint MonOverWorldData
MonOverWorldData is the table containing the data for which mini sprite should be shown in the pokemon party screen. We need to move this table to a new location so we can append to it when adding new pokemon. The MonOverWorldData table is 0x4C bytes long. Move those 0x4C bytes from address 0x7190D to 0x73bb0. Make sure to fill in the old MonOverWorldData table at address 0x7190D with 0x4C total 0x00 bytes because we aren't using it anymore. Now, we need to change the pointers for the new MonOverWorldData table. Luckily, there is only one pointer.

- Change pointer at 0x718F9 from 0D 59 to B0 7B

Run the game and make sure everything still works like usual.


4. Repoint MonsterPalettes
MonsterPalettes is the table cotaining the palette id's for each pokemon in pokedex order. We ened to repoint it just like part 3 in this tutorial. MonsterPalettes is 0x98 bytes long, and the table starts at address 0x725C8. Move those bytes to new address 0x73F20, and fill in the old table with 0x00 bytes because we're not using it anymore. Now, we change the pointers. Again, there is only one pointer.

- Change pointer at 0x71Fb1 from C8 65 to 20 7F.

Run the game and make sure everything still works like usual.


5. Change Location of "Seen Pokemon" in RAM
This step is where we limit the number of Pokemon in the Box to 19, instead of 20. The location of "owned pokemon" flags goes from 0xD2F7-0xD309. The loaction of the "seen pokemon" flags goes from 0xD30A-0xD31C. So, we'll need to change the location of the "seen pokemon" flags to some other free space in RAM that has about 0x20 free bytes. I don't know where any free space is, so we'll make some! Specifically, we'll just use the space in RAM that the 20th stored pokemon in the PC Box uses. That location goes from 0xDD09-0xDD29. Perfect!

First, let's repoint the "seen pokemon" flags from 0xD30A to the new location of 0xDD09 by doing the following steps:
- Change pointer at 0x7C3C from 0A D3 to 09 DD
- Change pointer at 0xF39F from 0A D3 to 09 DD
- Change pointer at 0x3AEC9 from 0A D3 to 09 DD
- Change pointer at 0x3EC13 from 0A D3 to 09 DD
- Change pointer at 0xF517 from 0A D3 to 09 DD
- Change pointer at 0x4008b from 0A D3 to 09 DD
- Change pointer at 0x40130 from 0A D3 to 09 DD
(Careful! This next one is different!)
- Change pointer at 0x4017C from 1C D3 to 27 DD
- Change pointer at 0x401E1 from 0A D3 to 09 DD
- Change pointer at 0x4416A from 0A D3 to 09 DD
- Change byte at 0x4416D from 13 to 1D
- Change byte at 0x4417A from 13 to 1D

Now, we need to make changes to limit the number of Pokemon in the Box to 19, instead of 20.
- Change byte at 0xF534 from 14 to 13
- Change byte at 0x215bF from 14 to 13
- Change byte at 0x4FDb7 from 14 to 13
- Change byte at 0xD6A3 from 14 to 13

Finally, we need to change the routine that occurs when changing Boxes.
- Change bytes at 0x7391E from 62 04 to 89 02
- Change pointer at 0x73921 from B5 00 to 60 7C
- Insert the following 0x19 bytes at address 0x73C60: (This is some asm that doesn't copy our "seen pokemon" flags with the Box data.)
E5 D5 CD B5 00 D1 E1 01 A9 02 E5 62 6B 09 54 5D
E1 09 01 B9 01 CD B5 00 C9

Phew. That should take care of the "Seen Pokemon" flags and limiting the Box to 19 pokemon. Note that we don't need to repoint the "owned pokemon" flags because that table can just overflow into the old "seen pokemon" table.

Run the game to see if everything is working the same. Make sure you can only have 19 pokemon in your Box, and make sure the Pokedex is behaving normally.

==============================================

Steps 1-5 above take care of all the dirty work that needs to be done before we can start inserting Pokemon. Now, comes the "easier" part. For the following steps, you'll need to be looking closely at the file called "main.asm" in the pokered disassembly project.
The structure of all the required data entries is detailed in there. Here are the steps to inserting a Pokemon into a ROM that has been modified by steps 1-5 above:

1. Insert name into table at address 0x1C21E. The names appear in order of internal id, and each entry is 0xA bytes long. Entries are padded with 0x50 if the names aren't 0xA bytes long.
Example: RHYDON = 91 87 98 83 8E 8D 50 50 50 50

2. Edit number of Pokemon in Pokedex. Change byte at address 0x13A4 to one more than the total number of pokemon in the pokedex. It starts at 0x98 (152) because there are a total of 0x97 (151) pokemon in the pokedex.

2. Create front and back sprites for the new pokemon. You can do this using a combination of TileMolester and WoodPression (links at the top). Use TileMolester to generate the 2bpp graphics for the front and back sprites, and then use WoodPression to generate the compressed hex data. Insert the compressed hex data for the front and back sprites into any free space in the ROM, and remember those addresses for the front and back sprites. Remember to use the Bank of these addresses when you create the BaseStats table entry for the new pokemon!

3. Add to the end of the BaseStats table. After creating your BaseStats table entry for a new pokemon, you need to append it to the BaseStats table located at 0xFC000. (See the pokered disassembly for the BaseStats entry format.) The BaseStats table is sorted by pokedex order. IMPORTANT: Since Mew's Basestats table entry is located elsewhere, make sure to leave 0x1C bytes of 0x00 at the end of the BaseStats table!!!!! In other words, if you're inserting pokemon number 152 into the pokedex, make sure you insert the 0x1C bytes at address 0xFD084, NOT 0xFD068!!! I just filled 0xFD068-0xFD083 with 0xFF bytes to remind myself.

4. Add CryData. The CryData table starts at 0x39446, and it's sorted by internal id of the pokemon.

5. Add EvosMoves entry. The EvosMoves entry contains information about how a pokemon evolves and which levels it learns different attacks. After you've created the EvosMoves entry for the new pokemon, insert in some free space in Bank 0x0E. There is lots of free space from 0x3bbE6-0x3bFFF. After inserting the EvosMoves entry, you need to change the pointer to it. The table of EvosMoves pointers is located at 0x3b05C, and the pointers are sorted by internal pokemon id (So, Rhydon's pointer is first, followed by Kangaskhan's, etc..). Find the correct pointer for your new pokemon, and change the pointer to point at the EvosMoves entry you just inserted.

6. Add the DexEntry. Once you've created the DexEntry entry for your new pokemon, insert it in some free space in Bank 0x10. Then, change the corresponding pointer in the DexEntryPointers table located at 0x4047E. Again, these are sorted by internal id of the pokemon.

7. Add to Pokedex order. The order of the pokedex is located at 0x41024. It's sorted by internal id of the pokemon. If you're inserting the 152th pokemon into the pokedex. You would go to the internal idth entry of this table and change the byte to 0x98 (152). As an example, the first byte of this table is 0x70 because Rhydon is the 0x70th (112th) pokemon in the pokedex.

8. Add to the MonOverWorldData table. We repointed the MonOverWorldData table so that we could add new entries to the end of it when we insert new pokemon. Using the pokered disassembly, it should be sraightforward how to add to this table, and the table is sorted by pokedex order. Remember, we repointed it to address 0x73bb0!

9. Add to the MonsterPalettes table. This should also be straightforward if you look at the pokered disassembly. It's just a single byte for each pokemon. The table is sorted by pokedex order. Remember we repointed it to address 0x73F20!


That should be everything. This should allow for around 230 total pokemon in the pokedex! I hope ths is useful, and be sure to let me know if something doesn't make sense, or I have an error in here.
 
24
Posts
11
Years
  • Seen Dec 23, 2022
Hello, can you please make a rom base with the extended pokemon enabled and using the available tools we can edit the pokemon to our liking.
 
Last edited:
15
Posts
6
Years
  • Age 33
  • Seen Sep 4, 2019
I cannot seem to get this to work.

I am working with the pokered disassembly with asembly code instead of hex so it's a little hard to follow.

This is what I have in (untouched) UncompressSpriteData

Code:
UncompressMonSprite::
	ld bc, wMonHeader
	add hl, bc
	ld a, [hli]
	ld [wSpriteInputPtr], a    ; fetch sprite input pointer
	ld a, [hl]
	ld [wSpriteInputPtr+1], a
; define (by index number) the bank that a pokemon's image is in
; index = Mew, bank 1
; index = Kabutops fossil, bank $B
; index < $1F, bank 9
; $1F ≤ index < $4A, bank $A
; $4A ≤ index < $74, bank $B
; $74 ≤ index < $99, bank $C
; $99 ≤ index,       bank $D
	ld a, [wcf91] ; XXX name for this ram location
	ld b, a
	cp MEW
	ld a, BANK(MewPicFront)
	jr z, .GotBank
	ld a, b
	cp FOSSIL_KABUTOPS
	ld a, BANK(FossilKabutopsPic)
	jr z, .GotBank
	ld a, b
	cp TANGELA + 1
	ld a, BANK(TangelaPicFront)
	jr c, .GotBank
	ld a, b
	cp MOLTRES + 1
	ld a, BANK(MoltresPicFront)
	jr c, .GotBank
	ld a, b
	cp BEEDRILL + 2
	ld a, BANK(BeedrillPicFront)
	jr c, .GotBank
	ld a, b
	cp STARMIE + 1
	ld a, BANK(StarmiePicFront)
	jr c, .GotBank
	ld a, BANK(VictreebelPicFront)
.GotBank
	
	jp UncompressSpriteData


If I just ignore everything at the top and add
ld a, [wMonSpriteBank]
before the jump, with wMonSpriteBank in wram.asm at the d0d3 spot with ds 1, seems like what you suggested.

But this does not work. I also think that GetMonHeader needs to be changed as well or something because I have all my sprites alphabetized in the banks and in many different banks and nothing is loading the sprites correctly. Alphabetizing has become a hassle, but even without that, the sprite is not loading from the last byte in the basestats (yes I made sure to set this as well to the bank the pics are in).
 
15
Posts
6
Years
  • Age 33
  • Seen Sep 4, 2019
I found what the problem is but I do not know how to fix it.

Before I UncompressSpriteData, I'm supposed to load the bank the sprite is in into register a. This is done with this code from all roms using this that I have seen
Code:
ld a, [wMonSpritesBank]
This variable location is in wram.asm right after wMonHLearnset

Code:
wMonHLearnset:: ; d0cc
; bit field
	flag_array 50 + 5
	ds 1

wMonSpritesBank::    ; d0d3
	ds 1

wSavedTilesetType:: ; d0d4
; saved at the start of a battle and then written back at the end of the battle
	ds 1

	ds 2

When I use the variable location of the sprite bank [wMonSpritesBank], it does not work and I get scrambled sprites. When I put in the actual address instead of the variable like so [$d0d3], it works just fine for the pokemon I have set the last bit in their base stats to the bank.

So what exactly is wrong with using the variable address? I know the syntax is correct, you use brackets []'s for when you are referring to an address, and this variable should be the address of the last byte in the base stats, so what is going on?
 
15
Posts
6
Years
  • Age 33
  • Seen Sep 4, 2019
I figured out the problem. In wram.asm, the section before wMonSpriteBank is wMonHLearnset and at the end of it is
ds 1
and I'm guessing that's where the padding was so I deleted that and now it's working.
 
Back
Top