Jump to content

Help trying to understand maingame NPC PID generation in Platinum (Solved)


Recommended Posts

Hello, I don't know if this is documented anywhere, but recently I have been trying to find out how Platinum generates PIDs for NPC trainers during the main game. I've mainly been examining Pokemon caught with a catch trainer code, some light hex editing on trpoke/data, and looking at the disassembly. So far, I've made the following observations:

  • The lowest byte is either 0x78 or 0x88 depending on the trainer's gender - 0x78 for female, 0x88 for male. This causes Pokemon to be gendered the same as their owner unless they have any sort of existing gender ratio, in which case it's their preference. It also causes all NPC Pokemon to have their first ability.
  • The highest byte is always 0.
  • The middle two bytes are where it's more complex. It seems the sum of species, difficulty, level, and trainer location (not sure if the trainer number or memory address) is used:
  • On the same trainer, a 250 difficulty Armaldo (species 348) has the same PID as a 200 difficulty Staraptor (species 398).
  • On the same trainer, a Level 31 Combee (species 415) has the same PID as a Level 48 Staraptor (species 398).
  • A Fisherman (trainer 175 in the disassembly) with a Level 42 Gyarados has the same PID as another Fisherman's (trainer 174) Level 43 Gyarados.
  • When any of these factors is changed by 1, the value of the middle bytes is incremented (or possibly decremented by its ffff-complement) by a near-constant amount (like, plus or minus 1). If it reaches 0 or ffff, it loops around. This leads me to believe that the previous sum is multiplied by some value, but I cannot parse what.
  • I have noticed that this increment seems to be determined by solely the trainer class as far as the trainer information is concerned. A Fisherman turned into a Rival class (no other modifications) is incremented by the same amount with each Level/Species/Difficulty/Trainer change as an actual fight against the Rival is.

Looking into the disassembly seems to verify a lot of these observations:

 
ldrb r0, [r0, #0x0] @ MainGameData_NPCTrainer_Class + r5*0x34 MainGameData_NPCTrainer_Size
bl Function_20793ac
cmp r0, #0x1
bne branch_207940a

mov r0, #0x78
str r0, [sp, #0x14]
b branch_207940e

branch_207940a: @ 207940a :thumb
mov r0, #0x88

str r0, [sp, #0x14]

This preceding code assigns either 0x78 or 0x88 and stores that on the stack, which is definitely the eventual gender byte.

branch_2079440: @ 2079440 :thumb
ldrh r0, [r7, #TrPkmn0_Species]
mov r1, #0x3f
lsl r1, r1, #10
and r1, r0
asr r2, r1, #10 @ I think the point of all this shifting and anding is to clear the higher bits (that have no legal reason to exist) on the species? It shouldn't affect the value for any legal Pokemon.
add r1, sp, #0x64
strb r2, [r1, #0x3]
ldr r1, =0x3ff
ldrh r2, [r7, #TrPkmn0_0] @ This data is definitely the difficulty.
and r0, r1
lsl r0, r0, #16
ldrh r1, [r7, #TrPkmn0_Lvl]
lsr r0, r0, #16
str r0, [sp, #0x34] @ Storing the should-be unaffected species on the stack
ldr r0, [sp, #0x38] @ NPCTrainerDataAdress
add r2, r2, r1 @ Difficulty+Level
ldr r1, [sp, #0x34] @ Retrieving the species
ldr r0, [r0, #0x18] @ Getting some offset of the "NPCTrainerDataAddress", I presume the location based on my observations
add r1, r1, r2 @ (Difficulty+Level)+Species
add r0, r0, r1 @ (Difficulty+Level+Species)+Trainer something
str r0, [sp, #0x58] @ Sum stored on the stack
bl SetPRNGSeed
add r0, r4, r5
add r0, #0x29
ldrb r0, [r0, #0x0] @ MainGameData_NPCTrainer_Class + r5*0x34 (this comment is from the disassembly)
mov r6, #0x0
cmp r0, #0x0
ble branch_207948c

branch_207947a: @ 207947a :thumb
bl PRNG
str r0, [sp, #0x58]
add r0, r4, r5
add r0, #0x29
ldrb r0, [r0, #0x0] @ MainGameData_NPCTrainer_Class + r5*0x34
.hword 0x1c76 @ add r6, r6, #0x1
cmp r6, r0
blt branch_207947a

branch_207948c: @ 207948c :thumb
ldr r0, [sp, #0x58] @ Retrieving the earlier sum after...something is done to it
lsl r1, r0, #8 @ Left shifting to clear the lowest byte
ldr r0, [sp, #0x14] @ Retrieving the gender value

add r6, r1, r0 @ Adding that gender value to the middle bytes

 

I am pretty sure this is where the process ends, but as you can tell by the lack of comments, the middle branch confuses me. Based on my limited understanding of assembly, I think loops like this are used to simulate multiplication as I theorized earlier, but there's an actual multiplication instruction so I assume it is more complex than that. The big issue is that even looking at the whole file, I am having trouble keeping track of what r4 and r5 should contain at this point, or if there any significance to 0x29 (an offset? idk) or if it's just being used as a constant. I'm also not sure if there's any significance to PRNG being called every time in the loop since iirc this is how player PIDs are generated, but the trainer PIDs don't seem to be random.

Writing this out, I realize it's a lot, but I think I'm close and I'm hoping someone more familiar with assembly and Pokemon internally can crack this final bit. Or this has already been discovered and I've wasted a lot of time. For verification purposes, some observed PIDs:

  • 200 Difficulty Level 48 Staraptor on League Barry (Empoleon team, #481): 0x00877488. Level 46: 0x000ae788. Level 47: 0x00c92e88. Level 49: 0x0045ba88
  • 200 Difficulty Level 48 Staraptor on Elite Four Aaron (#261): 0x00e0fb88, Level 49: 0x00b6ec88
  • 200 Difficulty Level 48 Staraptor on Elite Four Bertha (#262): 0x007b1578. Level 47: 0x00a11e78
  • 0 Difficulty Level 43 Gyarados on Fisherman (#175): 0x0079fc88. Level 42: 0x001f2488
  • 0 Difficulty Level 43 Gyarados on Fisherman (#174): 0x001f2488. Level 42: 0x00c44c88
Edited by sb the great
  • Like 2
Link to comment
Share on other sites

I figured it out!

The big thing that helped me out was realizing the assembly was in RAM, so I could edit instructions fairly easily with cheats. Since I knew R6 was where the PID was stored, I replaced the final instruction that adds the gender constant with one that stored whatever I wanted in R6. Then I could just catch a trainer Pokemon and look at its PID to find out what the value was. I first found out that the Trainer information that's part of the Level+Species+Difficulty+Trainer sum is their slot in trdata, not an address. So the Fishermen in my first post would just be 174 and 175. I then found out that R4 was some address, R5 was 0x34, but what really mattered was that R4+R5+0x29 was where the trainer class was located in RAM. With that in mind, the loop, outside of the PRNG call, doesn't actually do anything but iterate a number of times equal to the trainer class. So obviously the magic was happening with the PRNG call. I couldn't find it in the disassembly on github, but looking at in Desmume's disassembler:

PRNG

To be honest, I couldn't exactly parse what the values involved here were, other than that the loading from the program counter+offset should be constants since PC should always be the same at a given instruction, but thankfully RNG is one of the most well-documented aspects of Pokemon. From Bulbapedia:

result = 0x41C64E6D * seed + 0x00006073

This lined up with what I saw in the disassembler. As for what the seed was, there's a branch to SetPRNGSeed right after the sum of Level+Species+Difficulty+Trainer ID is calculated, so that seemed pretty clear. And indeed, that's what the initial seed was. For when the function is ran multiple times, the subsequent seeds are the low 4 bytes of the result function. The low 2 bytes are then shifted away as each PRNG call ends, and that's how the middle two bytes of trainer PID are calculated. The only thing I'm not sure of is where the higher (anything after the lowest 2) bytes of the PRNG result go. They're gone in the end for the PID, but as far as I can tell nothing the instructions do get rid of them. I feel like it has something to do with the shifts, since there's 3 (Right, Left, Right, all 16 bits), but afaik that should only remove the last 2 bytes. Still, it's not important for finding out the PID.

To summarize how this works:

  • The lowest byte is either 0x78 or 0x88 depending on the trainer's gender - 0x78 for female, 0x88 for male. This causes Pokemon to be gendered the same as their owner unless they have any sort of existing gender ratio, in which case it's whatever gender they're skewed to. It also causes all NPC Pokemon to have their first ability.
  • The middle two bytes are calculated by taking the sum of Level, Species, Difficulty, and Trainer ID, and using that to seed the PRNG function, which is called a number of times equal to the trainer class. The PRNG uses the function result = 0x41C64E6D * seed + 0x00006073, with the low 4 bytes being used to seed the next call. The result is then shifted right 2 bytes. For the final PID middle bytes result, discard anything but the lowest 2 bytes.
  • The highest byte is always 0.

(I do wonder if Gen 3 uses the same algorithm, since it has the same PRNG calculation)

Link to comment
Share on other sites

  • sb the great changed the title to Help trying to understand maingame NPC PID generation in Platinum (Solved)

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
×
×
  • Create New...