sm64coopdx/autogen/gen_hooks.py
djoslin0 24b92ecc2a
Some checks are pending
Build coop / build-linux (push) Waiting to run
Build coop / build-steamos (push) Waiting to run
Build coop / build-windows-opengl (push) Waiting to run
Build coop / build-windows-directx (push) Waiting to run
Build coop / build-macos-arm (push) Waiting to run
Build coop / build-macos-intel (push) Waiting to run
Add a safer version of Lua's require() (#847)
I didn't add standard Lua require() because I've always been
afraid of it. I'm not sure we can guarantee which files it
will read (or not read).

Instead, here is a custom implementation. It should work more
or less the same and allow for more modular code.

For backwards compatibility reasons, all of the lua files in
the base mod folder will be loaded as in the past. Aka one at
a time and alphabetically.

However, now coop will look for Lua files in subdirectories
and will load them in when another Lua file calls require().

The file search order is more reasonable than normal Lua
require(). It will first look for files relative to the
currently running script. If there is no matching relative
file, it will pick from any Lua file that is in any of the
mod's subdirectories.

---------

Co-authored-by: MysterD <myster@d>
2025-06-14 19:49:07 +10:00

309 lines
9 KiB
Python

import sys
import re
from vec_types import *
HOOK_RETURN_NEVER = "HOOK_RETURN_NEVER"
HOOK_RETURN_ON_SUCCESSFUL_CALL = "HOOK_RETURN_ON_SUCCESSFUL_CALL"
HOOK_RETURN_ON_OUTPUT_SET = "HOOK_RETURN_ON_OUTPUT_SET"
SMLUA_CALL_EVENT_HOOKS_BEGIN = """
bool smlua_call_event_hooks_{hook_type}({parameters}) {{
lua_State *L = gLuaState;
if (L == NULL) {{ return false; }}{define_hook_result}
struct LuaHookedEvent *hook = &sHookedEvents[{hook_type}];
for (int i = 0; i < hook->count; i++) {{
s32 prevTop = lua_gettop(L);
// push the callback onto the stack
lua_rawgeti(L, LUA_REGISTRYINDEX, hook->reference[i]);
"""
SMLUA_CALL_EVENT_HOOKS_DEFINE_HOOK_RESULT = """
bool hookResult = false;"""
SMLUA_CALL_EVENT_HOOKS_SET_HOOK_RESULT = """
hookResult = true;"""
SMLUA_CALL_EVENT_HOOKS_CALLBACK = """
// call the callback
if (0 != smlua_call_hook(L, {n_inputs}, {n_outputs}, 0, hook->mod[i], hook->modFile[i])) {{
LOG_LUA("Failed to call the callback for hook %s", sLuaHookedEventTypeName[{hook_type}]);
continue;
}}{set_hook_result}
"""
SMLUA_CALL_EVENT_HOOKS_RETURN_ON_OUTPUT_SET = """
lua_settop(L, prevTop);
return true;"""
SMLUA_CALL_EVENT_HOOKS_RETURN_ON_SUCCESSFUL_CALL = """
return true;"""
SMLUA_CALL_EVENT_HOOKS_END = """
lua_settop(L, prevTop);{return_on_successful_call}
}}
return {hook_result};
}}
"""
SMLUA_INTEGER_TYPES = {
"input": """
// push {name}
lua_pushinteger(L, {name});
""",
"output": """
// return {name}
if (lua_type(L, -{output_index}) == LUA_TNUMBER) {{
*{name} = smlua_to_integer(L, -{output_index});{return_on_output_set}
}}
"""
}
SMLUA_FLOAT_TYPES = {
"input": """
// push {name}
lua_pushnumber(L, {name});
""",
"output": """
// return {name}
if (lua_type(L, -{output_index}) == LUA_TNUMBER) {{
*{name} = smlua_to_number(L, -{output_index});{return_on_output_set}
}}
"""
}
SMLUA_TYPES = {
**{
type_name: {
"input": """
// push {name}
extern void smlua_new_%s(%s src);
smlua_new_%s({name});
""" % (type_name.lower(), type_name, type_name.lower()),
"output": """
// return {name}
if (lua_type(L, -{output_index}) == LUA_TTABLE) {{
extern void smlua_get_%s(%s dest, int index);
smlua_get_%s(*{name}, -{output_index});{return_on_output_set}
}}
""" % (type_name.lower(), type_name, type_name.lower())
}
for type_name in VEC_TYPES
},
"u8": SMLUA_INTEGER_TYPES,
"u16": SMLUA_INTEGER_TYPES,
"u32": SMLUA_INTEGER_TYPES,
"u64": SMLUA_INTEGER_TYPES,
"s8": SMLUA_INTEGER_TYPES,
"s16": SMLUA_INTEGER_TYPES,
"s32": SMLUA_INTEGER_TYPES,
"s64": SMLUA_INTEGER_TYPES,
"f32": SMLUA_FLOAT_TYPES,
"f64": SMLUA_FLOAT_TYPES,
"bool": {
"input": """
// push {name}
lua_pushboolean(L, {name});
""",
"output": """
// return {name}
if (lua_type(L, -{output_index}) == LUA_TBOOLEAN) {{
*{name} = smlua_to_boolean(L, -{output_index});{return_on_output_set}
}}
"""
},
"constchar*": {
"input": """
// push {name}
lua_pushstring(L, {name});
""",
"output": """
// return {name}
if (lua_type(L, -{output_index}) == LUA_TSTRING) {{
*{name} = smlua_to_string(L, -{output_index});{return_on_output_set}
}}
"""
},
"structMarioState*": {
"input": """
// push {name}
lua_getglobal(L, "gMarioStates");
lua_pushinteger(L, {name}->playerIndex);
lua_gettable(L, -2);
lua_remove(L, -2);
"""
},
"structWarpDest": {
"output": """
// if the hook returns a table, use it to override the warp parameters
if (lua_istable(L, -1)) {{
lua_getfield(L, -1, "destLevel");
if (lua_isnumber(L, -1)) {{
{name}->levelNum = smlua_to_integer(L, -1);
}}
lua_pop(L, 1);
lua_getfield(L, -1, "destArea");
if (lua_isnumber(L, -1)) {{
{name}->areaIdx = smlua_to_integer(L, -1);
}}
lua_pop(L, 1);
lua_getfield(L, -1, "destWarpNode");
if (lua_isnumber(L, -1)) {{
{name}->nodeId = smlua_to_integer(L, -1);
}}
lua_pop(L, 1);
{return_on_output_set}
}}
"""
},
}
def init():
# HACK: build LOT types from smlua_cobject_autogen.c
with open('src/pc/lua/smlua_cobject_autogen.c') as f:
file = f.read()
begin = file.find("const char *sLuaLotNames[] = {") + len("const char *sLuaLotNames[] = {")
end = file.find("};", begin)
types = [list(filter(None, re.split(r'\W+', line.strip()))) for line in file[begin:end].split('\n') if line.strip()]
for lot, name in types:
typename = f"struct{name}*"
if typename not in SMLUA_TYPES:
SMLUA_TYPES[typename] = {
"input": """
// push {name}
smlua_push_object(L, %s, {name}, NULL);
""" % (lot)
}
def extract_hook_event(line: str):
if line.startswith("//"):
return None
if not line.startswith("SMLUA_EVENT_HOOK"):
return None
tokens = [token.strip() for token in line.replace('(', ',').replace(')', ',').split(',') if token.strip()]
if len(tokens) < 3:
return None
hook_event_type = tokens[1]
if not hook_event_type.startswith("HOOK_"):
return None
hook_event_return = tokens[2]
if not hook_event_return.startswith("HOOK_RETURN_"):
return None
parameters = ", ".join(token.replace("OUTPUT ", "") for token in tokens[3:])
inputs = []
outputs = []
for token in tokens[3:]:
param = [x.strip() for x in token.split() if x.strip()]
if len(param) < 2:
continue
is_output = param[0] == "OUTPUT"
if is_output:
param = param[1:]
if len(param) < 2:
continue
param_type = " ".join(param[:-1]) if param[0] != "enum" else "s32"
param_name = param[-1]
stars = param_name.count('*')
param_name = param_name[stars:]
param_type += "*" * stars
param_type = "".join(param_type.split())
if is_output:
outputs.append({
"type": param_type[:-1],
"name": param_name
})
else:
inputs.append({
"type": param_type,
"name": param_name
})
return {
"type": hook_event_type,
"return": hook_event_return,
"parameters": parameters,
"inputs": inputs,
"outputs": outputs
}
def main():
verbose = len(sys.argv) > 1 and (sys.argv[1] == "-v" or sys.argv[1] == "--verbose")
with open("src/pc/lua/smlua_hook_events.inl", "r") as f:
lines = f.readlines()
hook_events = []
for line in lines:
hook_event = extract_hook_event(line)
if hook_event:
if verbose:
print(hook_event)
hook_events.append(hook_event)
generated = (
"/* THIS FILE IS AUTO-GENERATED */\n" +
"/* DO NOT EDIT IT MANUALLY */\n\n"
)
for hook_event in hook_events:
hook_return = hook_event["return"]
define_hook_result = SMLUA_CALL_EVENT_HOOKS_DEFINE_HOOK_RESULT if hook_return == HOOK_RETURN_NEVER else ""
set_hook_result = SMLUA_CALL_EVENT_HOOKS_SET_HOOK_RESULT if hook_return == HOOK_RETURN_NEVER else ""
return_on_successful_call = SMLUA_CALL_EVENT_HOOKS_RETURN_ON_SUCCESSFUL_CALL if hook_return == HOOK_RETURN_ON_SUCCESSFUL_CALL else ""
return_on_output_set = SMLUA_CALL_EVENT_HOOKS_RETURN_ON_OUTPUT_SET if hook_return == HOOK_RETURN_ON_OUTPUT_SET else ""
hook_result = "hookResult" if hook_return == HOOK_RETURN_NEVER else "false"
generated += SMLUA_CALL_EVENT_HOOKS_BEGIN.format(
hook_type=hook_event["type"],
parameters=hook_event["parameters"],
define_hook_result=define_hook_result
)
for input in hook_event["inputs"]:
generated += SMLUA_TYPES[input["type"]]["input"].format(
name=input["name"]
)
generated += SMLUA_CALL_EVENT_HOOKS_CALLBACK.format(
n_inputs=len(hook_event["inputs"]),
n_outputs=len(hook_event["outputs"]),
hook_type=hook_event["type"],
set_hook_result=set_hook_result
)
for i, output in enumerate(hook_event["outputs"]):
generated += SMLUA_TYPES[output["type"]]["output"].format(
name=output["name"],
output_index=i+1,
return_on_output_set=return_on_output_set
)
generated += SMLUA_CALL_EVENT_HOOKS_END.format(
return_on_successful_call=return_on_successful_call,
hook_result=hook_result
)
with open("src/pc/lua/smlua_hook_events_autogen.inl", "w", encoding="utf-8", newline="\n") as f:
f.write(generated)
if __name__ == "__main__":
init()
main()