From 4ef88959e3a51120d29046fc46be45ed72185802 Mon Sep 17 00:00:00 2001 From: phobos2077 Date: Sun, 6 Nov 2016 17:37:25 +0700 Subject: [PATCH] Moved argument validation to opcodeMetaTable. Now old-style opcode and sfall_funcX scripting functions work almost identical and their handlers can be interchanged. Fixed some bugs with sfall_funcX. --- artifacts/scripting/headers/sfall.h | 14 ++++----- sfall/ScriptExtender.cpp | 48 +++++++++++++++++++++++------ sfall/ScriptOps/MetaruleOp.hpp | 44 ++++++++++++++------------ sfall/ScriptOps/ObjectsOps.hpp | 6 ++-- 4 files changed, 73 insertions(+), 39 deletions(-) diff --git a/artifacts/scripting/headers/sfall.h b/artifacts/scripting/headers/sfall.h index 8de9f692..062afe42 100644 --- a/artifacts/scripting/headers/sfall.h +++ b/artifacts/scripting/headers/sfall.h @@ -189,10 +189,10 @@ #define party_member_list_all party_member_list(1) -#define spatial_radius(obj) sfall_func1("spatial_radius", obj) -#define critter_inven_obj2(obj, y) sfall_func2("critter_inven_obj2", obj, type) -#define intface_redraw sfall_func0("intface_redraw") -#define intface_hide sfall_func0("intface_hide") -#define intface_show sfall_func0("intface_show") -#define intface_is_hidden sfall_func0("intface_is_hidden") -#define exec_map_update_scripts sfall_func0("exec_map_update_scripts") +#define spatial_radius(obj) sfall_func1("spatial_radius", obj) +#define critter_inven_obj2(obj, type) sfall_func2("critter_inven_obj2", obj, type) +#define intface_redraw sfall_func0("intface_redraw") +#define intface_hide sfall_func0("intface_hide") +#define intface_show sfall_func0("intface_show") +#define intface_is_hidden sfall_func0("intface_is_hidden") +#define exec_map_update_scripts sfall_func0("exec_map_update_scripts") diff --git a/sfall/ScriptExtender.cpp b/sfall/ScriptExtender.cpp index 20eb956d..9f5866e8 100644 --- a/sfall/ScriptExtender.cpp +++ b/sfall/ScriptExtender.cpp @@ -49,7 +49,13 @@ void _stdcall HandleMapUpdateForScripts(DWORD procId); #define DATATYPE_MASK_VALID_OBJ (DATATYPE_MASK_INT | DATATYPE_MASK_NOT_NULL) struct SfallOpcodeMetadata { + // opcode handler, will be used as key void (*handler)(); + + // opcode name, only used for logging + const char* name; + + // argument validation masks int argTypeMasks[OP_MAX_ARGUMENTS]; }; @@ -216,14 +222,14 @@ public: _ret = val; } - // resets the state of handler - void resetState(TProgram* program = nullptr) { + // resets the state of handler for new opcode invocation + void resetState(TProgram* program, int argNum) { _program = program; // reset return value _ret = ScriptValue(); // reset argument list - _args.resize(0); + _args.resize(argNum); // reset arg shift _argShift = 0; } @@ -276,7 +282,7 @@ public: assert(argNum < OP_MAX_ARGUMENTS); // reset state after previous - resetState(program); + resetState(program, argNum); // process arguments on stack (reverse order) for (int i = argNum - 1; i >= 0; i--) { @@ -287,14 +293,27 @@ public: // retrieve string argument if (type == DATATYPE_STR) { - _args.push_back(InterpretGetString(program, rawValue, rawValueType)); + _args.at(i) = InterpretGetString(program, rawValue, rawValueType); } else { - _args.push_back(ScriptValue(type, rawValue)); + _args.at(i) = ScriptValue(type, rawValue); } } + // flag that arguments passed are valid + bool argumentsValid = true; - // call opcode handler - func(); + // check if metadata is available + OpcodeMetaTableType::iterator it = opcodeMetaTable.find(func); + if (it != opcodeMetaTable.end()) { + const SfallOpcodeMetadata* meta = it->second; + + // automatically validate argument types + argumentsValid = validateArguments(meta->argTypeMasks, argNum, meta->name); + } + + // call opcode handler if arguments are valid (or no automatic validation was done) + if (argumentsValid) { + func(); + } // process return value if (hasReturn) { @@ -339,9 +358,20 @@ static OpcodeHandler opHandler; #include "ScriptOps\MiscOps.hpp" #include "ScriptOps\MetaruleOp.hpp" +/* + Array for opcodes metadata. + This is completely optional, added for convenience only. + + By adding opcode to this array, Sfall will automatically validate it's arguments using provided info. + On fail, errors will be printed to debug.log and opcode will not be executed. + If you don't include opcode in this array, you should take care of all argument validation inside handler itself. +*/ static const SfallOpcodeMetadata opcodeMetaArray[] = { - {op_message_str_game, {}} + {sf_test, "validate_test", {DATATYPE_MASK_INT, DATATYPE_MASK_INT | DATATYPE_MASK_FLOAT, DATATYPE_MASK_STR, DATATYPE_NONE}}, + {sf_spatial_radius, "spatial_radius", {DATATYPE_MASK_VALID_OBJ}}, + {sf_critter_inven_obj2, "critter_inven_obj2", {DATATYPE_MASK_VALID_OBJ, DATATYPE_MASK_INT}}, + //{op_message_str_game, {}} }; static void InitOpcodeMetaTable() { diff --git a/sfall/ScriptOps/MetaruleOp.hpp b/sfall/ScriptOps/MetaruleOp.hpp index 5791e3aa..43e06997 100644 --- a/sfall/ScriptOps/MetaruleOp.hpp +++ b/sfall/ScriptOps/MetaruleOp.hpp @@ -28,8 +28,8 @@ // Metarule is a universal opcode(s) for all kinds of new sfall scripting functions. // Prefix all function handlers with sf_ and add them to sfall_metarule_table. // DO NOT add arguments and/or return values to function handlers! -// Use functions GetOpArgXXX(), IsOpArgXXX() inside handler function to read arguments, but argument 0 is always function name. -// Use opHandler.setReturn() to set return value. +// Use opHandler.arg(i), inside handler function to access arguments. +// Use opHandler.setReturn(x) to set return value. // If you want to call user-defined procedures in your handler, use RunScriptProc(). struct SfallMetarule { @@ -41,8 +41,6 @@ struct SfallMetarule { int minArgs; // maximum number of arguments int maxArgs; - // type masks of arguments DATATYPE_MASK_* (use bitwise OR to combine, use 0 to disable type validation) - int argTypeMasks[6]; }; typedef std::tr1::unordered_map MetaruleTableType; @@ -58,7 +56,7 @@ static std::string sf_test_stringBuf; static void sf_test() { std::ostringstream sstream; sstream << "sfall_funcX(\"test\""; - for (int i = 1; i < opHandler.numArgs(); i++) { + for (int i = 0; i < opHandler.numArgs(); i++) { const ScriptValue &arg = opHandler.arg(i); sstream << ", "; switch (arg.type()) { @@ -99,23 +97,21 @@ static void sf_get_metarule_table() { Add your custom scripting functions here. Format is as follows: - { name, handler, minArgs, maxArgs, {MASK1, MASK2, ...} } + { name, handler, minArgs, maxArgs } - name - name of function that will be used to call it from scripts, - - handler - pointer to handler function (see examples above), - - minArgs/maxArgs - minimum and maximum number of arguments allowed for this function, - - MASK1, MASK2, ... - validation parameters for each argument as bit masks (see DATATYPE_MASK_* defines) + - handler - pointer to handler function (see examples below), + - minArgs/maxArgs - minimum and maximum number of arguments allowed for this function */ static const SfallMetarule metaruleArray[] = { - {"test", sf_test, 0, 6, {}}, - {"get_metarule_table", sf_get_metarule_table, 0, 0, {}}, - {"validate_test", sf_test, 2, 5, {DATATYPE_MASK_INT, DATATYPE_MASK_INT | DATATYPE_MASK_FLOAT, DATATYPE_MASK_STR, DATATYPE_NONE}}, - {"spatial_radius", sf_spatial_radius, 1, 1, {DATATYPE_MASK_VALID_OBJ}}, - {"critter_inven_obj2", sf_critter_inven_obj2, 2, 2, {DATATYPE_MASK_VALID_OBJ, DATATYPE_MASK_INT}}, - {"intface_redraw", sf_intface_redraw, 0, 0, {}}, - {"intface_show", sf_intface_show, 0, 0, {}}, - {"intface_hide", sf_intface_hide, 0, 0, {}}, - {"intface_is_hidden", sf_intface_is_hidden, 0, 0, {}}, - {"exec_map_update_scripts", sf_exec_map_update_scripts, 0, 0, {}}, + {"get_metarule_table", sf_get_metarule_table, 0, 0}, + {"validate_test", sf_test, 2, 5}, + {"spatial_radius", sf_spatial_radius, 1, 1}, + {"critter_inven_obj2", sf_critter_inven_obj2, 2, 2}, + {"intface_redraw", sf_intface_redraw, 0, 0}, + {"intface_show", sf_intface_show, 0, 0}, + {"intface_hide", sf_intface_hide, 0, 0}, + {"intface_is_hidden", sf_intface_is_hidden, 0, 0}, + {"exec_map_update_scripts", sf_exec_map_update_scripts, 0, 0}, }; static void InitMetaruleTable() { @@ -139,8 +135,16 @@ static bool ValidateMetaruleArguments(const SfallMetarule* metaruleInfo) { return false; } else { - return opHandler.validateArguments(metaruleInfo->argTypeMasks, argCount, metaruleInfo->name); + // check if metadata is available for this handler + OpcodeMetaTableType::iterator it = opcodeMetaTable.find(metaruleInfo->func); + if (it != opcodeMetaTable.end()) { + const SfallOpcodeMetadata* meta = it->second; + + // automatically validate argument types + return opHandler.validateArguments(meta->argTypeMasks, argCount, metaruleInfo->name); + } } + return true; } static void _stdcall op_sfall_metarule_handler() { diff --git a/sfall/ScriptOps/ObjectsOps.hpp b/sfall/ScriptOps/ObjectsOps.hpp index ad636aa2..c60e0216 100644 --- a/sfall/ScriptOps/ObjectsOps.hpp +++ b/sfall/ScriptOps/ObjectsOps.hpp @@ -159,7 +159,7 @@ static void __declspec(naked) op_create_spatial() { } static void sf_spatial_radius() { - TGameObj* spatialObj = opHandler.arg(1).asObject(); + TGameObj* spatialObj = opHandler.arg(0).asObject(); TScript* script; if (ScrPtr(spatialObj->scriptID, &script) != -1) { opHandler.setReturn(script->spatial_radius); @@ -536,8 +536,8 @@ static void __declspec(naked) op_obj_is_carrying_obj() { } static void sf_critter_inven_obj2() { - TGameObj* critter = opHandler.arg(1).asObject(); - int slot = opHandler.arg(2).asInt(); + TGameObj* critter = opHandler.arg(0).asObject(); + int slot = opHandler.arg(1).asInt(); switch (slot) { case 0: opHandler.setReturn(InvenWorn(critter));