From 2a2849537c9ea7e47b42923cd545192379d7936b Mon Sep 17 00:00:00 2001 From: NovaRain Date: Sun, 1 Dec 2019 11:58:13 +0800 Subject: [PATCH] Added "string_format" script function Some code refactoring. --- artifacts/ddraw.ini | 14 +-- artifacts/scripting/headers/sfall.h | 1 + artifacts/scripting/sfall function notes.txt | 6 +- sfall/Modules/DebugEditor.cpp | 11 ++- sfall/Modules/LoadGameHook.cpp | 2 +- sfall/Modules/ScriptExtender.cpp | 2 + sfall/Modules/ScriptExtender.h | 5 ++ sfall/Modules/Scripting/Handlers/Metarule.cpp | 1 + sfall/Modules/Scripting/Handlers/Misc.cpp | 23 +++-- sfall/Modules/Scripting/Handlers/Utils.cpp | 89 ++++++++++++++----- sfall/Modules/Scripting/Handlers/Utils.h | 2 + 11 files changed, 105 insertions(+), 51 deletions(-) diff --git a/artifacts/ddraw.ini b/artifacts/ddraw.ini index e3d40dbf..30366a6e 100644 --- a/artifacts/ddraw.ini +++ b/artifacts/ddraw.ini @@ -707,29 +707,29 @@ CreditsAtBottom=0 ;XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX ;To change the path and filename of the critical table file, uncomment the next line -;OverrideCriticalFile=CriticalOverrides.ini +;OverrideCriticalFile=sfall\CriticalOverrides.ini ;To change the relationship between SPECIAL stats and derived stats, uncomment the next line ;See the Stats.ini in the modders pack for an example file -;DerivedStats=Stats.ini +;DerivedStats=sfall\Stats.ini ;Allows you to edit the skill tables ;Point the next line to an ini file containing the replacement skill data -;SkillsFile=Skills.ini +;SkillsFile=sfall\Skills.ini ;To add additional perks to the game, uncomment the next line and set it to point to a file containing perk information -;PerksFile=Perks.ini +;PerksFile=sfall\Perks.ini ;To add additional books to the game, uncomment the next line and point to a file containing book information ;See the Books.ini in the modders pack for an example file -;BooksFile=Books.ini +;BooksFile=sfall\Books.ini ;Allows you to change some parameters for drugs and their addictions ;See the Drugs.ini in the modders pack for an example file -;DrugsFile=Drugs.ini +;DrugsFile=sfall\Drugs.ini ;Point to an ini file containing elevator data -;ElevatorsFile=Elevators.ini +;ElevatorsFile=sfall\Elevators.ini ;XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX [Scripts] diff --git a/artifacts/scripting/headers/sfall.h b/artifacts/scripting/headers/sfall.h index 52a11781..173da3d1 100644 --- a/artifacts/scripting/headers/sfall.h +++ b/artifacts/scripting/headers/sfall.h @@ -320,6 +320,7 @@ #define spatial_radius(obj) sfall_func1("spatial_radius", obj) #define string_compare(str1, str2) sfall_func2("string_compare", str1, str2) #define string_compare_locale(str1, str2, codePage) sfall_func3("string_compare", str1, str2, codePage) +#define string_format(format, a1, a2) sfall_func3("string_format", format, a1, a2) #define tile_refresh_display sfall_func0("tile_refresh_display") #define unjam_lock(obj) sfall_func1("unjam_lock", obj) #define unset_unique_id(obj) sfall_func2("set_unique_id", obj, -1) diff --git a/artifacts/scripting/sfall function notes.txt b/artifacts/scripting/sfall function notes.txt index 2bdb4e27..29ce6aad 100644 --- a/artifacts/scripting/sfall function notes.txt +++ b/artifacts/scripting/sfall function notes.txt @@ -262,7 +262,7 @@ Some utility/math functions are available: - you can use this to search for a substring in a string like this: strlen(get_array(string_split(haystack, needle), 0)) > string substr(string, start, length) -- cuts a substring from a string starting at "start" up to "length" characters. The first character position starts with 0 (zero). +- cuts a substring from a string starting at "start" up to "length" characters. The first character position is 0 (zero). - If start is negative - it indicates starting position from the end of the string (for example substr("test", -2, 2) will return last 2 charactes: "st"). - If length is negative - it means so many characters will be omitted from the end of string (example: substr("test", 0, -2) will return string without last 2 characters: "te"). - If length is zero - it will return a string from the starting position to the end of the string **New behavior** for sfall 4.2.2/3.8.22 @@ -682,6 +682,10 @@ optional argument: - codePage: code page number to properly compare national characters in the range 128-255 of the ASCII code table available encodings: 1250-1252, 866 +> string sfall_func3("string_format", string format, any val1, any val2, ...) +- formats given value using standard syntax of C printf function (google "printf" for format details). However it is limited to formatting up to 4 values +- formatting is only supported for %s and %d. The format string is limited to 1024 characters + ------------------------ ------ MORE INFO ------- ------------------------ diff --git a/sfall/Modules/DebugEditor.cpp b/sfall/Modules/DebugEditor.cpp index 2e91c419..d3a69d30 100644 --- a/sfall/Modules/DebugEditor.cpp +++ b/sfall/Modules/DebugEditor.cpp @@ -310,19 +310,18 @@ hide: } } -static char* artDbgMsg = "\nError: file not found: %s\n"; +static char* artDbgMsg = "\nERROR: File not found: %s\n"; static void __declspec(naked) art_data_size_hook() { __asm { test edi, edi; jz artNotExist; retn; artNotExist: - mov eax, [esp + 0x124 - 0x1C + 4]; // filename - push eax; + mov edx, [esp + 0x124 - 0x1C + 4]; // filename + push edx; push artDbgMsg; call fo::funcoffs::debug_printf_; - mov eax, [esp + 0x124 - 0x1C + 12]; // filename - push eax; + push edx; // filename push artDbgMsg; lea eax, [esp + 0x124 - 0x124 + 20]; // buf push eax; @@ -368,7 +367,7 @@ static void DebugModePatch() { if (iniGetInt("Debugging", "HideObjIsNullMsg", 0, ::sfall::ddrawIni)) { MakeJump(0x453FD2, dbg_error_hack); } - // prints a debug message about missing art file for critters to debug.log and the message window + // prints a debug message about missing art file for critters to both debug.log and the message window HookCall(0x419B65, art_data_size_hook); dlogr(" Done", DL_INIT); diff --git a/sfall/Modules/LoadGameHook.cpp b/sfall/Modules/LoadGameHook.cpp index 2147ff27..51ed2c0d 100644 --- a/sfall/Modules/LoadGameHook.cpp +++ b/sfall/Modules/LoadGameHook.cpp @@ -523,7 +523,7 @@ end: static void __declspec(naked) DialogHook() { __asm { - test inLoop, DIALOG; // check bit flag + test inLoop, DIALOG; // check byte flag jz changeMode; jmp fo::funcoffs::gdProcess_; changeMode: diff --git a/sfall/Modules/ScriptExtender.cpp b/sfall/Modules/ScriptExtender.cpp index 7eb7ed9c..07365dce 100644 --- a/sfall/Modules/ScriptExtender.cpp +++ b/sfall/Modules/ScriptExtender.cpp @@ -51,6 +51,8 @@ static DWORD _stdcall HandleMapUpdateForScripts(const DWORD procId); static int idle; +char ScriptExtender::gTextBuffer[5120]; // used as global temp text buffer for script functions + std::string ScriptExtender::iniConfigFolder; struct GlobalScript { diff --git a/sfall/Modules/ScriptExtender.h b/sfall/Modules/ScriptExtender.h index 43ad45da..cf852a28 100644 --- a/sfall/Modules/ScriptExtender.h +++ b/sfall/Modules/ScriptExtender.h @@ -40,6 +40,11 @@ public: static std::string iniConfigFolder; + static char gTextBuffer[5120]; + + // returns the size of the global text buffer + inline static const long TextBufferSize() { return sizeof(gTextBuffer); } + static long GetScriptReturnValue(); static long GetResetScriptReturnValue(); diff --git a/sfall/Modules/Scripting/Handlers/Metarule.cpp b/sfall/Modules/Scripting/Handlers/Metarule.cpp index b79b876c..13e9184b 100644 --- a/sfall/Modules/Scripting/Handlers/Metarule.cpp +++ b/sfall/Modules/Scripting/Handlers/Metarule.cpp @@ -132,6 +132,7 @@ static const SfallMetarule metarules[] = { {"show_window", sf_show_window, 0, 1, -1, {ARG_STRING}}, {"spatial_radius", sf_spatial_radius, 1, 1, 0, {ARG_OBJECT}}, {"string_compare", sf_string_compare, 2, 3, 0, {ARG_STRING, ARG_STRING, ARG_INT}}, + {"string_format", sf_string_format, 2, 5, 0, {ARG_STRING, ARG_ANY, ARG_ANY, ARG_ANY, ARG_ANY}}, {"tile_refresh_display", sf_tile_refresh_display, 0, 0}, {"unjam_lock", sf_unjam_lock, 1, 1, -1, {ARG_OBJECT}}, {"unwield_slot", sf_unwield_slot, 2, 2, -1, {ARG_OBJECT, ARG_INT}}, diff --git a/sfall/Modules/Scripting/Handlers/Misc.cpp b/sfall/Modules/Scripting/Handlers/Misc.cpp index dc6aa34f..a2b9cbae 100644 --- a/sfall/Modules/Scripting/Handlers/Misc.cpp +++ b/sfall/Modules/Scripting/Handlers/Misc.cpp @@ -42,7 +42,7 @@ namespace sfall namespace script { -const char* stringTooLong = "%s() - the string length exceeds maximum of 64 characters."; +const char* stringTooLong = "%s() - the string exceeds maximum length of 64 characters."; void sf_set_dm_model(OpcodeContext& ctx) { auto model = ctx.arg(0).strValue(); @@ -453,7 +453,6 @@ specialIni: return 1; } -static char IniStrBuffer[256]; static DWORD GetIniSetting(const char* str, bool isString) { const char* key; char section[33], file[128]; @@ -462,9 +461,9 @@ static DWORD GetIniSetting(const char* str, bool isString) { return -1; } if (isString) { - IniStrBuffer[0] = 0; - iniGetString(section, key, "", IniStrBuffer, 256, file); - return (DWORD)&IniStrBuffer[0]; + ScriptExtender::gTextBuffer[0] = 0; + iniGetString(section, key, "", ScriptExtender::gTextBuffer, 256, file); + return (DWORD)&ScriptExtender::gTextBuffer[0]; } else { return iniGetInt(section, key, -1, file); } @@ -1037,8 +1036,8 @@ void sf_set_ini_setting(OpcodeContext& ctx) { const char* saveValue; if (argVal.isInt()) { - _itoa_s(argVal.rawValue(), IniStrBuffer, 10); - saveValue = IniStrBuffer; + _itoa_s(argVal.rawValue(), ScriptExtender::gTextBuffer, 10); + saveValue = ScriptExtender::gTextBuffer; } else { saveValue = argVal.strValue(); } @@ -1075,12 +1074,10 @@ static std::string GetIniFilePath(const ScriptValue& arg) { return fileName; } -char getIniSectionBuf[5120]; - void sf_get_ini_sections(OpcodeContext& ctx) { - GetPrivateProfileSectionNamesA(getIniSectionBuf, 5120, GetIniFilePath(ctx.arg(0)).data()); + GetPrivateProfileSectionNamesA(ScriptExtender::gTextBuffer, ScriptExtender::TextBufferSize(), GetIniFilePath(ctx.arg(0)).data()); std::vector sections; - char* section = getIniSectionBuf; + char* section = ScriptExtender::gTextBuffer; while (*section != 0) { sections.push_back(section); // position section += std::strlen(section) + 1; @@ -1099,10 +1096,10 @@ void sf_get_ini_sections(OpcodeContext& ctx) { void sf_get_ini_section(OpcodeContext& ctx) { auto section = ctx.arg(1).strValue(); - GetPrivateProfileSectionA(section, getIniSectionBuf, 5120, GetIniFilePath(ctx.arg(0)).data()); + GetPrivateProfileSectionA(section, ScriptExtender::gTextBuffer, ScriptExtender::TextBufferSize(), GetIniFilePath(ctx.arg(0)).data()); int arrayId = TempArray(-1, 0); // associative auto& arr = arrays[arrayId]; - char *key = getIniSectionBuf, *val = nullptr; + char *key = ScriptExtender::gTextBuffer, *val = nullptr; while (*key != 0) { char* val = std::strpbrk(key, "="); if (val != nullptr) { diff --git a/sfall/Modules/Scripting/Handlers/Utils.cpp b/sfall/Modules/Scripting/Handlers/Utils.cpp index 3f640b47..5576b8d6 100644 --- a/sfall/Modules/Scripting/Handlers/Utils.cpp +++ b/sfall/Modules/Scripting/Handlers/Utils.cpp @@ -190,8 +190,6 @@ void sf_string_split(OpcodeContext& ctx) { ctx.setReturn(StringSplit(ctx.arg(0).strValue(), ctx.arg(1).strValue())); } -static char* tempTextBuf = nullptr; - char* Substring(const char* str, int startPos, int length) { int len = strlen(str); @@ -201,11 +199,8 @@ char* Substring(const char* str, int startPos, int length) { } if (length < 0) { length += len - startPos; // cutoff at end - if (length == 0) { - return ""; - } else if (length < 0) { - length = -length; // length can't be negative - } + if (length == 0) return ""; + abs(length); // length can't be negative } // check position if (startPos >= len) return ""; // start position is out of string length, return empty string @@ -213,11 +208,12 @@ char* Substring(const char* str, int startPos, int length) { length = len - startPos; // set the correct length, the length of characters goes beyond the end of the string } - if (tempTextBuf) delete[] tempTextBuf; - tempTextBuf = new char[length + 1]; - memcpy(tempTextBuf, &str[startPos], length); - tempTextBuf[length] = '\0'; - return tempTextBuf; + const int bufMax = ScriptExtender::TextBufferSize() - 1; + if (length > bufMax) length = bufMax; + + memcpy(ScriptExtender::gTextBuffer, &str[startPos], length); + ScriptExtender::gTextBuffer[length] = '\0'; + return ScriptExtender::gTextBuffer; } void sf_substr(OpcodeContext& ctx) { @@ -241,8 +237,7 @@ static char* _stdcall sprintf_lite(const char* format, ScriptValue value) { int fmtlen = strlen(format); int buflen = fmtlen + 1; for (int i = 0; i < fmtlen; i++) { - if (format[i] == '%') - buflen++; // will possibly be escaped, need space for that + if (format[i] == '%') buflen++; // will possibly be escaped, need space for that } // parse format to make it safe char* newfmt = new char[buflen]; @@ -290,7 +285,8 @@ static char* _stdcall sprintf_lite(const char* format, ScriptValue value) { newfmt[j++] = c; } newfmt[j] = '\0'; - // calculate required memory + + // calculate required length if (hasDigits) { buflen = 254; } else if (specifier == 'c') { @@ -300,18 +296,18 @@ static char* _stdcall sprintf_lite(const char* format, ScriptValue value) { } else { buflen = j + 30; // numbers } - if (tempTextBuf) { - delete[] tempTextBuf; - } - tempTextBuf = new char[buflen + 1]; + + const long bufMaxLen = ScriptExtender::TextBufferSize() - 1; + if (buflen > bufMaxLen - 1) buflen = bufMaxLen - 1; + ScriptExtender::gTextBuffer[bufMaxLen] = '\0'; + if (value.isFloat()) { - _snprintf(tempTextBuf, buflen, newfmt, value.floatValue()); + _snprintf(ScriptExtender::gTextBuffer, buflen, newfmt, value.floatValue()); } else { - _snprintf(tempTextBuf, buflen, newfmt, value.rawValue()); + _snprintf(ScriptExtender::gTextBuffer, buflen, newfmt, value.rawValue()); } - tempTextBuf[buflen] = '\0'; // just in case delete[] newfmt; - return tempTextBuf; + return ScriptExtender::gTextBuffer; } void sf_sprintf(OpcodeContext& ctx) { @@ -320,6 +316,53 @@ void sf_sprintf(OpcodeContext& ctx) { ); } +void sf_string_format(OpcodeContext& ctx) { + const char* format = ctx.arg(0).strValue(); + + int fmtLen = strlen(format); + if (fmtLen == 0) { + ctx.setReturn(format); + return; + } + if (fmtLen > 1024) { + ctx.printOpcodeError("%s() - the format string exceeds maximum length of 1024 characters.", ctx.getMetaruleName()); + ctx.setReturn("Error"); + } else { + char* newFmt = new char[fmtLen + 1]; + newFmt[fmtLen] = '\0'; + // parse format to make it safe + int i = 0; + do { + char c = format[i]; + if (c == '%') { + char cf = format[i + 1]; + if (cf != 's' && cf != 'd' && cf != '%') c = ' '; // unsupported format + } + newFmt[i] = c; + } while (++i < fmtLen); + + const long bufMaxLen = ScriptExtender::TextBufferSize() - 1; + + switch (ctx.numArgs()) { + case 2 : + _snprintf(ScriptExtender::gTextBuffer, bufMaxLen, newFmt, ctx.arg(1).rawValue()); + break; + case 3 : + _snprintf(ScriptExtender::gTextBuffer, bufMaxLen, newFmt, ctx.arg(1).rawValue(), ctx.arg(2).rawValue()); + break; + case 4 : + _snprintf(ScriptExtender::gTextBuffer, bufMaxLen, newFmt, ctx.arg(1).rawValue(), ctx.arg(2).rawValue(), ctx.arg(3).rawValue()); + break; + case 5 : + _snprintf(ScriptExtender::gTextBuffer, bufMaxLen, newFmt, ctx.arg(1).rawValue(), ctx.arg(2).rawValue(), ctx.arg(3).rawValue(), ctx.arg(4).rawValue()); + } + ScriptExtender::gTextBuffer[bufMaxLen] = '\0'; // just in case + + delete[] newFmt; + ctx.setReturn(ScriptExtender::gTextBuffer); + } +} + void sf_power(OpcodeContext& ctx) { const ScriptValue &base = ctx.arg(0), &power = ctx.arg(1); diff --git a/sfall/Modules/Scripting/Handlers/Utils.h b/sfall/Modules/Scripting/Handlers/Utils.h index 0477c169..f82b427b 100644 --- a/sfall/Modules/Scripting/Handlers/Utils.h +++ b/sfall/Modules/Scripting/Handlers/Utils.h @@ -53,6 +53,8 @@ void sf_string_compare(OpcodeContext&); void sf_sprintf(OpcodeContext&); +void sf_string_format(OpcodeContext&); + void sf_ord(OpcodeContext&); void sf_typeof(OpcodeContext&);