Pokémon Palettes in Generation III

Go back to the main page

This is an explanation on how palettes work in the Pokémon games of Generation III, Ruby, Sapphire, and Emerald, as well as FireRed and LeafGreen, and any ROM hacks of these.
I am assuming some prior experience with a hex editor, and 24-bit color codes. Look up how to use them if you haven't before. I am also assuming basic Pokémon knowledge (e.g. what a shiny is).

This article is primarily for the sake of curiosity, not for practical use by ROM hackers. Unless you are working on some weird legacy project, there should be no reason to not use the decompilations. And even if you are, tools like UnLZ-GBA and HexManiacAdvance should let you skip most of the manual steps below.

If you find any errors or things I have missed, please contact me (see contact info on main page).

The Pokémon palettes are spread out in the games' ROM. While they are often stored after the sprites for the corresponding Pokémon, and shiny palettes after the back sprites, this is probably just a consequence of how the games were compiled from their source code as it is by no means a requirement. Instead, the palettes have a table of pointers to them. The palettes themselves are made up of 16 15-bit colors, compressed using LZ10 compression.

The palette pointer table

The pointers to all Pokémon palettes are stored in a single table.

The address of the table

What the offset for the pointer table is depends on the ROM. In all ROMs but the Japanese and English versions of Ruby and Sapphire, this offset is written near the start of the ROM, as a pointer at 0x130 (other table addresses are also written nearby). The address for the shiny palette pointers is also given separately at 0x134, but it turns out we can use the "normal" address to locate those as well. For Japanese and English Ruby/Sapphire, the offset would have to be found manually.
But since the number of versions is low enough (the number of [all Pokémon*the number of versions] is not), the addresses for most versions (e.g. not LeafGreen 1.1) are included in this article, whether trivial to find or not. See the tables below:

Game (U)/English (F)/French (G)/German (I)/Italian (J)/Japanese (S)/Spanish
Ruby 0x1EA5B4* 0x1F29BC 0x1F7530 0x1EC250 0x1BEDC0* 0x1EF2D4
Sapphire 0x1EA544* 0x1F294C 0x1F74C4 0x1EC1E0 0x1BED50* 0x1EF264
Emerald 0x303678 0x30B1A8 0x317FE8 0x30303C 0x2D6F08 0x3098DC
Fire Red 0x23730C 0x231718 0x2371DC 0x2303B0 0x1F68F0 0x232A78
Leaf Green 0x2372E8 0x2316F4 0x2371B8 0x23048C 0x1F68CC 0x232A54

Game (E)/English 1.2 (Europe)
Ruby 0x1EA5CC*
Sapphire 0x1EA55C*

Game (U)(Independent)/English 1.1 (J)(2CH)/Japanese 1.1
Fire Red 0x23737C 0x1F2180
*This version does not have this offset written at 0x130 in the ROM

The palette pointer table itself

The palette pointers are ordered according to internal id number, which in Generation III is different from either Pokédex order, with a placeholder ?????????? at index 0, 25 identical blank entries known as ? between Celebi and Treecko, and some Gen III Pokémon out of order. This order also holds for the Pokémon egg and Unown forms, the pointers to their palettes are right after the ones of the other Pokémon.
After the Unown pointers come the pointers to the shiny palettes. The break between them is easy to see, as all the Unown forms point to the same palette. The shiny palette pointers are also ordered according to the internal Pokémon order, and has the same quirks. Consequently, there is a pointer for a "shiny egg", though it points to the usual egg palette.
In conclusion, it goes:

?????????, Bulbasaur, Ivysaur, Venusaur, Charmander, ... , Ho-oh, Celebi, [? (25 times)], Treecko, Grovyle, ..., Deoxys, Chimecho, Pokémon Egg, [Unown (27 forms)],

S-?????????, S-Bulbasaur, S-Ivysaur, S-Venusaur, S-Charmander, ... , S-Ho-oh, S-Celebi, [S-? (25 times)], S-Treecko, S-Grovyle, ..., S-Deoxys, S-Chimecho, S-Pokémon Egg, [S-Unown (27 forms)]

Each "pointer" is 8 bytes long, consisting of the actual pointer to the palette plus a "counter". Both these parts are 4 bytes in little endian order, so the less significant bytes come first.
To visualize, this here are the first few palettes as they would look in an hex editor:

B4 19 D0 08 00 00 00 00 24 00 D3 08 01 00 00 00 B8 0A D3 08 02 00 00 00

Or if we order them up and add some comments:

B4 19 D0 08 actual pointer for ??????????
00 00 00 00 "counter" for ??????????
24 00 D3 08 actual pointer for Bulbasaur
01 00 00 00 "counter" for Bulbasaur
B8 0A D3 08 actual pointer for Ivysaur
02 00 00 00 "counter" for Ivysaur

The most significant (i.e. last) byte of the actual pointer is the bank number. As far as I know this is always 0x08 for vanilla ROMs, as the entire ROM is loaded into bank 8 of the GBA. Ignore this byte in this case. However, some ROM hacks are larger, and use higher-value banks. In this case, each value increase of the bank relative to 0x08 corresponds to adding 0x1000000 to the resulting pointer. Again, the little-endianness means we have to reverse the order of rest of the bytes. So:

"B4 19 D0 08" => 0x0D019B4,
"24 00 D3 08" => 0x0D30024,
"24 00 D3 09" => 0x1D30024,
"24 00 D3 10" => 0x2D30024

The "counter" I have no idea what it does, the function of it counting up is already fulfilled by the table being a table allowing for fast index-based lookup. It might be a programming error.

The compressed palettes

The palettes are each 16 colors long (except for Castform's), or 15 if you discount the first one, which is always transparent. The colors are 15-bit colors, written in little endian, so they take 2 bytes each. Normally, this would mean each Pokémon palette is 16*2=32 bytes long, but they are "compressed" using LZ10 compression. This is a variant of Lempel-Ziv compression used for many GBA games, and for both palettes and sprites in the Pokémon games. LZ10 is also know as LZ77 "variant 10", or LZSS.
(Strictly, LZ10 is an implementation of LZSS, short for Lempel-Ziv-Storer-Szymanski. LZSS is in turn based off LZ77. The "10" in LZ10 comes from the first identifying byte of the compressed data being "10".)

For the palettes, this "compression" actually only shortens a few palettes, making the rest of them longer, so really acts more like encryption.
How Lempel-Ziv works is mostly out-of-scope for this article, though some parts will be explained when relevant.
(The DS games use similar compression, hopefully to be covered in a later article.)

To undo the LZ10 compression, you can use this tool: LZ10 quicktool
There are many ROM hacking tools that undo LZ10, like UnLZ-GBA, but then they do it as part of a longer process and don't output the bytes directly, so for our purposes the above is preferred. In any case, here is the palette of Bulbasaur before and after decompression:

before/compressed:
10 20 00 00 00 39 57 FF
7F B0 63 4C 53 00 47 3E
23 25 BF 31 3B 21 00 B7
10 39 67 42 08 F7 3B 00
53 27 AE 1A 8A 15 1F 7C

after/uncompressed:
39 57 FF 7F B0 63 4C 53
47 3E 23 25 BF 31 3B 21
B7 10 39 67 42 08 F7 3B
53 27 AE 1A 8A 15 1F 7C

Note that the uncompressed palette starts with "10 20 00 00". The "10" in this case stands for LZ10 compression (or the reverse, that's the origin of its name). "20 00 00" tells us the length of the uncompressed palette, 32 read in little endian. As all our Pokémon palettes are 32 bytes and LZ10-compressed, all of them will start with "10 20 00 00" in their compressed state. This makes it easier to see where a palette begins they begin in a block of hexes, but not that it is a Pokémon palette. Pokémon sprites are far from the only ones to use this palette structure, so do most other (all?) sprites like trainer classes, overworld NPCs, move animations, and bag items. Pokémon menu icons are the strange outlier, they have uncompressed, shared palettes.

The first color of the uncompressed palette, "39 57", is transparent in-game (though it does have a color value, more on that in this article), so we will look at the second color, "FF 7F" (or FF7F), instead. FF7F stands for white, or #FFFFFF in standard 24-bit color, as you would use in a modern-day painting program. It is used for Bulbasaur's eyes, teeth and claws. To convert 24-bit colors to 15-bit, use this website: http://www.budmelvin.com/dev/15bitconverter.html.
bulbasaur's sprite
However, if you were to input #FFFFFF, you would get 7FFF, not FF7F. This is again because of the little-endianness, lower bytes come first in the code. The only thing you have to do is swap the bytes around (so you'd get FF7F). Don't forget to!


Now, let's do a concrete example. Here is AAAC, my Nincada (on an emulator).
A green-eyed, green-winged Nincada
I want to change her (and every other Nincada's) green eyes and wings to these rad red ones I've done in a painting program:
A red-eyed, red-winged Nincada
First, I need to find the pointer to Nincada's normal palette. This is a Ruby rom, so palette pointer table starts at 0x1EA5B4. Nincada's internal id number is 301, or 12D in hexadecimal. 1EA5B4 + 8*12D = 1EAF1C, so Nincada's pointer is found at 0x1EAF1C.
We go there with out hex editor, and see the pointer.
An hex editor, the bytes E8 E4 DE 08 2D 01 00 00 highlighted.
We look at the four first bytes of the pointer, E8 E4 DE 08, and reverse them to see we need to go to 0xDEE4E8 (in bank 08) to find the palette. We go there and select a few row of bytes.
An hex editor, bytes highlighted starting with 10 20 00 00 00 D0 3A.
Now we actually don't know how many bytes the compressed palette is, due to the compression. In this case, I happen to know that the "10 00 08 00" we see a bit down usually signifies the start of a compressed Pokémon sprite, as "10 20 00 00" does for a palette, so I stop before it. But even if I didn't it wouldn't matter much, since the decompressor tool ignores any extra bytes.

We then paste the bytes into the decompressor tool and press "decompress".
the decompressor tool
From the compressed bytes

10 20 00 00 00 D0 3A 5C
6B 99 36 35 32 00 D3 29
10 37 8C 36 2E 1D 00 BE
73 E9 2D F9 5A B6 4A 00
33 3E CF 31 4B 29 A5 14

we get the uncompressed

D0 3A 5C 6B 99 36 35 32
D3 29 10 37 8C 36 2E 1D
BE 73 E9 2D F9 5A B6 4A
33 3E CF 31 4B 29 A5 14

Going back to our painting program to look at Nincada's original sprite, we see that the first/brightest green is #84C66B. We insert this at http://www.budmelvin.com/dev/15bitconverter.html and get 3710. We flip these bytes so we have 10 37. We find it in our uncompressed palette.

D0 3A 5C 6B 99 36 35 32
D3 29 10 37 8C 36 2E 1D
BE 73 E9 2D F9 5A B6 4A
33 3E CF 31 4B 29 A5 14

We then look at the color code of our first/brightest red, which will replace that green, in our painting program. It is #BC3C3C, or 1CF7 when put through the 15-bit converter, and F7 1C when flipped. We replace 10 37 in our uncompressed palette with F7 1C.
We then repeat those steps for the other two green colors, and the red colors that we want to replace them with, and get the below uncompressed palette (replaced colors in bold):

D0 3A 5C 6B 99 36 35 32
D3 29 F7 1C 73 2D 2E 1D
BE 73 0E 21 F9 5A B6 4A
33 3E CF 31 4B 29 A5 14

We now paste the above into the "uncompressed" field, click the "compress" button and get a new LZ10-compressed palette:

10 20 00 00 00 D0 3A 5C
6B 99 36 35 32 00 D3 29
F7 1C 73 2D 2E 1D 00 BE
73 0E 21 F9 5A B6 4A 00
33 3E CF 31 4B 29 A5 14

As this new compressed palette isn't any longer than the old one, we can simply replace it at the same offset. If it were longer, we would have to put it somewhere else, and repoint the pointer at 0x1EAF1C to this new location. We finally load up our rom again in the emulator, and if using a save state we leave AAAC's status screen and re-enter it - palettes are only reloaded when you change screens.
A red-eyed, red-winged Nincada
Success!
This change also carries over to the backsprite and the Pokédex, with no need to do anything more. We are done. Hopefully the tedium has taught you though, that this is not something you normally do manually. Use a tool!
The backsprite of a red-eyed, red-winged Nincada shown in a battle A red-eyed, red-winged Nincada in the Pokédex



Originally written by Voliol 12022-04-21, HE, last updated 12023-04-04.

Appendix: Explaining Castform's palette

Castform has a palette that is 64 colors, four times that of any other Pokémon. It is stored and compressed the same way as other palettes, but is just longer. The reason for this is that the palette is divided between Castform's four forms, into normal 16-color palettes.
Normal Form Castform uses colors #0-15, Sunny Form uses #16-31, Rainy Form uses #32-47, and Snowy Form uses #48-63.



Originally written by Voliol 12022-07-10, HE.

LZ10 quicktool

Below are three files needed to compile into a working Java program (and a license). They use JavaFX, so getting it to run might be a little finicky.
The reason they are not just an executable file, is that I am still learning Java, and sadly do not have the know-hows to port the code I borrowed for this tool. As such, when I ran into problems exporting the program into a executable .jar file, I decided to put solving it off, in favor of getting this article out there.
I eventually plan on "fixing" it and making (or referring to) a tool that can easily be used to compress/decompress LZ10. Still, the main takeaway from this article should be the ways the palette data are stored, and not this tool to manipulate only part of it. If you can't get it to run you should only have missed ~5% or so. (I hope)

Main.java
DSCmp.java
DSDecmp.java
LICENSE.txt
(the .java files are really .txt, as Neocities only allows "supporter accounts" to upload .java files)



Originally written by Voliol 12022-06-10, HE.