mirror of
https://github.com/scummvm/scummvm.git
synced 2026-06-20 05:45:29 +00:00
If nv or nn (number of verbs or number of nouns) is equal to or larger than the number of words + 2, this loop will try to write out of bounds and assert. This happens for example in the C64 version of The Golden Baton. To fix this, it is really enough to change the >= operator to >, but I took the opportunity to simplify the code a bit as well.
1102 lines
24 KiB
C++
1102 lines
24 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 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/>.
|
|
*
|
|
*/
|
|
|
|
/*
|
|
* Based on ScottFree interpreter version 1.14 developed by Swansea
|
|
* University Computer Society without disassembly of any other game
|
|
* drivers, only of game databases as permitted by EEC law (for purposes
|
|
* of compatibility).
|
|
*
|
|
* Licensed under GPLv2
|
|
*
|
|
* https://github.com/angstsmurf/spatterlight/tree/master/terps/scott
|
|
*/
|
|
|
|
#include "common/str.h"
|
|
#include "glk/scott/scott.h"
|
|
#include "glk/scott/globals.h"
|
|
#include "glk/scott/command_parser.h"
|
|
#include "glk/scott/decompress_text.h"
|
|
#include "glk/scott/decompress_z80.h"
|
|
#include "glk/scott/detection.h"
|
|
#include "glk/scott/detection_tables.h"
|
|
#include "glk/scott/game_info.h"
|
|
#include "glk/scott/hulk.h"
|
|
#include "glk/scott/line_drawing.h"
|
|
#include "glk/scott/saga_draw.h"
|
|
#include "glk/scott/c64_checksums.h"
|
|
#include "glk/scott/game_specific.h"
|
|
#include "glk/scott/resource.h"
|
|
|
|
namespace Glk {
|
|
namespace Scott {
|
|
|
|
struct DictionaryKey {
|
|
DictionaryType _dict;
|
|
const char *_signature;
|
|
};
|
|
|
|
DictionaryKey g_dictKeys[] = {
|
|
{FOUR_LETTER_UNCOMPRESSED, "AUTO\0GO\0"},
|
|
{THREE_LETTER_UNCOMPRESSED, "AUT\0GO\0"},
|
|
{FIVE_LETTER_UNCOMPRESSED, "AUTO\0\0GO"},
|
|
{FOUR_LETTER_COMPRESSED, "aUTOgO\0"},
|
|
{GERMAN, "\xc7"
|
|
"EHENSTEIGE"},
|
|
{FIVE_LETTER_COMPRESSED, "gEHENSTEIGE"}, // Gremlins C64
|
|
{SPANISH, "ANDAENTRAVAN"},
|
|
{FIVE_LETTER_UNCOMPRESSED, "*CROSS*RUN\0\0"}, // Claymorgue
|
|
{ITALIAN, "AUTO\0VAI\0\0*ENTR"}
|
|
};
|
|
|
|
void readHeader(uint8_t *ptr) {
|
|
int i, value;
|
|
for (i = 0; i < 24; i++) {
|
|
value = *ptr + 256 * *(ptr + 1);
|
|
_G(_header)[i] = value;
|
|
ptr += 2;
|
|
}
|
|
}
|
|
|
|
int sanityCheckHeader(void) {
|
|
int16_t v = _G(_gameHeader)->_numItems;
|
|
if (v < 10 || v > 500)
|
|
return 0;
|
|
v = _G(_gameHeader)->_numActions;
|
|
if (v < 100 || v > 500)
|
|
return 0;
|
|
v = _G(_gameHeader)->_numWords; // word pairs
|
|
if (v < 50 || v > 190)
|
|
return 0;
|
|
v = _G(_gameHeader)->_numRooms; // Number of rooms
|
|
if (v < 10 || v > 100)
|
|
return 0;
|
|
|
|
return 1;
|
|
}
|
|
|
|
uint8_t *readDictionary(GameInfo info, uint8_t **pointer, int loud) {
|
|
uint8_t *ptr = *pointer;
|
|
char *dictword = new char[info._wordLength + 2];
|
|
char c = 0;
|
|
int wordnum = 0;
|
|
int charindex = 0;
|
|
|
|
int nw = info._numberOfWords;
|
|
|
|
int nv = info._numberOfVerbs;
|
|
int nn = info._numberOfNouns;
|
|
|
|
for (int i = 0; i < nw + 2; i++) {
|
|
_G(_verbs)[i] = ".";
|
|
_G(_nouns)[i] = ".";
|
|
}
|
|
|
|
do {
|
|
for (int i = 0; i < info._wordLength; i++) {
|
|
c = *(ptr++);
|
|
|
|
if (info._dictionary == FOUR_LETTER_COMPRESSED || info._dictionary == FIVE_LETTER_COMPRESSED) {
|
|
if (charindex == 0) {
|
|
if (c >= 'a') {
|
|
c = toupper(c);
|
|
} else if (c != '.' && c != 0) {
|
|
dictword[charindex++] = '*';
|
|
}
|
|
}
|
|
dictword[charindex++] = c;
|
|
} else if (info._subType & LOCALIZED) {
|
|
if (charindex == 0) {
|
|
if (c & 0x80) {
|
|
c = c & 0x7f;
|
|
} else if (c != '.') {
|
|
dictword[charindex++] = '*';
|
|
}
|
|
}
|
|
dictword[charindex++] = c;
|
|
} else {
|
|
if (c == 0) {
|
|
if (charindex == 0) {
|
|
c = *(ptr++);
|
|
}
|
|
}
|
|
if (c != ' ' && charindex > 0 && dictword[charindex - 1] == ' ') {
|
|
i--;
|
|
charindex--;
|
|
}
|
|
if (c == '*') {
|
|
if (charindex != 0)
|
|
charindex = 0;
|
|
i = -1;
|
|
}
|
|
dictword[charindex++] = c;
|
|
}
|
|
}
|
|
dictword[charindex] = 0;
|
|
if (wordnum < nv) {
|
|
_G(_verbs)[wordnum] = Common::String(dictword, charindex + 1);
|
|
if (loud)
|
|
debug("Verb %d: \"%s\"\n", wordnum, _G(_verbs)[wordnum].c_str());
|
|
} else {
|
|
_G(_nouns)[wordnum - nv] = Common::String(dictword, charindex + 1);
|
|
if (loud)
|
|
debug("Noun %d: \"%s\"\n", wordnum - nv, _G(_nouns)[wordnum - nv].c_str());
|
|
}
|
|
wordnum++;
|
|
|
|
if (c != 0 && !isascii(c))
|
|
return ptr;
|
|
|
|
charindex = 0;
|
|
} while (wordnum <= nv + nn);
|
|
|
|
return ptr;
|
|
}
|
|
|
|
int parseHeader(int *h, HeaderType type, int *ni, int *na, int *nw, int *nr, int *mc, int *pr, int *tr, int *wl, int *lt, int *mn, int *trm) {
|
|
switch (type) {
|
|
case NO_HEADER:
|
|
return 0;
|
|
case EARLY:
|
|
*ni = h[1];
|
|
*na = h[2];
|
|
*nw = h[3];
|
|
*nr = h[4];
|
|
*mc = h[5];
|
|
*pr = h[6];
|
|
*tr = h[7];
|
|
*wl = h[8];
|
|
*lt = h[9];
|
|
*mn = h[10];
|
|
*trm = h[11];
|
|
break;
|
|
case LATE:
|
|
*ni = h[1];
|
|
*na = h[2];
|
|
*nw = h[3];
|
|
*nr = h[4];
|
|
*mc = h[5];
|
|
*wl = h[6];
|
|
*mn = h[7];
|
|
*pr = 1;
|
|
*tr = 0;
|
|
*lt = -1;
|
|
*trm = 0;
|
|
break;
|
|
case HULK_HEADER:
|
|
*ni = h[3];
|
|
*na = h[2];
|
|
*nw = h[1];
|
|
*nr = h[5];
|
|
*mc = h[6];
|
|
*pr = h[7];
|
|
*tr = h[8];
|
|
*wl = h[0];
|
|
*lt = h[9];
|
|
*mn = h[4];
|
|
*trm = h[10];
|
|
break;
|
|
case ROBIN_C64_HEADER:
|
|
*ni = h[1];
|
|
*na = h[2];
|
|
*nw = h[6];
|
|
*nr = h[4];
|
|
*mc = h[5];
|
|
*pr = 1;
|
|
*tr = 0;
|
|
*wl = h[7];
|
|
*lt = -1;
|
|
*mn = h[3];
|
|
*trm = 0;
|
|
break;
|
|
case GREMLINS_C64_HEADER:
|
|
*ni = h[1];
|
|
*na = h[2];
|
|
*nw = h[5];
|
|
*nr = h[3];
|
|
*mc = h[6];
|
|
*pr = h[8];
|
|
*tr = 0;
|
|
*wl = h[7];
|
|
*lt = -1;
|
|
*mn = 98;
|
|
*trm = 0;
|
|
break;
|
|
case SUPERGRAN_C64_HEADER:
|
|
*ni = h[3];
|
|
*na = h[1];
|
|
*nw = h[2];
|
|
*nr = h[4];
|
|
*mc = h[8];
|
|
*pr = 1;
|
|
*tr = 0;
|
|
*wl = h[6];
|
|
*lt = -1;
|
|
*mn = h[5];
|
|
*trm = 0;
|
|
break;
|
|
case SEAS_OF_BLOOD_C64_HEADER:
|
|
*ni = h[0];
|
|
*na = h[1];
|
|
*nw = 134;
|
|
*nr = h[3];
|
|
*mc = h[4];
|
|
*pr = 1;
|
|
*tr = 0;
|
|
*wl = h[6];
|
|
*lt = -1;
|
|
*mn = h[2];
|
|
*trm = 0;
|
|
break;
|
|
case MYSTERIOUS_C64_HEADER:
|
|
*ni = h[1];
|
|
*na = h[2];
|
|
*nw = h[3];
|
|
*nr = h[4];
|
|
*mc = h[5] & 0xff;
|
|
*pr = h[5] >> 8;
|
|
*tr = h[6];
|
|
*wl = h[7];
|
|
*lt = h[8];
|
|
*mn = h[9];
|
|
*trm = 0;
|
|
break;
|
|
case ARROW_OF_DEATH_PT_2_C64_HEADER:
|
|
*ni = h[3];
|
|
*na = h[1];
|
|
*nw = h[2];
|
|
*nr = h[4];
|
|
*mc = h[5] & 0xff;
|
|
*pr = h[5] >> 8;
|
|
*tr = h[6];
|
|
*wl = h[7];
|
|
*lt = h[8];
|
|
*mn = h[9];
|
|
*trm = 0;
|
|
break;
|
|
case INDIANS_C64_HEADER:
|
|
*ni = h[1];
|
|
*na = h[2];
|
|
*nw = h[3];
|
|
*nr = h[4];
|
|
*mc = h[5] & 0xff;
|
|
*pr = h[5] >> 8;
|
|
*tr = h[6] & 0xff;
|
|
*wl = h[6] >> 8;
|
|
*lt = h[7] >> 8;
|
|
*mn = h[8] >> 8;
|
|
*trm = 0;
|
|
break;
|
|
default:
|
|
warning("Unhandled header type!\n");
|
|
return 0;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
void printHeaderInfo(int *h, int ni, int na, int nw, int nr, int mc, int pr, int tr, int wl, int lt, int mn, int trm) {
|
|
uint16_t value;
|
|
for (int i = 0; i < 13; i++) {
|
|
value = h[i];
|
|
debug("b $%X %d: ", 0x494d + 0x3FE5 + i * 2, i);
|
|
debug("\t%d\n", value);
|
|
}
|
|
|
|
debug("Number of items =\t%d\n", ni);
|
|
debug("Number of actions =\t%d\n", na);
|
|
debug("Number of words =\t%d\n", nw);
|
|
debug("Number of rooms =\t%d\n", nr);
|
|
debug("Max carried items =\t%d\n", mc);
|
|
debug("Word length =\t%d\n", wl);
|
|
debug("Number of messages =\t%d\n", mn);
|
|
debug("Player start location: %d\n", pr);
|
|
debug("Treasure room: %d\n", tr);
|
|
debug("Lightsource time left: %d\n", lt);
|
|
debug("Number of treasures: %d\n", tr);
|
|
}
|
|
|
|
void loadVectorData(GameInfo info, uint8_t *ptr) {
|
|
int offset;
|
|
|
|
if (info._startOfImageData == FOLLOWS)
|
|
ptr++;
|
|
else if (seekIfNeeded(info._startOfImageData, &offset, &ptr) == 0)
|
|
return;
|
|
|
|
_G(_lineImages).resize(info._numberOfRooms);
|
|
int ct = 0;
|
|
LineImage *lp = &_G(_lineImages)[0];
|
|
uint8_t byte = *(ptr++);
|
|
do {
|
|
_G(_rooms)[ct]._image = 0;
|
|
if (byte == 0xff) {
|
|
lp->_bgColour = *(ptr++);
|
|
lp->_data = ptr;
|
|
} else {
|
|
error("loadVectorData: Error! Image data does not start with 0xff");
|
|
}
|
|
do {
|
|
byte = *(ptr++);
|
|
if (ptr > _G(_entireFile) && static_cast<size_t>(ptr - _G(_entireFile)) >= _G(_fileLength)) {
|
|
warning("loadVectorData: Error! Image data for image %d cut off", ct);
|
|
if (_G(_gameHeader)->_numRooms - ct > 1)
|
|
g_scott->display(_G(_bottomWindow), "[This copy has %d broken or missing pictures. These have been patched out.]\n\n", _G(_gameHeader)->_numRooms - ct);
|
|
if (lp->_data >= ptr)
|
|
lp->_size = 0;
|
|
else
|
|
lp->_size = ptr - lp->_data - 1;
|
|
for (int i = ct + 2; i < _G(_gameHeader)->_numRooms; i++)
|
|
_G(_rooms)[i]._image = 255;
|
|
return;
|
|
}
|
|
} while (byte != 0xff);
|
|
|
|
lp->_size = ptr - lp->_data;
|
|
lp++;
|
|
ct++;
|
|
} while (ct < info._numberOfRooms);
|
|
}
|
|
|
|
uint8_t *seekToPos(uint8_t *buf, size_t offset) {
|
|
if (offset > _G(_fileLength))
|
|
return 0;
|
|
return buf + offset;
|
|
}
|
|
|
|
int seekIfNeeded(int expectedStart, int *offset, uint8_t **ptr) {
|
|
if (expectedStart != FOLLOWS) {
|
|
*offset = expectedStart + _G(_fileBaselineOffset);
|
|
*ptr = seekToPos(_G(_entireFile), *offset);
|
|
if (*ptr == 0)
|
|
return 0;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
int tryLoadingOld(GameInfo info, int dictStart) {
|
|
int ni, na, nw, nr, mc, pr, tr, wl, lt, mn, trm;
|
|
int ct;
|
|
|
|
Action *ap;
|
|
Room *rp;
|
|
Item *ip;
|
|
/* Load the header */
|
|
|
|
uint8_t *ptr = _G(_entireFile);
|
|
_G(_fileBaselineOffset) = dictStart - info._startOfDictionary;
|
|
int offset = info._startOfHeader + _G(_fileBaselineOffset);
|
|
|
|
ptr = seekToPos(_G(_entireFile), offset);
|
|
if (ptr == 0)
|
|
return 0;
|
|
|
|
readHeader(ptr);
|
|
|
|
if (!parseHeader(_G(_header), info._headerStyle, &ni, &na, &nw, &nr, &mc, &pr, &tr, &wl, <, &mn, &trm))
|
|
return 0;
|
|
|
|
if (ni != info._numberOfItems || na != info._numberOfActions || nw != info._numberOfWords || nr != info._numberOfRooms || mc != info._maxCarried) {
|
|
return 0;
|
|
}
|
|
|
|
_G(_gameHeader)->_numItems = ni;
|
|
_G(_gameHeader)->_numActions = na;
|
|
_G(_gameHeader)->_numWords = nw;
|
|
_G(_gameHeader)->_wordLength = wl;
|
|
_G(_gameHeader)->_numRooms = nr;
|
|
_G(_gameHeader)->_maxCarry = mc;
|
|
_G(_gameHeader)->_playerRoom = pr;
|
|
_G(_gameHeader)->_treasures = tr;
|
|
_G(_gameHeader)->_lightTime = lt;
|
|
_G(_lightRefill) = lt;
|
|
_G(_gameHeader)->_numMessages = mn;
|
|
_G(_gameHeader)->_treasureRoom = trm;
|
|
_G(_items).resize(ni + 1);
|
|
_G(_actions).resize(na + 1);
|
|
_G(_verbs).resize(nw + 2);
|
|
_G(_nouns).resize(nw + 2);
|
|
_G(_rooms).resize(nr + 1);
|
|
_G(_messages).resize(mn + 1);
|
|
|
|
// actions
|
|
if (seekIfNeeded(info._startOfActions, &offset, &ptr) == 0)
|
|
return 0;
|
|
|
|
ct = 0;
|
|
|
|
uint16_t value, cond, comm;
|
|
|
|
while (ct < na + 1) {
|
|
ap = &_G(_actions)[ct];
|
|
value = *(ptr++);
|
|
value += *(ptr++) * 0x100; /* verb/noun */
|
|
ap->_vocab = value;
|
|
|
|
cond = 5;
|
|
comm = 2;
|
|
|
|
for (int j = 0; j < 5; j++) {
|
|
if (j < cond) {
|
|
value = *(ptr++);
|
|
value += *(ptr++) * 0x100;
|
|
} else
|
|
value = 0;
|
|
ap->_condition[j] = value;
|
|
}
|
|
for (int j = 0; j < 2; j++) {
|
|
if (j < comm) {
|
|
value = *(ptr++);
|
|
value += *(ptr++) * 0x100;
|
|
} else
|
|
value = 0;
|
|
ap->_action[j] = value;
|
|
}
|
|
ct++;
|
|
}
|
|
|
|
// room connections
|
|
if (seekIfNeeded(info._startOfRoomConnections, &offset, &ptr) == 0)
|
|
return 0;
|
|
|
|
ct = 0;
|
|
|
|
while (ct < nr + 1) {
|
|
rp = &_G(_rooms)[ct];
|
|
for (int j = 0; j < 6; j++) {
|
|
rp->_exits[j] = *(ptr++);
|
|
}
|
|
ct++;
|
|
}
|
|
|
|
// item locations
|
|
if (seekIfNeeded(info._startOfItemLocations, &offset, &ptr) == 0)
|
|
return 0;
|
|
|
|
ct = 0;
|
|
while (ct < ni + 1) {
|
|
ip = &_G(_items)[ct];
|
|
ip->_location = *(ptr++);
|
|
ip->_initialLoc = ip->_location;
|
|
ct++;
|
|
}
|
|
|
|
// dictionary
|
|
if (seekIfNeeded(info._startOfDictionary, &offset, &ptr) == 0)
|
|
return 0;
|
|
|
|
ptr = readDictionary(info, &ptr, 0);
|
|
|
|
// rooms
|
|
if (seekIfNeeded(info._startOfRoomDescriptions, &offset, &ptr) == 0)
|
|
return 0;
|
|
|
|
if (info._startOfRoomDescriptions == FOLLOWS)
|
|
ptr++;
|
|
|
|
ct = 0;
|
|
|
|
char text[256];
|
|
char c = 0;
|
|
int charindex = 0;
|
|
|
|
do {
|
|
c = *(ptr++);
|
|
text[charindex] = c;
|
|
rp = &_G(_rooms)[ct];
|
|
if (c == 0) {
|
|
rp->_text = text;
|
|
if (info._numberOfPictures > 0)
|
|
rp->_image = ct - 1;
|
|
else
|
|
rp->_image = 255;
|
|
ct++;
|
|
charindex = 0;
|
|
} else {
|
|
charindex++;
|
|
}
|
|
if (c != 0 && !isascii(c))
|
|
return 0;
|
|
} while (ct < nr + 1);
|
|
|
|
// messages
|
|
if (seekIfNeeded(info._startOfMessages, &offset, &ptr) == 0)
|
|
return 0;
|
|
|
|
ct = 0;
|
|
charindex = 0;
|
|
|
|
while (ct < mn + 1) {
|
|
c = *(ptr++);
|
|
text[charindex] = c;
|
|
if (c == 0) {
|
|
_G(_messages)[ct] = text;
|
|
ct++;
|
|
charindex = 0;
|
|
} else {
|
|
charindex++;
|
|
}
|
|
}
|
|
|
|
// items
|
|
if (seekIfNeeded(info._startOfItemDescriptions, &offset, &ptr) == 0)
|
|
return 0;
|
|
|
|
ct = 0;
|
|
charindex = 0;
|
|
|
|
do {
|
|
ip = &_G(_items)[ct];
|
|
c = *(ptr++);
|
|
text[charindex] = c;
|
|
if (c == 0) {
|
|
ip->_text = text;
|
|
const char *p = strchr(ip->_text.c_str(), '/');
|
|
/* Some games use // to mean no auto get/drop word! */
|
|
if (p) {
|
|
ip->_autoGet = Common::String(p);
|
|
if (!(ip->_autoGet == "//") && !(ip->_autoGet == "/*")) {
|
|
ip->_text = Common::String(ip->_text.c_str(), p);
|
|
ip->_autoGet.deleteChar(0);
|
|
const char *t = strchr(ip->_autoGet.c_str(), '/');
|
|
if (t) {
|
|
ip->_autoGet = Common::String(ip->_autoGet.c_str(), t);
|
|
}
|
|
}
|
|
}
|
|
ct++;
|
|
charindex = 0;
|
|
} else {
|
|
charindex++;
|
|
}
|
|
} while (ct < ni + 1);
|
|
|
|
// line images
|
|
if (info._numberOfPictures > 0) {
|
|
loadVectorData(info, ptr);
|
|
}
|
|
|
|
// system messages
|
|
ct = 0;
|
|
if (seekIfNeeded(info._startOfSystemMessages, &offset, &ptr) == 0)
|
|
return 1;
|
|
|
|
charindex = 0;
|
|
|
|
do {
|
|
c = *(ptr++);
|
|
text[charindex] = c;
|
|
if (c == 0 || c == 0x0d) {
|
|
if (charindex > 0) {
|
|
if (c == 0x0d)
|
|
charindex++;
|
|
text[charindex] = '\0';
|
|
_G(_systemMessages)[ct] = Common::String(text, charindex + 1);
|
|
ct++;
|
|
charindex = 0;
|
|
}
|
|
} else {
|
|
charindex++;
|
|
}
|
|
|
|
if (c != 0 && c != 0x0d && c != '\x83' && c != '\xc9' && !isascii(c))
|
|
break;
|
|
} while (ct < 40);
|
|
|
|
charindex = 0;
|
|
|
|
if (seekIfNeeded(info._startOfDirections, &offset, &ptr) == 0)
|
|
return 0;
|
|
|
|
ct = 0;
|
|
do {
|
|
c = *(ptr++);
|
|
text[charindex] = c;
|
|
if (c == 0 || c == 0x0d) {
|
|
if (charindex > 0) {
|
|
if (c == 0x0d)
|
|
charindex++;
|
|
text[charindex] = '\0';
|
|
_G(_sys)[ct] = Common::String(text, charindex + 1);
|
|
ct++;
|
|
charindex = 0;
|
|
}
|
|
} else {
|
|
charindex++;
|
|
}
|
|
|
|
if (c != 0 && c != 0x0d && !isascii(c))
|
|
break;
|
|
} while (ct < 6);
|
|
|
|
return 1;
|
|
}
|
|
|
|
int tryLoading(GameInfo info, int dictStart, int loud) {
|
|
// The Hulk does everything differently so it gets its own function
|
|
if (info._gameID == HULK || info._gameID == HULK_C64)
|
|
return tryLoadingHulk(info, dictStart);
|
|
|
|
if (info._type == OLD_STYLE)
|
|
return tryLoadingOld(info, dictStart);
|
|
|
|
int ni, na, nw, nr, mc, pr, tr, wl, lt, mn, trm;
|
|
int ct;
|
|
|
|
Action *ap;
|
|
Room *rp;
|
|
Item *ip;
|
|
|
|
/* Load the header */
|
|
uint8_t *ptr = _G(_entireFile);
|
|
|
|
if (loud) {
|
|
debug("dict_start:%x\n", dictStart);
|
|
debug(" info._start_of_dictionary:%x\n", info._startOfDictionary);
|
|
}
|
|
_G(_fileBaselineOffset) = dictStart - info._startOfDictionary;
|
|
|
|
if (loud) {
|
|
debug("_fileBaselineOffset:%x (%d)\n", _G(_fileBaselineOffset), _G(_fileBaselineOffset));
|
|
}
|
|
|
|
int offset = info._startOfHeader + _G(_fileBaselineOffset);
|
|
|
|
ptr = seekToPos(_G(_entireFile), offset);
|
|
if (ptr == 0)
|
|
return 0;
|
|
|
|
readHeader(ptr);
|
|
|
|
if (!parseHeader(_G(_header), info._headerStyle, &ni, &na, &nw, &nr, &mc, &pr, &tr, &wl, <, &mn, &trm))
|
|
return 0;
|
|
|
|
if (loud)
|
|
printHeaderInfo(_G(_header), ni, na, nw, nr, mc, pr, tr, wl, lt, mn, trm);
|
|
|
|
_G(_gameHeader)->_numItems = ni;
|
|
_G(_gameHeader)->_numActions = na;
|
|
_G(_gameHeader)->_numWords = nw;
|
|
_G(_gameHeader)->_wordLength = wl;
|
|
_G(_gameHeader)->_numRooms = nr;
|
|
_G(_gameHeader)->_maxCarry = mc;
|
|
_G(_gameHeader)->_playerRoom = pr;
|
|
_G(_gameHeader)->_treasures = tr;
|
|
_G(_gameHeader)->_lightTime = lt;
|
|
_G(_lightRefill) = lt;
|
|
_G(_gameHeader)->_numMessages = mn;
|
|
_G(_gameHeader)->_treasureRoom = trm;
|
|
|
|
if (sanityCheckHeader() == 0) {
|
|
return 0;
|
|
}
|
|
|
|
if (loud) {
|
|
debug("Found a valid header at position 0x%x\n", offset);
|
|
debug("Expected: 0x%x\n", info._startOfHeader + _G(_fileBaselineOffset));
|
|
}
|
|
|
|
if (ni != info._numberOfItems || na != info._numberOfActions || nw != info._numberOfWords || nr != info._numberOfRooms || mc != info._maxCarried) {
|
|
if (loud) {
|
|
debug("Non-matching header\n");
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
_G(_items).resize(ni + 1);
|
|
_G(_actions).resize(na + 1);
|
|
_G(_verbs).resize(nw + 2);
|
|
_G(_nouns).resize(nw + 2);
|
|
_G(_rooms).resize(nr + 1);
|
|
_G(_messages).resize(mn + 1);
|
|
|
|
int compressed = (info._dictionary == FOUR_LETTER_COMPRESSED);
|
|
|
|
// room images
|
|
if (info._startOfRoomImageList != 0) {
|
|
if (seekIfNeeded(info._startOfRoomImageList, &offset, &ptr) == 0)
|
|
return 0;
|
|
|
|
for (ct = 0; ct <= _G(_gameHeader)->_numRooms; ct++) {
|
|
rp = &_G(_rooms)[ct];
|
|
rp->_image = *(ptr++);
|
|
}
|
|
}
|
|
|
|
// item flags
|
|
if (info._startOfItemFlags != 0) {
|
|
|
|
if (seekIfNeeded(info._startOfItemFlags, &offset, &ptr) == 0)
|
|
return 0;
|
|
|
|
for (ct = 0; ct <= _G(_gameHeader)->_numItems; ct++) {
|
|
ip = &_G(_items)[ct];
|
|
ip->_flag = *(ptr++);
|
|
}
|
|
}
|
|
|
|
// item images
|
|
if (info._startOfItemImageList != 0) {
|
|
|
|
if (seekIfNeeded(info._startOfItemImageList, &offset, &ptr) == 0)
|
|
return 0;
|
|
|
|
for (ct = 0; ct <= _G(_gameHeader)->_numItems; ct++) {
|
|
ip = &_G(_items)[ct];
|
|
ip->_image = *(ptr++);
|
|
}
|
|
if (loud)
|
|
debug("Offset after reading item images: %d\n", static_cast<int>(ptr - _G(_entireFile) - _G(_fileBaselineOffset)));
|
|
}
|
|
|
|
// actions
|
|
if (seekIfNeeded(info._startOfActions, &offset, &ptr) == 0)
|
|
return 0;
|
|
|
|
ct = 0;
|
|
|
|
uint16_t value, cond, comm;
|
|
|
|
while (ct < na + 1) {
|
|
ap = &_G(_actions)[ct];
|
|
value = *(ptr++);
|
|
value += *(ptr++) * 0x100; /* verb/noun */
|
|
ap->_vocab = value;
|
|
|
|
if (info._actionsStyle == COMPRESSED) {
|
|
value = *(ptr++); /* count of actions/conditions */
|
|
cond = value & 0x1f;
|
|
if (cond > 5) {
|
|
debug("Condition error at action %d!", ct);
|
|
cond = 5;
|
|
}
|
|
comm = (value & 0xe0) >> 5;
|
|
if (comm > 2) {
|
|
debug("Command error at action %d!", ct);
|
|
comm = 2;
|
|
}
|
|
} else {
|
|
cond = 5;
|
|
comm = 2;
|
|
}
|
|
for (int j = 0; j < 5; j++) {
|
|
if (j < cond) {
|
|
value = *(ptr++);
|
|
value += *(ptr++) * 0x100;
|
|
} else
|
|
value = 0;
|
|
ap->_condition[j] = value;
|
|
}
|
|
for (int j = 0; j < 2; j++) {
|
|
if (j < comm) {
|
|
value = *(ptr++);
|
|
value += *(ptr++) * 0x100;
|
|
} else
|
|
value = 0;
|
|
ap->_action[j] = value;
|
|
}
|
|
|
|
ct++;
|
|
}
|
|
if (loud)
|
|
debug("Offset after reading actions: %d\n", static_cast<int>(ptr - _G(_entireFile) - _G(_fileBaselineOffset)));
|
|
|
|
// dictionary
|
|
if (seekIfNeeded(info._startOfDictionary, &offset, &ptr) == 0)
|
|
return 0;
|
|
|
|
ptr = readDictionary(info, &ptr, loud);
|
|
|
|
if (loud)
|
|
debug("Offset after reading dictionary: %d\n", static_cast<int>(ptr - _G(_entireFile) - _G(_fileBaselineOffset)));
|
|
|
|
// rooms
|
|
if (info._startOfRoomDescriptions != 0) {
|
|
if (seekIfNeeded(info._startOfRoomDescriptions, &offset, &ptr) == 0)
|
|
return 0;
|
|
|
|
ct = 0;
|
|
|
|
char text[256];
|
|
char c = 0;
|
|
int charindex = 0;
|
|
|
|
if (!compressed) {
|
|
do {
|
|
rp = &_G(_rooms)[ct];
|
|
c = *(ptr++);
|
|
text[charindex] = c;
|
|
if (c == 0) {
|
|
rp->_text = text;
|
|
if (loud)
|
|
debug("Room %d: %s\n", ct, rp->_text.c_str());
|
|
ct++;
|
|
charindex = 0;
|
|
} else {
|
|
charindex++;
|
|
}
|
|
if (c != 0 && !isascii(c))
|
|
return 0;
|
|
} while (ct < nr + 1);
|
|
} else {
|
|
do {
|
|
rp = &_G(_rooms)[ct];
|
|
rp->_text = decompressText(ptr, ct);
|
|
if (rp->_text.size() == 0)
|
|
return 0;
|
|
rp->_text.replace(0, 1, Common::String(tolower(rp->_text[0])));
|
|
ct++;
|
|
} while (ct < nr);
|
|
}
|
|
}
|
|
|
|
// room connections
|
|
if (seekIfNeeded(info._startOfRoomConnections, &offset, &ptr) == 0)
|
|
return 0;
|
|
|
|
ct = 0;
|
|
|
|
while (ct < nr + 1) {
|
|
rp = &_G(_rooms)[ct];
|
|
for (int j = 0; j < 6; j++) {
|
|
rp->_exits[j] = *(ptr++);
|
|
}
|
|
|
|
ct++;
|
|
}
|
|
|
|
// messages
|
|
if (seekIfNeeded(info._startOfMessages, &offset, &ptr) == 0)
|
|
return 0;
|
|
|
|
ct = 0;
|
|
char text[256];
|
|
char c = 0;
|
|
int charindex = 0;
|
|
|
|
if (compressed) {
|
|
while (ct < mn + 1) {
|
|
_G(_messages)[ct] = decompressText(ptr, ct);
|
|
if (loud)
|
|
debug("Message %d: \"%s\"\n", ct, _G(_messages)[ct].c_str());
|
|
if (_G(_messages)[ct].size() == 0)
|
|
return 0;
|
|
ct++;
|
|
}
|
|
} else {
|
|
while (ct < mn + 1) {
|
|
c = *(ptr++);
|
|
text[charindex] = c;
|
|
if (c == 0) {
|
|
_G(_messages)[ct] = text;
|
|
if (loud)
|
|
debug("Message %d: \"%s\"\n", ct, _G(_messages)[ct].c_str());
|
|
ct++;
|
|
charindex = 0;
|
|
} else {
|
|
charindex++;
|
|
}
|
|
}
|
|
}
|
|
|
|
// items
|
|
if (seekIfNeeded(info._startOfItemDescriptions, &offset, &ptr) == 0)
|
|
return 0;
|
|
|
|
ct = 0;
|
|
charindex = 0;
|
|
|
|
if (compressed) {
|
|
do {
|
|
ip = &_G(_items)[ct];
|
|
ip->_text = decompressText(ptr, ct);
|
|
ip->_autoGet = "";
|
|
if (ip->_text != "" && ip->_text[0] != '.') {
|
|
if (loud)
|
|
debug("Item %d: %s\n", ct, ip->_text.c_str());
|
|
const char *p = strchr(ip->_text.c_str(), '.');
|
|
if (p) {
|
|
ip->_autoGet = Common::String(p);
|
|
ip->_text = Common::String(ip->_text.c_str(), p);
|
|
ip->_autoGet.deleteChar(0);
|
|
ip->_autoGet.deleteChar(0);
|
|
const char *t = strchr(ip->_autoGet.c_str(), '.');
|
|
if (t)
|
|
ip->_autoGet = Common::String(ip->_autoGet.c_str(), t);
|
|
for (uint i = 1; i < ip->_autoGet.size(); i++)
|
|
ip->_autoGet.replace(i, 1, Common::String(toupper(ip->_autoGet[i])));
|
|
}
|
|
}
|
|
ct++;
|
|
} while (ct < ni + 1);
|
|
} else {
|
|
do {
|
|
ip = &_G(_items)[ct];
|
|
c = *(ptr++);
|
|
text[charindex] = c;
|
|
if (c == 0) {
|
|
ip->_text = text;
|
|
if (loud)
|
|
debug("Item %d: %s\n", ct, ip->_text.c_str());
|
|
const char *p = strchr(ip->_text.c_str(), '/');
|
|
/* Some games use // to mean no auto get/drop word! */
|
|
if (p) {
|
|
ip->_autoGet = Common::String(p);
|
|
if (!(ip->_autoGet == "//") && !(ip->_autoGet == "/*")) {
|
|
ip->_text = Common::String(ip->_text.c_str(), p);
|
|
ip->_autoGet.deleteChar(0);
|
|
const char *t = strchr(ip->_autoGet.c_str(), '/');
|
|
if (t) {
|
|
ip->_autoGet = Common::String(ip->_autoGet.c_str(), t);
|
|
}
|
|
}
|
|
}
|
|
ct++;
|
|
charindex = 0;
|
|
} else {
|
|
charindex++;
|
|
}
|
|
} while (ct < ni + 1);
|
|
}
|
|
|
|
// item locations
|
|
if (seekIfNeeded(info._startOfItemLocations, &offset, &ptr) == 0)
|
|
return 0;
|
|
|
|
ct = 0;
|
|
while (ct < ni + 1) {
|
|
ip = &_G(_items)[ct];
|
|
ip->_location = *(ptr++);
|
|
ip->_initialLoc = ip->_location;
|
|
ct++;
|
|
}
|
|
|
|
// vector images
|
|
if (info._numberOfPictures > 0 && info._pictureFormatVersion == 99) {
|
|
loadVectorData(info, ptr);
|
|
}
|
|
|
|
// system messages
|
|
if (seekIfNeeded(info._startOfSystemMessages, &offset, &ptr) == 0)
|
|
return 1;
|
|
jumpSysMess:
|
|
ptr = seekToPos(_G(_entireFile), offset);
|
|
ct = 0;
|
|
charindex = 0;
|
|
|
|
do {
|
|
c = *(ptr++);
|
|
if ((ptr > _G(_entireFile) && static_cast<size_t>(ptr - _G(_entireFile)) > _G(_fileLength)) || ptr < _G(_entireFile)) {
|
|
debug("Read out of bounds!\n");
|
|
return 0;
|
|
}
|
|
if (charindex > 255)
|
|
charindex = 0;
|
|
text[charindex] = c;
|
|
if (c == 0 || c == 0x0d) {
|
|
if (charindex > 0) {
|
|
if (c == 0x0d)
|
|
charindex++;
|
|
if (ct == 0 && (info._subType & (C64 | ENGLISH)) == (C64 | ENGLISH) && memcmp(text, "NORTH", 5) != 0) {
|
|
offset--;
|
|
goto jumpSysMess;
|
|
}
|
|
text[charindex] = '\0';
|
|
_G(_systemMessages)[ct] = Common::String(text, charindex + 1);
|
|
if (loud)
|
|
debug("system_messages %d: \"%s\"\n", ct, _G(_systemMessages)[ct].c_str());
|
|
ct++;
|
|
charindex = 0;
|
|
}
|
|
} else {
|
|
charindex++;
|
|
}
|
|
} while (ct < 45);
|
|
|
|
if (loud)
|
|
debug("Offset after reading system messages: %d\n", static_cast<int>(ptr - _G(_entireFile)));
|
|
|
|
if ((info._subType & (C64 | ENGLISH)) == (C64 | ENGLISH)) {
|
|
return 1;
|
|
}
|
|
|
|
if (seekIfNeeded(info._startOfDirections, &offset, &ptr) == 0)
|
|
return 0;
|
|
|
|
charindex = 0;
|
|
|
|
ct = 0;
|
|
do {
|
|
c = *(ptr++);
|
|
text[charindex] = c;
|
|
if (c == 0 || c == 0x0d) {
|
|
if (charindex > 0) {
|
|
if (c == 0x0d)
|
|
charindex++;
|
|
text[charindex] = '\0';
|
|
_G(_sys)[ct] = Common::String(text, charindex + 1);
|
|
ct++;
|
|
charindex = 0;
|
|
}
|
|
} else {
|
|
charindex++;
|
|
}
|
|
|
|
if (c != 0 && c != 0x0d && !isascii(c))
|
|
break;
|
|
} while (ct < 6);
|
|
|
|
return 1;
|
|
}
|
|
|
|
DictionaryType getId(int *offset) {
|
|
for (int i = 0; i < 9; i++) {
|
|
*offset = findCode(g_dictKeys[i]._signature, 0);
|
|
if (*offset != -1) {
|
|
if (i == 4 || i == 5) // GERMAN
|
|
*offset -= 5;
|
|
else if (i == 6) // SPANISH
|
|
*offset -= 8;
|
|
else if (i == 7) // Claymorgue
|
|
*offset -= 11;
|
|
return g_dictKeys[i]._dict;
|
|
}
|
|
}
|
|
|
|
return NOT_A_GAME;
|
|
}
|
|
|
|
int findCode(const char *x, int base) {
|
|
unsigned const char *p = _G(_entireFile) + base;
|
|
int len = strlen(x);
|
|
if (len < 7)
|
|
len = 7;
|
|
while (p < _G(_entireFile) + _G(_fileLength) - len) {
|
|
if (memcmp(p, x, len) == 0) {
|
|
return p - _G(_entireFile);
|
|
}
|
|
p++;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
} // End of namespace Scott
|
|
} // End of namespace Glk
|