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
|