From 834512a5c5dee10c6c8e53910b718fe59a549ee2 Mon Sep 17 00:00:00 2001 From: PeachyPeach <72323920+PeachyPeachSM64@users.noreply.github.com> Date: Tue, 19 May 2026 02:56:08 +0200 Subject: [PATCH] Fix Lua/custom behaviors bugs and flaws (#1229) * improve custom behaviors logic * clarify description of get behavior functions; get name returns last name instead of first; create generic name only when needed * review * fix potential buffer overflow --- autogen/convert_constants.py | 5 +- autogen/lua_definitions/constants.lua | 3 - autogen/lua_definitions/functions.lua | 17 +- autogen/lua_definitions/manual.lua | 4 +- autogen/lua_definitions/structs.lua | 1 + data/behavior_data.c | 64 ++- data/behavior_table.c | 38 +- data/dynos_bin_behavior.cpp | 3 +- docs/lua/constants.md | 4 - docs/lua/functions-3.md | 33 +- docs/lua/functions.md | 1 + docs/lua/structs.md | 1 + include/behavior_table.h | 12 +- include/types.h | 1 + src/engine/behavior_script.c | 20 +- src/game/memory.h | 7 + src/game/object_helpers.c | 3 +- src/game/spawn_object.c | 4 +- src/pc/lua/smlua_cobject_autogen.c | 5 +- src/pc/lua/smlua_constants_autogen.c | 1 - src/pc/lua/smlua_functions_autogen.c | 18 + src/pc/lua/smlua_hooks.c | 564 ++++++++++++++------------ src/pc/lua/smlua_hooks.h | 53 ++- src/pc/mods/mod.h | 2 +- 24 files changed, 511 insertions(+), 353 deletions(-) diff --git a/autogen/convert_constants.py b/autogen/convert_constants.py index 7cb49e24c..b8ca66cde 100644 --- a/autogen/convert_constants.py +++ b/autogen/convert_constants.py @@ -68,7 +68,7 @@ exclude_constants = { "src/game/save_file.h": [ "EEPROM_SIZE" ], "src/game/obj_behaviors.c": [ "^o$" ], "src/pc/djui/djui_console.h": [ "CONSOLE_MAX_TMP_BUFFER" ], - "src/pc/lua/smlua_hooks.h": [ "MAX_HOOKED_MOD_MENU_ELEMENTS", "^HOOK_RETURN_.*", "^ACTION_HOOK_.*", "^MOD_MENU_ELEMENT_.*" ], + "src/pc/lua/smlua_hooks.h": [ "^LUA_BEHAVIOR_.*", "MAX_HOOKED_.*", "^HOOK_RETURN_.*", "^ACTION_HOOK_.*", "^MOD_MENU_ELEMENT_.*" ], "src/pc/djui/djui_panel_menu.h": [ "RAINBOW_TEXT_LEN" ], "src/pc/mods/mod_fs.h": [ "INT_TYPE_MAX", "FLOAT_TYPE_MAX", "FILE_SEEK_MAX" ], "src/engine/surface_load.h": [ "NUM_CELLS" ], @@ -451,7 +451,8 @@ def doc_constant_index(processed_files): s += '- [%s](#%s)\n' % (processed_file['filename'], processed_file['filename'].replace('.', '')) constants = [x for x in processed_file['constants'] if 'identifier' in x] for c in constants: - s += ' - [enum %s](#enum-%s)\n' % (c['identifier'], c['identifier']) + if len(c['constants']) > 0: + s += ' - [enum %s](#enum-%s)\n' % (c['identifier'], c['identifier']) s += '\n
\n\n' return s diff --git a/autogen/lua_definitions/constants.lua b/autogen/lua_definitions/constants.lua index 245bc0c4b..79fbddd1f 100644 --- a/autogen/lua_definitions/constants.lua +++ b/autogen/lua_definitions/constants.lua @@ -8350,9 +8350,6 @@ HOOK_MAX = 69 --- @type LuaHookedEventType --- | `HOOK_ON_PLAY_MODE_UPDATE` --- | `HOOK_MAX` ---- @type integer -MAX_HOOKED_BEHAVIORS = 1024 - HUD_DISPLAY_LIVES = 0 --- @type HudDisplayValue HUD_DISPLAY_COINS = 1 --- @type HudDisplayValue HUD_DISPLAY_STARS = 2 --- @type HudDisplayValue diff --git a/autogen/lua_definitions/functions.lua b/autogen/lua_definitions/functions.lua index c5c4db40d..a2457b6db 100644 --- a/autogen/lua_definitions/functions.lua +++ b/autogen/lua_definitions/functions.lua @@ -3011,35 +3011,42 @@ end --- @param behavior Pointer_BehaviorScript --- @return BehaviorId ---- Gets a behavior ID from a behavior script +--- Gets the behavior ID of the provided `behavior` function get_id_from_behavior(behavior) -- ... end --- @param behavior Pointer_BehaviorScript --- @return BehaviorId ---- Gets a behavior ID from only vanilla behavior scripts +--- Gets the behavior ID of the provided `behavior` if it's a vanilla behavior, `id_bhv_max_count` otherwise function get_id_from_vanilla_behavior(behavior) -- ... end --- @param id BehaviorId --- @return Pointer_BehaviorScript ---- Gets a behavior script from a behavior ID +--- Gets the behavior script corresponding to the provided `id` function get_behavior_from_id(id) -- ... end +--- @param id BehaviorId +--- @return Pointer_BehaviorScript +--- Gets the behavior script corresponding to the provided `id` if it's a vanilla behavior, `nil` otherwise +function get_vanilla_behavior_from_id(id) + -- ... +end + --- @param id BehaviorId --- @return string ---- Gets a behavior name from a behavior ID (bhvMyGreatMODCustom004) +--- Gets the behavior name from the provided `id` (bhvMyGreatMODCustom004) function get_behavior_name_from_id(id) -- ... end --- @param name string --- @return BehaviorId ---- Gets a behavior ID from a behavior name +--- Gets the behavior ID corresponding to the provided `name` function get_id_from_behavior_name(name) -- ... end diff --git a/autogen/lua_definitions/manual.lua b/autogen/lua_definitions/manual.lua index 142650be3..b58d31613 100644 --- a/autogen/lua_definitions/manual.lua +++ b/autogen/lua_definitions/manual.lua @@ -107,10 +107,10 @@ gHudDisplay = {} --- @param behaviorId BehaviorId | integer? The behavior id of the object to modify. Pass in as `nil` to create a custom object --- @param objectList ObjectList | integer Object list ---- @param replaceBehavior boolean Whether or not to completely replace the behavior +--- @param replaceBehavior boolean Whether or not to completely replace the behavior (ignored for non-vanilla behaviors, which are always replaced) --- @param initFunction? fun(obj:Object) Run on object creation --- @param loopFunction? fun(obj:Object) Run every frame ---- @param behaviorName? string Optional +--- @param behaviorName? string Optional, name to give to the behavior to be able to retrieve it with `get_id_from_behavior_name` --- @return BehaviorId BehaviorId Use if creating a custom object, otherwise can be ignored --- Modify an object's behavior or create a new custom object function hook_behavior(behaviorId, objectList, replaceBehavior, initFunction, loopFunction, behaviorName) diff --git a/autogen/lua_definitions/structs.lua b/autogen/lua_definitions/structs.lua index 2da475eec..0cbd15600 100644 --- a/autogen/lua_definitions/structs.lua +++ b/autogen/lua_definitions/structs.lua @@ -1305,6 +1305,7 @@ --- @field public collidedObjs Object[] --- @field public collisionData Pointer_Collision --- @field public behavior Pointer_BehaviorScript +--- @field public initBhvCommand Pointer_BehaviorScript --- @field public curBhvCommand Pointer_BehaviorScript --- @field public bhvStack integer[] --- @field public bhvStackIndex integer diff --git a/data/behavior_data.c b/data/behavior_data.c index 6988c4b50..d3b28073a 100644 --- a/data/behavior_data.c +++ b/data/behavior_data.c @@ -303,15 +303,17 @@ const BehaviorScript bhvSmallWaterWave[] = { SET_RANDOM_FLOAT(oWaterObjUnkFC, /*Minimum*/ 0, /*Range*/ 50), SUM_FLOAT(/*Dest*/ oPosY, /*Value 1*/ oPosY, /*Value 2*/ oWaterObjUnkFC), SET_INT(oAnimState, -1), - CALL(bhvSmallWaterWave398), + CALL(bhvSmallWaterWave398 + 2), BEGIN_REPEAT(60), - CALL(bhvSmallWaterWave398), + CALL(bhvSmallWaterWave398 + 2), CALL_NATIVE(bhv_small_water_wave_loop), END_REPEAT(), DEACTIVATE(), }; const BehaviorScript bhvSmallWaterWave398[] = { + BEGIN(OBJ_LIST_DEFAULT), + ID(id_bhvSmallWaterWave398), ADD_INT(oAnimState, 1), ADD_FLOAT(oPosY, 7), SET_RANDOM_FLOAT(oWaterObjUnkF4, /*Minimum*/ -2, /*Range*/ 5), @@ -1283,6 +1285,8 @@ const BehaviorScript bhvCutOutObject[] = { }; const BehaviorScript bhvBetaMovingFlamesSpawn[] = { + BEGIN(OBJ_LIST_DEFAULT), + ID(id_bhvBetaMovingFlamesSpawn), BEGIN_LOOP(), CALL_NATIVE(bhv_beta_moving_flames_spawn_loop), END_LOOP(), @@ -2224,6 +2228,8 @@ const BehaviorScript bhvLllMovingOctagonalMeshPlatform[] = { }; const BehaviorScript bhvSnowBall[] = { + BEGIN(OBJ_LIST_DEFAULT), + ID(id_bhvSnowBall), BREAK(), }; @@ -2560,10 +2566,12 @@ const BehaviorScript bhvChirpChirp[] = { BEGIN(OBJ_LIST_DEFAULT), ID(id_bhvChirpChirp), SET_INT(oBirdChirpChirpUnkF4, 1), - GOTO(bhvChirpChirpUnused), + GOTO(bhvChirpChirpUnused + 2), }; const BehaviorScript bhvChirpChirpUnused[] = { + BEGIN(OBJ_LIST_DEFAULT), + ID(id_bhvChirpChirpUnused), DISABLE_RENDERING(), OR_INT(oFlags, (OBJ_FLAG_COMPUTE_DIST_TO_MARIO | OBJ_FLAG_SET_FACE_YAW_TO_MOVE_YAW | OBJ_FLAG_UPDATE_GFX_POS_AND_ANGLE)), BEGIN_LOOP(), @@ -2732,6 +2740,8 @@ const BehaviorScript bhvSunkenShipPart[] = { }; const BehaviorScript bhvSunkenShipSetRotation[] = { + BEGIN(OBJ_LIST_DEFAULT), + ID(id_bhvSunkenShipSetRotation), SET_INT(oFaceAnglePitch, 0xE958), SET_INT(oFaceAngleYaw, 0xEE6C), SET_INT(oFaceAngleRoll, 0x0C80), @@ -2745,7 +2755,7 @@ const BehaviorScript bhvSunkenShipPart2[] = { SCALE(/*Unused*/ 0, /*Field*/ 100), SET_FLOAT(oDrawingDistance, 6000), SET_HOME(), - CALL(bhvSunkenShipSetRotation), + CALL(bhvSunkenShipSetRotation + 2), BREAK(), }; @@ -2763,7 +2773,7 @@ const BehaviorScript bhvInSunkenShip2[] = { // Sunken ship - common: OR_INT(oFlags, OBJ_FLAG_UPDATE_GFX_POS_AND_ANGLE), SET_FLOAT(oCollisionDistance, 4000), - CALL(bhvSunkenShipSetRotation), + CALL(bhvSunkenShipSetRotation + 2), BEGIN_LOOP(), CALL_NATIVE(load_object_collision_model), END_LOOP(), @@ -3141,6 +3151,8 @@ const BehaviorScript bhvPlaysMusicTrackWhenTouched[] = { #endif const BehaviorScript bhvInsideCannon[] = { + BEGIN(OBJ_LIST_DEFAULT), + ID(id_bhvInsideCannon), BREAK(), }; @@ -3188,14 +3200,6 @@ const BehaviorScript bhvUnusedFakeStar[] = { END_LOOP(), }; -// What is this? -UNUSED static const BehaviorScript unused_1[] = { - BREAK(), - BREAK(), - BREAK(), - BREAK(), -}; - const BehaviorScript bhvStaticObject[] = { BEGIN(OBJ_LIST_DEFAULT), ID(id_bhvStaticObject), @@ -3619,58 +3623,86 @@ const BehaviorScript bhvUnlockDoorStar[] = { }; const BehaviorScript bhvInstantActiveWarp[] = { + BEGIN(OBJ_LIST_DEFAULT), + ID(id_bhvInstantActiveWarp), BREAK(), }; const BehaviorScript bhvAirborneWarp[] = { + BEGIN(OBJ_LIST_DEFAULT), + ID(id_bhvAirborneWarp), BREAK(), }; const BehaviorScript bhvHardAirKnockBackWarp[] = { + BEGIN(OBJ_LIST_DEFAULT), + ID(id_bhvHardAirKnockBackWarp), BREAK(), }; const BehaviorScript bhvSpinAirborneCircleWarp[] = { + BEGIN(OBJ_LIST_DEFAULT), + ID(id_bhvSpinAirborneCircleWarp), BREAK(), }; const BehaviorScript bhvDeathWarp[] = { + BEGIN(OBJ_LIST_DEFAULT), + ID(id_bhvDeathWarp), BREAK(), }; const BehaviorScript bhvSpinAirborneWarp[] = { + BEGIN(OBJ_LIST_DEFAULT), + ID(id_bhvSpinAirborneWarp), BREAK(), }; const BehaviorScript bhvFlyingWarp[] = { + BEGIN(OBJ_LIST_DEFAULT), + ID(id_bhvFlyingWarp), BREAK(), }; const BehaviorScript bhvPaintingStarCollectWarp[] = { + BEGIN(OBJ_LIST_DEFAULT), + ID(id_bhvPaintingStarCollectWarp), BREAK(), }; const BehaviorScript bhvPaintingDeathWarp[] = { + BEGIN(OBJ_LIST_DEFAULT), + ID(id_bhvPaintingDeathWarp), BREAK(), }; const BehaviorScript bhvAirborneDeathWarp[] = { + BEGIN(OBJ_LIST_DEFAULT), + ID(id_bhvAirborneDeathWarp), BREAK(), }; const BehaviorScript bhvAirborneStarCollectWarp[] = { + BEGIN(OBJ_LIST_DEFAULT), + ID(id_bhvAirborneStarCollectWarp), BREAK(), }; const BehaviorScript bhvLaunchStarCollectWarp[] = { + BEGIN(OBJ_LIST_DEFAULT), + ID(id_bhvLaunchStarCollectWarp), BREAK(), }; const BehaviorScript bhvLaunchDeathWarp[] = { + BEGIN(OBJ_LIST_DEFAULT), + ID(id_bhvLaunchDeathWarp), BREAK(), }; const BehaviorScript bhvSwimmingWarp[] = { + BEGIN(OBJ_LIST_DEFAULT), + ID(id_bhvSwimmingWarp), BREAK(), }; @@ -4141,15 +4173,17 @@ const BehaviorScript bhvBobombExplosionBubble[] = { ADD_RANDOM_FLOAT(oPosX, /*Minimum*/ -50, /*Range*/ 100), ADD_RANDOM_FLOAT(oPosY, /*Minimum*/ -50, /*Range*/ 100), ADD_RANDOM_FLOAT(oPosZ, /*Minimum*/ -50, /*Range*/ 100), - CALL(bhvBobombExplosionBubble3600), + CALL(bhvBobombExplosionBubble3600 + 2), DELAY(1), BEGIN_LOOP(), - CALL(bhvBobombExplosionBubble3600), + CALL(bhvBobombExplosionBubble3600 + 2), CALL_NATIVE(bhv_bobomb_explosion_bubble_loop), END_LOOP(), }; const BehaviorScript bhvBobombExplosionBubble3600[] = { + BEGIN(OBJ_LIST_DEFAULT), + ID(id_bhvBobombExplosionBubble3600), ADD_RANDOM_FLOAT(oPosX, /*Minimum*/ -2, /*Range*/ 4), ADD_RANDOM_FLOAT(oPosZ, /*Minimum*/ -2, /*Range*/ 4), RETURN(), diff --git a/data/behavior_table.c b/data/behavior_table.c index 1312c363f..d086f23d3 100644 --- a/data/behavior_table.c +++ b/data/behavior_table.c @@ -573,34 +573,38 @@ enum BehaviorId get_id_from_vanilla_behavior(const BehaviorScript* behavior) { } const BehaviorScript* get_behavior_from_id(enum BehaviorId id) { - const BehaviorScript* behavior = smlua_get_hooked_behavior_from_id(id, true); - if (behavior != NULL) { return behavior; } - - if (id < 0 || id >= id_bhv_max_count) { - return NULL; + const BehaviorScript* behavior = smlua_get_original_behavior_from_id(id); + if (behavior != NULL) { + return behavior; } + return get_vanilla_behavior_from_id(id); +} - return gBehaviorTable[id].script; +const BehaviorScript* get_vanilla_behavior_from_id(enum BehaviorId id) { + if (id >= 0 && id < id_bhv_max_count) { + return gBehaviorTable[id].script; + } + return NULL; } const char* get_behavior_name_from_id(enum BehaviorId id) { - if (id < 0 || id >= id_bhv_max_count) { - return smlua_get_name_from_hooked_behavior_id(id); + if (id >= 0 && id < id_bhv_max_count) { + return gBehaviorTable[id].name; } - - return gBehaviorTable[id].name; + return smlua_get_behavior_name_from_id(id); } enum BehaviorId get_id_from_behavior_name(const char* name) { - for (enum BehaviorId i = 0; i < id_bhv_max_count; i++) { - if (gBehaviorTable[i].name && !strcmp(name, gBehaviorTable[i].name)) { - return i; + growing_array_for_each_(gHookedBehaviors, struct LuaHookedBehavior, hooked) { + growing_array_for_each_(hooked->bhvNames, const char, bhvName) { + if (strcmp(name, bhvName) == 0) { + return hooked->customId; + } } } - for (int i = 0; i < gHookedBehaviorsCount; i++) { - struct LuaHookedBehavior *hooked = &gHookedBehaviors[i]; - if (hooked->bhvName && !strcmp(name, hooked->bhvName)) { - return hooked->overrideId; + for (enum BehaviorId id = 0; id < id_bhv_max_count; id++) { + if (gBehaviorTable[id].name && strcmp(name, gBehaviorTable[id].name) == 0) { + return id; } } return id_bhv_max_count; diff --git a/data/dynos_bin_behavior.cpp b/data/dynos_bin_behavior.cpp index 7f0da7ed6..c6d1c6471 100644 --- a/data/dynos_bin_behavior.cpp +++ b/data/dynos_bin_behavior.cpp @@ -12,6 +12,7 @@ extern "C" { #include "game/area.h" #include "game/object_list_processor.h" #include "game/interaction.h" +#include "pc/lua/smlua_hooks.h" #include "pc/lua/utils/smlua_anim_utils.h" #include "pc/lua/utils/smlua_collision_utils.h" @@ -780,7 +781,7 @@ s64 DynOS_Bhv_ParseBehaviorScriptConstants(const String &_Arg, bool *found) { bhv_constant(id_bhvPointLight); // Define a special type for new ids that don't override. - if (_Arg == "id_bhvNewId") { return (BehaviorScript) (0xFFFF); } + if (_Arg == "id_bhvNewId") { return (BehaviorScript) LUA_BEHAVIOR_NEW_ID; } // Legacy behavior ids bhv_legacy_constant(id_bhvFish2, id_bhvManyBlueFishSpawner); diff --git a/docs/lua/constants.md b/docs/lua/constants.md index 8b197bcb7..09101617a 100644 --- a/docs/lua/constants.md +++ b/docs/lua/constants.md @@ -84,9 +84,6 @@ - [smlua_audio_utils.h](#smlua_audio_utilsh) - [smlua_hooks.h](#smlua_hooksh) - [enum LuaHookedEventType](#enum-LuaHookedEventType) - - [enum LuaHookedEventReturn](#enum-LuaHookedEventReturn) - - [enum LuaActionHookType](#enum-LuaActionHookType) - - [enum LuaModMenuElementType](#enum-LuaModMenuElementType) - [smlua_misc_utils.h](#smlua_misc_utilsh) - [enum HudDisplayValue](#enum-HudDisplayValue) - [enum HudDisplayFlags](#enum-HudDisplayFlags) @@ -3594,7 +3591,6 @@ | HOOK_BEFORE_PLAY_MODE_UPDATE | 67 | | HOOK_ON_PLAY_MODE_UPDATE | 68 | | HOOK_MAX | 69 | -- MAX_HOOKED_BEHAVIORS [:arrow_up_small:](#) diff --git a/docs/lua/functions-3.md b/docs/lua/functions-3.md index ec32aed47..5ea0a8626 100644 --- a/docs/lua/functions-3.md +++ b/docs/lua/functions-3.md @@ -190,7 +190,7 @@ Gets the draw distance scalar ## [get_id_from_behavior](#get_id_from_behavior) ### Description -Gets a behavior ID from a behavior script +Gets the behavior ID of the provided `behavior` ### Lua Example `local enumValue = get_id_from_behavior(behavior)` @@ -213,7 +213,7 @@ Gets a behavior ID from a behavior script ## [get_id_from_vanilla_behavior](#get_id_from_vanilla_behavior) ### Description -Gets a behavior ID from only vanilla behavior scripts +Gets the behavior ID of the provided `behavior` if it's a vanilla behavior, `id_bhv_max_count` otherwise ### Lua Example `local enumValue = get_id_from_vanilla_behavior(behavior)` @@ -236,7 +236,7 @@ Gets a behavior ID from only vanilla behavior scripts ## [get_behavior_from_id](#get_behavior_from_id) ### Description -Gets a behavior script from a behavior ID +Gets the behavior script corresponding to the provided `id` ### Lua Example `local pointerValue = get_behavior_from_id(id)` @@ -256,10 +256,33 @@ Gets a behavior script from a behavior ID
+## [get_vanilla_behavior_from_id](#get_vanilla_behavior_from_id) + +### Description +Gets the behavior script corresponding to the provided `id` if it's a vanilla behavior, `nil` otherwise + +### Lua Example +`local pointerValue = get_vanilla_behavior_from_id(id)` + +### Parameters +| Field | Type | +| ----- | ---- | +| id | [enum BehaviorId](constants.md#enum-BehaviorId) | + +### Returns +- `Pointer` <`BehaviorScript`> + +### C Prototype +`const BehaviorScript* get_vanilla_behavior_from_id(enum BehaviorId id);` + +[:arrow_up_small:](#) + +
+ ## [get_behavior_name_from_id](#get_behavior_name_from_id) ### Description -Gets a behavior name from a behavior ID (bhvMyGreatMODCustom004) +Gets the behavior name from the provided `id` (bhvMyGreatMODCustom004) ### Lua Example `local stringValue = get_behavior_name_from_id(id)` @@ -282,7 +305,7 @@ Gets a behavior name from a behavior ID (bhvMyGreatMODCustom004) ## [get_id_from_behavior_name](#get_id_from_behavior_name) ### Description -Gets a behavior ID from a behavior name +Gets the behavior ID corresponding to the provided `name` ### Lua Example `local enumValue = get_id_from_behavior_name(name)` diff --git a/docs/lua/functions.md b/docs/lua/functions.md index 1e1721dfb..5b28af7bf 100644 --- a/docs/lua/functions.md +++ b/docs/lua/functions.md @@ -632,6 +632,7 @@ - [get_id_from_behavior](functions-3.md#get_id_from_behavior) - [get_id_from_vanilla_behavior](functions-3.md#get_id_from_vanilla_behavior) - [get_behavior_from_id](functions-3.md#get_behavior_from_id) + - [get_vanilla_behavior_from_id](functions-3.md#get_vanilla_behavior_from_id) - [get_behavior_name_from_id](functions-3.md#get_behavior_name_from_id) - [get_id_from_behavior_name](functions-3.md#get_id_from_behavior_name) diff --git a/docs/lua/structs.md b/docs/lua/structs.md index 2f68f2d7c..ac807cb4f 100644 --- a/docs/lua/structs.md +++ b/docs/lua/structs.md @@ -1906,6 +1906,7 @@ | collidedObjs | `Array` <`Object`> | | | collisionData | `Pointer` <`Collision`> | | | behavior | `Pointer` <`BehaviorScript`> | read-only | +| initBhvCommand | `Pointer` <`BehaviorScript`> | read-only | | curBhvCommand | `Pointer` <`BehaviorScript`> | read-only | | bhvStack | `Array` <`integer`> | read-only | | bhvStackIndex | `integer` | read-only | diff --git a/include/behavior_table.h b/include/behavior_table.h index b3ab308f2..6b846ef51 100644 --- a/include/behavior_table.h +++ b/include/behavior_table.h @@ -549,15 +549,17 @@ enum BehaviorId { id_bhv_max_count // must be the last in the list }; -/* |description|Gets a behavior ID from a behavior script|descriptionEnd| */ +/* |description|Gets the behavior ID of the provided `behavior`|descriptionEnd| */ enum BehaviorId get_id_from_behavior(const BehaviorScript* behavior); -/* |description|Gets a behavior ID from only vanilla behavior scripts|descriptionEnd| */ +/* |description|Gets the behavior ID of the provided `behavior` if it's a vanilla behavior, `id_bhv_max_count` otherwise|descriptionEnd| */ enum BehaviorId get_id_from_vanilla_behavior(const BehaviorScript* behavior); -/* |description|Gets a behavior script from a behavior ID|descriptionEnd| */ +/* |description|Gets the behavior script corresponding to the provided `id`|descriptionEnd| */ const BehaviorScript* get_behavior_from_id(enum BehaviorId id); -/* |description|Gets a behavior name from a behavior ID (bhvMyGreatMODCustom004)|descriptionEnd| */ +/* |description|Gets the behavior script corresponding to the provided `id` if it's a vanilla behavior, `nil` otherwise|descriptionEnd| */ +const BehaviorScript* get_vanilla_behavior_from_id(enum BehaviorId id); +/* |description|Gets the behavior name from the provided `id` (bhvMyGreatMODCustom004)|descriptionEnd| */ const char* get_behavior_name_from_id(enum BehaviorId id); -/* |description|Gets a behavior ID from a behavior name|descriptionEnd| */ +/* |description|Gets the behavior ID corresponding to the provided `name`|descriptionEnd| */ enum BehaviorId get_id_from_behavior_name(const char* name); #endif diff --git a/include/types.h b/include/types.h index a8bd4c792..618f58726 100644 --- a/include/types.h +++ b/include/types.h @@ -258,6 +258,7 @@ struct Object void *respawnInfo; void (*areaTimerRunOnceCallback)(void); const BehaviorScript *behavior; + const BehaviorScript *initBhvCommand; const BehaviorScript *curBhvCommand; uintptr_t bhvStack[OBJECT_MAX_BHV_STACK]; diff --git a/src/engine/behavior_script.c b/src/engine/behavior_script.c index 316b5b82d..00daaecd0 100644 --- a/src/engine/behavior_script.c +++ b/src/engine/behavior_script.c @@ -1372,21 +1372,17 @@ cur_obj_update_begin:; // Execute the behavior script. gCurBhvCommand = gCurrentObject->curBhvCommand; - u8 skipBehavior = smlua_call_behavior_hook(&gCurBhvCommand, gCurrentObject, true); + do { + if (!gCurBhvCommand) { break; } - if (!skipBehavior) { - do { - if (!gCurBhvCommand) { break; } + u32 index = *gCurBhvCommand >> 24; + if (index >= BEHAVIOR_CMD_TABLE_MAX) { break; } - u32 index = *gCurBhvCommand >> 24; - if (index >= BEHAVIOR_CMD_TABLE_MAX) { break; } + bhvCmdProc = BehaviorCmdTable[index]; + bhvProcResult = bhvCmdProc(); + } while (bhvProcResult == BHV_PROC_CONTINUE); - bhvCmdProc = BehaviorCmdTable[index]; - bhvProcResult = bhvCmdProc(); - } while (bhvProcResult == BHV_PROC_CONTINUE); - } - - smlua_call_behavior_hook(&gCurBhvCommand, gCurrentObject, false); + smlua_call_behavior_hook(gCurrentObject); gCurrentObject->curBhvCommand = gCurBhvCommand; // Increment the object's timer. diff --git a/src/game/memory.h b/src/game/memory.h index ba7b9186d..0584d1646 100644 --- a/src/game/memory.h +++ b/src/game/memory.h @@ -83,6 +83,13 @@ bool growing_array_swap_and_pop(struct GrowingArray *array, void *ptr); void growing_array_free(struct GrowingArray **array); void growing_array_debug_print(struct GrowingArray *array, const char *name, s32 x, s32 y); +#define growing_array_for_each_(array, type, item) \ + for (type **_head_ = (type **)((array) != NULL ? (array)->buffer : NULL), \ + **_tail_ = _head_ + ((array) != NULL ? (array)->count : 0), \ + * item = NULL; \ + _head_ != NULL && _head_ != _tail_ && (item = *_head_, TRUE); \ + _head_++) + void alloc_display_list_reset(void); void *alloc_display_list(u32 size); diff --git a/src/game/object_helpers.c b/src/game/object_helpers.c index 8705b804a..df086d6ab 100644 --- a/src/game/object_helpers.c +++ b/src/game/object_helpers.c @@ -345,7 +345,8 @@ void obj_set_held_state(struct Object *obj, const BehaviorScript *heldBehavior) obj->heldByPlayerIndex = 0; } } else { - obj->curBhvCommand = segmented_to_virtual(smlua_override_behavior(heldBehavior)); + obj->initBhvCommand = smlua_get_behavior_command(heldBehavior); + obj->curBhvCommand = obj->initBhvCommand; obj->bhvStackIndex = 0; } } diff --git a/src/game/spawn_object.c b/src/game/spawn_object.c index f84adefbc..50e8ce8af 100644 --- a/src/game/spawn_object.c +++ b/src/game/spawn_object.c @@ -374,7 +374,6 @@ static void snap_object_to_floor(struct Object *obj) { struct Object *create_object(const BehaviorScript *bhvScript) { if (!bhvScript) { return NULL; } s32 objListIndex = OBJ_LIST_DEFAULT; - bool luaBehavior = smlua_is_behavior_hooked(bhvScript); const BehaviorScript *behavior = smlua_override_behavior(bhvScript); // If the first behavior script command is "begin ", then @@ -392,7 +391,8 @@ struct Object *create_object(const BehaviorScript *bhvScript) { struct Object *obj = allocate_object(objList); if (obj == NULL) { return NULL; } - obj->curBhvCommand = luaBehavior ? bhvScript : behavior; + obj->initBhvCommand = smlua_get_behavior_command(bhvScript); + obj->curBhvCommand = obj->initBhvCommand; obj->behavior = behavior; if (objListIndex == OBJ_LIST_UNIMPORTANT) { diff --git a/src/pc/lua/smlua_cobject_autogen.c b/src/pc/lua/smlua_cobject_autogen.c index 10461a038..8f6726de6 100644 --- a/src/pc/lua/smlua_cobject_autogen.c +++ b/src/pc/lua/smlua_cobject_autogen.c @@ -1478,7 +1478,7 @@ static struct LuaObjectField sMarioStateFields[LUA_MARIO_STATE_FIELD_COUNT] = { static struct LuaObjectField sModFields[LUA_MOD_FIELD_COUNT] = { { "basePath", LVT_STRING, offsetof(struct Mod, basePath), true, LOT_NONE }, { "category", LVT_STRING_P, offsetof(struct Mod, category), true, LOT_NONE }, - { "customBehaviorIndex", LVT_U8, offsetof(struct Mod, customBehaviorIndex), true, LOT_NONE }, + { "customBehaviorIndex", LVT_U16, offsetof(struct Mod, customBehaviorIndex), true, LOT_NONE }, { "description", LVT_STRING_P, offsetof(struct Mod, description), true, LOT_NONE }, { "enabled", LVT_BOOL, offsetof(struct Mod, enabled), true, LOT_NONE }, { "fileCapacity", LVT_U16, offsetof(struct Mod, fileCapacity), true, LOT_NONE }, @@ -1602,7 +1602,7 @@ static struct LuaObjectField sNetworkPlayerFields[LUA_NETWORK_PLAYER_FIELD_COUNT { "type", LVT_U8, offsetof(struct NetworkPlayer, type), true, LOT_NONE }, }; -#define LUA_OBJECT_FIELD_COUNT 763 +#define LUA_OBJECT_FIELD_COUNT 764 static struct LuaObjectField sObjectFields[LUA_OBJECT_FIELD_COUNT] = { { "activeFlags", LVT_S16, offsetof(struct Object, activeFlags), false, LOT_NONE }, { "allowRemoteInteractions", LVT_U8, offsetof(struct Object, allowRemoteInteractions), false, LOT_NONE }, @@ -1629,6 +1629,7 @@ static struct LuaObjectField sObjectFields[LUA_OBJECT_FIELD_COUNT] = { { "hookRender", LVT_U8, offsetof(struct Object, hookRender), false, LOT_NONE }, { "hurtboxHeight", LVT_F32, offsetof(struct Object, hurtboxHeight), false, LOT_NONE }, { "hurtboxRadius", LVT_F32, offsetof(struct Object, hurtboxRadius), false, LOT_NONE }, + { "initBhvCommand", LVT_BEHAVIORSCRIPT_P, offsetof(struct Object, initBhvCommand), true, LOT_POINTER }, { "numCollidedObjs", LVT_S16, offsetof(struct Object, numCollidedObjs), false, LOT_NONE }, { "numSurfaces", LVT_U32, offsetof(struct Object, numSurfaces), true, LOT_NONE }, { "o1UpForceSpawn", LVT_S32, offsetof(struct Object, o1UpForceSpawn), false, LOT_NONE }, diff --git a/src/pc/lua/smlua_constants_autogen.c b/src/pc/lua/smlua_constants_autogen.c index d9f006407..0e22ae050 100644 --- a/src/pc/lua/smlua_constants_autogen.c +++ b/src/pc/lua/smlua_constants_autogen.c @@ -3581,7 +3581,6 @@ char gSmluaConstants[] = "" "HOOK_BEFORE_PLAY_MODE_UPDATE=67\n" "HOOK_ON_PLAY_MODE_UPDATE=68\n" "HOOK_MAX=69\n" -"MAX_HOOKED_BEHAVIORS=1024\n" "HUD_DISPLAY_LIVES=0\n" "HUD_DISPLAY_COINS=1\n" "HUD_DISPLAY_STARS=2\n" diff --git a/src/pc/lua/smlua_functions_autogen.c b/src/pc/lua/smlua_functions_autogen.c index 4ebaff303..90f8276f9 100644 --- a/src/pc/lua/smlua_functions_autogen.c +++ b/src/pc/lua/smlua_functions_autogen.c @@ -9974,6 +9974,23 @@ int smlua_func_get_behavior_from_id(lua_State* L) { return 1; } +int smlua_func_get_vanilla_behavior_from_id(lua_State* L) { + if (L == NULL) { return 0; } + + int top = lua_gettop(L); + if (top != 1) { + LOG_LUA_LINE("Improper param count for '%s': Expected %u, Received %u", "get_vanilla_behavior_from_id", 1, top); + return 0; + } + + int id = smlua_to_integer(L, 1); + if (!gSmLuaConvertSuccess) { LOG_LUA("Failed to convert parameter %u for function '%s'", 1, "get_vanilla_behavior_from_id"); return 0; } + + smlua_push_pointer(L, LVT_BEHAVIORSCRIPT_P, (void*)get_vanilla_behavior_from_id(id), NULL); + + return 1; +} + int smlua_func_get_behavior_name_from_id(lua_State* L) { if (L == NULL) { return 0; } @@ -37717,6 +37734,7 @@ void smlua_bind_functions_autogen(void) { smlua_bind_function(L, "get_id_from_behavior", smlua_func_get_id_from_behavior); smlua_bind_function(L, "get_id_from_vanilla_behavior", smlua_func_get_id_from_vanilla_behavior); smlua_bind_function(L, "get_behavior_from_id", smlua_func_get_behavior_from_id); + smlua_bind_function(L, "get_vanilla_behavior_from_id", smlua_func_get_vanilla_behavior_from_id); smlua_bind_function(L, "get_behavior_name_from_id", smlua_func_get_behavior_name_from_id); smlua_bind_function(L, "get_id_from_behavior_name", smlua_func_get_id_from_behavior_name); diff --git a/src/pc/lua/smlua_hooks.c b/src/pc/lua/smlua_hooks.c index 0984c8223..b4eb31a9d 100644 --- a/src/pc/lua/smlua_hooks.c +++ b/src/pc/lua/smlua_hooks.c @@ -6,6 +6,7 @@ #include "behavior_commands.h" #include "pc/mods/mod.h" #include "game/object_list_processor.h" +#include "game/object_helpers.h" #include "pc/djui/djui_chat_message.h" #include "pc/crash_handler.h" #include "game/hud.h" @@ -30,7 +31,6 @@ extern void smlua_new_vec3f(Vec3f src); extern void smlua_get_vec3f(Vec3f dest, int index); #define MAX_HOOKED_REFERENCES 64 -#define LUA_BEHAVIOR_FLAG (1 << 15) u64* gBehaviorOffset = &gPcDebug.bhvOffset; @@ -699,112 +699,128 @@ u32 smlua_get_action_interaction_type(struct MarioState* m) { // hooked behaviors // ////////////////////// -struct LuaHookedBehavior gHookedBehaviors[MAX_HOOKED_BEHAVIORS] = { 0 }; -int gHookedBehaviorsCount = 0; +struct GrowingArray *gHookedBehaviors = NULL; -enum BehaviorId smlua_get_original_behavior_id(const BehaviorScript* behavior) { - enum BehaviorId id = get_id_from_behavior(behavior); - for (int i = 0; i < gHookedBehaviorsCount; i++) { - struct LuaHookedBehavior* hooked = &gHookedBehaviors[i]; - if (hooked->behavior == behavior) { - id = hooked->overrideId; +static struct LuaHookedBehavior *smlua_find_hooked_behavior(enum BehaviorId id) { + growing_array_for_each_(gHookedBehaviors, struct LuaHookedBehavior, hooked) { + if (hooked->behaviorId == id || hooked->customId == id) { + return hooked; } } - return id; -} - -const BehaviorScript* smlua_override_behavior(const BehaviorScript *behavior) { - lua_State *L = gLuaState; - if (L == NULL) { return behavior; } - - enum BehaviorId id = get_id_from_behavior(behavior); - const BehaviorScript *hookedBehavior = smlua_get_hooked_behavior_from_id(id, false); - if (hookedBehavior != NULL) { return hookedBehavior; } - return behavior + *gBehaviorOffset; -} - -const BehaviorScript* smlua_get_hooked_behavior_from_id(enum BehaviorId id, bool returnOriginal) { - lua_State *L = gLuaState; - if (L == NULL) { return NULL; } - - for (int i = 0; i < gHookedBehaviorsCount; i++) { - struct LuaHookedBehavior* hooked = &gHookedBehaviors[i]; - if (hooked->behaviorId != id && hooked->overrideId != id) { continue; } - if (returnOriginal && !hooked->replace) { return hooked->originalBehavior; } - return hooked->behavior; - } return NULL; } -bool smlua_is_behavior_hooked(const BehaviorScript *behavior) { - lua_State *L = gLuaState; - if (L == NULL) { return false; } - - enum BehaviorId id = get_id_from_behavior(behavior); - for (int i = 0; i < gHookedBehaviorsCount; i++) { - struct LuaHookedBehavior *hooked = &gHookedBehaviors[i]; - if (hooked->behaviorId != id && hooked->overrideId != id) { continue; } - return hooked->luaBehavior; - } - - return false; +static struct LuaHookedBehavior *smlua_create_hooked_behavior() { + struct LuaHookedBehavior *hooked = growing_array_alloc(gHookedBehaviors, sizeof(struct LuaHookedBehavior)); + hooked->bhvNames = growing_array_init(NULL, 4, malloc, free); + hooked->initCallbacks = growing_array_init(NULL, 4, malloc, free); + hooked->loopCallbacks = growing_array_init(NULL, 4, malloc, free); + return hooked; } -const char* smlua_get_name_from_hooked_behavior_id(enum BehaviorId id) { - for (int i = 0; i < gHookedBehaviorsCount; i++) { - struct LuaHookedBehavior *hooked = &gHookedBehaviors[i]; - if (hooked->behaviorId != id && hooked->overrideId != id) { continue; } - return hooked->bhvName; +// Replace the original behavior with a custom behavior if it exists. +const BehaviorScript *smlua_override_behavior(const BehaviorScript *behavior) { + enum BehaviorId id = get_id_from_behavior(behavior); + struct LuaHookedBehavior *hooked = smlua_find_hooked_behavior(id); + if (hooked) { + return hooked->script; + } + return behavior + *gBehaviorOffset; +} + +const BehaviorScript *smlua_get_original_behavior_from_id(enum BehaviorId id) { + struct LuaHookedBehavior *hooked = smlua_find_hooked_behavior(id); + if (hooked) { + if (hooked->type == LUA_BEHAVIOR_TYPE_CALLBACKS) { + const BehaviorScript *script = get_vanilla_behavior_from_id(id); + if (script) { + return script; + } + } + return hooked->script; + } + return NULL; +} + +// Return the first behavior command that will be executed by a freshly created object. +const BehaviorScript *smlua_get_behavior_command(const BehaviorScript *behavior) { + enum BehaviorId id = get_id_from_behavior(behavior); + struct LuaHookedBehavior *hooked = smlua_find_hooked_behavior(id); + + // Lua and custom behaviors only + if (hooked && hooked->type > LUA_BEHAVIOR_TYPE_CALLBACKS) { + return hooked->script; + } + return behavior; +} + +const char* smlua_get_behavior_name_from_id(enum BehaviorId id) { + struct LuaHookedBehavior *hooked = smlua_find_hooked_behavior(id); + if (hooked) { + return (const char *) hooked->bhvNames->buffer[hooked->bhvNames->count - 1]; // return the last name registered } return NULL; } int smlua_hook_custom_bhv(BehaviorScript *bhvScript, const char *bhvName) { - if (gHookedBehaviorsCount >= MAX_HOOKED_BEHAVIORS) { - LOG_ERROR("Hooked behaviors exceeded maximum references!"); - return 0; - } + enum BehaviorId id = get_id_from_behavior(bhvScript); - u32 originalBehaviorId = get_id_from_behavior(bhvScript); - - if (originalBehaviorId == id_bhvMario) { + // Can't hook Mario + if (id == id_bhvMario) { LOG_LUA_LINE("Cannot hook Mario's behavior. Use HOOK_MARIO_UPDATE and HOOK_BEFORE_MARIO_UPDATE."); return 0; } - u8 newBehavior = originalBehaviorId >= id_bhv_max_count; + // Do not allow custom behaviors to hook non-vanilla ids + bool isVanillaId = id < id_bhv_max_count; + struct LuaHookedBehavior *hooked = isVanillaId ? smlua_find_hooked_behavior(id) : NULL; - struct LuaHookedBehavior *hooked = &gHookedBehaviors[gHookedBehaviorsCount]; - u16 customBehaviorId = (gHookedBehaviorsCount & 0xFFFF) | LUA_BEHAVIOR_FLAG; - hooked->behavior = bhvScript; - hooked->behavior[1] = (BehaviorScript)BC_B0H(0x39, customBehaviorId); // This is ID(customBehaviorId) - hooked->behaviorId = customBehaviorId; - hooked->overrideId = newBehavior ? customBehaviorId : originalBehaviorId; - hooked->originalId = originalBehaviorId; - hooked->originalBehavior = newBehavior ? bhvScript : get_behavior_from_id(originalBehaviorId); - hooked->bhvName = bhvName; - hooked->initReference = 0; - hooked->loopReference = 0; - hooked->replace = true; - hooked->luaBehavior = false; - hooked->mod = gLuaActiveMod; - hooked->modFile = gLuaActiveModFile; + // Create a new hooked behavior + if (!hooked) { + if (gHookedBehaviors->count >= MAX_HOOKED_BEHAVIORS) { + LOG_ERROR("Hooked behaviors exceeded maximum references!"); + return 0; + } - gHookedBehaviorsCount++; + enum BehaviorId customId = LUA_BEHAVIOR_START + gHookedBehaviors->count; + + hooked = smlua_create_hooked_behavior(); + hooked->behaviorId = isVanillaId ? id : customId; + hooked->customId = customId; + hooked->type = LUA_BEHAVIOR_TYPE_CUSTOM; + hooked->script = bhvScript; + hooked->script[1] = (BehaviorScript) ID(customId); + + } else { + + // Behavior script can be replaced as long as it's not a custom behavior + if (hooked->type != LUA_BEHAVIOR_TYPE_CUSTOM) { + free(hooked->script); + hooked->type = LUA_BEHAVIOR_TYPE_CUSTOM; + hooked->script = bhvScript; + hooked->script[1] = (BehaviorScript) ID(hooked->customId); + } else { + LOG_LUA_WARNING("Hook behavior: the behavior script for the behavior %s is custom and cannot be changed", (const char *) hooked->bhvNames->buffer[hooked->bhvNames->count - 1]); + } + } + + // Add a name to that behavior + char *name = growing_array_alloc(hooked->bhvNames, strlen(bhvName) + 1); + strcpy(name, bhvName); // We want to push the behavior into the global LUA state. So mods can access it. // It's also used for some things that would normally access a LUA behavior instead. - lua_State* L = gLuaState; + lua_State *L = gLuaState; if (L != NULL) { - lua_pushinteger(L, customBehaviorId); + lua_pushinteger(L, hooked->behaviorId); lua_setglobal(L, bhvName); - LOG_INFO("Registered custom behavior: 0x%04hX - %s", customBehaviorId, bhvName); + LOG_INFO("Registered custom behavior for behavior id 0x%04hX (custom id: 0x%04hX, custom name: %s)", hooked->behaviorId, hooked->customId, bhvName); } return 1; } -int smlua_hook_behavior(lua_State* L) { +int smlua_hook_behavior(lua_State *L) { if (L == NULL) { return 0; } if (!smlua_functions_valid_param_range(L, 5, 6)) { return 0; } @@ -815,88 +831,154 @@ int smlua_hook_behavior(lua_State* L) { int paramCount = lua_gettop(L); - if (gHookedBehaviorsCount >= MAX_HOOKED_BEHAVIORS) { - LOG_LUA_LINE("Hooked behaviors exceeded maximum references!"); - return 0; - } - - bool noOverrideId = (lua_type(L, 1) == LUA_TNIL); - gSmLuaConvertSuccess = true; - lua_Integer overrideBehaviorId = noOverrideId ? 0xFFFFFF : smlua_to_integer(L, 1); - if (!gSmLuaConvertSuccess) { - LOG_LUA_LINE("Hook behavior: tried to override invalid behavior: %lld, %u", overrideBehaviorId, gSmLuaConvertSuccess); - return 0; - } - - if (overrideBehaviorId == id_bhvMario) { - LOG_LUA_LINE("Hook behavior: cannot hook Mario's behavior. Use HOOK_MARIO_UPDATE and HOOK_BEFORE_MARIO_UPDATE."); - return 0; - } - - lua_Integer objectList = smlua_to_integer(L, 2); - if (objectList <= 0 || objectList >= NUM_OBJ_LISTS || !gSmLuaConvertSuccess) { - LOG_LUA_LINE("Hook behavior: tried use invalid object list: %lld, %u", objectList, gSmLuaConvertSuccess); - return 0; - } - - bool replaceBehavior = smlua_to_boolean(L, 3); - if (!gSmLuaConvertSuccess) { - LOG_LUA_LINE("Hook behavior: could not parse replaceBehavior"); - return 0; - } - const BehaviorScript* originalBehavior = noOverrideId ? NULL : get_behavior_from_id(overrideBehaviorId); - if (originalBehavior == NULL) { - replaceBehavior = true; - } - - int initReference = 0; - int initReferenceType = lua_type(L, 4); - if (initReferenceType == LUA_TNIL) { - // nothing - } else if (initReferenceType == LUA_TFUNCTION) { - // get reference - lua_pushvalue(L, 4); - initReference = luaL_ref(L, LUA_REGISTRYINDEX); - } else { - LOG_LUA_LINE("Hook behavior: tried to reference non-function for init"); - return 0; - } - - int loopReference = 0; - int loopReferenceType = lua_type(L, 5); - if (loopReferenceType == LUA_TNIL) { - // nothing - } else if (loopReferenceType == LUA_TFUNCTION) { - // get reference - lua_pushvalue(L, 5); - loopReference = luaL_ref(L, LUA_REGISTRYINDEX); - } else { - LOG_LUA_LINE("Hook behavior: tried to reference non-function for loop"); - return 0; - } - - const char *bhvName = NULL; - if (paramCount >= 6) { - int bhvNameType = lua_type(L, 6); - if (bhvNameType == LUA_TNIL) { - // nothing - } else if (bhvNameType == LUA_TSTRING) { - bhvName = smlua_to_string(L, 6); - if (!bhvName || !gSmLuaConvertSuccess) { - LOG_LUA_LINE("Hook behavior: could not parse bhvName"); - return 0; - } - } else { - LOG_LUA_LINE("Hook behavior: invalid type passed for argument bhvName: %u", bhvNameType); + // Get behavior id + enum BehaviorId id = LUA_BEHAVIOR_NEW_ID; + if (lua_type(L, 1) != LUA_TNIL) { + gSmLuaConvertSuccess = true; + id = (enum BehaviorId) (u16) smlua_to_integer(L, 1); + if (!gSmLuaConvertSuccess) { + LOG_LUA_LINE("Hook behavior: tried to override invalid behavior id"); return 0; } } - // If not provided, generate generic behavior name: bhvCustom + // Can't hook Mario + if (id == id_bhvMario) { + LOG_LUA_LINE("Hook behavior: cannot hook Mario's behavior. Use HOOK_MARIO_UPDATE and HOOK_BEFORE_MARIO_UPDATE."); + return 0; + } + + // Get object list + enum ObjectList objectList = (enum ObjectList) (u8) smlua_to_integer(L, 2); + if (!gSmLuaConvertSuccess || objectList >= NUM_OBJ_LISTS) { + LOG_LUA_LINE("Hook behavior: tried use invalid object list: %d, %u", objectList, gSmLuaConvertSuccess); + return 0; + } + + // Check replace if it's a vanilla behavior hook + bool replaceBehavior = true; + bool isVanillaId = id < id_bhv_max_count; + if (isVanillaId) { + replaceBehavior = smlua_to_boolean(L, 3); + if (!gSmLuaConvertSuccess) { + LOG_LUA_LINE("Hook behavior: could not convert replaceBehavior to boolean"); + return 0; + } + } + + // Get init function + int initReference = 0; + int initReferenceType = lua_type(L, 4); + switch (initReferenceType) { + case LUA_TNIL: break; + + case LUA_TFUNCTION: { + lua_pushvalue(L, 4); + initReference = luaL_ref(L, LUA_REGISTRYINDEX); + } break; + + default: { + LOG_LUA_LINE("Hook behavior: invalid type passed for argument initFunction: '%s', should be '%s'", lua_typename(L, initReferenceType), lua_typename(L, LUA_TFUNCTION)); + } return 0; + } + + // Get loop function + int loopReference = 0; + int loopReferenceType = lua_type(L, 5); + switch (loopReferenceType) { + case LUA_TNIL: break; + + case LUA_TFUNCTION: { + lua_pushvalue(L, 5); + loopReference = luaL_ref(L, LUA_REGISTRYINDEX); + } break; + + default: { + LOG_LUA_LINE("Hook behavior: invalid type passed for argument loopFunction: '%s', should be '%s'", lua_typename(L, loopReferenceType), lua_typename(L, LUA_TFUNCTION)); + } return 0; + } + + // Get name + const char *bhvName = NULL; + if (paramCount >= 6) { + int bhvNameType = lua_type(L, 6); + switch (bhvNameType) { + case LUA_TNIL: break; + + case LUA_TSTRING: { + bhvName = smlua_to_string(L, 6); + if (!bhvName || !gSmLuaConvertSuccess) { + LOG_LUA_LINE("Hook behavior: could not parse bhvName"); + return 0; + } + } break; + + default: { + LOG_LUA_LINE("Hook behavior: invalid type passed for argument bhvName: '%s', should be '%s'", lua_typename(L, bhvNameType), lua_typename(L, LUA_TSTRING)); + } return 0; + } + } + + // Get an existing hooked behavior or create a new one + // Do not allow arbitrary non-vanilla ids to be hooked + struct LuaHookedBehavior *hooked = NULL; + if (id != LUA_BEHAVIOR_NEW_ID) { + hooked = smlua_find_hooked_behavior(id); + if (!hooked && !isVanillaId) { + LOG_LUA_LINE("Hook behavior: behavior id %u is not valid, cannot hook non-existing non-vanilla behaviors", id); + return 0; + } + } + if (!hooked) { + if (gHookedBehaviors->count >= MAX_HOOKED_BEHAVIORS) { + LOG_ERROR("Hooked behaviors exceeded maximum references!"); + return 0; + } + + enum BehaviorId customId = LUA_BEHAVIOR_START + gHookedBehaviors->count; + + hooked = smlua_create_hooked_behavior(); + hooked->behaviorId = id != LUA_BEHAVIOR_NEW_ID ? id : customId; + hooked->customId = customId; + hooked->type = replaceBehavior ? LUA_BEHAVIOR_TYPE_LUA : LUA_BEHAVIOR_TYPE_CALLBACKS; + hooked->script = calloc(4, sizeof(BehaviorScript)); + hooked->script[0] = (BehaviorScript) BEGIN(objectList); + hooked->script[1] = (BehaviorScript) ID(customId); + hooked->script[2] = (BehaviorScript) BREAK(); + hooked->script[3] = (BehaviorScript) BREAK(); + + } else { + + // Behavior script can be replaced as long as it's not a custom behavior + if (replaceBehavior) { + switch (hooked->type) { + case LUA_BEHAVIOR_TYPE_CALLBACKS: { + hooked->type = LUA_BEHAVIOR_TYPE_LUA; + hooked->script[0] = (BehaviorScript) BEGIN(objectList); // Override object list + } break; + + case LUA_BEHAVIOR_TYPE_LUA: { + // nothing to change + } break; + + case LUA_BEHAVIOR_TYPE_CUSTOM: { + LOG_LUA_WARNING("Hook behavior: the behavior script for the behavior %s is custom and cannot be changed", (const char *) hooked->bhvNames->buffer[hooked->bhvNames->count - 1]); + } break; + } + } + + // Warn user if trying to change the object list + enum ObjectList hookedObjectList = get_object_list_from_behavior(hooked->script); + if (hookedObjectList != objectList) { + LOG_LUA_WARNING("Hook behavior: trying to change the object list of the existing hooked behavior %s: %d (should be %d)", (const char *) hooked->bhvNames->buffer[hooked->bhvNames->count - 1], objectList, hookedObjectList); + } + } + + // If not provided and the hooked behavior has no name yet, generate generic behavior name: bhvCustom // - is the mod name in CamelCase format, alphanumeric chars only - // - is in 3-digit numeric format, ranged from 001 to 256 + // - is in 3-digit numeric format (from 001 to 999, no longer applies for index greater than 1000) // For example, the 4th unnamed behavior of the mod "my-great_MOD" will be named "bhvMyGreatMODCustom004" - if (!bhvName) { + if (!bhvName && hooked->bhvNames->count == 0) { static char sGenericBhvName[MOD_NAME_MAX_LENGTH + 16]; s32 i = 3; snprintf(sGenericBhvName, 4, "bhv"); @@ -918,90 +1000,76 @@ int smlua_hook_behavior(lua_State* L) { bhvName = sGenericBhvName; } - struct LuaHookedBehavior* hooked = &gHookedBehaviors[gHookedBehaviorsCount]; - u16 customBehaviorId = (gHookedBehaviorsCount & 0xFFFF) | LUA_BEHAVIOR_FLAG; - hooked->behavior = calloc(4, sizeof(BehaviorScript)); - hooked->behavior[0] = (BehaviorScript)BC_BB(0x00, objectList); // This is BEGIN(objectList) - hooked->behavior[1] = (BehaviorScript)BC_B0H(0x39, customBehaviorId); // This is ID(customBehaviorId) - hooked->behavior[2] = (BehaviorScript)BC_B(0x0A); // This is BREAK() - hooked->behavior[3] = (BehaviorScript)BC_B(0x0A); // This is BREAK() - hooked->behaviorId = customBehaviorId; - hooked->overrideId = noOverrideId ? customBehaviorId : overrideBehaviorId; - hooked->originalId = customBehaviorId; // For LUA behaviors. The only behavior id they have IS their custom one. - hooked->originalBehavior = originalBehavior ? originalBehavior : hooked->behavior; - hooked->bhvName = bhvName; - hooked->initReference = initReference; - hooked->loopReference = loopReference; - hooked->replace = replaceBehavior; - hooked->luaBehavior = true; - hooked->mod = gLuaActiveMod; - hooked->modFile = gLuaActiveModFile; + // Add name + if (bhvName) { + char *name = growing_array_alloc(hooked->bhvNames, strlen(bhvName) + 1); + strcpy(name, bhvName); + } - gHookedBehaviorsCount++; + // Add init function + if (initReference) { + struct LuaHookedBehaviorCallback *callback = growing_array_alloc(hooked->initCallbacks, sizeof(struct LuaHookedBehaviorCallback)); + callback->ref = initReference; + callback->mod = gLuaActiveMod; + callback->modFile = gLuaActiveModFile; + } + + // Add loop function + if (loopReference) { + struct LuaHookedBehaviorCallback *callback = growing_array_alloc(hooked->loopCallbacks, sizeof(struct LuaHookedBehaviorCallback)); + callback->ref = loopReference; + callback->mod = gLuaActiveMod; + callback->modFile = gLuaActiveModFile; + } // We want to push the behavior into the global LUA state. So mods can access it. // It's also used for some things that would normally access a LUA behavior instead. - lua_pushinteger(L, customBehaviorId); - lua_setglobal(L, bhvName); - LOG_INFO("Registered custom behavior: 0x%04hX - %s", customBehaviorId, bhvName); + if (bhvName) { + lua_pushinteger(L, hooked->behaviorId); + lua_setglobal(L, bhvName); + } else { + bhvName = hooked->bhvNames->buffer[hooked->bhvNames->count - 1]; // log with last registered name + } + LOG_INFO("Registered Lua behavior for behavior id 0x%04hX (custom id: 0x%04hX, custom name: %s)", hooked->behaviorId, hooked->customId, bhvName); // return behavior ID - lua_pushinteger(L, customBehaviorId); + lua_pushinteger(L, hooked->behaviorId); return 1; } -bool smlua_call_behavior_hook(const BehaviorScript** behavior, struct Object* object, bool before) { +void smlua_call_behavior_hook(struct Object* object) { lua_State* L = gLuaState; - if (L == NULL) { return false; } - for (int i = 0; i < gHookedBehaviorsCount; i++) { - struct LuaHookedBehavior* hooked = &gHookedBehaviors[i]; + if (L == NULL) { return; } - // find behavior - if (object->behavior != hooked->behavior) { - continue; + enum BehaviorId id = get_id_from_behavior(object->behavior); + struct LuaHookedBehavior *hooked = smlua_find_hooked_behavior(id); + if (hooked) { + + // This works for two reasons: + // - A behavior first command is always BEGIN(objList), so, after the first update, curBhvCommand will no longer be initBhvCommand + // - object->curBhvCommand is not updated until the end of this function + bool init = object->curBhvCommand == object->initBhvCommand; + + // Run callbacks one after the other + struct GrowingArray *callbacks = init ? hooked->initCallbacks : hooked->loopCallbacks; + growing_array_for_each_(callbacks, struct LuaHookedBehaviorCallback, callback) { + + // push the callback onto the stack + lua_rawgeti(L, LUA_REGISTRYINDEX, callback->ref); + + // push object + smlua_push_object(L, LOT_OBJECT, object, NULL); + + // call the callback + if (0 != smlua_call_hook(L, 1, 0, 0, callback->mod, callback->modFile)) { + LOG_LUA("Failed to call behavior %s callback for behavior id: %hu", + (init ? "init" : "loop"), hooked->behaviorId + ); + return; + } } - - // Figure out whether to run before or after - if (before && !hooked->replace) { - return false; - } - if (!before && hooked->replace) { - return false; - } - - // This behavior doesn't call it's LUA functions in this manner. It actually uses the normal behavior - // system. - if (!hooked->luaBehavior) { - return false; - } - - // retrieve and remember first run - bool firstRun = (object->curBhvCommand == hooked->originalBehavior) || (object->curBhvCommand == hooked->behavior); - if (firstRun && hooked->replace) { *behavior = &hooked->behavior[1]; } - - // get function and null check it - int reference = firstRun ? hooked->initReference : hooked->loopReference; - if (reference == 0) { - return true; - } - - // push the callback onto the stack - lua_rawgeti(L, LUA_REGISTRYINDEX, reference); - - // push object - smlua_push_object(L, LOT_OBJECT, object, NULL); - - // call the callback - if (0 != smlua_call_hook(L, 1, 0, 0, hooked->mod, hooked->modFile)) { - LOG_LUA("Failed to call the behavior callback: %u", hooked->behaviorId); - return true; - } - - return hooked->replace; } - - return false; } @@ -1802,10 +1870,13 @@ void smlua_hook_replace_function_references(lua_State* L, int oldReference, int smlua_hook_replace_function_reference(L, &hooked->reference, oldReference, newReference); } - for (int i = 0; i < gHookedBehaviorsCount; i++) { - struct LuaHookedBehavior* hooked = &gHookedBehaviors[i]; - smlua_hook_replace_function_reference(L, &hooked->initReference, oldReference, newReference); - smlua_hook_replace_function_reference(L, &hooked->loopReference, oldReference, newReference); + growing_array_for_each_(gHookedBehaviors, struct LuaHookedBehavior, hooked) { + growing_array_for_each_(hooked->initCallbacks, struct LuaHookedBehaviorCallback, callback) { + smlua_hook_replace_function_reference(L, &callback->ref, oldReference, newReference); + } + growing_array_for_each_(hooked->loopCallbacks, struct LuaHookedBehaviorCallback, callback) { + smlua_hook_replace_function_reference(L, &callback->ref, oldReference, newReference); + } } } @@ -1827,6 +1898,7 @@ void smlua_clear_hooks(void) { memset(hooked->actionHookRefs, 0, sizeof(hooked->actionHookRefs)); } sHookedMarioActionsCount = 0; + memset(gLuaMarioActionIndex, 0, sizeof(gLuaMarioActionIndex)); for (int i = 0; i < sHookedChatCommandsCount; i++) { struct LuaHookedChatCommand* hooked = &sHookedChatCommands[i]; @@ -1858,35 +1930,21 @@ void smlua_clear_hooks(void) { } gHookedModMenuElementsCount = 0; - for (int i = 0; i < gHookedBehaviorsCount; i++) { - struct LuaHookedBehavior* hooked = &gHookedBehaviors[i]; + growing_array_for_each_(gHookedBehaviors, struct LuaHookedBehavior, hooked) { - // If this is NULL. We can't do anything with it. - if (hooked->behavior != NULL) { - // If it's a LUA made behavior, The behavior is allocated so reset and free it. - // Otherwise it's a DynOS behavior and it needs to have it's original id put back where it belongs. - if (hooked->luaBehavior) { - // Just free the allocated behavior. - free(hooked->behavior); - } else { - hooked->behavior[1] = (BehaviorScript)BC_B0H(0x39, hooked->originalId); // This is ID(hooked->originalId) - } + // Free behavior script if Lua or change back id if it's custom + if (hooked->type != LUA_BEHAVIOR_TYPE_CUSTOM) { + free(hooked->script); + } else { + hooked->script[1] = (BehaviorScript) ID(hooked->behaviorId); } - // Reset the variables. - hooked->behaviorId = 0; - hooked->overrideId = 0; - hooked->originalId = 0; - hooked->behavior = NULL; - hooked->originalBehavior = NULL; - hooked->initReference = 0; - hooked->loopReference = 0; - hooked->replace = false; - hooked->luaBehavior = false; - hooked->mod = NULL; - hooked->modFile = NULL; + + // Clear arrays. + growing_array_free(&hooked->bhvNames); + growing_array_free(&hooked->initCallbacks); + growing_array_free(&hooked->loopCallbacks); } - gHookedBehaviorsCount = 0; - memset(gLuaMarioActionIndex, 0, sizeof(gLuaMarioActionIndex)); + gHookedBehaviors = growing_array_init(gHookedBehaviors, 16, malloc, free); } void smlua_bind_hooks(void) { diff --git a/src/pc/lua/smlua_hooks.h b/src/pc/lua/smlua_hooks.h index 67c795b85..5f8958c97 100644 --- a/src/pc/lua/smlua_hooks.h +++ b/src/pc/lua/smlua_hooks.h @@ -138,25 +138,35 @@ extern u32 gLuaMarioActionIndex[]; extern struct LuaHookedModMenuElement gHookedModMenuElements[]; extern int gHookedModMenuElementsCount; -#define MAX_HOOKED_BEHAVIORS 1024 +#define LUA_BEHAVIOR_START (1 << 15) +#define LUA_BEHAVIOR_NEW_ID (UINT16_MAX) // behavior id is 2-bytes long +#define MAX_HOOKED_BEHAVIORS (LUA_BEHAVIOR_NEW_ID - LUA_BEHAVIOR_START) -struct LuaHookedBehavior { - u32 behaviorId; - u32 overrideId; - u32 originalId; - BehaviorScript *behavior; - const BehaviorScript* originalBehavior; - const char* bhvName; - int initReference; - int loopReference; - bool replace; - bool luaBehavior; - struct Mod* mod; - struct ModFile* modFile; +enum LuaHookedBehaviorType { + LUA_BEHAVIOR_TYPE_CALLBACKS, // Lua callbacks on top of an existing behavior + LUA_BEHAVIOR_TYPE_LUA, // Full Lua custom behavior + LUA_BEHAVIOR_TYPE_CUSTOM, // DynOS custom behavior (with or without callbacks) }; -extern int gHookedBehaviorsCount; -extern struct LuaHookedBehavior gHookedBehaviors[MAX_HOOKED_BEHAVIORS]; +struct LuaHookedBehaviorCallback { + int ref; + struct Mod *mod; + struct ModFile *modFile; +}; + +struct LuaHookedBehavior { + enum BehaviorId behaviorId; // the original behavior id + enum BehaviorId customId; // unique Lua/custom behavior id + + enum LuaHookedBehaviorType type; + BehaviorScript *script; // Lua/custom behavior script + + struct GrowingArray *bhvNames; // const char * + struct GrowingArray *initCallbacks; // struct LuaHookedBehaviorCallback * + struct GrowingArray *loopCallbacks; // struct LuaHookedBehaviorCallback * +}; + +extern struct GrowingArray *gHookedBehaviors; #define OUTPUT #define SMLUA_EVENT_HOOK(hookEventType, hookReturn, ...) bool smlua_call_event_hooks_##hookEventType(__VA_ARGS__); @@ -168,12 +178,11 @@ extern struct LuaHookedBehavior gHookedBehaviors[MAX_HOOKED_BEHAVIORS]; smlua_call_event_hooks_##hookEventType(__VA_ARGS__) int smlua_hook_custom_bhv(BehaviorScript *bhvScript, const char *bhvName); -enum BehaviorId smlua_get_original_behavior_id(const BehaviorScript* behavior); -const BehaviorScript* smlua_override_behavior(const BehaviorScript* behavior); -const BehaviorScript* smlua_get_hooked_behavior_from_id(enum BehaviorId id, bool returnOriginal); -bool smlua_is_behavior_hooked(const BehaviorScript *behavior); -const char* smlua_get_name_from_hooked_behavior_id(enum BehaviorId id); -bool smlua_call_behavior_hook(const BehaviorScript** behavior, struct Object* object, bool before); +const BehaviorScript *smlua_override_behavior(const BehaviorScript *behavior); +const BehaviorScript *smlua_get_original_behavior_from_id(enum BehaviorId id); +const BehaviorScript *smlua_get_behavior_command(const BehaviorScript *behavior); +const char *smlua_get_behavior_name_from_id(enum BehaviorId id); +void smlua_call_behavior_hook(struct Object *object); int smlua_call_hook(lua_State* L, int nargs, int nresults, int errfunc, struct Mod* activeMod, struct ModFile* activeModFile); bool smlua_call_action_hook(enum LuaActionHookType hookType, struct MarioState* m, s32* cancel); diff --git a/src/pc/mods/mod.h b/src/pc/mods/mod.h index 9f14f361e..68ae465df 100644 --- a/src/pc/mods/mod.h +++ b/src/pc/mods/mod.h @@ -48,7 +48,7 @@ struct Mod { bool ignoreScriptWarnings; bool showedScriptWarning; size_t size; - u8 customBehaviorIndex; + u16 customBehaviorIndex; }; size_t mod_get_lua_size(struct Mod* mod);