I'm working on a lua script for Pokemon FireRed which changes certain values of wild Pokemon when you meet them (for example, turning a wild pokemon into a Bulbasaur, or changing its moveset). My current issue is with changing the species of the Pokemon.
To do this, I read in the data of the wild Pokemon, decrypt it, find the growth substructure, change the first 2 bytes to the new Pokemon's id, calculate the checksum, re-encrypt the data, and then write it all back to memory. See "Pokémon data substructures in Generation III" on Bulbapedia.
It mostly works fine. I can encounter the modified Pokemon, capture it, and fight with it. The only problem is that when I try to view the Pokemon's summary page, the game usually crashes (I say "usually" because it seems to depend on the species of the Pokemon that was modified: out of the one's I've tried, it consistently crashes for Weedle, Caterpie, Kakuna, and Rattata, and works for Pidgey). Any ideas as to what would cause this? The only thing I can think of is that there's some species specific value that I'm neglecting to change.
For anyone who is inclined, the following is a lua script which reproduces the error. I'm using Pokemon FireRed US V1.1 and the VBA-ReRecording emulator. Save the script as a .lua file, run it in the emulator, and every wild Pokemon you meet (and the first Pokemon in enemy trainers' parties) should become a Bulbasaur.
Spoiler:
Code:
G = 1
A = 2
E = 3
M = 4
substructureOrders = {
[0] = {G,A,E,M},
[1] = {G,A,M,E},
[2] = {G,E,A,M},
[3] = {G,E,M,A},
[4] = {G,M,A,E},
[5] = {G,M,E,A},
[6] = {A,G,E,M},
[7] = {A,G,M,E},
[8] = {A,E,G,M},
[9] = {A,E,M,G},
[10] = {A,M,G,E},
[11] = {A,M,E,G},
[12] = {E,G,A,M},
[13] = {E,G,M,A},
[14] = {E,A,G,M},
[15] = {E,A,M,G},
[16] = {E,M,G,A},
[17] = {E,M,A,G},
[18] = {M,G,A,E},
[19] = {M,G,E,A},
[20] = {M,A,G,E},
[21] = {M,A,E,G},
[22] = {M,E,G,A},
[23] = {M,E,A,G}
}
enemyPokemonPointer = 0x0202402C -- pointer to the enemy pokemon's data structure
dataStructureOffset = 32
checksumOffset = 28
substructureSize = 12
pokemonId = 1 -- bulbasaur
function run()
print('battle started')
otId = memory.readdword(enemyPokemonPointer+4)
personality = memory.readdword(enemyPokemonPointer)
encryptionKey = bit.bxor(otId, personality)
substructureOrder = substructureOrders[personality%24]
-- read the data in 12 byte chunks, one chunk for each substructure
encryptedData = {}
for i=1,4 do
encryptedData[i] = memory.readbyterange(enemyPokemonPointer+dataStructureOffset+(i-1)*substructureSize,substructureSize)
end
-- decrypt the data
decryptedData = {}
for i=1,4 do
decryptedData[i] = xor(encryptedData[i], encryptionKey)
end
-- organize the data
growth = decryptedData[indexOf(substructureOrder,G)]
attacks = decryptedData[indexOf(substructureOrder,A)]
ev = decryptedData[indexOf(substructureOrder,E)]
misc = decryptedData[indexOf(substructureOrder,M)]
-- set the pokemon species
growth[1] = bit.band(pokemonId, 0xFF)
growth[2] = bit.rshift(pokemonId, 8)
-- reassemble the data
newUnencryptedData = {}
newUnencryptedData[indexOf(substructureOrder,G)] = growth
newUnencryptedData[indexOf(substructureOrder,A)] = attacks
newUnencryptedData[indexOf(substructureOrder,E)] = ev
newUnencryptedData[indexOf(substructureOrder,M)] = misc
-- calculate the checksum
checksum = calculateChecksum(newUnencryptedData)
-- re-encrypt the data
newEncryptedData = {}
for i=1,4 do
newEncryptedData[i] = xor(newUnencryptedData[i], encryptionKey)
end
-- write the new data
for i=1,4 do
for j=1,#newEncryptedData[i] do
memory.writebyte(enemyPokemonPointer + dataStructureOffset + (i-1)*substructureSize + (j-1), newEncryptedData[i][j])
end
end
memory.writeword(enemyPokemonPointer+checksumOffset, checksum)
end
function calculateChecksum(unencryptedData)
sum = 0
for i=1,#unencryptedData do
for j=1,#unencryptedData[i],2 do
sum = sum +
unencryptedData[i][j] +
unencryptedData[i][j+1]*0x100
end
end
return bit.band(sum, 0xFFFF)
end
-- xor a table of bytes with a double word repeatedly
function xor(byteTable, val)
valTable = {
bit.band(val,0xFF),
bit.ror(bit.band(val,bit.rol(0xFF,8)), 8),
bit.ror(bit.band(val,bit.rol(0xFF,16)), 16),
bit.ror(bit.band(val,bit.rol(0xFF,24)), 24)
}
ret = {}
for i=1,#byteTable do
ret[i] = bit.bxor(byteTable[i], bit.bxor(valTable[(i-1)%4+1]))
end
return ret
end
-- get the index of val in table. Return 0 if not found
function indexOf(table, val)
for i=1,#table do
if table[i] == val then
return i
end
end
return 0
end
print('script started')
memory.registerexec(0x08010672,run) -- address is executed whenever a battle starts
Last edited by DoomInAJar; 26th January 2017 at 04:05 AM.
Reason: wrapping code in spoiler block for readability
I think I have a lead on the problem: I need to be changing experience and/or levels along with the Pokemon species.
The reason for this is that different Pokemon level up at different rates. For example, Pidgey and Bulbasaur level up at a medium-slow rate, whereas Weedle, Caterpie, Kakuna, and Rattata level up at a medium-fast rate; thus, a Rattata with X experience should be at an equal or higher level to a Bulbasaur with X experience. When I convert a Rattata to a Bulbasaur and don't touch the level or experience, the game gets a little bit confused since the Pokemon has a higher level than it should. This fault triggers a failure when trying to calculate the experience required to reach the next level, which is shown in the Summary screen. The game did not crash when converting a Pidgey to a Bulbasaur since they level up at a similar rate.
A possible solution would be to calculate the experience that the new Pokemon should have at its current level, and write that value to the Pokemon.
I figured I'd follow up for anyone with the same problem as me. The fix described above appears to work, and I have updated the code below.
The important updates are contained between the "NEW CODE" comments. I have also include a csv file used in the script which lists the minimum experience points Pokemon of each level and level up type will have, courtesy of Bulbapedia.
Spoiler:
script.lua:
Code:
G = 1
A = 2
E = 3
M = 4
substructureOrders = {
[0] = {G,A,E,M},
[1] = {G,A,M,E},
[2] = {G,E,A,M},
[3] = {G,E,M,A},
[4] = {G,M,A,E},
[5] = {G,M,E,A},
[6] = {A,G,E,M},
[7] = {A,G,M,E},
[8] = {A,E,G,M},
[9] = {A,E,M,G},
[10] = {A,M,G,E},
[11] = {A,M,E,G},
[12] = {E,G,A,M},
[13] = {E,G,M,A},
[14] = {E,A,G,M},
[15] = {E,A,M,G},
[16] = {E,M,G,A},
[17] = {E,M,A,G},
[18] = {M,G,A,E},
[19] = {M,G,E,A},
[20] = {M,A,G,E},
[21] = {M,A,E,G},
[22] = {M,E,G,A},
[23] = {M,E,A,G}
}
enemyPokemonPointer = 0x0202402C -- pointer to the enemy pokemon's data structure
pokemonBaseStatsPointer = 0x08254810
dataStructureOffset = 32
checksumOffset = 28
substructureSize = 12
pokemonId = 1 -- bulbasaur
levelOffset = 84
function run()
print('battle started')
otId = memory.readdword(enemyPokemonPointer+4)
personality = memory.readdword(enemyPokemonPointer)
encryptionKey = bit.bxor(otId, personality)
substructureOrder = substructureOrders[personality%24]
-- read the data in 12 byte chunks, one chunk for each substructure
encryptedData = {}
for i=1,4 do
encryptedData[i] = memory.readbyterange(enemyPokemonPointer+dataStructureOffset+(i-1)*substructureSize,substructureSize)
end
-- decrypt the data
decryptedData = {}
for i=1,4 do
decryptedData[i] = xor(encryptedData[i], encryptionKey)
end
-- organize the data
growth = decryptedData[indexOf(substructureOrder,G)]
attacks = decryptedData[indexOf(substructureOrder,A)]
ev = decryptedData[indexOf(substructureOrder,E)]
misc = decryptedData[indexOf(substructureOrder,M)]
-- set the pokemon species
growth[1] = bit.band(pokemonId, 0xFF)
growth[2] = bit.rshift(pokemonId, 8)
----------------------- NEW CODE --------------------------
-- read the level of the pokemon
level = memory.readbyte(enemyPokemonPointer+levelOffset)
-- read the csv file
levelsToExpCsv = {}
for line in io.lines('levels.csv') do
levelsToExpCsv[#levelsToExpCsv+1] = line
end
levelsToExp = {}
for i=2,#levelsToExpCsv do
levelsToExp[#levelsToExp+1] = ParseCSVLine(levelsToExpCsv[i], ",")
end
-- read the level up type from memory
levelUpType = memory.readbyte(pokemonBaseStatsPointer + (pokemonId-1)*28 + 19)
-- look up how much exp the pokemon should have
exp = levelsToExp[level][levelUpType+2]
-- overwrite the exp.
growth[5] = bit.band(exp, 0xFF)
growth[6] = bit.band(bit.rshift(exp,8),0xFF)
growth[7] = bit.band(bit.rshift(exp,16),0xFF)
growth[8] = bit.band(bit.rshift(exp,24),0xFF)
----------------------- NEW CODE --------------------------
-- reassemble the data
newUnencryptedData = {}
newUnencryptedData[indexOf(substructureOrder,G)] = growth
newUnencryptedData[indexOf(substructureOrder,A)] = attacks
newUnencryptedData[indexOf(substructureOrder,E)] = ev
newUnencryptedData[indexOf(substructureOrder,M)] = misc
-- calculate the checksum
checksum = calculateChecksum(newUnencryptedData)
-- re-encrypt the data
newEncryptedData = {}
for i=1,4 do
newEncryptedData[i] = xor(newUnencryptedData[i], encryptionKey)
end
-- write the new data
for i=1,4 do
for j=1,#newEncryptedData[i] do
memory.writebyte(enemyPokemonPointer + dataStructureOffset + (i-1)*substructureSize + (j-1), newEncryptedData[i][j])
end
end
memory.writeword(enemyPokemonPointer+checksumOffset, checksum)
end
function calculateChecksum(unencryptedData)
sum = 0
for i=1,#unencryptedData do
for j=1,#unencryptedData[i],2 do
sum = sum +
unencryptedData[i][j] +
unencryptedData[i][j+1]*0x100
end
end
return bit.band(sum, 0xFFFF)
end
-- xor a table of bytes with a double word repeatedly
function xor(byteTable, val)
valTable = {
bit.band(val,0xFF),
bit.ror(bit.band(val,bit.rol(0xFF,8)), 8),
bit.ror(bit.band(val,bit.rol(0xFF,16)), 16),
bit.ror(bit.band(val,bit.rol(0xFF,24)), 24)
}
ret = {}
for i=1,#byteTable do
ret[i] = bit.bxor(byteTable[i], bit.bxor(valTable[(i-1)%4+1]))
end
return ret
end
-- get the index of val in table. Return 0 if not found
function indexOf(table, val)
for i=1,#table do
if table[i] == val then
return i
end
end
return 0
end
-- via http://lua-users.org/wiki/LuaCsv
function ParseCSVLine (line,sep)
local res = {}
local pos = 1
sep = sep or ','
while true do
local c = string.sub(line,pos,pos)
if (c == "") then break end
if (c == '"') then
local txt = ""
repeat
local startp,endp = string.find(line,'^%b""',pos)
txt = txt..string.sub(line,startp+1,endp-1)
pos = endp + 1
c = string.sub(line,pos,pos)
if (c == '"') then txt = txt..'"' end
until (c ~= '"')
table.insert(res,txt)
assert(c == sep or c == "")
pos = pos + 1
else
local startp,endp = string.find(line,sep,pos)
if (startp) then
table.insert(res,string.sub(line,pos,startp-1))
pos = endp + 1
else
table.insert(res,string.sub(line,pos))
break
end
end
end
return res
end
print('script started')
memory.registerexec(0x08010672,run) -- address is executed whenever a battle starts