scummvm/engines/agi/words.cpp
Willem Jan Palenstijn b1a7492da8 AGI: Skip words starting with digits that are filed under 'a' in the dictionary.
The fan game SQ0 does this (for '7up', among others), and this caused us
to skip all words starting with an 'a'. Bug #3615061.
2013-10-01 19:58:51 +02:00

232 lines
5.6 KiB
C++

/* 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.
*
*/
#include "agi/agi.h"
#include "common/textconsole.h"
namespace Agi {
//
// Local implementation to avoid problems with strndup() used by
// gcc 3.2 Cygwin (see #635984)
//
static char *myStrndup(const char *src, int n) {
char *tmp = strncpy((char *)malloc(n + 1), src, n);
tmp[n] = 0;
return tmp;
}
int AgiEngine::loadWords_v1(Common::File &f) {
char str[64];
int k;
debug(0, "Loading dictionary");
// Loop through alphabet, as words in the dictionary file are sorted by
// first character
f.seek(f.pos() + 26 * 2, SEEK_SET);
do {
// Read next word
for (k = 0; k < (int)sizeof(str) - 1; k++) {
str[k] = f.readByte();
if (str[k] == 0 || (uint8)str[k] == 0xFF)
break;
}
// And store it in our internal dictionary
if (k > 0) {
AgiWord *w = new AgiWord;
w->word = myStrndup(str, k + 1);
w->id = f.readUint16LE();
_game.words[str[0] - 'a'].push_back(w);
debug(3, "'%s' (%d)", w->word, w->id);
}
} while((uint8)str[0] != 0xFF);
return errOK;
}
int AgiEngine::loadWords(const char *fname) {
Common::File fp;
if (!fp.open(fname)) {
warning("loadWords: can't open %s", fname);
return errOK; // err_BadFileOpen
}
debug(0, "Loading dictionary: %s", fname);
// Loop through alphabet, as words in the dictionary file are sorted by
// first character
for (int i = 0; i < 26; i++) {
fp.seek(i * 2, SEEK_SET);
int offset = fp.readUint16BE();
if (offset == 0)
continue;
fp.seek(offset, SEEK_SET);
int k = fp.readByte();
while (!fp.eos() && !fp.err()) {
// Read next word
char c, str[64];
do {
c = fp.readByte();
str[k++] = (c ^ 0x7F) & 0x7F;
} while (!(c & 0x80) && k < (int)sizeof(str) - 1);
str[k] = 0;
// WORKAROUND:
// The SQ0 fan game stores words starting with numbers (like '7up')
// in its dictionary under the 'a' entry. We skip these.
// See bug #3615061
if (str[0] == 'a' + i) {
// And store it in our internal dictionary
AgiWord *w = new AgiWord;
w->word = myStrndup(str, k);
w->id = fp.readUint16BE();
_game.words[i].push_back(w);
}
k = fp.readByte();
// Are there more words with an already known prefix?
// WORKAROUND: We only break after already seeing words with the
// right prefix, for the SQ0 words starting with digits filed under
// 'a'. See above comment and bug #3615061.
if (k == 0 && str[0] >= 'a' + i)
break;
}
}
return errOK;
}
void AgiEngine::unloadWords() {
for (int i = 0; i < 26; i++)
_game.words[i].clear();
}
/**
* Find a word in the dictionary
* Uses an algorithm hopefully like the one Sierra used. Returns the ID
* of the word and the length in flen. Returns -1 if not found.
*/
int AgiEngine::findWord(const char *word, int *flen) {
int c;
int result = -1;
debugC(2, kDebugLevelScripts, "find_word(%s)", word);
if (word[0] >= 'a' && word[0] <= 'z')
c = word[0] - 'a';
else
return -1;
*flen = 0;
Common::Array<AgiWord *> &a = _game.words[c];
for (int i = 0; i < (int)a.size(); i++) {
int wlen = strlen(a[i]->word);
// Keep looking till we find the word itself, or the whole phrase.
// Try to find the best match (i.e. the longest matching phrase).
if (!strncmp(a[i]->word, word, wlen) && (word[wlen] == 0 || word[wlen] == 0x20) && wlen >= *flen) {
*flen = wlen;
result = a[i]->id;
}
}
return result;
}
void AgiEngine::dictionaryWords(char *msg) {
char *p = NULL;
char *q = NULL;
int wid, wlen;
debugC(2, kDebugLevelScripts, "msg = \"%s\"", msg);
cleanInput();
for (p = msg; p && *p && getvar(vWordNotFound) == 0;) {
if (*p == 0x20)
p++;
if (*p == 0)
break;
wid = findWord(p, &wlen);
debugC(2, kDebugLevelScripts, "find_word(p) == %d", wid);
switch (wid) {
case -1:
debugC(2, kDebugLevelScripts, "unknown word");
_game.egoWords[_game.numEgoWords].word = strdup(p);
q = _game.egoWords[_game.numEgoWords].word;
_game.egoWords[_game.numEgoWords].id = 19999;
setvar(vWordNotFound, 1 + _game.numEgoWords);
_game.numEgoWords++;
p += strlen(p);
break;
case 0:
// ignore this word
debugC(2, kDebugLevelScripts, "ignore word");
p += wlen;
q = NULL;
break;
default:
// an OK word
debugC(3, kDebugLevelScripts, "ok word (%d)", wid);
_game.egoWords[_game.numEgoWords].id = wid;
_game.egoWords[_game.numEgoWords].word = myStrndup(p, wlen);
_game.numEgoWords++;
p += wlen;
break;
}
if (p != NULL && *p) {
debugC(2, kDebugLevelScripts, "p = %s", p);
*p = 0;
p++;
}
if (q != NULL) {
for (; (*q != 0 && *q != 0x20); q++)
;
if (*q) {
*q = 0;
q++;
}
}
}
debugC(4, kDebugLevelScripts, "num_ego_words = %d", _game.numEgoWords);
if (_game.numEgoWords > 0) {
setflag(fEnteredCli, true);
setflag(fSaidAcceptedInput, false);
}
}
} // End of namespace Agi