scummvm/engines/sci/engine/kstring.cpp

820 lines
20 KiB
C++
Raw Normal View History

2009-02-17 15:02:16 +00:00
/* 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/kernel.h"
namespace Sci {
#define CHECK_OVERFLOW1(pt, size, rv) \
if (((pt) - (str_base)) + (size) > maxsize) { \
error("String expansion exceeded heap boundaries"); \
return rv;\
}
/* Returns the string the script intended to address */
char *kernel_lookup_text(EngineState *s, reg_t address, int index) {
char *seeker;
Resource *textres;
if (address.segment)
return (char *)kernel_dereference_bulk_pointer(s, address, 0);
else {
int textlen;
int _index = index;
textres = s->resmgr->findResource(kResourceTypeText, address.offset, 0);
if (!textres) {
error("text.%03d not found", address.offset);
return NULL; /* Will probably segfault */
}
textlen = textres->size;
seeker = (char *) textres->data;
while (index--)
while ((textlen--) && (*seeker++))
;
if (textlen)
return seeker;
else {
error("Index %d out of bounds in text.%03d", _index, address.offset);
return 0;
}
}
}
/*************************************************************/
/* Parser */
/**********/
reg_t kSaid(EngineState *s, int funct_nr, int argc, reg_t *argv) {
reg_t heap_said_block = argv[0];
byte *said_block;
int new_lastmatch;
if (!heap_said_block.segment)
return NULL_REG;
said_block = (byte *) kernel_dereference_bulk_pointer(s, heap_said_block, 0);
if (!said_block) {
warning("Said on non-string, pointer %04x:%04x", PRINT_REG(heap_said_block));
return NULL_REG;
}
#ifdef DEBUG_PARSER
debugC(2, kDebugLevelParser, "Said block:", 0);
s->_vocabulary->decipherSaidBlock(said_block);
#endif
if (s->parser_event.isNull() || (GET_SEL32V(s->parser_event, claimed))) {
return NULL_REG;
}
new_lastmatch = said(s, said_block,
#ifdef DEBUG_PARSER
1
#else
0
#endif
);
if (new_lastmatch != SAID_NO_MATCH) { /* Build and possibly display a parse tree */
#ifdef DEBUG_PARSER
sciprintf("Match.\n");
#endif
s->r_acc = make_reg(0, 1);
if (new_lastmatch != SAID_PARTIAL_MATCH)
PUT_SEL32V(s->parser_event, claimed, 1);
} else {
return NULL_REG;
}
return s->r_acc;
}
reg_t kSetSynonyms(EngineState *s, int funct_nr, int argc, reg_t *argv) {
reg_t object = argv[0];
List *list;
Node *node;
int script;
s->_synonyms.clear();
list = lookup_list(s, GET_SEL32(object, elements));
node = lookup_node(s, list->first);
while (node) {
reg_t objpos = node->value;
int seg;
int synonyms_nr = 0;
script = GET_SEL32V(objpos, number);
seg = s->seg_manager->segGet(script);
if (seg >= 0)
synonyms_nr = s->seg_manager->getScript(seg)->getSynonymsNr();
if (synonyms_nr) {
byte *synonyms;
synonyms = s->seg_manager->getScript(seg)->getSynonyms();
if (synonyms) {
debugC(2, kDebugLevelParser, "Setting %d synonyms for script.%d\n",
synonyms_nr, script);
if (synonyms_nr > 16384) {
error("Segtable corruption: script.%03d has %d synonyms",
script, synonyms_nr);
/* We used to reset the corrupted value here. I really don't think it's appropriate.
* Lars */
} else
for (int i = 0; i < synonyms_nr; i++) {
synonym_t tmp;
tmp.replaceant = (int16)READ_LE_UINT16(synonyms + i * 4);
tmp.replacement = (int16)READ_LE_UINT16(synonyms + i * 4 + 2);
s->_synonyms.push_back(tmp);
}
} else
warning("Synonyms of script.%03d were requested, but script is not available", script);
}
node = lookup_node(s, node->succ);
}
debugC(2, kDebugLevelParser, "A total of %d synonyms are active now.\n", s->_synonyms.size());
return s->r_acc;
}
reg_t kParse(EngineState *s, int funct_nr, int argc, reg_t *argv) {
reg_t stringpos = argv[0];
char *string = kernel_dereference_char_pointer(s, stringpos, 0);
char *error;
ResultWordList words;
reg_t event = argv[1];
s->parser_event = event;
if (s->parser_valid == 2) {
sciprintf("Parsing skipped: Parser in simparse mode\n");
return s->r_acc;
}
bool res = s->_vocabulary->tokenizeString(words, string, &error);
s->parser_valid = 0; /* not valid */
if (res && !words.empty()) {
int syntax_fail = 0;
vocab_synonymize_tokens(words, s->_synonyms);
s->r_acc = make_reg(0, 1);
#ifdef DEBUG_PARSER
debugC(2, kDebugLevelParser, "Parsed to the following blocks:\n", 0);
for (ResultWordList::const_iterator i = words.begin(); i != words.end(); ++i)
debugC(2, kDebugLevelParser, " Type[%04x] Group[%04x]\n", i->_class, i->_group);
#endif
if (s->_vocabulary->parseGNF(s->parser_nodes, words))
syntax_fail = 1; /* Building a tree failed */
if (syntax_fail) {
s->r_acc = make_reg(0, 1);
PUT_SEL32V(event, claimed, 1);
invoke_selector(INV_SEL(s->game_obj, syntaxFail, kStopOnInvalidSelector), 2, s->parser_base, stringpos);
/* Issue warning */
debugC(2, kDebugLevelParser, "Tree building failed\n");
} else {
s->parser_valid = 1;
PUT_SEL32V(event, claimed, 0);
#ifdef DEBUG_PARSER
vocab_dump_parse_tree("Parse-tree", s->parser_nodes);
#endif
}
} else {
s->r_acc = make_reg(0, 0);
PUT_SEL32V(event, claimed, 1);
if (error) {
char *pbase_str = kernel_dereference_char_pointer(s, s->parser_base, 0);
strcpy(pbase_str, error);
debugC(2, kDebugLevelParser, "Word unknown: %s\n", error);
/* Issue warning: */
invoke_selector(INV_SEL(s->game_obj, wordFail, kStopOnInvalidSelector), 2, s->parser_base, stringpos);
free(error);
return make_reg(0, 1); /* Tell them that it dind't work */
}
}
return s->r_acc;
}
reg_t kStrEnd(EngineState *s, int funct_nr, int argc, reg_t *argv) {
reg_t address = argv[0];
char *seeker = kernel_dereference_char_pointer(s, address, 0);
while (*seeker++)
++address.offset;
return address;
}
reg_t kStrCat(EngineState *s, int funct_nr, int argc, reg_t *argv) {
char *s1 = kernel_dereference_char_pointer(s, argv[0], 0);
char *s2 = kernel_dereference_char_pointer(s, argv[1], 0);
strcat(s1, s2);
return argv[0];
}
reg_t kStrCmp(EngineState *s, int funct_nr, int argc, reg_t *argv) {
char *s1 = kernel_dereference_char_pointer(s, argv[0], 0);
char *s2 = kernel_dereference_char_pointer(s, argv[1], 0);
if (argc > 2)
return make_reg(0, strncmp(s1, s2, UKPV(2)));
else
return make_reg(0, strcmp(s1, s2));
}
reg_t kStrCpy(EngineState *s, int funct_nr, int argc, reg_t *argv) {
char *dest = (char *) kernel_dereference_bulk_pointer(s, argv[0], 0);
char *src = (char *) kernel_dereference_bulk_pointer(s, argv[1], 0);
if (!dest) {
warning("Attempt to strcpy TO invalid pointer %04x:%04x",
PRINT_REG(argv[0]));
return NULL_REG;
}
if (!src) {
warning("Attempt to strcpy FROM invalid pointer %04x:%04x",
PRINT_REG(argv[1]));
*dest = 0;
return argv[1];
}
if (argc > 2) {
int length = SKPV(2);
if (length >= 0)
strncpy(dest, src, length);
else {
if (s->seg_manager->_heap[argv[0].segment]->getType() == MEM_OBJ_DYNMEM) {
reg_t *srcp = (reg_t *) src;
int i;
warning("Performing reg_t to raw conversion for AvoidPath");
for (i = 0; i < -length / 2; i++) {
dest[2 * i] = srcp->offset & 0xff;
dest[2 * i + 1] = srcp->offset >> 8;
srcp++;
}
} else
memcpy(dest, src, -length);
}
} else
strcpy(dest, src);
return argv[0];
}
/* Simple heuristic to work around array handling peculiarity in SQ4:
It uses StrAt() to read the individual elements, so we must determine
whether a string is really a string or an array. */
2009-03-13 16:31:38 +00:00
static int is_print_str(const char *str) {
int printable = 0;
int len = strlen(str);
if (len == 0) return 1;
while (*str) {
// The parameter passed to isprint() needs to be in the range
// 0 to 0xFF or EOF, according to MSDN, therefore we cast it
// to an unsigned char. Values outside this range (in this
// case, negative values) yield unpredictable results. Refer to:
// http://msdn.microsoft.com/en-us/library/ewx8s4kw.aspx
2009-03-13 16:31:38 +00:00
if (isprint((byte)*str))
printable++;
str++;
}
return ((float)printable / (float)len >= 0.5);
}
reg_t kStrAt(EngineState *s, int funct_nr, int argc, reg_t *argv) {
2009-03-13 16:31:38 +00:00
byte *dest = (byte *)kernel_dereference_bulk_pointer(s, argv[0], 0);
reg_t *dest2;
if (!dest) {
warning("Attempt to StrAt at invalid pointer %04x:%04x", PRINT_REG(argv[0]));
return NULL_REG;
}
bool lsl5PasswordWorkaround = false;
// LSL5 stores the password at the beginning in memory.drv, using XOR encryption,
// which means that is_print_str() will fail. Therefore, do not use the heuristic to determine
// if we're handling a string or an array for LSL5's password screen (room 155)
if (s->_gameName.equalsIgnoreCase("lsl5") && s->currentRoomNumber() == 155)
lsl5PasswordWorkaround = true;
const char* dst = (const char *)dest; // used just for code beautification purposes
if ((argc == 2) &&
/* Our pathfinder already works around the issue we're trying to fix */
(strcmp(s->seg_manager->getDescription(argv[0]), AVOIDPATH_DYNMEM_STRING) != 0) &&
((strlen(dst) < 2) || (!lsl5PasswordWorkaround && !is_print_str(dst)))) {
// SQ4 array handling detected
#ifndef SCUMM_BIG_ENDIAN
int odd = KP_UINT(argv[1]) & 1;
#else
int odd = !(KP_UINT(argv[1]) & 1);
#endif
dest2 = ((reg_t *) dest) + (KP_UINT(argv[1]) / 2);
2009-03-13 16:31:38 +00:00
dest = ((byte *)(&dest2->offset)) + odd;
} else
dest += KP_UINT(argv[1]);
s->r_acc = make_reg(0, *dest);
if (argc > 2)
*dest = KP_SINT(argv[2]); /* Request to modify this char */
return s->r_acc;
}
reg_t kReadNumber(EngineState *s, int funct_nr, int argc, reg_t *argv) {
char *source = kernel_dereference_char_pointer(s, argv[0], 0);
while (isspace(*source))
source++; /* Skip whitespace */
if (*source == '$') /* SCI uses this for hex numbers */
return make_reg(0, (int16)strtol(source + 1, NULL, 16)); /* Hex */
else
return make_reg(0, (int16)strtol(source, NULL, 10)); /* Force decimal */
}
#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 funct_nr, int argc, reg_t *argv) {
int *arguments;
reg_t dest = argv[0];
char *target = (char *) kernel_dereference_bulk_pointer(s, dest, 0);
reg_t position = argv[1]; /* source */
int index = UKPV(2);
char *source;
char *str_base = target;
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;
int maxsize = 4096; /* Arbitrary... */
if (position.segment)
startarg = 2;
else
startarg = 3; /* First parameter to use for formatting */
source = kernel_lookup_text(s, position, index);
debugC(2, kDebugLevelStrings, "Formatting \"%s\"\n", source);
arguments = (int*)malloc(sizeof(int) * argc);
#ifdef SATISFY_PURIFY
memset(arguments, 0, sizeof(int) * argc);
#endif
for (i = startarg; i < argc; i++)
arguments[i-startarg] = UKPV(i); /* Parameters are copied to prevent overwriting */
while ((xfer = *source++)) {
if (xfer == '%') {
if (mode == 1) {
CHECK_OVERFLOW1(target, 2, NULL_REG);
*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;
source++;
} else
if (isdigit(xfer))
source--; /* Stepped over 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;
CHECK_OVERFLOW1(target, str_leng + 1, NULL_REG);
switch (xfer) {
case 's': { /* Copy string */
reg_t reg = argv[startarg + paramindex];
char *tempsource = kernel_lookup_text(s, reg,
arguments[paramindex + 1]);
int slen = strlen(tempsource);
int extralen = str_leng - slen;
CHECK_OVERFLOW1(target, extralen, NULL_REG);
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);
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 */
CHECK_OVERFLOW1(target, 2, NULL_REG);
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";
if (!unsigned_var)
if (arguments[paramindex] & 0x8000)
/* sign extend */
arguments[paramindex] = (~0xffff) | arguments[paramindex];
target += sprintf(target, format_string, arguments[paramindex++]);
CHECK_OVERFLOW1(target, 0, NULL_REG);
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 */
return dest; /* Return target addr */
}
reg_t kStrLen(EngineState *s, int funct_nr, int argc, reg_t *argv) {
char *str = kernel_dereference_char_pointer(s, argv[0], 0);
if (!str) {
warning("StrLen: invalid pointer %04x:%04x", PRINT_REG(argv[0]));
return NULL_REG;
}
return make_reg(0, strlen(str));
}
reg_t kGetFarText(EngineState *s, int funct_nr, int argc, reg_t *argv) {
Resource *textres = s->resmgr->findResource(kResourceTypeText, UKPV(0), 0);
char *seeker;
int counter = UKPV(1);
if (!textres) {
error("text.%d does not exist", UKPV(0));
return NULL_REG;
}
seeker = (char *) textres->data;
while (counter--) {
while (*seeker++)
;
}
/* The second parameter (counter) determines the number of the string inside the text
** resource.
*/
strcpy(kernel_dereference_char_pointer(s, argv[2], 0), 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
};
reg_t kMessage(EngineState *s, int funct_nr, int argc, reg_t *argv) {
2009-05-10 14:57:27 +00:00
MessageTuple tuple;
int func;
// For earlier version of of this function (GetMessage)
bool isGetMessage = argc == 4;
if (isGetMessage) {
func = K_MESSAGE_GET;
2009-05-10 14:57:27 +00:00
tuple.noun = UKPV(0);
tuple.verb = UKPV(2);
tuple.cond = 0;
tuple.seq = 1;
} else {
func = UKPV(0);
if (argc >= 6) {
tuple.noun = UKPV(2);
tuple.verb = UKPV(3);
tuple.cond = UKPV(4);
tuple.seq = UKPV(5);
}
}
switch (func) {
case K_MESSAGE_GET:
case K_MESSAGE_NEXT: {
reg_t bufferReg;
char *buffer = NULL;
Common::String str;
reg_t retval;
if (func == K_MESSAGE_GET) {
s->_msgState.loadRes(s->resmgr, UKPV(1), true);
s->_msgState.findTuple(tuple);
if (isGetMessage)
bufferReg = (argc == 4 ? argv[3] : NULL_REG);
else
bufferReg = (argc == 7 ? argv[6] : NULL_REG);
} else {
bufferReg = (argc == 2 ? argv[1] : NULL_REG);
}
if (s->_msgState.getMessage()) {
str = s->_msgState.getText();
if (isGetMessage)
retval = bufferReg;
else
retval = make_reg(0, s->_msgState.getTalker());
} else {
str = Common::String(DUMMY_MESSAGE);
retval = NULL_REG;
}
if (!bufferReg.isNull()) {
int len = str.size() + 1;
buffer = kernel_dereference_char_pointer(s, bufferReg, len);
if (buffer) {
strcpy(buffer, str.c_str());
} else {
warning("Message: buffer %04x:%04x invalid or too small to hold the following text of %i bytes: '%s'", PRINT_REG(bufferReg), len, str.c_str());
// Set buffer to empty string if possible
buffer = kernel_dereference_char_pointer(s, bufferReg, 1);
if (buffer)
*buffer = 0;
}
s->_msgState.gotoNext();
}
return retval;
}
case K_MESSAGE_SIZE: {
MessageState tempState;
if (tempState.loadRes(s->resmgr, UKPV(1), false) && tempState.findTuple(tuple) && tempState.getMessage())
return make_reg(0, tempState.getText().size() + 1);
else
return NULL_REG;
}
case K_MESSAGE_REFCOND:
case K_MESSAGE_REFVERB:
case K_MESSAGE_REFNOUN: {
MessageState tempState;
if (tempState.loadRes(s->resmgr, UKPV(1), false) && tempState.findTuple(tuple)) {
MessageTuple t = tempState.getRefTuple();
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 NULL_REG;
}
case K_MESSAGE_LASTMESSAGE: {
MessageTuple msg = s->_msgState.getLastTuple();
int module = s->_msgState.getLastModule();
byte *buffer = kernel_dereference_bulk_pointer(s, argv[1], 10);
if (buffer) {
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 {
warning("Message: buffer %04x:%04x invalid or too small to hold the tuple", PRINT_REG(argv[1]));
}
return NULL_REG;
}
default:
warning("Message: subfunction %i invoked (not implemented)", func);
}
return NULL_REG;
}
reg_t kSetQuitStr(EngineState *s, int funct_nr, int argc, reg_t *argv) {
char *quitStr = kernel_dereference_char_pointer(s, argv[0], 0);
debug("Setting quit string to '%s'", quitStr);
return s->r_acc;
}
} // End of namespace Sci