Skip to content

Commit

Permalink
new return opcodes (#44)
Browse files Browse the repository at this point in the history
* Stack validation in cleo_return opcodes

ScmFunction moved to separate files
cleo_return_with renamed to cleo_return_true
cleo_return_fallse now returns parameters
new opcode cleo_return_none

* Return opcodes updated.
  • Loading branch information
MiranDMC authored Dec 20, 2023
1 parent 9fa9b50 commit 44b761b
Show file tree
Hide file tree
Showing 7 changed files with 240 additions and 165 deletions.
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

0 comments on commit 44b761b

Please sign in to comment.