From c82e45c0a0bfd73a6902865eb953857f82d48533 Mon Sep 17 00:00:00 2001 From: MysterD Date: Thu, 22 Jun 2023 01:13:38 -0700 Subject: [PATCH] Add level script pointer validation --- data/dynos.cpp.h | 3 + data/dynos_bin_lvl.cpp | 15 ++- data/dynos_bin_lvl_validate.cpp | 168 ++++++++++++++++++++++++++++++++ include/level_commands.h | 13 +++ src/engine/level_script.c | 6 +- 5 files changed, 203 insertions(+), 2 deletions(-) create mode 100644 data/dynos_bin_lvl_validate.cpp diff --git a/data/dynos.cpp.h b/data/dynos.cpp.h index bc131a28..af532bd4 100644 --- a/data/dynos.cpp.h +++ b/data/dynos.cpp.h @@ -1091,6 +1091,9 @@ GfxData *DynOS_Lvl_LoadFromBinary(const SysPath &aFilename, const char *aLevelNa void DynOS_Lvl_GeneratePack(const SysPath &aPackFolder); s64 DynOS_Lvl_ParseLevelScriptConstants(const String& _Arg, bool* found); +void DynOS_Lvl_Validate_Begin(); +bool DynOS_Lvl_Validate_RequirePointer(u32 value); + DataNode *DynOS_Bhv_Parse(GfxData *aGfxData, DataNode *aNode, bool aDisplayPercent); GfxData *DynOS_Bhv_LoadFromBinary(const SysPath &aFilename, const char *aBehaviorName); void DynOS_Bhv_GeneratePack(const SysPath &aPackFolder); diff --git a/data/dynos_bin_lvl.cpp b/data/dynos_bin_lvl.cpp index 05a12a03..5a3bec71 100644 --- a/data/dynos_bin_lvl.cpp +++ b/data/dynos_bin_lvl.cpp @@ -993,14 +993,27 @@ static DataNode* DynOS_Lvl_Load(BinFile *aFile, GfxData *aGfxData) aGfxData->mLevelScripts.Add(_Node); } + DynOS_Lvl_Validate_Begin(); + // Read it for (u32 i = 0; i != _Node->mSize; ++i) { u32 _Value = aFile->Read(); + + bool requirePointer = DynOS_Lvl_Validate_RequirePointer(_Value); + void *_Ptr = DynOS_Pointer_Load(aFile, aGfxData, _Value, &_Node->mFlags); if (_Ptr) { + if (!requirePointer) { + PrintError("Didn't expect a pointer while reading level script: %s, %u", _Node->mName, _Value); + } _Node->mData[i] = (uintptr_t) _Ptr; } else { - _Node->mData[i] = (uintptr_t) _Value; + if (requirePointer) { + PrintError("Expected a pointer while reading level script: %s, %u", _Node->mName, _Value); + _Node->mData[i] = 0; + } else { + _Node->mData[i] = (uintptr_t) _Value; + } } } diff --git a/data/dynos_bin_lvl_validate.cpp b/data/dynos_bin_lvl_validate.cpp new file mode 100644 index 00000000..40abd056 --- /dev/null +++ b/data/dynos_bin_lvl_validate.cpp @@ -0,0 +1,168 @@ +#include "dynos.cpp.h" +#include +#include +extern "C" { +#include "include/level_commands.h" +#include "include/model_ids.h" +#include "include/behavior_data.h" +#include "include/surface_terrains.h" +#include "include/seq_ids.h" +#include "level_commands.h" +#include "src/game/level_update.h" +#include "include/dialog_ids.h" +#include "levels/scripts.h" +#include "levels/menu/header.h" +#include "src/game/area.h" +} + +#define POINTER 0xD34DB33F + +struct LevelScriptCommand { + u8 id; + u8 size; + u8 ptrIdx[2]; +}; + +static bool sCommandMapFilled = false; +static std::map sCommandMap; + +static u8 sCurCommandId = 0xFF; +static u8 sCurCommandOffset = 0xFF; + +#define ADD_COMMAND(_cmd) { \ + LevelScript _script[] = { _cmd }; \ + size_t _size = ARRAY_COUNT(_script); \ + LvlCmd_Add(_script, _size); \ +} + +static void LvlCmd_Add(LevelScript script[], size_t size) { + // make sure size isn't crazy + assert(size < 0xFF); + + // find the single pointer index + u8 ptrIdx[2] = { 0xFF, 0xFF }; + for (u8 i = 0; i < size; i++) { + if (script[i] != POINTER) { continue; } + if (ptrIdx[0] == 0xFF) { + ptrIdx[0] = i; + } else { + assert(ptrIdx[1] == 0xFF); + ptrIdx[1] = i; + } + } + + // extract the id and make sure it's unique + u8 id = (u8)(script[0] & 0xFF); + if (sCommandMap.count(id) != 0) { return; } + + // add the command to the map + sCommandMap[id] = { + .id = id, + .size = (u8)size, + .ptrIdx = { ptrIdx[0], ptrIdx[1] }, + }; +} + +static void LvlCmd_Init() { + ADD_COMMAND(EXECUTE(0, 0, 0, POINTER)); + ADD_COMMAND(EXIT_AND_EXECUTE(0, 0, 0, POINTER)); + ADD_COMMAND(EXIT()); + ADD_COMMAND(SLEEP(0)); + ADD_COMMAND(SLEEP_BEFORE_EXIT(0)); + ADD_COMMAND(JUMP(POINTER)); + ADD_COMMAND(JUMP_LINK(POINTER)); + ADD_COMMAND(RETURN()); + ADD_COMMAND(JUMP_LINK_PUSH_ARG(0)); + ADD_COMMAND(JUMP_N_TIMES()); + ADD_COMMAND(LOOP_BEGIN()); + ADD_COMMAND(LOOP_UNTIL(0, 0)); + ADD_COMMAND(JUMP_IF(0, 0, POINTER)); + ADD_COMMAND(JUMP_LINK_IF(0, 0, POINTER)); + ADD_COMMAND(SKIP_IF(0, 0)); + ADD_COMMAND(SKIP()); + ADD_COMMAND(SKIP_NOP()); + ADD_COMMAND(CALL(0, POINTER)); + ADD_COMMAND(CALL_LOOP(0, POINTER)); + ADD_COMMAND(SET_REG(0)); + ADD_COMMAND(PUSH_POOL()); + ADD_COMMAND(POP_POOL()); + ADD_COMMAND(FIXED_LOAD(0, 0, 0)); + ADD_COMMAND(LOAD_RAW(0, 0, 0)); + ADD_COMMAND(LOAD_MIO0(0, 0, 0)); + ADD_COMMAND(LOAD_MARIO_HEAD(0)); + ADD_COMMAND(LOAD_MIO0_TEXTURE(0, 0, 0)); + ADD_COMMAND(INIT_LEVEL()); + ADD_COMMAND(CLEAR_LEVEL()); + ADD_COMMAND(ALLOC_LEVEL_POOL()); + ADD_COMMAND(FREE_LEVEL_POOL()); + ADD_COMMAND(AREA(0, POINTER)); + ADD_COMMAND(END_AREA()); + ADD_COMMAND(LOAD_MODEL_FROM_DL(0, 0, 0)); + ADD_COMMAND(LOAD_MODEL_FROM_GEO(0, POINTER)); + ADD_COMMAND(CMD23(0, 0, 0)); + ADD_COMMAND(OBJECT_WITH_ACTS(0, 0, 0, 0, 0, 0, 0, 0, POINTER, 0)); + ADD_COMMAND(OBJECT(0, 0, 0, 0, 0, 0, 0, 0, POINTER)); + ADD_COMMAND(MARIO(0, 0, POINTER)); + ADD_COMMAND(WARP_NODE(0, 0, 0, 0, 0)); + ADD_COMMAND(PAINTING_WARP_NODE(0, 0, 0, 0, 0)); + ADD_COMMAND(INSTANT_WARP(0, 0, 0, 0, 0)); + ADD_COMMAND(LOAD_AREA(0)); + ADD_COMMAND(CMD2A(0)); + ADD_COMMAND(MARIO_POS(0, 0, 0, 0, 0)); + ADD_COMMAND(CMD2C()); + ADD_COMMAND(CMD2D()); + ADD_COMMAND(TERRAIN(POINTER)); + ADD_COMMAND(ROOMS(POINTER)); + ADD_COMMAND(SHOW_DIALOG(0, 0)); + ADD_COMMAND(TERRAIN_TYPE(0)); + ADD_COMMAND(NOP()); + ADD_COMMAND(TRANSITION(0, 0, 0, 0, 0)); + ADD_COMMAND(BLACKOUT(0)); + ADD_COMMAND(GAMMA(0)); + ADD_COMMAND(SET_BACKGROUND_MUSIC(0, 0)); + ADD_COMMAND(SET_MENU_MUSIC(0)); + ADD_COMMAND(STOP_MUSIC(0)); + ADD_COMMAND(MACRO_OBJECTS(POINTER)); + ADD_COMMAND(CMD3A(0, 0, 0, 0, 0)); + ADD_COMMAND(WHIRLPOOL(0, 0, 0, 0, 0, 0)); + ADD_COMMAND(GET_OR_SET(0, 0)); + ADD_COMMAND(ADV_DEMO()); + ADD_COMMAND(CLEAR_DEMO_PTR()); + ADD_COMMAND(OBJECT_WITH_ACTS_EXT(0, 0, 0, 0, 0, 0, 0, 0, POINTER, 0)); + ADD_COMMAND(OBJECT_WITH_ACTS_EXT2(POINTER, 0, 0, 0, 0, 0, 0, 0, POINTER, 0)); + ADD_COMMAND(OBJECT_EXT(0, 0, 0, 0, 0, 0, 0, 0, POINTER)); + ADD_COMMAND(OBJECT_EXT2(POINTER, 0, 0, 0, 0, 0, 0, 0, POINTER)); + ADD_COMMAND(LOAD_MODEL_FROM_GEO_EXT(0, POINTER)); + ADD_COMMAND(JUMP_AREA_EXT(0, 0, POINTER)); +} + +void DynOS_Lvl_Validate_Begin() { + // fill our command map if it hasn't been initialized + if (!sCommandMapFilled) { + LvlCmd_Init(); + sCommandMapFilled = true; + } + + // set current command info to defaults + sCurCommandId = 0xFF; + sCurCommandOffset = 0xFF; +} + +bool DynOS_Lvl_Validate_RequirePointer(u32 value) { + // figure out which command we're inside + if (sCurCommandId == 0xFF || sCurCommandOffset >= sCommandMap[sCurCommandId].size) { + u8 id = (u8)(value & 0xFF); + sCurCommandId = id; + sCurCommandOffset = 0; + } + + // figure out if we expect a pointer + bool ret = ( + sCurCommandOffset == sCommandMap[sCurCommandId].ptrIdx[0] + || sCurCommandOffset == sCommandMap[sCurCommandId].ptrIdx[1]); + + // advance command offset + sCurCommandOffset++; + + return ret; +} diff --git a/include/level_commands.h b/include/level_commands.h index c8a72d91..7f6a5a5a 100644 --- a/include/level_commands.h +++ b/include/level_commands.h @@ -312,4 +312,17 @@ CMD_W(arg), \ CMD_PTR(target) +// README // +// README // +// README // + +/* When adding new level script commands, +you have to add the commadn to dynos_bin_lvl_validate.cpp's +LvlCmd_Init(), and specify which params are pointers */ + +// README // +// README // +// README // + + #endif // LEVEL_COMMANDS_H \ No newline at end of file diff --git a/src/engine/level_script.c b/src/engine/level_script.c index 175725a3..8190bf61 100644 --- a/src/engine/level_script.c +++ b/src/engine/level_script.c @@ -1133,7 +1133,11 @@ struct LevelCommand *level_script_execute(struct LevelCommand *cmd) { while (sScriptStatus == SCRIPT_RUNNING) { sCurrentCmd = dynos_swap_cmd(sCurrentCmd); void *dynosCurrCmd = (void *) sCurrentCmd; - LevelScriptJumpTable[sCurrentCmd->type](); + + if (sCurrentCmd->type < ARRAY_COUNT(LevelScriptJumpTable)) { + LevelScriptJumpTable[sCurrentCmd->type](); + } + void *dynosNextCmd = dynos_update_cmd(dynosCurrCmd); if (dynosNextCmd) sCurrentCmd = dynosNextCmd; }