diff --git a/CHANGELOG.md b/CHANGELOG.md
index efb4f683..f489763c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -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
diff --git a/CLEO5.vcxproj b/CLEO5.vcxproj
index c82564ea..850780ea 100644
--- a/CLEO5.vcxproj
+++ b/CLEO5.vcxproj
@@ -53,6 +53,7 @@
+
Create
Create
@@ -77,6 +78,7 @@
+
diff --git a/CLEO5.vcxproj.filters b/CLEO5.vcxproj.filters
index dc072f49..79d21bd5 100644
--- a/CLEO5.vcxproj.filters
+++ b/CLEO5.vcxproj.filters
@@ -99,6 +99,9 @@
source\utils
+
+ source\extensions
+
@@ -161,6 +164,9 @@
source\utils
+
+ source\extensions
+
diff --git a/source/CCustomOpcodeSystem.cpp b/source/CCustomOpcodeSystem.cpp
index e322b26d..482dba5b 100644
--- a/source/CCustomOpcodeSystem.cpp
+++ b/source/CCustomOpcodeSystem.cpp
@@ -3,6 +3,7 @@
#include "CLegacy.h"
#include "CGameVersionManager.h"
#include "CCustomOpcodeSystem.h"
+#include "ScmFunction.h"
#include "CTextManager.h"
#include "CModelInfo.h"
@@ -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 {
@@ -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(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
@@ -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);
@@ -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)
@@ -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 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(::operator new(size));
- Store[allocationPlace] = obj;
- return obj;
- }
-
- void operator delete(void *mem)
- {
- Store[reinterpret_cast(mem)->thisScmFunctionId] = nullptr;
- ::operator delete(mem);
- }
-
- ScmFunction(CRunningScript *thread) :
- prevScmFunctionId(reinterpret_cast(thread)->GetScmFunction())
- {
- auto cs = reinterpret_cast(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(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 */
/************************************************************************/
@@ -2174,10 +2089,17 @@ namespace CLEO
return OR_CONTINUE;
}
- //0AB2=-1,ret
+ //0AB2=-1,cleo_return
OpcodeResult __stdcall opcode_0AB2(CRunningScript *thread)
{
- ScmFunction *scmFunc = ScmFunction::Store[reinterpret_cast(thread)->GetScmFunction()];
+ auto cs = reinterpret_cast(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)
@@ -2185,19 +2107,19 @@ namespace CLEO
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);
@@ -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);
@@ -3084,44 +3006,31 @@ namespace CLEO
//2002=-1, cleo_return_with ...
OpcodeResult __stdcall opcode_2002(CRunningScript* thread)
{
- auto cs = reinterpret_cast(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(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);
}
}
diff --git a/source/CCustomOpcodeSystem.h b/source/CCustomOpcodeSystem.h
index 4835b993..96913524 100644
--- a/source/CCustomOpcodeSystem.h
+++ b/source/CCustomOpcodeSystem.h
@@ -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);
@@ -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:
diff --git a/source/ScmFunction.cpp b/source/ScmFunction.cpp
new file mode 100644
index 00000000..0540d010
--- /dev/null
+++ b/source/ScmFunction.cpp
@@ -0,0 +1,119 @@
+#include "stdafx.h"
+#include "ScmFunction.h"
+#include "CCustomOpcodeSystem.h"
+#include "CScriptEngine.h"
+
+namespace CLEO
+{
+ ScmFunction* ScmFunction::store[Store_Size] = { 0 };
+ size_t ScmFunction::allocationPlace = 0;
+
+ ScmFunction* ScmFunction::Get(unsigned short idx)
+ {
+ if (idx >= Store_Size)
+ return nullptr;
+
+ return store[idx];
+ }
+
+ void ScmFunction::Clear()
+ {
+ for each (ScmFunction* scmFunc in store)
+ {
+ if (scmFunc != nullptr) delete scmFunc;
+ }
+ ScmFunction::allocationPlace = 0;
+ }
+
+ void* ScmFunction::operator new(size_t size)
+ {
+ size_t start_search = allocationPlace;
+ while (store[allocationPlace] != nullptr) // 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(::operator new(size));
+ store[allocationPlace] = obj;
+ return obj;
+ }
+
+ void ScmFunction::operator delete(void* mem)
+ {
+ store[reinterpret_cast(mem)->thisScmFunctionId] = nullptr;
+ ::operator delete(mem);
+ }
+
+ ScmFunction::ScmFunction(CLEO::CRunningScript* thread) :
+ prevScmFunctionId(reinterpret_cast(thread)->GetScmFunction())
+ {
+ auto cs = reinterpret_cast(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 ScmFunction::Return(CRunningScript* thread)
+ {
+ auto cs = reinterpret_cast(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);
+ }
+};
diff --git a/source/ScmFunction.h b/source/ScmFunction.h
new file mode 100644
index 00000000..31570552
--- /dev/null
+++ b/source/ScmFunction.h
@@ -0,0 +1,39 @@
+#pragma once
+#include "..\cleo_sdk\CLEO.h"
+#include "CDebug.h"
+
+#include
+#include
+#include
+
+namespace CLEO
+{
+ struct ScmFunction
+ {
+ static const size_t Store_Size = 0x400;
+ static ScmFunction* store[Store_Size];
+ static size_t allocationPlace; // contains an index of last allocated object
+ static ScmFunction* Get(unsigned short idx);
+ static void Clear();
+
+ unsigned short prevScmFunctionId, thisScmFunctionId;
+ void* savedBaseIP;
+ BYTE* retnAddress;
+ BYTE* savedStack[8]; // gosub stack
+ WORD savedSP;
+ SCRIPT_VAR savedTls[32];
+ std::list stringParams; // texts with this scope lifetime
+ bool savedCondResult;
+ eLogicalOperation savedLogicalOp;
+ bool savedNotFlag;
+ 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);
+ void operator delete(void* mem);
+ ScmFunction(CRunningScript* thread);
+
+ void Return(CRunningScript* thread);
+ };
+}