diff --git a/CHANGELOG.md b/CHANGELOG.md index fbb08539..282a641e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,18 +9,23 @@ - new opcode **2102 ([log_to_file](https://library.sannybuilder.com/#/sa/debug/2102))** - implemented support of opcodes **0662**, **0663** and **0664** (original Rockstar's script debugging opcodes. See DebugUtils.ini) - new and updated opcodes + - **0B1E ([sign_extend](https://library.sannybuilder.com/#/sa/bitwise/0B1E))** - **0DD5 ([get_game_platform](https://library.sannybuilder.com/#/sa/CLEO/0DD5))** - **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_fail](https://library.sannybuilder.com/#/sa/CLEO/2003))** - **2004 ([forget_memory](https://library.sannybuilder.com/#/sa/CLEO/2004))** + - **2300 ([get_file_position](https://library.sannybuilder.com/#/sa/file/2300))** + - **2301 ([read_block_from_file](https://library.sannybuilder.com/#/sa/file/2301))** + - opcodes **0A9A**, **0A9B**, **0A9C**, **0A9D**, **0A9E**, **0AAB**, **0AD5**, **0AD6**, **0AD7**, **0AD8**, **0AD9**, **0ADA**, **0AE4**, **0AE5**, **0AE6**, **0AE7** and **0AE8** moved to the [FileSystemOperations](https://github.com/cleolibrary/CLEO5/tree/master/cleo_plugins/FileSystemOperations) plugin + - fixed bug preventing file stream opcodes from working correctly for read-write modes + - fixed buffer overflows in file stream read opcodes + - added/fixed support of all file stream opcodes in legacy mode (Cleo3) - '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 - **cleo_return_\*** opcodes now can pass strings as return arguments - SCM functions **(0AB1)** now keep their own GOSUB's call stack - - new opcode **0B1E ([sign_extend](https://library.sannybuilder.com/#/sa/bitwise/0B1E))** - changes in file operations - file paths can now use 'virtual absolute paths'. Use prefix in file path strings to access predefined locations: - `root:\` for _game root_ directory @@ -49,6 +54,7 @@ - new SDK method: CLEO_GetVarArgCount - new SDK method: CLEO_SkipUnusedVarArgs - new SDK method: CLEO_ReadParamsFormatted +- new SDK method: CLEO_ReadStringParamWriteBuffer - new SDK method: CLEO_GetScriptVersion - new SDK method: CLEO_GetScriptInfoStr - new SDK method: CLEO_ResolvePath diff --git a/cleo_plugins/FileSystemOperations/FileSystemOperations.cpp b/cleo_plugins/FileSystemOperations/FileSystemOperations.cpp index 45f9395b..62c43901 100644 --- a/cleo_plugins/FileSystemOperations/FileSystemOperations.cpp +++ b/cleo_plugins/FileSystemOperations/FileSystemOperations.cpp @@ -1,18 +1,30 @@ #include "plugin.h" #include "CLEO.h" +#include "FileUtils.h" +#include "Utils.h" #include using namespace CLEO; using namespace plugin; +#define READ_HANDLE_PARAM() CLEO_GetIntOpcodeParam(thread); \ + if((size_t)handle <= MinValidAddress) \ + { auto info = scriptInfoStr(thread); SHOW_ERROR("Invalid '0x%X' file handle param in script %s \nScript suspended.", handle, info.c_str()); return thread->Suspend(); } \ + else if(m_hFiles.find(handle) == m_hFiles.end()) { auto info = scriptInfoStr(thread); SHOW_ERROR("Invalid or already closed '0x%X' file handle param in script %s \nScript suspended.", handle, info.c_str()); return thread->Suspend(); } + class FileSystemOperations { public: + static std::set m_hFiles; static std::set m_hFileSearches; static void WINAPI OnFinalizeScriptObjects() { + // clean up opened files + for (auto handle : m_hFiles) File::close(handle); + m_hFiles.clear(); + // clean up file searches for (auto handle : m_hFileSearches) FindClose(handle); m_hFileSearches.clear(); @@ -23,7 +35,23 @@ class FileSystemOperations auto cleoVer = CLEO_GetVersion(); if (cleoVer >= CLEO_VERSION) { + File::initialize(CLEO_GetGameVersion()); // file utils + //register opcodes + CLEO_RegisterOpcode(0x0A9A, opcode_0A9A); + CLEO_RegisterOpcode(0x0A9B, opcode_0A9B); + CLEO_RegisterOpcode(0x0A9C, opcode_0A9C); + CLEO_RegisterOpcode(0x0A9D, opcode_0A9D); + CLEO_RegisterOpcode(0x0A9E, opcode_0A9E); + CLEO_RegisterOpcode(0x0AD5, opcode_0AD5); + CLEO_RegisterOpcode(0x0AD6, opcode_0AD6); + CLEO_RegisterOpcode(0x0AD7, opcode_0AD7); + CLEO_RegisterOpcode(0x0AD8, opcode_0AD8); + CLEO_RegisterOpcode(0x0AD9, opcode_0AD9); + CLEO_RegisterOpcode(0x0ADA, opcode_0ADA); + CLEO_RegisterOpcode(0x2300, opcode_2300); + CLEO_RegisterOpcode(0x2301, opcode_2301); + CLEO_RegisterOpcode(0x0AAB, Script_FS_FileExists); CLEO_RegisterOpcode(0x0AE4, Script_FS_DirectoryExists); CLEO_RegisterOpcode(0x0AE5, Script_FS_CreateDirectory); @@ -58,6 +86,94 @@ class FileSystemOperations return path; } + //0A9A=3,%3d% = openfile %1d% mode %2d% // IF and SET + static OpcodeResult WINAPI opcode_0A9A(CRunningScript* thread) + { + auto filename = ReadPathParam(thread); + + char mode[16]; + auto paramType = CLEO_GetOperandType(thread); + if (IsImmInteger(paramType) || IsVariable(paramType)) + { + // integer param (for backward compatibility with CLEO 3) + union + { + DWORD uParam; + char strParam[4]; + } param; + param.uParam = CLEO_GetIntOpcodeParam(thread); + strcpy(mode, param.strParam); + } + else + { + CLEO_ReadStringOpcodeParam(thread, mode, sizeof(mode)); + } + + // either CLEO 3 or CLEO 4 made a big mistake! (they differ in one major unapparent preference) + // lets try to resolve this with a legacy mode + bool legacy = CLEO_GetScriptVersion(thread) < CLEO_VER_4_3; + + auto handle = File::open(filename.c_str(), mode, legacy); + if (!File::isOk(handle)) + { + CLEO_SetIntOpcodeParam(thread, NULL); + CLEO_SetThreadCondResult(thread, false); + return OR_CONTINUE; + } + + m_hFiles.insert(handle); + CLEO_SetIntOpcodeParam(thread, handle); + CLEO_SetThreadCondResult(thread, true); + return OR_CONTINUE; + } + + //0A9B=1,closefile %1d% + static OpcodeResult WINAPI opcode_0A9B(CRunningScript* thread) + { + DWORD handle = READ_HANDLE_PARAM(); + + if (m_hFiles.find(handle) != m_hFiles.end()) + { + File::close(handle); + m_hFiles.erase(handle); + } + return OR_CONTINUE; + } + + //0A9C=2,%2d% = file %1d% size + static OpcodeResult WINAPI opcode_0A9C(CRunningScript* thread) + { + DWORD handle = READ_HANDLE_PARAM(); + + auto size = File::getSize(handle); + CLEO_SetIntOpcodeParam(thread, size); + return OR_CONTINUE; + } + + //0A9D=3,readfile %1d% size %2d% to %3d% + static OpcodeResult WINAPI opcode_0A9D(CRunningScript* thread) + { + DWORD handle = READ_HANDLE_PARAM(); + DWORD size = CLEO_GetIntOpcodeParam(thread); + SCRIPT_VAR* buffer = CLEO_GetPointerToScriptVariable(thread); + + buffer->dwParam = 0; // https://github.com/cleolibrary/CLEO4/issues/91 + File::read(handle, buffer, size); + return OR_CONTINUE; + } + + //0A9E=3,writefile %1d% size %2d% from %3d% + static OpcodeResult WINAPI opcode_0A9E(CRunningScript* thread) + { + DWORD handle = READ_HANDLE_PARAM(); + DWORD size = CLEO_GetIntOpcodeParam(thread); + SCRIPT_VAR* buffer = CLEO_GetPointerToScriptVariable(thread); + + File::write(handle, buffer, size); + if (File::isOk(handle)) File::flush(handle); + return OR_CONTINUE; + } + // 0AAB=1, file_exists %1s% static OpcodeResult WINAPI Script_FS_FileExists(CRunningScript* thread) { @@ -70,6 +186,181 @@ class FileSystemOperations return OR_CONTINUE; } + //0AD5=3,file %1d% seek %2d% from_origin %3d% //IF and SET + static OpcodeResult WINAPI opcode_0AD5(CRunningScript* thread) + { + DWORD handle = READ_HANDLE_PARAM(); + int offset = (int)CLEO_GetIntOpcodeParam(thread); + DWORD origin = CLEO_GetIntOpcodeParam(thread); + + bool ok = File::seek(handle, offset, origin); + CLEO_SetThreadCondResult(thread, ok); + return OR_CONTINUE; + } + + //0AD6=1,end_of_file %1d% reached + static OpcodeResult WINAPI opcode_0AD6(CRunningScript* thread) + { + DWORD handle = READ_HANDLE_PARAM(); + + bool end = !File::isOk(handle) || File::isEndOfFile(handle); + CLEO_SetThreadCondResult(thread, end); + return OR_CONTINUE; + } + + //0AD7=3,read_string_from_file %1d% to %2d% size %3d% //IF and SET + static OpcodeResult WINAPI opcode_0AD7(CRunningScript* thread) + { + DWORD handle = READ_HANDLE_PARAM(); + + char* buffer = nullptr; + int bufferSize = 0; + DWORD needsTerminator = TRUE; + CLEO_ReadStringParamWriteBuffer(thread, &buffer, &bufferSize, &needsTerminator); + + int size = CLEO_GetIntOpcodeParam(thread); + if (size == 0) + { + if (bufferSize > 0) buffer[0] = '\0'; + CLEO_SetThreadCondResult(thread, false); + return OR_CONTINUE; + } + if (size < 0) + { + auto info = scriptInfoStr(thread); + SHOW_ERROR("Invalid size argument (%d) in opcode [0AD7] in script %s\nScript suspended.", size, info.c_str()); + return thread->Suspend(); + } + + std::vector tmpBuff; + tmpBuff.resize(size); + auto data = tmpBuff.data(); + + bool ok = File::readString(handle, data, size) != nullptr; + if(!ok) + { + CLEO_SetThreadCondResult(thread, false); + return OR_CONTINUE; + } + + // copy into result param + int len = strlen(data); + int resultSize = min(len, bufferSize - (int)needsTerminator); + + memcpy(buffer, data, resultSize); + if(resultSize < bufferSize) buffer[resultSize] = '\0'; // terminate string whenever possible + + CLEO_SetThreadCondResult(thread, true); + return OR_CONTINUE; + } + + //0AD8=2,write_string_to_file %1d% from %2d% //IF and SET + static OpcodeResult WINAPI opcode_0AD8(CRunningScript* thread) + { + DWORD handle = READ_HANDLE_PARAM(); + auto text = CLEO_ReadStringOpcodeParam(thread); + + auto ok = File::writeString(handle, text); + if (!ok) + { + CLEO_SetThreadCondResult(thread, false); + return OR_CONTINUE; + } + + File::flush(handle); + CLEO_SetThreadCondResult(thread, true); + return OR_CONTINUE; + } + + //0AD9=-1,write_formated_text %2d% to_file %1d% + static OpcodeResult WINAPI opcode_0AD9(CRunningScript* thread) + { + DWORD handle = READ_HANDLE_PARAM(); + auto format = CLEO_ReadStringOpcodeParam(thread); + static char text[4 * MAX_STR_LEN]; CLEO_ReadParamsFormatted(thread, format, text, MAX_STR_LEN); + + auto ok = File::writeString(handle, text); + if (!ok) + { + return OR_CONTINUE; + } + + File::flush(handle); + return OR_CONTINUE; + } + + //0ADA=-1,%3d% = scan_file %1d% format %2d% //IF and SET + static OpcodeResult WINAPI opcode_0ADA(CRunningScript* thread) + { + DWORD handle = READ_HANDLE_PARAM(); + auto format = CLEO_ReadStringOpcodeParam(thread); + auto result = (DWORD*)CLEO_GetPointerToScriptVariable(thread); + + size_t paramCount = 0; + SCRIPT_VAR* outputParams[35]; + while (CLEO_GetOperandType(thread) != eDataType::DT_END) + { + // TODO: if target param is string variable it should be handled correctly + outputParams[paramCount++] = CLEO_GetPointerToScriptVariable(thread); + } + CLEO_SkipUnusedVarArgs(thread); // var arg terminator + + *result = File::scan(handle, format, (void**)&outputParams); + + //CLEO_SetThreadCondResult(thread, paramCount == *result); + CLEO_SetThreadCondResult(thread, true); + return OR_CONTINUE; + } + + //2300=2,get_file_position %1d% store_to %2d% + static OpcodeResult WINAPI opcode_2300(CRunningScript* thread) + { + DWORD handle = READ_HANDLE_PARAM(); + + auto pos = File::getPos(handle); + CLEO_SetIntOpcodeParam(thread, pos); + return OR_CONTINUE; + } + + //2301=3,read_block_from_file %1d% size %2d% buffer %3d% // IF and SET + static OpcodeResult WINAPI opcode_2301(CRunningScript* thread) + { + DWORD handle = READ_HANDLE_PARAM(); + DWORD size = CLEO_GetIntOpcodeParam(thread); + + auto paramType = CLEO_GetOperandType(thread); + if(!IsImmInteger(paramType) && !IsVariable(paramType)) + { + auto info = scriptInfoStr(thread); + SHOW_ERROR("Invalid type (0x%02X) of 'address' argument in opcode [2301] in script %s\nScript suspended.", paramType, info.c_str()); + return thread->Suspend(); + } + DWORD target = CLEO_GetIntOpcodeParam(thread); OPCODE_VALIDATE_POINTER(target) + + if(size < 0) + { + auto info = scriptInfoStr(thread); + SHOW_ERROR("Invalid size argument (%d) in opcode [2301] in script %s\nScript suspended.", size, info.c_str()); + return thread->Suspend(); + } + + if (size == 0) + { + CLEO_SetThreadCondResult(thread, true); // done + return OR_CONTINUE; + } + + auto readCount = File::read(handle, (void*)target, size); + if (readCount != size) + { + CLEO_SetThreadCondResult(thread, false); + return OR_CONTINUE; + } + + CLEO_SetThreadCondResult(thread, true); + return OR_CONTINUE; + } + // 0AE4=1, directory_exist %1s% static OpcodeResult WINAPI Script_FS_DirectoryExists(CRunningScript* thread) { @@ -122,6 +413,15 @@ class FileSystemOperations { auto handle = (HANDLE)CLEO_GetIntOpcodeParam(thread); + if (m_hFileSearches.find(handle) == m_hFileSearches.end()) + { + auto info = scriptInfoStr(thread); + LOG_WARNING(thread, "[0AE7] used with handle (0x%X) to unknown or already closed file search in script %s", handle, info.c_str()); + CLEO_SkipOpcodeParams(thread, 1); + CLEO_SetThreadCondResult(thread, false); + return OR_CONTINUE; + } + WIN32_FIND_DATA ffd = { 0 }; if (FindNextFile(handle, &ffd)) { @@ -140,6 +440,14 @@ class FileSystemOperations static OpcodeResult WINAPI Script_FS_FindClose(CRunningScript* thread) { auto handle = (HANDLE)CLEO_GetIntOpcodeParam(thread); + + if (m_hFileSearches.find(handle) == m_hFileSearches.end()) + { + auto info = scriptInfoStr(thread); + LOG_WARNING(thread, "[0AE8] used with handle (0x%X) to unknown or already closed file search in script %s", handle, info.c_str()); + return OR_CONTINUE; + } + FindClose(handle); m_hFileSearches.erase(handle); return OR_CONTINUE; @@ -150,8 +458,8 @@ class FileSystemOperations { auto filename = ReadPathParam(thread); - CLEO_SetThreadCondResult(thread, DeleteFile(filename.c_str())); - + auto success = DeleteFile(filename.c_str()); + CLEO_SetThreadCondResult(thread, success); return OR_CONTINUE; } @@ -209,12 +517,12 @@ class FileSystemOperations BOOL result; if (DeleteAllInsideFlag) { - //remove directory with all files and subdirectories + // remove directory with all files and subdirectories result = DeleteDir(dirpath.c_str()); } else { - //try to remove as empty directory + // try to remove as empty directory result = RemoveDirectory(dirpath.c_str()); } @@ -333,4 +641,5 @@ class FileSystemOperations } } fileSystemOperations; +std::set FileSystemOperations::m_hFiles; std::set FileSystemOperations::m_hFileSearches; diff --git a/cleo_plugins/FileSystemOperations/FileSystemOperations.vcxproj b/cleo_plugins/FileSystemOperations/FileSystemOperations.vcxproj index a309c585..a338af16 100644 --- a/cleo_plugins/FileSystemOperations/FileSystemOperations.vcxproj +++ b/cleo_plugins/FileSystemOperations/FileSystemOperations.vcxproj @@ -111,6 +111,11 @@ xcopy /Y "$(OutDir)$(TargetName).*" "$(GTA_SA_DIR)\cleo\cleo_plugins\" + + + + + diff --git a/cleo_plugins/FileSystemOperations/FileSystemOperations.vcxproj.filters b/cleo_plugins/FileSystemOperations/FileSystemOperations.vcxproj.filters index 5ba189d8..6e505e7d 100644 --- a/cleo_plugins/FileSystemOperations/FileSystemOperations.vcxproj.filters +++ b/cleo_plugins/FileSystemOperations/FileSystemOperations.vcxproj.filters @@ -2,5 +2,10 @@ + + + + + \ No newline at end of file diff --git a/cleo_plugins/FileSystemOperations/FileUtils.cpp b/cleo_plugins/FileSystemOperations/FileUtils.cpp new file mode 100644 index 00000000..bb698681 --- /dev/null +++ b/cleo_plugins/FileSystemOperations/FileUtils.cpp @@ -0,0 +1,425 @@ +#include "FileUtils.h" +#include + +DWORD File::FUNC_fopen = 0; +DWORD File::FUNC_fclose = 0; +DWORD File::FUNC_fread = 0; +DWORD File::FUNC_fwrite = 0; +DWORD File::FUNC_fgetc = 0; +DWORD File::FUNC_fgets = 0; +DWORD File::FUNC_fputs = 0; +DWORD File::FUNC_fseek = 0; +DWORD File::FUNC_fprintf = 0; +DWORD File::FUNC_ftell = 0; +DWORD File::FUNC_fflush = 0; +DWORD File::FUNC_feof = 0; +DWORD File::FUNC_ferror = 0; + +void File::initialize(CLEO::eGameVersion version) +{ + // GV_US10, GV_US11, GV_EU10, GV_EU11, GV_STEAM + const DWORD MA_FOPEN_FUNCTION[] = { 0x008232D8, 0, 0x00823318, 0x00824098, 0x0085C75E }; + const DWORD MA_FCLOSE_FUNCTION[] = { 0x0082318B, 0, 0x008231CB, 0x00823F4B, 0x0085C396 }; + const DWORD MA_FGETC_FUNCTION[] = { 0x008231DC, 0, 0x0082321C, 0x00823F9C, 0x0085C680 }; + const DWORD MA_FGETS_FUNCTION[] = { 0x00823798, 0, 0x008237D8, 0x00824558, 0x0085D00C }; + const DWORD MA_FPUTS_FUNCTION[] = { 0x008262B8, 0, 0x008262F8, 0x00826BA8, 0x008621F1 }; + const DWORD MA_FREAD_FUNCTION[] = { 0x00823521, 0, 0x00823561, 0x008242E1, 0x0085CD04 }; + const DWORD MA_FWRITE_FUNCTION[] = { 0x00823674, 0, 0x008236B4, 0x00824434, 0x0085CE7E }; + const DWORD MA_FSEEK_FUNCTION[] = { 0x0082374F, 0, 0x0082378F, 0x0082450F, 0x0085CF87 }; + const DWORD MA_FPRINTF_FUNCTION[] = { 0x00823A30, 0, 0x00823A70, 0x008247F0, 0x0085D464 }; + const DWORD MA_FTELL_FUNCTION[] = { 0x00826261, 0, 0x008262A1, 0x00826B51, 0x00862183 }; + const DWORD MA_FFLUSH_FUNCTION[] = { 0x00823E86, 0, 0x00823EC6, 0x00824C46, 0x0085DDDD }; + const DWORD MA_FEOF_FUNCTION[] = { 0x008262A2, 0, 0x008262E2, 0x00826B92, 0x0085D193 }; + const DWORD MA_FERROR_FUNCTION[] = { 0x008262AD, 0, 0x008262ED, 0x00826B9D, 0x0085D1C2 }; + + FUNC_fopen = MA_FOPEN_FUNCTION[version]; + FUNC_fclose = MA_FCLOSE_FUNCTION[version]; + FUNC_fread = MA_FREAD_FUNCTION[version]; + FUNC_fwrite = MA_FWRITE_FUNCTION[version]; + FUNC_fgetc = MA_FGETC_FUNCTION[version]; + FUNC_fgets = MA_FGETS_FUNCTION[version]; + FUNC_fputs = MA_FPUTS_FUNCTION[version]; + FUNC_fseek = MA_FSEEK_FUNCTION[version]; + FUNC_fprintf = MA_FPRINTF_FUNCTION[version]; + FUNC_ftell = MA_FTELL_FUNCTION[version]; + FUNC_fflush = MA_FFLUSH_FUNCTION[version]; + FUNC_feof = MA_FEOF_FUNCTION[version]; + FUNC_ferror = MA_FERROR_FUNCTION[version];; +} + +bool File::isLegacy(DWORD handle) { return (handle & 0x1) == 0; } + +FILE* File::handleToFile(DWORD handle) { return (FILE*)(handle & ~0x1); } + +DWORD File::fileToHandle(FILE* file, bool legacy) +{ + if (file == nullptr) return 0; + + auto handle = (DWORD)file; + if (!legacy) handle |= 0x1; + return handle; +} + +bool File::flush(DWORD handle) +{ + FILE* file = handleToFile(handle); + if (file == nullptr) return false; + + int result = 0; + if (isLegacy(handle)) + { + _asm + { + push file + call FUNC_fflush + add esp, 0x4 + mov result, eax + } + } + else + result = fflush(file); + + return result == 0; +} + +DWORD File::open(const char* filename, const char* mode, bool legacy) +{ + FILE* file = nullptr; + if (legacy) + { + _asm + { + push mode + push filename + call FUNC_fopen + add esp, 8 + mov file, eax + } + } + else + file = fopen(filename, mode); + + return fileToHandle(file, legacy); +} + +void File::close(DWORD handle) +{ + FILE* file = handleToFile(handle); + if (file == nullptr) return; + + if (isLegacy(handle)) + { + _asm + { + push file + call FUNC_fclose + add esp, 4 + } + } + else + fclose(file); +} + +bool File::isOk(DWORD handle) +{ + FILE* file = handleToFile(handle); + if (file == nullptr) return false; + + int result = 0; + if (isLegacy(handle)) + { + _asm + { + push file + call FUNC_ferror + add esp, 0x4 + } + } + else + result = ferror(file); + + return result == 0; +} + +DWORD File::getSize(DWORD handle) +{ + auto pos = getPos(handle); + seek(handle, 0, SEEK_END); + DWORD size = getPos(handle); + seek(handle, pos, SEEK_SET); + return size; +} + +bool File::seek(DWORD handle, int offset, DWORD orign) +{ + FILE* file = handleToFile(handle); + if (file == nullptr) return false; + + int result = 0; + if (isLegacy(handle)) + { + auto off = offset; // 'offset' is keyword in asm + _asm + { + push orign + push off + push file + call FUNC_fseek + add esp, 0xC + mov result, eax + } + } + else + result = fseek(file, offset, orign); + + return result == 0; +} + +DWORD File::getPos(DWORD handle) +{ + FILE* file = handleToFile(handle); + if (file == nullptr) return 0; + + DWORD pos = 0; + if (isLegacy(handle)) + { + _asm + { + push file + call FUNC_ftell + add esp, 0x4 + mov pos, eax + } + } + else + pos = (DWORD)ftell(file); + + return pos; +} + +bool File::isEndOfFile(DWORD handle) +{ + FILE* file = handleToFile(handle); + if (file == nullptr) return true; + + int result = 0; + if (isLegacy(handle)) + { + _asm + { + push file + call FUNC_feof + add esp, 0x4 + mov result, eax + } + } + else + result = feof(file); + + return result != 0; +} + +DWORD File::read(DWORD handle, void* buffer, DWORD size) +{ + FILE* file = handleToFile(handle); + if (file == nullptr) return 0; + + DWORD read = 0; + if (isLegacy(handle)) + { + auto siz = size; // 'size' is keyword in asm + _asm + { + push file + push siz + push 1 + push buffer + call FUNC_fread + add esp, 0x10 + mov read, eax + } + } + else + read = fread(buffer, 1, size, file); + + seek(handle, 0, SEEK_CUR); // required for RW streams (https://en.wikibooks.org/wiki/C_Programming/stdio.h/fopen) + + return read; +} + +char File::readChar(DWORD handle) +{ + FILE* file = handleToFile(handle); + if (file == nullptr) return 0; + + char result = '_'; + if (isLegacy(handle)) + { + _asm + { + push file + call FUNC_fgetc + add esp, 0x4 + mov result, al + } + } + else + result = fgetc(file); + + seek(handle, 0, SEEK_CUR); // required for RW streams (https://en.wikibooks.org/wiki/C_Programming/stdio.h/fopen) + + return result; +} + +char* File::readString(DWORD handle, char* buffer, DWORD bufferSize) +{ + FILE* file = handleToFile(handle); + if (file == nullptr) return nullptr; + + char* result = nullptr; + if (isLegacy(handle)) + { + _asm + { + push file + push bufferSize + push buffer + call FUNC_fgets + add esp, 0xC + mov result, eax + } + } + else + result = fgets(buffer, bufferSize, file); + + seek(handle, 0, SEEK_CUR); // required for RW streams (https://en.wikibooks.org/wiki/C_Programming/stdio.h/fopen) + + return result; +} + +DWORD File::write(DWORD handle, const void* buffer, DWORD size) +{ + FILE* file = handleToFile(handle); + if (file == nullptr) return 0; + + DWORD writen = 0; + if (isLegacy(handle)) + { + auto siz = size; // 'size' is keyword in asm + _asm + { + push file + push siz + push 1 + push buffer + call FUNC_fwrite + add esp, 0x10 + mov writen, eax + } + } + else + writen = (DWORD)fwrite(buffer, 1, size, file); + + seek(handle, 0, SEEK_CUR); // required for RW streams (https://en.wikibooks.org/wiki/C_Programming/stdio.h/fopen) + + return writen; +} + +bool File::writeString(DWORD handle, const char* text) +{ + FILE* file = handleToFile(handle); + if (file == nullptr) return 0; + + int result = 0; + if (isLegacy(handle)) + { + _asm + { + push file + push text + call FUNC_fputs + add esp, 0x8 + mov result, eax + } + } + else + result = (DWORD)fputs(text, file); + + seek(handle, 0, SEEK_CUR); // required for RW streams (https://en.wikibooks.org/wiki/C_Programming/stdio.h/fopen) + + return result >= 0; +} + +DWORD File::scan(DWORD handle, const char* format, void** outputParams) +{ + FILE* file = handleToFile(handle); + if (file == nullptr) return 0; + + int read = 0; + if (isLegacy(handle)) + { + // fscanf not existent in game's code. Emulate it + + size_t paramCount = 0; + const char* f = format; + while(*f != '\0') + { + if (*f == '%') + { + if (*(f + 1) != '%') // not escaped % + paramCount++; + else + f++; // skip escaped + } + f++; + } + + auto newFormat = std::string(format) + "%n"; // %n returns characters processed by + DWORD charRead = 0; + outputParams[paramCount] = &charRead; + + DWORD prevCharRead = 0; + std::string readText; + while(true) + { + readText += readChar(handle); + + prevCharRead = charRead; + auto p = outputParams; + read = sscanf(readText.c_str(), newFormat.c_str(), + p[0], p[1], p[2], p[3], p[4], p[5], p[6], p[7], p[8], p[9], // 10 + p[10], p[11], p[12], p[13], p[14], p[15], p[16], p[17], p[18], p[19], // 20 + p[20], p[21], p[22], p[23], p[24], p[25], p[26], p[27], p[28], p[29], // 30 + p[30], p[31], p[32], p[33], p[34]); // 35 + + if (!isOk(handle)) break; + + if (read == paramCount) + { + if (charRead == prevCharRead) // all params collected and scan doesn't consume input text anymore + { + seek(handle, -1, SEEK_CUR); // return the character not used by scan + break; + } + + if (isEndOfFile(handle)) + { + break; + } + } + } + } + else + { + auto p = outputParams; + read = fscanf(file, format, + p[0], p[1], p[2], p[3], p[4], p[5], p[6], p[7], p[8], p[9], // 10 + p[10], p[11], p[12], p[13], p[14], p[15], p[16], p[17], p[18], p[19], // 20 + p[20], p[21], p[22], p[23], p[24], p[25], p[26], p[27], p[28], p[29], // 30 + p[30], p[31], p[32], p[33], p[34]); // 35 + } + + seek(handle, 0, SEEK_CUR); // required for RW streams (https://en.wikibooks.org/wiki/C_Programming/stdio.h/fopen) + + return read; +} diff --git a/cleo_plugins/FileSystemOperations/FileUtils.h b/cleo_plugins/FileSystemOperations/FileUtils.h new file mode 100644 index 00000000..31eb52d7 --- /dev/null +++ b/cleo_plugins/FileSystemOperations/FileUtils.h @@ -0,0 +1,49 @@ +#pragma once +#include "CLEO.h" +#include +#include + +class File +{ +public: + static void initialize(CLEO::eGameVersion version); + + static DWORD open(const char* filename, const char* mode, bool legacy); + static void close(DWORD handle); + + static bool isOk(DWORD handle); + + static DWORD getSize(DWORD handle); + static bool seek(DWORD handle, int offset, DWORD orign); + static DWORD getPos(DWORD handle); + static bool isEndOfFile(DWORD handle); + + static DWORD read(DWORD handle, void* buffer, DWORD size); + static char readChar(DWORD handle); + static char* readString(DWORD handle, char* buffer, DWORD bufferSize); + + static DWORD write(DWORD handle, const void* buffer, DWORD size); + static bool writeString(DWORD handle, const char* text); + static DWORD scan(DWORD handle, const char* format, void** outputParams); + static bool flush(DWORD handle); + + static bool isLegacy(DWORD handle); // Legacy modes for CLEO 3 + static FILE* handleToFile(DWORD handle); + +private: + static DWORD FUNC_fopen; + static DWORD FUNC_fclose; + static DWORD FUNC_fread; + static DWORD FUNC_fwrite; + static DWORD FUNC_fgetc; + static DWORD FUNC_fgets; + static DWORD FUNC_fputs; + static DWORD FUNC_fseek; + static DWORD FUNC_fprintf; + static DWORD FUNC_ftell; + static DWORD FUNC_fflush; + static DWORD FUNC_feof; + static DWORD FUNC_ferror; + + static DWORD fileToHandle(FILE* file, bool legacy); +}; diff --git a/cleo_plugins/FileSystemOperations/Utils.h b/cleo_plugins/FileSystemOperations/Utils.h new file mode 100644 index 00000000..25b6d76d --- /dev/null +++ b/cleo_plugins/FileSystemOperations/Utils.h @@ -0,0 +1,90 @@ +#pragma once +#include "CLEO.h" +#include +#include + +std::string stringPrintf(const char* format, ...) +{ + va_list args; + + va_start(args, format); + auto len = std::vsnprintf(nullptr, 0, format, args) + 1; + va_end(args); + + std::string result(len, '\0'); + + va_start(args, format); + std::vsnprintf(result.data(), result.length(), format, args); + va_end(args); + + return result; +} + +std::string scriptInfoStr(CLEO::CRunningScript* thread) +{ + std::string info(1024, '\0'); + CLEO_GetScriptInfoStr(thread, true, info.data(), info.length()); + return std::move(info); +} + +const char* TraceVArg(CLEO::eLogLevel level, const char* format, va_list args) +{ + static char szBuf[1024]; + vsprintf(szBuf, format, args); // put params into format + CLEO_Log(level, szBuf); + return szBuf; +} + +void Trace(CLEO::eLogLevel level, const char* format, ...) +{ + va_list args; + va_start(args, format); + TraceVArg(level, format, args); + va_end(args); +} + +void Trace(const CLEO::CRunningScript* thread, CLEO::eLogLevel level, const char* format, ...) +{ + if (CLEO_GetScriptVersion(thread) < CLEO::eCLEO_Version::CLEO_VER_5) + { + return; // do not log this in older versions + } + + va_list args; + va_start(args, format); + TraceVArg(level, format, args); + va_end(args); +} + +void ShowError(const char* format, ...) +{ + va_list args; + va_start(args, format); + auto msg = TraceVArg(CLEO::eLogLevel::Error, format, args); + va_end(args); + + QUERY_USER_NOTIFICATION_STATE pquns; + SHQueryUserNotificationState(&pquns); + bool fullscreen = (pquns == QUNS_BUSY) || (pquns == QUNS_RUNNING_D3D_FULL_SCREEN) || (pquns == QUNS_PRESENTATION_MODE); + + if (fullscreen) + { + PostMessage(NULL, WM_SYSCOMMAND, SC_MINIMIZE, 0); + ShowWindow(NULL, SW_MINIMIZE); + } + + MessageBox(NULL, msg, "CLEO error", MB_SYSTEMMODAL | MB_TOPMOST | MB_ICONERROR | MB_OK); + + if (fullscreen) + { + PostMessage(NULL, WM_SYSCOMMAND, SC_RESTORE, 0); + ShowWindow(NULL, SW_RESTORE); + } +} + +#define TRACE(format,...) {Trace(CLEO::eLogLevel::Default, format, __VA_ARGS__);} +#define LOG_WARNING(script, format, ...) {Trace(script, CLEO::eLogLevel::Error, format, __VA_ARGS__);} +#define SHOW_ERROR(a,...) {ShowError(a, __VA_ARGS__);} + +static const size_t MinValidAddress = 0x10000; // used for validation of pointers received from scripts. First 64kb are for sure reserved by Windows. +#define OPCODE_VALIDATE_POINTER(x) if((size_t)x <= MinValidAddress) { auto info = scriptInfoStr(thread); SHOW_ERROR("Invalid '0x%X' pointer param in script %s \nScript suspended.", x, info.c_str()); return thread->Suspend(); } diff --git a/cleo_sdk/CLEO.h b/cleo_sdk/CLEO.h index 653c2823..7344f768 100644 --- a/cleo_sdk/CLEO.h +++ b/cleo_sdk/CLEO.h @@ -269,6 +269,14 @@ enum class eLogLevel : DWORD Default // all log messages }; +enum OpcodeResult : char +{ + OR_NONE = -2, + OR_ERROR = -1, + OR_CONTINUE = 0, + OR_INTERRUPT = 1, +}; + typedef int SCRIPT_HANDLE; typedef SCRIPT_HANDLE HANDLE_ACTOR, ACTOR, HACTOR, PED, HPED, HANDLE_PED; typedef SCRIPT_HANDLE HANDLE_CAR, CAR, HCAR, VEHICLE, HVEHICLE, HANDLE_VEHICLE; @@ -354,6 +362,7 @@ struct CRunningScript CRunningScript* GetPrev() const { return Previous; } void SetIsExternal(bool b) { bIsExternal = b; } void SetActive(bool b) { bIsActive = b; } + OpcodeResult Suspend() { WakeTime = 0xFFFFFFFF; return OpcodeResult::OR_INTERRUPT; } // suspend script execution forever void SetNext(CRunningScript* v) { Next = v; } void SetPrev(CRunningScript* v) { Previous = v; } SCRIPT_VAR* GetVarPtr() { return LocalVar; } @@ -398,14 +407,6 @@ static_assert(sizeof(CRunningScript) == 0xE0, "Invalid size of CRunningScript!") typedef struct CRunningScript CScriptThread; #endif -enum OpcodeResult : char -{ - OR_NONE = -2, - OR_ERROR = -1, - OR_CONTINUE = 0, - OR_INTERRUPT = 1, -}; - typedef OpcodeResult (CALLBACK* _pOpcodeHandler)(CRunningScript*); typedef void(*FuncScriptDeleteDelegateT) (CRunningScript*); @@ -440,6 +441,7 @@ DWORD WINAPI CLEO_GetIntOpcodeParam(CRunningScript* thread); float WINAPI CLEO_GetFloatOpcodeParam(CRunningScript* thread); LPSTR WINAPI CLEO_ReadStringOpcodeParam(CRunningScript* thread, char* buf = nullptr, int bufSize = 0); LPSTR WINAPI CLEO_ReadStringPointerOpcodeParam(CRunningScript* thread, char* buf = nullptr, int bufSize = 0); // exactly same as CLEO_ReadStringOpcodeParam +void WINAPI CLEO_ReadStringParamWriteBuffer(CRunningScript* thread, char** outBuf, int* outBufSize, DWORD* outNeedsTerminator); // get info about the string opcode param, so it can be written latter. If outNeedsTerminator is not 0 then whole bufSize can be used as text characters. Advances script to next param char* WINAPI CLEO_ReadParamsFormatted(CRunningScript* thread, const char* format, char* buf = nullptr, int bufSize = 0); // consumes all var-arg params and terminator // param skip without reading diff --git a/source/CCustomOpcodeSystem.cpp b/source/CCustomOpcodeSystem.cpp index 33c22058..2f4af817 100644 --- a/source/CCustomOpcodeSystem.cpp +++ b/source/CCustomOpcodeSystem.cpp @@ -11,9 +11,9 @@ #include #include -#define OPCODE_VALIDATE_STR_ARG_READ(x) if((void*)x == nullptr) { SHOW_ERROR("%s in script %s \nScript suspended.", CCustomOpcodeSystem::lastErrorMsg.c_str(), ((CCustomScript*)thread)->GetInfoStr().c_str()); return CCustomOpcodeSystem::ErrorSuspendScript(thread); } -#define OPCODE_VALIDATE_STR_ARG_WRITE(x) if((void*)x == nullptr) { SHOW_ERROR("%s in script %s \nScript suspended.", CCustomOpcodeSystem::lastErrorMsg.c_str(), ((CCustomScript*)thread)->GetInfoStr().c_str()); return CCustomOpcodeSystem::ErrorSuspendScript(thread); } -#define OPCODE_READ_FORMATTED_STRING(thread, buf, bufSize, format) if(ReadFormattedString(thread, buf, bufSize, format) == -1) { SHOW_ERROR("%s in script %s \nScript suspended.", CCustomOpcodeSystem::lastErrorMsg.c_str(), ((CCustomScript*)thread)->GetInfoStr().c_str()); return CCustomOpcodeSystem::ErrorSuspendScript(thread); } +#define OPCODE_VALIDATE_STR_ARG_READ(x) if((void*)x == nullptr) { SHOW_ERROR("%s in script %s \nScript suspended.", CCustomOpcodeSystem::lastErrorMsg.c_str(), ((CCustomScript*)thread)->GetInfoStr().c_str()); return thread->Suspend(); } +#define OPCODE_VALIDATE_STR_ARG_WRITE(x) if((void*)x == nullptr) { SHOW_ERROR("%s in script %s \nScript suspended.", CCustomOpcodeSystem::lastErrorMsg.c_str(), ((CCustomScript*)thread)->GetInfoStr().c_str()); return thread->Suspend(); } +#define OPCODE_READ_FORMATTED_STRING(thread, buf, bufSize, format) if(ReadFormattedString(thread, buf, bufSize, format) == -1) { SHOW_ERROR("%s in script %s \nScript suspended.", CCustomOpcodeSystem::lastErrorMsg.c_str(), ((CCustomScript*)thread)->GetInfoStr().c_str()); return thread->Suspend(); } namespace CLEO { @@ -22,20 +22,6 @@ namespace CLEO template inline CRunningScript& operator<<(CRunningScript& thread, memory_pointer pval); template inline CRunningScript& operator>>(CRunningScript& thread, memory_pointer& pval); - DWORD FUNC_fopen; - DWORD FUNC_fclose; - DWORD FUNC_fwrite; - DWORD FUNC_fread; - DWORD FUNC_fgetc; - DWORD FUNC_fgets; - DWORD FUNC_fputs; - DWORD FUNC_fseek; - DWORD FUNC_fprintf; - DWORD FUNC_ftell; - DWORD FUNC_fflush; - DWORD FUNC_feof; - DWORD FUNC_ferror; - OpcodeResult __stdcall opcode_0A8C(CRunningScript *thread); OpcodeResult __stdcall opcode_0A8D(CRunningScript *thread); OpcodeResult __stdcall opcode_0A8E(CRunningScript *thread); @@ -50,11 +36,6 @@ namespace CLEO OpcodeResult __stdcall opcode_0A97(CRunningScript *thread); OpcodeResult __stdcall opcode_0A98(CRunningScript *thread); OpcodeResult __stdcall opcode_0A99(CRunningScript *thread); - OpcodeResult __stdcall opcode_0A9A(CRunningScript *thread); - OpcodeResult __stdcall opcode_0A9B(CRunningScript *thread); - OpcodeResult __stdcall opcode_0A9C(CRunningScript *thread); - OpcodeResult __stdcall opcode_0A9D(CRunningScript *thread); - OpcodeResult __stdcall opcode_0A9E(CRunningScript *thread); OpcodeResult __stdcall opcode_0A9F(CRunningScript *thread); OpcodeResult __stdcall opcode_0AA0(CRunningScript *thread); OpcodeResult __stdcall opcode_0AA1(CRunningScript *thread); @@ -108,12 +89,6 @@ namespace CLEO OpcodeResult __stdcall opcode_0AD2(CRunningScript *thread); OpcodeResult __stdcall opcode_0AD3(CRunningScript *thread); OpcodeResult __stdcall opcode_0AD4(CRunningScript *thread); - OpcodeResult __stdcall opcode_0AD5(CRunningScript *thread); - OpcodeResult __stdcall opcode_0AD6(CRunningScript *thread); - OpcodeResult __stdcall opcode_0AD7(CRunningScript *thread); - OpcodeResult __stdcall opcode_0AD8(CRunningScript *thread); - OpcodeResult __stdcall opcode_0AD9(CRunningScript *thread); - OpcodeResult __stdcall opcode_0ADA(CRunningScript *thread); OpcodeResult __stdcall opcode_0ADB(CRunningScript *thread); OpcodeResult __stdcall opcode_0ADC(CRunningScript *thread); OpcodeResult __stdcall opcode_0ADD(CRunningScript *thread); @@ -229,7 +204,7 @@ namespace CLEO if(opcode > LastCustomOpcode) { SHOW_ERROR("Opcode [%04X] out of supported range! \nCalled in script %s\nScript suspended.", opcode, ((CCustomScript*)thread)->GetInfoStr().c_str()); - return ErrorSuspendScript(thread); + return thread->Suspend(); } CustomOpcodeHandler handler = customOpcodeProc[opcode]; @@ -244,7 +219,7 @@ namespace CLEO if (opcode > LastOriginalOpcode) { SHOW_ERROR("Opcode [%04X] not registered! \nCalled in script %s\nPreviously called opcode: [%04X]\nScript suspended.", opcode, ((CCustomScript*)thread)->GetInfoStr().c_str(), prevOpcode); - return ErrorSuspendScript(thread); + return thread->Suspend(); } size_t tableIdx = opcode / 100; // 100 opcodes peer handler table @@ -253,7 +228,7 @@ namespace CLEO if(result == OR_ERROR) { SHOW_ERROR("Opcode [%04X] not found! \nCalled in script %s\nScript suspended.", opcode, ((CCustomScript*)thread)->GetInfoStr().c_str()); - return ErrorSuspendScript(thread); + return thread->Suspend(); } } @@ -280,27 +255,27 @@ namespace CLEO if ((size_t)func <= CCustomOpcodeSystem::MinValidAddress) { SHOW_ERROR("Invalid '0x%X' function pointer param of opcode [%04X] in script %s\nScript suspended.", func, opcode, ((CCustomScript*)thread)->GetInfoStr().c_str()); - return CCustomOpcodeSystem::ErrorSuspendScript(thread); + return thread->Suspend(); } if (thisCall && (size_t)struc <= CCustomOpcodeSystem::MinValidAddress) { SHOW_ERROR("Invalid '0x%X' struct pointer param of opcode [%04X] in script %s\nScript suspended.", struc, opcode, ((CCustomScript*)thread)->GetInfoStr().c_str()); - return CCustomOpcodeSystem::ErrorSuspendScript(thread); + return thread->Suspend(); } int nVarArg = GetVarArgCount(thread); if (numParams + returnArg != nVarArg) // and return argument { SHOW_ERROR("Opcode [%04X] declared %d input args, but provided %d in script %s\nScript suspended.", opcode, numParams, (int)nVarArg - returnArg, ((CCustomScript*)thread)->GetInfoStr().c_str()); - return CCustomOpcodeSystem::ErrorSuspendScript(thread); + return thread->Suspend(); } constexpr size_t Max_Args = 32; if (numParams > Max_Args) { SHOW_ERROR("Opcode [%04X] used with more than supported arguments in script %s\nScript suspended.", opcode, ((CCustomScript*)thread)->GetInfoStr().c_str()); - return CCustomOpcodeSystem::ErrorSuspendScript(thread); + return thread->Suspend(); } static SCRIPT_VAR arguments[Max_Args] = { 0 }; @@ -329,7 +304,7 @@ namespace CLEO if (currTextParam >= Max_Text_Params) { SHOW_ERROR("Opcode [%04X] used with more than supported string arguments in script %s\nScript suspended.", opcode, ((CCustomScript*)thread)->GetInfoStr().c_str()); - return CCustomOpcodeSystem::ErrorSuspendScript(thread); + return thread->Suspend(); } param.pcParam = ReadStringParam(thread, textParams[currTextParam], MAX_STR_LEN); OPCODE_VALIDATE_STR_ARG_READ(param.pcParam) @@ -338,7 +313,7 @@ namespace CLEO else { SHOW_ERROR("Invalid param type (%s) in opcode [%04X] in script %s \nScript suspended.", opcode, ToKindStr(paramType), ((CCustomScript*)thread)->GetInfoStr().c_str()); - return CCustomOpcodeSystem::ErrorSuspendScript(thread); + return thread->Suspend(); } } @@ -350,7 +325,7 @@ namespace CLEO if (!IsVariable(paramType) && !IsVarString(paramType)) { SHOW_ERROR("Invalid return param type (%s) in opcode [%04X] in script %s \nScript suspended.", opcode, ToKindStr(paramType), ((CCustomScript*)thread)->GetInfoStr().c_str()); - return CCustomOpcodeSystem::ErrorSuspendScript(thread); + return thread->Suspend(); } } @@ -381,17 +356,10 @@ namespace CLEO return OR_CONTINUE; } - OpcodeResult CCustomOpcodeSystem::ErrorSuspendScript(CRunningScript* thread) - { - //thread->SetActive(false): // will crash game if no active script left - ((CCustomScript*)thread)->WakeTime = 0xFFFFFFFF; - return OpcodeResult::OR_INTERRUPT; - } - void CCustomOpcodeSystem::FinalizeScriptObjects() { - TRACE("Cleaning up script data... %u files, %u libs, %u allocations...", - m_hFiles.size(), m_hNativeLibs.size(), m_pAllocations.size() + TRACE("Cleaning up script data... %u libs, %u allocations...", + m_hNativeLibs.size(), m_pAllocations.size() ); for (void* func : GetInstance().GetCallbacks(eCallbackId::ScriptsFinalize)) @@ -400,14 +368,6 @@ namespace CLEO ((callback*)func)(); } - // clean up after opcode_0A9A - for (auto i = m_hFiles.begin(); i != m_hFiles.end(); ++i) - { - if (!is_legacy_handle(*i)) - fclose(convert_handle_to_file(*i)); - } - m_hFiles.clear(); - // clean up after opcode_0AA2 std::for_each(m_hNativeLibs.begin(), m_hNativeLibs.end(), FreeLibrary); m_hNativeLibs.clear(); @@ -437,11 +397,6 @@ namespace CLEO CLEO_RegisterOpcode(0x0A97, opcode_0A97); CLEO_RegisterOpcode(0x0A98, opcode_0A98); CLEO_RegisterOpcode(0x0A99, opcode_0A99); - CLEO_RegisterOpcode(0x0A9A, opcode_0A9A); - CLEO_RegisterOpcode(0x0A9B, opcode_0A9B); - CLEO_RegisterOpcode(0x0A9C, opcode_0A9C); - CLEO_RegisterOpcode(0x0A9D, opcode_0A9D); - CLEO_RegisterOpcode(0x0A9E, opcode_0A9E); CLEO_RegisterOpcode(0x0A9F, opcode_0A9F); CLEO_RegisterOpcode(0x0AA0, opcode_0AA0); CLEO_RegisterOpcode(0x0AA1, opcode_0AA1); @@ -495,12 +450,6 @@ namespace CLEO CLEO_RegisterOpcode(0x0AD2, opcode_0AD2); CLEO_RegisterOpcode(0x0AD3, opcode_0AD3); CLEO_RegisterOpcode(0x0AD4, opcode_0AD4); - CLEO_RegisterOpcode(0x0AD5, opcode_0AD5); - CLEO_RegisterOpcode(0x0AD6, opcode_0AD6); - CLEO_RegisterOpcode(0x0AD7, opcode_0AD7); - CLEO_RegisterOpcode(0x0AD8, opcode_0AD8); - CLEO_RegisterOpcode(0x0AD9, opcode_0AD9); - CLEO_RegisterOpcode(0x0ADA, opcode_0ADA); CLEO_RegisterOpcode(0x0ADB, opcode_0ADB); CLEO_RegisterOpcode(0x0ADC, opcode_0ADC); CLEO_RegisterOpcode(0x0ADD, opcode_0ADD); @@ -547,20 +496,6 @@ namespace CLEO MemWrite(gvm.TranslateMemoryAddress(MA_OPCODE_HANDLER_REF), &customOpcodeHandlers); MemWrite(0x00469EF0, &customOpcodeHandlers); // TODO: game version translation - FUNC_fopen = gvm.TranslateMemoryAddress(MA_FOPEN_FUNCTION); - FUNC_fclose = gvm.TranslateMemoryAddress(MA_FCLOSE_FUNCTION); - FUNC_fread = gvm.TranslateMemoryAddress(MA_FREAD_FUNCTION); - FUNC_fwrite = gvm.TranslateMemoryAddress(MA_FWRITE_FUNCTION); - FUNC_fgetc = gvm.TranslateMemoryAddress(MA_FGETC_FUNCTION); - FUNC_fgets = gvm.TranslateMemoryAddress(MA_FGETS_FUNCTION); - FUNC_fputs = gvm.TranslateMemoryAddress(MA_FPUTS_FUNCTION); - FUNC_fseek = gvm.TranslateMemoryAddress(MA_FSEEK_FUNCTION); - FUNC_fprintf = gvm.TranslateMemoryAddress(MA_FPRINTF_FUNCTION); - FUNC_ftell = gvm.TranslateMemoryAddress(MA_FTELL_FUNCTION); - FUNC_fflush = gvm.TranslateMemoryAddress(MA_FFLUSH_FUNCTION); - FUNC_feof = gvm.TranslateMemoryAddress(MA_FEOF_FUNCTION); - FUNC_ferror = gvm.TranslateMemoryAddress(MA_FERROR_FUNCTION); - pedPool = gvm.TranslateMemoryAddress(MA_PED_POOL); vehiclePool = gvm.TranslateMemoryAddress(MA_VEHICLE_POOL); objectPool = gvm.TranslateMemoryAddress(MA_OBJECT_POOL); @@ -1118,7 +1053,7 @@ namespace CLEO 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); + return thread->Suspend(); } // store return arguments @@ -1130,14 +1065,14 @@ namespace CLEO if (returnArgCount > 32) { SHOW_ERROR("Opcode [%04X] has too many (%d) args in script %s\nScript suspended.", opcode, returnArgCount, cs->GetInfoStr().c_str()); - return CCustomOpcodeSystem::ErrorSuspendScript(thread); + return thread->Suspend(); } auto nVarArg = GetVarArgCount(thread); if (returnArgCount > nVarArg) { SHOW_ERROR("Opcode [%04X] declared %d args, but %d was provided in script %s\nScript suspended.", opcode, returnArgCount, nVarArg, ((CCustomScript*)thread)->GetInfoStr().c_str()); - return CCustomOpcodeSystem::ErrorSuspendScript(thread); + return thread->Suspend(); } for (DWORD i = 0; i < returnArgCount; i++) @@ -1165,7 +1100,7 @@ namespace CLEO else { SHOW_ERROR("Invalid argument type '0x%02X' in opcode [%04X] in script %s\nScript suspended.", paramType, opcode, ((CCustomScript*)thread)->GetInfoStr().c_str()); - return CCustomOpcodeSystem::ErrorSuspendScript(thread); + return thread->Suspend(); } } } @@ -1181,7 +1116,7 @@ namespace CLEO if (returnSlotCount > returnArgCount || (strictArgCount && returnSlotCount < returnArgCount)) { SHOW_ERROR("Opcode [%04X] returned %d params, while function caller expected %d in script %s\nScript suspended.", opcode, returnArgCount, returnSlotCount, cs->GetInfoStr().c_str()); - return CCustomOpcodeSystem::ErrorSuspendScript(cs); + return cs->Suspend(); } else if (returnSlotCount < returnArgCount) { @@ -1210,7 +1145,7 @@ namespace CLEO else { SHOW_ERROR("Invalid output argument type '0x%02X' in opcode [%04X] in script %s\nScript suspended.", paramType, opcode, ((CCustomScript*)thread)->GetInfoStr().c_str()); - return CCustomOpcodeSystem::ErrorSuspendScript(thread); + return thread->Suspend(); } } } @@ -1220,176 +1155,6 @@ namespace CLEO return OR_CONTINUE; } - // Legacy modes for CLEO 3 - FILE* legacy_fopen(const char* szPath, const char* szMode) - { - FILE* hFile; - _asm - { - push szMode - push szPath - call FUNC_fopen - add esp, 8 - mov hFile, eax - } - return hFile; - } - void legacy_fclose(FILE * hFile) - { - _asm - { - push hFile - call FUNC_fclose - add esp, 4 - } - } - size_t legacy_fread(void * buf, size_t len, size_t count, FILE * stream) - { - _asm - { - push stream - push count - push len - push buf - call FUNC_fread - add esp, 0x10 - } - } - size_t legacy_fwrite(const void * buf, size_t len, size_t count, FILE * stream) - { - _asm - { - push stream - push count - push len - push buf - call FUNC_fwrite - add esp, 0x10 - } - } - char legacy_fgetc(FILE * stream) - { - _asm - { - push stream - call FUNC_fgetc - add esp, 0x4 - } - } - char * legacy_fgets(char *pStr, int num, FILE * stream) - { - _asm - { - push stream - push num - push pStr - call FUNC_fgets - add esp, 0xC - } - } - int legacy_fputs(const char *pStr, FILE * stream) - { - _asm - { - push stream - push pStr - call FUNC_fputs - add esp, 0x8 - } - } - int legacy_fseek(FILE * stream, long int offs, int original) - { - _asm - { - push stream - push offs - push original - call FUNC_fseek - add esp, 0xC - } - } - int legacy_ftell(FILE * stream) - { - _asm - { - push stream - call FUNC_ftell - add esp, 0x4 - } - } - int __declspec(naked) fprintf(FILE * stream, const char * format, ...) - { - _asm jmp FUNC_fprintf - } - int legacy_fflush(FILE * stream) - { - _asm - { - push stream - call FUNC_fflush - add esp, 0x4 - } - } - int legacy_feof(FILE * stream) - { - _asm - { - push stream - call FUNC_feof - add esp, 0x4 - } - } - int legacy_ferror(FILE * stream) - { - _asm - { - push stream - call FUNC_ferror - add esp, 0x4 - } - } - - bool is_legacy_handle(DWORD dwHandle) { return (dwHandle & 0x1) == 0; } - FILE * convert_handle_to_file(DWORD dwHandle) { return dwHandle ? reinterpret_cast(is_legacy_handle(dwHandle) ? dwHandle : dwHandle & ~(0x1)) : nullptr; } - - inline DWORD open_file(const char * szPath, const char * szMode, bool bLegacy) - { - FILE * hFile = bLegacy ? legacy_fopen(szPath, szMode) : fopen(szPath, szMode); - if (hFile) return bLegacy ? (DWORD)hFile : (DWORD)hFile | 0x1; - return NULL; - } - inline void close_file(DWORD dwHandle) - { - if (is_legacy_handle(dwHandle)) legacy_fclose(convert_handle_to_file(dwHandle)); - else fclose(convert_handle_to_file(dwHandle)); - } - inline DWORD file_get_size(DWORD file_handle) - { - FILE * hFile = convert_handle_to_file(file_handle); - if (hFile) - { - auto savedPos = ftell(hFile); - fseek(hFile, 0, SEEK_END); - DWORD dwSize = static_cast(ftell(hFile)); - fseek(hFile, savedPos, SEEK_SET); - return dwSize; - } - return 0; - } - inline DWORD read_file(void *buf, DWORD size, DWORD count, DWORD hFile) - { - return is_legacy_handle(hFile) ? legacy_fread(buf, size, 1, convert_handle_to_file(hFile)) : fread(buf, size, 1, convert_handle_to_file(hFile)); - } - inline DWORD write_file(const void *buf, DWORD size, DWORD count, DWORD hFile) - { - return is_legacy_handle(hFile) ? legacy_fwrite(buf, size, 1, convert_handle_to_file(hFile)) : fwrite(buf, size, 1, convert_handle_to_file(hFile)); - } - inline void flush_file(DWORD dwHandle) - { - if (is_legacy_handle(dwHandle)) legacy_fflush(convert_handle_to_file(dwHandle)); - else fflush(convert_handle_to_file(dwHandle)); - } - inline void ThreadJump(CRunningScript *thread, int off) { thread->SetIp(off < 0 ? thread->GetBasePointer() - off : scmBlock + off); @@ -1434,7 +1199,7 @@ namespace CLEO if ((size_t)address <= CCustomOpcodeSystem::MinValidAddress) { SHOW_ERROR("Invalid '0x%X' pointer param of opcode [0A8C] in script %s\nScript suspended.", address, ((CCustomScript*)thread)->GetInfoStr().c_str()); - return CCustomOpcodeSystem::ErrorSuspendScript(thread); + return thread->Suspend(); } switch (size) @@ -1463,7 +1228,7 @@ namespace CLEO if ((size_t)address <= CCustomOpcodeSystem::MinValidAddress) { SHOW_ERROR("Invalid '0x%X' pointer param of opcode [0A8D] in script %s\nScript suspended.", address, ((CCustomScript*)thread)->GetInfoStr().c_str()); - return CCustomOpcodeSystem::ErrorSuspendScript(thread); + return thread->Suspend(); } opcodeParams[0].dwParam = 0; @@ -1480,7 +1245,7 @@ namespace CLEO break; default: SHOW_ERROR("Invalid size param '%d' of opcode [0A8D] in script %s\nScript suspended.", size, ((CCustomScript*)thread)->GetInfoStr().c_str()); - return CCustomOpcodeSystem::ErrorSuspendScript(thread); + return thread->Suspend(); } SetScriptParams(thread, 1); @@ -1664,105 +1429,6 @@ namespace CLEO return OR_CONTINUE; } - //0A9A=3,%3d% = openfile %1d% mode %2d% // IF and SET - OpcodeResult __stdcall opcode_0A9A(CRunningScript *thread) - { - auto path = ReadStringParam(thread); OPCODE_VALIDATE_STR_ARG_READ(path) - - auto filename = reinterpret_cast(thread)->ResolvePath(path); - auto paramType = *thread->GetBytePointer(); - char mode[0x10]; - - // either CLEO 3 or CLEO 4 made a big mistake! (they differ in one major unapparent preference) - // lets try to resolve this with a legacy mode - auto cs = (CCustomScript*)thread; - bool bLegacyMode = cs->IsCustom() && cs->GetCompatibility() < CLEO_VER_4_3; - - if (paramType >= 1 && paramType <= 8) - { - // integer param (for backward compatibility with CLEO 3) - union - { - DWORD uParam; - char strParam[4]; - } param; - *thread >> param.uParam; - strcpy(mode, param.strParam); - } - else - { - auto modeOk = ReadStringParam(thread, mode, sizeof(mode)); - OPCODE_VALIDATE_STR_ARG_READ(modeOk) - } - - if (auto hfile = open_file(filename.c_str(), mode, bLegacyMode)) - { - GetInstance().OpcodeSystem.m_hFiles.insert(hfile); - - *thread << hfile; - SetScriptCondResult(thread, true); - } - else - { - *thread << NULL; - SetScriptCondResult(thread, false); - } - - return OR_CONTINUE; - } - - //0A9B=1,closefile %1d% - OpcodeResult __stdcall opcode_0A9B(CRunningScript *thread) - { - DWORD hFile; - *thread >> hFile; - if (convert_handle_to_file(hFile)) - { - close_file(hFile); - GetInstance().OpcodeSystem.m_hFiles.erase(hFile); - } - return OR_CONTINUE; - } - - //0A9C=2,%2d% = file %1d% size - OpcodeResult __stdcall opcode_0A9C(CRunningScript *thread) - { - DWORD hFile; - *thread >> hFile; - if (convert_handle_to_file(hFile)) *thread << file_get_size(hFile); - return OR_CONTINUE; - } - - //0A9D=3,readfile %1d% size %2d% to %3d% - OpcodeResult __stdcall opcode_0A9D(CRunningScript *thread) - { - DWORD hFile; - DWORD size; - *thread >> hFile >> size; - - SCRIPT_VAR* buf = GetScriptParamPointer(thread); - buf->dwParam = 0; // https://github.com/cleolibrary/CLEO4/issues/91 - - if (convert_handle_to_file(hFile)) read_file(buf, size, 1, hFile); - return OR_CONTINUE; - } - - //0A9E=3,writefile %1d% size %2d% from %3d% - OpcodeResult __stdcall opcode_0A9E(CRunningScript *thread) - { - DWORD hFile; - DWORD size; - const void *buf; - *thread >> hFile >> size; - buf = GetScriptParamPointer(thread); - if (convert_handle_to_file(hFile)) - { - write_file(buf, size, 1, hFile); - flush_file(hFile); - } - return OR_CONTINUE; - } - //0A9F=1,%1d% = current_thread_pointer OpcodeResult __stdcall opcode_0A9F(CRunningScript *thread) { @@ -1951,7 +1617,7 @@ namespace CLEO else { SHOW_ERROR("Invalid type (%s) of the 'input param count' argument in opcode [0AB1] in script %s \nScript suspended.", ToKindStr(paramType), ((CCustomScript*)thread)->GetInfoStr().c_str()); - return CCustomOpcodeSystem::ErrorSuspendScript(thread); + return thread->Suspend(); } ScmFunction* scmFunc = new ScmFunction(thread); @@ -1964,7 +1630,7 @@ namespace CLEO if (pos == str.npos) { SHOW_ERROR("Invalid module reference '%s' in opcode [0AB1] in script %s \nScript suspended.", moduleTxt, ((CCustomScript*)thread)->GetInfoStr().c_str()); - return CCustomOpcodeSystem::ErrorSuspendScript(thread); + return thread->Suspend(); } std::string_view strExport = str.substr(0, pos); std::string_view strModule = str.substr(pos + 1); @@ -1978,7 +1644,7 @@ namespace CLEO if (!scriptRef.Valid()) { SHOW_ERROR("Not found module '%s' export '%s', requested by opcode [0AB1] in script %s", modulePath.c_str(), &str[0], ((CCustomScript*)thread)->GetInfoStr().c_str()); - return CCustomOpcodeSystem::ErrorSuspendScript(thread); + return thread->Suspend(); } scmFunc->moduleExportRef = scriptRef.base; // to be released on return @@ -2000,7 +1666,7 @@ namespace CLEO else { SHOW_ERROR("Invalid type of first argument in opcode [0AB1], in script %s", ((CCustomScript*)thread)->GetInfoStr().c_str()); - return CCustomOpcodeSystem::ErrorSuspendScript(thread); + return thread->Suspend(); } } if (nParams) @@ -2009,13 +1675,13 @@ namespace CLEO if (nParams > nVarArg) // if less it means there are return params too { SHOW_ERROR("Opcode [0AB1] declared %d input args, but provided %d in script %s\nScript suspended.", nParams, nVarArg, ((CCustomScript*)thread)->GetInfoStr().c_str()); - return CCustomOpcodeSystem::ErrorSuspendScript(thread); + return thread->Suspend(); } if (nParams > 32) { SHOW_ERROR("Argument count %d is out of supported range (32) of opcode [0AB1] in script %s", nParams, ((CCustomScript*)thread)->GetInfoStr().c_str()); - return CCustomOpcodeSystem::ErrorSuspendScript(thread); + return thread->Suspend(); } } @@ -2055,7 +1721,7 @@ namespace CLEO else { SHOW_ERROR("Invalid argument type '0x%02X' in opcode [0AB1] in script %s\nScript suspended.", paramType, ((CCustomScript*)thread)->GetInfoStr().c_str()); - return CCustomOpcodeSystem::ErrorSuspendScript(thread); + return thread->Suspend(); } } @@ -2094,14 +1760,14 @@ namespace CLEO if (!IsImmInteger(paramType)) { SHOW_ERROR("Invalid type of first argument in opcode [0AB2], in script %s", ((CCustomScript*)thread)->GetInfoStr().c_str()); - return CCustomOpcodeSystem::ErrorSuspendScript(thread); + return thread->Suspend(); } 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()); - return CCustomOpcodeSystem::ErrorSuspendScript(thread); + return thread->Suspend(); } else if (returnParamCount - 1 > declaredParamCount) // more args than needed, not critical { @@ -2382,7 +2048,7 @@ namespace CLEO if ((size_t)mem <= CCustomOpcodeSystem::MinValidAddress) { SHOW_ERROR("[0AC9] used with invalid '0x%X' pointer argument in script %s\nScript suspended.", mem, ((CCustomScript*)thread)->GetInfoStr().c_str()); - return CCustomOpcodeSystem::ErrorSuspendScript(thread); + return thread->Suspend(); } // allocated with 0AC8 @@ -2548,105 +2214,6 @@ namespace CLEO return OR_CONTINUE; } - //0AD5=3,file %1d% seek %2d% from_origin %3d% //IF and SET - OpcodeResult __stdcall opcode_0AD5(CRunningScript *thread) - { - DWORD hFile; - int seek, origin; - *thread >> hFile >> seek >> origin; - if (convert_handle_to_file(hFile)) SetScriptCondResult(thread, fseek(convert_handle_to_file(hFile), seek, origin) == 0); - else SetScriptCondResult(thread, false); - return OR_CONTINUE; - } - - //0AD6=1,end_of_file %1d% reached - OpcodeResult __stdcall opcode_0AD6(CRunningScript *thread) - { - DWORD hFile; - *thread >> hFile; - if (FILE *file = convert_handle_to_file(hFile)) - SetScriptCondResult(thread, ferror(file) || feof(file) != 0); - else - SetScriptCondResult(thread, true); - return OR_CONTINUE; - } - - //0AD7=3,read_string_from_file %1d% to %2d% size %3d% //IF and SET - OpcodeResult __stdcall opcode_0AD7(CRunningScript *thread) - { - DWORD hFile; - char *buf; - DWORD size; - *thread >> hFile; - if (*thread->GetBytePointer() >= 1 && *thread->GetBytePointer() <= 8) *thread >> buf; - else buf = (char *)GetScriptParamPointer(thread); - *thread >> size; - if (convert_handle_to_file(hFile)) SetScriptCondResult(thread, fgets(buf, size, convert_handle_to_file(hFile)) == buf); - else SetScriptCondResult(thread, false); - return OR_CONTINUE; - } - - //0AD8=2,write_string_to_file %1d% from %2d% //IF and SET - OpcodeResult __stdcall opcode_0AD8(CRunningScript *thread) - { - DWORD hFile; *thread >> hFile; - auto text = ReadStringParam(thread); OPCODE_VALIDATE_STR_ARG_READ(text) - - if (FILE * file = convert_handle_to_file(hFile)) - { - SetScriptCondResult(thread, fputs(text, file) > 0); - fflush(file); - } - else - { - SetScriptCondResult(thread, false); - } - return OR_CONTINUE; - } - - //0AD9=-1,write_formated_text %2d% to_file %1d% - OpcodeResult __stdcall opcode_0AD9(CRunningScript *thread) - { - DWORD hFile; *thread >> hFile; - auto format = ReadStringParam(thread); OPCODE_VALIDATE_STR_ARG_READ(format) - char text[MAX_STR_LEN]; OPCODE_READ_FORMATTED_STRING(thread, text, sizeof(text), format) - - if (FILE * file = convert_handle_to_file(hFile)) - { - fputs(text, file); - fflush(file); - } - return OR_CONTINUE; - } - - //0ADA=-1,%3d% = scan_file %1d% format %2d% //IF and SET - OpcodeResult __stdcall opcode_0ADA(CRunningScript *thread) - { - DWORD hFile; *thread >> hFile; - auto format = ReadStringParam(thread); OPCODE_VALIDATE_STR_ARG_READ(format) - int *result = (int *)GetScriptParamPointer(thread); - - size_t cExParams = 0; - SCRIPT_VAR *ExParams[35]; - // read extra params - while (*thread->GetBytePointer()) ExParams[cExParams++] = GetScriptParamPointer(thread); - thread->IncPtr(); - - if (FILE *file = convert_handle_to_file(hFile)) - { - *result = fscanf(file, format, - /* extra parameters (will be aligned automatically, but the limit of 35 elements maximum exists) */ - ExParams[0], ExParams[1], ExParams[2], ExParams[3], ExParams[4], ExParams[5], - ExParams[6], ExParams[7], ExParams[8], ExParams[9], ExParams[10], ExParams[11], - ExParams[12], ExParams[13], ExParams[14], ExParams[15], ExParams[16], ExParams[17], - ExParams[18], ExParams[19], ExParams[20], ExParams[21], ExParams[22], ExParams[23], - ExParams[24], ExParams[25], ExParams[26], ExParams[27], ExParams[28], ExParams[29], - ExParams[30], ExParams[31], ExParams[32], ExParams[33], ExParams[34]); - } - SetScriptCondResult(thread, cExParams == *result); - return OR_CONTINUE; - } - //0ADB=2,%2d% = car_model %1o% name OpcodeResult __stdcall opcode_0ADB(CRunningScript *thread) { @@ -2989,7 +2556,7 @@ namespace CLEO if (argCount < 1) { SHOW_ERROR("Opcode [2002] missing condition result argument in script %s\nScript suspended.", ((CCustomScript*)thread)->GetInfoStr().c_str()); - return CCustomOpcodeSystem::ErrorSuspendScript(thread); + return thread->Suspend(); } DWORD result; *thread >> result; @@ -3006,7 +2573,7 @@ namespace CLEO 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); + return thread->Suspend(); } SetScriptCondResult(thread, false); @@ -3021,7 +2588,7 @@ namespace CLEO if ((size_t)mem <= CCustomOpcodeSystem::MinValidAddress) { SHOW_ERROR("[2004] used with invalid '0x%X' pointer argument in script %s\nScript suspended.", mem, ((CCustomScript*)thread)->GetInfoStr().c_str()); - return CCustomOpcodeSystem::ErrorSuspendScript(thread); + return thread->Suspend(); } // allocated with 0AC8 @@ -3098,6 +2665,23 @@ extern "C" return result; } + void WINAPI CLEO_ReadStringParamWriteBuffer(CLEO::CRunningScript* thread, char** outBuf, int* outBufSize, DWORD* outNeedsTerminator) + { + if (thread == nullptr || + outBuf == nullptr || + outBufSize == nullptr || + outNeedsTerminator == nullptr) + { + LOG_WARNING(thread, "Invalid argument of CLEO_ReadStringParamWriteBuffer in script %s", ((CCustomScript*)thread)->GetInfoStr().c_str()); + return; + } + + auto target = GetStringParamWriteBuffer(thread); + *outBuf = target.data; + *outBufSize = target.size; + *outNeedsTerminator = target.needTerminator; + } + void WINAPI CLEO_WriteStringOpcodeParam(CLEO::CRunningScript* thread, const char* str) { if(!WriteStringParam(thread, str)) diff --git a/source/CCustomOpcodeSystem.h b/source/CCustomOpcodeSystem.h index f8e940c2..7885d4a9 100644 --- a/source/CCustomOpcodeSystem.h +++ b/source/CCustomOpcodeSystem.h @@ -8,8 +8,6 @@ namespace CLEO { typedef OpcodeResult(__stdcall * CustomOpcodeHandler)(CRunningScript*); - bool is_legacy_handle(DWORD dwHandle); - FILE * convert_handle_to_file(DWORD dwHandle); extern const char* (__cdecl* GetUserDirectory)(); extern void(__cdecl* ChangeToUserDir)(); @@ -45,18 +43,14 @@ namespace CLEO static OpcodeResult CallFunctionGeneric(WORD opcode, CRunningScript* thread, bool thisCall, bool returnArg); static OpcodeResult CleoReturnGeneric(WORD opcode, CRunningScript* thread, bool returnArgs = false, DWORD returnArgCount = 0, bool strictArgCount = true); - static OpcodeResult ErrorSuspendScript(CRunningScript* thread); // suspend script execution forever private: - friend OpcodeResult __stdcall opcode_0A9A(CRunningScript *pScript); - friend OpcodeResult __stdcall opcode_0A9B(CRunningScript *pScript); friend OpcodeResult __stdcall opcode_0AA2(CRunningScript *pScript); friend OpcodeResult __stdcall opcode_0AA3(CRunningScript *pScript); friend OpcodeResult __stdcall opcode_0AC8(CRunningScript *pScript); friend OpcodeResult __stdcall opcode_0AC9(CRunningScript *pScript); friend OpcodeResult __stdcall opcode_2004(CRunningScript* pScript); - std::set m_hFiles; std::set m_hNativeLibs; std::set m_pAllocations; diff --git a/source/CGameVersionManager.cpp b/source/CGameVersionManager.cpp index 1c48e62f..663f01a7 100644 --- a/source/CGameVersionManager.cpp +++ b/source/CGameVersionManager.cpp @@ -9,21 +9,6 @@ namespace CLEO { 0x0053E981, memory_und, 0x0053E981, 0x0053EE21, 0x00551174 }, // MA_CALL_UPDATE_GAME_LOGICS, { 0x0053BEE0, memory_und, 0x0053BEE0, 0x0053C380, 0x0054DE60 }, // MA_UPDATE_GAME_LOGICS_FUNCTION, - // GV_US10, GV_US11, GV_EU10, GV_EU11, GV_STEAM - { 0x008232D8, memory_und, 0x00823318, 0x00824098, 0x0085C75E }, // MA_FOPEN_FUNCTION, - { 0x0082318B, memory_und, 0x008231CB, 0x00823F4B, 0x0085C396 }, // MA_FCLOSE_FUNCTION, - { 0x008231DC, memory_und, 0x0082321C, 0x00823F9C, 0x0085C680 }, // MA_FGETC_FUNCTION, - { 0x00823798, memory_und, 0x008237D8, 0x00824558, 0x0085D00C }, // MA_FGETS_FUNCTION, - { 0x008262B8, memory_und, 0x008262F8, 0x00826BA8, 0x008621F1 }, // MA_FPUTS_FUNCTION, - { 0x00823521, memory_und, 0x00823561, 0x008242E1, 0x0085CD04 }, // MA_FREAD_FUNCTION, - { 0x00823674, memory_und, 0x008236B4, 0x00824434, 0x0085CE7E }, // MA_FWRITE_FUNCTION, - { 0x0082374F, memory_und, 0x0082378F, 0x0082450F, 0x0085CF87 }, // MA_FSEEK_FUNCTION, - { 0x00823A30, memory_und, 0x00823A70, 0x008247F0, 0x0085D464 }, // MA_FPRINTF_FUNCTION, - { 0x00826261, memory_und, 0x008262A1, 0x00826B51, 0x00862183 }, // MA_FTELL_FUNCTION, - { 0x00823E86, memory_und, 0x00823EC6, 0x00824C46, 0x0085DDDD }, // MA_FFLUSH_FUNCTION, - { 0x008262A2, memory_und, 0x008262E2, 0x00826B92, 0x0085D193 }, // MA_FEOF_FUNCTION, - { 0x008262AD, memory_und, 0x008262ED, 0x00826B9D, 0x0085D1C2 }, // MA_FERROR_FUNCTION, - // GV_US10, GV_US11, GV_EU10, GV_EU11, GV_STEAM { 0x00BA6748, memory_und, 0x00BA6748, 0x00BA8DC8, 0x00C33100 }, // MA_MENU_MANAGER, { 0x0071A700, memory_und, 0x0071A700, 0x0071AF30, 0x0073BF50 }, // MA_DRAW_TEXT_FUNCTION, diff --git a/source/CGameVersionManager.h b/source/CGameVersionManager.h index ed73ed6e..aa0a82fa 100644 --- a/source/CGameVersionManager.h +++ b/source/CGameVersionManager.h @@ -27,21 +27,6 @@ namespace CLEO MA_CALL_UPDATE_GAME_LOGICS, MA_UPDATE_GAME_LOGICS_FUNCTION, - // CrtFix - MA_FOPEN_FUNCTION, - MA_FCLOSE_FUNCTION, - MA_FGETC_FUNCTION, - MA_FGETS_FUNCTION, - MA_FPUTS_FUNCTION, - MA_FREAD_FUNCTION, - MA_FWRITE_FUNCTION, - MA_FSEEK_FUNCTION, - MA_FPRINTF_FUNCTION, - MA_FTELL_FUNCTION, - MA_FFLUSH_FUNCTION, - MA_FEOF_FUNCTION, - MA_FERROR_FUNCTION, - // MenuStatusNotifier MA_MENU_MANAGER, MA_DRAW_TEXT_FUNCTION, diff --git a/source/cleo.def b/source/cleo.def index c9dffce9..3174d3ab 100644 --- a/source/cleo.def +++ b/source/cleo.def @@ -37,3 +37,4 @@ EXPORTS _CLEO_GetScriptDebugMode@4 @34 _CLEO_SetScriptDebugMode@8 @35 _CLEO_Log@8 @36 + _CLEO_ReadStringParamWriteBuffer@16 @37 diff --git a/tests/test_file_read_write.txt b/tests/test_file_read_write.txt new file mode 100644 index 00000000..65c77cf0 --- /dev/null +++ b/tests/test_file_read_write.txt @@ -0,0 +1,205 @@ +{$CLEO .cs} +{$USE file} +{$USE debug} + +debug_on +wait 3000 + +var 5@ : Integer +var 6@ : Integer + +copy_file "cleo\.cleo.log" {to} "cleo\.cleo_test.log" +while true + if + // test 0A9A + 0@ = open_file "cleo\.cleo_test.log" {mode} "r+" // read and write + then + print_formatted_now "0A9A File opened" time 1000 + wait 1000 + + // test 0A9C + 1@ = get_file_size 0@ + print_formatted_now "0A9C File size: %d" time 2000 1@ + wait 2000 + + // test 0A9D + 5@ = 0xCCCCCCCC + 0A9D: readfile 0@ size 2 to 5@ + print_formatted_now "0A9D Read WORD %x" time 2000 5@ + wait 2000 + + // test 0A9E + 5@ = 0xAABBCCDD + 0A9E: write_file 0@ size 2 from 5@ + 0AD5: file 0@ seek -2 from_origin SeekOrigin.Current //IF and SET + 5@ = 0 + 0A9D: readfile 0@ size 2 to 5@ + if + 5@ == 0xCCDD + then + print_formatted_now "0A9E ok" time 1000 + wait 1000 + else + print_formatted_now "~r~0A9E failed~n~read: 0x%X, expected: 0xCCDD" time 5000 5@ + wait 5000 + end + + // test 0AD5 + 0A9D: readfile 0@ size 4 to 5@ + if + 0AD5: file 0@ seek -2 from_origin SeekOrigin.Current //IF and SET + then + 0A9D: readfile 0@ size 4 to 6@ + if + 5@ <> 6@ + then + print_formatted_now "0AD5 ok" time 1000 + wait 1000 + else + print_formatted_now "~r~0AD5 invalid result" time 5000 + wait 5000 + end + else + print_formatted_now "~r~0AD5 seek back failed" time 5000 + wait 5000 + end + + // test 0AD6 + if + not is_end_of_file_reached 0@ + then + print_formatted_now "0AD6: not EOF yet" time 1000 + wait 1000 + else + print_formatted_now "~r~0AD6: EOF reached" time 5000 + wait 5000 + end + + // test 0AD7 + 0AD5: file 0@ seek 30 from_origin SeekOrigin.Current + if + 0AD7: read_string_from_file 0@ to 1@v size 15 + then + 0ACE: print_help_formatted "0AD7 read string" + print_formatted_now "Read: %s" time 2000 1@v + wait 2000 + else + print_formatted_now "~r~0AD7 failed" time 5000 + wait 5000 + end + + // test 0AD8 + if + 0AD8: write_string_to_file 0@ {text} "test text" + then + 0AD5: file 0@ seek -9 from_origin SeekOrigin.Current + 0AD7: read_string_from_file 0@ to 1@v size 10 + + if + 1@v == "test text" + then + print_formatted_now "0AD8 ok" time 1000 + wait 1000 + else + print_formatted_now "~r~0AD8 invalid result~n~%s" time 5000 1@v + wait 5000 + end + else + print_formatted_now "~r~0AD8 failed to write" time 5000 + wait 5000 + end + + // test 0AD9 + 0AD9: write_formatted_string_to_file 0@ {format} "%x%X%s" {args} 0xA 0xB "CD" + 0AD5: file 0@ seek -4 from_origin SeekOrigin.Current + 0AD7: read_string_from_file 0@ to 1@v size 5 + if + 1@v == "aBCD" + then + print_formatted_now "0AD9 ok" time 1000 + wait 1000 + else + print_formatted_now "~r~0AD9 invalid result~n~%s" time 5000 1@v + wait 5000 + end + + // test 0ADA + 0AD8: write_string_to_file 0@ {text} "5:17 3.1415 END" + 0AD5: file 0@ seek -15 from_origin SeekOrigin.Current + if + 0ADA: scan_file 0@ {format} "%d:%d %f" {nValues} 5@ {values} 6@ 7@ 8@ + then + if and + 5@ == 3 + 6@ == 5 + 7@ == 17 + 8@ == 3.1415 + then + 0AD7: read_string_from_file 0@ to 1@v size 5 + if + 1@v == " END" + then + print_formatted_now "0ADA ok" time 1000 + wait 1000 + else + print_formatted_now "~r~0ADA post check fail~n~%s" time 5000 1@v + wait 5000 + end + else + print_formatted_now "~r~0ADA invalid result~n~%d %d %d %f" time 5000 5@ 6@ 7@ 8@ + wait 5000 + end + else + print_formatted_now "~r~0ADA failed. Read args: %d" time 5000 5@ + wait 5000 + end + + // test 2300 + 2300: get_file_position 0@ {store_to} 5@ + 0AD8: write_string_to_file 0@ {text} "abc" + 2300: get_file_position 0@ {store_to} 6@ + 6@ -= 5@ + if + 6@ == 3 + then + print_formatted_now "2300 ok" time 1000 + wait 1000 + else + print_formatted_now "~r~2300 failed. Difference: %d" time 1000 6@ + wait 1000 + end + + // test 2301 + 0AD8: write_string_to_file 0@ {text} "test text" + 0AD5: file 0@ seek -9 from_origin SeekOrigin.Current + 1@ = 0 + 2@ = 0 + 3@ = 0 + 4@ = 0 + 5@ = get_var_pointer 1@ + if + 2301: read_block_from_file 0@ {size} 9 {buffer} 5@ + then + if + 1@v == "test text" + then + print_formatted_now "2301 ok" time 1000 + wait 1000 + else + print_formatted_now "~r~2301 invalid result~n~%s" time 5000 1@v + wait 5000 + end + else + print_formatted_now "~r~2301 failed to read" time 5000 + wait 5000 + end + + // test 0A9B + 0A9B: close_file 0@ + else + print_formatted_now "Failed to open the file" time 5000 + end + + print_formatted_now "Finished testing file read write opcodes" time 5000 + wait 5000 +end