This was requested, and apparently there's a
route of it for a TAS, so I'll use this thread to put some findings here.
Known glitches:
*New game stat smuggle
https://www.youtube.com/watch?v=DyDmD-ocSPs
*Getting Ron to the last boss
https://www.youtube.com/watch?v=Dv4hFya5wsE
*Burrow cutscene skip
https://www.youtube.com/watch?v=R40BX0YzPY0
*Nick cutscene skip
https://www.youtube.com/watch?v=wD78ZgszSBs
*McGonagall cutscene skip
https://www.youtube.com/watch?v=Ta_Rjz2cOo8
*Cutscene thing
https://www.youtube.com/watch?v=GjufoyTP8b8
*Pausing before a cutscene still messes things up (opening menus causes softlock)
*If the enemy is faster than you, you can skip their turn by attempting to run, fail, then press "A" on their turn. Turboing allows you to even skip your partners turn.
Addresses:
System Bus:
CD54 - Unsigned 2 byte big endian - Message countdown (0 allows you to press A)
CE4B to 0xCE56 - Binary, Flags for Folio Bruti unlocks
FFD0 - Game state? Similar to the prequel, changing to certain values gives a CHEAT menu.
https://tcrf.net/Harry_Potter_and_the_Chamber_of_Secrets_(Game_Boy_Color)#CHEAT_menus
FFD8 - Unsigned 1 byte, Map ID
WRAM:
0B56 - Unsigned 2 byte - First minigame timer
0E3E - Unsigned 2 byte little? endian - RNG
0C36 - Unsigned 1 byte - Counter to next enemy spawn
06AE - Unsigned 1 byte - Hitsplat digit color (can be either 1's or 10's digit)
0692 - Unsigned 1 byte - Hitsplat digit number corresponding to above
065E - Unsigned 1 byte - Hitsplat digit color (can be either 1's or 10's digit)
063E - Unsigned 1 byte - Hitsplat digit number corresponding to above
05EA - Unsigned 1 byte - Hitsplat digit color (can be either 1's or 10's digit)
0606 - Unsigned 1 byte - Hitsplat digit number corresponding to above
5050 - Hp1 NPC. Also exists in 0572 WRAM, and C572, E572 in System Bus, but changing them don't seem to do anything.
Note: Digit colors can be used for scripts to detect if a crit or miss has landed. Crits are red colored with value of 2, and miss has value of 3. To get the actual number, use either 0BCA, 061A in WRAM instead.
The X/Y addresses seems to be all over the place. A simple search for Y in system bus gave: C2D8, C2DC, C2E6, CABB, CABD, CD74, CDBB, E2D8, E2DC, E2E6, EABB, EABD, ED74, EDBB. Changing any one of them all work for some reason. For partners maybe?
Edit: Thanks for SuperMonkeypotato for providing details on routes, some addresses, glitches, etc.
Download hpcs.luaLanguage: lua
memory.usememorydomain("System Bus")
client.SetGameExtraPadding(0, 0, 220, 90)
local hp2_data = require 'hp2 data'
local Critical = hp2_data.RNG
local Addresses = hp2_data.Addresses
local text
local read8
local read16
local read24
local read32
console.clear()
if vba then
text = gui.text
read8 = memory.readbyteunsigned
read16 = memory.readwordunsigned
read32 = memory.readlongunsigned
else
text = gui.pixelText
memory.usememorydomain("System Bus")
read8 = memory.read_u8
read16 = memory.read_u16_le
read24 = memory.read_u24_le
read32 = memory.read_u32_le
end
-- function text(x, y, message, font, color)
-- local font = (font == nil) and 10 or font
-- drawtext(x, y, message,color,"black",font,nil,nil)
-- -- drawtext(x+1, y+1, message,nil,nil,font,nil,nil)
-- end
function waitframe(frame)
while (frame >= 0) do
emu.frameadvance()
frame = frame-1
end
end
function input_n(input,frame)--input for n amount of frames
while (frame >= 0) do
joypad.set(input)
emu.frameadvance()
frame = frame-1
end
end
function contains(t, element)
for _, value in pairs(t) do
if value == element then
return true
end
end
return false
end
function set_player_value(addr,o)
--Base 0x5064
--next npc +0x19
o = o or {} --Construct a table if we didn't get one
o.id = read8(addr)
o.max_hp = memory.read16(addr + 0x1)
o.current_hp = memory.read16(addr + 0x5)
--... And so on
return o
end
function write_file(state, max_loop)
local cond = false
local frame_start = emu.framecount()
local loop_count = 0
local monster_count = 0
local file_name = "HPCoS NPC"
local line = "NPC1\tNPC2\tNPC3\n"
local formations = {}
--save the uniques
local line2 = ""
--console.clear()
savestate.saveslot(state) --Makes first savestate
file = io.open(file_name..".txt","w")
io.output(file)
io.write(line)
repeat
line = "" --clear out line
local frame_now = emu.framecount()
local frame_dif = 0 --difference between this frame and start frame
local npc = {} --clear npc objects
local temp = "" -- hold string here
if (frame_dif ~= (frame_now - frame_start)) then --Check just in case
savestate.saveslot(state)
end
memory.write_u16_le(rng, loop_count) --Write RNG
waitframe(50) --Wait until stats get loaded
frame_dif = frame_dif + 1
loop_count = loop_count + 1
for i = 0,2 do
npc = set_player_value(Addresses.npc_top_id, npc)
if npc.current_hp > 0 then --ids aren't cleared right
if i > 0 then
temp = temp..", "
end
temp = temp..npc.id
monster_count = monster_count + 1
line = line..npc.id.."\t"
end
end
if contains(formations, temp) == false then
formations[#formations+1] = temp
end
io.write(monster_count.."\t"..line.."\n")
if loop_count % 1000 == 0 then
line2 = line2..os.date():sub(12).."\n"
console.log(os.date():sub(12))
end
monster_count = 0
savestate.loadslot(state)
emu.frameadvance()
cond = (loop_count > max_loop) and true or false
until (cond == true)
io.close(file)
for i = 1, #formations do
line2 = line2..formations[i].."\n"
console.log(formations[i])
end
file = io.open(file_name.." uniques.txt","w")
io.output(file)
io.write(line2)
io.close(file)
client.pause()
end
function bot(save)
local frame_start = emu.framecount()
savestate.saveslot(save)
console.log("Area:"..read8(Addresses.area))
repeat
local frame_now = emu.framecount()
local frame_dif = 0 --difference between this frame and start frame
-- if (frame_dif ~= (frame_now - frame_start)) then --Check just in case
-- savestate.saveslot(save)
-- end
-- joypad.set({A = 1})
-- emu.frameadvance()
waitframe(100)
frame_dif = frame_dif + 1
if read8(0x2A00) == 81 then --item
-- if memory.readbyte(0x042C) == 7 then --recruit
cond = true
end
savestate.loadslot(1)
console.log("Dif"..(frame_now - frame_start))
console.log("Frame now: "..frame_now)
console.log("Frame now: "..frame_now)
emu.frameadvance()
until (cond == true)
console.log("Done!\n"..(emu.framecount()-1).."\t"..(read32(0x0840)-1)) --since I loaded state before stopping
client.pause()
end
function print_crit(rng)
text(160, 0, "RNG\tCRIT\tDrop")
for i = 0,40 do
if Critical[rng+i] == "Yes" then color = "Green" else color = "white" end
text(160, 7 + 7 * (i), (rng+i)%65535 .."\t"..Critical[(rng+i)%65535],nil,color)
end
end
local offsets = {
0x010A,
0x000B,
0x000E,
0x0011,
0x0014,
0x0017,
0x001A,
0x001D,
0x0020,
0x0023,
0x0026,
0x0029,
0x002C,
0x002F,
0x0032,
0x0035,
0x0038,
0x003B,
0x003E,
0x0041,
0x0044,
0x0047,
0x004A,
0x004D,
0x0050,
0x0053,
0x0056,
0x0059,
0x005C,
0x005F,
0x0062,
0x0065,
0x0068,
0x006B,
0x006E,
0x0071,
0x0074,
0x0077,
0x007A,
0x007D,
0x0080,
0x0083,
0x0086,
0x0089,
0x008C,
0x008F,
0x0092,
0x0095,
0x0098,
0x009B,
0x009E,
0x00A1,
0x00A4,
0x00A7,
0x00AA,
0x00AD,
0x00B0,
0x00B3,
0x00B6,
0x00B9,
0x00BC,
0x00BF,
0x00C2,
0x00C5,
0x00C8,
0x00CB,
0x00CE,
0x00D1,
0x00D4,
0x00D7,
0x00DA,
0x00DD,
0x00E0,
0x00E3,
0x00E6,
0x00E9,
0x00EC,
0x00EF,
0x00F2,
0x00F5,
0x00F8,
0x00FB,
0x00FE,
0x0101,
0x0104,
0x0107
}
function display_script_as_hex()
--displays the memory values, in terms of hex edit view, for the dorm select bug
local start = 0xCB10
local l_end = 0xCC1F
local text_x = 160
local text_y = 0
local amount = 16
text(text_x+20, text_y,"00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F")
text_y = text_y + 8
for i = 0, amount do
-- text(text_x, text_y, string.format("%02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X", read8(start + 0 + (16 * i)), read8(start + 1 + (16 * i)), read8(start + 2 + (16 * i)), read8(start + 3 + (16 * i)), read8(start + 4 + (16 * i)), read8(start + 5 + (16 * i)), read8(start + 6 + (16 * i)), read8(start + 7 + (16 * i)), read8(start + 8 + (16 * i)), read8(start + 9 + (16 * i)), read8(start + 10 + (16 * i)), read8(start + 11 + (16 * i)), read8(start + 12 + (16 * i)), read8(start + 13 + (16 * i)), read8(start + 14 + (16 * i)), read8(start + 15 + (16 * i)) ))
text(text_x, text_y, string.format("%04X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X", start + (16 * i), read8(start + 0 + (16 * i)), read8(start + 1 + (16 * i)), read8(start + 2 + (16 * i)), read8(start + 3 + (16 * i)), read8(start + 4 + (16 * i)), read8(start + 5 + (16 * i)), read8(start + 6 + (16 * i)), read8(start + 7 + (16 * i)), read8(start + 8 + (16 * i)), read8(start + 9 + (16 * i)), read8(start + 10 + (16 * i)), read8(start + 11 + (16 * i)), read8(start + 12 + (16 * i)), read8(start + 13 + (16 * i)), read8(start + 14 + (16 * i)), read8(start + 15 + (16 * i)) ))
text_y = text_y + 8
end
end
function display_script_as_hex2(offset, color)
--offset is a number + start address that i should color
local start = 0xCB10
local end_2 = 0xCC1F
local target = start + offset
local text_x = 160+20
local text_y = math.floor(offset/16)*8+8
local amount = 16
text_x = text_x + (target & 0xF) * 12 --seems every byte is 12 pixels apart in display
text(text_x, text_y, string.format("%02X", read8(target)),nil, color)
end
local npc_offset = 0x19
function display_battle()
--[[
Game states for battle:
25 using cards
26 choose action (spell, thing, item, run, folio bruti)
27 choose spell
28 choose spell version
29 choose item
30 using an item
31 run
32 pack rat?
33 npc attacking
34 select npc + attack
35 win screen
36 select team mate
39 scabbers attack
47 card attack
]]--
local line = ""
local start_adr = Addresses.npc_top_id
local offset = 10
for i = 0,2 do
local id = read8(start_adr+(npc_offset*i),"WRAM")
local max_hp = memory.read_s16_le(start_adr+(npc_offset*i)+1,"WRAM")
local current_hp = memory.read_s16_be(start_adr+(npc_offset*i)+4,"WRAM")
line = "E"..(i+1).." ".." ID:"..id.." HP:"..current_hp.."/"..max_hp
text(0,offset,line)
offset = offset + 10
end
end
-- write_file(2, 2)
while true do
local x = string.format('%.6f',read24(Addresses.x)/4096.0)
local y = string.format('%.6f',read24(Addresses.y)/4096.0)
local text_y = 138
local text_x = 290
local game_state = read8(Addresses.game_state)
local rng = read16(Addresses.rng)
local area = read8(Addresses.area)
local story = read8(Addresses.story,"WRAM")
local sickles = read16(Addresses.sickles)
local deck = read8(Addresses.deck)
local current_script = read8(0xCB2C)
local current_offset = 0
--They're super long, but just in case i get an item replaced
local items_hash = memory.hash_region(Addresses.item_start,Addresses.item_end - Addresses.item_start,"WRAM")
local cards_hash = memory.hash_region(Addresses.card_start,Addresses.card_end - Addresses.card_start,"WRAM")
local combos_hash = memory.hash_region(Addresses.card_combo_start,Addresses.card_combo_end - Addresses.card_combo_start,"WRAM")
local spell_hash = memory.hash_region(Addresses.spell_start,Addresses.spell_end - Addresses.spell_start)
local bruti_hash = memory.hash_region(Addresses.folio_bruti_start,Addresses.folio_bruti_end - Addresses.folio_bruti_start)
local harry_hash = memory.hash_region(Addresses.harry_max_hp, 0x65)
--checksum to see if anything modified, without giving me giant string
local items = memory.read_bytes_as_array(Addresses.item_start,Addresses.item_end - Addresses.item_start,"WRAM")
local cards = memory.read_bytes_as_array(Addresses.card_start,Addresses.card_end - Addresses.card_start,"WRAM")
local combos = memory.read_bytes_as_array(Addresses.card_combo_start,Addresses.card_combo_end - Addresses.card_combo_start,"WRAM")
local spell = memory.read_bytes_as_array(Addresses.spell_start,Addresses.spell_end - Addresses.spell_start)
local bruti = memory.read_bytes_as_array(Addresses.folio_bruti_start,Addresses.folio_bruti_end - Addresses.folio_bruti_start)
local cards_sum = 0
local items_sum = 0
local combos_sum = 0
local spell_sum = 0
local bruti_sum = 0
if current_script == 0 then
current_offset = 0x010A
else
current_offset = current_script + 0xCB1A - 0xCB10
end
for i = 1, #items do
items_sum = items_sum + items[i]
end
for i = 1, #cards do
cards_sum = cards_sum + cards[i]
end
for i = 1, #combos do
combos_sum = combos_sum + combos[i]
end
for i = 1, #spell do
spell_sum = spell_sum + spell[i]
end
for i = 1, #bruti do
bruti_sum = bruti_sum + bruti[i]
end
-- text(0,50,"State "..game_state)
text(0,text_y,read8(Addresses.minute)..":"..read8(Addresses.second)..":"..read8(Addresses.frame))
text_y = text_y + 8
text(0,text_y,"RNG"..rng .. " State "..game_state .. " CB2C: " .. read8(0xCB2C))
text_y = text_y + 8
text(0,text_y,"X"..x.."Y"..y)
text_y = text_y + 8
text(0,text_y,"MSG"..memory.read_s16_be(Addresses.message_countdown))
text(text_x, text_y, "Harry")
text_y = text_y + 8
text(0,text_y,string.format("Area: %d Story: %d Deck: %d Sickles: %d",area, story, deck, sickles))
text(text_x, text_y, string.format("LV: %d XP: %d",read8(Addresses.harry_level), read16(Addresses.harry_xp)))
text_y = text_y + 8
text(0,text_y,string.format('Items: %d Cards: %d Combos: %d Spell: %d Bruti: %d',items_sum, cards_sum, combos_sum, spell_sum, bruti_sum))
text(text_x, text_y, string.format("HP: %d/%d MP: %d/%d",read16(Addresses.harry_hp), read16(Addresses.harry_max_hp), read16(Addresses.harry_mp), read16(Addresses.harry_max_mp)))
text_y = text_y + 8
text(0, text_y, "Items: " .. items_hash)
text(text_x, text_y, string.format("STR: %d DF: %d AG: %d",read8(Addresses.harry_str), read8(Addresses.harry_def), read8(Addresses.harry_agi)))
text_y = text_y + 8
text(0, text_y, "Cards: " .. cards_hash)
text(text_x, text_y, string.format("MSTR: %d MDF: %d",read8(Addresses.harry_magic_str), read8(Addresses.harry_magic_def)))
text_y = text_y + 8
text(0, text_y, "Combos: " .. combos_hash)
text_y = text_y + 8
text(0, text_y, "Spells: " .. spell_hash)
text_y = text_y + 8
text(0, text_y, "Bruti: " .. bruti_hash)
text_y = text_y + 8
text(0, text_y, "Stats: " .. harry_hash)
-- print_crit(rng)
display_script_as_hex()
for i = 1, #offsets do
display_script_as_hex2(offsets[i],"red")
end
display_script_as_hex2(current_offset,"blue")
display_script_as_hex2(current_offset+1,"blue")
display_script_as_hex2(current_offset+2,"blue")
if game_state > 24 and game_state < 48 then
display_battle()
else
end
emu.frameadvance()
end