-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathautotracker.lua
323 lines (285 loc) · 11.7 KB
/
autotracker.lua
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
function print_inventory_state()
print("inventory state discrepancy detected; printing updated state")
for name, mem in pairs(INVENTORY_MEM_BLOCKS) do
print(string.format("%s\t%0" .. mem["size"] .. "x", name, inventory_state[name]))
end
end
SCENE_ADDR = 0x1C8544
function resync()
resync_checks_state()
resync_inventory_state()
end
function resync_inventory_state()
inventory_state_discrepancy = resync_running_state_with_mem(scene_and_global_flags, CHECKS_MEM_BLOCKS)
if inventory_state_discrepancy then
print_inventory_state()
end
end
function resync_checks_state()
resync_running_state_with_mem(inventory_state, INVENTORY_MEM_BLOCKS)
end
function resync_running_state_with_mem(running_state, mem_blocks)
local discrepancy_exists = false
for name, mem in pairs(mem_blocks) do
local addr = mem["addr"]
local size = mem["size"]
local fixed_size_read_fn = SIZE_TO_READ_FN[size]
local updated_val
if fixed_size_read_fn ~= nil then
updated_val = fixed_size_read_fn(addr)
else
updated_val = read_range(size)(addr)
end
if running_state[name] ~= updated_val then
discrepancy_exists = true
running_state[name] = updated_val
end
end
return discrepancy_exists
end
function execute_next_frame(fn, name)
return function()
function execute_then_unregister()
fn()
event.unregisterbyname(name)
end
event.onframeend(execute_then_unregister, name)
end
end
function resync_next_frame()
return execute_next_frame(resync, "resync")
end
function read_mem_and_handle(addr, size, handler, name)
return function()
fixed_size_read_fn = SIZE_TO_READ_FN[size]
if fixed_size_read_fn ~= nil then
handler(fixed_size_read_fn(addr), name)
else
handler(read_range(size)(addr), name)
end
end
end
function read_mem_and_handle_next_frame(addr, size, handler, name)
return execute_next_frame(read_mem_and_handle(addr, size, handler, name), name)
end
function is_opening_load_sequence()
-- This first clause is only true right after the system powers on or restarts
if mainmemory.read_u32_be(0x11A5EC) == 0 then
return true
end
if boot_sequence > 0 then
return true
end
return false
end
function handle_check(name, val)
if is_opening_load_sequence() then
return
end
update_flags(name, val)
end
function handle_inventory(val, name)
if is_opening_load_sequence() then
return
end
scene_number = get_scene_number()
if scene_number ~= 0x802c and scene_number ~= 0x8017 and scene_number ~= 0x8018 then
prev_val = update_inventory(val, name)
if prev_val ~= val then
-- When learning the windmill song, the song/item get flag is written _before_
-- the "learned song of storms" and "song played in windmill" flags.
-- All other check logic works on the basis of storing the last flag
-- set between chest flags, freestanding item (collectible) flags,
-- song learned flags, and event item obtained (NPC and scrubs) flags.
-- This clause looks to see if the player is in the windmill and playing ocarina
-- when the check is obtained.
if scene_number == 0x48 and mainmemory.read_u32_be(0x1D9008) == 0x6FE260 then
last_ck_ty = "misc_event_3"
last_ck_val = 0x800
end
print(string.format("%s\t%s\t%x -> %x\t%s\t%x\t%x", os.date("%Y-%m-%d %H:%M:%S", os.time()), name, prev_val, val, last_ck_ty, last_ck_val, get_scene_number()))
end
end
end
function update_inventory(val, name)
prev_val = inventory_state[name]
inventory_state[name] = val
return prev_val
end
function update_flags(val, name)
local prev_val = scene_and_global_flags[name]
local ck_val = val - prev_val
last_ck_ty = name
last_ck_val = ck_val
scene_and_global_flags[name] = val
print(string.format("Scene: %.02x\tCk type: %s\tCk val: %x", get_scene_number(), name, ck_val))
end
function get_scene_number()
-- TODO: map addr to name
return mainmemory.read_u16_be(0x1C8544)
end
function hexify(padding)
return function(n) return string.format("%0" .. padding .. "x", tonumber(n)) end
end
-- for fuck's sake, when specifying a base (i.e. non-10),
-- tonumber is capped at 2^32 - 1
-- I think this is specific to the Bizhawk lua interpreter
-- oh, and using string.format to represent a number greater than 2^32 - 1
-- in hex just doesn't work
function read_range(size)
local function read_n_range(addr)
local bytes = one_index(mainmemory.readbyterange(addr, size))
local hex_str_bytes = map(hexify(2), bytes)
local hex_str_rep = table.concat(hex_str_bytes, "")
-- local length = string.len(hex_str_rep)
-- vals = {}
-- i = 1
-- while length > 8 do
-- vals[i] = tonumber(string.sub(hex_str_rep, -8, -1), 16)
-- hex_str_rep = string.sub(hex_str_rep, 1, -9)
-- i = i + 1
-- end
-- vals[i] = tonumber(hex_str_rep, 16)
-- local vals = tonumber()
-- return vals
return tonumber(hex_str_rep, 16)
end
return read_n_range
end
-- readbyterange returns a 0-indexed """array""" (table), which none of lua's builtin functions treat properly,
-- since it expects 1-indexing...
function one_index(array)
local new_array = {}
for i,v in pairs(array) do
new_array[i+1] = v
end
return new_array
end
function map(func, array)
local new_array = {}
for i,v in ipairs(array) do
new_array[i] = func(v)
end
return new_array
end
SIZE_TO_READ_FN = {
[1] = mainmemory.read_u8,
[2] = mainmemory.read_u16_be,
[4] = mainmemory.read_u32_be
}
last_ck_ty = ""
last_ck_val = 0
boot_sequence = 3
-- this is just a random address I noticed was 0 during n64 scene and file select scene
-- if tracking randomly stops during the game, maybe this address being 0 is why
-- update: shit, it happens *after* the write to inventory
-- 11B91C
-- 11B920
-- 1CA0E0
-- 11A5D0: entrance idx
-- 11A5DC: world time
-- 11A5EC: static string for corruption test
-- 0x17CA48 u16: always val 5678 except at very start of execution
reboot_detect = function()
if mainmemory.read_u16_be(0x17CA48) == 0 then
boot_sequence = 1
print("n64")
end
end
file_select = function()
if mainmemory.read_u32_be(0x11A5EC) == 0 and boot_sequence == 2 then
boot_sequence = boot_sequence + 1
print("file_select")
end
end
handle_scene_setup_index = function()
if mainmemory.read_u32_be(0x11B930) >= 4 and (boot_sequence == 1 or boot_sequence == 3) then
boot_sequence = (boot_sequence + 1) % 4
print("load in")
end
end
-- https://wiki.cloudmodding.com/oot/Save_Format#Event_Flags
-- The trigger address has to be the actual start of the word
-- i.e. if a 4-byte word is written to address 0x0002, triggering off
-- writes to 0x0005 will do nothing.
-- I think this is mostly relevant in the writes that are patched in;
-- i.e. the writes in the base game tend to be 1 byte at a time.
CHECKS_MEM_BLOCKS = {
chest = {addr = 0x1CA1D8, size = 4},
collec = {addr = 0x1CA1E4, size = 4},
misc_event_1 = {addr = 0x11B4A4, size = 4},
misc_event_2 = {addr = 0x11B4A8, size = 4},
misc_event_3 = {addr = 0x11B4AC, size = 4},
-- songs1 = {addr = 0x11B4AE, size = 1},
-- songs2 = {addr = 0x11B4AF, size = 1},
misc_event_4 = {addr = 0x11B4B0, size = 4},
misc_event_5 = {addr = 0x11B4B4, size = 4},
songs3 = {addr = 0x11B4B8, size = 1},
misc_event_6 = {addr = 0x11B4B9, size = 1},
saria_bridge = {addr = 0x11B4BA, size = 4},
skulltula_token_turnin_checks = {addr = 0x11B4BE, size = 1},
frog_checks = {addr = 0x11B4BF, size = 1},
-- not sure if it's lua's fault or Bizhawk's, but this implementation
-- of lua interpreter doesn't deal well with numbers > 2^32 - 1
npc_scrub1 = {addr = 0x11B4C0, size = 4},
npc_scrub2 = {addr = 0x11B4C4, size = 4},
link_the_goron = {addr = 0x11B4E8, size = 4},
thaw_king_zora = {addr = 0x11B4EC, size = 4},
nut_stick_richard_horsebackarchery = {addr = 0x11B4F8, size = 4},
}
scene_and_global_flags = {}
for name, mem in pairs(CHECKS_MEM_BLOCKS) do
scene_and_global_flags[name] = 0
event.onmemorywrite(read_mem_and_handle_next_frame(mem["addr"], mem["size"], handle_check, name), 0x80000000 + mem["addr"])
end
INVENTORY_MEM_BLOCKS = {
fire_arrow = {addr = 0x11A648, size = 1},
dins_fire = {addr = 0x11A649, size = 1},
ocarina = {addr = 0x11A64B, size = 1},
bombchu = {addr = 0x11A64C, size = 1},
hookshot = {addr = 0x11A64D, size = 1},
farores_wind = {addr = 0x11A64F, size = 1},
boomerang = {addr = 0x11A650, size = 1},
lens = {addr = 0x11A651, size = 1},
beans = {addr = 0x11A652, size = 1},
hammer = {addr = 0x11A653, size = 1},
light_arrow = {addr = 0x11A654, size = 1},
nayrus_love = {addr = 0x11A655, size = 1},
bottle1 = {addr = 0x11A656, size = 1},
bottle2 = {addr = 0x11A657, size = 1},
bottle3 = {addr = 0x11A658, size = 1},
bottle4 = {addr = 0x11A659, size = 1},
child_trade = {addr = 0x11A65A, size = 1},
adult_trade = {addr = 0x11A65B, size = 1},
boots_tunic_shield_sword = {addr = 0x11A66C, size = 2},
-- the first byte of this 4-byte sequence is unused; the remaining three
-- are updated at the same time, and twice in a row when a check is gotten
-- the sequence is treated as a single word
stick_nut_scale_wallet_bullet_quiver_bomb_str = {addr = 0x11A670, size = 4},
quest_items = {addr = 0x11A674, size = 4},
}
inventory_state = {}
for name, mem in pairs(INVENTORY_MEM_BLOCKS) do
inventory_state[name] = 0
event.onmemorywrite(read_mem_and_handle_next_frame(mem["addr"], mem["size"], handle_inventory, name), 0x80000000 + mem["addr"])
end
-- event.onmemorywrite(read_mem_and_handle_next_frame(INVENTORY_ADDRS["stick_nut_scale_wallet_bullet_quiver_bomb_str"], 4, "stick_nut_scale_wallet_bullet_quiver_bomb_str"), INVENTORY_ADDRS["stick_nut_scale_wallet_bullet_quiver_bomb_str"] + 0x80000000)
-- event.onmemorywrite(read_mem_and_handle_next_frame(INVENTORY_ADDRS["hammer"], 1, "hammer"), INVENTORY_ADDRS["hammer"] + 0x80000000)
-- event.onmemorywrite(read_mem_and_handle_next_frame(INVENTORY_ADDRS["boomerang"], 1, "boomerang"), INVENTORY_ADDRS["boomerang"] + 0x80000000)
-- event.onmemorywrite(read_mem_and_handle_next_frame(INVENTORY_ADDRS["boots_tunic_shield_sword"], 2, "boots_tunic_shield_sword"), INVENTORY_ADDRS["boots_tunic_shield_sword"] + 0x80000000)
-- event.onmemorywrite(read_mem_and_handle_next_frame(INVENTORY_ADDRS["quest_items"], 3, "quest_items"), INVENTORY_ADDRS["quest_items"] + 0x80000000)
-- event.onmemorywrite(read_mem_and_handle_next_frame(0x1CA1D8, 4, "chest"), 0x801CA1D8)
-- event.onmemorywrite(read_mem_and_handle_next_frame(0x1CA1E4, 4, "collec"), 0x801CA1E4)
-- event.onmemorywrite(read_mem_and_handle_next_frame(0x11BC2E, 1, "songs1"), 0x8011BC2E)
-- event.onmemorywrite(read_mem_and_handle_next_frame(0x11BC2F, 1, "songs2"), 0x8011BC2F)
-- event.onmemorywrite(read_mem_and_handle_next_frame(0x11BC38, 1, "songs3"), 0x8011BC38)
-- the +1 here means the trigger doesn't depend on the first byte, which holds no useful information
event.onmemorywrite(reboot_detect, 0x8017CA48)
event.onmemorywrite(file_select, 0x8011A5EC)
event.onmemorywrite(handle_scene_setup_index, 0x8011B930)
event.onmemorywrite(resync_next_frame(), SCENE_ADDR + 0x80000000)
-- event.onmemorywrite(function() scene_changing = true end, 0x801C8544)
-- event.onmemorywrite(function() scene_changing = false end, 0x801DA298)
-- dive: +0x02 per upgrade
-- wallet: +0x10 per upgrade
-- bullet: +0x40 per upgrade