From 234094ce9c06d8effdc6dad16fa19fc68faa2d31 Mon Sep 17 00:00:00 2001 From: Connor Olding Date: Thu, 19 Feb 2015 11:15:45 -0800 Subject: [PATCH] moving to github --- MM addrs.lua | 181 +++++++++++++++++++++++++++++++++++++++ MM movement tests.lua | 195 ++++++++++++++++++++++++++++++++++++++++++ MM test chests.lua | 64 ++++++++++++++ boilerplate.lua | 50 +++++++++++ chksum.py | 102 ++++++++++++++++++++++ 5 files changed, 592 insertions(+) create mode 100755 MM addrs.lua create mode 100755 MM movement tests.lua create mode 100755 MM test chests.lua create mode 100755 boilerplate.lua create mode 100755 chksum.py diff --git a/MM addrs.lua b/MM addrs.lua new file mode 100755 index 0000000..dc710b3 --- /dev/null +++ b/MM addrs.lua @@ -0,0 +1,181 @@ +local A = require "boilerplate" + +local link = 0x1EF670 -- "ZELDA3" - 0x24 +local function AL(a, s) return A(link+a, s) end +local US_10 = { + link = A(link, 0x4000), -- what gets copied to save files (mostly) + area_mod = AL(0x02, 2), + cutscene_status = AL(0x0A, 2), -- TODO: RE + time = AL(0x0C, 2), + time_speed = AL(0x16, 2), + day = AL(0x1B, 1), + transformation = AL(0x20, 1), -- fierce deity, goron, zora, deku, normal + zeroth_day = AL(0x23, 1), -- mayor's warp effect + sot_count = AL(0x2A, 2), + name = AL(0x2C, 8), + max_hearts = AL(0x34, 2), + hearts = AL(0x36, 2), + magic_1 = AL(0x39, 1), -- set to 0x60? + rupees = AL(0x3A, 2), + magic_2 = AL(0x40, 2), -- set to 0x101? + owls_hit = AL(0x46, 2), -- bitfield + sword_shield = AL(0x6D, 1), -- mixed + inventory_items = AL(0x70, 24), + inventory_masks = AL(0x88, 24), + inventory_counts = AL(0xA0, 24), -- number of arrows, bombs, etc. + wallet_flags = AL(0xBA, 1), -- needs testing, 0xEF = max? + quiver_bag = AL(0xBB, 1), -- mixed + status_items = AL(0xBD, 3), -- bitfield + scene_flags_save = AL(0x470, 0x960), + area_map = AL(0xEB2, 1), -- bitfield 0x80 + banked_rupees = AL(0xEDE, 2), -- max 9999 before messed up text + archery = AL(0xF00, 1), -- bitfield 0x01 + chateau_romani = AL(0xF06, 1), -- bitfield 0x08 + disable_c_buttons = AL(0xF4A, 1), -- bitfield 0x08 + sword_disable_c = AL(0xF52, 1), -- bitfield 0x20, TODO: RE + map_visited = AL(0xF5E, 2), -- bitfield, for pause menu map + map_visible = AL(0xF62, 2), -- bitfield, for pause menu map + checksum = AL(0x100A, 2), -- only relevant for save files + disable_pause = AL(0x100D, 1), -- bitfield 0x80 + hookshot_ba = AL(0x100E, 1), -- set to 0x80 for endless day + disable_c_buttons_2 = AL(0x100F, 1), -- bitfield 0x10, also hides hearts/magic + disable_items = AL(0x1010, 1), -- bitfield 0x02, dims B/C buttons + rock_sirloin = AL(0x1014, 1), -- maybe other flags? TODO: RE + sword_disabler = AL(0x1015, 1), -- TODO: RE + bubble_timer = AL(0x1016, 2), + rupee_accumulator = AL(0x1018, 2), + spring_water_timers = AL(0x1020, 0xC0), + spring_water_time_1 = AL(0x1020, 0x20), + spring_water_time_2 = AL(0x1040, 0x20), + spring_water_time_3 = AL(0x1060, 0x20), + spring_water_time_4 = AL(0x1080, 0x20), + spring_water_time_5 = AL(0x10A0, 0x20), + spring_water_time_6 = AL(0x10C0, 0x20), + pictograph_picture = AL(0x10E0, 0x2BC0), + -- first non-pictograph byte: 0x1F3310 (link+0x3CA0) + title_screen_mod = AL(0x3CA8, 4), --[[ + nonzero: the HUD is hidden and you can't pause. + 1: no other effects occur. this is used for the title screen. + 2: it takes you to the file select menu. + 3: certain areas load a different scene setup. + 4: it loads the title screen from the start. + 4+: same effect as three? + --]] + entrance_mod = AL(0x3CAC, 4), -- gets added to area mod, can play cutscenes + timer_crap = AL(0x3DD0, 4), -- TODO: RE + timer_x = AL(0x3EFA, 2), + timer_y = AL(0x3F08, 2), + buttons_enabled = AL(0x3F18, 4), -- C and A button booleans + magic_modifier = AL(0x3F28, 4), -- TODO: RE + magic_max = AL(0x3F2E, 2), + weird_a_graphic = AL(0x3F42, 1), + target_style = AL(0x3F45, 1), -- 0 for switch, 1 for target + music_mod = AL(0x3F46, 2), + entrance_mod_setter = AL(0x3F4A, 2), -- sets entrance mod. -10 = 0 + insta_crash = AL(0x3F4C, 1), -- TODO: RE + transition_mod = AL(0x3F55, 2), -- does it even work? + suns_song_effect = AL(0x3F58, 2), + health_mod = AL(0x3F5A, 2), -- heals you + screen_scale_enable = AL(0x3F60, 1), + screen_scale = AL(0x3F64, 'f'), + scene_flags_ingame = AL(0x3F68, 0x960), + -- last link byte (probably): 0x1F3670 + + inventory = { + b_button = AL(0x4C, 1), + + ocarina = AL(0x70, 1), + bow = AL(0x71, 1), + fire_arrows = AL(0x72, 1), + ice_arrows = AL(0x73, 1), + light_arrows = AL(0x74, 1), + event_1 = AL(0x75, 1), + bombs = AL(0x76, 1), + bombchu = AL(0x77, 1), + deku_stick = AL(0x78, 1), + deku_nut = AL(0x79, 1), + magic_beans = AL(0x7A, 1), + event_2 = AL(0x7B, 1), + powder_keg = AL(0x7C, 1), + pictograph = AL(0x7D, 1), + lens_of_truth = AL(0x7E, 1), + hookshot = AL(0x7F, 1), + fairy_sword = AL(0x80, 1), + event_3 = AL(0x81, 1), + bottle_1 = AL(0x82, 1), + bottle_2 = AL(0x83, 1), + bottle_3 = AL(0x84, 1), + bottle_4 = AL(0x85, 1), + bottle_5 = AL(0x86, 1), + bottle_6 = AL(0x87, 1), + }, + masks = { + postman = AL(0x88, 1), + all_night = AL(0x89, 1), + blast = AL(0x8A, 1), + stone = AL(0x8B, 1), + great_fairy = AL(0x8C, 1), + deku = AL(0x8D, 1), + keaton = AL(0x8E, 1), + bremen = AL(0x8F, 1), + bunny = AL(0x90, 1), + don_gero = AL(0x91, 1), + scents = AL(0x92, 1), + goron = AL(0x93, 1), + romani = AL(0x94, 1), + troupe_leader = AL(0x95, 1), + kafei = AL(0x96, 1), + couples = AL(0x97, 1), + truth = AL(0x98, 1), + zora = AL(0x99, 1), + kamaro = AL(0x9A, 1), + gibdo = AL(0x9B, 1), + garos = AL(0x9C, 1), + captains = AL(0x9D, 1), + giants = AL(0x9E, 1), + fierce_deity = AL(0x9F, 1), + }, + counts = { + arrows = AL(0xA1, 1), + bombs = AL(0xA6, 1), + bombchu = AL(0xA7, 1), + sticks = AL(0xA8, 1), + nuts = AL(0xA9, 1), + beans = AL(0xAA, 1), + kegs = AL(0xAC, 1), + }, + + random = A(0x097530, 4), + visibility = A(0x166118, 2), -- wtf does this even do? + bomb_counter = A(0x1AF10E, 1), -- used for limiting number of bombs active + stored_epona = A(0x1BDA9F, 1), -- takes effect on load (REQUIRES EPONA'S SONG) + stored_song = A(0x1C6A7D, 1), + buttons_3 = A(0x1FB870, 2), -- used for turbo cheat + buttons_4 = A(0x1FB876, 2), -- used for turbo cheat + buttons_1 = A(0x3E6B3A, 1), -- some buttons + buttons_2 = A(0x3E6B3B, 1), -- some more buttons + framerate_limiter = A(0x3E6BC2, 1), -- 1 = 60fps, 2 = 30fps, 3 = 20fps, etc. + bomb_counter_2 = A(0x3E87F7, 1), -- or maybe this, can't remember + text_open = A(0x3FD33B, 1), + text_status = A(0x3FD34A, 1), + room_number = A(0x3FF200, 1), + actor_disable = A(0x3FF366, 2), -- set to -10 and load + warp_begin = A(0x3FF395, 1), -- set to nonzero to begin warping + screen_dim = A(0x3FF397, 1), -- B) + warp_destination = A(0x3FF39A, 2), + link_scale_x = A(0x3FFE08, 2), -- need to confirm this is x + link_scale_y = A(0x3FFE0C, 2), -- need to confirm this is y + link_scale_z = A(0x3FFE10, 2), -- need to confirm this is z + z_vel = A(0x3FFE18, 'f'), + quick_draw = A(0x3FFEF8, 1), -- item in link's hand + linear_vel = A(0x400880, 'f'), + infinite_sword = A(0x40088B, 1), +} + +local hash = gameinfo.getromhash() +local versions = { + ['D6133ACE5AFAA0882CF214CF88DABA39E266C078'] = US_10, +} +local addrs = versions[hash] + +return addrs diff --git a/MM movement tests.lua b/MM movement tests.lua new file mode 100755 index 0000000..adcaf23 --- /dev/null +++ b/MM movement tests.lua @@ -0,0 +1,195 @@ +-- movement speed testing in Majora's Mask +-- by notwa, for Bizhawk 1.9.1, ROM version US 1.0 +-- +-- go to the fairy's fountain in clock town as human link and run this script. + +local length = 60 -- in frames +local print_each = true + +local tests = { + optimal_roll = { + [ 1]={ Y=127}, + [ 2]={Z=true, Y=127, A=true}, + [17]={Z=true, Y=127}, + [18]={Z=true, Y=127, A=true}, + [33]={goto=17}, + }, + + mash_roll = { + [ 1]={ Y=127}, + [ 2]={Z=true, Y=127, A=true}, + [13]={Z=true, Y=127}, + [14]={Z=true, Y=127, A=true}, + [25]={goto=13}, + }, + + sidehop = { + [ 1]={ X=127}, + [ 2]={Z=true}, + [ 8]={Z=true, X=-127, A=true}, + [ 9]={Z=true, X=-127}, + [15]={goto=8}, + }, + + quick_turnaround = { + [1]={Z=true}, + [2]={}, + [5]={ Y=-127}, + [6]={Z=true, Y=-127}, + }, + + backwalk = { + [1]={ Y=-127}, + [2]={Z=true}, + [8]={Z=true, Y=-127}, + }, + + walk = { + [1]={Y=127}, + }, + + inverse_backwalk = { + [1]={Z=true, Y=127}, + [4]={}, + [7]={ Y=-127}, + [8]={Z=true, Y=127}, + }, +} + +local x_ptr = 0x3FFDD4 -- my x and y pointers +local y_ptr = 0x3FFDDC -- might be backwards +local z_ptr = 0x3FFDD8 +local a_ptr = 0x3FFE6E + +local pos = {2400, 375, 20} +local angle = 180/360*65536 + +local fn = 'lua movement test' + +function pythag(x, y) + return math.sqrt(x*x + y*y) +end + +function reset_stick() + joypad.setanalog({["X Axis"]=false, ["Y Axis"]=false}, 1) +end + +function find_displacement() + local x = mainmemory.readfloat(x_ptr, true) + local y = mainmemory.readfloat(y_ptr, true) + return pythag(pos[1] - x, pos[2] - y) +end + +function setup() + client.unpause() + for _=1, 2 do + reset_stick() + mainmemory.write_s16_be(a_ptr, angle) + mainmemory.writefloat(x_ptr, pos[1], true) + mainmemory.writefloat(y_ptr, pos[2], true) + mainmemory.writefloat(z_ptr, pos[3], true) + for i=1, 3*21 do + emu.frameadvance() + joypad.set({A=i % 4 > 0, Z=i > 9 and i <= 12}, 1) + end + end + savestate.save(fn) +end + +function reload() + savestate.load(fn) +end + +function finish() + reset_stick() + client.pause() +end + +function preprocess(inputs) + for f, j in pairs(inputs) do + if type(f) == 'number' then + j['Start'] = j['S'] + j['C Down'] = j['CD'] + j['C Left'] = j['CL'] + j['C Right'] = j['CR'] + j['C Up'] = j['CU'] + j['X Axis'] = j['X'] + j['Y Axis'] = j['Y'] + end + end +end + +function test_inputs(name, inputs, length) + preprocess(inputs) + reload() + local to = length or inputs.length + local frame = 0 + local latest, action + for _=1, to do + frame = frame + 1 + for _=1, 10 do -- limit number of goto's to follow + latest = 0 + action = nil + for f, j in pairs(inputs) do + if type(f) == 'number' and frame >= f and f > latest then + latest = f + action = j + end + end + if action == nil or type(action.goto) ~= 'number' then + break + else + frame = action.goto + end + end + if action ~= nil then + for i=1, 3 do + joypad.setanalog(action, 1) + joypad.set(action, 1) + emu.frameadvance() + end + end + reset_stick() + end + return x +end + +function run_tests(length) + setup() + + local fmt = '%20s: %10.5f' + local spd_fmt = '%20.5f units/frame' + print('# testing') + + if tests['testme'] ~= nil then + local key = 'testme' + local x = test_inputs(key, tests[key], length) + local distance = find_displacement() + print(fmt:format(key, distance)) + print(spd_fmt:format(distance/length)) + else + local furthest = nil + local distance = 0 + for k, v in pairs(tests) do + local x = test_inputs(k, v, length) + local new_distance = find_displacement() + if print_each then + print(fmt:format(k, new_distance)) + end + if new_distance > distance then + furthest = k + distance = new_distance + end + end + if furthest ~= nil then + print() + print(('## and the winner for %i frames is...'):format(length)) + print(fmt:format(furthest, distance)) + print(spd_fmt:format(distance/length)) + end + end + print() + finish() +end + +run_tests(length) diff --git a/MM test chests.lua b/MM test chests.lua new file mode 100755 index 0000000..1ff3115 --- /dev/null +++ b/MM test chests.lua @@ -0,0 +1,64 @@ +-- go to a grotto with a red rupee chest, stand in front of it and run this script +-- US 1.0 of course + +local start = 0x779884 -- the get item table +local ours = 0x779896 -- the chest we're standing in front of +local text = 0x3FCE10 -- ascii text buffer + +function advance() + emu.frameadvance() + emu.frameadvance() + emu.frameadvance() +end + +local fn = 'lua chest test' +savestate.save(fn) +client.unpause() +for off=0, 185*6, 6 do + for i=0, 5 do + local byte = mainmemory.readbyte(start + off + i) + mainmemory.writebyte(ours + i, byte) + end + gui.addmessage(("%02X"):format(mainmemory.readbyte(start + off))) + joypad.set({A=true}, 1) + advance() + joypad.set({A=false}, 1) + local good = false + for i=1, 9*20 do + advance() + if mainmemory.readbyte(text + 0xA) == 0xFF then + local begin = text + 0xC + local bytes = mainmemory.readbyterange(begin, 0x100) + local str = "" + + -- pairs() won't give us the bytes in order + -- so we'll set up a table we can use ipairs() on + local ordered_bytes = {} + for a, v in pairs(bytes) do + ordered_bytes[tonumber(a, 16) - begin + 1] = v + end + + local seq = false + for i, v in ipairs(ordered_bytes) do + local c = tonumber(v, 16) + if c == 9 or c == 10 or c == 13 or (c >= 32 and c < 127) then + str = str..string.char(c) + seq = false + elseif seq == false then + str = str..' ' + seq = true + end + end + + print(off/6 + 1, str) + + good = true + break + end + end + if not good then + print(off/6 + 1, '[error]') + end + savestate.load(fn) +end +client.pause() diff --git a/boilerplate.lua b/boilerplate.lua new file mode 100755 index 0000000..3d446f3 --- /dev/null +++ b/boilerplate.lua @@ -0,0 +1,50 @@ +-- boilerplate convenience functions +-- TODO: respect little endian consoles too + +local mm = mainmemory + +function M1(self, value) + return (value and mm.writebyte or mm.readbyte)(self.addr, value) +end +function M2(self, value) + return (value and mm.write_u16_be or mm.read_u16_be)(self.addr, value) +end +function M3(self, value) + return (value and mm.write_u24_be or mm.read_u24_be)(self.addr, value) +end +function M4(self, value) + return (value and mm.write_u32_be or mm.read_u32_be)(self.addr, value) +end +function MF(self, value) + return (value and mm.writefloat or mm.readfloat)(self.addr, value or true, true) +end + +local Ms = { + [1] = {__call = M1}, + [2] = {__call = M2}, + [3] = {__call = M3}, + [4] = {__call = M4}, + ['f'] = {__call = MF}, +} + +function A(addr, atype) + return setmetatable({addr=addr, type=atype}, Ms[atype]) +end + +--[[ +-- now we can just write: +handle = A(0x123456, 1) +print(handle()) -- get 1 byte at address +handle(0xFF) -- set 1 byte at address + +-- or just: +A(0x123456, 1)(0xFF) -- set address value + +-- and taking advantage of A returning a table and not just a function: +A(handle.addr + 1, handle.type)(0x00) -- set the byte after our address + +-- this doesn't limit us to just the type we initially specified. eg: +A(handle.addr, 2)(0x1234) -- set 2 bytes as opposed to our original 1 +--]] + +return A diff --git a/chksum.py b/chksum.py new file mode 100755 index 0000000..dcfa709 --- /dev/null +++ b/chksum.py @@ -0,0 +1,102 @@ +#!/bin/python +# fixes the checksums in a Majora's Mask US 1.0 savefile for Bizhawk +# note: copies are ignored and overwritten, so don't bother editing them. + +import sys +import struct + +lament = lambda *args, **kwargs: print(*args, file=sys.stderr, **kwargs) +chunkize = lambda a, n: (a[i:i+n] for i in range(0, len(a), n)) + +# seems redundant, but it's a lot less error-prone +pack_u8 = lambda data: struct.pack('>B', data) +pack_u16 = lambda data: struct.pack('>H', data) +unpack_u8 = lambda data: struct.unpack('>B', data)[0] +unpack_u16 = lambda data: struct.unpack('>H', data)[0] + +save_1 = 0x20800 +save_2 = 0x24800 +owl_1 = 0x28800 +owl_2 = 0x30800 +save_1_copy = 0x22800 +save_2_copy = 0x26800 +owl_1_copy = 0x2C800 +owl_2_copy = 0x34800 + +def calc_sum(data): + chksum = 0 + for b in chunkize(data, 1): + chksum += unpack_u8(b) + chksum &= 0xFFFF + return chksum + +def fix_sum(f, addr, owl=False): + sum_pos = 0x100A + f.seek(addr) + data = f.read(0x2000) + chksum = calc_sum(data[:sum_pos]) + + if owl and data != b'\x00'*0x2000: + chksum += 0x24 # don't know why + chksum &= 0xFFFF + + f.seek(addr + sum_pos) + old_chksum = unpack_u16(f.read(2)) + f.seek(addr + sum_pos) + f.write(pack_u16(chksum)) + lament('{:04X} -> {:04X}'.format(old_chksum, chksum)) + +def copy_save(f, addr, addr2): + sum_pos = 0x100A + f.seek(addr) + data = f.read(0x2000) + f.seek(addr2) + f.write(data) + +def delete_save(f, addr): + f.seek(addr) + f.write(b'\x00'*0x2000) + +def swap_endian(f): + f.seek(0) + data = f.read() + f.seek(0) + for s in chunkize(data, 4): + f.write(s[::-1]) + +def run(args): + args = args[1:] + if len(args) == 0: + lament("TODO: convert stdin to stdout") + return 0 + for fn in args: + with open(fn, 'r+b') as f: + # dumb way to determine byte order + endian = 'big' + f.seek(0x10000) + if f.read(4) != b'\x03\x00\x03\x00': + endian = 'little' + + if endian == 'little': + swap_endian(f) + + copy_save(f, save_1, save_1_copy) + copy_save(f, save_2, save_2_copy) + copy_save(f, owl_1, owl_1_copy) + copy_save(f, owl_2, owl_2_copy) + fix_sum(f, save_1) + fix_sum(f, save_2) + fix_sum(f, owl_1, owl=True) + fix_sum(f, owl_2, owl=True) + + if endian == 'little': + swap_endian(f) + return 0 + +if __name__ == '__main__': + ret = 0 + try: + ret = run(sys.argv) + except KeyboardInterrupt: + sys.exit(1) + sys.exit(ret)