diff options
author | Colin Snover | 2016-09-04 16:30:47 -0500 |
---|---|---|
committer | Colin Snover | 2016-09-29 19:39:16 -0500 |
commit | 3f9172676526aa04983f148d1262af6ea9fb53ef (patch) | |
tree | e2729dc6ac8a5142147001dfdcc5856919706340 /engines/sci/engine/kstring.cpp | |
parent | 240b0ca3488231e0cfb9c56b1c21ccdd309f0908 (diff) | |
download | scummvm-rg350-3f9172676526aa04983f148d1262af6ea9fb53ef.tar.gz scummvm-rg350-3f9172676526aa04983f148d1262af6ea9fb53ef.tar.bz2 scummvm-rg350-3f9172676526aa04983f148d1262af6ea9fb53ef.zip |
SCI32: Rewrite kArray & kString
This change invalidates earlier SCI32 save games, which separated
arrays and strings in an incompatible manner. Old save games
contain invalid references to a string segment which no longer
exists, and contain incompatible array structures that lack
critical type information.
Diffstat (limited to 'engines/sci/engine/kstring.cpp')
-rw-r--r-- | engines/sci/engine/kstring.cpp | 358 |
1 files changed, 169 insertions, 189 deletions
diff --git a/engines/sci/engine/kstring.cpp b/engines/sci/engine/kstring.cpp index 1c08bf597c..4d96ddc64e 100644 --- a/engines/sci/engine/kstring.cpp +++ b/engines/sci/engine/kstring.cpp @@ -202,11 +202,6 @@ reg_t kReadNumber(EngineState *s, int argc, reg_t *argv) { } -#define ALIGN_NONE 0 -#define ALIGN_RIGHT 1 -#define ALIGN_LEFT -1 -#define ALIGN_CENTER 2 - /* Format(targ_address, textresnr, index_inside_res, ...) ** or ** Format(targ_address, heap_text_addr, ...) @@ -214,6 +209,13 @@ reg_t kReadNumber(EngineState *s, int argc, reg_t *argv) { ** the supplied parameters and writes it to the targ_address. */ reg_t kFormat(EngineState *s, int argc, reg_t *argv) { + enum { + ALIGN_NONE = 0, + ALIGN_RIGHT = 1, + ALIGN_LEFT = -1, + ALIGN_CENTER = 2 + }; + uint16 *arguments; reg_t dest = argv[0]; int maxsize = 4096; /* Arbitrary... */ @@ -301,12 +303,6 @@ reg_t kFormat(EngineState *s, int argc, reg_t *argv) { case 's': { /* Copy string */ reg_t reg = argv[startarg + paramindex]; -#ifdef ENABLE_SCI32 - // If the string is a string object, get to the actual string in the data selector - if (s->_segMan->isObject(reg)) - reg = readSelector(s->_segMan, reg, SELECTOR(data)); -#endif - Common::String tempsource = g_sci->getKernel()->lookupText(reg, arguments[paramindex + 1]); int slen = strlen(tempsource.c_str()); @@ -379,12 +375,6 @@ reg_t kFormat(EngineState *s, int argc, reg_t *argv) { case 'u': unsignedVar = true; case 'd': { /* Copy decimal */ - // In the new SCI2 kString function, %d is used for unsigned - // integers. An example is script 962 in Shivers - it uses %d - // to create file names. - if (getSciVersion() >= SCI_VERSION_2) - unsignedVar = true; - /* int templen; -- unused atm */ const char *format_string = "%d"; @@ -437,14 +427,6 @@ reg_t kFormat(EngineState *s, int argc, reg_t *argv) { *target = 0; /* Terminate string */ -#ifdef ENABLE_SCI32 - // Resize SCI32 strings if necessary - if (getSciVersion() >= SCI_VERSION_2) { - SciString *string = s->_segMan->lookupString(dest); - string->setSize(strlen(targetbuf) + 1); - } -#endif - s->_segMan->strcpy(dest, targetbuf); return dest; /* Return target addr */ @@ -661,238 +643,236 @@ reg_t kStrSplit(EngineState *s, int argc, reg_t *argv) { #ifdef ENABLE_SCI32 -// TODO: there is an unused second argument, happens at least in LSL6 right during the intro +reg_t kString(EngineState *s, int argc, reg_t *argv) { + if (!s) + return make_reg(0, getSciVersion()); + error("not supposed to call this"); +} + reg_t kStringNew(EngineState *s, int argc, reg_t *argv) { reg_t stringHandle; - SciString *string = s->_segMan->allocateString(&stringHandle); - string->setSize(argv[0].toUint16()); - - // Make sure the first character is a null character - if (string->getSize() > 0) - string->setValue(0, 0); - + const uint16 size = argv[0].toUint16(); + s->_segMan->allocateArray(kArrayTypeString, size, &stringHandle); return stringHandle; } -reg_t kStringSize(EngineState *s, int argc, reg_t *argv) { - return make_reg(0, s->_segMan->getString(argv[0]).size()); -} - -// At (return value at an index) -reg_t kStringAt(EngineState *s, int argc, reg_t *argv) { - // Note that values are put in bytes to avoid sign extension - if (argv[0].getSegment() == s->_segMan->getStringSegmentId()) { - SciString *string = s->_segMan->lookupString(argv[0]); - byte val = string->getRawData()[argv[1].toUint16()]; - return make_reg(0, val); - } else { - Common::String string = s->_segMan->getString(argv[0]); - byte val = string[argv[1].toUint16()]; - return make_reg(0, val); - } -} - -// Atput (put value at an index) -reg_t kStringPutAt(EngineState *s, int argc, reg_t *argv) { - SciString *string = s->_segMan->lookupString(argv[0]); +reg_t kStringGetChar(EngineState *s, int argc, reg_t *argv) { + const uint16 index = argv[1].toUint16(); - uint32 index = argv[1].toUint16(); - uint32 count = argc - 2; + // Game scripts may contain static raw string data + if (!s->_segMan->isArray(argv[0])) { + const Common::String string = s->_segMan->getString(argv[0]); + if (index >= string.size()) { + return make_reg(0, 0); + } - if (index + count > 65535) - return NULL_REG; + return make_reg(0, (byte)string[index]); + } - if (string->getSize() < index + count) - string->setSize(index + count); + SciArray &array = *s->_segMan->lookupArray(argv[0]); - for (uint16 i = 0; i < count; i++) - string->setValue(i + index, argv[i + 2].toUint16()); + if (index >= array.size()) { + return make_reg(0, 0); + } - return argv[0]; // We also have to return the handle + return array.getAsID(index); } reg_t kStringFree(EngineState *s, int argc, reg_t *argv) { - // Freeing of strings is handled by the garbage collector + if (!argv[0].isNull()) { + s->_segMan->freeArray(argv[0]); + } return s->r_acc; } -reg_t kStringFill(EngineState *s, int argc, reg_t *argv) { - SciString *string = s->_segMan->lookupString(argv[0]); - uint16 index = argv[1].toUint16(); +reg_t kStringCompare(EngineState *s, int argc, reg_t *argv) { + const Common::String string1 = s->_segMan->getString(argv[0]); + const Common::String string2 = s->_segMan->getString(argv[1]); - // A count of -1 means fill the rest of the array - uint16 count = argv[2].toSint16() == -1 ? string->getSize() - index : argv[2].toUint16(); - uint16 stringSize = string->getSize(); + int result; + if (argc == 3) { + result = strncmp(string1.c_str(), string2.c_str(), argv[2].toUint16()); + } else { + result = strcmp(string1.c_str(), string2.c_str()); + } - if (stringSize < index + count) - string->setSize(index + count); + return make_reg(0, (result > 0) - (result < 0)); +} - for (uint16 i = 0; i < count; i++) - string->setValue(i + index, argv[3].toUint16()); +reg_t kStringGetData(EngineState *s, int argc, reg_t *argv) { + if (s->_segMan->isObject(argv[0])) { + return readSelector(s->_segMan, argv[0], SELECTOR(data)); + } return argv[0]; } -reg_t kStringCopy(EngineState *s, int argc, reg_t *argv) { - const char *string2 = 0; - uint32 string2Size = 0; - Common::String string; +reg_t kStringLength(EngineState *s, int argc, reg_t *argv) { + return make_reg(0, s->_segMan->getString(argv[0]).size()); +} - if (argv[2].getSegment() == s->_segMan->getStringSegmentId()) { - SciString *sstr; - sstr = s->_segMan->lookupString(argv[2]); - string2 = sstr->getRawData(); - string2Size = sstr->getSize(); - } else { - string = s->_segMan->getString(argv[2]); - string2 = string.c_str(); - string2Size = string.size() + 1; +namespace { + bool isFlag(const char c) { + return strchr("-+ 0#", c); } - uint32 index1 = argv[1].toUint16(); - uint32 index2 = argv[3].toUint16(); - - if (argv[0] == argv[2]) { - // source and destination string are one and the same - if (index1 == index2) { - // even same index? ignore this call - // Happens in KQ7, when starting a chapter - return argv[0]; - } - // TODO: this will crash, when setSize() is triggered later - // we need to exactly replicate original interpreter behavior - warning("kString(Copy): source is the same as destination string"); + bool isPrecision(const char c) { + return strchr(".0123456789*", c); } - // The original engine ignores bad copies too - if (index2 >= string2Size) - return NULL_REG; + bool isWidth(const char c) { + return strchr("0123456789*", c); + } - // A count of -1 means fill the rest of the array - uint32 count = string2Size - index2; - if (argv[4].toSint16() != -1) { - count = MIN(count, (uint32)argv[4].toUint16()); + bool isLength(const char c) { + return strchr("hjlLtz", c); } -// reg_t strAddress = argv[0]; - SciString *string1 = s->_segMan->lookupString(argv[0]); - //SciString *string1 = !argv[1].isNull() ? s->_segMan->lookupString(argv[1]) : s->_segMan->allocateString(&strAddress); + bool isType(const char c) { + return strchr("dsuxXaAceEfFgGinop", c); + } - if (string1->getSize() < index1 + count) - string1->setSize(index1 + count); + bool isSignedType(const char type) { + return type == 'd' || type == 'i'; + } - // Note: We're accessing from c_str() here because the - // string's size ignores the trailing 0 and therefore - // triggers an assert when doing string2[i + index2]. - for (uint16 i = 0; i < count; i++) - string1->setValue(i + index1, string2[i + index2]); + bool isUnsignedType(const char type) { + return strchr("uxXoc", type); + } - return argv[0]; -} + bool isStringType(const char type) { + return type == 's'; + } -reg_t kStringCompare(EngineState *s, int argc, reg_t *argv) { - Common::String string1 = argv[0].isNull() ? "" : s->_segMan->getString(argv[0]); - Common::String string2 = argv[1].isNull() ? "" : s->_segMan->getString(argv[1]); + Common::String readPlaceholder(const char *&in, reg_t arg) { + const char *const start = in; - if (argc == 3) // Strncmp - return make_reg(0, strncmp(string1.c_str(), string2.c_str(), argv[2].toUint16())); - else // Strcmp - return make_reg(0, strcmp(string1.c_str(), string2.c_str())); -} + assert(*in == '%'); + ++in; -// was removed for SCI2.1 Late+ -reg_t kStringDup(EngineState *s, int argc, reg_t *argv) { - reg_t stringHandle; - - SciString *dupString = s->_segMan->allocateString(&stringHandle); + while (isFlag(*in)) { + ++in; + } + while (isWidth(*in)) { + ++in; + } + while (isPrecision(*in)) { + ++in; + } + while (isLength(*in)) { + ++in; + } - if (argv[0].getSegment() == s->_segMan->getStringSegmentId()) { - *dupString = *s->_segMan->lookupString(argv[0]); - } else { - dupString->fromString(s->_segMan->getString(argv[0])); + char format[64]; + format[0] = '\0'; + const char type = *in++; + Common::strlcpy(format, start, MIN<size_t>(64, in - start + 1)); + + if (isType(type)) { + if (isSignedType(type)) { + const int value = arg.toSint16(); + return Common::String::format(format, value); + } else if (isUnsignedType(type)) { + const uint value = arg.toUint16(); + return Common::String::format(format, value); + } else if (isStringType(type)) { + Common::String value; + SegManager *segMan = g_sci->getEngineState()->_segMan; + if (segMan->isObject(arg)) { + value = segMan->getString(readSelector(segMan, arg, SELECTOR(data))); + } else { + value = segMan->getString(arg); + } + return Common::String::format(format, value.c_str()); + } else { + error("Unsupported format type %c", type); + } + } else { + return Common::String::format("%s", format); + } } +}; - return stringHandle; -} - -// was removed for SCI2.1 Late+ -reg_t kStringGetData(EngineState *s, int argc, reg_t *argv) { - if (!s->_segMan->isHeapObject(argv[0])) - return argv[0]; +Common::String format(const Common::String &source, int argc, const reg_t *argv) { + Common::String out; + const char *in = source.c_str(); + int argIndex = 0; + while (*in != '\0') { + if (*in == '%') { + if (in[1] == '%') { + in += 2; + out += "%"; + continue; + } - return readSelector(s->_segMan, argv[0], SELECTOR(data)); -} + assert(argIndex < argc); + out += readPlaceholder(in, argv[argIndex++]); + } else { + out += *in++; + } + } -reg_t kStringLen(EngineState *s, int argc, reg_t *argv) { - return make_reg(0, s->_segMan->strlen(argv[0])); + return out; } -reg_t kStringPrintf(EngineState *s, int argc, reg_t *argv) { +reg_t kStringFormat(EngineState *s, int argc, reg_t *argv) { reg_t stringHandle; - s->_segMan->allocateString(&stringHandle); - - reg_t *adjustedArgs = new reg_t[argc + 1]; - adjustedArgs[0] = stringHandle; - memcpy(&adjustedArgs[1], argv, argc * sizeof(reg_t)); - - kFormat(s, argc + 1, adjustedArgs); - delete[] adjustedArgs; + SciArray &target = *s->_segMan->allocateArray(kArrayTypeString, 0, &stringHandle); + reg_t source = argv[0]; + // Str objects may be passed in place of direct references to string data + if (s->_segMan->isObject(argv[0])) { + source = readSelector(s->_segMan, argv[0], SELECTOR(data)); + } + target.fromString(format(s->_segMan->getString(source), argc - 1, argv + 1)); return stringHandle; } -reg_t kStringPrintfBuf(EngineState *s, int argc, reg_t *argv) { - return kFormat(s, argc, argv); +reg_t kStringFormatAt(EngineState *s, int argc, reg_t *argv) { + SciArray &target = *s->_segMan->lookupArray(argv[0]); + reg_t source = argv[1]; + // Str objects may be passed in place of direct references to string data + if (s->_segMan->isObject(argv[1])) { + source = readSelector(s->_segMan, argv[1], SELECTOR(data)); + } + target.fromString(format(s->_segMan->getString(source), argc - 2, argv + 2)); + return argv[0]; } -reg_t kStringAtoi(EngineState *s, int argc, reg_t *argv) { - Common::String string = s->_segMan->getString(argv[0]); - return make_reg(0, (uint16)atoi(string.c_str())); +reg_t kStringToInteger(EngineState *s, int argc, reg_t *argv) { + return make_reg(0, (uint16)s->_segMan->getString(argv[0]).asUint64()); } reg_t kStringTrim(EngineState *s, int argc, reg_t *argv) { - Common::String string = s->_segMan->getString(argv[0]); - - string.trim(); - // TODO: Second parameter (bitfield, trim from left, right, center) - warning("kStringTrim (%d)", argv[1].getOffset()); - s->_segMan->strcpy(argv[0], string.c_str()); - return NULL_REG; + SciArray &array = *s->_segMan->lookupArray(argv[0]); + const int8 flags = argv[1].toSint16(); + const char showChar = argc > 2 ? argv[2].toSint16() : '\0'; + array.trim(flags, showChar); + return s->r_acc; } -reg_t kStringUpper(EngineState *s, int argc, reg_t *argv) { +reg_t kStringToUpperCase(EngineState *s, int argc, reg_t *argv) { Common::String string = s->_segMan->getString(argv[0]); - string.toUppercase(); s->_segMan->strcpy(argv[0], string.c_str()); - return NULL_REG; + return argv[0]; } -reg_t kStringLower(EngineState *s, int argc, reg_t *argv) { +reg_t kStringToLowerCase(EngineState *s, int argc, reg_t *argv) { Common::String string = s->_segMan->getString(argv[0]); - string.toLowercase(); s->_segMan->strcpy(argv[0], string.c_str()); - return NULL_REG; -} - -// Possibly kStringTranslate? -reg_t kStringTrn(EngineState *s, int argc, reg_t *argv) { - warning("kStringTrn (argc = %d)", argc); - return NULL_REG; + return argv[0]; } -// Possibly kStringTranslateExclude? -reg_t kStringTrnExclude(EngineState *s, int argc, reg_t *argv) { - warning("kStringTrnExclude (argc = %d)", argc); - return NULL_REG; +reg_t kStringReplaceSubstring(EngineState *s, int argc, reg_t *argv) { + error("TODO: kStringReplaceSubstring not implemented"); + return argv[3]; } -reg_t kString(EngineState *s, int argc, reg_t *argv) { - if (!s) - return make_reg(0, getSciVersion()); - error("not supposed to call this"); +reg_t kStringReplaceSubstringEx(EngineState *s, int argc, reg_t *argv) { + error("TODO: kStringReplaceSubstringEx not implemented"); + return argv[3]; } - #endif } // End of namespace Sci |