diff --git a/obse/include/EasyDetour.h b/obse/include/EasyDetour.h new file mode 100644 index 0000000..79cd12f --- /dev/null +++ b/obse/include/EasyDetour.h @@ -0,0 +1,230 @@ +#pragma once + +#ifdef _M_ARM_ +#error Does't support ARM +#endif + +#define WIN32_LEAN_AND_MEN +#include +#include +#include +#include +#include + +#include "detours.h" +#include "x86Decoder.h" + +namespace easydetour_internals { class __easy_detour_Iternal_class; } + + +namespace EasyDetour +{ + + template + class EasyDetour + { + public: + typedef R(*TARGET_FUNCTION)(Types...); + typedef R(T::*DETOUR_CLASS_FUNCTION)(TARGET_FUNCTION, Types ...); + typedef R(*DETOUR_FUNCTION)(T*, TARGET_FUNCTION, Types ...); + + EasyDetour(T* pClassIntance, TARGET_FUNCTION lpTargetFunction) + : pInstance(pClassIntance), lpTargetFunction(lpTargetFunction), bitFlag(0) + { + this->lpTrampolineFunction = lpTargetFunction; + lpDetourFunction.lpClassFunction = nullptr; + } + ~EasyDetour() + { + if (isFunHooked()) + { + easydetour_internals::__easy_detour_Iternal_class::_removeDetourInstance(lpTargetFunction); + UnHookFunction(); + } + } + + bool HookFunction(DETOUR_CLASS_FUNCTION lpClassFunction) + { + if (isFunHooked()) + return false; + if (DetourTransactionBegin() == NO_ERROR) + { + DetourUpdateThread(GetCurrentThread()); + DetourAttach((LPVOID*)&this->lpTrampolineFunction, (LPVOID)&GenericDetour); + if (DetourTransactionCommit() == NO_ERROR) + { + lpDetourFunction.lpClassFunction = lpClassFunction; + easydetour_internals::__easy_detour_Iternal_class::_addEasyDetourInstance(this, (LPVOID)this->lpTargetFunction); + isClassFunction(true); + isFunHooked(true); + return true; + } + else + DetourTransactionAbort(); + } + return false; + } + bool HookFunction(DETOUR_FUNCTION lpDetourFunction) + { + if (isFunHooked()) + return false; + + if (DetourTransactionBegin() == NO_ERROR) + { + DetourUpdateThread(GetCurrentThread()); + DetourAttach((LPVOID*)&this->lpTrampolineFunction, (LPVOID)&GenericDetour); + if (DetourTransactionCommit() == NO_ERROR) + { + this->lpDetourFunction.lpDetourFunction = lpDetourFunction; + easydetour_internals::__easy_detour_Iternal_class::_addEasyDetourInstance(this, (LPVOID)this->lpTargetFunction); + isClassFunction(false); + isFunHooked(true); + return true; + } + else + DetourTransactionAbort(); + } + return false; + } + bool UnHookFunction() + { + if (!isFunHooked()) + return false; + + if (DetourTransactionBegin() == NO_ERROR) + { + DetourUpdateThread(GetCurrentThread()); + DetourDetach((LPVOID*)&this->lpTrampolineFunction, (PVOID)&GenericDetour); + if (DetourTransactionCommit() == NO_ERROR) + { + easydetour_internals::__easy_detour_Iternal_class::_removeDetourInstance(lpTargetFunction); + isFunHooked(false); + return true; + } + else + DetourTransactionAbort(); + } + return false; + } + + + private: + T* const pInstance; + uint8_t bitFlag; + const TARGET_FUNCTION lpTargetFunction; + TARGET_FUNCTION lpTrampolineFunction; + + union + { + DETOUR_CLASS_FUNCTION lpClassFunction; + DETOUR_FUNCTION lpDetourFunction; + }lpDetourFunction; + + constexpr bool isFunHooked() { return (bool)((bitFlag & 0xF0) > 0x7); } + constexpr void isFunHooked(bool value) + { + if (value) + bitFlag |= 0xF0; + else + bitFlag -= bitFlag & 0xF0; + } + constexpr bool isClassFunction() { return (bool)((bitFlag & 0xF) > 0x3); } + constexpr void isClassFunction(bool value) + { + if (value) + bitFlag |= 0xF; + else + bitFlag -= bitFlag & 0xF; + } + + static R __cdecl GenericDetour(Types ...args) + { + const void* stackR = _AddressOfReturnAddress(); + + const uintptr_t retAddrs = *(uintptr_t*)stackR; + + void* lpHookFunction; + EasyDetour* pInstance; + + CallCommand pCallCmd; + if (decodeCall((void*)retAddrs, &pCallCmd)) + { + if (pCallCmd.type == NEAR_ABSOLUTE_CALL) + { + /* + ============================ ISSUE #1 ============================ + We cant get the function pointer if is store in another register than EBP + because EBP is saved in every function call. + To solve this issue we need to create a custom prolog and epilog of this function + and save the registers in the process. + EBP is the most likely to be the register used because locals use EBP + with some offset to be accessed + ================================================================== + */ + if (pCallCmd.types.callMPTR.reg == REG_EBP) + { + const void* basePointer = (void*)((char*)stackR - sizeof(void*)); + lpHookFunction = (void*)(*(intptr_t*)(*((intptr_t*)basePointer) + pCallCmd.types.callMPTR.offset)); + } + else + lpHookFunction = nullptr; + } + else + { +#ifdef _M_AMD64 + const uintptr_t relativePtr = *((uint32_t*)(retAddrs - 0x4)) | 0xffffffff00000000; +#else + //const uintptr_t relativePtr = *((uintptr_t*)(retAddrs - 0x4)); + const uintptr_t relativePtr = *((uint32_t*)(retAddrs - 0x4)); +#endif + lpHookFunction = (void*)(relativePtr + retAddrs); + } + } + else + lpHookFunction = nullptr; + + pInstance = reinterpret_cast*>(easydetour_internals::__easy_detour_Iternal_class::_getDetourInstance(lpHookFunction)); + + if (!pInstance) + { + // if We reach here, we got a big problem. But we can't call a null pointer, so is nothing left to do... + return (R)0; + } + if (pInstance->isClassFunction()) + { + T* const klass = pInstance->pInstance; + return (*klass.*pInstance->lpDetourFunction.lpClassFunction)(pInstance->lpTrampolineFunction, args...); + } + return pInstance->lpDetourFunction.lpDetourFunction(pInstance->pInstance, pInstance->lpTrampolineFunction, args...); + } + }; + + template + EasyDetour make_detour(T* klass, R(*pTargetFunction)(Types...)) + { + return EasyDetour(klass, pTargetFunction); + } + +} + + +namespace easydetour_internals { + + class __easy_detour_Iternal_class + { + public: + template + friend class EasyDetour; + + static void _addEasyDetourInstance(void* pInstance, void* pDetourTargetFunction); + static void _removeDetourInstance(void* pDetourTargetFunction); + static void* _getDetourInstance(void* pDetourTargetFunction); + + private: + /// + /// key-> TargetFunction | value -> easyDetour instance + /// + static std::map easydetourMap; + static std::mutex mtx; + }; +} \ No newline at end of file diff --git a/obse/include/x86Decoder.h b/obse/include/x86Decoder.h new file mode 100644 index 0000000..8178002 --- /dev/null +++ b/obse/include/x86Decoder.h @@ -0,0 +1,88 @@ +#pragma once +#include + +#define NONE 0xFFFFFFFF + +#define REG_EAX 0 +#define REG_ECX 1 +#define REG_EDX 2 +#define REG_EBX 3 +#define REG_ESP 4 +#define REG_EBP 5 +#define REG_ESI 6 +#define REG_EDI 7 +#define REG_EPI 8 +#ifdef REG_NONE +#undef REG_NONE +#endif +#define REG_NONE NONE + +#define CMD_NOP 1 +#define CMD_PUSH 2 +#define CMD_CALL 3 +#define CMD_XCHG 4 + +#define NEAR_REALTIVE_CALL 0 +#define NEAR_ABSOLUTE_CALL 1 + +#define TYPE_REG8 1 +#define TYPE_REG32 2 +#define TYPE_REG64 3 +#define TYPE_NONE NONE + +#define COMMAND 0 +#define OPCODE 1 +#define OPERAND0_TYPE 2 +#define OPERAND0 3 +#define OPERAND1_TYPE 4 +#define OPERAND1 5 + +// Command opcode type dest type source +static const unsigned int single_opcode_map[][6] = +{ + {NONE, NONE, TYPE_NONE, REG_NONE, TYPE_NONE, REG_NONE}, + + {CMD_NOP, 0x90, TYPE_NONE, REG_NONE, TYPE_NONE, REG_NONE}, + + {CMD_PUSH, 0x50, TYPE_REG32, REG_EAX, TYPE_NONE, REG_NONE}, + {CMD_PUSH, 0x51, TYPE_REG32, REG_ECX, TYPE_NONE, REG_NONE}, + {CMD_PUSH, 0x52, TYPE_REG32, REG_EDX, TYPE_NONE, REG_NONE}, + {CMD_PUSH, 0x53, TYPE_REG32, REG_EBX, TYPE_NONE, REG_NONE}, + {CMD_PUSH, 0x54, TYPE_REG32, REG_ESP, TYPE_NONE, REG_NONE}, + {CMD_PUSH, 0x55, TYPE_REG32, REG_EBP, TYPE_NONE, REG_NONE}, + {CMD_PUSH, 0x56, TYPE_REG32, REG_ESI, TYPE_NONE, REG_NONE}, + {CMD_PUSH, 0x57, TYPE_REG32, REG_EDI, TYPE_NONE, REG_NONE}, + + {CMD_XCHG, 0x91, TYPE_REG32, REG_ECX, TYPE_REG32, REG_EAX}, + {CMD_XCHG, 0x92, TYPE_REG32, REG_EDX, TYPE_REG32, REG_EAX}, + {CMD_XCHG, 0x93, TYPE_REG32, REG_EBX, TYPE_REG32, REG_EAX}, + {CMD_XCHG, 0x94, TYPE_REG32, REG_ESP, TYPE_REG32, REG_EAX}, + {CMD_XCHG, 0x95, TYPE_REG32, REG_EBP, TYPE_REG32, REG_EAX}, + {CMD_XCHG, 0x96, TYPE_REG32, REG_ESI, TYPE_REG32, REG_EAX}, + {CMD_XCHG, 0x97, TYPE_REG32, REG_EDI, TYPE_REG32, REG_EAX} +}; + +typedef const unsigned int* singleOpcodeMap; + +typedef struct sCallCmd +{ + unsigned int type; + union + { + unsigned int call_r; + struct + { + unsigned int reg; + signed int offset; + } callMPTR; + } types; +} CallCommand, *PCallCommand; + +/// +/// WARNING! This is not a complete decoder! It can fail in some cases +/// +/// The address of the next instruction relative to call +/// A pointer to CallCommand struct +/// On success, it returns true +bool decodeCall(const void* const retAddrs, PCallCommand pCallCmd); +const singleOpcodeMap getSingleOpcodeMap(const unsigned char opcode); \ No newline at end of file diff --git a/obse/obse/Commands_String.cpp b/obse/obse/Commands_String.cpp index 6b307d5..6ba3b27 100644 --- a/obse/obse/Commands_String.cpp +++ b/obse/obse/Commands_String.cpp @@ -249,7 +249,7 @@ static bool Cmd_sv_ToNumeric_Execute(COMMAND_ARGS) StringVar* strVar = g_StringMap.Get(strID); if (strVar) { - const char* cStr = strVar->GetCString(); + const auto [ cStr, _] = strVar->GetCString(); *result = strtod(cStr + startPos, NULL); } @@ -907,7 +907,7 @@ static bool Cmd_sv_PrintBytes_Execute(COMMAND_ARGS) { Console_Print("Get stringVar key"); if (!rhVar) return true; - const char* str = rhVar->GetCString(); + const char* str = std::get<0>(rhVar->GetCString()); Console_Print("%s", str); char buf[3] = {0}; std::string final = ""; diff --git a/obse/obse/ExpressionEvaluator.cpp b/obse/obse/ExpressionEvaluator.cpp index 2f54557..7988369 100644 --- a/obse/obse/ExpressionEvaluator.cpp +++ b/obse/obse/ExpressionEvaluator.cpp @@ -755,7 +755,7 @@ ScriptToken* ExpressionEvaluator::Evaluate() StringVar* strVar = g_StringMap.Get(cmdResult); if (strVar) { - curToken = ScriptToken::Create(strVar->GetCString()); + curToken = ScriptToken::Create(std::get<0>(strVar->GetCString())); } else { curToken = ScriptToken::Create(""); diff --git a/obse/obse/GameAPI.cpp b/obse/obse/GameAPI.cpp index 03d98e4..f7ce125 100644 --- a/obse/obse/GameAPI.cpp +++ b/obse/obse/GameAPI.cpp @@ -373,7 +373,7 @@ static const char* StringFromStringVar(UInt32 strID) { #if OBSE_CORE StringVar* strVar = g_StringMap.Get(strID); - return strVar ? strVar->GetCString() : ""; + return strVar ? std::get<0>(strVar->GetCString()) : ""; #else if (s_StringVarInterface) return s_StringVarInterface->GetString(strID); diff --git a/obse/obse/Hooks_Script.cpp b/obse/obse/Hooks_Script.cpp index c564096..f9d1133 100644 --- a/obse/obse/Hooks_Script.cpp +++ b/obse/obse/Hooks_Script.cpp @@ -75,9 +75,11 @@ static void __stdcall DoExtractString(char* scriptData, UInt32 dataLen, char* de { StringVar* strVar; strVar = g_StringMap.Get(var->data); - if (strVar) - if (strVar->GetLength() < 0x100) // replace string with contents of string var - strcpy_s(dest, strVar->GetLength() + 1, strVar->GetCString()); + if (strVar) { + auto [string, len] = strVar->GetCString(); //len should aready be null terminated + if(len < 0x100) // replace string with contents of string var TODO complain if too big + strcpy_s(dest, len, string); + } } } } // "%e" becomes an empty string diff --git a/obse/obse/PluginAPI.h b/obse/obse/PluginAPI.h index 7670269..8cbffd9 100644 --- a/obse/obse/PluginAPI.h +++ b/obse/obse/PluginAPI.h @@ -121,7 +121,7 @@ struct OBSEConsoleInterface struct OBSEStringVarInterface { enum { - kVersion = 1 + kVersion = 2 }; UInt32 version; @@ -130,6 +130,7 @@ struct OBSEStringVarInterface UInt32 (* CreateString)(const char* value, void* owningScript); void (* Register)(OBSEStringVarInterface* intfc); // DEPRECATED: use RegisterStringVarInterface() in GameAPI.h bool (* Assign)(ParamInfo * paramInfo, void * arg1, TESObjectREFR * thisObj, TESObjectREFR* contObj, Script * scriptObj, ScriptEventList * eventList, double * result, UInt32 * opcodeOffsetPtr, const char* newValue); + const char* (*GetStringWithSize)(UInt32 stringID, UInt32* len); }; // Added in v0016, Deprecated in xOBSE 22.2 use OBSEInputInterface diff --git a/obse/obse/PluginManager.cpp b/obse/obse/PluginManager.cpp index 7f3d4d6..0fc4fe7 100644 --- a/obse/obse/PluginManager.cpp +++ b/obse/obse/PluginManager.cpp @@ -42,7 +42,8 @@ static OBSEStringVarInterface g_OBSEStringVarInterface = PluginAPI::SetString, PluginAPI::CreateString, RegisterStringVarInterface, - AssignToStringVar + AssignToStringVar, + PluginAPI::GetStringWithSize }; bool IsKeyPressedOld(UInt32 key) { return PluginAPI::IsKeyPressedSimulated(key); } diff --git a/obse/obse/ScriptTokens.cpp b/obse/obse/ScriptTokens.cpp index b7fb4c1..59f4d14 100644 --- a/obse/obse/ScriptTokens.cpp +++ b/obse/obse/ScriptTokens.cpp @@ -330,7 +330,12 @@ const char* ScriptToken::GetString() const else if (type == kTokenType_StringVar && value.var) { StringVar* strVar = g_StringMap.Get(value.var->data); - result = strVar ? strVar->GetCString() : NULL; + if (strVar) { + auto [string, _] = strVar->GetCString(); + result = string; + } + else + result = NULL; } #endif return result ? result : empty; diff --git a/obse/obse/ScriptUtils.cpp b/obse/obse/ScriptUtils.cpp index 45187a1..528486d 100644 --- a/obse/obse/ScriptUtils.cpp +++ b/obse/obse/ScriptUtils.cpp @@ -575,7 +575,7 @@ ScriptToken* Eval_TimesEquals_String(OperatorType op, ScriptToken* lh, ScriptTok } strVar->Set(result.c_str()); - return ScriptToken::Create(strVar->GetCString()); + return ScriptToken::Create(std::get<0>(strVar->GetCString())); } ScriptToken* Eval_Multiply_String_Number(OperatorType op, ScriptToken* lh, ScriptToken* rh, ExpressionEvaluator* context) diff --git a/obse/obse/StringVar.cpp b/obse/obse/StringVar.cpp index 09b3899..d38bfee 100644 --- a/obse/obse/StringVar.cpp +++ b/obse/obse/StringVar.cpp @@ -16,11 +16,11 @@ wchar_t* ConvertToWideString(const char* string) { } -char* ConvertToMultibyteString(const wchar_t* widestring) { +std::tuple ConvertToMultibyteString(const wchar_t* widestring) { int sizeMultibyteBuffer = WideCharToMultiByte(CP_ACP, 0, widestring, -1, nullptr, 0, nullptr,nullptr); char* string = new char[sizeMultibyteBuffer]; WideCharToMultiByte(CP_ACP, 0, widestring, -1, string, sizeMultibyteBuffer,nullptr,nullptr); - return string; + return { string, sizeMultibyteBuffer }; } StringVar::StringVar(const char* in_data, UInt32 in_refID) @@ -32,16 +32,18 @@ StringVar::StringVar(const char* in_data, UInt32 in_refID) } std::string StringVar::String() { - char* string = ConvertToMultibyteString(data.c_str()); + auto [string, size] = ConvertToMultibyteString(data.c_str()); return std::string(string); } -const char* StringVar::GetCString() +const std::tuple StringVar::GetCString() { if (modified == true || multibyte_ptr.get() == nullptr) { - multibyte_ptr = std::unique_ptr(ConvertToMultibyteString(data.c_str())); + auto [string, size] = ConvertToMultibyteString(data.c_str()); + multibyte_ptr = std::unique_ptr(string); + multibyte_len = size; } - return multibyte_ptr.get(); + return { multibyte_ptr.get(), (UInt16)multibyte_len }; } void StringVar::Set(const char* newString) @@ -253,7 +255,7 @@ std::string StringVar::SubString(UInt32 startPos, UInt32 numChars) if (startPos < GetLength()) { std::wstring sub = data.substr(startPos, numChars); - char* string = ConvertToMultibyteString(sub.data()); + auto [string, _] = ConvertToMultibyteString(sub.data()); return std::string(string); } else @@ -308,9 +310,9 @@ void StringVarMap::Save(OBSESerializationInterface* intfc) intfc->WriteRecordData(&modIndex, sizeof(UInt8)); intfc->WriteRecordData(&iter->first, sizeof(UInt32)); - UInt16 len = iter->second->GetLength(); + auto [string, len] = iter->second->GetCString(); intfc->WriteRecordData(&len, sizeof(len)); - intfc->WriteRecordData(iter->second->GetCString(), len); + intfc->WriteRecordData(string, len); } intfc->OpenRecord('STVE', 0); @@ -320,7 +322,6 @@ void StringVarMap::Load(OBSESerializationInterface* intfc) { _MESSAGE("Loading strings"); UInt32 type, length, version, stringID, tempRefID; - UInt16 strLength; UInt8 modIndex; char buffer[kMaxMessageLength] = { 0 }; @@ -334,8 +335,11 @@ void StringVarMap::Load(OBSESerializationInterface* intfc) std::set exceededMods; bool bContinue = true; + while (bContinue && intfc->GetNextRecordInfo(&type, &version, &length)) { + UInt16 strLen = 0; + switch (type) { case 'STVE': //end of block @@ -358,12 +362,17 @@ void StringVarMap::Load(OBSESerializationInterface* intfc) } else modIndex = tempRefID >> 24; - intfc->ReadRecordData(&stringID, sizeof(stringID)); - intfc->ReadRecordData(&strLength, sizeof(strLength)); - - intfc->ReadRecordData(buffer, strLength); - buffer[strLength] = 0; + + if (version == 0) { + intfc->ReadRecordData(&strLen, sizeof(strLen)); + } + else { + _MESSAGE("Unrecognized version for STVR record. Version %u, expected max version 1", version); + break; + } + intfc->ReadRecordData(buffer, strLen); + buffer[strLen] = 0; Insert(stringID, new StringVar(buffer, tempRefID)); modVarCounts[modIndex] += 1; @@ -454,7 +463,18 @@ namespace PluginAPI { StringVar* var = g_StringMap.Get(stringID); if (var) - return var->GetCString(); + return std::get<0>(var->GetCString()); + else + return NULL; + } + const char* GetStringWithSize(UInt32 stringID, UInt32* size) + { + StringVar* var = g_StringMap.Get(stringID); + if (var) { + auto [string, len] = var->GetCString(); + if (size) *size = len; + return string; + } else return NULL; } diff --git a/obse/obse/StringVar.h b/obse/obse/StringVar.h index 6257c2b..96a21a7 100644 --- a/obse/obse/StringVar.h +++ b/obse/obse/StringVar.h @@ -22,6 +22,7 @@ class StringVar std::wstring data; UInt8 owningModIndex; std::unique_ptr multibyte_ptr; + size_t multibyte_len; bool modified; public: StringVar(const char* in_data, UInt32 in_refID); @@ -39,7 +40,7 @@ class StringVar static UInt32 GetCharType(char ch); std::string String(); - const char* GetCString(); + const std::tuple GetCString(); UInt32 GetLength(); UInt8 GetOwningModIndex(); }; @@ -69,6 +70,7 @@ bool AssignToStringVar(ParamInfo * paramInfo, void * arg1, TESObjectREFR * thisO namespace PluginAPI { const char* GetString(UInt32 stringID); + const char* GetStringWithSize(UInt32 stringID, UInt32* len); void SetString(UInt32 stringID, const char* newVal); UInt32 CreateString(const char* strVal, void* owningScript); } \ No newline at end of file diff --git a/obse/x86.lib/EasyDetour.lib b/obse/x86.lib/EasyDetour.lib new file mode 100644 index 0000000..ea4abf8 Binary files /dev/null and b/obse/x86.lib/EasyDetour.lib differ