Skip to content

Commit

Permalink
Use proper length for tostring operator and serialization routines
Browse files Browse the repository at this point in the history
Fix #237
  • Loading branch information
llde committed Oct 27, 2024
1 parent 3c8e842 commit 4988c0d
Show file tree
Hide file tree
Showing 13 changed files with 377 additions and 28 deletions.
230 changes: 230 additions & 0 deletions obse/include/EasyDetour.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,230 @@
#pragma once

#ifdef _M_ARM_
#error Does't support ARM
#endif

#define WIN32_LEAN_AND_MEN
#include <Windows.h>
#include <inttypes.h>
#include <intrin.h>
#include <map>
#include <mutex>

#include "detours.h"
#include "x86Decoder.h"

namespace easydetour_internals { class __easy_detour_Iternal_class; }


namespace EasyDetour
{

template<typename T, typename R, typename ... Types>
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<T, R, Types...>* 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<T, R, Types...>*>(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<typename T, typename R, typename ...Types>
EasyDetour<T, R, Types...> make_detour(T* klass, R(*pTargetFunction)(Types...))
{
return EasyDetour<T, R, Types...>(klass, pTargetFunction);
}

}


namespace easydetour_internals {

class __easy_detour_Iternal_class
{
public:
template<typename T, typename R, typename ...Types>
friend class EasyDetour;

static void _addEasyDetourInstance(void* pInstance, void* pDetourTargetFunction);
static void _removeDetourInstance(void* pDetourTargetFunction);
static void* _getDetourInstance(void* pDetourTargetFunction);

private:
/// <summary>
/// key-> TargetFunction | value -> easyDetour instance
/// </summary>
static std::map<void*, void*> easydetourMap;
static std::mutex mtx;
};
}
88 changes: 88 additions & 0 deletions obse/include/x86Decoder.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
#pragma once
#include <inttypes.h>

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

/// <summary>
/// WARNING! This is not a complete decoder! It can fail in some cases
/// </summary>
/// <param name="retAddrs">The address of the next instruction relative to call</param>
/// <param name="pCallCmd">A pointer to CallCommand struct</param>
/// <returns>On success, it returns true</returns>
bool decodeCall(const void* const retAddrs, PCallCommand pCallCmd);
const singleOpcodeMap getSingleOpcodeMap(const unsigned char opcode);
4 changes: 2 additions & 2 deletions obse/obse/Commands_String.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

Expand Down Expand Up @@ -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 = "";
Expand Down
2 changes: 1 addition & 1 deletion obse/obse/ExpressionEvaluator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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("");
Expand Down
2 changes: 1 addition & 1 deletion obse/obse/GameAPI.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
8 changes: 5 additions & 3 deletions obse/obse/Hooks_Script.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 2 additions & 1 deletion obse/obse/PluginAPI.h
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ struct OBSEConsoleInterface
struct OBSEStringVarInterface
{
enum {
kVersion = 1
kVersion = 2
};

UInt32 version;
Expand All @@ -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
Expand Down
3 changes: 2 additions & 1 deletion obse/obse/PluginManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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); }
Expand Down
Loading

0 comments on commit 4988c0d

Please sign in to comment.