Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Reimplement native methods for reading and storing opcode parameters #165

Draft
wants to merge 13 commits into
base: master
Choose a base branch
from
15 changes: 15 additions & 0 deletions cleo_sdk/CLEO.h
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,8 @@ enum eDataType : BYTE
DT_LVAR_STRING, // localVarVString @v
DT_VAR_STRING_ARRAY, // globalVarStringArr v$(,)
DT_LVAR_STRING_ARRAY, // localVarStringArr @v(,)
DT_PUSH,
DT_POP,
DT_INVALID = 0xFF // CLEO internal
};

Expand All @@ -78,6 +80,7 @@ enum eArrayDataType : BYTE
ADT_FLOAT, // variable with integer
ADT_TEXTLABEL, // variable with short string (8 char)
ADT_STRING, // variable with long string (16 char)
ADT_POP,
ADT_NONE = 0xFF // CLEO internal
};
static const BYTE ArrayDataTypeMask = ADT_INT | ADT_FLOAT | ADT_TEXTLABEL | ADT_STRING; // array flags byte contains other info too. Type needs to be masked when read
Expand Down Expand Up @@ -159,6 +162,8 @@ static bool IsVariable(eDataType type) // can carry int, float, pointer to text
case DT_VAR_ARRAY:
case DT_LVAR:
case DT_LVAR_ARRAY:
case DT_POP:
case DT_PUSH:
return true;
}
return false;
Expand Down Expand Up @@ -432,13 +437,23 @@ struct CRunningScript
int ReadDataByte() { char b = *CurrentIP; ++CurrentIP; return b; }
short ReadDataWord() { short v = *(short*)CurrentIP; CurrentIP += 2; return v; }
int ReadDataInt() { int i = *(int*)CurrentIP; CurrentIP += 4; return i; }
float ReadDataFloat() { float f = *(float*)CurrentIP; CurrentIP += 4; return f; }

void PushStack(BYTE* ptr) { Stack[SP++] = ptr; }
BYTE* PopStack() { return Stack[--SP]; }

WORD GetScmFunction() const { return ScmFunction; }
void SetScmFunction(WORD id) { ScmFunction = id; }

SCRIPT_VAR* GetPointerToLocalVariable(CRunningScript* script, int varIndex);
void ReadArrayInformation(CRunningScript* script, WORD* outArrVarOffset, int* outArrElemIdx);
void CollectParams(CRunningScript*, WORD count);
void StoreParams(CRunningScript*, WORD count);
SCRIPT_VAR* GetPointerToLocalArrayElement(CRunningScript* script, int off, WORD idx, BYTE mul);
void ReadTextLabelFromScript(CRunningScript*, char* buffer, BYTE nBufferLength);
DWORD CollectNextParameterWithoutIncreasingPC(CRunningScript* script);
SCRIPT_VAR* GetPointerToScriptVariable(CRunningScript* script, BYTE type);

#endif // __cplusplus
};
#pragma pack(pop)
Expand Down
1 change: 1 addition & 0 deletions source/CGameVersionManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ namespace CLEO
{ 0x00465AA0, memory_und, 0x00465AA0, 0x00465B20, 0x0046B2C0 }, // MA_STOP_SCRIPT_FUNCTION,
{ 0x00465E60, memory_und, 0x00465E60, 0x00465EE0, 0x0046B640 }, // MA_SCRIPT_OPCODE_HANDLER0_FUNCTION,
{ 0x00464080, memory_und, 0x00464080, 0x00464100, 0x00469790 }, // MA_GET_SCRIPT_PARAMS_FUNCTION,
{ 0x00464250, memory_und, memory_und, memory_und, memory_und }, // MA_GET_NEXT_SCRIPT_PARAM_NO_UPDATE_FUNCTION,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So it is time to add check for US 1.0 version and throw error message for others.

{ 0x00464500, memory_und, 0x00464500, 0x00464580, 0x00469BF0 }, // MA_TRANSMIT_SCRIPT_PARAMS_FUNCTION,
{ 0x00464370, memory_und, 0x00464370, 0x004643F0, 0x00469A70 }, // MA_SET_SCRIPT_PARAMS_FUNCTION,
{ 0x004859D0, memory_und, 0x004859D0, 0x00485A50, 0x0048BF40 }, // MA_SET_SCRIPT_COND_RESULT_FUNCTION,
Expand Down
1 change: 1 addition & 0 deletions source/CGameVersionManager.h
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ namespace CLEO
MA_STOP_SCRIPT_FUNCTION,
MA_SCRIPT_OPCODE_HANDLER0_FUNCTION,
MA_GET_SCRIPT_PARAMS_FUNCTION,
MA_GET_NEXT_SCRIPT_PARAM_NO_UPDATE_FUNCTION,
MA_TRANSMIT_SCRIPT_PARAMS_FUNCTION,
MA_SET_SCRIPT_PARAMS_FUNCTION,
MA_SET_SCRIPT_COND_RESULT_FUNCTION,
Expand Down
288 changes: 288 additions & 0 deletions source/CScriptEngine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#include "CFileMgr.h"
#include "CGame.h"
#include <CTheScripts.h>
#include "patch.h"

#include <sstream>

Expand Down Expand Up @@ -347,6 +348,289 @@ namespace CLEO
GetInstance().ScriptEngine.ReregisterAllScripts();
}

SCRIPT_VAR* CRunningScript::GetPointerToLocalVariable(CRunningScript* script, int varIndex) {
if (IsMission())
return &missionLocals[varIndex];
else
return script->GetVarPtr(varIndex);
}

void CRunningScript::ReadArrayInformation(CRunningScript* script, WORD* outArrVarOffset, int* outArrElemIdx) {
BYTE* ip = script->GetBytePointer();

*outArrVarOffset = script->ReadDataWord();
WORD arrayIndexVar = script->ReadDataWord();
script->IncPtr(); // skip size
BYTE arrayType = script->ReadDataByte();
eArrayDataType elementType = static_cast<eArrayDataType>(arrayType & ArrayDataTypeMask);
bool isGlobalIndex = arrayType >> 7; // is high bit set

if (elementType == ADT_POP) {
*outArrElemIdx = GetInstance().StackPop();
return;
}

if (isGlobalIndex) {
*outArrElemIdx = *reinterpret_cast<int*>(&scmBlock[arrayIndexVar]);
}
else {
*outArrElemIdx = GetPointerToLocalVariable(script, arrayIndexVar)->dwParam;
}
}

void CRunningScript::CollectParams(CRunningScript* script, WORD count) {
WORD arrVarOffset;
int arrElemIdx;

for (int i = 0; i < count; i++) {
int dt = script->ReadDataByte();
switch (dt) {
case DT_DWORD:
opcodeParams[i].dwParam = script->ReadDataInt();
break;
case DT_VAR:
{
WORD index = script->ReadDataVarIndex();
opcodeParams[i] = *reinterpret_cast<SCRIPT_VAR*>(&scmBlock[index]);
break;
}
case DT_LVAR:
{
WORD index = script->ReadDataVarIndex();
opcodeParams[i] = *GetPointerToLocalVariable(script, index);
break;
}
case DT_BYTE:
opcodeParams[i].dwParam = script->ReadDataByte();
break;
case DT_WORD:
opcodeParams[i].dwParam = script->ReadDataWord();
break;
case DT_FLOAT:
opcodeParams[i].fParam = script->ReadDataFloat();
break;
case DT_VAR_ARRAY:
ReadArrayInformation(script, &arrVarOffset, &arrElemIdx);
opcodeParams[i] = *reinterpret_cast<SCRIPT_VAR*>(&scmBlock[arrVarOffset + (4 * arrElemIdx)]);
break;
case DT_LVAR_ARRAY:
ReadArrayInformation(script, &arrVarOffset, &arrElemIdx);
opcodeParams[i] = *GetPointerToLocalVariable(script, arrVarOffset + arrElemIdx);
break;
case DT_POP:
opcodeParams[i].dwParam = GetInstance().StackPop();
break;
}
}
}

// wrapper around CRunningScript::CollectParams to preserve the value of ecx register
static void __declspec(naked) HOOK_CRunningScript__CollectParams()
{
_asm
{
push ecx // save ecx
push dword ptr[esp+8] // count
push ecx // script
call CRunningScript::CollectParams
pop ecx // restore ecx
retn 4
}
}

void CRunningScript::StoreParams(CRunningScript* script, WORD count) {
WORD arrVarOffset;
int arrElemIdx;

for (int i = 0; i < count; i++) {
switch (script->ReadDataByte()) {
case DT_VAR:
{
WORD index = script->ReadDataVarIndex();
*(SCRIPT_VAR*)(&scmBlock[index]) = opcodeParams[i];
break;
}
case DT_LVAR:
{
WORD index = script->ReadDataVarIndex();
*GetPointerToLocalVariable(script, index) = opcodeParams[i];
break;
}
case DT_VAR_ARRAY:
ReadArrayInformation(script, &arrVarOffset, &arrElemIdx);
*(SCRIPT_VAR*)(&scmBlock[arrVarOffset + 4 * arrElemIdx]) = opcodeParams[i];
break;
case DT_LVAR_ARRAY:
ReadArrayInformation(script, &arrVarOffset, &arrElemIdx);
*GetPointerToLocalVariable(script, arrVarOffset + arrElemIdx) = opcodeParams[i];
break;
case DT_PUSH:
GetInstance().StackPush(opcodeParams[i].dwParam);
break;
}
}
}

// wrapper around CRunningScript::StoreParams to preserve the value of ecx register
static void __declspec(naked) HOOK_CRunningScript__StoreParams()
{
_asm
{
push ecx // save ecx
push dword ptr[esp + 8] // count
push ecx // script
call CRunningScript::StoreParams
pop ecx // restore ecx
retn 4
}
}

SCRIPT_VAR* CRunningScript::GetPointerToLocalArrayElement(CRunningScript* script, int off, WORD idx, BYTE mul)
{
int final_index = off + mul * idx;

if (script->IsMission())
return &missionLocals[final_index];
return script->GetVarPtr(final_index);
}

DWORD CRunningScript::CollectNextParameterWithoutIncreasingPC(CRunningScript* script) {
WORD arrVarOffset;
int arrElemIdx;

SCRIPT_VAR result;
result.dwParam = -1;

BYTE* ip = script->GetBytePointer();

int dt = script->ReadDataByte();
switch (dt) {
case DT_DWORD:
result.dwParam = script->ReadDataInt();
break;
case DT_VAR:
{
WORD index = script->ReadDataVarIndex();
result = *reinterpret_cast<SCRIPT_VAR*>(&scmBlock[index]);
break;
}
case DT_LVAR:
{
WORD index = script->ReadDataVarIndex();
result = *GetPointerToLocalVariable(script, index);
break;
}
case DT_BYTE:
result.dwParam = script->ReadDataByte();
break;
case DT_WORD:
result.dwParam = script->ReadDataWord();
break;
case DT_FLOAT:
result.fParam = script->ReadDataFloat();
break;
case DT_VAR_ARRAY:
ReadArrayInformation(script, &arrVarOffset, &arrElemIdx);
result = *reinterpret_cast<SCRIPT_VAR*>(&scmBlock[arrVarOffset + (4 * arrElemIdx)]);
break;
case DT_LVAR_ARRAY:
ReadArrayInformation(script, &arrVarOffset, &arrElemIdx);
result = *GetPointerToLocalVariable(script, arrVarOffset + arrElemIdx);
break;
case DT_POP:
break;
}

script->SetIp(ip);

return result.dwParam;
}

// wrapper around CRunningScript::CollectNextParameterWithoutIncreasingPC to preserve the value of ecx register
static void __declspec(naked) HOOK_CRunningScript__CollectNextParameterWithoutIncreasingPC()
{
_asm
{
push ecx // save ecx
push ecx // script
call CRunningScript::CollectNextParameterWithoutIncreasingPC
pop ecx // restore ecx
retn
}
}


SCRIPT_VAR* CRunningScript::GetPointerToScriptVariable(CRunningScript* script, BYTE type) {
BYTE arrElemSize;
WORD arrVarOffset;
int arrElemIdx;

int dt = script->ReadDataByte();
switch (dt) {
case DT_VAR:
case DT_VAR_STRING:
case DT_VAR_TEXTLABEL:
{
auto index = script->ReadDataWord();
return reinterpret_cast<SCRIPT_VAR*>(&CTheScripts::ScriptSpace[index]);
}
case DT_LVAR:
case DT_LVAR_STRING:
case DT_LVAR_TEXTLABEL:
{
auto index = script->ReadDataWord();
return GetPointerToLocalVariable(script, index);
}
case DT_VAR_ARRAY:
case DT_VAR_STRING_ARRAY:
case DT_VAR_TEXTLABEL_ARRAY:
{
ReadArrayInformation(script, &arrVarOffset, &arrElemIdx);
if (dt == DT_VAR_STRING_ARRAY)
return reinterpret_cast<SCRIPT_VAR*>(&CTheScripts::ScriptSpace[16 * arrElemIdx + arrVarOffset]);
else if (dt == DT_VAR_TEXTLABEL_ARRAY)
return reinterpret_cast<SCRIPT_VAR*>(&CTheScripts::ScriptSpace[8 * arrElemIdx + arrVarOffset]);
else // DT_VAR_ARRAY
return reinterpret_cast<SCRIPT_VAR*>(&CTheScripts::ScriptSpace[4 * arrElemIdx + arrVarOffset]);
}
case DT_LVAR_ARRAY:
case DT_LVAR_STRING_ARRAY:
case DT_LVAR_TEXTLABEL_ARRAY:
{
ReadArrayInformation(script, &arrVarOffset, &arrElemIdx);
if (dt == DT_LVAR_STRING_ARRAY)
arrElemSize = 4;
else if (dt == DT_LVAR_TEXTLABEL_ARRAY)
arrElemSize = 2;
else // DT_LVAR_ARRAY
arrElemSize = 1;
return GetPointerToLocalArrayElement(script, arrVarOffset, arrElemIdx, arrElemSize);
}
case DT_PUSH:
{
GetInstance().StackPush(0); // allocate space for the variable
return reinterpret_cast<SCRIPT_VAR*>(&GetInstance().CleoStack.top());
}
case DT_POP:
// todo: does not work, value should be popped from stack
return reinterpret_cast<SCRIPT_VAR*>(&GetInstance().CleoStack.top());
}
}

// wrapper around CRunningScript::CollectNextParameterWithoutIncreasingPC to preserve the value of ecx register
static void __declspec(naked) HOOK_CRunningScript__GetPointerToScriptVariable()
{
_asm
{
push ecx // save ecx
push dword ptr[esp + 8] // type
push ecx // script
call CRunningScript::GetPointerToScriptVariable
pop ecx // restore ecx
retn 4
}
}

struct CleoSafeHeader
{
const static unsigned sign;
Expand Down Expand Up @@ -944,6 +1228,10 @@ namespace CLEO
inj.ReplaceFunction(OnLoadScmData, gvm.TranslateMemoryAddress(MA_CALL_LOAD_SCM_DATA));
inj.ReplaceFunction(OnSaveScmData, gvm.TranslateMemoryAddress(MA_CALL_SAVE_SCM_DATA));
inj.InjectFunction(&opcode_004E_hook, gvm.TranslateMemoryAddress(MA_OPCODE_004E));
inj.InjectFunction(&HOOK_CRunningScript__CollectParams, gvm.TranslateMemoryAddress(MA_GET_SCRIPT_PARAMS_FUNCTION));
inj.InjectFunction(&HOOK_CRunningScript__CollectNextParameterWithoutIncreasingPC, gvm.TranslateMemoryAddress(MA_GET_NEXT_SCRIPT_PARAM_NO_UPDATE_FUNCTION));
inj.InjectFunction(&HOOK_CRunningScript__StoreParams, gvm.TranslateMemoryAddress(MA_SET_SCRIPT_PARAMS_FUNCTION));
inj.InjectFunction(&HOOK_CRunningScript__GetPointerToScriptVariable, gvm.TranslateMemoryAddress(MA_GET_SCRIPT_PARAM_POINTER2_FUNCTION));
}

CScriptEngine::~CScriptEngine()
Expand Down
11 changes: 11 additions & 0 deletions source/CleoBase.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ namespace CLEO
{
CCleoInstance CleoInstance;
CCleoInstance& GetInstance() { return CleoInstance; }
std::stack<int> CleoStack;
Copy link
Collaborator

@MiranDMC MiranDMC Jul 22, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like it should hold SCRIPT_VAR type. Also what with the strings?
Named temp variable stack or something like that.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

strings are out of scope for now


inline CCleoInstance::CCleoInstance()
{
Expand Down Expand Up @@ -309,5 +310,15 @@ namespace CLEO

return CreateStringList(found);
}

void CCleoInstance::StackPush(int val) {
CleoStack.push(val);
}

int CCleoInstance::StackPop() {
int val = CleoStack.top();
CleoStack.pop();
return val;
}
}

Loading