This Platinum hack aims to make it so each individual Pokémon has a slight color variation based on its personality value. This is inspired by a similar feature from the Pokémon Stadium games that does just this but based on the Pokémon's nickname. The ROM hack Pokémon Polished Crystal implements a very similar feature based on IV values. This implementation performs a hue shift on the Pokémon's palette on load.
If you would like to use this in your own hacks, please feel free to do so!
Some Pokémon have shiny colors that are very close to their non-shiny colors, or at least close enough that the simple hue shift performed by this hack may make a normal Pokémon look shiny. To remedy this, many Pokémon have been given revamped shiny colors. In additon, the opportunity has been taken to revamp Pokémon that seem to have obviously "bad" shiny palettes even if there's no ambiguity issue.
List of changed Pokémon (so far):
Pichu, Pikachu, Raichu - Tried to make visually distinct instead of the slight orange tint. A bit of a gradient from pale yellow to pale orange throughout the evolutionary line.
Nidoqueen - Colors made to look like Nidoking, which matches the pattern set by all other members of the Nidoran lines.
Meowth, Persian - Mint green to complement vanilla shiny Meowth's pink, and with a silver coin and ears, respectively.
Abra, Kadabra - More pink instead of brown to match, and create a gradient towards, shiny Alakazam.
Seel, Dewgong - Made to look more visibly gold.
Haunter, Gengar - Tried to make a sensible gradient from Gastly to Mega Gengar (purple -> white with blue highlights). Normal Gengar has been also modified to be like its anime palette. (The image data of this sprite has also been tweaked.)
Scyther - Made to be red-orange, which makes shiny Scyther and Scizor effectively be a loose swap of their normal colors.
Elekid, Electabuzz, Electivire - Light blue, with Elekid being slightly more Cyan. Electivire has gold tips.
Munchlax, Snorlax - Like a brown bear.
Articuno - Lavender.
Zapdos - Brown.
Sunkern, Sunflora - Purple and a more blueish green, resembling a purple sunflower.
Espeon - Less overly saturated.
Leafeon - Fall colors, inspired by various posts suggesting the idea.
Gligar - Normal Gligar has been changed to its HGSS colors, which are much closer to the official artwork. (No need to change the shiny palette, as this now makes them very distinguished.)
Magcargo - Lava part matches shiny Slugma.
Mantyke, Mantine - Green and aqua-ish scheme, based on this post by RayquazaFlygon.
Phanphy - Inverted from its normal palette.
Smoochum - Lavender and light-pink body, and platinum blonde hair to match shiny Jynx.
Magby - Red-pink and pink to match Magmar and Magmortar's shiny colors.
Happiny, Blissey - Green and light-gold-tan to match shiny Chansey. (The image data of this sprite has also been tweaked, including the D/P sprite.)
Combusken - Changed to match the color change made in Sword/Shield.
Loudred, Exploud - Lime green details instead of yellow to match shiny Whismur.
Plusle - Hot pink/magenta.
Regice - A deep dark blue, inspired by this post by /u/SmallBigBrainBoi.
Piplup, Prinplup, Empoleon - Based off of this post from cjgart2000. Intended to give the "emporer penguin" look.
Gabite, Garchomp - Tried to make them consistent with shiny Gible (deeper blue, yellow/orange belly).
Snover, Abomasnow - Extremeties made a deeper blue to make the worst case between normal and shiny less ambiguous.
Dusknoir - Red to match shiny Duskull and Dusclops.
Froslass - A rose/sakura body with a violet belt/bow.
Phione, Manaphy - Purplish-blue body, cyan eyelashes for Manaphy, purple gem. Based on this post by EpicGordoMan.
- Install devkitARM.
- Install a D compiler.
- Use a program like Nitro Explorer 3 to extract
arm9.bin
,overlay9-12.bin
,overlay9-16.bin
,overlay9-86.bin
, andoverlay9-87.bin
from your Platinum ROM. - Place them in the root folder of this repo, and name them to
arm9_vanilla.bin
,overlay12_vanilla.bin
,overlay16_vanilla.bin
,overlay86_vanilla.bin
, andoverlay87_vanilla.bin
, respectively. - Run
./build.sh
. - Inject
arm9_patched.bin
,overlay12_patched.bin
,overlay86_patched.bin
, andoverlay87_patched.bin
back intoarm9.bin
,overlay9-12.bin
,overlay9-16.bin
,overlay9-86.bin
, andoverlay9-87.bin
, respectively. - Extract
poketool/pokegra/pl_pokegra.narc
. - For each image in the
ShinyChanges
folder, insert that image to the proper place using "Pokemon Ds/Pic Platinum". (Note that some Pokémon might have changes to the base sprite.) - Reinsert
pl_pokegra.narc
. - If you want to be really thorough, extract
poketool/pokegra/pokegra.narc
and replace each changed palette entry (only the palette ones, not the image ones, unless necessary like Blissey!).
As stated, each Pokémon has its palette hue shifted by an amount determined by their personality value. To be more precise, the hue shift is currently coded to be within +/- 20 degrees, and the third personality byte, masked with 0x3F
, is used to determine it, meaning there are 64 possible steps within that range. (Though, it should be noted values 0x00
and 0x20
both mean "no shift", and realistically, the steps are granular enough to the point where two adjacent values may end up producing the same color. So, it's effectively less than 64.)
The added code used to make this work are inserted in arm9.bin
at 0x5003C
through 0x50414
, as this I'm told this range (and up until 0x507E4
) is unused by the game. I also use this space as free RAM when needed, also. A quick layout:
Address | Description |
---|---|
0x020501E0 |
Personality value of player Pokémon 1 |
0x020501E4 |
Personality value of enemy Pokémon 1 |
0x020501E8 |
Personality value of player/partner Pokémon 2 |
0x020501EC |
Personality value of enemy Pokémon 2 |
0x020501F0 |
Saved-off variable battle data pointer |
0x020501F4 |
Unused |
0x020501F8 |
0xBA771E if currently in battle, something else otherwise. Set by Hijack_BattleStart , Hijack_BattleEnd , Hijack_BattleEndCaught . Read by Hijack_HueShift.s |
0x020501FC |
Read in Hijack_GbaPal.s to determine if up the call chain, it was signalled that a Pokémon's battle sprite palette is being loaded. 0xBEEFXXXX where XXXX is the index of the sprite being loaded (0-3). Also set to 0xFA3E when loading a sprite for the Hall of Fame, to be read in Hijack_PaletteUpload.s ; 0x0E66 when loading the egg hatching animation graphics, to be read by Hijack_AnimPal.s . |
0x02050200 |
Contains the personality value of the Pokémon read by the last call to GetPkmnData or GetBoxPkmnData , read by Hijack_HueShift.s and Hijack_MiscSprite.s . |
A rundown of the code files involved:
-
hueshift.c
- Contains the function that performs a hue shift on a given palette by a value (limited through a mask with0x3F
). The method for hue shifting was taken from this StackOverflow post but adapted to do it in fixed point since the DS does not have a floating point unit. Instead of trying to run sin/cos functions directly, I generate two tables (seetableprinter.d
) of precomputed sin/cos values in the fixed point format I wanted. Values0x00
through0x1F
shift positively, while0x20
through0x3F
shift negatively. -
Hijack_PersonalitySave.s
- This code is jumped to from hijacks in both GetPkmnData and GetBoxPkmnData. It just grabs the personality value of the Pokémon involved in this function call (stored conveniently as the first thing in the data block pointed to byr0
) and puts it at0x02050200
to be used byHijack_HueShift.s
. -
Hijack_HueShift.s
- Hijacks right during a palette load for most instances of a Pokémon's sprite. Most of the time, Pokémon sprites are drawn as a textured polygon for the squash/stretch effect for when they're animated. Outside of battle, the personality value used is stored at0x02050200
. The hope here is that whatever the last call toGetPkmnData
orGetBoxPkmnData
prior to this code being reached was for the Pokémon we're loading the palette for. However, this is not necessarily true during a battle, and in that case, it will read from the personality table at0x020501E0
(seeHijack_PersonalityTableBuild2.s
). The code inhueshift.c
is then called to achieve the hue shift effect. -
Hijack_PersonalityClearPokedex.s
- Hijack in some function that appears to always be called when entering the Pokédex. This sets0x02050200
to0
so that Pokémon viewed from the Pokédex don't have any hue shift applied to them. -
Hijack_BattleDataPtrSave.s
- This hijacks a function in overlay 16 ("Battle Interface") calledGetMainBattleData_GetAdrOfPkmnInParty
. It stores a pointer stored passed inr0
relating to varialbe battle data to0x020501F0
so that it can be used inHijack_PersonalityTableBuild.s
. (Note: This very probably could have been achieved by hijacking a different place instead, but this still works.) -
Hijack_BattleStart
- Hijacks the0
case of the switch inBattleEngineInit
in overlay 16 (right when a battle starts). Stores0xBA771E
into0x020501F8
to be read later byHijack_HueShift.s
. -
Hijack_BattleEnd
- Hijacks the9
case of the switch inBattleEngineInit
in overlay 16 (right when a battle ends). Stores0
into0x020501F8
to be read later byHijack_HueShift.s
. -
Hijack_BattleEndCaught
- Hijacks inTryToCatchPkmn
in overlay 16 when a Pokémon is caught. Stores0
into0x020501F8
to be read later byHijack_HueShift.s
. This prevents the sprites loaded during the post-capture sequence from using the in-battle personality table, since they always use the 0th sprite slot. -
Hijack_PersonalityTableBuild.s
- Hijacks inside a function in overlay 12 ("Move Animations") that gets called whenever a Pokémon is being switched out. In battles, not only do Pokémon load the typical textured polygon sprite, but during various battle animations, a GBA-styled tile-based sprite that looks just like the other one is placed on top of it. To account for this, I had to do a lot more work to figure out which Pokémon's sprite is being loaded. My code is ran in the middle of a loop from 0 to 3 (one for each Pokémon that could be on the field). I have to use this current index to read into a table describing which Pokémon are actually on the field at the moment (can be 0-5), callGetMainBattleData_GetAdrOfPkmnInParty
to grab the pointer to that Pokémon in whatever party it's in, get the personality value, then store it off in my table at0x020501E0
so it can be read later. (Note: The meaning of the loop indices 0-3 is the same as in the free RAM table above). -
Hijack_PersonalityTableBuild2.s
- Hijacks insideAllocInitMainBattleData
in overlay 16; when a battle begins, right after all the data is loaded. Basically the same asHijack_PersonalityTableBuild.s
, but it does a loop over active Pokémon indices 0-3 itself. -
Hijack_BattleSprite.s
- Hijacked in the same functionHijack_PersonalityTableBuild.s
, reached soon after it if the current loop index is for a Pokémon who needs to load its alternate sprite during the switchout animation. Stores0xBEEFXXXX
whereXXXX
is the current loop index at0x020501FC
so it can be read byHijack_GbaPal.s
. -
Hijack_BattleSprite2.s
- Reached during move animations. Does basically the same thing asHijack_BattleSprite.s
. -
Hijack_GbaPal.s
- Reached from down the call chain (inFunction_2002fec
) afterHijack_BattleSprite.s
orHijack_BattleSprite2.s
. Reads0x020501FC
, looking for0xBEEF
to determine whether this is a Pokémon's palette being loaded. If so, it uses the current battle sprite ID to index into the personality value table at0x020501E0
and then use it to call the code inhueshift.c
.0x020501FC
is then set to0
so to ensure that this code won't run again unless this is for a Pokémon sprite. -
Hijack_MiscSprite.s
- Hijacks inside a function used for loading the palette for Pokémon sprites in miscellaneous circumstances, like during the HM use animation and in the introduction when Professor Rowan sends out a Pokémon. Loads the personality value at0x02050200
and calls the code athueshift.c
. -
Hijack_HallOfFame.s
- Hijacks functions in overlay 86 and overlay 87 that are about to callCall_LoadFromNARC_RLCN
to load Pokémon palettes during viewing the Hall of Fame (actual location and in the PC, respectively). Sets0x020501FC
to0xFA3E
so it can be read down the call chain byHijack_PaletteUpload.s
. -
Hijack_PaletteUpload.s
- Hijacks in a common function that uploads palettes to the palette RAM. Checks if0x020501FC
was set to0xFA3E
earlier in the call chain - if so, loads the personality value at0x02050200
, calls the code athueshift.c
, then resets0x020501FC
to0
. -
Hijack_EggHatching.s
- Hijacks in overlay 119, right before a function call to load the palette for the egg hatching animation. Sets0x020501FC
to0x0E66
to be read further down the call chain byHijack_AnimPal.s
. -
Hijack_AnimPal.s
- Hijacks a common function related to loading palettes (unsure of its exact purpose), just before uploading to palette RAM occurs. Checks if0x020501FC
was set to0x0E66
earlier in the call chain - if so, loads the personality value at0x02050200
, calls the code athueshift.c
, then resets0x020501FC
to0
.
- MKHT - Lots of help choosing shiny colors.