The PokéCommunity Forums Fan Games ROM Hacking Tools, Tutorials & Resources
Tutorial Extending the Pokedex in Red Version

Tools, Tutorials & Resources Various tools to help you develop your hacks can be found here.

Thread Tools
Old December 14th, 2013 (11:20 AM).
ShantyTown's Avatar
ShantyTown ShantyTown is offline
Forever Young
    Join Date: Aug 2013
    Gender: Male
    Posts: 316
    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:
    - Red ROM Map:
    - Red RAM Map:
    - pokered disassembly (specifically, the file called "main.asm"):

    Tools You'll Need:
    - TileMolester:
    - WoodPression:

    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.

    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.
    Reply With Quote
    Old June 5th, 2014 (3:59 AM).
    Seth1353's Avatar
    Seth1353 Seth1353 is offline
      Join Date: May 2013
      Location: Australia
      Gender: Male
      Nature: Jolly
      Posts: 112
      pretty cool and explained well, thank you :)
      Reply With Quote
      Old July 13th, 2014 (8:54 AM). Edited July 13th, 2014 by marillmau5.
      marillmau5's Avatar
      marillmau5 marillmau5 is offline
        Join Date: Oct 2012
        Posts: 24
        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.
        Reply With Quote
        Old February 13th, 2018 (10:52 PM).
        knuxyl knuxyl is offline
          Join Date: Jun 2017
          Posts: 15
          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

          	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)
          	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).
          Reply With Quote
          Old February 14th, 2018 (2:14 AM).
          knuxyl knuxyl is offline
            Join Date: Jun 2017
            Posts: 15
            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
            ld a, [wMonSpritesBank]
            This variable location is in wram.asm right after wMonHLearnset

            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?
            Reply With Quote
            Old February 14th, 2018 (2:35 AM).
            knuxyl knuxyl is offline
              Join Date: Jun 2017
              Posts: 15
              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.
              Reply With Quote

              Quick Reply

              Join the conversation!

              Create an account to post a reply in this thread, participate in other discussions, and more!

              Create a PokéCommunity Account
              Thread Tools

              Posting Rules
              You may not post new threads
              You may not post replies
              You may not post attachments
              You may not edit your posts

              BB code is On
              Smilies are On
              [IMG] code is On
              HTML code is Off

              Forum Jump

              All times are GMT -8. The time now is 11:46 PM.