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); + }; +}