- 119
- Posts
- 15
- Years
- Mt. Silver
- Seen Aug 4, 2024
This problem is solved. See bottom of the post for the solution.
I'm trying to write a script in which the player receives a Pokémon that is not theirs originally. I don't want a trading cutscene or anything, just a givepokemon sequence. One important element of this is changing the OT ID of the Pokémon to a random number.
I have tried to approach this the same way in which I changed the OT name and the Pokémon's nickname, namely by using "copybyte" or "writebytetooffset". I am using Leon's & UltimaSoul's ROM base wich has disabled checksum reads. For good measure, I followed all of the steps below to completely disable Pokémon data encryption and allow for easy data manipulation without the risk of Bad Eggs. (I know the OT ID is not part of the encrypted data, but still.) This went fine and didn't hurt the game in any way.
Next, I double-checked where in the RAM the OT ID of the party Pokémon are stored. With help of Bulbapedia, some other pages and a hex calculator I have identified the following addresses as the locations of the visible (non-secret) OT ID's of the party Pokémon. Presumably because of the disabled encryption, the ID numbers are simply stored in "plaintext", i.e. not in reversed order or anything. I have checked this multiple times with the memory viewer of the emulator I am using. Each time, the two bytes stored at this location (in hex) corresponds to the visible OT ID (in dec). For example, in one run with a full party, each of these addresses would contain the bytes "A2 07" which translates to "41479" in decimal. This was exactly the OT ID for all Pokémon.
As mentioned, in other scripts I have succesfully manipulated the nickname and OT name of party Pokémon by using buffers and "copybyte" commands to copy string bytes to the right offsets. I hoped the same could be done with the OT ID of party Pokémon. So I wrote a script which followed three steps:
The locations of the 0x8001 and 0x8002 variables (each two bytes long) are as follows (source):
This resulted in the following script:
Here is the problem:
The script is not game breaking, the Pokémon is received and everything is in order. Except that the OT ID remains unchanged from the Player's Trainer ID. Also when looking in the memory at the abovementioned locations in which the OT ID's are stored, they remain the same and identical for each Pokémon received.
To check whether it had to do with the "copybyte" command, I also included a "writebytetooffset" branch in the script above (@otid2) to write a pre-determined OT ID into the memory. But this has no effect either.
For some reason unbeknownst to me, these commands are able to manipulate Pokémon nicknames, but not OT ID's. It's not just that the script is faulty, but the actual process of writing bytes to memory does not take place. Can someone explain to me where I went wrong?
Thanks a lot for any help!
PS: I am aware that this level of game data manipulation might be better suited for ASM, but I kind of hope I can solve it using XSE and common sense.
EDIT: I have found the solution to this problem, as described below:
What I got wrong was how addresses/offsets and memory locations work. I don't know the correct technical terminology, but using simple language, I assumed that the order of all bytes in the RAM memory was from left-to-right. While in reality each block of four bytes goes from right-to-left. I had seen this explained before but I somehow didn't think of it before. It is best illustrated as follows:
Because of this, I had simply gotten the locations of both the relevant bytes in the 0x8001 and 0x8002 bytes wrong, but most importantly the correct destinations of these bytes. The old script above was actually still changing the OT ID in a way, but the secret (invisible) OT ID, not the visible one. I hope this makes some sense, but it is hard to properly explain it.
In any case, the corrected script is as follows - which works as a charm:
Hope this helps anyone in need of changing the OT ID's of party Pokémon. Remember to disable checksum reading as the above script will otherwise result in a Bad Egg. Also, once you have done that, trading from your hack with games that do not have checksum disabled is not (always) possible.
I'm trying to write a script in which the player receives a Pokémon that is not theirs originally. I don't want a trading cutscene or anything, just a givepokemon sequence. One important element of this is changing the OT ID of the Pokémon to a random number.
I have tried to approach this the same way in which I changed the OT name and the Pokémon's nickname, namely by using "copybyte" or "writebytetooffset". I am using Leon's & UltimaSoul's ROM base wich has disabled checksum reads. For good measure, I followed all of the steps below to completely disable Pokémon data encryption and allow for easy data manipulation without the risk of Bad Eggs. (I know the OT ID is not part of the encrypted data, but still.) This went fine and didn't hurt the game in any way.
Spoiler:
(...)
At any rate, this inspired me to work out how to get rid of the encryption surrounding them (all offsets for FR):
To unscramble the order, write 00 20 00 00 00 00 at x3F94C. This will always put them in order 0, (growth, attacks, EVs, misc.).
To remove the XOR encryption used for them, put 00 00 at x3F92A, x3F930, x3F906, x3F90C.
To remove the checksum reads, allowing for easy memory editor usage to tweak values, put 0B E0 at x3FDA8 and 0E E0 at x40530.
To remove the checksum writes, effectively adding two more bytes to the Pokemon structure to be used for whatever you feel like, put 00 00 at x40AE6.
For obvious reasons, this will ruin existing saves, but makes memory editing and Pokemon hacking simpler in addition to adding two more bytes to the Pokemon struct.
Code:
Party slot nr. visible OT ID
in memory
-------------------------------------------------------------------------------------------------
1 0x0202428A .. 8B
2 0x020242EE .. EF
3 0x02024352 .. 53
4 0x020243B6 .. B7
5 0x0202441A .. 1B
6 0x0202447E .. 7F
As mentioned, in other scripts I have succesfully manipulated the nickname and OT name of party Pokémon by using buffers and "copybyte" commands to copy string bytes to the right offsets. I hoped the same could be done with the OT ID of party Pokémon. So I wrote a script which followed three steps:
- Give the player a Pokémon using givepokemon.
- Generate a random number between 0x0 and 0xEE and store in 0x8001. Repeat for 0x8002.
- Depending on the party slot, copy the randomly generated number to the location of the OT ID.
The locations of the 0x8001 and 0x8002 variables (each two bytes long) are as follows (source):
Code:
0x020370BA .. BB Script variable 0x8001
0x020370BC .. BD Script variable 0x8002
This resulted in the following script:
Spoiler:
Code:
#dynamic 0xA03D00
#org @start
faceplayer
lockall
setflag 0x828 // enables Pokémon party in menu
countpokemon // checks if party is full
compare LASTRESULT 0x6
if 0x1 goto @full
goto @give
#org @give
givepokemon 0x3C 0x5 0x0 0x0 0x0 0x0 // Give a Poliwag
random 0xEE // Generate random number, stored in 0x800D
copyvar 0x8001 0x800D // Copy random number to 0x8001
random 0xEE // Repeat
copyvar 0x8002 0x800D
goto @writeotid
#org @writeotid
countpokemon // Check in which slot the new Pokémon was placed, go to corresponding branch
compare LASTRESULT 0x6
if 0x1 goto @otid6
compare LASTRESULT 0x5
if 0x1 goto @otid5
compare LASTRESULT 0x4
if 0x1 goto @otid4
compare LASTRESULT 0x3
if 0x1 goto @otid3
compare LASTRESULT 0x2
if 0x1 goto @otid2
compare LASTRESULT 0x1
if 0x1 goto @otid1
msgbox @msgbye 0x6
releaseall
end
#org @otid6
copybyte 0x0202447E 0x020370BD // Note: 0x0202447E is the OT ID location for slot 6 (first byte), 0x020370BD is var 0x8002 (second byte)
copybyte 0x0202447F 0x020370BB // Note: 0x0202447F is the OT ID location for slot 6 (second byte), 0x020370BB is var 0x8001 (second byte)
goto @info
#org @otid5
copybyte 0x0202441A 0x020370BD
copybyte 0x0202441B 0x020370BB
goto @info
#org @otid4
copybyte 0x020243B6 0x020370BD
copybyte 0x020243B7 0x020370BB
goto @info
#org @otid3
copybyte 0x02024352 0x020370BD
copybyte 0x02024353 0x020370BB
goto @info
#org @otid2
writebytetooffset 0x1E 0x020242EE // Note: 1E61 (hex) = 7777 (dec). Included this to test whether writebytetooffset would work as well.
writebytetooffset 0x61 0x020242EF // Note: 0x020242EE and 0x020242EF are the locations of the OT ID for slot 2.
goto @info
#org @otid1
copybyte 0x0202428A 0x020370BD
copybyte 0x0202428B 0x020370BB
goto @info
#org @info
bufferpokemon 0x0 0x3C // copy name of the given Pokemon 0x3C into buffer 1.
showpokepic 0x3C 0xA 0x3 // show pic + cry of given Pokemon, coordinates 0xA 0x3.
waitcry
pause 0x13
fanfare 0x13E
msgbox @msgreceived 0x4 // message "player received X".
waitfanfare
closeonkeypress
hidepokepic
msgbox @msgbye 0x6
releaseall
end
#org @full
msgbox @msgfull 0x6
releaseall
end
#org @msgfull
= Your party is full. Come\nback when you have space.
#org @msgreceived
= [player] got a [buffer1]!
#org @msgbye
= See you again.
The script is not game breaking, the Pokémon is received and everything is in order. Except that the OT ID remains unchanged from the Player's Trainer ID. Also when looking in the memory at the abovementioned locations in which the OT ID's are stored, they remain the same and identical for each Pokémon received.
To check whether it had to do with the "copybyte" command, I also included a "writebytetooffset" branch in the script above (@otid2) to write a pre-determined OT ID into the memory. But this has no effect either.
For some reason unbeknownst to me, these commands are able to manipulate Pokémon nicknames, but not OT ID's. It's not just that the script is faulty, but the actual process of writing bytes to memory does not take place. Can someone explain to me where I went wrong?
Thanks a lot for any help!
PS: I am aware that this level of game data manipulation might be better suited for ASM, but I kind of hope I can solve it using XSE and common sense.
EDIT: I have found the solution to this problem, as described below:
What I got wrong was how addresses/offsets and memory locations work. I don't know the correct technical terminology, but using simple language, I assumed that the order of all bytes in the RAM memory was from left-to-right. While in reality each block of four bytes goes from right-to-left. I had seen this explained before but I somehow didn't think of it before. It is best illustrated as follows:
Code:
address ..B0 ..B4 ..B8 ..BC
020370B0 | 00000000 00000000 00540000 0000004C
what i thought: B8B9BABB BCBDBEBF
in reality: BBBAB9B8 BFBDBDBC
In any case, the corrected script is as follows - which works as a charm:
Spoiler:
Code:
#dynamic 0xA03D00
#org @start
faceplayer
lockall
setflag 0x828
countpokemon
compare LASTRESULT 0x6
if 0x1 goto @full
goto @give
#org @give
givepokemon 0x3C 0x5 0x0 0x0 0x0 0x0
random 0xEE
copyvar 0x8001 0x800D
random 0xEE
copyvar 0x8002 0x800D
goto @writeotid
#org @writeotid
countpokemon
compare LASTRESULT 0x6
if 0x1 goto @otid6
compare LASTRESULT 0x5
if 0x1 goto @otid5
compare LASTRESULT 0x4
if 0x1 goto @otid4
compare LASTRESULT 0x3
if 0x1 goto @otid3
compare LASTRESULT 0x2
if 0x1 goto @otid2
compare LASTRESULT 0x1
if 0x1 goto @otid1
msgbox @msgbye 0x6
releaseall
end
#org @otid6
copybyte 0x0202447C 0x020370BA // Changed the variable locations AND the OT ID locations
copybyte 0x0202447D 0x020370BC
goto @info
#org @otid5
copybyte 0x02024418 0x020370BA
copybyte 0x02024419 0x020370BC
goto @info
#org @otid4
copybyte 0x020243B4 0x020370BA
copybyte 0x020243B5 0x020370BC
goto @info
#org @otid3
copybyte 0x02024350 0x020370BA
copybyte 0x02024351 0x020370BC
goto @info
#org @otid2
writebytetooffset 0x1E 0x020242ED // 77...
writebytetooffset 0x61 0x020242EC // ...77
goto @info
#org @otid1
copybyte 0x02024288 0x020370BA
copybyte 0x02024289 0x020370BC
goto @info
#org @info
bufferpokemon 0x0 0x3C // copy name of the given Pokemon 0x3C into buffer 1.
showpokepic 0x3C 0xA 0x3 // show pic + cry of Pokemon, coordinates 0xA 0x3.
waitcry
pause 0x13
fanfare 0x13E
msgbox @msgreceived 0x4 // message "player received X".
waitfanfare
closeonkeypress
hidepokepic
msgbox @msgbye 0x6
releaseall
end
#org @full
msgbox @msgfull 0x6
releaseall
end
#org @msgfull
= Your party is full. Come\nback when you have space.
#org @msgreceived
= [player] got a [buffer1]!
#org @msgbye
= See you again.
Hope this helps anyone in need of changing the OT ID's of party Pokémon. Remember to disable checksum reading as the above script will otherwise result in a Bad Egg. Also, once you have done that, trading from your hack with games that do not have checksum disabled is not (always) possible.
Last edited: