mirror of
https://github.com/notwa/mm
synced 2024-11-05 00:19:02 -08:00
moving to github
This commit is contained in:
commit
234094ce9c
5 changed files with 592 additions and 0 deletions
181
MM addrs.lua
Executable file
181
MM addrs.lua
Executable file
|
@ -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
|
195
MM movement tests.lua
Executable file
195
MM movement tests.lua
Executable file
|
@ -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)
|
64
MM test chests.lua
Executable file
64
MM test chests.lua
Executable file
|
@ -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()
|
50
boilerplate.lua
Executable file
50
boilerplate.lua
Executable file
|
@ -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
|
102
chksum.py
Executable file
102
chksum.py
Executable file
|
@ -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)
|
Loading…
Reference in a new issue