-- V2.0:
-- By Warepire, Spikestuff, Fog, Tremane
-- Slightly Less Shitty Script
-- Prints Zook position, charge shot timer, weapons cooldown timer, invulnerable timer
-- Enemy position (different coordinate system because ... fuck you!?), HP, action timer and active flag, invulnerable timer
-- Active item drop, and next five item drops
-- imported: hitbox feature
--
-- Currently no speed value, because there seems to be only 2 speeds: Standing still and moving.
-- If speed is discovered to be more complex, speed value can be added.
camera_x = 0
camera_y = 0
local_x = 0
local_y = 0
-- Level layer positions addresses in IWRAM:
zook_ukn_layer_x = 0x14F0
zook_ukn_layer_y = 0x14F4
zook_fg_layer_x = 0x14F8
zook_fg_layer_y = 0x14FC
zook_lvl_layer_x = 0x1500
zook_lvl_layer_y = 0x1504
zook_bg_layer_x = 0x1508
zook_bg_layer_y = 0x150C
function UpdateCamera()
-- using lvl layer as "camera":
memory.usememorydomain("IWRAM")
camera_x = memory.read_u32_le(zook_lvl_layer_x)
camera_y = memory.read_u32_le(zook_lvl_layer_y)
end
function PrintZookData()
-- 0x15E0: Cooldown timer, can only shoot when 0.
local zook_x_addr = 0x1560
local zook_y_addr = 0x1564
local zook_wpn_cooldown_timer_addr = 0x15E0
local zook_charge_timer_addr = 0x1608
local zook_invuln_timer_addr = 0x1628
-- These are some odd values, related to movement, goes from 0-31, wraps at 32
-- local zook_subx_addr = 0x1568
-- local zook_suby_addr = 0x156C
memory.usememorydomain("IWRAM")
local zook_x = memory.read_u32_le(zook_x_addr)
local zook_y = memory.read_u32_le(zook_y_addr)
local zook_wpn_cooldown_timer = memory.read_u32_le(zook_wpn_cooldown_timer_addr)
local zook_charge_timer = memory.read_u16_le(zook_charge_timer_addr)
local zook_invuln_timer = memory.read_u32_le(zook_invuln_timer_addr)
-- the charge timer is special
local zook_charge_timer_base = 60 - zook_charge_timer
local zook_charge_timer_extra = 80
if zook_charge_timer_base <= 0 then
zook_charge_timer_extra = zook_charge_timer_extra + zook_charge_timer_base
zook_charge_timer_base = 0
end
--gui.drawText(local_x+10, local_y+30, string.format("X=%d,Y=%d\nCharge Shot=%d,Extra=%d\nBuster Cooldown=%d\nInvulnerable timer=%d", zook_x, zook_y, zook_charge_timer_base, zook_charge_timer_extra, zook_wpn_cooldown_timer, zook_invuln_timer), "red", null, 10)
local x, y, dy = 8, 56, 14
local function print_info(format, ...)
gui.text(x, y, string.format(format, ...),"aqua")
y = y + dy
end
print_info("X=%d, Y=%d", zook_x, zook_y)
print_info("Charge Shot=%d, Extra=%d", zook_charge_timer_base, zook_charge_timer_extra)
print_info("Buster Cooldown=%d", zook_wpn_cooldown_timer)
print_info("Invulnerable timer=%d", zook_invuln_timer)
end
initial_boss_bar_needed_hits = 0
function PrintBossData()
memory.usememorydomain("IWRAM")
local boss_can_be_damaged = memory.read_u32_le(0x16F4)
local boss_invuln_timer = memory.read_u32_le(0x16F8)
local boss_hp_bar_count = memory.read_u32_le(0x1708)
local boss_hp_bar_needed_hits = memory.read_s32_le(0x170C)
if boss_hp_bar_needed_hits < 0 then
boss_hp_bar_needed_hits = 0 - boss_hp_bar_needed_hits
end
if initial_boss_bar_needed_hits == 0 then
initial_boss_bar_needed_hits = boss_hp_bar_needed_hits
end
local boss_damage_flag = " "
if boss_can_be_damaged == 6 then -- so far known: 6 = yes, 7 = no
boss_damage_flag = "*"
end
if boss_hp_bar_count == 0 then
initial_boss_bar_needed_hits = 0
return
end
--print("Boss HP Bar Count: " .. boss_hp_bar_count)
--print("Initial Boss Bar Needed Hits: " .. initial_boss_bar_needed_hits)
--print("Boss HP Bar Needed Hits:" .. boss_hp_bar_needed_hits)
local boss_hp = ((boss_hp_bar_count - 1) * initial_boss_bar_needed_hits) + boss_hp_bar_needed_hits
--gui.drawText(10, 75, string.format("Boss HP=%d %s\nInvulnerable timer=%d", boss_hp, boss_damage_flag, boss_invuln_timer), "red", null, 10)
local x, y, dy = 8, 112, 14
local function print_info(format, ...)
gui.text(x, y, string.format(format, ...),"red")
y = y + dy
end
print_info("Boss HP=%d %s", boss_hp, boss_damage_flag)
print_info("Invulnerable timer=%d", boss_invuln_timer)
end
function PrintItemDrop()
local active_frame_count = memory.read_u32_le(0x17A0, "IWRAM")
local item_drop_table = memory.readbyterange(0x3E8880, 132, "ROM") -- 33 entries of four bytes each
local active_item_drop = item_drop_table[bit.band(active_frame_count, 0x1F) * 4] -- multiplied by four to read the four byte drop table
local active_item_drop_strings = {"Nothing", "Small Health", "Medium Health", "Large Health", "Small Energy", "Medium Energy", "Large Energy", "Extra Life"}
--gui.drawText(10, 100, string.format("Active Item Drop:\n%s", active_item_drop_strings[active_item_drop + 1]), "red", null, 10)
local x, y, dy = 320, 14, 14
local function print_info(format, ...)
gui.text(x, y, string.format(format, ...),"lightgreen")
y = y + dy
end
print_info("AID: %s", active_item_drop_strings[active_item_drop + 1]) --Active Item Drop
print_info("Next Five Drops:", 0)
for i=1,5 do
local next_item_drop = item_drop_table[bit.band(active_frame_count + i, 0x1F) * 4] -- multiplied by four to read the four byte drop table
print_info("%s", active_item_drop_strings[next_item_drop + 1])
end
end
-- FOV/Hitbox Work
-- -----------------------------------
-- Globals
-- -----------------------------------
local zook_entity_struct_data
local current_entity_struct_data
-- ---------------------------------
-- Constants
-- ---------------------------------
local max_entries = 60
local enemies_index_begin = 30 -- enemies start at position 30, first 6 reserved for Zook?
local entity_struct_addr = 0x1DE4
local entity_struct_size = 0x74
local entity_x_offset = 0x0
local entity_y_offset = 0x4
local entity_sprite_index_offset = 0x8
local entity_sprite_map_offset = 0xA
local entity_direction_offset = 0xC
local entity_type_offset = 0x14
local entity_action_state_offset = 0x16
local entity_action_timer_offset = 0x18
local entity_hp_offset = 0x2C
local entity_field_0x3C_offset = 0x3C
local entity_invuln_timer_offset = 0x58
-- maps enemy type(?) to box color, if a color sucks, change it!
-- when a new enemy is identified to map to one of these boxes, update the enemy name in the list!
local color_map = {}
color_map[0x0F] = "AliceBlue" -- red tank (intro)
color_map[0x17] = "AntiqueWhite" -- blue tank (intro)
color_map[0x1E] = "Aqua" -- cone eneny (intro)
color_map[0x1B] = "Aquamarine" -- speedy spikey thing (intro)
color_map[0x25] = "Azure" -- no clue
color_map[0x4B] = "Beige" -- no clue
color_map[0x56] = "Bisque" -- no clue
color_map[0x58] = "BlanchedAlmond" -- no clue
color_map[0x5B] = "Blue" -- no clue
color_map[0x5E] = "BlueViolet" -- no clue
color_map[0x62] = "Brown" -- no clue
color_map[0x71] = "BurlyWood" -- no clue
color_map[0x72] = "CadetBlue" -- no clue
color_map[0x74] = "Chartreuse" -- no clue
color_map[0x87] = "Chocolate" -- no clue
color_map[0x8A] = "Coral" -- no clue
color_map[0x8B] = "CornflowerBlue" -- no clue
color_map[0x8C] = "Cornsilk" -- no clue
color_map[0x8D] = "Crimson" -- no clue
color_map[0xA2] = "Cyan" -- no clue
color_map[0xA4] = "DarkBlue" -- no clue
color_map[0xAB] = "DarkCyan" -- no clue
color_map[0xAF] = "DarkGoldenrod" -- no clue
color_map[0xBA] = "DarkGray" -- no clue ... like really... no clue
color_map[0xC0] = "DarkGreen" -- no clue
color_map[0xC1] = "DarkKhaki" -- no clue
color_map[0xC2] = "DarkMagenta" -- no clue
color_map[0xC3] = "DarkOliveGreen" -- no clue
color_map[0xC8] = "DarkOrange" -- no clue ... like really... no clue
color_map[0xD0] = "DarkOrchid" -- no clue
color_map[0xD5] = "DarkViolet" -- no clue
color_map[0xDE] = "DarkSalmon" -- no clue
color_map[0xDF] = "DarkSeaGreen" -- no clue
color_map[0xE2] = "DarkSlateBlue" -- no clue
color_map[0xE3] = "DarkSlateGray" -- no clue
color_map[0xE6] = "DarkTurquoise" -- no clue
-- ----------------------------------
-- Utility functions
-- ----------------------------------
local function GetWordFromMemTable(table, offset)
local low_byte = table[offset]
local high_byte = table[offset+1]
local word = low_byte + (high_byte * 0x100)
return word
end
local function GetDwordFromMemTable(table, offset)
local low_word = GetWordFromMemTable(table, offset)
local high_word = GetWordFromMemTable(table, offset + 2)
local dword = low_word + (high_word * 0x10000)
return dword
end
-- exactly how the game computes it, every time. I know IDA says differently sometimes in the pseudocode view.
function GetOffsetFromIndex(index)
return 4 * (29 * index)
end
function read_u32_le_SystemBus(addr)
if addr >= 0x8000000 then
return memory.read_u32_le(addr - 0x8000000, "ROM")
else -- addr >= 0x3000000 then
return memory.read_u32_le(addr - 0x3000000, "IWRAM")
end
end
-- args: color_id, left, right, top, bottom
function DrawFieldOfViewBox(args)
assert(args.color_id, "BUG! color_id == nil")
local x_left = 16 + (args.left or 0)
local x_right = 16 + (args.right or (239 - 16))
local y_top = (args.top or 0)
local y_bottom = (args.bottom or 159)
gui.drawBox(x_left, y_top, x_right, y_bottom, color_map[args.color_id])
end
function DrawHitBox(x1, x2, y1, y2)
gui.drawBox(x1, y1, x2, y2, "red")
end
-- -----------------------------------
-- Draw logic
-- -----------------------------------
function DrawEntityData()
local enemy_x = GetDwordFromMemTable(current_entity_struct_data, entity_x_offset)
local enemy_y = GetDwordFromMemTable(current_entity_struct_data, entity_y_offset)
local enemy_type = GetWordFromMemTable(current_entity_struct_data, entity_type_offset)
local enemy_active_flag = GetWordFromMemTable(current_entity_struct_data, entity_action_state_offset)
local enemy_action_timer = GetWordFromMemTable(current_entity_struct_data, entity_action_timer_offset)
local enemy_hp = GetWordFromMemTable(current_entity_struct_data, entity_hp_offset)
local enemy_invuln_timer = GetDwordFromMemTable(current_entity_struct_data, entity_invuln_timer_offset)
local active = " "
if enemy_active_flag ~= 0 then active = "*" end
local x, y, dy = enemy_x + 16, enemy_y, 7
local function print_info(format, ...)
gui.pixelText(x + 0, y, string.format(format, ...),"red")
y = y + dy
end
if enemy_x ~= 0 and enemy_y ~= 0 then
print_info("X=%d, Y=%d", enemy_x, enemy_y)
if enemy_hp ~= 0 and enemy_hp ~= 0xFFFF then
print_info("HP=%d", enemy_hp)
print_info("%sA.T.=%d", active, enemy_action_timer)
print_info("I.T=%d", enemy_invuln_timer)
end
print_info("Type=0x%02X", enemy_type)
end
end
-- Amazingly enough all the field of view tests are conducted versus the position of Zooks left foot big toe.
-- Note: Some boxes may be inversed, if they are, swap the left/right values!
function FieldOfViewBox()
-- Function pointers whose detection logic are implemented below, start at 0x1818 in IWRAM and seem to go on for about 256 entries
local entity_type = GetWordFromMemTable(current_entity_struct_data, entity_type_offset)
local x = GetDwordFromMemTable(current_entity_struct_data, entity_x_offset)
local y = GetDwordFromMemTable(current_entity_struct_data, entity_y_offset)
if entity_type == 0xF or entity_type == 0x17 or entity_type == 0x1E then -- IDA functions: sub_8019208, sub_8018BA8, sub_80185C0
DrawFieldOfViewBox{color_id=entity_type, left=x-48, right=x+48}
elseif entity_type == 0x1B then -- IDA functions: sub_8018884
DrawFieldOfViewBox{color_id=entity_type, left=x-96, right=x+48}
elseif entity_type == 0x25 then -- IDA functions: sub_80173CC
DrawFieldOfViewBox{color_id=entity_type, left=x-56, right=x+64}
DrawFieldOfViewBox{color_id=entity_type, left=x-10, right=x+10, top=y+10, bottom=x-10} -- some sort of inner attack box...?
elseif entity_type == 0x4B then -- IDA functions: sub_801431C
DrawFieldOfViewBox{color_id=entity_type, left=48, right=144}
elseif entity_type == 0x56 then -- IDA functions: sub_8013374
DrawFieldOfViewBox{color_id=entity_type, left=x-64, right=x+32}
elseif entity_type == 0x58 then -- IDA functions: sub_80130D8
DrawFieldOfViewBox{color_id=entity_type, left=x-80, right=x+32}
elseif entity_type == 0x5B or entity_type == 0x62 then -- IDA functions: sub_8012FE8, sub_8012AAC
DrawFieldOfViewBox{color_id=entity_type, left=x-80, right=x+80}
elseif entity_type == 0x5E or entity_type == 0xC3 then -- IDA functions: sub_8012C80, sub_8009FD4
local dir = GetWordFromMemTable(current_entity_struct_data, entity_direction_offset)
local left = 0
if dir == 1 then
left = 32
end
DrawFieldOfViewBox{color_id=entity_type, left=x-left, right=x+112}
elseif entity_type == 0x71 or entity_type == 0x72 then -- IDA functions: sub_8011708 (0x8011924 is a "subfunction" of that)
local action_state = GetWordFromMemTable(current_entity_struct_data, entity_action_state_offset)
if entity_type == 0x71 and action_state == 1 then
DrawFieldOfViewBox{color_id=entity_type, left=x-112, right=x+96}
else
DrawFieldOfViewBox{color_id=entity_type, left=x-80, right=x+64}
end
elseif entity_type == 0x74 then -- IDA functions: sub_8011278
DrawFieldOfViewBox{color_id=entity_type, left=x-48, right=x+48}
DrawFieldOfViewBox{color_id=entity_type, left=x-20, right=x-48}
elseif entity_type == 0x87 then -- IDA functions: sub_800F920
DrawFieldOfViewBox{color_id=entity_type, left=x-88, right=x+48}
elseif entity_type == 0x8A then -- IDA functions: sub_800F4D4
DrawFieldOfViewBox{color_id=entity_type, left=x-80, right=x+48}
elseif entity_type == 0x8B then -- IDA functions: sub_800F394
DrawFieldOfViewBox{color_id=entity_type, left=x-24, right=x+8}
elseif entity_type == 0x8C then -- IDA functions: sub_800F298
DrawFieldOfViewBox{color_id=entity_type, left=x-32, right=x+48}
elseif entity_type == 0x8D or entity_type == 0xAF then -- IDA functions: sub_800F130, sub_800BAE8
DrawFieldOfViewBox{color_id=entity_type, left=x-64, right=x+48}
elseif entity_type == 0xA2 then -- IDA functions: sub_800D514
DrawFieldOfViewBox{color_id=entity_type, top=y-16, bottom=y-32}
elseif entity_type == 0xA4 then -- IDA functions: sub_800D234
DrawFieldOfViewBox{color_id=entity_type, left=x-96, right=x+64, top=y+96, bottom=y-64}
elseif entity_type == 0xAB then -- IDA functions: sub_800C114
DrawFieldOfViewBox{color_id=entity_type, top=y-16, bottom=y-40}
elseif entity_type == 0xBA then -- IDA functions: sub_8017F18
print("ALERT: state 0xBA found!") -- What does this entity do? It does some math if Zook is in certain parts of the screen.
x = memory.read_s32_le(entity_struct_addr + GetOffsetFromIndex(1) + entity_x_offset)
DrawFieldOfViewBox{color_id=entity_type, left=x+90, right=x+200}
DrawFieldOfViewBox{color_id=entity_type, left=x+40, right=x+80}
elseif entity_type == 0xC0 then -- IDA functions: sub_800A380
DrawFieldOfViewBox{color_id=entity_type, left=x-16, right=x+16}
elseif entity_type == 0xC1 or entity_type == 0xD0 then -- IDA functions: sub_800A27C, sub_8008A18
DrawFieldOfViewBox{color_id=entity_type, left=x-32, right=x+16}
elseif entity_type == 0xC2 then -- IDA functions: sub_800A128
DrawFieldOfViewBox{color_id=entity_type, left=x-72, right=x+48}
elseif entity_type == 0xC8 then -- IDA functions: sub_8008D28
print("ALERT: state 0xC8 found!") -- What does this entity do? It does some math depending on which zone of the screen Zook's in.
y = memory.read_s32_le(entity_struct_addr + GetOffsetFromIndex(1) + entity_y_offset)
DrawFieldOfViewBox{color_id=entity_type, bottom=40}
DrawFieldOfViewBox{color_id=entity_type, top=40, bottom=72}
DrawFieldOfViewBox{color_id=entity_type, top=72}
elseif entity_type == 0xD5 then -- IDA functions: sub_8008654
DrawFieldOfViewBox{color_id=entity_type, left=x-80, right=x+32}
elseif entity_type == 0xDE then -- IDA functions: sub_800728C
print("ALERT: state 0xDE found!")
DrawFieldOfViewBox{color_id=entity_type, bottom=y-32}
elseif entity_type == 0xDF then -- IDA functions: sub_800703C
-- What does this entity do? It does some math if Zook is in certain parts of the screen.
DrawFieldOfViewBox{color_id=entity_type, left=48, right=96}
DrawFieldOfViewBox{color_id=entity_type, left=96, right=144}
elseif entity_type == 0xE2 then -- IDA functions: sub_8006C6C
DrawFieldOfViewBox{color_id=entity_type, left=x-58, right=x+88}
elseif entity_type == 0xE3 then -- IDA functions: sub_8006A78
DrawFieldOfViewBox{color_id=entity_type, left=x-64, right=192}
DrawFieldOfViewBox{color_id=entity_type, left=8, right=x-16}
DrawFieldOfViewBox{color_id=entity_type, top=y+8, bottom=y-24}
elseif entity_type == 0xE6 then -- IDA functions: sub_8006248
DrawFieldOfViewBox{color_id=entity_type, top=72, bottom=40}
end
end
function DrawHitbox() -- sub_801C7A0 & sub_801C72C
local field_3C = GetDwordFromMemTable(current_entity_struct_data, entity_field_0x3C_offset)
local sprite_map = GetWordFromMemTable(current_entity_struct_data, entity_sprite_map_offset)
local sprite_index = GetWordFromMemTable(current_entity_struct_data, entity_sprite_index_offset)
local x = GetDwordFromMemTable(current_entity_struct_data, entity_x_offset)
local y = GetDwordFromMemTable(current_entity_struct_data, entity_y_offset)
local addr
if field_3C == 2 then -- sub_801C7A0
addr = read_u32_le_SystemBus(0x806885C + (sprite_map * 4))
else -- sub_801C72C
-- table is defined as: DWORD* array[][17]
-- however, hitboxes can be read just fine without accessing the array
addr = read_u32_le_SystemBus(0x83CD5C4 + (memory.read_u32_le(0x152C, "IWRAM") * 4))
addr = read_u32_le_SystemBus(addr + (sprite_map * 4))
end
addr = addr + (16 * sprite_index)
local x1 = x + read_u32_le_SystemBus(addr + 8)
local x2 = x1 + read_u32_le_SystemBus(addr + 12)
local y1 = y + read_u32_le_SystemBus(addr)
local y2 = y1 + read_u32_le_SystemBus(addr + 4)
DrawHitBox(x1, x2, y1, y2)
end
function DrawZookData()
zook_entity_struct_data = memory.readbyterange(entity_struct_addr + GetOffsetFromIndex(1), entity_struct_size, "IWRAM")
local sprite_map = GetWordFromMemTable(zook_entity_struct_data, entity_sprite_map_offset)
local sprite_index = GetWordFromMemTable(zook_entity_struct_data, entity_sprite_index_offset)
local x = GetDwordFromMemTable(zook_entity_struct_data, entity_x_offset)
local y = GetDwordFromMemTable(zook_entity_struct_data, entity_y_offset)
local addr = read_u32_le_SystemBus(0x8087F34 + (16 * sprite_map))
addr = addr + (16 * sprite_index)
local x1 = x + read_u32_le_SystemBus(addr + 8)
local x2 = x1 + read_u32_le_SystemBus(addr + 12)
local y1 = y + read_u32_le_SystemBus(addr)
local y2 = y1 + read_u32_le_SystemBus(addr + 4)
DrawHitBox(x1, x2, y1, y2)
-- Zook's x position can be invalid if spawned off screen, so force it to zero to avoid issues
if x < 0 or x > 240 then
x = 0
end
gui.drawLine(x + 16, 0, x + 16, 159, "orange") -- Test pixel if zook is in field of view. Since height is not used, ignore it and draw a line.
end
function DrawEnemyStuff()
-- do a framecount check to avoid crashing the script
if emu.framecount() >= 270 then
for i=enemies_index_begin,(max_entries - 1) do
current_entity_struct_data = memory.readbyterange(entity_struct_addr + GetOffsetFromIndex(i), entity_struct_size, "IWRAM")
DrawEntityData()
FieldOfViewBox()
DrawHitbox()
end
end
end
----------------------------------------------------------
-- Main:
----------------------------------------------------------
function main()
client.SetClientExtraPadding(12, 58, 12, 12)
while true do
UpdateCamera()
DrawZookData()
DrawEnemyStuff()
PrintZookData()
PrintBossData()
PrintItemDrop()
emu.frameadvance()
end
end
main()