diff --git a/include/nwnx_cplugin.h b/include/nwnx_cplugin.h index 05f8742..de4dfc2 100644 --- a/include/nwnx_cplugin.h +++ b/include/nwnx_cplugin.h @@ -22,6 +22,49 @@ struct NWNXCPlugin_InitInfo { const char* nwn2_module_path; /// Path to the NWNX4 user directory, where nwnx4_controller.exe is located. const char* nwnx_install_path; + /// Function pointers to interact with the nwn2server instance + const struct NWNXCPlugin_NWN2Hooks* nwn2_hooks; +}; + +/// Bound to NWScript ExecuteScript function +/// @param outExecuted If not null, the bool will be set to true if the script +/// has been successfully executed. +typedef void(ExecuteScriptFn)(const char* sScript, uint32_t oTarget, bool* outExecuted); +/// Bound to NWScript ExecuteScriptEnhanced function +/// @param outExecuted If not null, the bool will be set to true if the script +/// has been successfully executed. This is used to differenciate between a +/// script returning the value -1 and a script not being executed. +typedef int32_t(ExecuteScriptEnhancedFn)(const char* sScriptName, + uint32_t oTarget, + bool bClearParams, + bool* outExecuted); +/// Bound to NWScript AddScriptParameterInt function. Bound values are not +/// shared between the NWScript and CPlugin environments +typedef void(AddScriptParameterIntFn)(int32_t nParam); +/// Bound to NWScript AddScriptParameterString function. Bound values are not +/// shared between the NWScript and CPlugin environments. +/// @warning The string value is only borrowed and must live at least until +/// ExecuteScriptEnhanced is called +typedef void(AddScriptParameterStringFn)(const char* sParam); +/// Bound to NWScript AddScriptParameterFloat function. Bound values are not +/// shared between the NWScript and CPlugin environments +typedef void(AddScriptParameterFloatFn)(float fParam); +/// Bound to NWScript AddScriptParameterObject function. Bound values are not +/// shared between the NWScript and CPlugin environments +typedef void(AddScriptParameterObjectFn)(uint32_t oParam); +/// Bound to NWScript ClearScriptParams function. Only clears the bound values +/// from the CPlugin environment +typedef void(ClearScriptParamsFn)(); + +/// Function pointers to interact with the nwn2server instance +struct NWNXCPlugin_NWN2Hooks { + ExecuteScriptFn* ExecuteScript; + ExecuteScriptEnhancedFn* ExecuteScriptEnhanced; + AddScriptParameterIntFn* AddScriptParameterInt; + AddScriptParameterStringFn* AddScriptParameterString; + AddScriptParameterFloatFn* AddScriptParameterFloat; + AddScriptParameterObjectFn* AddScriptParameterObject; + ClearScriptParamsFn* ClearScriptParams; }; // diff --git a/src/hook/CPlugin.h b/src/hook/CPlugin.h index c1d5823..189d42f 100644 --- a/src/hook/CPlugin.h +++ b/src/hook/CPlugin.h @@ -1,12 +1,31 @@ #ifndef NWNX4_CPLUGIN_H #define NWNX4_CPLUGIN_H +#include "scriptManagement.h" #include #include #include class CPlugin { public: + struct NWN2Hooks { + using ExecuteScriptFn = decltype(NWScript::ExecuteScript); + using ExecuteScriptEnhancedFn = decltype(NWScript::ExecuteScriptEnhanced); + using AddScriptParameterIntFn = decltype(NWScript::AddScriptParameterInt); + using AddScriptParameterStringFn = decltype(NWScript::AddScriptParameterString); + using AddScriptParameterFloatFn = decltype(NWScript::AddScriptParameterFloat); + using AddScriptParameterObjectFn = decltype(NWScript::AddScriptParameterObject); + using ClearScriptParamsFn = decltype(NWScript::ClearScriptParams); + + ExecuteScriptFn* ExecuteScript = NWScript::ExecuteScript; + ExecuteScriptEnhancedFn* ExecuteScriptEnhanced = NWScript::ExecuteScriptEnhanced; + AddScriptParameterIntFn* AddScriptParameterInt = NWScript::AddScriptParameterInt; + AddScriptParameterStringFn* AddScriptParameterString = NWScript::AddScriptParameterString; + AddScriptParameterFloatFn* AddScriptParameterFloat = NWScript::AddScriptParameterFloat; + AddScriptParameterObjectFn* AddScriptParameterObject = NWScript::AddScriptParameterObject; + ClearScriptParamsFn* ClearScriptParams = NWScript::ClearScriptParams; + }; + struct InitInfo { const char* dll_path; const char* nwnx_user_path; @@ -14,6 +33,7 @@ class CPlugin { const char* nwn2_home_path; const char* nwn2_module_path; const char* nwnx_install_path; + const struct NWN2Hooks* nwn2_hooks; }; CPlugin(HINSTANCE hDLL, const InitInfo& initInfo); diff --git a/src/hook/hook.cpp b/src/hook/hook.cpp index 9ca8ad7..1987e39 100644 --- a/src/hook/hook.cpp +++ b/src/hook/hook.cpp @@ -22,6 +22,7 @@ #include "../misc/windows_utils.h" #include "../nwnx_version.h" #include "scorcohook.h" +#include "scriptManagement.h" #include #include #include @@ -744,6 +745,9 @@ void loadPlugins() = (std::filesystem::path(nwn2HomeDir) / "modules" / serverArgs["moduledir"]).string(); const char* nwn2ModulePathCStr = nwn2ModulePath.size() > 0 ? nwn2ModulePath.c_str() : nullptr; + // Get NWN2 hooks + struct CPlugin::NWN2Hooks hooks; + // Start loading plugins for (auto& pluginPath : pluginList) { if (++pluginPath.begin() == pluginPath.end()) { @@ -788,6 +792,7 @@ void loadPlugins() .nwn2_home_path = nwn2HomeDir.c_str(), .nwn2_module_path = nwn2ModulePathCStr, .nwnx_install_path = nwnxInstallDirStr.c_str(), + .nwn2_hooks = &hooks, }; // Instantiate & initialize CPlugin diff --git a/src/hook/meson.build b/src/hook/meson.build index 6d93e91..380fded 100644 --- a/src/hook/meson.build +++ b/src/hook/meson.build @@ -5,6 +5,7 @@ NWNX4_Hook_lib = shared_library('NWNX4_Hook', 'hook.cpp', 'scorcohook.cpp', 'crashdump.cpp', + 'scriptManagement.cpp', 'CPlugin.cpp', '../plugins/plugin.cpp', '../plugins/legacy_plugin.cpp', diff --git a/src/hook/scriptManagement.cpp b/src/hook/scriptManagement.cpp new file mode 100644 index 0000000..f426464 --- /dev/null +++ b/src/hook/scriptManagement.cpp @@ -0,0 +1,180 @@ + +#include "scriptManagement.h" +#include +#include +#include +#include + +#include "../misc/log.h" +extern std::unique_ptr logger; + +constexpr uint32_t NWN_DEFAULT_EXECUTESCRIPT_ENH_PARAMS_LEN = 32; + +struct NWN2Param { + uint32_t _; + uint32_t value; + uint32_t ptr; + uint32_t size; + uint32_t type; +}; +static_assert(sizeof(NWN2Param) == 5 * 4); + +static std::vector scriptparams; + +// 1.23 +// g_pVirtualMachine +constexpr uint32_t NWN2_OFFSET_CVIRTUALMACHINE = 0x00864424; +// CVirtualMachine::ExecuteScript +constexpr uint32_t NWN2_OFFSET_EXECUTESCRIPT = 0x0072B380; +constexpr uint32_t NWN2_OFFSET_EXECUTESCRIPT_ENH = 0x0072B050; + +constexpr uint32_t NWN2_OFFSET_InitParam = 0x0055EA40; +constexpr uint32_t NWN2_OFFSET_CleanParam = 0x006b5cd0; + +struct NWN2ParamsList { + struct NWN2Param* list; + size_t size; +}; +static_assert(sizeof(NWN2ParamsList) == 8); + +static struct NWN2ParamsList* nwn2_scriptparams = (struct NWN2ParamsList*)(0x0086F15C); +// static size_t scriptparams_count = 0; + +struct CVirtualMachine { + uint32_t _; + uint32_t execscript_ret_value; +}; +static CVirtualMachine** nwn2_vm + = std::bit_cast(NWN2_OFFSET_CVIRTUALMACHINE); + +// Function hooks +using CVirtualMachine_ExecuteScript_t = BOOL(__thiscall*)(CVirtualMachine* thisVM, + const NWN::CExoString& scriptName, + NWN::OBJECTID objectId, + uint32_t unknown1, + uint32_t unknown2); +static CVirtualMachine_ExecuteScript_t CVirtualMachine_ExecuteScript + = std::bit_cast(NWN2_OFFSET_EXECUTESCRIPT); + +// +using CVirtualMachine_ExecuteScriptEnhanced_t + = int32_t(__thiscall*)(CVirtualMachine* thisVM, + const NWN::CExoString& scriptName, + NWN::OBJECTID objectID, + void* ParamList, + uint32_t unknow1, + uint32_t unknow2); +static CVirtualMachine_ExecuteScriptEnhanced_t CVirtualMachine_ExecuteScriptEnhanced + = std::bit_cast(NWN2_OFFSET_EXECUTESCRIPT_ENH); + +// // +// using CVirtualMachine_InitParam_t = void(__thiscall*)(void* paramLst, uint32_t iNb); +// static CVirtualMachine_InitParam_t CVirtualMachine_InitParam +// = std::bit_cast(NWN2_OFFSET_InitParam); + +// // +// using CVirtualMachine_CleanParam_t = void(__thiscall*)(void* paramLst); +// static CVirtualMachine_CleanParam_t CVirtualMachine_CleanParam +// = std::bit_cast(NWN2_OFFSET_CleanParam); + +namespace NWScript { + +void ExecuteScript(const char* sScript, NWN::OBJECTID oTarget, bool* outExecuted) +{ + logger->Trace("ExecuteScript %s, %lu", sScript, oTarget); + auto executed = CVirtualMachine_ExecuteScript( + *nwn2_vm, + NWN::CExoString {.m_sString = (char*)sScript, // un-const cast, safe as param is read only + .m_nBufferLength = strlen(sScript)}, + oTarget, 1, 1); + if (outExecuted != nullptr) + *outExecuted = executed; +} + +int32_t ExecuteScriptEnhanced(const char* sScriptName, + NWN::OBJECTID oTarget, + bool bClearParams, + bool* outExecuted) +{ + logger->Trace("ExecuteScriptEnhanced %s, %lu", sScriptName, oTarget); + + const NWN::CExoString script + = {.m_sString = (char*)sScriptName, .m_nBufferLength = strlen(sScriptName)}; + + NWN2ParamsList save = *nwn2_scriptparams; + + nwn2_scriptparams->list = scriptparams.data(); + nwn2_scriptparams->size = scriptparams.size(); + + // call the script + int retValue + = CVirtualMachine_ExecuteScriptEnhanced(*nwn2_vm, script, oTarget, nwn2_scriptparams, 1, 1); + + if (outExecuted != nullptr) + *outExecuted = retValue != 0; + + // Is the script ok? + if (retValue != 0) + retValue = (*nwn2_vm)->execscript_ret_value; + else + retValue = -1; + + *nwn2_scriptparams = save; + + return retValue; +} +void AddScriptParameterInt(int32_t nParam) +{ + logger->Trace("AddScriptParameterInt %d", nParam); + + scriptparams.push_back(NWN2Param { + ._ = 0, + .value = std::bit_cast(nParam), + .ptr = 0, + .size = 0, + .type = 0, + }); +} +void AddScriptParameterString(const char* sParam) +{ + logger->Trace("AddScriptParameterString %s", sParam); + + scriptparams.push_back(NWN2Param { + ._ = 0, + .value = 0, + .ptr = std::bit_cast(sParam), + .size = strlen(sParam) + 1, + .type = 2, + }); +} +void AddScriptParameterFloat(float fParam) +{ + logger->Trace("AddScriptParameterFloat %f", fParam); + + scriptparams.push_back(NWN2Param { + ._ = 0, + .value = std::bit_cast(fParam), + .ptr = 0, + .size = 0, + .type = 1, + }); +} +void AddScriptParameterObject(NWN::OBJECTID oParam) +{ + logger->Trace("AddScriptParameterObject %lu", oParam); + + scriptparams.push_back(NWN2Param { + ._ = 0, + .value = oParam, + .ptr = 0, + .size = 0, + .type = 4, + }); +} + +void ClearScriptParams() +{ + logger->Trace("ClearScriptParams"); + scriptparams.clear(); +} +} // namespace NWScript diff --git a/src/hook/scriptManagement.h b/src/hook/scriptManagement.h new file mode 100644 index 0000000..063399d --- /dev/null +++ b/src/hook/scriptManagement.h @@ -0,0 +1,27 @@ +#if !defined(SCRIPTMANAGEMENT_H_INCLUDED) +#define SCRIPTMANAGEMENT_H_INCLUDED + +#define DLLEXPORT extern "C" __declspec(dllexport) + +#include "../NWN2Lib/NWN2.h" +#include "../NWN2Lib/NWN2Common.h" +#include + +namespace NWScript { + +extern "C" { +void ExecuteScript(const char* sScript, NWN::OBJECTID oTarget, bool* outExecuted = NULL); +int32_t ExecuteScriptEnhanced(const char* sScriptName, + NWN::OBJECTID oTarget, + bool bClearParams, + bool* outExecuted = NULL); +void AddScriptParameterInt(int32_t nParam); +void AddScriptParameterString(const char* sParam); +void AddScriptParameterFloat(float fParam); +void AddScriptParameterObject(NWN::OBJECTID oParam); +void ClearScriptParams(); +} + +} // namespace NWScript + +#endif \ No newline at end of file diff --git a/src/plugins/xp_example_cplugin/cplugin_example.cpp b/src/plugins/xp_example_cplugin/cplugin_example.cpp index 3ca628b..80e742b 100644 --- a/src/plugins/xp_example_cplugin/cplugin_example.cpp +++ b/src/plugins/xp_example_cplugin/cplugin_example.cpp @@ -1,5 +1,6 @@ #include +#include #include #include #include @@ -13,13 +14,15 @@ const uint32_t nwnxcplugin_abi_version = 1; class Plugin { public: - Plugin(const std::filesystem::path& logFile) + Plugin(const std::filesystem::path& logFile, const NWNXCPlugin_NWN2Hooks* hooks) { this->logFile.open(logFile); // Disable buffering // This is bad for performance but useful for testing this->logFile.rdbuf()->pubsetbuf(0, 0); + + this->hooks = *hooks; } std::ofstream logFile; @@ -30,12 +33,16 @@ class Plugin { typedef std::vector GffData; std::unordered_map> storedGFFs; + + NWNXCPlugin_NWN2Hooks hooks; }; +Plugin* g_plugin; + void* NWNXCPlugin_New(NWNXCPlugin_InitInfo info) { auto logFilePath = std::filesystem::path(info.nwnx_user_path) / "xp_cplugin_example.txt"; - auto plugin = new Plugin(logFilePath); + auto plugin = new Plugin(logFilePath, info.nwn2_hooks); plugin->logFile << "Plugin initialized" << std::endl; plugin->logFile << " dll_path: " << info.dll_path << std::endl; plugin->logFile << " nwnx_user_path: " << info.nwnx_user_path << std::endl; @@ -45,6 +52,22 @@ void* NWNXCPlugin_New(NWNXCPlugin_InitInfo info) plugin->logFile << " nwn2_module_path: " << (info.nwn2_module_path == nullptr ? "unknown" : info.nwn2_module_path) << std::endl; + plugin->logFile << " nwn2_hooks: " << std::endl; + plugin->logFile << " ExecuteScript: " << (void*)info.nwn2_hooks->ExecuteScript + << std::endl; + plugin->logFile << " ExecuteScriptEnhanced: " + << (void*)info.nwn2_hooks->ExecuteScriptEnhanced << std::endl; + plugin->logFile << " AddScriptParameterInt: " + << (void*)info.nwn2_hooks->AddScriptParameterInt << std::endl; + plugin->logFile << " AddScriptParameterString: " + << (void*)info.nwn2_hooks->AddScriptParameterString << std::endl; + plugin->logFile << " AddScriptParameterFloat: " + << (void*)info.nwn2_hooks->AddScriptParameterFloat << std::endl; + plugin->logFile << " AddScriptParameterObject: " + << (void*)info.nwn2_hooks->AddScriptParameterObject << std::endl; + plugin->logFile << " ClearScriptParams: " << info.nwn2_hooks->ClearScriptParams + << std::endl; + g_plugin = plugin; return plugin; } @@ -80,6 +103,37 @@ NWNXCPlugin_GetInt(void* cplugin, const char* sFunction, const char* sParam1, in plugin->counter++; plugin->logFile << " Increment and return counter value: " << plugin->counter << std::endl; return plugin->counter; + } else if (function == "TEST_EXECUTESCRIPT") { + constexpr uint32_t OBJID_MODULE = 0; + bool executed = false; + plugin->hooks.ExecuteScript("gui_test_executescript", OBJID_MODULE, &executed); + return executed == true ? 12 : 0; + } else if (function == "TEST_EXECUTESCRIPTBAD") { + constexpr uint32_t OBJID_MODULE = 0; + bool executed = true; + plugin->hooks.ExecuteScript("euqsgdihohcqsc", OBJID_MODULE, &executed); + return executed == false ? 13 : 0; + } else if (function == "TEST_EXECUTESCRIPTENH") { + constexpr uint32_t OBJID_MODULE = 0; + plugin->hooks.ClearScriptParams(); + plugin->hooks.AddScriptParameterString(sParam1); + plugin->hooks.AddScriptParameterFloat(13.37f); + plugin->hooks.AddScriptParameterInt(-1234); + plugin->hooks.AddScriptParameterObject(0x01020304); + bool executed = false; + auto res = plugin->hooks.ExecuteScriptEnhanced("gui_test_executescriptenh", OBJID_MODULE, + true, &executed); + if (!executed) + res -= 1000; + return res; + } else if (function == "TEST_EXECUTESCRIPTENHBAD") { + constexpr uint32_t OBJID_MODULE = 0; + bool executed = true; + auto res + = plugin->hooks.ExecuteScriptEnhanced("euqsgdihohcqsc", OBJID_MODULE, true, &executed); + if (executed) + res -= 1000; + return res; } else { plugin->logFile << " ERROR: NWNXCPlugin_GetInt: unknown function \"" << function << "\"" << std::endl; diff --git a/test/modules/NWNX4UnitTests/gui_test_executescript.ncs b/test/modules/NWNX4UnitTests/gui_test_executescript.ncs new file mode 100644 index 0000000..e41a014 Binary files /dev/null and b/test/modules/NWNX4UnitTests/gui_test_executescript.ncs differ diff --git a/test/modules/NWNX4UnitTests/gui_test_executescript.nss b/test/modules/NWNX4UnitTests/gui_test_executescript.nss new file mode 100644 index 0000000..2a6f9c9 --- /dev/null +++ b/test/modules/NWNX4UnitTests/gui_test_executescript.nss @@ -0,0 +1,8 @@ + + +void main() +{ + WriteTimestampedLogEntry("[DBG] exec gui_test_executescript"); + object oModule = GetModule(); + SetLocalString(oModule, "testexescript", "executed"); +} \ No newline at end of file diff --git a/test/modules/NWNX4UnitTests/gui_test_executescriptenh.ncs b/test/modules/NWNX4UnitTests/gui_test_executescriptenh.ncs new file mode 100644 index 0000000..06d211d Binary files /dev/null and b/test/modules/NWNX4UnitTests/gui_test_executescriptenh.ncs differ diff --git a/test/modules/NWNX4UnitTests/gui_test_executescriptenh.nss b/test/modules/NWNX4UnitTests/gui_test_executescriptenh.nss new file mode 100644 index 0000000..c319258 --- /dev/null +++ b/test/modules/NWNX4UnitTests/gui_test_executescriptenh.nss @@ -0,0 +1,19 @@ + + +int StartingConditional(string sString, float fFloat, int nInt, object oObject) +{ + WriteTimestampedLogEntry("[DBG] exec gui_test_executescriptenh"); + WriteTimestampedLogEntry("[DBG] testexescriptenh_sString=" + sString); + WriteTimestampedLogEntry("[DBG] testexescriptenh_fFloat=" + FloatToString(fFloat)); + WriteTimestampedLogEntry("[DBG] testexescriptenh_nInt=" + IntToString(nInt)); + WriteTimestampedLogEntry("[DBG] testexescriptenh_oObject=" + ObjectToString(oObject)); + object oModule = GetModule(); + SetLocalString(oModule, "testexescriptenh_string", sString); + SetLocalFloat(oModule, "testexescriptenh_float", fFloat); + SetLocalInt(oModule, "testexescriptenh_int", nInt); + SetLocalObject(oModule, "testexescriptenh_object", oObject); + + int nRet = GetLocalInt(oModule, "testexescriptenh_cnt") + 10; + SetLocalInt(oModule, "testexescriptenh_cnt", nRet); + return nRet; +} \ No newline at end of file diff --git a/test/modules/NWNX4UnitTests/onmoduleload.ncs b/test/modules/NWNX4UnitTests/onmoduleload.ncs index cdc2cee..2c8ab0f 100644 Binary files a/test/modules/NWNX4UnitTests/onmoduleload.ncs and b/test/modules/NWNX4UnitTests/onmoduleload.ncs differ diff --git a/test/modules/NWNX4UnitTests/onmoduleload.nss b/test/modules/NWNX4UnitTests/onmoduleload.nss index 1cd9649..ec3372a 100644 --- a/test/modules/NWNX4UnitTests/onmoduleload.nss +++ b/test/modules/NWNX4UnitTests/onmoduleload.nss @@ -179,6 +179,48 @@ void xp_example_cplugin(){ Assert(!GetIsObjectValid(RetrieveCampaignObject("NWNX.CPluginExample", "A", GetLocation(oContainer), oContainer)), __FUNCTION__, __LINE__); Assert(!GetIsObjectValid(RetrieveCampaignObject("NWNX.CPluginExample", "B", GetLocation(oContainer), oContainer)), __FUNCTION__, __LINE__); + + // Test executescript hooks + // Standard NWScript call + AddScriptParameterString("test"); + AddScriptParameterFloat(345.3); + AddScriptParameterInt(45237); + AddScriptParameterObject(GetFirstArea()); + AssertEqI(ExecuteScriptEnhanced("qsdqsdqds", GetModule(), FALSE), -1, __FUNCTION__, __LINE__); + AssertEqI(ExecuteScriptEnhanced("gui_test_executescriptenh", GetModule(), FALSE), 10, __FUNCTION__, __LINE__); + AssertEqS(GetLocalString(GetModule(), "testexescriptenh_string"), "test", __FUNCTION__, __LINE__); + AssertEqF(GetLocalFloat(GetModule(), "testexescriptenh_float"), 345.3, 0.001, __FUNCTION__, __LINE__); + AssertEqI(GetLocalInt(GetModule(), "testexescriptenh_int"), 45237, __FUNCTION__, __LINE__); + AssertEqI(ObjectToInt(GetLocalObject(GetModule(), "testexescriptenh_object")), ObjectToInt(GetFirstArea()), __FUNCTION__, __LINE__); + + // CPlugin call + AssertEqI(NWNXGetInt("CPluginExample", "TEST_EXECUTESCRIPT", "", 0), 12, __FUNCTION__, __LINE__); + AssertEqS(GetLocalString(GetModule(), "testexescript"), "executed", __FUNCTION__, __LINE__); + DeleteLocalString(GetModule(), "testexescript"); + + // CPlugin call + AssertEqI(NWNXGetInt("CPluginExample", "TEST_EXECUTESCRIPTBAD", "", 0), 13, __FUNCTION__, __LINE__); + + // CPlugin call + AssertEqI(NWNXGetInt("CPluginExample", "TEST_EXECUTESCRIPTENH", "hellow orld", 0), 20, __FUNCTION__, __LINE__); + AssertEqS(GetLocalString(GetModule(), "testexescriptenh_string"), "hellow orld", __FUNCTION__, __LINE__); + AssertEqF(GetLocalFloat(GetModule(), "testexescriptenh_float"), 13.37, 0.001, __FUNCTION__, __LINE__); + AssertEqI(GetLocalInt(GetModule(), "testexescriptenh_int"), -1234, __FUNCTION__, __LINE__); + AssertEqI(ObjectToInt(GetLocalObject(GetModule(), "testexescriptenh_object")), 0x01020304, __FUNCTION__, __LINE__); + + // + AssertEqI(NWNXGetInt("CPluginExample", "TEST_EXECUTESCRIPTENH", "yolo 2", 0), 30, __FUNCTION__, __LINE__); + AssertEqS(GetLocalString(GetModule(), "testexescriptenh_string"), "yolo 2", __FUNCTION__, __LINE__); + + // Try executing unknown script + AssertEqI(NWNXGetInt("CPluginExample", "TEST_EXECUTESCRIPTENHBAD", "", 0), -1, __FUNCTION__, __LINE__); + + // Standard NWScript call, previous parameters should be kept + AssertEqI(ExecuteScriptEnhanced("gui_test_executescriptenh", GetModule(), FALSE), 40, __FUNCTION__, __LINE__); + AssertEqS(GetLocalString(GetModule(), "testexescriptenh_string"), "test", __FUNCTION__, __LINE__); + AssertEqF(GetLocalFloat(GetModule(), "testexescriptenh_float"), 345.3, 0.001, __FUNCTION__, __LINE__); + AssertEqI(GetLocalInt(GetModule(), "testexescriptenh_int"), 45237, __FUNCTION__, __LINE__); + AssertEqI(ObjectToInt(GetLocalObject(GetModule(), "testexescriptenh_object")), ObjectToInt(GetFirstArea()), __FUNCTION__, __LINE__); } void nwnx_general(){