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
This commit is contained in:
PeachyPeach 2026-05-19 02:56:08 +02:00 committed by GitHub
parent 925325c5eb
commit 834512a5c5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
24 changed files with 511 additions and 353 deletions

View file

@ -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<br />\n\n'
return s

View file

@ -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

View file

@ -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

View file

@ -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)

View file

@ -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

View file

@ -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(),

View file

@ -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;

View file

@ -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);

View file

@ -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:](#)

View file

@ -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
<br />
## [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:](#)
<br />
## [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)`

View file

@ -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)

View file

@ -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 |

View file

@ -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

View file

@ -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];

View file

@ -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.

View file

@ -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);

View file

@ -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;
}
}

View file

@ -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 <object list>", 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) {

View file

@ -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 },

View file

@ -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"

View file

@ -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);

View file

@ -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: bhv<ModName>Custom<Index>
// 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: bhv<ModName>Custom<Index>
// - <ModName> is the mod name in CamelCase format, alphanumeric chars only
// - <Index> is in 3-digit numeric format, ranged from 001 to 256
// - <Index> 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) {

View file

@ -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);

View file

@ -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);