Reverse-engineered analysis of shiny generation in Pokémon Alpha Sapphire

  • Thread starter Thread starter Zetta_D
  • Start date Start date
  • Views Views 838
  • Replies Replies 1
  • Likes Likes 5

Zetta_D

Well-Known Member
Member
Joined
Jun 25, 2019
Messages
141
Reaction score
129
Trophies
1
Age
23
Location
France
XP
742
Country
France
Hello,
This morning I decided to gather my various works on Pokémon games and start a documentation series.
The first topic I am presenting is Shiny Pokémon generation.
It is often said that the chance of encountering a shiny Pokémon is 1 in 4096, but do you know where this number comes from?
The analysis was performed on Pokémon Alpha Sapphire v1.4, based on disassembled code.
This document aims to educate shiny hunters and debunk myths, such as: “Fishing near a rock will increase your chances of finding a shiny.”

1) Random Number Generation

The game uses the TinyMT32 PRNG algorithm.
The function generating random numbers is located at address 0x00164714.
I will not explain how this algorithm works; we will just use the following function from now on.
C:
/*
 * Returns a uniformly distributed random value.
 * Each bit has a 50% chance of being 0 or 1.
 */
u32 GetRandomU32();

2) Checking if a Pokémon is Shiny

The corresponding function is at address 0x00168F48.
The game does not use a boolean isShiny. Instead, it uses two identifiers: ID and PID (see PKHeX: PK6.cs).
Both are 32-bit random numbers generated using the algorithm described above.

Code:
IsShiny:
    UXTH    R3, R0
    EOR     R0, R3, R0, LSR #16
    UXTH    R2, R1
    EOR     R0, R0, R1, LSR #16
    EOR     R0, R0, R2
    CMP     R0, #0x10
    MOVCC   R0, #1
    MOVCS   R0, #0
    BX      LR

Here is the function translated into C for those who are not familiar with ARM assembly.
For the rest of this document, I will only provide the C code that I have rewritten - no more assembly code - but I am keeping the addresses in case you want to decompile the game yourself.

C:
bool IsShiny(u32 id, u32 pid)
{
    u16 a = (u16)id;         // low 16 bits of ID
    u16 b = (u16)(id >> 16); // high 16 bits of ID
    u16 c = (u16)pid;        // low 16 bits of PID
    u16 d = (u16)(pid >> 16);// high 16 bits of PID

    u16 r = a ^ b ^ c ^ d;   // XOR all parts
    return r < 16;           // shiny if the top 12 bits are all 0
}

For the condition r < 16 to be satisfied, r must look like this in binary:
r = 0b000000000000XXXX, where each X can be 0 or 1.
The probability that bit 15 is 0 is 1/2, bit 14 is also 1/2, and so on down to bit 4.
Therefore, the total probability is:
(1/2) x ... x (1/2) = (1/2)^12 = 1/4096 ≈ 0.00024414062 ≈ 0.024%
This is the chance of encountering a shiny Pokémon.

3) Forcing a Pokémon Not to Be Shiny

Function at 0x00168FCC.
By toggling the correct bit, the condition above is no longer satisfied:
C:
void SetNoShiny(u32 id, u32 *pid)
{
    if (IsShiny(id, *pid))
    {
        *pid ^= 0x10000000;
    }
}

4) Forcing a Pokémon to Be Shiny

Function at 0x00168F6C.
Here, we use the property X ^ X = 0 (where ^ = XOR).
Since d = a ^ b ^ c, the condition becomes:
r = a ^ b ^ c ^ d = (a ^ b ^ c) ^ (a ^ b ^ c) = 0 < 16

C:
void SetShiny(u32 id, u32 *pid)
{
    if (IsShiny(id, *pid))
        return;

    u16 a = (u16)id;
    u16 b = (u16)(id >> 16);
    u16 c = (u16)*pid;
    u16 d = a ^ b ^ c;

    *pid = (d << 16) | c;
}

5) Masuda Method: Increasing Shiny Chance via Breeding

The function that initializes the egg is located at address 0x003B1784, and the portion handling shiny generation is at 0x003B1D38.
- If no conditions are met, n = 0 → no additional draws are performed, and the PID generated initially is kept.
This gives a base chance of 1/4096.
- If the parents have different languages, 6 draws are performed.
Since these draws are independent, the total probability becomes 1/4096 + ... + 1/4096 = 6/4096 ≈ 0.00146484375 ≈ 0.146%.
- With the Shiny Charm, the total increases to 8/4096 ≈ 0.001953125 ≈ 0.195%.

C:
void ShinyEgg(u32 id, u32 *pid, u8 motherLanguage, u8 fatherLanguage, bool hasShinyCharm)
{
    u32 n = 0; // number of random draws

    if (motherLanguage != fatherLanguage)
        n += 6;

    if (hasShinyCharm)
        n += 2;

    for (u32 k = 0; k < n; k++)
    {
        u32 randomPid = GetRandomU32();
        if (IsShiny(id, randomPid))
        {
            *pid = randomPid;
            break;
        }
    }
}

6) Regular Encounters + Fishing Method

The relevant portion of the function is at 0x007D8BD8, and the second part at 0x0014EC00.
For fishing, the game uses a counter that starts at 0 and caps at 20.
- Each successful cast increments the counter by 1.
- The counter resets if the encountered Pokémon is shiny, or if the rod is cast too early or too late, resulting in a failure.
Nothing else affects it—no conditions based on the player’s position, etc.

As above, all draws are independent.
Examples :
- With the Shiny Charm and the first fishing attempt (fishingCounter = 0), n = 3, giving a 3/4096 chance of encountering a shiny.
- If fishingCounter = 20, the probability becomes (3+2∗20)/4096 = 43/4096 ≈ 0.010498 ≈ 1.05%.

C:
void ShinyEncounter(u32 id, u32 *pid, bool hasShinyCharm, u32 fishingCounter, u32 type)
{
    static const u32 MUST_BE_NO_SHINY = 0x1FFFFFFFF;
    static const u32 MUST_BE_SHINY    = 0x2FFFFFFFF;
    static const u32 NO_CONSTRAINT    = 0x3FFFFFFFF;

    u32 n = 1 + (hasShinyCharm ? 2 : 0);
    n += fishingCounter * 2;

    if (type == MUST_BE_NO_SHINY)
    {
        SetNoShiny(id, pid);
    }
    else if (type == MUST_BE_SHINY)
    {
        SetShiny(id, pid);
    }
    else if (type == NO_CONSTRAINT)
    {
        for (u32 k = 0; k < n; k++)
        {
            u32 randomPid = GetRandomU32();
            if (IsShiny(id, randomPid))
            {
                *pid = randomPid;
                break;
            }
        }
    }
}

7) DexNav
The portion of code we are interested in is at address 0x007D891C within the function at 0x007D8728.
I am not able to tell you what the variable v represents or why they chose to perform this calculation.

C:
/**
 * @type: Pointer to the encounter type (may be set to MUST_BE_SHINY)
 * @consecutiveEncountersNum: Number of consecutive encounters (not necessarily the same Pokémon)
 * @encountNum: Total number of encounters for this Pokémon
 * @hasShinyCharm: True if the player possesses the Shiny Charm
 * @isDexNavSpecial: True if the Pokémon is a special DexNav encounter
 */
void ShinyDexNav(u32 *type, u32 consecutiveEncountersNum, u32 encountNum, bool hasShinyCharm, bool isDexNavSpecial)
{
    double p = 0;
    u32 v = encountNum;

    if (v > 200)
    {
        p = v - 200;
        v = 200;
    }
    if (v > 100)
    {
        p += 2 * v - 200;
        v = 100;
    }
    if (v > 0)
    {
        p += 6 * v;
    }

    u32 n = 1 + (hasShinyCharm ? 2 : 0);
    if (isDexNavSpecial)
        n += 4;
    if (consecutiveEncountersNum == 50)
        n += 5;
    else if (consecutiveEncountersNum == 100)
        n += 10;

    for (u32 k = 0; k < n; k++)
    {
        if (GetRandomU32() % 10000 < p * 0.01)
        {
            *type = MUST_BE_SHINY;
            break;
        }
    }
}

If you have any questions or notice anything missing, feel free to ask.

Bonus Section
Since I have provided the function that checks whether a Pokémon is shiny or not,
you can simply always return true so that all Pokémon in the game (starters, legendaries, opposing players’ Pokémon, etc.) are shiny.
In assembly: MOV R0, #1; BX LR
This gives the following Action Replay code:
Code:
[ALWAYS SHINY]
00168F48 E3A00001
00168F4C E12FFF1E
 

Site & Scene News

Popular threads in this forum