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

Stack validation in cleo_return opcodes #44

Merged
merged 2 commits into from
Dec 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
- **2000 ([resolve_filepath](https://library.sannybuilder.com/#/sa/CLEO/2000))**
- **2001 ([get_script_filename](https://library.sannybuilder.com/#/sa/CLEO/2001))**
- **2002 ([cleo_return_with](https://library.sannybuilder.com/#/sa/CLEO/2002))**
- **2003 ([cleo_return_false](https://library.sannybuilder.com/#/sa/CLEO/2003))**
- **2003 ([cleo_return_fail](https://library.sannybuilder.com/#/sa/CLEO/2003))**
- 'argument count' parameter of **0AB1 (cleo_call)** is now optional. `cleo_call @LABEL args 0` can be written as `cleo_call @LABEL`
- 'argument count' parameter of **0AB2 (cleo_return)** is now optional. `cleo_return 0` can be written as `cleo_return`
- opcodes **0AAB**, **0AE4**, **0AE5**, **0AE6**, **0AE7** and **0AE8** moved to the [FileSystemOperations](https://github.com/cleolibrary/CLEO5/tree/master/cleo_plugins/FileSystemOperations) plugin
Expand Down
2 changes: 2 additions & 0 deletions CLEO5.vcxproj
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
<ClCompile Include="source\CTextManager.cpp" />
<ClCompile Include="source\dllmain.cpp" />
<ClCompile Include="source\PluginSdkExternals.cpp" />
<ClCompile Include="source\ScmFunction.cpp" />
<ClCompile Include="source\stdafx.cpp">
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">Create</PrecompiledHeader>
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">Create</PrecompiledHeader>
Expand All @@ -77,6 +78,7 @@
<ClInclude Include="source\FileEnumerator.h" />
<ClInclude Include="source\Mem.h" />
<ClInclude Include="source\resource.h" />
<ClInclude Include="source\ScmFunction.h" />
<ClInclude Include="source\Singleton.h" />
<ClInclude Include="source\stdafx.h" />
</ItemGroup>
Expand Down
6 changes: 6 additions & 0 deletions CLEO5.vcxproj.filters
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,9 @@
<ClCompile Include="source\CDebug.cpp">
<Filter>source\utils</Filter>
</ClCompile>
<ClCompile Include="source\ScmFunction.cpp">
<Filter>source\extensions</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="source\stdafx.h">
Expand Down Expand Up @@ -161,6 +164,9 @@
<ClInclude Include="source\Singleton.h">
<Filter>source\utils</Filter>
</ClInclude>
<ClInclude Include="source\ScmFunction.h">
<Filter>source\extensions</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<None Include="source\cleo.def">
Expand Down
235 changes: 72 additions & 163 deletions source/CCustomOpcodeSystem.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#include "CLegacy.h"
#include "CGameVersionManager.h"
#include "CCustomOpcodeSystem.h"
#include "ScmFunction.h"
#include "CTextManager.h"
#include "CModelInfo.h"

Expand Down Expand Up @@ -127,7 +128,7 @@ namespace CLEO
OpcodeResult __stdcall opcode_2000(CRunningScript* thread); // resolve_filepath
OpcodeResult __stdcall opcode_2001(CRunningScript* thread); // get_script_filename
OpcodeResult __stdcall opcode_2002(CRunningScript* thread); // cleo_return_with
OpcodeResult __stdcall opcode_2003(CRunningScript* thread); // cleo_return_false
OpcodeResult __stdcall opcode_2003(CRunningScript* thread); // cleo_return_fail

typedef void(*FuncScriptDeleteDelegateT) (CRunningScript *script);
struct ScriptDeleteDelegate {
Expand Down Expand Up @@ -263,6 +264,44 @@ namespace CLEO
return (callbackResult != OR_NONE) ? callbackResult : result;
}

OpcodeResult CCustomOpcodeSystem::CleoReturnGeneric(WORD opcode, CRunningScript* thread, bool returnArgs)
{
auto cs = reinterpret_cast<CCustomScript*>(thread);

ScmFunction* scmFunc = ScmFunction::Get(cs->GetScmFunction());
if (scmFunc == nullptr)
{
SHOW_ERROR("Invalid Cleo Call reference. [%04X] possibly used without preceding [0AB1] in script %s\nScript suspended.", opcode, cs->GetInfoStr().c_str());
return CCustomOpcodeSystem::ErrorSuspendScript(thread);
}

DWORD returnParamCount = 0;
if(returnArgs)
{
returnParamCount = GetVarArgCount(cs);
if (returnParamCount) GetScriptParams(cs, returnParamCount);
}

scmFunc->Return(cs); // jump back to cleo_call, right after last input param. Return slot var args starts here
if (scmFunc->moduleExportRef != nullptr) GetInstance().ModuleSystem.ReleaseModuleRef((char*)scmFunc->moduleExportRef); // exiting export - release module
delete scmFunc;

if (returnArgs)
{
DWORD returnSlotCount = GetVarArgCount(cs);
if (returnParamCount != returnSlotCount) // new CLEO5 opcode, strict error checks
{
SHOW_ERROR("Opcode [%04X] returned %d params, while function caller expected %d in script %s\nScript suspended.", opcode, returnParamCount, returnSlotCount, cs->GetInfoStr().c_str());
return CCustomOpcodeSystem::ErrorSuspendScript(cs);
}

if (returnSlotCount) SetScriptParams(cs, returnSlotCount);
cs->IncPtr(); // skip var args
}

return OR_CONTINUE;
}

OpcodeResult CCustomOpcodeSystem::ErrorSuspendScript(CRunningScript* thread)
{
//thread->SetActive(false): // will crash game if no active script left
Expand Down Expand Up @@ -295,7 +334,7 @@ namespace CLEO
m_hNativeLibs.clear();

// clean up after opcode_0AB1
ResetScmFunctionStore();
ScmFunction::Clear();

// clean up after opcode_0AC8
std::for_each(m_pAllocations.begin(), m_pAllocations.end(), free);
Expand Down Expand Up @@ -403,7 +442,7 @@ namespace CLEO
CLEO_RegisterOpcode(0x2000, opcode_2000); // resolve_filepath
CLEO_RegisterOpcode(0x2001, opcode_2001); // get_script_filename
CLEO_RegisterOpcode(0x2002, opcode_2002); // cleo_return_with
CLEO_RegisterOpcode(0x2003, opcode_2003); // cleo_return_false
CLEO_RegisterOpcode(0x2003, opcode_2003); // cleo_return_fail
}

void CCustomOpcodeSystem::Inject(CCodeInjector& inj)
Expand Down Expand Up @@ -1151,130 +1190,6 @@ namespace CLEO
return count;
}

struct ScmFunction
{
unsigned short prevScmFunctionId, thisScmFunctionId;
void* savedBaseIP;
BYTE *retnAddress;
BYTE* savedStack[8]; // gosub stack
WORD savedSP;
SCRIPT_VAR savedTls[32];
std::list<std::string> stringParams; // texts with this scope lifetime
bool savedCondResult;
eLogicalOperation savedLogicalOp;
bool savedNotFlag;
static const size_t store_size = 0x400;
static ScmFunction *Store[store_size];
static size_t allocationPlace; // contains an index of last allocated object
void* moduleExportRef = 0; // modules switching. Points to modules baseIP in case if this is export call
std::string savedScriptFileDir; // modules switching
std::string savedScriptFileName; // modules switching

void *operator new(size_t size)
{
size_t start_search = allocationPlace;
while (Store[allocationPlace]) // find first unused position in store
{
if (++allocationPlace >= store_size) allocationPlace = 0; // end of store reached
if (allocationPlace == start_search)
{
SHOW_ERROR("CLEO function storage stack overfllow!");
throw std::bad_alloc(); // the store is filled up
}
}
ScmFunction *obj = reinterpret_cast<ScmFunction *>(::operator new(size));
Store[allocationPlace] = obj;
return obj;
}

void operator delete(void *mem)
{
Store[reinterpret_cast<ScmFunction *>(mem)->thisScmFunctionId] = nullptr;
::operator delete(mem);
}

ScmFunction(CRunningScript *thread) :
prevScmFunctionId(reinterpret_cast<CCustomScript*>(thread)->GetScmFunction())
{
auto cs = reinterpret_cast<CCustomScript*>(thread);

// create snapshot of current scope
savedBaseIP = cs->BaseIP;
std::copy(std::begin(cs->Stack), std::end(cs->Stack), std::begin(savedStack));
savedSP = cs->SP;

auto scope = cs->IsMission() ? missionLocals : cs->LocalVar;
std::copy(scope, scope + 32, savedTls);

savedCondResult = cs->bCondResult;
savedLogicalOp = cs->LogicalOp;
savedNotFlag = cs->NotFlag;

savedScriptFileDir = cs->GetScriptFileDir();
savedScriptFileName = cs->GetScriptFileName();

// init new scope
std::fill(std::begin(cs->Stack), std::end(cs->Stack), nullptr);
cs->SP = 0;
cs->bCondResult = false;
cs->LogicalOp = eLogicalOperation::NONE;
cs->NotFlag = false;

cs->SetScmFunction(thisScmFunctionId = (unsigned short)allocationPlace);
}

void Return(CRunningScript *thread)
{
auto cs = reinterpret_cast<CCustomScript*>(thread);

// restore parent scope's gosub call stack
cs->BaseIP = savedBaseIP;
std::copy(std::begin(savedStack), std::end(savedStack), std::begin(cs->Stack));
cs->SP = savedSP;

// restore parent scope's local variables
std::copy(savedTls, savedTls + 32, cs->IsMission() ? missionLocals : cs->LocalVar);

// process conditional result of just ended function in parent scope
bool condResult = cs->bCondResult;
if (savedNotFlag) condResult = !condResult;

if (savedLogicalOp >= eLogicalOperation::AND_2 && savedLogicalOp < eLogicalOperation::AND_END)
{
cs->bCondResult = savedCondResult && condResult;
cs->LogicalOp = --savedLogicalOp;
}
else if(savedLogicalOp >= eLogicalOperation::OR_2 && savedLogicalOp < eLogicalOperation::OR_END)
{
cs->bCondResult = savedCondResult || condResult;
cs->LogicalOp = --savedLogicalOp;
}
else // eLogicalOperation::NONE
{
cs->bCondResult = condResult;
cs->LogicalOp = savedLogicalOp;
}

cs->SetScriptFileDir(savedScriptFileDir.c_str());
cs->SetScriptFileName(savedScriptFileName.c_str());

cs->SetIp(retnAddress);
cs->SetScmFunction(prevScmFunctionId);
}
};

ScmFunction *ScmFunction::Store[store_size] = { /* default initializer - nullptr */ };
size_t ScmFunction::allocationPlace = 0;

void ResetScmFunctionStore()
{
for each(ScmFunction *scmFunc in ScmFunction::Store)
{
if (scmFunc) delete scmFunc;
}
ScmFunction::allocationPlace = 0;
}

/************************************************************************/
/* Opcode definitions */
/************************************************************************/
Expand Down Expand Up @@ -2174,30 +2089,37 @@ namespace CLEO
return OR_CONTINUE;
}

//0AB2=-1,ret
//0AB2=-1,cleo_return
OpcodeResult __stdcall opcode_0AB2(CRunningScript *thread)
{
ScmFunction *scmFunc = ScmFunction::Store[reinterpret_cast<CCustomScript*>(thread)->GetScmFunction()];
auto cs = reinterpret_cast<CCustomScript*>(thread);

ScmFunction* scmFunc = ScmFunction::Get(cs->GetScmFunction());
if (scmFunc == nullptr)
{
SHOW_ERROR("Invalid Cleo Call reference. [0AB2] possibly used without preceding [0AB1] in script %s\nScript suspended.", cs->GetInfoStr().c_str());
return CCustomOpcodeSystem::ErrorSuspendScript(thread);
}

DWORD returnParamCount = GetVarArgCount(thread);
if (returnParamCount)
{
auto paramType = (eDataType)*thread->GetBytePointer();
if (!IsImmInteger(paramType))
{
SHOW_ERROR("Invalid type of first argument in opcode [0AB2], in script %s", ((CCustomScript*)thread)->GetInfoStr().c_str());
SHOW_ERROR("Invalid type of first argument in opcode [0AB2], in script %s", cs->GetInfoStr().c_str());
return CCustomOpcodeSystem::ErrorSuspendScript(thread);
}
DWORD declaredParamCount; *thread >> declaredParamCount;

if(returnParamCount - 1 < declaredParamCount) // minus 'num args' itself
{
SHOW_ERROR("Opcode [0AB2] declared %d return args, but provided %d in script %s\nScript suspended.", declaredParamCount, returnParamCount - 1, ((CCustomScript*)thread)->GetInfoStr().c_str());
SHOW_ERROR("Opcode [0AB2] declared %d return args, but provided %d in script %s\nScript suspended.", declaredParamCount, returnParamCount - 1, cs->GetInfoStr().c_str());
return CCustomOpcodeSystem::ErrorSuspendScript(thread);
}
else if (returnParamCount - 1 > declaredParamCount) // more args than needed, not critical
{
LOG_WARNING(thread, "Opcode [0AB2] declared %d return args, but provided %d in script %s", declaredParamCount, returnParamCount - 1, ((CCustomScript*)thread)->GetInfoStr().c_str());
LOG_WARNING(thread, "Opcode [0AB2] declared %d return args, but provided %d in script %s", declaredParamCount, returnParamCount - 1, cs->GetInfoStr().c_str());
}
}
if (returnParamCount) GetScriptParams(thread, returnParamCount);
Expand All @@ -2210,12 +2132,12 @@ namespace CLEO
if(returnParamCount) returnParamCount--; // do not count the 'num args' argument itself
if (returnSlotCount > returnParamCount)
{
SHOW_ERROR("Opcode [0AB2] returned %d params, while function caller expected %d in script %s\nScript suspended.", returnParamCount, returnSlotCount, ((CCustomScript*)thread)->GetInfoStr().c_str());
SHOW_ERROR("Opcode [0AB2] returned %d params, while function caller expected %d in script %s\nScript suspended.", returnParamCount, returnSlotCount, cs->GetInfoStr().c_str());
return CCustomOpcodeSystem::ErrorSuspendScript(thread);
}
else if (returnSlotCount < returnParamCount) // more args than needed, not critical
{
LOG_WARNING(thread, "Opcode [0AB2] returned %d params, while function caller expected %d in script %s", returnParamCount, returnSlotCount, ((CCustomScript*)thread)->GetInfoStr().c_str());
LOG_WARNING(thread, "Opcode [0AB2] returned %d params, while function caller expected %d in script %s", returnParamCount, returnSlotCount, cs->GetInfoStr().c_str());
}

if (returnSlotCount) SetScriptParams(thread, returnSlotCount);
Expand Down Expand Up @@ -3084,44 +3006,31 @@ namespace CLEO
//2002=-1, cleo_return_with ...
OpcodeResult __stdcall opcode_2002(CRunningScript* thread)
{
auto cs = reinterpret_cast<CCustomScript*>(thread);
DWORD returnParamCount = GetVarArgCount(cs);

if (returnParamCount) GetScriptParams(cs, returnParamCount);

ScmFunction* scmFunc = ScmFunction::Store[cs->GetScmFunction()];
scmFunc->Return(cs); // jump back to cleo_call, right after last input param. Return slot var args starts here
if (scmFunc->moduleExportRef != nullptr) GetInstance().ModuleSystem.ReleaseModuleRef((char*)scmFunc->moduleExportRef); // exiting export - release module
delete scmFunc;

DWORD returnSlotCount = GetVarArgCount(cs);
if(returnParamCount != returnSlotCount) // new CLEO5 opcode, strict error checks
DWORD argCount = GetVarArgCount(thread);
if (argCount < 1)
{
SHOW_ERROR("Opcode [2002] returned %d params, while function caller expected %d in script %s\nScript suspended.", returnParamCount, returnSlotCount, cs->GetInfoStr().c_str());
return CCustomOpcodeSystem::ErrorSuspendScript(cs);
SHOW_ERROR("Opcode [2002] missing condition result argument in script %s\nScript suspended.", ((CCustomScript*)thread)->GetInfoStr().c_str());
return CCustomOpcodeSystem::ErrorSuspendScript(thread);
}

if (returnSlotCount) SetScriptParams(cs, returnSlotCount);
cs->IncPtr(); // skip var args
DWORD result; *thread >> result;
SetScriptCondResult(thread, result != 0);

SetScriptCondResult(cs, true);
return OR_CONTINUE;
return CCustomOpcodeSystem::CleoReturnGeneric(0x2002, thread, true);
}

//2003=0, cleo_return_false
//2003=-1, cleo_return_fail
OpcodeResult __stdcall opcode_2003(CRunningScript* thread)
{
auto cs = reinterpret_cast<CCustomScript*>(thread);

ScmFunction* scmFunc = ScmFunction::Store[cs->GetScmFunction()];
scmFunc->Return(cs); // jump back to cleo_call, right after last input param. Return slot var args starts here
if (scmFunc->moduleExportRef != nullptr) GetInstance().ModuleSystem.ReleaseModuleRef((char*)scmFunc->moduleExportRef); // exiting export - release module
delete scmFunc;

SkipUnusedVarArgs(thread); // just exit without change of return params
DWORD argCount = GetVarArgCount(thread);
if (argCount != 0) // argument(s) not supported yet
{
SHOW_ERROR("Too many arguments of opcode [2003] in script %s\nScript suspended.", ((CCustomScript*)thread)->GetInfoStr().c_str());
return CCustomOpcodeSystem::ErrorSuspendScript(thread);
}

SetScriptCondResult(cs, false);
return OR_CONTINUE;
SetScriptCondResult(thread, false);
return CCustomOpcodeSystem::CleoReturnGeneric(0x2003, thread, false);
}
}

Expand Down
2 changes: 1 addition & 1 deletion source/CCustomOpcodeSystem.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
namespace CLEO
{
typedef OpcodeResult(__stdcall * CustomOpcodeHandler)(CRunningScript*);
void ResetScmFunctionStore();
bool is_legacy_handle(DWORD dwHandle);
FILE * convert_handle_to_file(DWORD dwHandle);

Expand Down Expand Up @@ -44,6 +43,7 @@ namespace CLEO

static bool RegisterOpcode(WORD opcode, CustomOpcodeHandler callback);

static OpcodeResult CleoReturnGeneric(WORD opcode, CRunningScript* thread, bool returnArgs);
static OpcodeResult ErrorSuspendScript(CRunningScript* thread); // suspend script execution forever

private:
Expand Down
Loading