diff --git a/artifacts/scripting/headers/sfall.h b/artifacts/scripting/headers/sfall.h index 874c2eb8..80f76316 100644 --- a/artifacts/scripting/headers/sfall.h +++ b/artifacts/scripting/headers/sfall.h @@ -391,6 +391,11 @@ #define string_format2(format, a1, a2) sfall_func3("string_format", format, a1, a2) #define string_format3(format, a1, a2, a3) sfall_func4("string_format", format, a1, a2, a3) #define string_format4(format, a1, a2, a3, a4) sfall_func5("string_format", format, a1, a2, a3, a4) +#define string_format5(format, a1, a2, a3, a4, a5) sfall_func6("string_format", format, a1, a2, a3, a4, a5) +#define string_format6(format, a1, a2, a3, a4, a5, a6) sfall_func7("string_format", format, a1, a2, a3, a4, a5, a6) +#define string_format7(format, a1, a2, a3, a4, a5, a6, a7) sfall_func8("string_format", format, a1, a2, a3, a4, a5, a6, a7) +#define string_format_MACRO(_0,_1,_2,_3,_4,_5,_6,FUNC,...) FUNC +#define string_format(...) string_format_MACRO(__VA_ARGS__, string_format6, string_format5, string_format4, string_format3, string_format2, string_format1)(__VA_ARGS__) #define string_tolower(text) sfall_func2("string_to_case", text, 0) #define string_toupper(text) sfall_func2("string_to_case", text, 1) #define tile_by_position(x, y) sfall_func2("tile_by_position", x, y) diff --git a/artifacts/scripting/sfall function notes.md b/artifacts/scripting/sfall function notes.md index b05f0bec..4491f81b 100644 --- a/artifacts/scripting/sfall function notes.md +++ b/artifacts/scripting/sfall function notes.md @@ -339,7 +339,7 @@ FUNCTION REFERENCE ----- ##### `string sprintf(string format, any value)` -- Formats given value using standard syntax of C `printf` function (google "printf" for format details). However it is limited to formatting only 1 value. +- Formats given value using standard syntax of C `printf` function (google "printf" for format details). However, it is limited to formatting only 1 value. - Can be used to get character by ASCII code (`%c`). ----- @@ -907,9 +907,10 @@ sfall_funcX metarule functions ---- #### string_format -`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`, and the format string is limited to 1024 characters +`string sfall_func2("string_format", string format, any val1)` +`string sfall_funcX("string_format", string format, any val1, any val2, ...)` +- Formats given values using standard syntax of C `printf` function (google "printf" for format details). However, it is limited to formatting up to 7 values +- The format string is limited to 1024 characters ---- #### objects_in_radius diff --git a/sfall/ConsoleWindow.cpp b/sfall/ConsoleWindow.cpp index 233150e7..ddffc0dd 100644 --- a/sfall/ConsoleWindow.cpp +++ b/sfall/ConsoleWindow.cpp @@ -55,10 +55,14 @@ void ConsoleWindow::loadPosition() { screenHeight = GetSystemMetrics(SM_CYSCREEN); int w = min(max(windowData[2], 640), screenWidth), h = min(max(windowData[3], 480), screenHeight), - x = min(max(windowData[0], 0), screenWidth - w), - y = min(max(windowData[1], 0), screenHeight - h); + x = min(max(windowData[0], 0), screenWidth - w/2), + y = min(max(windowData[1], 0), screenHeight - h/2); - if (!SetWindowPos(wnd, HWND_TOP, x, y, w, h, 0)) { + dlog_f("Setting console window position: (%d, %d), size: %dx%d\n", DL_MAIN, x, y, w, h); + if (!SetWindowPos(wnd, HWND_TOP, 0, 0, w, h, SWP_NOMOVE)) { + dlog_f("Error resizing console window: 0x%x\n", DL_MAIN, GetLastError()); + } + if (!SetWindowPos(wnd, HWND_TOP, x, y, 0, 0, SWP_NOSIZE)) { dlog_f("Error repositioning console window: 0x%x\n", DL_MAIN, GetLastError()); } } diff --git a/sfall/Modules/Scripting/Handlers/Metarule.cpp b/sfall/Modules/Scripting/Handlers/Metarule.cpp index 974bc663..6f7051c2 100644 --- a/sfall/Modules/Scripting/Handlers/Metarule.cpp +++ b/sfall/Modules/Scripting/Handlers/Metarule.cpp @@ -65,7 +65,7 @@ static MetaruleTableType metaruleTable; { name, handler, minArgs, maxArgs, error, {arg1, arg2, ...} } - name - name of function that will be used in scripts, - handler - pointer to handler function (see examples below), - - minArgs/maxArgs - minimum and maximum number of arguments allowed for this function (max 6) + - minArgs/maxArgs - minimum and maximum number of arguments allowed for this function (max 8) - returned error value for argument validation, - arg1, arg2, ... - argument types for automatic validation */ @@ -153,7 +153,7 @@ static const SfallMetarule metarules[] = { {"show_window", mf_show_window, 0, 1, -1, {ARG_STRING}}, {"spatial_radius", mf_spatial_radius, 1, 1, 0, {ARG_OBJECT}}, {"string_compare", mf_string_compare, 2, 3, 0, {ARG_STRING, ARG_STRING, ARG_INT}}, - {"string_format", mf_string_format, 2, 5, 0, {ARG_STRING, ARG_ANY, ARG_ANY, ARG_ANY, ARG_ANY}}, + {"string_format", mf_string_format, 2, 8, 0, {ARG_STRING, ARG_ANY, ARG_ANY, ARG_ANY, ARG_ANY, ARG_ANY, ARG_ANY, ARG_ANY}}, {"string_to_case", mf_string_to_case, 2, 2, -1, {ARG_STRING, ARG_INT}}, {"tile_by_position", mf_tile_by_position, 2, 2, -1, {ARG_INT, ARG_INT}}, {"tile_refresh_display", mf_tile_refresh_display, 0, 0}, diff --git a/sfall/Modules/Scripting/Handlers/Objects.cpp b/sfall/Modules/Scripting/Handlers/Objects.cpp index 1ee38b3f..41372d1d 100644 --- a/sfall/Modules/Scripting/Handlers/Objects.cpp +++ b/sfall/Modules/Scripting/Handlers/Objects.cpp @@ -156,7 +156,7 @@ void op_set_script(OpcodeContext& ctx) { long scriptIndex = valArg & ~0xF0000000; if (scriptIndex == 0 || valArg > 0x8FFFFFFF) { // negative values are not allowed - ctx.printOpcodeError("%s() - the script index number is incorrect.", ctx.getOpcodeName()); + ctx.printOpcodeError("%s() - invalid script index number.", ctx.getOpcodeName()); return; } scriptIndex--; @@ -429,7 +429,7 @@ void mf_item_make_explosive(OpcodeContext& ctx) { if (pid > 0 && pidActive > 0) { Explosions::AddToExplosives(pid, pidActive, min, max); } else { - ctx.printOpcodeError("%s() - invalid PID number, must be greater than 0.", ctx.getMetaruleName()); + ctx.printOpcodeError("%s() - invalid PID number.", ctx.getMetaruleName()); ctx.setReturn(-1); } } diff --git a/sfall/Modules/Scripting/Handlers/Utils.cpp b/sfall/Modules/Scripting/Handlers/Utils.cpp index 7e7b83ea..f34f77ee 100644 --- a/sfall/Modules/Scripting/Handlers/Utils.cpp +++ b/sfall/Modules/Scripting/Handlers/Utils.cpp @@ -199,148 +199,92 @@ void mf_string_compare(OpcodeContext& ctx) { } // A safer version of sprintf for using in user scripts. -static char* sprintf_lite(const char* format, const ScriptValue& value) { - int fmtlen = strlen(format); - int buflen = fmtlen + 1; +static const char* sprintf_lite(OpcodeContext& ctx, const char* opcodeName) { + const char* format = ctx.arg(0).strValue(); + int fmtLen = strlen(format); + if (fmtLen == 0) { + return format; + } + if (fmtLen > 1024) { + ctx.printOpcodeError("%s() - format string exceeds maximum length of 1024 characters.", opcodeName); + return "Error"; + } + int newFmtLen = fmtLen; - for (int i = 0; i < fmtlen; i++) { - if (format[i] == '%') buflen++; // will possibly be escaped, need space for that + for (int i = 0; i < fmtLen; i++) { + if (format[i] == '%') newFmtLen++; // will possibly be escaped, need space for that } // parse format to make it safe - char* newfmt = new char[buflen]; - unsigned char mode = 0; - char specifier = 0; - bool hasDigits = false; + char* newFmt = new char[newFmtLen + 1]; + bool conversion = false; int j = 0; + int valIdx = 0; + char* outBuf = ScriptExtender::gTextBuffer; + long bufCount = ScriptExtender::TextBufferSize() - 1; + int numArgs = ctx.numArgs(); - for (int i = 0; i < fmtlen; i++) { + for (int i = 0; i < fmtLen; i++) { char c = format[i]; - switch (mode) { - case 0: // prefix + if (!conversion) { + // Start conversion. + if (c == '%') conversion = true; + } else { if (c == '%') { - mode = 1; - } - break; - case 1: // definition - if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) { - if (c == 'h' || c == 'l' || c == 'j' || c == 'z' || c == 't' || c == 'L') continue; // ignore sub-specifiers - - if (c == 's' && !value.isString() || // don't allow to treat non-string values as string pointers - c == 'n') // don't allow "n" specifier + conversion = false; // escaped % sign, just skip + } else if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) { + // ignore size prefixes + if (c == 'h' || c == 'l' || c == 'j' || c == 'z' || c == 't' || c == 'w' || c == 'L' || c == 'I') continue; + // Type specifier, perform conversion. + if (++valIdx == numArgs) { + ctx.printOpcodeError("%s() - format string contains more conversions than passed arguments (%d): %s", opcodeName, numArgs - 1, format); + } + const ScriptValue& arg = ctx.arg(valIdx < numArgs ? valIdx : numArgs - 1); + if (c == 'S' || c == 'Z') { + c = 's'; // don't allow wide strings + } + if (c == 's' && !arg.isString() || // don't allow treating non-string values as string pointers + c == 'n') // don't allow "n" specifier { c = 'd'; } - specifier = c; - mode = 2; - } else if (c == '%') { - mode = 0; - hasDigits = false; - } else if (c >= '0' && c <= '9') { - hasDigits = true; + newFmt[j++] = c; + newFmt[j] = '\0'; + int partLen = arg.isFloat() + ? _snprintf(outBuf, bufCount, newFmt, arg.floatValue()) + : _snprintf(outBuf, bufCount, newFmt, arg.rawValue()); + outBuf += partLen; + bufCount -= partLen; + conversion = false; + j = 0; + if (bufCount <= 0) { + break; + } + continue; } - break; - case 2: // postfix - if (c == '%') { // don't allow more than one specifier - newfmt[j++] = '%'; // escape it - if (format[i + 1] == '%') i++; // skip already escaped - } - break; } - newfmt[j++] = c; + newFmt[j++] = c; } - newfmt[j] = '\0'; - - // calculate required length - if (hasDigits) { - buflen = 254; - } else if (specifier == 'c') { - buflen = j; - } else if (specifier == 's') { - buflen = j + strlen(value.strValue()); - } else { - buflen = j + 30; // numbers + // Copy the remainder of the string. + if (bufCount > 0) { + newFmt[j] = '\0'; + strcpy_s(outBuf, bufCount, newFmt); } - const long bufMaxLen = ScriptExtender::TextBufferSize() - 1; - if (buflen > bufMaxLen - 1) buflen = bufMaxLen - 1; - ScriptExtender::gTextBuffer[bufMaxLen] = '\0'; - - if (value.isFloat()) { - _snprintf(ScriptExtender::gTextBuffer, buflen, newfmt, value.floatValue()); - } else { - _snprintf(ScriptExtender::gTextBuffer, buflen, newfmt, value.rawValue()); - } - delete[] newfmt; + delete[] newFmt; return ScriptExtender::gTextBuffer; } void op_sprintf(OpcodeContext& ctx) { ctx.setReturn( - sprintf_lite(ctx.arg(0).strValue(), ctx.arg(1)) + sprintf_lite(ctx, ctx.getOpcodeName()) ); } void mf_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, arg = 0, totalArg = ctx.numArgs(); // total passed args - do { - char c = format[i]; - if (c == '%') { - char cf = format[i + 1]; - if (cf != '%') { - if (arg >= 0) { - arg++; - if (arg == totalArg) arg = -1; // format '%' prefixes in the format string exceed the number of passed value args - } - if (arg < 0) { // have % more than passed value args - c = '^'; // delete % - } - // check string is valid or replace unsupported format - else if ((cf == 's' && (arg > 0 && !ctx.arg(arg).isString())) || (cf != 's' && cf != 'd')) { - newFmt[i++] = c; - c = 'd'; // replace with %d - } - } else { - newFmt[i++] = cf; // skip %% - } - } - newFmt[i] = c; - } while (++i < fmtLen); - - const long bufMaxLen = ScriptExtender::TextBufferSize() - 1; - - switch (totalArg) { - 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); - } + ctx.setReturn( + sprintf_lite(ctx, ctx.getMetaruleName()) + ); } void op_message_str_game(OpcodeContext& ctx) {