From 469dda7d8593452118ce9f35cf0b27976909614b Mon Sep 17 00:00:00 2001 From: espeon65536 <81029175+espeon65536@users.noreply.github.com> Date: Sun, 27 Mar 2022 14:44:22 -0500 Subject: [PATCH] AP Ocarina of Time Client (#352) --- OoTClient.py | 287 +++ .../static/assets/tutorial/zelda5/setup_en.md | 64 +- data/lua/OOT/core.dll | Bin 0 -> 29184 bytes data/lua/OOT/json.lua | 380 ++++ data/lua/OOT/oot_connector.lua | 1838 +++++++++++++++++ data/lua/OOT/socket.lua | 132 ++ host.yaml | 4 + inno_setup.iss | 15 +- kvui.py | 6 + setup.py | 1 + 10 files changed, 2700 insertions(+), 27 deletions(-) create mode 100644 OoTClient.py create mode 100644 data/lua/OOT/core.dll create mode 100644 data/lua/OOT/json.lua create mode 100644 data/lua/OOT/oot_connector.lua create mode 100644 data/lua/OOT/socket.lua diff --git a/OoTClient.py b/OoTClient.py new file mode 100644 index 00000000..700098ec --- /dev/null +++ b/OoTClient.py @@ -0,0 +1,287 @@ +import asyncio +import json +import os +import multiprocessing +import subprocess +from asyncio import StreamReader, StreamWriter + +from CommonClient import CommonContext, server_loop, gui_enabled, console_loop, \ + ClientCommandProcessor, logger, get_base_parser +import Utils +from worlds import network_data_package +from worlds.oot.Rom import Rom, compress_rom_file +from worlds.oot.N64Patch import apply_patch_file +from worlds.oot.Utils import data_path + + +CONNECTION_TIMING_OUT_STATUS = "Connection timing out. Please restart your emulator, then restart oot_connector.lua" +CONNECTION_REFUSED_STATUS = "Connection refused. Please start your emulator and make sure oot_connector.lua is running" +CONNECTION_RESET_STATUS = "Connection was reset. Please restart your emulator, then restart oot_connector.lua" +CONNECTION_TENTATIVE_STATUS = "Initial Connection Made" +CONNECTION_CONNECTED_STATUS = "Connected" +CONNECTION_INITIAL_STATUS = "Connection has not been initiated" + +""" +Payload: lua -> client +{ + playerName: string, + locations: dict, + deathlinkActive: bool, + isDead: bool, + gameComplete: bool +} + +Payload: client -> lua +{ + items: list, + playerNames: list, + triggerDeath: bool +} + +Deathlink logic: +"Dead" is true <-> Link is at 0 hp. + +deathlink_pending: we need to kill the player +deathlink_sent_this_death: we interacted with the multiworld on this death, waiting to reset with living link + +""" + +oot_loc_name_to_id = network_data_package["games"]["Ocarina of Time"]["location_name_to_id"] + +def get_item_value(ap_id): + return ap_id - 66000 + +class OoTCommandProcessor(ClientCommandProcessor): + def __init__(self, ctx): + super().__init__(ctx) + + def _cmd_n64(self): + """Check N64 Connection State""" + if isinstance(self.ctx, OoTContext): + logger.info(f"N64 Status: {self.ctx.n64_status}") + + +class OoTContext(CommonContext): + command_processor = OoTCommandProcessor + items_handling = 0b001 # full local + + def __init__(self, server_address, password): + super().__init__(server_address, password) + self.game = 'Ocarina of Time' + self.n64_streams: (StreamReader, StreamWriter) = None + self.n64_sync_task = None + self.n64_status = CONNECTION_INITIAL_STATUS + self.awaiting_rom = False + self.location_table = {} + self.deathlink_enabled = False + self.deathlink_pending = False + self.deathlink_sent_this_death = False + + async def server_auth(self, password_requested: bool = False): + if password_requested and not self.password: + await super(OoTContext, self).server_auth(password_requested) + if not self.auth: + self.awaiting_rom = True + logger.info('Awaiting connection to Bizhawk to get player information') + return + + await self.send_connect() + + def on_deathlink(self, data: dict): + self.deathlink_pending = True + super().on_deathlink(data) + + +def get_payload(ctx: OoTContext): + if ctx.deathlink_enabled and ctx.deathlink_pending: + trigger_death = True + ctx.deathlink_sent_this_death = True + else: + trigger_death = False + + return json.dumps({ + "items": [get_item_value(item.item) for item in ctx.items_received], + "playerNames": [name for (i, name) in ctx.player_names.items() if i != 0], + "triggerDeath": trigger_death + }) + + +async def parse_payload(payload: dict, ctx: OoTContext, force: bool): + + # Turn on deathlink if it is on + if payload['deathlinkActive'] and not ctx.deathlink_enabled: + await ctx.update_death_link(True) + ctx.deathlink_enabled = True + + # Game completion handling + if payload['gameComplete'] and not ctx.finished_game: + await ctx.send_msgs([{ + "cmd": "StatusUpdate", + "status": 30 + }]) + ctx.finished_game = True + + # Locations handling + if ctx.location_table != payload['locations']: + ctx.location_table = payload['locations'] + await ctx.send_msgs([{ + "cmd": "LocationChecks", + "locations": [oot_loc_name_to_id[loc] for loc in ctx.location_table if ctx.location_table[loc]] + }]) + + # Deathlink handling + if ctx.deathlink_enabled: + if payload['isDead']: # link is dead + ctx.deathlink_pending = False + if not ctx.deathlink_sent_this_death: + ctx.deathlink_sent_this_death = True + await ctx.send_death() + else: # link is alive + ctx.deathlink_sent_this_death = False + + +async def n64_sync_task(ctx: OoTContext): + logger.info("Starting n64 connector. Use /n64 for status information.") + while not ctx.exit_event.is_set(): + error_status = None + if ctx.n64_streams: + (reader, writer) = ctx.n64_streams + msg = get_payload(ctx).encode() + writer.write(msg) + writer.write(b'\n') + try: + await asyncio.wait_for(writer.drain(), timeout=1.5) + try: + # Data will return a dict with up to five fields: + # 1. str: player name (always) + # 2. bool: deathlink active (always) + # 3. dict[str, bool]: checked locations + # 4. bool: whether Link is currently at 0 HP + # 5. bool: whether the game currently registers as complete + data = await asyncio.wait_for(reader.readline(), timeout=10) + data_decoded = json.loads(data.decode()) + if ctx.game is not None and 'locations' in data_decoded: + # Not just a keep alive ping, parse + asyncio.create_task(parse_payload(data_decoded, ctx, False)) + if not ctx.auth: + ctx.auth = data_decoded['playerName'] + if ctx.awaiting_rom: + await ctx.server_auth(False) + except asyncio.TimeoutError: + logger.debug("Read Timed Out, Reconnecting") + error_status = CONNECTION_TIMING_OUT_STATUS + writer.close() + ctx.n64_streams = None + except ConnectionResetError as e: + logger.debug("Read failed due to Connection Lost, Reconnecting") + error_status = CONNECTION_RESET_STATUS + writer.close() + ctx.n64_streams = None + except TimeoutError: + logger.debug("Connection Timed Out, Reconnecting") + error_status = CONNECTION_TIMING_OUT_STATUS + writer.close() + ctx.n64_streams = None + except ConnectionResetError: + logger.debug("Connection Lost, Reconnecting") + error_status = CONNECTION_RESET_STATUS + writer.close() + ctx.n64_streams = None + if ctx.n64_status == CONNECTION_TENTATIVE_STATUS: + if not error_status: + logger.info("Successfully Connected to N64") + ctx.n64_status = CONNECTION_CONNECTED_STATUS + else: + ctx.n64_status = f"Was tentatively connected but error occured: {error_status}" + elif error_status: + ctx.n64_status = error_status + logger.info("Lost connection to N64 and attempting to reconnect. Use /n64 for status updates") + else: + try: + logger.debug("Attempting to connect to N64") + ctx.n64_streams = await asyncio.wait_for(asyncio.open_connection("localhost", 28921), timeout=10) + ctx.n64_status = CONNECTION_TENTATIVE_STATUS + except TimeoutError: + logger.debug("Connection Timed Out, Trying Again") + ctx.n64_status = CONNECTION_TIMING_OUT_STATUS + continue + except ConnectionRefusedError: + logger.debug("Connection Refused, Trying Again") + ctx.n64_status = CONNECTION_REFUSED_STATUS + continue + + +async def run_game(romfile): + auto_start = Utils.get_options()["oot_options"].get("rom_start", True) + if auto_start is True: + import webbrowser + webbrowser.open(romfile) + elif os.path.isfile(auto_start): + subprocess.Popen([auto_start, romfile], + stdin=subprocess.DEVNULL, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) + + +async def patch_and_run_game(apz5_file): + base_name = os.path.splitext(apz5_file)[0] + decomp_path = base_name + '-decomp.z64' + comp_path = base_name + '.z64' + # Load vanilla ROM, patch file, compress ROM + rom = Rom(Utils.get_options()["oot_options"]["rom_file"]) + apply_patch_file(rom, apz5_file) + rom.write_to_file(decomp_path) + os.chdir(data_path("Compress")) + compress_rom_file(decomp_path, comp_path) + os.remove(decomp_path) + asyncio.create_task(run_game(comp_path)) + + +if __name__ == '__main__': + + Utils.init_logging("OoTClient") + + async def main(): + multiprocessing.freeze_support() + parser = get_base_parser() + parser.add_argument('apz5_file', default="", type=str, nargs="?", + help='Path to an APZ5 file') + args = parser.parse_args() + + if args.apz5_file: + logger.info("APZ5 file supplied, beginning patching process...") + asyncio.create_task(patch_and_run_game(args.apz5_file)) + + ctx = OoTContext(args.connect, args.password) + ctx.server_task = asyncio.create_task(server_loop(ctx), name="Server Loop") + if gui_enabled: + input_task = None + from kvui import OoTManager + ctx.ui = OoTManager(ctx) + ui_task = asyncio.create_task(ctx.ui.async_run(), name="UI") + else: + input_task = asyncio.create_task(console_loop(ctx), name="Input") + ui_task = None + + ctx.n64_sync_task = asyncio.create_task(n64_sync_task(ctx), name="N64 Sync") + + await ctx.exit_event.wait() + ctx.server_address = None + + await ctx.shutdown() + + if ctx.n64_sync_task: + await ctx.n64_sync_task + + if ui_task: + await ui_task + + if input_task: + input_task.cancel() + + import colorama + + colorama.init() + + loop = asyncio.get_event_loop() + loop.run_until_complete(main()) + loop.close() + colorama.deinit() diff --git a/WebHostLib/static/assets/tutorial/zelda5/setup_en.md b/WebHostLib/static/assets/tutorial/zelda5/setup_en.md index ccbdab2a..a62a78ed 100644 --- a/WebHostLib/static/assets/tutorial/zelda5/setup_en.md +++ b/WebHostLib/static/assets/tutorial/zelda5/setup_en.md @@ -1,27 +1,40 @@ -# Setup Guide for Ocarina of time Archipelago +# Setup Guide for Ocarina of Time Archipelago ## Important -As we are using Z5Client and BizHawk, this guide is only applicable to Windows. +As we are using Bizhawk, this guide is only applicable to Windows and Linux systems. ## Required Software -- BizHawk and Z5Client from: [Z5Client Releases Page](https://github.com/ArchipelagoMW/Z5Client/releases) - - We recommend download Z5Client-setup as it makes some steps automatic. +- Bizhawk: [Bizhawk Releases from TASVideos](https://tasvideos.org/BizHawk/ReleaseHistory) + - Version 2.3.1 and later are supported. Version 2.7 is recommended for stability. + - Detailed installation instructions for Bizhawk can be found at the above link. + - Windows users must run the prereq installer first, which can also be found at the above link. +- An Archipelago client for Ocarina of Time. There are two options available: + - The built-in Archipelago client, which can be installed [here](https://github.com/ArchipelagoMW/Archipelago/releases) + (select `Ocarina of Time Client` during installation). This client is kept up-to-date with the latest Archipelago version + and will always be supported. + - Z5Client, which can be installed [here](https://github.com/ArchipelagoMW/Z5Client/releases), and its associated Lua script `ootMulti.lua`. +- An Ocarina of Time v1.0 ROM. -## Install Emulator and client +## Configuring Bizhawk -Download getBizhawk.ps1 from previous link. Place it on the folder where you want your emulator to be installed, right -click on it and select "Run with PowerShell". This will download all the needed dependencies used by the emulator. This -can take a while. +Once Bizhawk has been installed, open Bizhawk and change the following settings: -It is strongly recommended to associate N64 rom extension (\*.n64) to the BizHawk we've just installed. To do so, we -simply have to search any N64 rom we happened to own, right click and select "Open with...", we unfold the list that -appears and select the bottom option "Look for another application", we browse to BizHawk folder and select EmuHawk.exe +- Go to Config > Customize. Switch to the Advanced tab, then switch the Lua Core from "NLua+KopiLua" to "Lua+LuaInterface". + This is required for the Lua script to function correctly. +- Under Config > Customize > Advanced, make sure the box for AutoSaveRAM is checked, and click the 5s button. This reduces + the possibility of losing save data in emulator crashes. +- Under Config > Customize, check the "Run in background" and "Accept background input" boxes. This will allow you to continue + playing in the background, even if another window is selected. +- Under Config > Hotkeys, many hotkeys are listed, with many bound to common keys on the keyboard. You will likely want + to disable most of these, which you can do quickly using `Esc`. +- If playing with a controller, when you bind controls, disable "P1 A Up", "P1 A Down", "P1 A Left", and "P1 A Right" as these interfere + with aiming if bound. Set directional input using the Analog tab instead. -Place the ootMulti.lua file from the previous link inside the "lua" folder from the just installed emulator. - -Install the Z5Client using its setup. +It is strongly recommended to associate N64 rom extensions (\*.n64, \*.z64) to the Bizhawk we've just installed. To do so, we +simply have to search any N64 rom we happened to own, right click and select "Open with...", unfold the list that +appears and select the bottom option "Look for another application", then browse to the Bizhawk folder and select EmuHawk.exe. ## Configuring your YAML file @@ -33,7 +46,7 @@ an experience customized for their taste, and different players in the same mult ### Where do I get a YAML file? -A basic OOT yaml will look like this. There are lots of cosmetic options that have been removed for the sake of this +A basic OoT yaml will look like this. There are lots of cosmetic options that have been removed for the sake of this tutorial, if you want to see a complete list, download Archipelago from the [Archipelago Releases Page](https://github.com/ArchipelagoMW/Archipelago/releases) and look for the sample file in the "Players" folder. @@ -382,20 +395,23 @@ Ocarina of Time: When you join a multiworld game, you will be asked to provide your YAML file to whoever is hosting. Once that is done, the host will provide you with either a link to download your data file, or with a zip file containing everyone's data -files. Your data file should have a `.z5ap` extension. +files. Your data file should have a `.apz5` extension. -Double-click on your `.z5ap` file to start Z5Client and start the ROM patch process. Once the process is finished (this -can take a while), the emulator will be started automatically (If we associated the extension to the emulator as -recommended) +Double-click on your `.apz5` file to start your client and start the ROM patch process. Once the process is finished (this +can take a while), the client and the emulator will be started automatically (if you associated the extension to the emulator as +recommended). ### Connect to the Multiserver -Once both the Z5Client and the emulator are started we must connect them. Within the emulator we click on the "Tools" -menu and select "Lua console". In the new window click on the folder icon and look for the ootMulti.lua file. Once the -file is loaded it will connect automatically to Z5Client. +Once both the client and the emulator are started, you must connect them. Within the emulator click on the "Tools" +menu and select "Lua Console". Click the folder button or press Ctrl+O to open a Lua script. -Note: We strongly advise you don't open any emulator menu while it and Z5client are connected, as the script will halt -and disconnects can happen. If you get disconnected just double-click on the script again. +If you are using the Archipelago OoTClient, navigate to your Archipelago install folder and open `data/lua/OOT/oot_connector.lua`. + +If you are using Z5Client, find the `ootMulti.lua` file and open it. + +Note: If using Z5Client, we strongly advise you don't open any menus in Bizhawk while the emulator and Z5Client are connected, as the script will halt +and force-disconnect from the server. If you get disconnected just double-click on the script again. To connect the client to the multiserver simply put `
:` on the textfield on top and press enter (if the server uses password, type in the bottom textfield `/connect
: [password]`) diff --git a/data/lua/OOT/core.dll b/data/lua/OOT/core.dll new file mode 100644 index 0000000000000000000000000000000000000000..3e9569571ab0947dcb7bcd789dc9c06c009d072d GIT binary patch literal 29184 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 B{xARl literal 0 HcmV?d00001 diff --git a/data/lua/OOT/json.lua b/data/lua/OOT/json.lua new file mode 100644 index 00000000..0833bf6f --- /dev/null +++ b/data/lua/OOT/json.lua @@ -0,0 +1,380 @@ +-- +-- json.lua +-- +-- Copyright (c) 2015 rxi +-- +-- This library is free software; you can redistribute it and/or modify it +-- under the terms of the MIT license. See LICENSE for details. +-- + +local json = { _version = "0.1.0" } + +------------------------------------------------------------------------------- +-- Encode +------------------------------------------------------------------------------- + +local encode + +local escape_char_map = { + [ "\\" ] = "\\\\", + [ "\"" ] = "\\\"", + [ "\b" ] = "\\b", + [ "\f" ] = "\\f", + [ "\n" ] = "\\n", + [ "\r" ] = "\\r", + [ "\t" ] = "\\t", +} + +local escape_char_map_inv = { [ "\\/" ] = "/" } +for k, v in pairs(escape_char_map) do + escape_char_map_inv[v] = k +end + + +local function escape_char(c) + return escape_char_map[c] or string.format("\\u%04x", c:byte()) +end + + +local function encode_nil(val) + return "null" +end + + +local function encode_table(val, stack) + local res = {} + stack = stack or {} + + -- Circular reference? + if stack[val] then error("circular reference") end + + stack[val] = true + + if val[1] ~= nil or next(val) == nil then + -- Treat as array -- check keys are valid and it is not sparse + local n = 0 + for k in pairs(val) do + if type(k) ~= "number" then + error("invalid table: mixed or invalid key types") + end + n = n + 1 + end + if n ~= #val then + error("invalid table: sparse array") + end + -- Encode + for i, v in ipairs(val) do + table.insert(res, encode(v, stack)) + end + stack[val] = nil + return "[" .. table.concat(res, ",") .. "]" + + else + -- Treat as an object + for k, v in pairs(val) do + if type(k) ~= "string" then + error("invalid table: mixed or invalid key types") + end + table.insert(res, encode(k, stack) .. ":" .. encode(v, stack)) + end + stack[val] = nil + return "{" .. table.concat(res, ",") .. "}" + end +end + + +local function encode_string(val) + return '"' .. val:gsub('[%z\1-\31\\"]', escape_char) .. '"' +end + + +local function encode_number(val) + -- Check for NaN, -inf and inf + if val ~= val or val <= -math.huge or val >= math.huge then + error("unexpected number value '" .. tostring(val) .. "'") + end + return string.format("%.14g", val) +end + + +local type_func_map = { + [ "nil" ] = encode_nil, + [ "table" ] = encode_table, + [ "string" ] = encode_string, + [ "number" ] = encode_number, + [ "boolean" ] = tostring, +} + + +encode = function(val, stack) + local t = type(val) + local f = type_func_map[t] + if f then + return f(val, stack) + end + error("unexpected type '" .. t .. "'") +end + + +function json.encode(val) + return ( encode(val) ) +end + + +------------------------------------------------------------------------------- +-- Decode +------------------------------------------------------------------------------- + +local parse + +local function create_set(...) + local res = {} + for i = 1, select("#", ...) do + res[ select(i, ...) ] = true + end + return res +end + +local space_chars = create_set(" ", "\t", "\r", "\n") +local delim_chars = create_set(" ", "\t", "\r", "\n", "]", "}", ",") +local escape_chars = create_set("\\", "/", '"', "b", "f", "n", "r", "t", "u") +local literals = create_set("true", "false", "null") + +local literal_map = { + [ "true" ] = true, + [ "false" ] = false, + [ "null" ] = nil, +} + + +local function next_char(str, idx, set, negate) + for i = idx, #str do + if set[str:sub(i, i)] ~= negate then + return i + end + end + return #str + 1 +end + + +local function decode_error(str, idx, msg) + --local line_count = 1 + --local col_count = 1 + --for i = 1, idx - 1 do + -- col_count = col_count + 1 + -- if str:sub(i, i) == "\n" then + -- line_count = line_count + 1 + -- col_count = 1 + -- end + -- end + -- emu.message( string.format("%s at line %d col %d", msg, line_count, col_count) ) +end + + +local function codepoint_to_utf8(n) + -- http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=iws-appendixa + local f = math.floor + if n <= 0x7f then + return string.char(n) + elseif n <= 0x7ff then + return string.char(f(n / 64) + 192, n % 64 + 128) + elseif n <= 0xffff then + return string.char(f(n / 4096) + 224, f(n % 4096 / 64) + 128, n % 64 + 128) + elseif n <= 0x10ffff then + return string.char(f(n / 262144) + 240, f(n % 262144 / 4096) + 128, + f(n % 4096 / 64) + 128, n % 64 + 128) + end + error( string.format("invalid unicode codepoint '%x'", n) ) +end + + +local function parse_unicode_escape(s) + local n1 = tonumber( s:sub(3, 6), 16 ) + local n2 = tonumber( s:sub(9, 12), 16 ) + -- Surrogate pair? + if n2 then + return codepoint_to_utf8((n1 - 0xd800) * 0x400 + (n2 - 0xdc00) + 0x10000) + else + return codepoint_to_utf8(n1) + end +end + + +local function parse_string(str, i) + local has_unicode_escape = false + local has_surrogate_escape = false + local has_escape = false + local last + for j = i + 1, #str do + local x = str:byte(j) + + if x < 32 then + decode_error(str, j, "control character in string") + end + + if last == 92 then -- "\\" (escape char) + if x == 117 then -- "u" (unicode escape sequence) + local hex = str:sub(j + 1, j + 5) + if not hex:find("%x%x%x%x") then + decode_error(str, j, "invalid unicode escape in string") + end + if hex:find("^[dD][89aAbB]") then + has_surrogate_escape = true + else + has_unicode_escape = true + end + else + local c = string.char(x) + if not escape_chars[c] then + decode_error(str, j, "invalid escape char '" .. c .. "' in string") + end + has_escape = true + end + last = nil + + elseif x == 34 then -- '"' (end of string) + local s = str:sub(i + 1, j - 1) + if has_surrogate_escape then + s = s:gsub("\\u[dD][89aAbB]..\\u....", parse_unicode_escape) + end + if has_unicode_escape then + s = s:gsub("\\u....", parse_unicode_escape) + end + if has_escape then + s = s:gsub("\\.", escape_char_map_inv) + end + return s, j + 1 + + else + last = x + end + end + decode_error(str, i, "expected closing quote for string") +end + + +local function parse_number(str, i) + local x = next_char(str, i, delim_chars) + local s = str:sub(i, x - 1) + local n = tonumber(s) + if not n then + decode_error(str, i, "invalid number '" .. s .. "'") + end + return n, x +end + + +local function parse_literal(str, i) + local x = next_char(str, i, delim_chars) + local word = str:sub(i, x - 1) + if not literals[word] then + decode_error(str, i, "invalid literal '" .. word .. "'") + end + return literal_map[word], x +end + + +local function parse_array(str, i) + local res = {} + local n = 1 + i = i + 1 + while 1 do + local x + i = next_char(str, i, space_chars, true) + -- Empty / end of array? + if str:sub(i, i) == "]" then + i = i + 1 + break + end + -- Read token + x, i = parse(str, i) + res[n] = x + n = n + 1 + -- Next token + i = next_char(str, i, space_chars, true) + local chr = str:sub(i, i) + i = i + 1 + if chr == "]" then break end + if chr ~= "," then decode_error(str, i, "expected ']' or ','") end + end + return res, i +end + + +local function parse_object(str, i) + local res = {} + i = i + 1 + while 1 do + local key, val + i = next_char(str, i, space_chars, true) + -- Empty / end of object? + if str:sub(i, i) == "}" then + i = i + 1 + break + end + -- Read key + if str:sub(i, i) ~= '"' then + decode_error(str, i, "expected string for key") + end + key, i = parse(str, i) + -- Read ':' delimiter + i = next_char(str, i, space_chars, true) + if str:sub(i, i) ~= ":" then + decode_error(str, i, "expected ':' after key") + end + i = next_char(str, i + 1, space_chars, true) + -- Read value + val, i = parse(str, i) + -- Set + res[key] = val + -- Next token + i = next_char(str, i, space_chars, true) + local chr = str:sub(i, i) + i = i + 1 + if chr == "}" then break end + if chr ~= "," then decode_error(str, i, "expected '}' or ','") end + end + return res, i +end + + +local char_func_map = { + [ '"' ] = parse_string, + [ "0" ] = parse_number, + [ "1" ] = parse_number, + [ "2" ] = parse_number, + [ "3" ] = parse_number, + [ "4" ] = parse_number, + [ "5" ] = parse_number, + [ "6" ] = parse_number, + [ "7" ] = parse_number, + [ "8" ] = parse_number, + [ "9" ] = parse_number, + [ "-" ] = parse_number, + [ "t" ] = parse_literal, + [ "f" ] = parse_literal, + [ "n" ] = parse_literal, + [ "[" ] = parse_array, + [ "{" ] = parse_object, +} + + +parse = function(str, idx) + local chr = str:sub(idx, idx) + local f = char_func_map[chr] + if f then + return f(str, idx) + end + decode_error(str, idx, "unexpected character '" .. chr .. "'") +end + + +function json.decode(str) + if type(str) ~= "string" then + error("expected argument of type string, got " .. type(str)) + end + return ( parse(str, next_char(str, 1, space_chars, true)) ) +end + + +return json \ No newline at end of file diff --git a/data/lua/OOT/oot_connector.lua b/data/lua/OOT/oot_connector.lua new file mode 100644 index 00000000..0d42e626 --- /dev/null +++ b/data/lua/OOT/oot_connector.lua @@ -0,0 +1,1838 @@ +local socket = require("socket") +local json = require('json') +local math = require('math') + +local script_version = '2022-03-22' -- Should be the last modified date + +-------------------------------------------------- +-- Heavily modified form of RiptideSage's tracker +-------------------------------------------------- + +-- TODO: read this from the ROM +local NUM_BIG_POES_REQUIRED = 1 + +-- The offset constants are all from N64 RAM start. Offsets in the check statements are relative. +local save_context_offset = 0x11A5D0 +local equipment_offset = save_context_offset + 0x70 -- 0x11A640 +local scene_flags_offset = save_context_offset + 0xD4 --0x11A6A4 +local shop_context_offset = save_context_offset + 0x5B4 --0x11AB84 +local skulltula_flags_offset = save_context_offset + 0xE9C --0x11B46C +local event_context_offset = save_context_offset + 0xED4 --0x11B4A4 +local big_poe_points_offset = save_context_offset + 0xEBC -- 0x11B48C +local fishing_context_offset = save_context_offset + 0xEC0 --0x11B490 +local item_get_inf_offset = save_context_offset + 0xEF0 --0x11B4C0 +local inf_table_offset = save_context_offset + 0xEF8 -- 0x11B4C8 + +local temp_context = nil + +-- Offsets for scenes can be found here +-- https://wiki.cloudmodding.com/oot/Scene_Table/NTSC_1.0 +-- Each scene is 0x1c bits long, chests at 0x0, switches at 0x4, collectibles at 0xc +local scene_check = function(scene_offset, bit_to_check, scene_data_offset) + local local_scene_offset = scene_flags_offset + (0x1c * scene_offset) + scene_data_offset + local nearby_memory = mainmemory.read_u32_be(local_scene_offset) + return bit.check(nearby_memory,bit_to_check) +end + +-- Whenever a check is opened, values are written to 0x40002C. +-- We can use this to send checks before they are written to the main save context. +-- [0] should always be 0x00 when a non-local multiworld item is checked +-- [1] is the scene id +-- [2] is the location type, which varies as input to the function +-- [3] is the location id within the scene, and represents the bit which was checked +-- Note that temp_context is 0-indexed and expected_values is 1-indexed, because consistency. +local check_temp_context = function(expected_values) + if temp_context[0] ~= 0x00 then return false end + for i=1,3 do + if temp_context[i] ~= expected_values[i] then return false end + end + return true +end + +-- When checking locations, we check two spots: +-- First, we check the main save context. This is "permanent" memory. +-- If the main save context doesn't have the check recorded, we check the temporary context instead, +-- which holds the value of the last location checked. +-- The main save context is written on loading zone or save, +-- but we can get checks sent faster using the temporary context. + +local chest_check = function(scene_offset, bit_to_check) + return scene_check(scene_offset, bit_to_check, 0x0) + or check_temp_context({scene_offset, 0x01, bit_to_check}) +end + +local on_the_ground_check = function(scene_offset, bit_to_check) + return scene_check(scene_offset, bit_to_check, 0xC) + or check_temp_context({scene_offset, 0x02, bit_to_check}) +end + +local boss_item_check = function(scene_offset) + return chest_check(scene_offset, 0x1F) + or check_temp_context({scene_offset, 0x00, 0x4F}) +end + +-- NOTE: Scrubs are stored in the "unused" block of scene memory +-- These always write instantly to save context, so no need to check temp context +local scrub_sanity_check = function(scene_offset, bit_to_check) + return scene_check(scene_offset, bit_to_check, 0x10) +end + +local cow_check = function(scene_offset, bit_to_check) + return scene_check(scene_offset, bit_to_check, 0xC) + or check_temp_context({scene_offset, 0x00, bit_to_check}) +end + +-- Haven't been able to get DMT and DMC fairy to send instantly +local great_fairy_magic_check = function(scene_offset, bit_to_check) + return scene_check(scene_offset, bit_to_check, 0x4) + or check_temp_context({scene_offset, 0x05, bit_to_check}) +end + +-- Fire arrow location reports 0x00570058 to 0x40002C +local fire_arrows_check = function(scene_offset, bit_to_check) + return scene_check(scene_offset, bit_to_check, 0x0) + or check_temp_context({scene_offset, 0x00, 0x58}) +end + +-- Bean salesman reports 0x00540016 to 0x40002C +local bean_sale_check = function(scene_offset, bit_to_check) + return scene_check(scene_offset, bit_to_check, 0xC) + or check_temp_context({scene_offset, 0x00, 0x16}) +end + +--Helper method to resolve skulltula lookup location +local function skulltula_scene_to_array_index(i) + return (i + 3) - 2 * (i % 4) +end + +-- NOTE: The Rando LocationList offsets are bit masks not locations, so +-- 0x1 -> 0 offset, 0x2 -> 1 offset, 0x4 -> 2 offset, 0x8 -> 3 offset, etc. +-- NOTE: 8-bit array, scene_offsets are filled on [0x00,0x15] but use a lookup array above +local skulltula_check = function(scene_offset, bit_to_check) + --For some reason the skulltula array isn't a straight mapping from the scene ID + scene_offset = skulltula_scene_to_array_index(scene_offset) + local local_skulltula_offset = skulltula_flags_offset + (scene_offset) + local nearby_memory = mainmemory.read_u8(local_skulltula_offset) + return bit.check(nearby_memory,bit_to_check) +end + +-- Left shelf bit masks are: +-- 0x8 0x2 +-- 0x4 0x1 +local shop_check = function(shop_offset, item_offset) + local local_shop_offset = shop_context_offset + local nearby_memory = mainmemory.read_u32_be(local_shop_offset) + local bitToCheck = shop_offset*4 + item_offset + return bit.check(nearby_memory,bitToCheck) +end + +-- NOTE: Getting the bit poe bottle isn't flagged directly, instead only the points on the card are saved and +-- checked on each big poe turn in. +local big_poe_bottle_check = function() + local nearby_memory = mainmemory.read_u32_be(big_poe_points_offset) + local points_required = 100*NUM_BIG_POES_REQUIRED + return (nearby_memory >= points_required) +end + +-- Offsets can be found at the OOT save context layout here: +-- https://wiki.cloudmodding.com/oot/Save_Format#event_chk_inf +local event_check = function(major_offset,bit_to_check) + -- shifting over to the next 4 hex digits + local event_address = event_context_offset + 0x2 * major_offset + local u_16_event_row = mainmemory.read_u16_be(event_address) + return bit.check(u_16_event_row,bit_to_check) +end + +-- Used by the game to track some non-quest item event flags +local item_get_info_check = function(check_offset,bit_to_check) + local local_offset = item_get_inf_offset + (check_offset) + local nearby_memory = mainmemory.read_u8(local_offset) + return bit.check(nearby_memory,bit_to_check) +end + +-- Used by the game to track lots of misc information (Talking to people, getting items, etc.) +local info_table_check = function(check_offset,bit_to_check) + local local_offset = inf_table_offset + (check_offset) + local nearby_memory = mainmemory.read_u8(local_offset) + return bit.check(nearby_memory,bit_to_check) +end + +local membership_card_check = function(scene_offset,bit_to_check) + -- These checks used to be part of Gerudo Fortress, but they are better used as an approximation for the + -- membership card check. You will always have obtained the membership card if you have rescued all four carpenters. + -- checks["Gerudo Fortress - Free North F1 Carpenter"] = event_check(0x9, 0x0) + -- checks["Gerudo Fortress - Free North F2 Carpenter"] = event_check(0x9, 0x3) + -- checks["Gerudo Fortress - Free South F1 Carpenter"] = event_check(0x9, 0x1) + -- checks["Gerudo Fortress - Free South F2 Carpenter"] = event_check(0x9, 0x2) + + -- No need to save these checks in a table as they combine to create a conditional + return event_check(0x9, 0x0) and event_check(0x9, 0x1) and event_check(0x9, 0x2) and event_check(0x9, 0x3) + + -- This is the old version of the membership card check, which is inaccurate and always returns true + -- so long as a save context is loaded + -- return scene_check(scene_offset, bit_to_check, 0x4) +end + +-- The fishing records are intricate and in their own memory area +-- NOTE: Fishing in rando is patched and getting the adult reward first doesn't result in the "Golden scale glitch" +local fishing_check = function(isAdult) + local bitToCheck = 10 --for child + if(isAdult) then + bitToCheck = 11 --for adult + end + + local nearby_memory = mainmemory.read_u32_be(fishing_context_offset) + return bit.check(nearby_memory,bitToCheck) +end + +local big_goron_sword_check = function () + local nearby_memory = mainmemory.read_u32_be(equipment_offset) + local bitToCheck = 0x8 + return bit.check(nearby_memory,bitToCheck) +end + +local is_master_quest_dungeon = function(mq_table_address, dungeon_id) + return mainmemory.readbyte(mq_table_address + dungeon_id) == 1 +end + +local read_kokiri_forest_checks = function() + local checks = {} + checks["KF Midos Top Left Chest"] = chest_check(0x28, 0x00) + checks["KF Midos Top Right Chest"] = chest_check(0x28, 0x01) + checks["KF Midos Bottom Left Chest"] = chest_check(0x28, 0x02) + checks["KF Midos Bottom Right Chest"] = chest_check(0x28, 0x03) + checks["KF Kokiri Sword Chest"] = chest_check(0x55, 0x00) + checks["KF Storms Grotto Chest"] = chest_check(0x3E, 0x0C) + checks["KF Links House Cow"] = cow_check(0x34, 0x18) + + checks["KF GS Know It All House"] = skulltula_check(0x0C, 0x1) + checks["KF GS Bean Patch"] = skulltula_check(0x0C, 0x0) + checks["KF GS House of Twins"] = skulltula_check(0x0C, 0x2) + + checks["KF Shop Item 5"] = shop_check(0x6, 0x0) + checks["KF Shop Item 6"] = shop_check(0x6, 0x1) + checks["KF Shop Item 7"] = shop_check(0x6, 0x2) + checks["KF Shop Item 8"] = shop_check(0x6, 0x3) + return checks +end + +local read_lost_woods_checks = function() + local checks = {} + checks["LW Gift from Saria"] = event_check(0xC, 0x1) + checks["LW Ocarina Memory Game"] = item_get_info_check(0x3, 0x7) + checks["LW Target in Woods"] = item_get_info_check(0x2, 0x5) + checks["LW Near Shortcuts Grotto Chest"] = chest_check(0x3E, 0x14) + checks["Deku Theater Skull Mask"] = item_get_info_check(0x2, 0x6) + checks["Deku Theater Mask of Truth"] = item_get_info_check(0x2, 0x7) + checks["LW Skull Kid"] = item_get_info_check(0x3, 0x6) + + -- This is the first of three deku scrubs which are always included in the item pool, not just in scrub-sanity + checks["LW Deku Scrub Near Bridge"] = info_table_check(0x33, 0x2) + if not checks["LW Deku Scrub Near Bridge"] then + checks["LW Deku Scrub Near Bridge"] = scrub_sanity_check(0x5B, 0xA) + end + + -- This is the second of three deku scrubs which are always included in the item pool, not just in scrub-sanity + checks["LW Deku Scrub Grotto Front"] = info_table_check(0x33, 0x3) + if not checks["LW Deku Scrub Grotto Front"] then + checks["LW Deku Scrub Grotto Front"] = scrub_sanity_check(0x1F, 0xB) + end + + checks["LW Deku Scrub Near Deku Theater Left"] = scrub_sanity_check(0x5B, 0x2) + checks["LW Deku Scrub Near Deku Theater Right"] = scrub_sanity_check(0x5B, 0x1) + checks["LW Deku Scrub Grotto Rear"] = scrub_sanity_check(0x1F, 0x4) + + checks["LW GS Bean Patch Near Bridge"] = skulltula_check(0x0D, 0x0) + checks["LW GS Bean Patch Near Theater"] = skulltula_check(0x0D, 0x1) + checks["LW GS Above Theater"] = skulltula_check(0x0D, 0x2) + return checks +end + +local read_sacred_forest_meadow_checks = function() + local checks = {} + checks["SFM Wolfos Grotto Chest"] = chest_check(0x3E, 0x11) + checks["SFM Deku Scrub Grotto Front"] = scrub_sanity_check(0x18, 0x9) + checks["SFM Deku Scrub Grotto Rear"] = scrub_sanity_check(0x18, 0x8) + checks["SFM GS"] = skulltula_check(0x0D, 0x3) + return checks +end + +local read_deku_tree_checks = function(mq_table_address) + local checks = {} + if not is_master_quest_dungeon(mq_table_address, 0x0) then + checks["Deku Tree Map Chest"] = chest_check(0x00, 0x3) + checks["Deku Tree Slingshot Room Side Chest"] = chest_check(0x00, 0x5) + checks["Deku Tree Slingshot Chest"] = chest_check(0x00, 0x1) + checks["Deku Tree Compass Chest"] = chest_check(0x00, 0x2) + checks["Deku Tree Compass Room Side Chest"] = chest_check(0x00, 0x6) + checks["Deku Tree Basement Chest"] = chest_check(0x00, 0x4) + + checks["Deku Tree GS Compass Room"] = skulltula_check(0x0, 0x3) + checks["Deku Tree GS Basement Vines"] = skulltula_check(0x0, 0x2) + checks["Deku Tree GS Basement Gate"] = skulltula_check(0x0, 0x1) + checks["Deku Tree GS Basement Back Room"] = skulltula_check(0x0, 0x0) + else + checks["Deku Tree MQ Map Chest"] = chest_check(0x00, 0x3) + checks["Deku Tree MQ Slingshot Chest"] = chest_check(0x00, 0x6) + checks["Deku Tree MQ Slingshot Room Back Chest"] = chest_check(0x00, 0x2) + checks["Deku Tree MQ Compass Chest"] = chest_check(0x00, 0x1) + checks["Deku Tree MQ Basement Chest"] = chest_check(0x00, 0x4) + checks["Deku Tree MQ Before Spinning Log Chest"] = chest_check(0x00, 0x5) + checks["Deku Tree MQ After Spinning Log Chest"] = chest_check(0x00, 0x0) + + checks["Deku Tree MQ Deku Scrub"] = scrub_sanity_check(0x00, 0x5) + + checks["Deku Tree MQ GS Lobby"] = skulltula_check(0x0, 0x1) + checks["Deku Tree MQ GS Compass Room"] = skulltula_check(0x0, 0x3) + checks["Deku Tree MQ GS Basement Graves Room"] = skulltula_check(0x0, 0x2) + checks["Deku Tree MQ GS Basement Back Room"] = skulltula_check(0x0, 0x0) + end + + checks["Deku Tree Queen Gohma Heart"] = boss_item_check(0x11) + return checks +end + +local read_forest_temple_checks = function(mq_table_address) + local checks = {} + if not is_master_quest_dungeon(mq_table_address, 0x3) then + checks["Forest Temple First Room Chest"] = chest_check(0x3, 0x3) + checks["Forest Temple First Stalfos Chest"] = chest_check(0x3, 0x0) + checks["Forest Temple Raised Island Courtyard Chest"] = chest_check(0x3, 0x5) + checks["Forest Temple Map Chest"] = chest_check(0x3, 0x1) + checks["Forest Temple Well Chest"] = chest_check(0x3, 0x9) + checks["Forest Temple Eye Switch Chest"] = chest_check(0x3, 0x4) + checks["Forest Temple Boss Key Chest"] = chest_check(0x3, 0xE) + checks["Forest Temple Floormaster Chest"] = chest_check(0x3, 0x2) + checks["Forest Temple Red Poe Chest"] = chest_check(0x3, 0xD) + checks["Forest Temple Bow Chest"] = chest_check(0x3, 0xC) + checks["Forest Temple Blue Poe Chest"] = chest_check(0x3, 0xF) + checks["Forest Temple Falling Ceiling Room Chest"] = chest_check(0x3, 0x7) + checks["Forest Temple Basement Chest"] = chest_check(0x3, 0xB) + + checks["Forest Temple GS First Room"] = skulltula_check(0x03, 0x1) + checks["Forest Temple GS Lobby"] = skulltula_check(0x03, 0x3) + checks["Forest Temple GS Raised Island Courtyard"] = skulltula_check(0x03, 0x0) + checks["Forest Temple GS Level Island Courtyard"] = skulltula_check(0x03, 0x2) + checks["Forest Temple GS Basement"] = skulltula_check(0x03, 0x4) + else + checks["Forest Temple MQ First Room Chest"] = chest_check(0x3, 0x3) + checks["Forest Temple MQ Wolfos Chest"] = chest_check(0x3, 0x0) + checks["Forest Temple MQ Well Chest"] = chest_check(0x3, 0x9) + checks["Forest Temple MQ Raised Island Courtyard Lower Chest"] = chest_check(0x3, 0x1) + checks["Forest Temple MQ Raised Island Courtyard Upper Chest"] = chest_check(0x3, 0x5) + checks["Forest Temple MQ Boss Key Chest"] = chest_check(0x3, 0xE) + checks["Forest Temple MQ Redead Chest"] = chest_check(0x3, 0x2) + checks["Forest Temple MQ Map Chest"] = chest_check(0x3, 0xD) + checks["Forest Temple MQ Bow Chest"] = chest_check(0x3, 0xC) + checks["Forest Temple MQ Compass Chest"] = chest_check(0x3, 0xF) + checks["Forest Temple MQ Falling Ceiling Room Chest"] = chest_check(0x3, 0x6) + checks["Forest Temple MQ Basement Chest"] = chest_check(0x3, 0xB) + + checks["Forest Temple MQ GS First Hallway"] = skulltula_check(0x3, 0x1) + checks["Forest Temple MQ GS Raised Island Courtyard"] = skulltula_check(0x3, 0x0) + checks["Forest Temple MQ GS Level Island Courtyard"] = skulltula_check(0x3, 0x2) + checks["Forest Temple MQ GS Well"] = skulltula_check(0x3, 0x3) + checks["Forest Temple MQ GS Block Push Room"] = skulltula_check(0x3, 0x4) + end + + checks["Forest Temple Phantom Ganon Heart"] = boss_item_check(0x14) + return checks +end + +local read_hyrule_field_checks = function() + local checks = {} + checks["HF Ocarina of Time Item"] = event_check(0x4, 0x3) + checks["HF Near Market Grotto Chest"] = chest_check(0x3E, 0x00) + checks["HF Tektite Grotto Freestanding PoH"] = on_the_ground_check(0x3E, 0x01) + checks["HF Southeast Grotto Chest"] = chest_check(0x3E, 0x02) + checks["HF Open Grotto Chest"] = chest_check(0x3E, 0x03) + checks["HF Cow Grotto Cow"] = cow_check(0x3E, 0x19) + + -- This is the third of three deku scrubs which are always included in the item pool, not just in scrub-sanity + checks["HF Deku Scrub Grotto"] = item_get_info_check(0x0, 0x3) + if not checks["HF Deku Scrub Grotto"] then + checks["HF Deku Scrub Grotto"] = scrub_sanity_check(0x10, 0x3) + end + + checks["HF GS Cow Grotto"] = skulltula_check(0x0A, 0x0) + checks["HF GS Near Kak Grotto"] = skulltula_check(0x0A, 0x1) + return checks +end + +local read_lon_lon_ranch_checks = function() + local checks = {} + checks["LLR Talons Chickens"] = item_get_info_check(0x1, 0x2) + checks["LLR Freestanding PoH"] = on_the_ground_check(0x4C, 0x01) + checks["LLR Tower Left Cow"] = cow_check(0x4C, 0x19) + checks["LLR Tower Right Cow"] = cow_check(0x4C, 0x18) + + -- checks["Lon Lon Ranch - Epona"] = event_check(0x1, 0x8) + + checks["LLR Deku Scrub Grotto Left"] = scrub_sanity_check(0x26, 0x1) + checks["LLR Deku Scrub Grotto Center"] = scrub_sanity_check(0x26, 0x4) + checks["LLR Deku Scrub Grotto Right"] = scrub_sanity_check(0x26, 0x6) + + checks["LLR Stables Left Cow"] = cow_check(0x36, 0x18) + checks["LLR Stables Right Cow"] = cow_check(0x36, 0x19) + + checks["LLR GS House Window"] = skulltula_check(0x0B, 0x2) + checks["LLR GS Tree"] = skulltula_check(0x0B, 0x3) + checks["LLR GS Rain Shed"] = skulltula_check(0x0B, 0x1) + checks["LLR GS Back Wall"] = skulltula_check(0x0B, 0x0) + return checks +end + +--NOTE Logic has bombchus from bomchu bowling here, but it's an endless drop so it is not printed +local read_market_checks = function() + local checks = {} + checks["Market Shooting Gallery Reward"] = item_get_info_check(0x0, 0x5) + checks["Market Bombchu Bowling First Prize"] = item_get_info_check(0x3, 0x1) + checks["Market Bombchu Bowling Second Prize"] = item_get_info_check(0x3, 0x2) + checks["Market Treasure Chest Game Reward"] = item_get_info_check(0x2, 0x3) + checks["Market Lost Dog"] = info_table_check(0x33, 0x1) + checks["Market 10 Big Poes"] = big_poe_bottle_check() + checks["ToT Light Arrows Cutscene"] = event_check(0xC, 0x4) + + checks["Market GS Guard House"] = skulltula_check(0x0E, 0x3) + + checks["Market Bazaar Item 5"] = shop_check(0x4, 0x0) + checks["Market Bazaar Item 6"] = shop_check(0x4, 0x1) + checks["Market Bazaar Item 7"] = shop_check(0x4, 0x2) + checks["Market Bazaar Item 8"] = shop_check(0x4, 0x3) + + checks["Market Potion Shop Item 5"] = shop_check(0x8, 0x0) + checks["Market Potion Shop Item 6"] = shop_check(0x8, 0x1) + checks["Market Potion Shop Item 7"] = shop_check(0x8, 0x2) + checks["Market Potion Shop Item 8"] = shop_check(0x8, 0x3) + + checks["Market Bombchu Shop Item 5"] = shop_check(0x1, 0x0) + checks["Market Bombchu Shop Item 6"] = shop_check(0x1, 0x1) + checks["Market Bombchu Shop Item 7"] = shop_check(0x1, 0x2) + checks["Market Bombchu Shop Item 8"] = shop_check(0x1, 0x3) + return checks +end + +local read_hyrule_castle_checks = function() + local checks = {} + checks["HC Malon Egg"] = event_check(0x1, 0x2) + checks["HC Zeldas Letter"] = event_check(0x4, 0x0) + checks["HC Great Fairy Reward"] = item_get_info_check(0x2, 0x1) + checks["HC GS Tree"] = skulltula_check(0xE, 0x2) + checks["HC GS Storms Grotto"] = skulltula_check(0xE, 0x1) + return checks +end + +local read_kakariko_village_checks = function() + local checks = {} + checks["Kak Anju as Child"] = item_get_info_check(0x0, 0x4) + checks["Kak Anju as Adult"] = item_get_info_check(0x4, 0x4) + checks["Kak Impas House Freestanding PoH"] = on_the_ground_check(0x37, 0x1) + checks["Kak Windmill Freestanding PoH"] = on_the_ground_check(0x48, 0x1) + + checks["Kak Man on Roof"] = item_get_info_check(0x3, 0x5) + checks["Kak Open Grotto Chest"] = chest_check(0x3E, 0x08) + checks["Kak Redead Grotto Chest"] = chest_check(0x3E, 0x0A) + checks["Kak Shooting Gallery Reward"] = item_get_info_check(0x0, 0x6) + checks["Kak 10 Gold Skulltula Reward"] = event_check(0xD, 0xA) + checks["Kak 20 Gold Skulltula Reward"] = event_check(0xD, 0xB) + checks["Kak 30 Gold Skulltula Reward"] = event_check(0xD, 0xC) + checks["Kak 40 Gold Skulltula Reward"] = event_check(0xD, 0xD) + checks["Kak 50 Gold Skulltula Reward"] = event_check(0xD, 0xE) + checks["Kak Impas House Cow"] = cow_check(0x37, 0x18) + + checks["Kak GS Tree"] = skulltula_check(0x10, 0x5) + checks["Kak GS Guards House"] = skulltula_check(0x10, 0x1) + checks["Kak GS Watchtower"] = skulltula_check(0x10, 0x2) + checks["Kak GS Skulltula House"] = skulltula_check(0x10, 0x4) + checks["Kak GS House Under Construction"] = skulltula_check(0x10, 0x3) + checks["Kak GS Above Impas House"] = skulltula_check(0x10, 0x6) + + --In rando these shops contain different items from market bazaar/potion + checks["Kak Bazaar Item 5"] = shop_check(0x7, 0x0) + checks["Kak Bazaar Item 6"] = shop_check(0x7, 0x1) + checks["Kak Bazaar Item 7"] = shop_check(0x7, 0x2) + checks["Kak Bazaar Item 8"] = shop_check(0x7, 0x3) + + checks["Kak Potion Shop Item 5"] = shop_check(0x3, 0x0) + checks["Kak Potion Shop Item 6"] = shop_check(0x3, 0x1) + checks["Kak Potion Shop Item 7"] = shop_check(0x3, 0x2) + checks["Kak Potion Shop Item 8"] = shop_check(0x3, 0x3) + return checks +end + +local read_graveyard_checks = function() + local checks = {} + checks["Graveyard Shield Grave Chest"] = chest_check(0x40, 0x00) + checks["Graveyard Heart Piece Grave Chest"] = chest_check(0x3F, 0x00) + checks["Graveyard Composers Grave Chest"] = chest_check(0x41, 0x00) + checks["Graveyard Freestanding PoH"] = on_the_ground_check(0x53, 0x4) + checks["Graveyard Dampe Gravedigging Tour"] = on_the_ground_check(0x53, 0x8) + checks["Graveyard Hookshot Chest"] = chest_check(0x48, 0x00) + checks["Graveyard Dampe Race Freestanding PoH"] = on_the_ground_check(0x48, 0x7) + + checks["Graveyard GS Bean Patch"] = skulltula_check(0x10, 0x0) + checks["Graveyard GS Wall"] = skulltula_check(0x10, 0x7) + return checks +end + +local read_bottom_of_the_well_checks = function(mq_table_address) + local checks = {} + if not is_master_quest_dungeon(mq_table_address, 0x8) then + checks["Bottom of the Well Front Left Fake Wall Chest"] = chest_check(0x08, 0x08) + checks["Bottom of the Well Front Center Bombable Chest"] = chest_check(0x08, 0x02) + checks["Bottom of the Well Back Left Bombable Chest"] = chest_check(0x08, 0x04) + checks["Bottom of the Well Underwater Left Chest"] = chest_check(0x08, 0x09) + checks["Bottom of the Well Freestanding Key"] = on_the_ground_check(0x08, 0x01) + checks["Bottom of the Well Compass Chest"] = chest_check(0x08, 0x01) + checks["Bottom of the Well Center Skulltula Chest"] = chest_check(0x08, 0x0E) + checks["Bottom of the Well Right Bottom Fake Wall Chest"] = chest_check(0x08, 0x05) + checks["Bottom of the Well Fire Keese Chest"] = chest_check(0x08, 0x0A) + checks["Bottom of the Well Like Like Chest"] = chest_check(0x08, 0x0C) + checks["Bottom of the Well Map Chest"] = chest_check(0x08, 0x07) + checks["Bottom of the Well Underwater Front Chest"] = chest_check(0x08, 0x10) + checks["Bottom of the Well Invisible Chest"] = chest_check(0x08, 0x14) + checks["Bottom of the Well Lens of Truth Chest"] = chest_check(0x08, 0x03) + + checks["Bottom of the Well GS West Inner Room"] = skulltula_check(0x08, 0x2) + checks["Bottom of the Well GS East Inner Room"] = skulltula_check(0x08, 0x1) + checks["Bottom of the Well GS Like Like Cage"] = skulltula_check(0x08, 0x0) + else + checks["Bottom of the Well MQ Map Chest"] = chest_check(0x8, 0x3) + checks["Bottom of the Well MQ East Inner Room Freestanding Key"] = on_the_ground_check(0x8, 0x1) + checks["Bottom of the Well MQ Compass Chest"] = chest_check(0x8, 0x2) + checks["Bottom of the Well MQ Dead Hand Freestanding Key"] = on_the_ground_check(0x8, 0x2) + checks["Bottom of the Well MQ Lens of Truth Chest"] = chest_check(0x8, 0x1) + + checks["Bottom of the Well MQ GS Coffin Room"] = skulltula_check(0x08, 0x2) + checks["Bottom of the Well MQ GS West Inner Room"] = skulltula_check(0x08, 0x1) + checks["Bottom of the Well MQ GS Basement"] = skulltula_check(0x08, 0x0) + end + + return checks +end + +local read_shadow_temple_checks = function(mq_table_address) + local checks = {} + if not is_master_quest_dungeon(mq_table_address, 0x7) then + checks["Shadow Temple Map Chest"] = chest_check(0x07, 0x01) + checks["Shadow Temple Hover Boots Chest"] = chest_check(0x07, 0x07) + checks["Shadow Temple Compass Chest"] = chest_check(0x07, 0x03) + checks["Shadow Temple Early Silver Rupee Chest"] = chest_check(0x07, 0x02) + checks["Shadow Temple Invisible Blades Visible Chest"] = chest_check(0x07, 0x0C) + checks["Shadow Temple Invisible Blades Invisible Chest"] = chest_check(0x07, 0x16) + checks["Shadow Temple Falling Spikes Lower Chest"] = chest_check(0x07, 0x05) + checks["Shadow Temple Falling Spikes Upper Chest"] = chest_check(0x07, 0x06) + checks["Shadow Temple Falling Spikes Switch Chest"] = chest_check(0x07, 0x04) + checks["Shadow Temple Invisible Spikes Chest"] = chest_check(0x07, 0x09) + checks["Shadow Temple Freestanding Key"] = on_the_ground_check(0x07, 0x01) + checks["Shadow Temple Wind Hint Chest"] = chest_check(0x07, 0x15) + checks["Shadow Temple After Wind Enemy Chest"] = chest_check(0x07, 0x08) + checks["Shadow Temple After Wind Hidden Chest"] = chest_check(0x07, 0x14) + checks["Shadow Temple Spike Walls Left Chest"] = chest_check(0x07, 0x0A) + checks["Shadow Temple Boss Key Chest"] = chest_check(0x07, 0x0B) + checks["Shadow Temple Invisible Floormaster Chest"] = chest_check(0x07, 0x0D) + + checks["Shadow Temple GS Like Like Room"] = skulltula_check(0x07, 0x3) + checks["Shadow Temple GS Falling Spikes Room"] = skulltula_check(0x07, 0x1) + checks["Shadow Temple GS Single Giant Pot"] = skulltula_check(0x07, 0x0) + checks["Shadow Temple GS Near Ship"] = skulltula_check(0x07, 0x4) + checks["Shadow Temple GS Triple Giant Pot"] = skulltula_check(0x07, 0x2) + else + checks["Shadow Temple MQ Early Gibdos Chest"] = chest_check(0x7, 0x3) + checks["Shadow Temple MQ Map Chest"] = chest_check(0x7, 0x2) + checks["Shadow Temple MQ Near Ship Invisible Chest"] = chest_check(0x7, 0xE) + checks["Shadow Temple MQ Compass Chest"] = chest_check(0x7, 0x1) + checks["Shadow Temple MQ Hover Boots Chest"] = chest_check(0x7, 0x7) + checks["Shadow Temple MQ Invisible Blades Invisible Chest"] = chest_check(0x7, 0x16) + checks["Shadow Temple MQ Invisible Blades Visible Chest"] = chest_check(0x7, 0xC) + checks["Shadow Temple MQ Beamos Silver Rupees Chest"] = chest_check(0x7, 0xF) + checks["Shadow Temple MQ Falling Spikes Lower Chest"] = chest_check(0x7, 0x5) + checks["Shadow Temple MQ Falling Spikes Upper Chest"] = chest_check(0x7, 0x6) + checks["Shadow Temple MQ Falling Spikes Switch Chest"] = chest_check(0x7, 0x4) + checks["Shadow Temple MQ Invisible Spikes Chest"] = chest_check(0x7, 0x9) + checks["Shadow Temple MQ Stalfos Room Chest"] = chest_check(0x7, 0x10) + checks["Shadow Temple MQ Wind Hint Chest"] = chest_check(0x7, 0x15) + checks["Shadow Temple MQ After Wind Hidden Chest"] = chest_check(0x7, 0x14) + checks["Shadow Temple MQ After Wind Enemy Chest"] = chest_check(0x7, 0x8) + checks["Shadow Temple MQ Boss Key Chest"] = chest_check(0x7, 0xB) + checks["Shadow Temple MQ Spike Walls Left Chest"] = chest_check(0x7, 0xA) + checks["Shadow Temple MQ Freestanding Key"] = on_the_ground_check(0x7, 0x6) + checks["Shadow Temple MQ Bomb Flower Chest"] = chest_check(0x7, 0xD) + + checks["Shadow Temple MQ GS Falling Spikes Room"] = skulltula_check(0x7, 0x1) + checks["Shadow Temple MQ GS Wind Hint Room"] = skulltula_check(0x7, 0x0) + checks["Shadow Temple MQ GS After Wind"] = skulltula_check(0x7, 0x3) + checks["Shadow Temple MQ GS After Ship"] = skulltula_check(0x7, 0x4) + checks["Shadow Temple MQ GS Near Boss"] = skulltula_check(0x7, 0x2) + end + + checks["Shadow Temple Bongo Bongo Heart"] = boss_item_check(0x18) + return checks +end + +local read_death_mountain_trail_checks = function() + local checks = {} + checks["DMT Freestanding PoH"] = on_the_ground_check(0x60, 0x1E) + checks["DMT Chest"] = chest_check(0x60, 0x01) + checks["DMT Storms Grotto Chest"] = chest_check(0x3E, 0x17) + checks["DMT Great Fairy Reward"] = great_fairy_magic_check(0x3B, 0x18) + checks["DMT Biggoron"] = big_goron_sword_check() + checks["DMT Cow Grotto Cow"] = cow_check(0x3E, 0x18) + + checks["DMT GS Near Kak"] = skulltula_check(0x0F, 0x2) + checks["DMT GS Bean Patch"] = skulltula_check(0x0F, 0x1) + checks["DMT GS Above Dodongos Cavern"] = skulltula_check(0x0F, 0x3) + checks["DMT GS Falling Rocks Path"] = skulltula_check(0x0F, 0x4) + return checks +end + +local read_goron_city_checks = function() + local checks = {} + checks["GC Darunias Joy"] = event_check(0x3, 0x6) + checks["GC Pot Freestanding PoH"] = on_the_ground_check(0x62, 0x1F) + checks["GC Rolling Goron as Child"] = info_table_check(0x22, 0x6) + checks["GC Rolling Goron as Adult"] = info_table_check(0x20, 0x1) + checks["GC Medigoron"] = on_the_ground_check(0x62, 0x1) + checks["GC Maze Left Chest"] = chest_check(0x62, 0x00) + checks["GC Maze Right Chest"] = chest_check(0x62, 0x01) + checks["GC Maze Center Chest"] = chest_check(0x62, 0x02) + checks["GC Deku Scrub Grotto Left"] = scrub_sanity_check(0x25, 0x1) + checks["GC Deku Scrub Grotto Center"] = scrub_sanity_check(0x25, 0x4) + checks["GC Deku Scrub Grotto Right"] = scrub_sanity_check(0x25, 0x6) + checks["GC GS Center Platform"] = skulltula_check(0x0F, 0x5) + checks["GC GS Boulder Maze"] = skulltula_check(0x0F, 0x6) + + checks["GC Shop Item 5"] = shop_check(0x5, 0x0) + checks["GC Shop Item 6"] = shop_check(0x5, 0x1) + checks["GC Shop Item 7"] = shop_check(0x5, 0x2) + checks["GC Shop Item 8"] = shop_check(0x5, 0x3) + return checks +end + +local read_death_mountain_crater_checks = function() + local checks = {} + checks["DMC Volcano Freestanding PoH"] = on_the_ground_check(0x61, 0x08) + checks["DMC Wall Freestanding PoH"] = on_the_ground_check(0x61, 0x02) + checks["DMC Upper Grotto Chest"] = chest_check(0x3E, 0x1A) + checks["DMC Great Fairy Reward"] = great_fairy_magic_check(0x3B, 0x10) + + checks["DMC Deku Scrub"] = scrub_sanity_check(0x61, 0x6) + checks["DMC Deku Scrub Grotto Left"] = scrub_sanity_check(0x23, 0x1) + checks["DMC Deku Scrub Grotto Center"] = scrub_sanity_check(0x23, 0x4) + checks["DMC Deku Scrub Grotto Right"] = scrub_sanity_check(0x23, 0x6) + + checks["DMC GS Crate"] = skulltula_check(0x0F, 0x7) + checks["DMC GS Bean Patch"] = skulltula_check(0x0F, 0x0) + return checks +end + +local read_dodongos_cavern_checks = function(mq_table_address) + local checks = {} + if not is_master_quest_dungeon(mq_table_address, 0x1) then + checks["Dodongos Cavern Map Chest"] = chest_check(0x01, 0x8) + checks["Dodongos Cavern Compass Chest"] = chest_check(0x01, 0x5) + checks["Dodongos Cavern Bomb Flower Platform Chest"] = chest_check(0x01, 0x6) + checks["Dodongos Cavern Bomb Bag Chest"] = chest_check(0x01, 0x4) + checks["Dodongos Cavern End of Bridge Chest"] = chest_check(0x01, 0xA) + + checks["Dodongos Cavern Deku Scrub Lobby"] = scrub_sanity_check(0x1, 0x5) + checks["Dodongos Cavern Deku Scrub Side Room Near Dodongos"] = scrub_sanity_check(0x1, 0x2) + checks["Dodongos Cavern Deku Scrub Near Bomb Bag Left"] = scrub_sanity_check(0x1, 0x1) + checks["Dodongos Cavern Deku Scrub Near Bomb Bag Right"] = scrub_sanity_check(0x1, 0x4) + + checks["Dodongos Cavern GS Side Room Near Lower Lizalfos"] = skulltula_check(0x01, 0x4) + checks["Dodongos Cavern GS Scarecrow"] = skulltula_check(0x01, 0x1) + checks["Dodongos Cavern GS Alcove Above Stairs"] = skulltula_check(0x01, 0x2) + checks["Dodongos Cavern GS Vines Above Stairs"] = skulltula_check(0x01, 0x0) + checks["Dodongos Cavern GS Back Room"] = skulltula_check(0x01, 0x3) + else + checks["Dodongos Cavern MQ Map Chest"] = chest_check(0x1, 0x0) + checks["Dodongos Cavern MQ Bomb Bag Chest"] = chest_check(0x1, 0x4) + checks["Dodongos Cavern MQ Torch Puzzle Room Chest"] = chest_check(0x1, 0x3) + checks["Dodongos Cavern MQ Larvae Room Chest"] = chest_check(0x1, 0x2) + checks["Dodongos Cavern MQ Compass Chest"] = chest_check(0x1, 0x5) + checks["Dodongos Cavern MQ Under Grave Chest"] = chest_check(0x1, 0x1) + + checks["Dodongos Cavern MQ Deku Scrub Lobby Front"] = scrub_sanity_check(0x1, 0x4) + checks["Dodongos Cavern MQ Deku Scrub Lobby Rear"] = scrub_sanity_check(0x1, 0x2) + checks["Dodongos Cavern MQ Deku Scrub Side Room Near Lower Lizalfos"] = scrub_sanity_check(0x1, 0x8) + checks["Dodongos Cavern MQ Deku Scrub Staircase"] = scrub_sanity_check(0x1, 0x5) + + checks["Dodongos Cavern MQ GS Scrub Room"] = skulltula_check(0x1, 0x1) + checks["Dodongos Cavern MQ GS Larvae Room"] = skulltula_check(0x1, 0x4) + checks["Dodongos Cavern MQ GS Lizalfos Room"] = skulltula_check(0x1, 0x2) + checks["Dodongos Cavern MQ GS Song of Time Block Room"] = skulltula_check(0x1, 0x3) + checks["Dodongos Cavern MQ GS Back Area"] = skulltula_check(0x1, 0x0) + end + + -- Both of these are shared between vanilla and MQ + checks["Dodongos Cavern Boss Room Chest"] = chest_check(0x12, 0x0) + checks["Dodongos Cavern King Dodongo Heart"] = boss_item_check(0x12) + return checks +end + +local read_fire_temple_checks = function(mq_table_address) + local checks = {} + if not is_master_quest_dungeon(mq_table_address, 0x4) then + checks["Fire Temple Near Boss Chest"] = chest_check(0x04, 0x01) + checks["Fire Temple Flare Dancer Chest"] = chest_check(0x04, 0x00) + checks["Fire Temple Boss Key Chest"] = chest_check(0x04, 0x0C) + checks["Fire Temple Big Lava Room Lower Open Door Chest"] = chest_check(0x04, 0x04) + checks["Fire Temple Big Lava Room Blocked Door Chest"] = chest_check(0x04, 0x02) + checks["Fire Temple Boulder Maze Lower Chest"] = chest_check(0x04, 0x03) + checks["Fire Temple Boulder Maze Side Room Chest"] = chest_check(0x04, 0x08) + checks["Fire Temple Map Chest"] = chest_check(0x04, 0x0A) + checks["Fire Temple Boulder Maze Shortcut Chest"] = chest_check(0x04, 0x0B) + checks["Fire Temple Boulder Maze Upper Chest"] = chest_check(0x04, 0x06) + checks["Fire Temple Scarecrow Chest"] = chest_check(0x04, 0x0D) + checks["Fire Temple Compass Chest"] = chest_check(0x04, 0x07) + checks["Fire Temple Megaton Hammer Chest"] = chest_check(0x04, 0x05) + checks["Fire Temple Highest Goron Chest"] = chest_check(0x04, 0x09) + + checks["Fire Temple GS Boss Key Loop"] = skulltula_check(0x04, 0x1) + checks["Fire Temple GS Song of Time Room"] = skulltula_check(0x04, 0x0) + checks["Fire Temple GS Boulder Maze"] = skulltula_check(0x04, 0x2) + checks["Fire Temple GS Scarecrow Climb"] = skulltula_check(0x04, 0x4) + checks["Fire Temple GS Scarecrow Top"] = skulltula_check(0x04, 0x3) + else + checks["Fire Temple MQ Map Room Side Chest"] = chest_check(0x4, 0x2) + checks["Fire Temple MQ Megaton Hammer Chest"] = chest_check(0x4, 0x0) + checks["Fire Temple MQ Map Chest"] = chest_check(0x4, 0xC) + checks["Fire Temple MQ Near Boss Chest"] = chest_check(0x4, 0x7) + checks["Fire Temple MQ Big Lava Room Blocked Door Chest"] = chest_check(0x4, 0x1) + checks["Fire Temple MQ Boss Key Chest"] = chest_check(0x4, 0x4) + checks["Fire Temple MQ Lizalfos Maze Side Room Chest"] = chest_check(0x4, 0x8) + checks["Fire Temple MQ Compass Chest"] = chest_check(0x4, 0xB) + checks["Fire Temple MQ Lizalfos Maze Upper Chest"] = chest_check(0x4, 0x6) + checks["Fire Temple MQ Lizalfos Maze Lower Chest"] = chest_check(0x4, 0x3) + checks["Fire Temple MQ Freestanding Key"] = on_the_ground_check(0x4, 0x1C) + checks["Fire Temple MQ Chest On Fire"] = chest_check(0x4, 0x5) + + checks["Fire Temple MQ GS Big Lava Room Open Door"] = skulltula_check(0x4, 0x0) + checks["Fire Temple MQ GS Skull On Fire"] = skulltula_check(0x4, 0x2) + checks["Fire Temple MQ GS Fire Wall Maze Center"] = skulltula_check(0x4, 0x3) + checks["Fire Temple MQ GS Fire Wall Maze Side Room"] = skulltula_check(0x4, 0x4) + checks["Fire Temple MQ GS Above Fire Wall Maze"] = skulltula_check(0x4, 0x1) + end + + checks["Fire Temple Volvagia Heart"] = boss_item_check(0x15) + return checks +end + +local read_zoras_river_checks = function() + local checks = {} + checks["ZR Magic Bean Salesman"] = bean_sale_check(0x54, 0x1) + checks["ZR Open Grotto Chest"] = chest_check(0x3E, 0x09) + checks["ZR Frogs in the Rain"] = event_check(0xD, 0x6) + checks["ZR Frogs Ocarina Game"] = event_check(0xD, 0x0) + checks["ZR Near Open Grotto Freestanding PoH"] = on_the_ground_check(0x54, 0x04) + checks["ZR Near Domain Freestanding PoH"] = on_the_ground_check(0x54, 0x0B) + checks["ZR Deku Scrub Grotto Front"] = scrub_sanity_check(0x15, 0x9) + checks["ZR Deku Scrub Grotto Rear"] = scrub_sanity_check(0x15, 0x8) + + checks["ZR GS Tree"] = skulltula_check(0x11, 0x1) + --NOTE: There is no GS in the soft soil. It's the only one that doesn't have one. + checks["ZR GS Ladder"] = skulltula_check(0x11, 0x0) + checks["ZR GS Near Raised Grottos"] = skulltula_check(0x11, 0x4) + checks["ZR GS Above Bridge"] = skulltula_check(0x11, 0x3) + return checks +end + +local read_zoras_domain_checks = function() + local checks = {} + checks["ZD Diving Minigame"] = event_check(0x3, 0x8) + checks["ZD Chest"] = chest_check(0x58, 0x00) + checks["ZD King Zora Thawed"] = info_table_check(0x26, 0x1) + checks["ZD GS Frozen Waterfall"] = skulltula_check(0x11, 0x6) + + checks["ZD Shop Item 5"] = shop_check(0x2, 0x0) + checks["ZD Shop Item 6"] = shop_check(0x2, 0x1) + checks["ZD Shop Item 7"] = shop_check(0x2, 0x2) + checks["ZD Shop Item 8"] = shop_check(0x2, 0x3) + return checks +end + +local read_zoras_fountain_checks = function() + local checks = {} + checks["ZF Great Fairy Reward"] = item_get_info_check(0x2, 0x0) + checks["ZF Iceberg Freestanding PoH"] = on_the_ground_check(0x59, 0x01) + checks["ZF Bottom Freestanding PoH"] = on_the_ground_check(0x59, 0x14) + checks["ZF GS Above the Log"] = skulltula_check(0x11, 0x2) + checks["ZF GS Tree"] = skulltula_check(0x11, 0x7) + checks["ZF GS Hidden Cave"] = skulltula_check(0x11, 0x5) + return checks +end + +local read_jabu_checks = function(mq_table_address) + local checks = {} + if not is_master_quest_dungeon(mq_table_address, 0x2) then + checks["Jabu Jabus Belly Boomerang Chest"] = chest_check(0x02, 0x01) + checks["Jabu Jabus Belly Map Chest"] = chest_check(0x02, 0x02) + checks["Jabu Jabus Belly Compass Chest"] = chest_check(0x02, 0x04) + checks["Jabu Jabus Belly Deku Scrub"] = scrub_sanity_check(0x02, 0x1) + checks["Jabu Jabus Belly GS Water Switch Room"] = skulltula_check(0x02, 0x3) + checks["Jabu Jabus Belly GS Lobby Basement Lower"] = skulltula_check(0x02, 0x0) + checks["Jabu Jabus Belly GS Lobby Basement Upper"] = skulltula_check(0x02, 0x1) + checks["Jabu Jabus Belly GS Near Boss"] = skulltula_check(0x02, 0x2) + else + checks["Jabu Jabus Belly MQ Map Chest"] = chest_check(0x2, 0x3) + checks["Jabu Jabus Belly MQ First Room Side Chest"] = chest_check(0x2, 0x5) + checks["Jabu Jabus Belly MQ Second Room Lower Chest"] = chest_check(0x2, 0x2) + checks["Jabu Jabus Belly MQ Compass Chest"] = chest_check(0x2, 0x0) + checks["Jabu Jabus Belly MQ Basement Near Switches Chest"] = chest_check(0x2, 0x8) + checks["Jabu Jabus Belly MQ Basement Near Vines Chest"] = chest_check(0x2, 0x4) + checks["Jabu Jabus Belly MQ Boomerang Room Small Chest"] = chest_check(0x2, 0x1) + checks["Jabu Jabus Belly MQ Boomerang Chest"] = chest_check(0x2, 0x6) + checks["Jabu Jabus Belly MQ Falling Like Like Room Chest"] = chest_check(0x2, 0x9) + checks["Jabu Jabus Belly MQ Second Room Upper Chest"] = chest_check(0x2, 0x7) + checks["Jabu Jabus Belly MQ Near Boss Chest"] = chest_check(0x2, 0xA) + + checks["Jabu Jabus Belly MQ Cow"] = cow_check(0x2, 0x18) + + checks["Jabu Jabus Belly MQ GS Boomerang Chest Room"] = skulltula_check(0x2, 0x0) + checks["Jabu Jabus Belly MQ GS Tailpasaran Room"] = skulltula_check(0x2, 0x2) + checks["Jabu Jabus Belly MQ GS Invisible Enemies Room"] = skulltula_check(0x2, 0x3) + checks["Jabu Jabus Belly MQ GS Near Boss"] = skulltula_check(0x2, 0x1) + end + + checks["Jabu Jabus Belly Barinade Heart"] = boss_item_check(0x13) + return checks +end + +local read_ice_cavern_checks = function(mq_table_address) + local checks = {} + if not is_master_quest_dungeon(mq_table_address, 0x9) then + checks["Ice Cavern Map Chest"] = chest_check(0x09, 0x00) + checks["Ice Cavern Compass Chest"] = chest_check(0x09, 0x01) + checks["Ice Cavern Freestanding PoH"] = on_the_ground_check(0x09, 0x01) + checks["Ice Cavern Iron Boots Chest"] = chest_check(0x09, 0x02) + checks["Ice Cavern GS Spinning Scythe Room"] = skulltula_check(0x09, 0x1) + checks["Ice Cavern GS Heart Piece Room"] = skulltula_check(0x09, 0x2) + checks["Ice Cavern GS Push Block Room"] = skulltula_check(0x09, 0x0) + else + checks["Ice Cavern MQ Map Chest"] = chest_check(0x09, 0x01) + checks["Ice Cavern MQ Compass Chest"] = chest_check(0x09, 0x00) + checks["Ice Cavern MQ Freestanding PoH"] = on_the_ground_check(0x09, 0x01) + checks["Ice Cavern MQ Iron Boots Chest"] = chest_check(0x09, 0x02) + + checks["Ice Cavern MQ GS Red Ice"] = skulltula_check(0x09, 0x1) + checks["Ice Cavern MQ GS Ice Block"] = skulltula_check(0x09, 0x2) + checks["Ice Cavern MQ GS Scarecrow"] = skulltula_check(0x09, 0x0) + end + return checks +end + +local read_lake_hylia_checks = function() + local checks = {} + checks["LH Underwater Item"] = event_check(0x3, 0x1) + checks["LH Child Fishing"] = fishing_check(false) + checks["LH Adult Fishing"] = fishing_check(true) + checks["LH Lab Dive"] = item_get_info_check(0x3, 0x0) + checks["LH Freestanding PoH"] = on_the_ground_check(0x57, 0x1E) + --It's not actually a chest, but it is marked in the chest section + checks["LH Sun"] = fire_arrows_check(0x57, 0x0) + checks["LH Deku Scrub Grotto Left"] = scrub_sanity_check(0x19, 0x1) + checks["LH Deku Scrub Grotto Center"] = scrub_sanity_check(0x19, 0x4) + checks["LH Deku Scrub Grotto Right"] = scrub_sanity_check(0x19, 0x6) + + checks["LH GS Lab Wall"] = skulltula_check(0x12, 0x2) + checks["LH GS Bean Patch"] = skulltula_check(0x12, 0x0) + checks["LH GS Small Island"] = skulltula_check(0x12, 0x1) + checks["LH GS Lab Crate"] = skulltula_check(0x12, 0x3) + checks["LH GS Tree"] = skulltula_check(0x12, 0x4) + return checks +end + +local read_water_temple_checks = function(mq_table_address) + local checks = {} + if not is_master_quest_dungeon(mq_table_address, 0x5) then + checks["Water Temple Compass Chest"] = chest_check(0x05, 0x09) + checks["Water Temple Map Chest"] = chest_check(0x05, 0x02) + checks["Water Temple Cracked Wall Chest"] = chest_check(0x05, 0x00) + checks["Water Temple Torches Chest"] = chest_check(0x05, 0x01) + checks["Water Temple Boss Key Chest"] = chest_check(0x05, 0x05) + checks["Water Temple Central Pillar Chest"] = chest_check(0x05, 0x06) + checks["Water Temple Central Bow Target Chest"] = chest_check(0x05, 0x08) + checks["Water Temple Longshot Chest"] = chest_check(0x05, 0x07) + checks["Water Temple River Chest"] = chest_check(0x05, 0x03) + checks["Water Temple Dragon Chest"] = chest_check(0x05, 0x0A) + + checks["Water Temple GS Behind Gate"] = skulltula_check(0x05, 0x0) + checks["Water Temple GS Near Boss Key Chest"] = skulltula_check(0x05, 0x3) + checks["Water Temple GS Central Pillar"] = skulltula_check(0x05, 0x2) + checks["Water Temple GS Falling Platform Room"] = skulltula_check(0x05, 0x1) + checks["Water Temple GS River"] = skulltula_check(0x05, 0x4) + else + checks["Water Temple MQ Longshot Chest"] = chest_check(0x5, 0x0) + checks["Water Temple MQ Map Chest"] = chest_check(0x5, 0x2) + checks["Water Temple MQ Compass Chest"] = chest_check(0x5, 0x1) + checks["Water Temple MQ Central Pillar Chest"] = chest_check(0x5, 0x6) + checks["Water Temple MQ Boss Key Chest"] = chest_check(0x5, 0x5) + checks["Water Temple MQ Freestanding Key"] = on_the_ground_check(0x5, 0x1) + + checks["Water Temple MQ GS Lizalfos Hallway"] = skulltula_check(0x5, 0x0) + checks["Water Temple MQ GS Before Upper Water Switch"] = skulltula_check(0x5, 0x2) + checks["Water Temple MQ GS River"] = skulltula_check(0x5, 0x1) + checks["Water Temple MQ GS Freestanding Key Area"] = skulltula_check(0x5, 0x3) + checks["Water Temple MQ GS Triple Wall Torch"] = skulltula_check(0x5, 0x4) + end + + checks["Water Temple Morpha Heart"] = boss_item_check(0x16) + return checks +end + +local read_gerudo_valley_checks = function() + local checks = {} + checks["GV Crate Freestanding PoH"] = on_the_ground_check(0x5A, 0x2) + checks["GV Waterfall Freestanding PoH"] = on_the_ground_check(0x5A, 0x1) + checks["GV Chest"] = chest_check(0x5A, 0x00) + checks["GV Deku Scrub Grotto Front"] = scrub_sanity_check(0x1A, 0x9) + checks["GV Deku Scrub Grotto Rear"] = scrub_sanity_check(0x1A, 0x8) + checks["GV Cow"] = cow_check(0x5A, 0x18) + + checks["GV GS Small Bridge"] = skulltula_check(0x13, 0x1) + checks["GV GS Bean Patch"] = skulltula_check(0x13, 0x0) + checks["GV GS Behind Tent"] = skulltula_check(0x13, 0x3) + checks["GV GS Pillar"] = skulltula_check(0x13, 0x2) + return checks +end + +local read_gerudo_fortress_checks = function() + local checks = {} + checks["GF North F1 Carpenter"] = on_the_ground_check(0xC, 0xC) + checks["GF North F2 Carpenter"] = on_the_ground_check(0xC, 0xA) + checks["GF South F1 Carpenter"] = on_the_ground_check(0xC, 0xE) + checks["GF South F2 Carpenter"] = on_the_ground_check(0xC, 0xF) + checks["GF Gerudo Membership Card"] = membership_card_check(0xC, 0x2) + checks["GF Chest"] = chest_check(0x5D, 0x0) + checks["GF HBA 1000 Points"] = info_table_check(0x33, 0x0) + checks["GF HBA 1500 Points"] = item_get_info_check(0x0, 0x7) + checks["GF GS Top Floor"] = skulltula_check(0x14, 0x1) + checks["GF GS Archery Range"] = skulltula_check(0x14, 0x0) + return checks +end + +local read_gerudo_training_ground_checks = function(mq_table_address) + local checks = {} + if not is_master_quest_dungeon(mq_table_address, 0xB) then + checks["Gerudo Training Grounds Lobby Left Chest"] = chest_check(0x0B, 0x13) + checks["Gerudo Training Grounds Lobby Right Chest"] = chest_check(0x0B, 0x07) + checks["Gerudo Training Grounds Stalfos Chest"] = chest_check(0x0B, 0x00) + checks["Gerudo Training Grounds Before Heavy Block Chest"] = chest_check(0x0B, 0x11) + checks["Gerudo Training Grounds Heavy Block First Chest"] = chest_check(0x0B, 0x0F) + checks["Gerudo Training Grounds Heavy Block Second Chest"] = chest_check(0x0B, 0x0E) + checks["Gerudo Training Grounds Heavy Block Third Chest"] = chest_check(0x0B, 0x14) + checks["Gerudo Training Grounds Heavy Block Fourth Chest"] = chest_check(0x0B, 0x02) + checks["Gerudo Training Grounds Eye Statue Chest"] = chest_check(0x0B, 0x03) + checks["Gerudo Training Grounds Near Scarecrow Chest"] = chest_check(0x0B, 0x04) + checks["Gerudo Training Grounds Hammer Room Clear Chest"] = chest_check(0x0B, 0x12) + checks["Gerudo Training Grounds Hammer Room Switch Chest"] = chest_check(0x0B, 0x10) + checks["Gerudo Training Grounds Freestanding Key"] = on_the_ground_check(0x0B, 0x1) + checks["Gerudo Training Grounds Maze Right Central Chest"] = chest_check(0x0B, 0x05) + checks["Gerudo Training Grounds Maze Right Side Chest"] = chest_check(0x0B, 0x08) + checks["Gerudo Training Grounds Underwater Silver Rupee Chest"] = chest_check(0x0B, 0x0D) + checks["Gerudo Training Grounds Beamos Chest"] = chest_check(0x0B, 0x01) + checks["Gerudo Training Grounds Hidden Ceiling Chest"] = chest_check(0x0B, 0x0B) + checks["Gerudo Training Grounds Maze Path First Chest"] = chest_check(0x0B, 0x06) + checks["Gerudo Training Grounds Maze Path Second Chest"] = chest_check(0x0B, 0x0A) + checks["Gerudo Training Grounds Maze Path Third Chest"] = chest_check(0x0B, 0x09) + checks["Gerudo Training Grounds Maze Path Final Chest"] = chest_check(0x0B, 0x0C) + else + checks["Gerudo Training Grounds MQ Lobby Left Chest"] = chest_check(0xB, 0x13) + checks["Gerudo Training Grounds MQ Lobby Right Chest"] = chest_check(0xB, 0x7) + checks["Gerudo Training Grounds MQ First Iron Knuckle Chest"] = chest_check(0xB, 0x0) + checks["Gerudo Training Grounds MQ Before Heavy Block Chest"] = chest_check(0xB, 0x11) + checks["Gerudo Training Grounds MQ Heavy Block Chest"] = chest_check(0xB, 0x2) + checks["Gerudo Training Grounds MQ Eye Statue Chest"] = chest_check(0xB, 0x3) + checks["Gerudo Training Grounds MQ Ice Arrows Chest"] = chest_check(0xB, 0x4) + checks["Gerudo Training Grounds MQ Second Iron Knuckle Chest"] = chest_check(0xB, 0x12) + checks["Gerudo Training Grounds MQ Flame Circle Chest"] = chest_check(0xB, 0xE) + checks["Gerudo Training Grounds MQ Maze Right Central Chest"] = chest_check(0xB, 0x5) + checks["Gerudo Training Grounds MQ Maze Right Side Chest"] = chest_check(0xB, 0x8) + checks["Gerudo Training Grounds MQ Underwater Silver Rupee Chest"] = chest_check(0xB, 0xD) + checks["Gerudo Training Grounds MQ Dinolfos Chest"] = chest_check(0xB, 0x1) + checks["Gerudo Training Grounds MQ Hidden Ceiling Chest"] = chest_check(0xB, 0xB) + checks["Gerudo Training Grounds MQ Maze Path First Chest"] = chest_check(0xB, 0x6) + checks["Gerudo Training Grounds MQ Maze Path Third Chest"] = chest_check(0xB, 0x9) + checks["Gerudo Training Grounds MQ Maze Path Second Chest"] = chest_check(0xB, 0xA) + end + return checks +end + +local read_haunted_wasteland_checks = function() + local checks = {} + checks["Wasteland Bombchu Salesman"] = on_the_ground_check(0x5E, 0x01) + checks["Wasteland Chest"] = chest_check(0x5E, 0x00) + checks["Wasteland GS"] = skulltula_check(0x15, 0x1) + return checks +end + +local read_desert_colossus_checks = function() + local checks = {} + checks["Colossus Great Fairy Reward"] = item_get_info_check(0x2, 0x2) + checks["Colossus Freestanding PoH"] = on_the_ground_check(0x5C, 0xD) + checks["Colossus Deku Scrub Grotto Front"] = scrub_sanity_check(0x27, 0x9) + checks["Colossus Deku Scrub Grotto Rear"] = scrub_sanity_check(0x27, 0x8) + + checks["Colossus GS Bean Patch"] = skulltula_check(0x15, 0x0) + checks["Colossus GS Tree"] = skulltula_check(0x15, 0x3) + checks["Colossus GS Hill"] = skulltula_check(0x15, 0x2) + return checks +end + +local read_spirit_temple_checks = function(mq_table_address) + local checks = {} + if not is_master_quest_dungeon(mq_table_address, 0x6) then + checks["Spirit Temple Child Bridge Chest"] = chest_check(0x06, 0x08) + checks["Spirit Temple Child Early Torches Chest"] = chest_check(0x06, 0x00) + checks["Spirit Temple Child Climb North Chest"] = chest_check(0x06, 0x06) + checks["Spirit Temple Child Climb East Chest"] = chest_check(0x06, 0x0C) + checks["Spirit Temple Map Chest"] = chest_check(0x06, 0x03) + checks["Spirit Temple Sun Block Room Chest"] = chest_check(0x06, 0x01) + checks["Spirit Temple Silver Gauntlets Chest"] = chest_check(0x5C, 0x0B) + + checks["Spirit Temple Compass Chest"] = chest_check(0x06, 0x04) + checks["Spirit Temple Early Adult Right Chest"] = chest_check(0x06, 0x07) + checks["Spirit Temple First Mirror Left Chest"] = chest_check(0x06, 0x0D) + checks["Spirit Temple First Mirror Right Chest"] = chest_check(0x06, 0x0E) + checks["Spirit Temple Statue Room Northeast Chest"] = chest_check(0x06, 0x0F) + checks["Spirit Temple Statue Room Hand Chest"] = chest_check(0x06, 0x02) + checks["Spirit Temple Near Four Armos Chest"] = chest_check(0x06, 0x05) + checks["Spirit Temple Hallway Right Invisible Chest"] = chest_check(0x06, 0x14) + checks["Spirit Temple Hallway Left Invisible Chest"] = chest_check(0x06, 0x15) + checks["Spirit Temple Mirror Shield Chest"] = chest_check(0x5C, 0x09) + + checks["Spirit Temple Boss Key Chest"] = chest_check(0x06, 0x0A) + checks["Spirit Temple Topmost Chest"] = chest_check(0x06, 0x12) + + checks["Spirit Temple GS Metal Fence"] = skulltula_check(0x06, 0x4) + checks["Spirit Temple GS Sun on Floor Room"] = skulltula_check(0x06, 0x3) + checks["Spirit Temple GS Hall After Sun Block Room"] = skulltula_check(0x06, 0x0) + checks["Spirit Temple GS Lobby"] = skulltula_check(0x06, 0x2) + checks["Spirit Temple GS Boulder Room"] = skulltula_check(0x06, 0x1) + else + checks["Spirit Temple MQ Entrance Front Left Chest"] = chest_check(0x6, 0x1A) + checks["Spirit Temple MQ Entrance Back Right Chest"] = chest_check(0x6, 0x1F) + checks["Spirit Temple MQ Entrance Front Right Chest"] = chest_check(0x6, 0x1B) + checks["Spirit Temple MQ Entrance Back Left Chest"] = chest_check(0x6, 0x1E) + checks["Spirit Temple MQ Map Chest"] = chest_check(0x6, 0x0) + checks["Spirit Temple MQ Map Room Enemy Chest"] = chest_check(0x6, 0x8) + checks["Spirit Temple MQ Child Climb North Chest"] = chest_check(0x6, 0x6) + checks["Spirit Temple MQ Child Climb South Chest"] = chest_check(0x6, 0xC) + checks["Spirit Temple MQ Compass Chest"] = chest_check(0x6, 0x3) + checks["Spirit Temple MQ Silver Block Hallway Chest"] = chest_check(0x6, 0x1C) + checks["Spirit Temple MQ Sun Block Room Chest"] = chest_check(0x6, 0x1) + checks["Spirit Temple Silver Gauntlets Chest"] = chest_check(0x5C, 0xB) + + checks["Spirit Temple MQ Child Hammer Switch Chest"] = chest_check(0x6, 0x1D) + checks["Spirit Temple MQ Statue Room Lullaby Chest"] = chest_check(0x6, 0xF) + checks["Spirit Temple MQ Statue Room Invisible Chest"] = chest_check(0x6, 0x2) + checks["Spirit Temple MQ Leever Room Chest"] = chest_check(0x6, 0x4) + checks["Spirit Temple MQ Symphony Room Chest"] = chest_check(0x6, 0x7) + checks["Spirit Temple MQ Beamos Room Chest"] = chest_check(0x6, 0x19) + checks["Spirit Temple MQ Chest Switch Chest"] = chest_check(0x6, 0x18) + checks["Spirit Temple MQ Boss Key Chest"] = chest_check(0x6, 0x5) + checks["Spirit Temple Mirror Shield Chest"] = chest_check(0x5C, 0x9) + checks["Spirit Temple MQ Mirror Puzzle Invisible Chest"] = chest_check(0x6, 0x12) + + checks["Spirit Temple MQ GS Sun Block Room"] = skulltula_check(0x6, 0x0) + checks["Spirit Temple MQ GS Leever Room"] = skulltula_check(0x6, 0x1) + checks["Spirit Temple MQ GS Symphony Room"] = skulltula_check(0x6, 0x3) + checks["Spirit Temple MQ GS Nine Thrones Room West"] = skulltula_check(0x6, 0x2) + checks["Spirit Temple MQ GS Nine Thrones Room North"] = skulltula_check(0x6, 0x4) + end + + checks["Spirit Temple Twinrova Heart"] = boss_item_check(0x17) + return checks +end + +local read_ganons_castle_checks = function(mq_table_address) + local checks = {} + if not is_master_quest_dungeon(mq_table_address, 0xD) then + checks["Ganons Castle Forest Trial Chest"] = chest_check(0x0D, 0x09) + checks["Ganons Castle Water Trial Left Chest"] = chest_check(0x0D, 0x07) + checks["Ganons Castle Water Trial Right Chest"] = chest_check(0x0D, 0x06) + checks["Ganons Castle Shadow Trial Front Chest"] = chest_check(0x0D, 0x08) + checks["Ganons Castle Shadow Trial Golden Gauntlets Chest"] = chest_check(0x0D, 0x05) + checks["Ganons Castle Light Trial First Left Chest"] = chest_check(0x0D, 0x0C) + checks["Ganons Castle Light Trial Second Left Chest"] = chest_check(0x0D, 0x0B) + checks["Ganons Castle Light Trial Third Left Chest"] = chest_check(0x0D, 0x0D) + checks["Ganons Castle Light Trial First Right Chest"] = chest_check(0x0D, 0x0E) + checks["Ganons Castle Light Trial Second Right Chest"] = chest_check(0x0D, 0x0A) + checks["Ganons Castle Light Trial Third Right Chest"] = chest_check(0x0D, 0x0F) + checks["Ganons Castle Light Trial Invisible Enemies Chest"] = chest_check(0x0D, 0x10) + checks["Ganons Castle Light Trial Lullaby Chest"] = chest_check(0x0D, 0x11) + checks["Ganons Castle Spirit Trial Crystal Switch Chest"] = chest_check(0x0D, 0x12) + checks["Ganons Castle Spirit Trial Invisible Chest"] = chest_check(0x0D, 0x14) + + checks["Ganons Castle Deku Scrub Left"] = scrub_sanity_check(0xD, 0x9) + checks["Ganons Castle Deku Scrub Center-Left"] = scrub_sanity_check(0xD, 0x6) + checks["Ganons Castle Deku Scrub Center-Right"] = scrub_sanity_check(0xD, 0x4) + checks["Ganons Castle Deku Scrub Right"] = scrub_sanity_check(0xD, 0x8) + else + checks["Ganons Castle MQ Forest Trial Freestanding Key"] = on_the_ground_check(0xD, 0x1) + checks["Ganons Castle MQ Forest Trial Eye Switch Chest"] = chest_check(0xD, 0x2) + checks["Ganons Castle MQ Forest Trial Frozen Eye Switch Chest"] = chest_check(0xD, 0x3) + checks["Ganons Castle MQ Water Trial Chest"] = chest_check(0xD, 0x1) + checks["Ganons Castle MQ Shadow Trial Bomb Flower Chest"] = chest_check(0xD, 0x0) + checks["Ganons Castle MQ Shadow Trial Eye Switch Chest"] = chest_check(0xD, 0x5) + checks["Ganons Castle MQ Light Trial Lullaby Chest"] = chest_check(0xD, 0x4) + checks["Ganons Castle MQ Spirit Trial First Chest"] = chest_check(0xD, 0xA) + checks["Ganons Castle MQ Spirit Trial Invisible Chest"] = chest_check(0xD, 0x14) + checks["Ganons Castle MQ Spirit Trial Sun Front Left Chest"] = chest_check(0xD, 0x9) + checks["Ganons Castle MQ Spirit Trial Sun Back Left Chest"] = chest_check(0xD, 0x8) + checks["Ganons Castle MQ Spirit Trial Sun Back Right Chest"] = chest_check(0xD, 0x7) + checks["Ganons Castle MQ Spirit Trial Golden Gauntlets Chest"] = chest_check(0xD, 0x6) + + checks["Ganons Castle MQ Deku Scrub Left"] = scrub_sanity_check(0xD, 0x9) + checks["Ganons Castle MQ Deku Scrub Center-Left"] = scrub_sanity_check(0xD, 0x6) + checks["Ganons Castle MQ Deku Scrub Center"] = scrub_sanity_check(0xD, 0x4) + checks["Ganons Castle MQ Deku Scrub Center-Right"] = scrub_sanity_check(0xD, 0x8) + checks["Ganons Castle MQ Deku Scrub Right"] = scrub_sanity_check(0xD, 0x1) + end + + checks["Ganons Tower Boss Key Chest"] = chest_check(0x0A, 0x0B) + return checks +end + +local read_outside_ganons_castle_checks = function() + local checks = {} + checks["OGC Great Fairy Reward"] = great_fairy_magic_check(0x3B, 0x8) + checks["OGC GS"] = skulltula_check(0x0E, 0x0) + return checks +end + +local read_song_checks = function() + local checks = {} + checks["Song from Impa"] = event_check(0x5, 0x9) -- Zelda's Lullaby + checks["Song from Malon"] = event_check(0x5, 0x8) -- Epona's Song + checks["Song from Saria"] = event_check(0x5, 0x7) -- Saria's Song + checks["Song from Composers Grave"] = event_check(0x5, 0xA) -- Sun's Song + checks["Song from Ocarina of Time"] = event_check(0xA, 0x9) -- Song of Time + checks["Song from Windmill"] = event_check(0x5, 0xB) -- Song of Storms + checks["Sheik in Forest"] = event_check(0x5, 0x0) -- Minuet of Forest + checks["Sheik in Crater"] = event_check(0x5, 0x1) -- Bolero of Fire + checks["Sheik in Ice Cavern"] = event_check(0x5, 0x2) -- Serenade of Water + checks["Sheik at Colossus"] = event_check(0xA, 0xC) -- Requiem of Spirit + checks["Sheik in Kakariko"] = event_check(0x5, 0x4) -- Nocturne of Shadows + checks["Sheik at Temple"] = event_check(0x5, 0x5) -- Prelude of Light + return checks +end + +local check_all_locations = function(mq_table_address) +-- TODO: make MQ better + local location_checks = {} + temp_context = mainmemory.readbyterange(0x40002C, 4) + for k,v in pairs(read_kokiri_forest_checks()) do location_checks[k] = v end + for k,v in pairs(read_lost_woods_checks()) do location_checks[k] = v end + for k,v in pairs(read_sacred_forest_meadow_checks()) do location_checks[k] = v end + for k,v in pairs(read_deku_tree_checks(mq_table_address)) do location_checks[k] = v end + for k,v in pairs(read_forest_temple_checks(mq_table_address)) do location_checks[k] = v end + for k,v in pairs(read_hyrule_field_checks()) do location_checks[k] = v end + for k,v in pairs(read_lon_lon_ranch_checks()) do location_checks[k] = v end + for k,v in pairs(read_market_checks()) do location_checks[k] = v end + for k,v in pairs(read_hyrule_castle_checks()) do location_checks[k] = v end + for k,v in pairs(read_kakariko_village_checks()) do location_checks[k] = v end + for k,v in pairs(read_graveyard_checks()) do location_checks[k] = v end + for k,v in pairs(read_bottom_of_the_well_checks(mq_table_address)) do location_checks[k] = v end + for k,v in pairs(read_shadow_temple_checks(mq_table_address)) do location_checks[k] = v end + for k,v in pairs(read_death_mountain_trail_checks()) do location_checks[k] = v end + for k,v in pairs(read_goron_city_checks()) do location_checks[k] = v end + for k,v in pairs(read_death_mountain_crater_checks()) do location_checks[k] = v end + for k,v in pairs(read_dodongos_cavern_checks(mq_table_address)) do location_checks[k] = v end + for k,v in pairs(read_fire_temple_checks(mq_table_address)) do location_checks[k] = v end + for k,v in pairs(read_zoras_river_checks()) do location_checks[k] = v end + for k,v in pairs(read_zoras_domain_checks()) do location_checks[k] = v end + for k,v in pairs(read_zoras_fountain_checks()) do location_checks[k] = v end + for k,v in pairs(read_jabu_checks(mq_table_address)) do location_checks[k] = v end + for k,v in pairs(read_ice_cavern_checks(mq_table_address)) do location_checks[k] = v end + for k,v in pairs(read_lake_hylia_checks()) do location_checks[k] = v end + for k,v in pairs(read_water_temple_checks(mq_table_address)) do location_checks[k] = v end + for k,v in pairs(read_gerudo_valley_checks()) do location_checks[k] = v end + for k,v in pairs(read_gerudo_fortress_checks()) do location_checks[k] = v end + for k,v in pairs(read_gerudo_training_ground_checks(mq_table_address)) do location_checks[k] = v end + for k,v in pairs(read_haunted_wasteland_checks()) do location_checks[k] = v end + for k,v in pairs(read_desert_colossus_checks()) do location_checks[k] = v end + for k,v in pairs(read_spirit_temple_checks(mq_table_address)) do location_checks[k] = v end + for k,v in pairs(read_ganons_castle_checks(mq_table_address)) do location_checks[k] = v end + for k,v in pairs(read_outside_ganons_castle_checks()) do location_checks[k] = v end + for k,v in pairs(read_song_checks()) do location_checks[k] = v end + return location_checks +end + + +-- convenience functions + +-- invert a table (assumes values are unique) +local function invert_table(t) + local inverted = {} + for key,val in pairs(t) do + inverted[val] = key + end + return inverted +end + +-- a Layout describes how a section of memory is laid out +-- getting a specific data type should return its value, +-- getting a rescursive structure will return the structure with the layout (this is the default behavior) +local Layout = { + rawget = function(pointer) return pointer.get(pointer) end, + get = function(pointer) return pointer end, + set = function(pointer, value) end +} +function Layout:create (l) + setmetatable(l, self) + self.__index = self + return l +end + +-- a Layout_Entry gives an offset within the Layout and, recursively, a Layout of memory at that offset +local function Layout_Entry(offset, layout) + return { offset = offset, layout = layout } +end + +local e = Layout_Entry + +-- Pointer holds an absolute offset, and has a Layout as its type +local Pointer = {} +function Pointer:new (offset, layout) + local p = { offset = offset, layout = layout } + setmetatable(p, Pointer) + return p +end +function Pointer:cast(layout) + return Pointer:new(self.offset, layout) +end +function Pointer:rawget(key) + if not self.layout[key] then + return self.layout.rawget(self) + end + -- get the struct at this entry + local inner = self.layout[key] + -- update get the new offset and layout + local offset = self.offset + inner.offset + local layout = inner.layout + -- create a new pointer + return Pointer:new(offset, layout) +end +function Pointer:get() return self.layout.get(self) end +function Pointer:set(value) return self.layout.set(self, value) end +function Pointer.__index(pointer, key) + if Pointer[key] then + return Pointer[key] + end + -- get the pointer + local p = pointer:rawget(key) + -- resolve the pointer (if the layout is not concrete, it resolves to itself) + return p:get() +end +function Pointer.__newindex(pointer, key, value) + -- get the pointer + local p = pointer:rawget(key) + -- resolve the pointer (if the layout is not concrete, it resolves to itself) + p:set(value) +end + +-- CONCRETE TYPES + +-- Int has a width in bytes to read +local function Int(width) + local obj = Layout:create {} + + local gets = { + [1] = function(p)return mainmemory.read_u8(p.offset) end, + [2] = function(p) return mainmemory.read_u16_be(p.offset) end, + [3] = function(p) return mainmemory.read_u24_be(p.offset) end, + [4] = function(p) return mainmemory.read_u32_be(p.offset) end, + } + obj.get = gets[width] + + local sets = { + [1] = function(p, value) mainmemory.write_u8(p.offset, value) end, + [2] = function(p, value) mainmemory.write_u16_be(p.offset, value) end, + [3] = function(p, value) mainmemory.write_u24_be(p.offset, value) end, + [4] = function(p, value) mainmemory.write_u32_be(p.offset, value) end, + } + obj.set = sets[width] + + return obj +end + +-- alias for types to save space by not creating them multiple times +local Int_8 = Int(1) +local Int_16 = Int(2) +local Int_24 = Int(3) +local Int_32 = Int(4) + +-- Bit is a single flag at an address +-- values passed in and returned are booleans +local function Bit(pos) + local obj = Layout:create {} + + function obj.get(p) + return bit.check(mainmemory.read_u8(p.offset), pos) + end + + function obj.set(p, value) + local orig = mainmemory.readbyte(p.offset) + local changed + if value then + changed = bit.set(orig, pos) + else + changed = bit.clear(orig, pos) + end + mainmemory.writebyte(p.offset, changed) + end + + return obj +end + +-- Bits is an int that is some mask of bits at the address +-- the range of bit positions is inclusive +local function Bits(start, ending) + local obj = Layout:create {} + + local mask = 0x00 + for b = start, ending do + mask = bit.set(mask, b) + end + + function obj.get(p) + return bit.rshift( bit.band(mainmemory.read_u8(p.offset), mask), start ) + end + + function obj.set(p, value) + local orig = mainmemory.readbyte(p.offset) + orig = bit.band( orig, bit.bnot(mask) ) + mainmemory.writebyte(p.offset, bit.bor(bit.lshift(value, start), orig) ) + end + + return obj +end + +local function Value_Named_Layout(layout, lookup) + local obj = Layout:create {} + + obj.lookup = lookup + + function obj.get(p) + local value = layout.get(p) + if lookup[value] then + value = lookup[value] + end + return value + end + + function obj.rawget(p) + return layout.get(p) + end + + local inverse_lookup = invert_table(lookup) + + function obj.set(p, value) + if type(value) == "string" then + if inverse_lookup[value] then + value = inverse_lookup[value] + else + return + end + end + layout.set(p, value) + end + + return obj +end + +-- RECURSIVE TYPES + +-- holds a list of layouts of a given type, that can be indexed into +-- the Array can be given a list of names for each key to be used as an alternative lookup +local function Array(width, layout, keys) + local obj = Layout:create {} + + obj.keys = keys + + setmetatable(obj, { + __index = function(array, key) + -- allows us to still call get and set + if Layout[key] then + return Layout[key] + end + -- compute the offset from the start of the array + if type(key) == "string" then + if keys[key] then + key = keys[key] + else + key = 0 + end + end + local offset = key * width + -- since this is a layout, we are expected to return a layout entry + return Layout_Entry( offset, layout ) + end + }) + + return obj +end + +-- holds a list of bit flags +-- the Bit_Array can be given a list of names for each key to be used as an alternative lookup +local function Bit_Array(bytes, keys) + local obj = Layout:create {} + + obj.keys = keys + + setmetatable(obj, { + __index = function(array, key) + -- allows us to still call get and set + if Layout[key] then + return Layout[key] + end + -- compute the offset from the start of the array + if type(key) == "string" then + if keys[key] then + key = keys[key] + else + key = 0 + end + end + local byte = bytes - math.floor(key / 8) - 1 + local bit = key % 8 + -- since this is a layout, we are expected to return a layout entry + return Layout_Entry( byte, Bit(bit) ) + end + }) + + return obj +end + +-- a pointer to a specific location in memory +local function Address(layout) + local obj = Layout:create {} + + function obj.get(p) + -- get the address + local address = Int_32.get(p) + address = bit.band(address, 0x00FFFFFF) + -- return "Null" for address 0 + if address == 0 then + return "Null" + end + -- create a pointer to it + return Pointer:new( address, layout ) + end + + function obj.set(p, value) + -- Null a pointer + if value == "Null" then + Int_32.set(p, 0) + return + end + -- assume this is a Pointer + if type(value) == "table" then + value = value.offset + end + -- create address + local address = bit.bor(value, 0x80000000) + Int_32.set(p, address) + end + + return obj +end + +-- OOT Structs + +local scene_names = { + deku_tree = 0x00, + dodongos_cavern = 0x01, + jabu_jabus_belly = 0x02, + forest_temple = 0x03, + fire_temple = 0x04, + water_temple = 0x05, + spirit_temple = 0x06, + shadow_temple = 0x07, + bottom_of_the_well = 0x08, + ice_cavern = 0x09, + ganons_tower = 0x0A, + gerudo_training_ground = 0x0B, + thieves_hideout = 0x0C, + inside_ganons_castle = 0x0D, + treasure_box_shop = 0x10, +} + +local Global_Context = Layout:create { + cur_scene = Layout_Entry( 0x00A4, Value_Named_Layout(Int_16, invert_table(scene_names)) ), + + actor_table = Layout_Entry( 0x1C30, Array( 0x8, Actor_Table_Entry, actor_category ) ), + + switch_flags = Layout_Entry( 0x1D28, Bit_Array( 0x4 ) ), + temp_switch_flags = Layout_Entry( 0x1D2C, Bit_Array( 0x4 ) ), + chest_flags = Layout_Entry( 0x1D38, Bit_Array( 0x4 ) ), + room_clear_flags = Layout_Entry( 0x1D3C, Bit_Array( 0x4 ) ), +} + +local Save_Context = Layout:create { + time = Layout_Entry( 0x000C, Int_16 ), + max_health = Layout_Entry( 0x002E, Int_16 ), + cur_health = Layout_Entry( 0x0030, Int_16 ), + magic_meter_level = Layout_Entry( 0x0032, Int_8 ), + cur_magic = Layout_Entry( 0x0033, Int_8 ), + rupees = Layout_Entry( 0x0034, Int_16 ), + have_magic = Layout_Entry( 0x003A, Bit(0) ), + have_double_magic = Layout_Entry( 0x003C, Bit(0) ), + double_defense = Layout_Entry( 0x003D, Bit(0) ), + biggoron_sword_durable = Layout_Entry( 0x003E, Bit(0) ), + + stored_child_equips = Layout_Entry( 0x0040, Equips ), + stored_adult_equips = Layout_Entry( 0x004A, Equips ), + current_equips = Layout_Entry( 0x0068, Equips ), + + inventory = Layout_Entry( 0x0074, Array( 1, Item, item_slot_names ) ), + ammo = Layout_Entry( 0x008C, Array( 1, Int_8, item_slot_names ) ), + + beans_purchased = Layout_Entry( 0x009B, Int_8 ), + + equipment = Layout_Entry( 0x009C, Equipment ), + heart_pieces = Layout_Entry( 0x00A4, Bits(4,5) ), + quest_status = Layout_Entry( 0x00A5, Bit_Array( 0x3, quest_status_names ) ), + + dungeon_items = Layout_Entry( 0x00A8, Array( 0x1, Dungeon_Item, scene_names ) ), + small_keys = Layout_Entry( 0x00BC, Array( 0x1, Int_8, scene_names ) ), + + double_defense_hearts = Layout_Entry( 0x00CF, Int_8 ), + gold_skulltulas = Layout_Entry( 0x00D0, Int_16 ), + + scene_flags = Layout_Entry( 0x00D4, Array( 0x1C, Scene_Flags_Type, scene_names ) ), + + skulltula_flags = Layout_Entry( 0xE9C, Array( 0x1, Bit_Array( 0x1 ) ) ), + + events = Layout_Entry( 0xED4, Array( 0x2, Bit_Array( 0x2 ) ) ), + + magic_meter_size = Layout_Entry( 0x13F4, Int_16 ), + + -- ex: oot.sav.triforce_pieces = 0x01 - doesn't seem to work with decimal vals + triforce_pieces = Layout_Entry( 0xD4 + 0x1C * 0x48 + 0x10, Int_32) + +} + +local save_context = Pointer:new( 0x11A5D0, Save_Context ) +local global_context = Pointer:new( 0x1C84A0, Global_Context ) + + +local STATE_OK = "Ok" +local STATE_TENTATIVELY_CONNECTED = "Tentatively Connected" +local STATE_INITIAL_CONNECTION_MADE = "Initial Connection Made" +local STATE_UNINITIALIZED = "Uninitialized" + +local prevstate = "" +local curstate = STATE_UNINITIALIZED +local ootSocket = nil +local frame = 0 + +-- Various useful values +local rando_context = mainmemory.read_u32_be(0x1C6E90 + 0x15D4) - 0x80000000 +local coop_context = mainmemory.read_u32_be(rando_context + 0x0000) - 0x80000000 + +local player_id_addr = coop_context + 4 + +local incoming_player_addr = coop_context + 6 +local incoming_item_addr = coop_context + 8 + +local outgoing_key_addr = coop_context + 12 +local outgoing_item_addr = coop_context + 16 +local outgoing_player_addr = coop_context + 18 + +local player_names_address = coop_context + 20 +local player_name_length = 8 -- 8 bytes +local rom_name_location = player_names_address + 0x800 + +local master_quest_table_address = rando_context + 0xB220 + +local save_context_addr = 0x11A5D0 +local internal_count_addr = save_context_addr + 0x90 +local item_queue = {} + +local first_connect = true +local game_complete = false + +local bytes_to_string = function(bytes) + local string = '' + for i=0,#(bytes) do + if bytes[i] == 0 then return string end + string = string .. string.char(bytes[i]) + end + return string +end + +-- ROM reading and writing functions + +-- Reading game state +local state_main = Pointer:new( 0x11B92F, Int(1) ) +local state_sub = Pointer:new( 0x11B933, Int(1) ) +local state_menu = Pointer:new( 0x1D8DD5, Int(1) ) +local state_logo = Pointer:new( 0x11F200, Int(4) ) +local state_link = Pointer:new( 0x1DB09C, Bit_Array( 0x8, {dying=0x27} ) ) +local state_fairy_queued = Pointer:new( 0x1DB26F, Bit(0) ) + + +local game_modes = { + [-1]={name="Unknown", loaded=false}, + [0]={name="N64 Logo", loaded=false}, + [1]={name="Title Screen", loaded=false}, + [2]={name="File Select", loaded=false}, + [3]={name="Normal Gameplay", loaded=true}, + [4]={name="Cutscene", loaded=true}, + [5]={name="Paused", loaded=true}, + [6]={name="Dying", loaded=true}, + [7]={name="Dying Menu Start", loaded=false}, + [8]={name="Dead", loaded=false}, +} + +local function get_current_game_mode() + local mode = -1 + local logo_state = state_logo:get() + if logo_state == 0x802C5880 or logo_state == 0x00000000 then + mode = 0 + else + if state_main:get() == 1 then + mode = 1 + elseif state_main:get() == 2 then + mode = 2 + else + local menu_state = state_menu:get() + if menu_state == 0 then + if state_link.dying or save_context.cur_health <= 0 then + mode = 6 + else + if state_sub:get() == 4 then + mode = 4 + else + mode = 3 + end + end + elseif (0 < menu_state and menu_state < 9) or menu_state == 13 then + mode = 5 + elseif menu_state == 9 or menu_state == 0xB then + mode = 7 + else + mode = 8 + end + end + end + return mode, game_modes[mode] +end + +function InSafeState() + return game_modes[get_current_game_mode()].loaded +end + +function item_receivable() + local shop_scenes = {[0x2C]=1, [0x2D]=1, [0x2E]=1, [0x2F]=1, [0x30]=1, [0x31]=1, [0x32]=1, [0x33]=1, + [0x42]=1, [0x4B]=1} + local details + local scene + _, details = get_current_game_mode() + scene = global_context:rawget('cur_scene'):rawget() + + local playerQueued = mainmemory.read_u16_be(incoming_player_addr) + local itemQueued = mainmemory.read_u16_be(incoming_item_addr) + + -- Safe to receive an item if the scene is normal, player is not in a shop, and no item is already queued + return details.name == "Normal Gameplay" and shop_scenes[scene] == nil and playerQueued == 0 and itemQueued == 0 +end + +function get_player_name() + local rom_name_bytes = mainmemory.readbyterange(rom_name_location, 16) + return bytes_to_string(rom_name_bytes) +end + +function setPlayerName(id, name) + local name_address = player_names_address + (id * player_name_length) + local name_index = 0 + + for _,c in pairs({string.byte(name, 1, 100)}) do + if c >= string.byte('0') and c <= string.byte('9') then + c = c - string.byte('0') + elseif c >= string.byte('A') and c <= string.byte('Z') then + c = c + 0x6A + elseif c >= string.byte('a') and c <= string.byte('z') then + c = c + 0x64 + elseif c == string.byte('.') then + c = 0xEA + elseif c == string.byte('-') then + c = 0xE4 + elseif c == string.byte(' ') then + c = 0xDF + else + c = nil + end + + if c ~= nil then + mainmemory.write_u8(name_address + name_index, c) + + name_index = name_index + 1 + if name_index >= 8 then + break + end + end + end + + for i = name_index, player_name_length - 1 do + mainmemory.write_u8(name_address + i, 0xDF) + end +end + +function is_game_complete() + -- If the game is complete, do not read memory + if game_complete then return true end + + -- contains a pointer to the current scene + local scene_pointer = mainmemory.read_u32_be(0x1CA208) + + local triforce_hunt_complete = 0x80383C10 -- pointer credits location set by completed triforce hunt + local ganon_defeated = 0x80382720 -- pointer to cutscene when ganon is defeated + + -- If the game is complete, set the lib variable and report the game as completed + if (scene_pointer == triforce_hunt_complete) or (scene_pointer == ganon_defeated) then + game_complete = true + return true + end + + -- Game is still ongoing + return false +end + +function deathlink_enabled() + local death_link_flag = mainmemory.read_u16_be(coop_context + 0xA) + return death_link_flag > 0 +end + +function get_death_state() + -- Load the current game mode + local game_mode = get_current_game_mode() + + -- If the N64 Logo is loaded, Link isn't dead, he just doesn't exist + if (game_mode == "N64 Logo" or game_mode == "File Select") then return false end + + -- Read Link's current HP value + local hp_counter = mainmemory.read_u16_be(0x11A600) + + return (hp_counter == 0) +end + +function kill_link() + mainmemory.write_u16_be(0x11A600, 0) +end + +function process_block(block) + -- Sometimes the block is nothing, if this is the case then quietly stop processing + if block == nil then + return + end + -- Write player names on first connect or after reset (N64 logo, title screen, file select) + cur_mode = get_current_game_mode() + if (first_connect or cur_mode == 0 or cur_mode == 1 or cur_mode == 2) and (#block['playerNames'] > 0) then + first_connect = false + local index = 1 + while (index <= #block['playerNames']) and (index < 255) do + setPlayerName(index, block['playerNames'][index]) + index = index + 1 + end + setPlayerName(255, 'APPlayer') + end + -- Kill Link if needed + if block['triggerDeath'] then + kill_link() + end + -- Queue item for receiving, if one exists + item_queue = block['items'] + received_items_count = mainmemory.read_u16_be(internal_count_addr) + if received_items_count < #item_queue then + -- There are items to send: remember lua tables are 1-indexed! + if item_receivable() then + mainmemory.write_u16_be(incoming_player_addr, 0x00) + mainmemory.write_u16_be(incoming_item_addr, item_queue[received_items_count+1]) + end + end + return +end + +-- Main control handling: main loop and socket receive + +function receive() + l, e = ootSocket:receive() + -- Handle incoming message + if e == 'closed' then + if curstate == STATE_OK then + print("Connection closed") + end + curstate = STATE_UNINITIALIZED + return + elseif e == 'timeout' then + print("timeout") + return + elseif e ~= nil then + print(e) + curstate = STATE_UNINITIALIZED + return + end + process_block(json.decode(l)) + + -- Determine message to send back + local retTable = {} + retTable["playerName"] = get_player_name() + retTable["deathlinkActive"] = deathlink_enabled() + if InSafeState() then + retTable["locations"] = check_all_locations(master_quest_table_address) + retTable["isDead"] = get_death_state() + retTable["gameComplete"] = is_game_complete() + end + + -- Send the message + msg = json.encode(retTable).."\n" + local ret, error = ootSocket:send(msg) + if ret == nil then + print(error) + elseif curstate == STATE_INITIAL_CONNECTION_MADE then + curstate = STATE_TENTATIVELY_CONNECTED + elseif curstate == STATE_TENTATIVELY_CONNECTED then + print("Connected!") + curstate = STATE_OK + end + +end + +function main() + if (is23Or24Or25 or is26To27) == false then + print("Must use a version of bizhawk 2.3.1 or higher") + return + end + server, error = socket.bind('localhost', 28921) + + while true do + frame = frame + 1 + if not (curstate == prevstate) then + prevstate = curstate + end + if (curstate == STATE_OK) or (curstate == STATE_INITIAL_CONNECTION_MADE) or (curstate == STATE_TENTATIVELY_CONNECTED) then + if (frame % 30 == 0) then + receive() + end + elseif (curstate == STATE_UNINITIALIZED) then + if (frame % 60 == 0) then + server:settimeout(2) + print("Attempting to connect") + local client, timeout = server:accept() + if timeout == nil then + print('Initial Connection Made') + curstate = STATE_INITIAL_CONNECTION_MADE + ootSocket = client + ootSocket:settimeout(0) + end + end + end + emu.frameadvance() + end +end + +main() \ No newline at end of file diff --git a/data/lua/OOT/socket.lua b/data/lua/OOT/socket.lua new file mode 100644 index 00000000..a98e9521 --- /dev/null +++ b/data/lua/OOT/socket.lua @@ -0,0 +1,132 @@ +----------------------------------------------------------------------------- +-- LuaSocket helper module +-- Author: Diego Nehab +-- RCS ID: $Id: socket.lua,v 1.22 2005/11/22 08:33:29 diego Exp $ +----------------------------------------------------------------------------- + +----------------------------------------------------------------------------- +-- Declare module and import dependencies +----------------------------------------------------------------------------- +local base = _G +local string = require("string") +local math = require("math") +local socket = require("socket.core") +module("socket") + +----------------------------------------------------------------------------- +-- Exported auxiliar functions +----------------------------------------------------------------------------- +function connect(address, port, laddress, lport) + local sock, err = socket.tcp() + if not sock then return nil, err end + if laddress then + local res, err = sock:bind(laddress, lport, -1) + if not res then return nil, err end + end + local res, err = sock:connect(address, port) + if not res then return nil, err end + return sock +end + +function bind(host, port, backlog) + local sock, err = socket.tcp() + if not sock then return nil, err end + sock:setoption("reuseaddr", true) + local res, err = sock:bind(host, port) + if not res then return nil, err end + res, err = sock:listen(backlog) + if not res then return nil, err end + return sock +end + +try = newtry() + +function choose(table) + return function(name, opt1, opt2) + if base.type(name) ~= "string" then + name, opt1, opt2 = "default", name, opt1 + end + local f = table[name or "nil"] + if not f then base.error("unknown key (".. base.tostring(name) ..")", 3) + else return f(opt1, opt2) end + end +end + +----------------------------------------------------------------------------- +-- Socket sources and sinks, conforming to LTN12 +----------------------------------------------------------------------------- +-- create namespaces inside LuaSocket namespace +sourcet = {} +sinkt = {} + +BLOCKSIZE = 2048 + +sinkt["close-when-done"] = function(sock) + return base.setmetatable({ + getfd = function() return sock:getfd() end, + dirty = function() return sock:dirty() end + }, { + __call = function(self, chunk, err) + if not chunk then + sock:close() + return 1 + else return sock:send(chunk) end + end + }) +end + +sinkt["keep-open"] = function(sock) + return base.setmetatable({ + getfd = function() return sock:getfd() end, + dirty = function() return sock:dirty() end + }, { + __call = function(self, chunk, err) + if chunk then return sock:send(chunk) + else return 1 end + end + }) +end + +sinkt["default"] = sinkt["keep-open"] + +sink = choose(sinkt) + +sourcet["by-length"] = function(sock, length) + return base.setmetatable({ + getfd = function() return sock:getfd() end, + dirty = function() return sock:dirty() end + }, { + __call = function() + if length <= 0 then return nil end + local size = math.min(socket.BLOCKSIZE, length) + local chunk, err = sock:receive(size) + if err then return nil, err end + length = length - string.len(chunk) + return chunk + end + }) +end + +sourcet["until-closed"] = function(sock) + local done + return base.setmetatable({ + getfd = function() return sock:getfd() end, + dirty = function() return sock:dirty() end + }, { + __call = function() + if done then return nil end + local chunk, err, partial = sock:receive(socket.BLOCKSIZE) + if not err then return chunk + elseif err == "closed" then + sock:close() + done = 1 + return partial + else return nil, err end + end + }) +end + + +sourcet["default"] = sourcet["until-closed"] + +source = choose(sourcet) diff --git a/host.yaml b/host.yaml index 9b80b0b6..a96d7f1b 100644 --- a/host.yaml +++ b/host.yaml @@ -108,6 +108,10 @@ minecraft_options: oot_options: # File name of the OoT v1.0 ROM rom_file: "The Legend of Zelda - Ocarina of Time.z64" + # Set this to false to never autostart a rom (such as after patching) + # true for operating system default program + # Alternatively, a path to a program to open the .z64 file with + rom_start: true soe_options: # File name of the SoE US ROM rom_file: "Secret of Evermore (USA).sfc" diff --git a/inno_setup.iss b/inno_setup.iss index 1e4e3cf3..08ff7c2a 100644 --- a/inno_setup.iss +++ b/inno_setup.iss @@ -64,7 +64,7 @@ Name: "client/sni/lttp"; Description: "SNI Client - A Link to the Past Patch Se Name: "client/sni/sm"; Description: "SNI Client - Super Metroid Patch Setup"; Types: full playing; Flags: disablenouninstallwarning Name: "client/factorio"; Description: "Factorio"; Types: full playing Name: "client/minecraft"; Description: "Minecraft"; Types: full playing; ExtraDiskSpaceRequired: 226894278 -Name: "client/oot"; Description: "Ocarina of Time Adjuster"; Types: full playing +Name: "client/oot"; Description: "Ocarina of Time"; Types: full playing Name: "client/ff1"; Description: "Final Fantasy 1"; Types: full playing Name: "client/text"; Description: "Text, to !command and chat"; Types: full playing @@ -75,7 +75,7 @@ NAME: "{app}"; Flags: setntfscompression; Permissions: everyone-modify users-mod Source: "{code:GetROMPath}"; DestDir: "{app}"; DestName: "Zelda no Densetsu - Kamigami no Triforce (Japan).sfc"; Flags: external; Components: client/sni/lttp or generator/lttp Source: "{code:GetSMROMPath}"; DestDir: "{app}"; DestName: "Super Metroid (JU).sfc"; Flags: external; Components: client/sni/sm or generator/sm Source: "{code:GetSoEROMPath}"; DestDir: "{app}"; DestName: "Secret of Evermore (USA).sfc"; Flags: external; Components: generator/soe -Source: "{code:GetOoTROMPath}"; DestDir: "{app}"; DestName: "The Legend of Zelda - Ocarina of Time.z64"; Flags: external; Components: generator/oot +Source: "{code:GetOoTROMPath}"; DestDir: "{app}"; DestName: "The Legend of Zelda - Ocarina of Time.z64"; Flags: external; Components: client/oot or generator/oot Source: "{#source_path}\*"; Excludes: "*.sfc, *.log, data\sprites\alttpr, SNI, EnemizerCLI, Archipelago*.exe"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs Source: "{#source_path}\SNI\*"; Excludes: "*.sfc, *.log"; DestDir: "{app}\SNI"; Flags: ignoreversion recursesubdirs createallsubdirs; Components: client/sni Source: "{#source_path}\EnemizerCLI\*"; Excludes: "*.sfc, *.log"; DestDir: "{app}\EnemizerCLI"; Flags: ignoreversion recursesubdirs createallsubdirs; Components: generator/lttp @@ -87,6 +87,7 @@ Source: "{#source_path}\ArchipelagoTextClient.exe"; DestDir: "{app}"; Flags: ign Source: "{#source_path}\ArchipelagoSNIClient.exe"; DestDir: "{app}"; Flags: ignoreversion; Components: client/sni Source: "{#source_path}\ArchipelagoLttPAdjuster.exe"; DestDir: "{app}"; Flags: ignoreversion; Components: client/sni/lttp or generator/lttp Source: "{#source_path}\ArchipelagoMinecraftClient.exe"; DestDir: "{app}"; Flags: ignoreversion; Components: client/minecraft +Source: "{#source_path}\ArchipelagoOoTClient.exe"; DestDir: "{app}"; Flags: ignoreversion; Components: client/oot Source: "{#source_path}\ArchipelagoOoTAdjuster.exe"; DestDir: "{app}"; Flags: ignoreversion; Components: client/oot Source: "{#source_path}\ArchipelagoFF1Client.exe"; DestDir: "{app}"; Flags: ignoreversion; Components: client/ff1 Source: "vc_redist.x64.exe"; DestDir: {tmp}; Flags: deleteafterinstall @@ -98,13 +99,16 @@ Name: "{group}\{#MyAppName} Text Client"; Filename: "{app}\ArchipelagoTextClient Name: "{group}\{#MyAppName} SNI Client"; Filename: "{app}\ArchipelagoSNIClient.exe"; Components: client/sni Name: "{group}\{#MyAppName} Factorio Client"; Filename: "{app}\ArchipelagoFactorioClient.exe"; Components: client/factorio Name: "{group}\{#MyAppName} Minecraft Client"; Filename: "{app}\ArchipelagoMinecraftClient.exe"; Components: client/minecraft +Name: "{group}\{#MyAppName} Ocarina of Time Client"; Filename: "{app}\ArchipelagoOoTClient.exe"; Components: client/oot Name: "{group}\{#MyAppName} Final Fantasy 1 Client"; Filename: "{app}\ArchipelagoFF1Client.exe"; Components: client/ff1 Name: "{commondesktop}\{#MyAppName} Folder"; Filename: "{app}"; Tasks: desktopicon Name: "{commondesktop}\{#MyAppName} Server"; Filename: "{app}\{#MyAppExeName}"; Tasks: desktopicon; Components: server Name: "{commondesktop}\{#MyAppName} SNI Client"; Filename: "{app}\ArchipelagoSNIClient.exe"; Tasks: desktopicon; Components: client/sni Name: "{commondesktop}\{#MyAppName} Factorio Client"; Filename: "{app}\ArchipelagoFactorioClient.exe"; Tasks: desktopicon; Components: client/factorio -Name: "{commondesktop}\{#MyAppName} Final Fantasy 1 Client"; Filename: "{app}\ArchipelagoFF1Client.exe"; Tasks: desktopicon; Components: client/factorio +Name: "{commondesktop}\{#MyAppName} Minecraft Client"; Filename: "{app}\ArchipelagoMinecraftClient.exe"; Tasks: desktopicon; Components: client/minecraft +Name: "{commondesktop}\{#MyAppName} Ocarina of Time Client"; Filename: "{app}\ArchipelagoOoTClient.exe"; Tasks: desktopicon; Components: client/oot +Name: "{commondesktop}\{#MyAppName} Final Fantasy 1 Client"; Filename: "{app}\ArchipelagoFF1Client.exe"; Tasks: desktopicon; Components: client/ff1 [Run] @@ -140,6 +144,11 @@ Root: HKCR; Subkey: "{#MyAppName}mcdata"; ValueData: "Archip Root: HKCR; Subkey: "{#MyAppName}mcdata\DefaultIcon"; ValueData: "{app}\ArchipelagoMinecraftClient.exe,0"; ValueType: string; ValueName: ""; Components: client/minecraft Root: HKCR; Subkey: "{#MyAppName}mcdata\shell\open\command"; ValueData: """{app}\ArchipelagoMinecraftClient.exe"" ""%1"""; ValueType: string; ValueName: ""; Components: client/minecraft +Root: HKCR; Subkey: ".apz5"; ValueData: "{#MyAppName}n64zpf"; Flags: uninsdeletevalue; ValueType: string; ValueName: ""; Components: client/oot +Root: HKCR; Subkey: "{#MyAppName}n64zpf"; ValueData: "Archipelago Ocarina of Time Patch"; Flags: uninsdeletekey; ValueType: string; ValueName: ""; Components: client/oot +Root: HKCR; Subkey: "{#MyAppName}n64zpf\DefaultIcon"; ValueData: "{app}\ArchipelagoOoTClient.exe,0"; ValueType: string; ValueName: ""; Components: client/oot +Root: HKCR; Subkey: "{#MyAppName}n64zpf\shell\open\command"; ValueData: """{app}\ArchipelagoOoTClient.exe"" ""%1"""; ValueType: string; ValueName: ""; Components: client/oot + Root: HKCR; Subkey: ".archipelago"; ValueData: "{#MyAppName}multidata"; Flags: uninsdeletevalue; ValueType: string; ValueName: ""; Components: server Root: HKCR; Subkey: "{#MyAppName}multidata"; ValueData: "Archipelago Server Data"; Flags: uninsdeletekey; ValueType: string; ValueName: ""; Components: server Root: HKCR; Subkey: "{#MyAppName}multidata\DefaultIcon"; ValueData: "{app}\ArchipelagoServer.exe,0"; ValueType: string; ValueName: ""; Components: server diff --git a/kvui.py b/kvui.py index 00d1e3f2..9e619305 100644 --- a/kvui.py +++ b/kvui.py @@ -406,6 +406,12 @@ class FF1Manager(GameManager): ] base_title = "Archipelago Final Fantasy 1 Client" +class OoTManager(GameManager): + logging_pairs = [ + ("Client", "Archipelago") + ] + base_title = "Archipelago Ocarina of Time Client" + class LogtoUI(logging.Handler): def __init__(self, on_log): diff --git a/setup.py b/setup.py index a5831c49..84afb5ed 100644 --- a/setup.py +++ b/setup.py @@ -91,6 +91,7 @@ scripts = { # Minecraft "MinecraftClient.py": ("ArchipelagoMinecraftClient", False, mcicon), # Ocarina of Time + "OoTClient.py": ("ArchipelagoOoTClient", True, icon), "OoTAdjuster.py": ("ArchipelagoOoTAdjuster", True, icon), # FF1 "FF1Client.py": ("ArchipelagoFF1Client", True, icon),