/* 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 2 * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * $URL$ * $Id$ * */ /* String and parser handling */ #include "sci/resource.h" #include "sci/engine/state.h" #include "sci/engine/message.h" #include "sci/engine/selector.h" #include "sci/engine/kernel.h" namespace Sci { reg_t kStrEnd(EngineState *s, int argc, reg_t *argv) { reg_t address = argv[0]; address.offset += s->_segMan->strlen(address); return address; } reg_t kStrCat(EngineState *s, int argc, reg_t *argv) { Common::String s1 = s->_segMan->getString(argv[0]); Common::String s2 = s->_segMan->getString(argv[1]); s1 += s2; s->_segMan->strcpy(argv[0], s1.c_str()); return argv[0]; } reg_t kStrCmp(EngineState *s, int argc, reg_t *argv) { Common::String s1 = s->_segMan->getString(argv[0]); Common::String s2 = s->_segMan->getString(argv[1]); if (argc > 2) return make_reg(0, strncmp(s1.c_str(), s2.c_str(), argv[2].toUint16())); else return make_reg(0, strcmp(s1.c_str(), s2.c_str())); } reg_t kStrCpy(EngineState *s, int argc, reg_t *argv) { if (argc > 2) { int length = argv[2].toSint16(); if (length >= 0) s->_segMan->strncpy(argv[0], argv[1], length); else s->_segMan->memcpy(argv[0], argv[1], -length); } else s->_segMan->strcpy(argv[0], argv[1]); return argv[0]; } reg_t kStrAt(EngineState *s, int argc, reg_t *argv) { if (argv[0] == SIGNAL_REG) { warning("Attempt to perform kStrAt() on a signal reg"); return NULL_REG; } SegmentRef dest_r = s->_segMan->dereference(argv[0]); if (!dest_r.isValid()) { warning("Attempt to StrAt at invalid pointer %04x:%04x", PRINT_REG(argv[0])); return NULL_REG; } byte value; byte newvalue = 0; unsigned int offset = argv[1].toUint16(); if (argc > 2) newvalue = argv[2].toSint16(); // in kq5 this here gets called with offset 0xFFFF // (in the desert wheng getting the staff) if ((int)offset >= dest_r.maxSize) { warning("kStrAt offset %X exceeds maxSize", offset); return s->r_acc; } // FIXME: Move this to segman if (dest_r.isRaw) { value = dest_r.raw[offset]; if (argc > 2) /* Request to modify this char */ dest_r.raw[offset] = newvalue; } else { if (dest_r.skipByte) offset++; reg_t &tmp = dest_r.reg[offset / 2]; if (!(offset & 1)) { value = tmp.offset & 0x00ff; if (argc > 2) { /* Request to modify this char */ tmp.offset &= 0xff00; tmp.offset |= newvalue; tmp.segment = 0; } } else { value = tmp.offset >> 8; if (argc > 2) { /* Request to modify this char */ tmp.offset &= 0x00ff; tmp.offset |= newvalue << 8; tmp.segment = 0; } } } s->r_acc = make_reg(0, value); return s->r_acc; } reg_t kReadNumber(EngineState *s, int argc, reg_t *argv) { Common::String source_str = s->_segMan->getString(argv[0]); const char *source = source_str.c_str(); while (isspace((unsigned char)*source)) source++; /* Skip whitespace */ int16 result = 0; if (*source == '$') { // Hexadecimal input result = (int16)strtol(source + 1, NULL, 16); } else { // Decimal input. We can not use strtol/atoi in here, because while // Sierra used atoi, it was a non standard compliant atoi, that didn't // do clipping. In SQ4 we get the door code in here and that's even // larger than uint32! if (*source == '-') { result = -1; source++; } while (*source) { if ((*source < '0') || (*source > '9')) { // Sierra's atoi stopped processing at anything which is not // a digit. Sometimes the input has a trailing space, that's // fine (example: lsl3) if (*source != ' ') { // TODO: this happens in lsl5 right in the intro -> we get '1' '3' 0xCD 0xCD 0xCD 0xCD 0xCD // find out why this happens and fix it warning("Invalid character in kReadNumber input"); } break; } result *= 10; result += *source - 0x30; source++; } } return make_reg(0, result); } #define ALIGN_NONE 0 #define ALIGN_RIGHT 1 #define ALIGN_LEFT -1 #define ALIGN_CENTRE 2 /* Format(targ_address, textresnr, index_inside_res, ...) ** or ** Format(targ_address, heap_text_addr, ...) ** Formats the text from text.textresnr (offset index_inside_res) or heap_text_addr according to ** the supplied parameters and writes it to the targ_address. */ reg_t kFormat(EngineState *s, int argc, reg_t *argv) { uint16 *arguments; reg_t dest = argv[0]; int maxsize = 4096; /* Arbitrary... */ char targetbuf[4096]; char *target = targetbuf; reg_t position = argv[1]; /* source */ int index = argv[2].toUint16(); int mode = 0; int paramindex = 0; /* Next parameter to evaluate */ char xfer; int i; int startarg; int str_leng = 0; /* Used for stuff like "%13s" */ int unsigned_var = 0; if (position.segment) startarg = 2; else startarg = 3; /* First parameter to use for formatting */ Common::String source_str = g_sci->getKernel()->lookupText(position, index); const char* source = source_str.c_str(); debugC(2, kDebugLevelStrings, "Formatting \"%s\"", source); arguments = (uint16 *)malloc(sizeof(uint16) * argc); memset(arguments, 0, sizeof(uint16) * argc); for (i = startarg; i < argc; i++) arguments[i-startarg] = argv[i].toUint16(); /* Parameters are copied to prevent overwriting */ while ((xfer = *source++)) { if (xfer == '%') { if (mode == 1) { assert((target - targetbuf) + 2 <= maxsize); *target++ = '%'; /* Literal % by using "%%" */ mode = 0; } else { mode = 1; str_leng = 0; } } else if (mode == 1) { /* xfer != '%' */ char fillchar = ' '; int align = ALIGN_NONE; char *writestart = target; /* Start of the written string, used after the switch */ /* int writelength; -- unused atm */ if (xfer && (isdigit(xfer) || xfer == '-' || xfer == '=')) { char *destp; if (xfer == '0') fillchar = '0'; else if (xfer == '=') align = ALIGN_CENTRE; else if (isdigit(xfer) || (xfer == '-')) source--; // Go to start of length argument str_leng = strtol(source, &destp, 10); if (destp > source) source = destp; if (str_leng < 0) { align = ALIGN_LEFT; str_leng = -str_leng; } else if (align != ALIGN_CENTRE) align = ALIGN_RIGHT; xfer = *source++; } else str_leng = 0; assert((target - targetbuf) + str_leng + 1 <= maxsize); switch (xfer) { 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 = (reg == NULL_REG) ? "" : g_sci->getKernel()->lookupText(reg, arguments[paramindex + 1]); int slen = strlen(tempsource.c_str()); int extralen = str_leng - slen; assert((target - targetbuf) + extralen <= maxsize); if (extralen < 0) extralen = 0; if (reg.segment) /* Heap address? */ paramindex++; else paramindex += 2; /* No, text resource address */ switch (align) { case ALIGN_NONE: case ALIGN_RIGHT: while (extralen-- > 0) *target++ = ' '; /* Format into the text */ break; case ALIGN_CENTRE: { int half_extralen = extralen >> 1; while (half_extralen-- > 0) *target++ = ' '; /* Format into the text */ break; } default: break; } strcpy(target, tempsource.c_str()); target += slen; switch (align) { case ALIGN_CENTRE: { int half_extralen; align = 0; half_extralen = extralen - (extralen >> 1); while (half_extralen-- > 0) *target++ = ' '; /* Format into the text */ break; } default: break; } mode = 0; } break; case 'c': { /* insert character */ assert((target - targetbuf) + 2 <= maxsize); if (align >= 0) while (str_leng-- > 1) *target++ = ' '; /* Format into the text */ *target++ = arguments[paramindex++]; mode = 0; } break; case 'x': case 'u': unsigned_var = 1; case 'd': { /* Copy decimal */ /* int templen; -- unused atm */ const char *format_string = "%d"; if (xfer == 'x') format_string = "%x"; int val = arguments[paramindex]; if (!unsigned_var) val = (int16)arguments[paramindex]; target += sprintf(target, format_string, val); paramindex++; assert((target - targetbuf) <= maxsize); unsigned_var = 0; mode = 0; } break; default: *target = '%'; target++; *target = xfer; target++; mode = 0; } if (align) { int written = target - writestart; int padding = str_leng - written; if (padding > 0) { if (align > 0) { memmove(writestart + padding, writestart, written); memset(writestart, fillchar, padding); } else { memset(target, ' ', padding); } target += padding; } } } else { /* mode != 1 */ *target = xfer; target++; } } free(arguments); *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 */ } reg_t kStrLen(EngineState *s, int argc, reg_t *argv) { return make_reg(0, s->_segMan->strlen(argv[0])); } reg_t kGetFarText(EngineState *s, int argc, reg_t *argv) { Resource *textres = g_sci->getResMan()->findResource(ResourceId(kResourceTypeText, argv[0].toUint16()), 0); char *seeker; int counter = argv[1].toUint16(); if (!textres) { error("text.%d does not exist", argv[0].toUint16()); return NULL_REG; } seeker = (char *)textres->data; // The second parameter (counter) determines the number of the string // inside the text resource. while (counter--) { while (*seeker++) ; } // If the third argument is NULL, allocate memory for the destination. This // occurs in SCI1 Mac games. The memory will later be freed by the game's // scripts. if (argv[2] == NULL_REG) s->_segMan->allocDynmem(strlen(seeker) + 1, "Mac FarText", &argv[2]); s->_segMan->strcpy(argv[2], seeker); // Copy the string and get return value return argv[2]; } #define DUMMY_MESSAGE "Message not found!" enum kMessageFunc { K_MESSAGE_GET, K_MESSAGE_NEXT, K_MESSAGE_SIZE, K_MESSAGE_REFCOND, K_MESSAGE_REFVERB, K_MESSAGE_REFNOUN, K_MESSAGE_PUSH, K_MESSAGE_POP, K_MESSAGE_LASTMESSAGE, K_MESSAGE_ECOQUEST1_DEMO = 99 }; reg_t kGetMessage(EngineState *s, int argc, reg_t *argv) { MessageTuple tuple = MessageTuple(argv[0].toUint16(), argv[2].toUint16()); s->_msgState->getMessage(argv[1].toUint16(), tuple, argv[3]); return argv[3]; } reg_t kMessage(EngineState *s, int argc, reg_t *argv) { uint func = argv[0].toUint16(); #ifdef ENABLE_SCI32 if (getSciVersion() >= SCI_VERSION_2) { // In complete weirdness, SCI32 bumps up subops 3-8 to 4-9 and stubs off subop 3. // In addition, SCI32 reorders the REF* subops. if (func == 3) error("SCI32 kMessage(3)"); else if (func > 3) { func--; if (func == K_MESSAGE_REFCOND) func = K_MESSAGE_REFNOUN; else if (func == K_MESSAGE_REFNOUN || func == K_MESSAGE_REFVERB) func--; } } #endif if ((func != K_MESSAGE_NEXT) && (argc < 2)) { warning("Message: not enough arguments passed to subfunction %d", func); return NULL_REG; } MessageTuple tuple; if (argc >= 6) tuple = MessageTuple(argv[2].toUint16(), argv[3].toUint16(), argv[4].toUint16(), argv[5].toUint16()); switch (func) { case K_MESSAGE_GET: return make_reg(0, s->_msgState->getMessage(argv[1].toUint16(), tuple, (argc == 7 ? argv[6] : NULL_REG))); case K_MESSAGE_NEXT: return make_reg(0, s->_msgState->nextMessage((argc == 2 ? argv[1] : NULL_REG))); case K_MESSAGE_SIZE: return make_reg(0, s->_msgState->messageSize(argv[1].toUint16(), tuple)); case K_MESSAGE_REFCOND: case K_MESSAGE_REFVERB: case K_MESSAGE_REFNOUN: { MessageTuple t; if (s->_msgState->messageRef(argv[1].toUint16(), tuple, t)) { switch (func) { case K_MESSAGE_REFCOND: return make_reg(0, t.cond); case K_MESSAGE_REFVERB: return make_reg(0, t.verb); case K_MESSAGE_REFNOUN: return make_reg(0, t.noun); } } return SIGNAL_REG; } case K_MESSAGE_LASTMESSAGE: { MessageTuple msg; int module; s->_msgState->lastQuery(module, msg); bool ok = false; if (s->_segMan->dereference(argv[1]).isRaw) { byte *buffer = s->_segMan->derefBulkPtr(argv[1], 10); if (buffer) { ok = true; WRITE_LE_UINT16(buffer, module); WRITE_LE_UINT16(buffer + 2, msg.noun); WRITE_LE_UINT16(buffer + 4, msg.verb); WRITE_LE_UINT16(buffer + 6, msg.cond); WRITE_LE_UINT16(buffer + 8, msg.seq); } } else { reg_t *buffer = s->_segMan->derefRegPtr(argv[1], 5); if (buffer) { ok = true; buffer[0] = make_reg(0, module); buffer[1] = make_reg(0, msg.noun); buffer[2] = make_reg(0, msg.verb); buffer[3] = make_reg(0, msg.cond); buffer[4] = make_reg(0, msg.seq); } } if (!ok) warning("Message: buffer %04x:%04x invalid or too small to hold the tuple", PRINT_REG(argv[1])); return NULL_REG; } case K_MESSAGE_ECOQUEST1_DEMO: // The EcoQuest 1 demo uses a special version of kMessage. It's one of the // earliest SCI 1.1 games. The noun is always 99 in this case, so we can // treat it as a subop. If any other games require this syntax, we can change // this to work with those games too. if (g_sci->getGameId() != GID_ECOQUEST || !g_sci->isDemo()) error("kMessage called with EcoQuest 1 demo syntax in a different game"); tuple.noun = argv[0].toUint16(); tuple.verb = argv[2].toUint16(); return make_reg(0, s->_msgState->getMessage(argv[1].toUint16(), tuple, argv[3])); default: warning("Message: subfunction %i invoked (not implemented)", func); } return NULL_REG; } reg_t kSetQuitStr(EngineState *s, int argc, reg_t *argv) { Common::String quitStr = s->_segMan->getString(argv[0]); //debug("Setting quit string to '%s'", quitStr.c_str()); return s->r_acc; } reg_t kStrSplit(EngineState *s, int argc, reg_t *argv) { Common::String format = s->_segMan->getString(argv[1]); Common::String sep_str; const char *sep = NULL; if (!argv[2].isNull()) { sep_str = s->_segMan->getString(argv[2]); sep = sep_str.c_str(); } Common::String str = g_sci->strSplit(format.c_str(), sep); // Make sure target buffer is large enough SegmentRef buf_r = s->_segMan->dereference(argv[0]); if (!buf_r.isValid() || buf_r.maxSize < (int)str.size() + 1) { warning("StrSplit: buffer %04x:%04x invalid or too small to hold the following text of %i bytes: '%s'", PRINT_REG(argv[0]), str.size() + 1, str.c_str()); return NULL_REG; } s->_segMan->strcpy(argv[0], str.c_str()); return argv[0]; } #ifdef ENABLE_SCI32 reg_t kText(EngineState *s, int argc, reg_t *argv) { switch (argv[0].toUint16()) { case 0: return kTextSize(s, argc - 1, argv + 1); default: // TODO: Other subops here too, perhaps kTextColors and kTextFonts warning("kText(%d)", argv[0].toUint16()); break; } return s->r_acc; } reg_t kString(EngineState *s, int argc, reg_t *argv) { switch (argv[0].toUint16()) { case 0: { // New reg_t stringHandle; SciString *string = s->_segMan->allocateString(&stringHandle); string->setSize(argv[1].toUint16()); // Make sure the first character is a null character if (string->getSize() > 0) string->setValue(0, 0); return stringHandle; } case 1: // Size return make_reg(0, s->_segMan->getString(argv[1]).size()); case 2: { // At (return value at an index) if (argv[1].segment == s->_segMan->getStringSegmentId()) return make_reg(0, s->_segMan->lookupString(argv[1])->getRawData()[argv[2].toUint16()]); return make_reg(0, s->_segMan->getString(argv[1])[argv[2].toUint16()]); } case 3: { // Atput (put value at an index) SciString *string = s->_segMan->lookupString(argv[1]); uint32 index = argv[2].toUint16(); uint32 count = argc - 3; if (index + count > 65535) break; if (string->getSize() < index + count) string->setSize(index + count); for (uint16 i = 0; i < count; i++) string->setValue(i + index, argv[i + 3].toUint16()); return argv[1]; // We also have to return the handle } case 4: // Free // Freeing of strings is handled by the garbage collector return s->r_acc; case 5: { // Fill SciString *string = s->_segMan->lookupString(argv[1]); uint16 index = argv[2].toUint16(); // A count of -1 means fill the rest of the array uint16 count = argv[3].toSint16() == -1 ? string->getSize() - index : argv[3].toUint16(); uint16 stringSize = string->getSize(); if (stringSize < index + count) string->setSize(index + count); for (uint16 i = 0; i < count; i++) string->setValue(i + index, argv[4].toUint16()); return argv[1]; } case 6: { // Cpy const char *string2 = 0; uint32 string2Size = 0; if (argv[3].segment == s->_segMan->getStringSegmentId()) { SciString *string = s->_segMan->lookupString(argv[3]); string2 = string->getRawData(); string2Size = string->getSize(); } else { Common::String string = s->_segMan->getString(argv[3]); string2 = string.c_str(); string2Size = string.size() + 1; } uint32 index1 = argv[2].toUint16(); uint32 index2 = argv[4].toUint16(); // The original engine ignores bad copies too if (index2 > string2Size) break; // A count of -1 means fill the rest of the array uint32 count = argv[5].toSint16() == -1 ? string2Size - index2 + 1 : argv[5].toUint16(); // We have a special case here for argv[1] being a system string if (argv[1].segment == s->_segMan->getSysStringsSegment()) { // Resize if necessary const uint16 sysStringId = argv[1].toUint16(); SystemString *sysString = s->_segMan->getSystemString(sysStringId); assert(sysString); if ((uint32)sysString->_maxSize < index1 + count) { free(sysString->_value); sysString->_maxSize = index1 + count; sysString->_value = (char *)calloc(index1 + count, sizeof(char)); } strncpy(sysString->_value + index1, string2 + index2, count); } else { SciString *string1 = s->_segMan->lookupString(argv[1]); if (string1->getSize() < index1 + count) string1->setSize(index1 + count); // 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]); } } return argv[1]; case 7: { // Cmp Common::String string1 = argv[1].isNull() ? "" : s->_segMan->getString(argv[1]); Common::String string2 = argv[2].isNull() ? "" : s->_segMan->getString(argv[2]); if (argc == 4) // Strncmp return make_reg(0, strncmp(string1.c_str(), string2.c_str(), argv[3].toUint16())); else // Strcmp return make_reg(0, strcmp(string1.c_str(), string2.c_str())); } case 8: { // Dup const char *rawString = 0; uint32 size = 0; if (argv[1].segment == s->_segMan->getStringSegmentId()) { SciString *string = s->_segMan->lookupString(argv[1]); rawString = string->getRawData(); size = string->getSize(); } else { Common::String string = s->_segMan->getString(argv[1]); rawString = string.c_str(); size = string.size() + 1; } reg_t stringHandle; SciString *dupString = s->_segMan->allocateString(&stringHandle); dupString->setSize(size); for (uint32 i = 0; i < size; i++) dupString->setValue(i, rawString[i]); return stringHandle; } case 9: // Getdata if (!s->_segMan->isHeapObject(argv[1])) return argv[1]; return readSelector(s->_segMan, argv[1], SELECTOR(data)); case 10: // Stringlen return make_reg(0, s->_segMan->strlen(argv[1])); case 11: { // Printf reg_t stringHandle; s->_segMan->allocateString(&stringHandle); reg_t *adjustedArgs = new reg_t[argc]; adjustedArgs[0] = stringHandle; memcpy(&adjustedArgs[1], argv + 1, (argc - 1) * sizeof(reg_t)); kFormat(s, argc, adjustedArgs); delete[] adjustedArgs; return stringHandle; } case 12: // Printf Buf return kFormat(s, argc - 1, argv + 1); case 13: { // atoi Common::String string = s->_segMan->getString(argv[1]); return make_reg(0, (uint16)atoi(string.c_str())); } default: error("Unknown kString subop %d", argv[0].toUint16()); } return NULL_REG; } #endif } // End of namespace Sci