1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246
|
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "ags/shared/ac/game_version.h"
#include "ags/shared/script/cc_common.h"
#include "ags/engine/script/runtime_script_value.h"
#include "ags/engine/script/script_api.h"
#include "ags/shared/util/math.h"
#include "ags/shared/util/utf8.h"
#include "ags/globals.h"
namespace AGS3 {
using namespace AGS::Shared;
enum FormatParseResult {
kFormatParseNone,
kFormatParseInvalid,
kFormatParseLiteralPercent,
kFormatParseArgInteger,
kFormatParseArgFloat,
kFormatParseArgCharacter,
kFormatParseArgString,
kFormatParseArgPointer,
kFormatParseArgFirst = kFormatParseArgInteger,
kFormatParseArgLast = kFormatParseArgPointer
};
// Helper functions for getting parameter value either from script val array or va_list
inline int GetArgInt(const RuntimeScriptValue *sc_args, va_list *varg_ptr, int arg_idx) {
if (varg_ptr)
return va_arg(*varg_ptr, int);
else
return sc_args[arg_idx].IValue;
}
inline float GetArgFloat(const RuntimeScriptValue *sc_args, va_list *varg_ptr, int arg_idx) {
// note that script variables store only floats, but va_list has floats promoted to double
if (varg_ptr)
return (float)va_arg(*varg_ptr, double);
else
return sc_args[arg_idx].FValue;
}
inline const char *GetArgPtr(const RuntimeScriptValue *sc_args, va_list *varg_ptr, int arg_idx) {
if (varg_ptr)
return va_arg(*varg_ptr, const char *);
else
return reinterpret_cast<const char *>(sc_args[arg_idx].Ptr);
}
// TODO: this implementation can be further optimised by either not calling
// snprintf but formatting values ourselves, or by using some library method
// that supports customizing, such as getting arguments in a custom way.
const char *ScriptSprintf(char *buffer, size_t buf_length, const char *format,
const RuntimeScriptValue *sc_args, int32_t sc_argc, va_list *varg_ptr) {
if (!buffer || buf_length == 0) {
cc_error("Internal error in ScriptSprintf: buffer is null");
return "";
}
if (!format) {// NOTE: interpreter (usually) catches null-pointer sent as format at some stage earlier
cc_error("Internal error in ScriptSprintf: format string is null");
return "";
}
if (!varg_ptr && sc_argc > 0 && !sc_args) {
cc_error("Internal error in ScriptSprintf: args pointer is null");
return "";
}
// Expected format character count:
// percent sign: 1
// flag: 1
// field width 10 (an uint32 number)
// precision sign 1
// precision 10 (an uint32 number)
// length modifier 2
// type 1
// NOTE: although width and precision will
// not likely be defined by a 10-digit
// number, such case is theoretically valid.
const size_t fmtbuf_size = 27;
char fmtbuf[fmtbuf_size];
char *fmt_bufptr;
char *fmt_bufendptr = &fmtbuf[fmtbuf_size - 1];
char *out_ptr = buffer;
// save 1 character for null terminator
const char *out_endptr = buffer + buf_length - 1;
const char *fmt_ptr = format;
int32_t arg_idx = 0;
ptrdiff_t avail_outbuf;
int snprintf_res;
FormatParseResult fmt_done;
// Parse the format string, looking for argument placeholders
while (*fmt_ptr && out_ptr != out_endptr) {
// Try to put argument into placeholder
if (*fmt_ptr == '%') {
avail_outbuf = out_endptr - out_ptr;
fmt_bufptr = fmtbuf;
*(fmt_bufptr++) = '%';
snprintf_res = 0;
fmt_done = kFormatParseNone;
// Parse placeholder
while (*(++fmt_ptr) && fmt_done == kFormatParseNone && fmt_bufptr != fmt_bufendptr) {
*(fmt_bufptr++) = *fmt_ptr;
switch (*fmt_ptr) {
case 'd':
case 'i':
case 'o':
case 'u':
case 'x':
case 'X':
fmt_done = kFormatParseArgInteger;
break;
case 'c':
fmt_done = kFormatParseArgCharacter;
break;
case 'e':
case 'E':
case 'f':
case 'F':
case 'g':
case 'G':
case 'a':
case 'A':
fmt_done = kFormatParseArgFloat;
break;
case 'p':
fmt_done = kFormatParseArgPointer;
break;
case 's':
fmt_done = kFormatParseArgString;
break;
case '%':
// This may be a literal percent sign ('%%')
if (fmt_bufptr - fmtbuf == 2) {
fmt_done = kFormatParseLiteralPercent;
}
// ...Otherwise we reached the next placeholder
else {
fmt_ptr--;
fmt_bufptr--;
fmt_done = kFormatParseInvalid;
}
break;
}
}
// Deal with the placeholder parsing results
if (fmt_done == kFormatParseLiteralPercent) {
// literal percent sign
*(out_ptr++) = '%';
continue;
} else if (fmt_done >= kFormatParseArgFirst && fmt_done <= kFormatParseArgLast &&
(varg_ptr || arg_idx < sc_argc)) {
// Print the actual value
// NOTE: snprintf is called with avail_outbuf + 1 here, because we let it use our reserved
// character for null-terminator, in case we are at the end of the buffer
*fmt_bufptr = 0; // terminate the format buffer, we are going to use it
switch (fmt_done) {
case kFormatParseArgInteger:
snprintf_res = snprintf(out_ptr, avail_outbuf + 1, fmtbuf, GetArgInt(sc_args, varg_ptr, arg_idx)); break;
case kFormatParseArgFloat:
snprintf_res = snprintf(out_ptr, avail_outbuf + 1, fmtbuf, GetArgFloat(sc_args, varg_ptr, arg_idx)); break;
case kFormatParseArgCharacter:
{
int chr = GetArgInt(sc_args, varg_ptr, arg_idx);
char cbuf[5]{};
usetc(cbuf, chr);
snprintf_res = snprintf(out_ptr, avail_outbuf + 1, "%s", cbuf);
break;
}
case kFormatParseArgString:
{
const char *p = GetArgPtr(sc_args, varg_ptr, arg_idx);
// Do extra checks for %s placeholder
if (fmt_done == kFormatParseArgString && !p) {
if (_G(loaded_game_file_version) < kGameVersion_320) {
// explicitly put "(null)" into the placeholder
p = "(null)";
} else {
cc_error("!ScriptSprintf: formatting argument %d is expected to be a string, but it is a null pointer", arg_idx + 1);
return "";
}
} else if (fmt_done == kFormatParseArgString && p == buffer) {
cc_error("!ScriptSprintf: formatting argument %d is a pointer to output buffer", arg_idx + 1);
return "";
}
snprintf_res = snprintf(out_ptr, avail_outbuf + 1, fmtbuf, p);
break;
}
case kFormatParseArgPointer:
snprintf_res = snprintf(out_ptr, avail_outbuf + 1, fmtbuf, GetArgPtr(sc_args, varg_ptr, arg_idx)); break;
default: /* should not happen */ break;
}
arg_idx++;
if (snprintf_res >= 0) {
// snprintf returns maximal number of characters, so limit it with buffer size
out_ptr += MIN<ptrdiff_t>(snprintf_res, avail_outbuf);
continue;
}
// -- pass further to invalid format case
}
// If format was not valid, or there are no available
// parameters, just copy stored format buffer as it is
size_t copy_len = MIN(MIN<ptrdiff_t>(fmt_bufptr - fmtbuf, fmtbuf_size - 1), avail_outbuf);
memcpy(out_ptr, fmtbuf, copy_len);
out_ptr += copy_len;
}
// If there's no placeholder, simply copy the character to output buffer
else {
*(out_ptr++) = *(fmt_ptr++);
}
}
// Terminate the string
*out_ptr = 0;
return buffer;
}
} // namespace AGS3
|