mirror of
https://github.com/scummvm/scummvm.git
synced 2025-04-02 10:52:32 -04:00
529 lines
13 KiB
C++
529 lines
13 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 "glk/scott/scott.h"
|
|
#include "glk/scott/globals.h"
|
|
#include "glk/scott/resource.h"
|
|
#include "glk/scott/game_info.h"
|
|
#include "glk/scott/load_ti99_4a.h"
|
|
|
|
namespace Glk {
|
|
namespace Scott {
|
|
|
|
struct DataHeader {
|
|
uint8_t _numObjects; /* number of objects */
|
|
uint8_t _numVerbs; /* number of verbs */
|
|
uint8_t _numNouns; /* number of nouns */
|
|
uint8_t _redRoom; /* the red room (dead room) */
|
|
uint8_t _maxItemsCarried; /* max number of items can be carried */
|
|
uint8_t _beginLocn; /* room to start in */
|
|
uint8_t _numTreasures; /* number of treasures */
|
|
uint8_t _cmdLength; /* number of letters in commands */
|
|
uint16_t _lightTurns; /* max number of turns light lasts */
|
|
uint8_t _treasureLocn; /* location of where to store treasures */
|
|
uint8_t _strange; /* !?! not known. */
|
|
|
|
uint16_t _pObjTable; /* pointer to object table */
|
|
uint16_t _pOrigItems; /* pointer to original items */
|
|
uint16_t _pObjLink; /* pointer to link table from noun to object */
|
|
uint16_t _pObjDescr; /* pointer to object descriptions */
|
|
uint16_t _pMessage; /* pointer to message pointers */
|
|
uint16_t _pRoomExit; /* pointer to room exits table */
|
|
uint16_t _pRoomDescr; /* pointer to room descr table */
|
|
|
|
uint16_t _pNounTable; /* pointer to noun table */
|
|
uint16_t _pVerbTable; /* pointer to verb table */
|
|
|
|
uint16_t _pExplicit; /* pointer to explicit action table */
|
|
uint16_t _pImplicit; /* pointer to implicit actions */
|
|
};
|
|
|
|
uint16_t fixAddress(uint16_t ina) {
|
|
return (ina - 0x380 + _G(_fileBaselineOffset));
|
|
}
|
|
|
|
uint16_t fixWord(uint16_t word) {
|
|
return (((word & 0xFF) << 8) | ((word >> 8) & 0xFF));
|
|
}
|
|
|
|
uint16_t getWord(uint8_t *mem) {
|
|
uint16_t x = *(uint16_t *)mem;
|
|
return fixWord(x);
|
|
}
|
|
|
|
void getMaxTI99Messages(DataHeader dh) {
|
|
uint8_t *msg;
|
|
uint16_t msg1;
|
|
|
|
msg = _G(_entireFile) + fixAddress(fixWord(dh._pMessage));
|
|
msg1 = fixAddress(getWord(msg));
|
|
_G(_maxMessages) = (msg1 - fixAddress(fixWord(dh._pMessage))) / 2;
|
|
}
|
|
|
|
void getMaxTI99Items(DataHeader dh) {
|
|
uint8_t *msg;
|
|
uint16_t msg1;
|
|
|
|
msg = _G(_entireFile) + fixAddress(fixWord(dh._pObjDescr));
|
|
msg1 = fixAddress(getWord(msg));
|
|
_G(_maxItemDescr) = (msg1 - fixAddress(fixWord(dh._pObjDescr))) / 2;
|
|
}
|
|
|
|
uint8_t *getTI994AWord(uint8_t* string, uint8_t** result, size_t* length) {
|
|
uint8_t *msg;
|
|
|
|
msg = string;
|
|
*length = msg[0];
|
|
if (*length == 0 || *length > 100) {
|
|
*length = 0;
|
|
*result = nullptr;
|
|
return nullptr;
|
|
}
|
|
msg++;
|
|
*result = new uint8_t[*length];
|
|
memcpy(*result, msg, *length);
|
|
|
|
msg += *length;
|
|
|
|
return (msg);
|
|
}
|
|
|
|
char *getTI994AString(uint16_t table, int tableOffset) {
|
|
uint8_t *msgx, *msgy, *nextword;
|
|
char *result;
|
|
uint16_t msg1, msg2;
|
|
uint8_t buffer[1024];
|
|
size_t length, totalLength = 0;
|
|
|
|
uint8_t *game = _G(_entireFile);
|
|
|
|
msgx = game + fixAddress(fixWord(table));
|
|
|
|
msgx += tableOffset * 2;
|
|
msg1 = fixAddress(getWord((uint8_t *)msgx));
|
|
msg2 = fixAddress(getWord((uint8_t *)msgx + 2));
|
|
|
|
msgy = game + msg2;
|
|
msgx = game + msg1;
|
|
|
|
while (msgx < msgy) {
|
|
msgx = getTI994AWord(msgx, &nextword, &length);
|
|
if (length == 0 || nextword == nullptr) {
|
|
return nullptr;
|
|
}
|
|
if (length > 100) {
|
|
delete[] nextword;
|
|
return nullptr;
|
|
}
|
|
memcpy(buffer + totalLength, nextword, length);
|
|
delete[] nextword;
|
|
totalLength += length;
|
|
if (totalLength > 1000)
|
|
break;
|
|
if (msgx < msgy)
|
|
buffer[totalLength++] = ' ';
|
|
}
|
|
if (totalLength == 0)
|
|
return nullptr;
|
|
totalLength++;
|
|
result = new char[totalLength];
|
|
memcpy(result, buffer, totalLength);
|
|
result[totalLength - 1] = '\0';
|
|
return result;
|
|
}
|
|
|
|
void loadTI994ADict(int vorn, uint16_t table, int numWords, Common::StringArray &dict) {
|
|
uint16_t *wtable;
|
|
int i;
|
|
int wordLen;
|
|
char *w1;
|
|
char *w2;
|
|
|
|
/* table is either verb or noun table */
|
|
wtable = (uint16_t *)(_G(_entireFile) + fixAddress(fixWord(table)));
|
|
|
|
for (i = 0; i <= numWords; i++) {
|
|
do {
|
|
w1 = (char *)_G(_entireFile) + fixAddress(getWord((uint8_t *)wtable + (i * 2)));
|
|
w2 = (char *)_G(_entireFile) + fixAddress(getWord((uint8_t *)wtable + ((1 + i) * 2)));
|
|
} while (w1 == w2);
|
|
|
|
wordLen = w2 - w1;
|
|
|
|
if (wordLen < 20) {
|
|
char *text = new char[wordLen + 1];
|
|
strncpy(text, w1, wordLen);
|
|
text[wordLen] = 0;
|
|
dict[i] = text;
|
|
}
|
|
}
|
|
}
|
|
|
|
void readTI99ImplicitActions(DataHeader dh) {
|
|
uint8_t *ptr, *implicitStart;
|
|
int loopFlag;
|
|
|
|
implicitStart = _G(_entireFile) + fixAddress(fixWord(dh._pImplicit));
|
|
ptr = implicitStart;
|
|
loopFlag = 0;
|
|
|
|
/* fall out, if no auto acts in the game. */
|
|
if (*ptr == 0x0)
|
|
loopFlag = 1;
|
|
|
|
while (loopFlag == 0) {
|
|
if (ptr[1] == 0)
|
|
loopFlag = 1;
|
|
|
|
/* skip code chunk */
|
|
ptr += 1 + ptr[1];
|
|
}
|
|
|
|
_G(_ti99ImplicitExtent) = MIN(_G(_fileLength), static_cast<size_t>(ptr - _G(_entireFile)));
|
|
if (_G(_ti99ImplicitExtent)) {
|
|
_G(_ti99ImplicitActions) = new uint8_t[_G(_ti99ImplicitExtent)];
|
|
memcpy(_G(_ti99ImplicitActions), implicitStart, _G(_ti99ImplicitExtent));
|
|
}
|
|
}
|
|
|
|
void readTI99ExplicitActions(DataHeader dh) {
|
|
uint8_t *ptr, *start, *end, *blockstart;
|
|
uint16_t address;
|
|
int loopFlag;
|
|
int i;
|
|
|
|
start = _G(_entireFile) + _G(_fileLength);
|
|
end = _G(_entireFile);
|
|
|
|
size_t explicitOffset = fixAddress(fixWord(dh._pExplicit));
|
|
blockstart = _G(_entireFile) + explicitOffset;
|
|
|
|
_G(_verbActionOffsets) = new uint8_t*[dh._numVerbs + 1];
|
|
|
|
for (i = 0; i <= dh._numVerbs; i++) {
|
|
ptr = blockstart;
|
|
address = getWord(ptr + ((i)*2));
|
|
|
|
_G(_verbActionOffsets)[i] = nullptr;
|
|
|
|
if (address != 0) {
|
|
ptr = _G(_entireFile) + fixAddress(address);
|
|
if (ptr < start)
|
|
start = ptr;
|
|
_G(_verbActionOffsets)[i] = ptr;
|
|
loopFlag = 0;
|
|
|
|
while (loopFlag != 1) {
|
|
if (ptr[1] == 0)
|
|
loopFlag = 1;
|
|
|
|
/* go to next block. */
|
|
ptr += 1 + ptr[1];
|
|
if (ptr > end)
|
|
end = ptr;
|
|
}
|
|
}
|
|
}
|
|
|
|
_G(_ti99ExplicitExtent) = end - start;
|
|
_G(_ti99ExplicitActions) = new uint8_t[_G(_ti99ExplicitExtent)];
|
|
memcpy(_G(_ti99ExplicitActions), start, _G(_ti99ExplicitExtent));
|
|
for (i = 0; i <= dh._numVerbs; i++) {
|
|
if (_G(_verbActionOffsets)[i] != nullptr) {
|
|
_G(_verbActionOffsets)[i] = _G(_ti99ExplicitActions) + (_G(_verbActionOffsets)[i] - start);
|
|
}
|
|
}
|
|
}
|
|
|
|
uint8_t *loadTitleScreen() {
|
|
char buf[3074];
|
|
int offset = 0;
|
|
uint8_t *p;
|
|
int lines;
|
|
|
|
/* title screen offset starts at 0x80 */
|
|
p = _G(_entireFile) + 0x80 + _G(_fileBaselineOffset);
|
|
if (static_cast<size_t>(p - _G(_entireFile)) > _G(_fileLength))
|
|
return nullptr;
|
|
int parens = 0;
|
|
for (lines = 0; lines < 24; lines++) {
|
|
for (int i = 0; i < 40; i++) {
|
|
char c = *(p++);
|
|
if (static_cast<size_t>(p - _G(_entireFile)) >= _G(_fileLength))
|
|
return nullptr;
|
|
if (c & 0x80) /* if not 7-bit ascii */
|
|
c = '?';
|
|
switch (c) {
|
|
case '\\':
|
|
c = ' ';
|
|
break;
|
|
case '(':
|
|
parens = 1;
|
|
break;
|
|
case ')':
|
|
if (!parens)
|
|
c = '@';
|
|
parens = 0;
|
|
break;
|
|
case '|':
|
|
if (*p != ' ')
|
|
c = 12;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
buf[offset++] = c;
|
|
if (offset >= 3072)
|
|
return nullptr;
|
|
}
|
|
buf[offset++] = '\n';
|
|
}
|
|
|
|
buf[offset] = '\0';
|
|
uint8_t *result = new uint8_t[offset + 1];
|
|
memcpy(result, buf, offset + 1);
|
|
return result;
|
|
}
|
|
|
|
int tryLoadingTI994A(DataHeader dh, int loud) {
|
|
int ni, nw, nr, mc, pr, tr, wl, lt, mn, trm;
|
|
int ct;
|
|
|
|
Room *rp;
|
|
Item *ip;
|
|
/* Load the header */
|
|
|
|
ni = dh._numObjects;
|
|
nw = MAX(dh._numVerbs, dh._numNouns);
|
|
nr = dh._redRoom;
|
|
mc = dh._maxItemsCarried;
|
|
pr = dh._beginLocn;
|
|
tr = 0;
|
|
trm = dh._treasureLocn;
|
|
wl = dh._cmdLength;
|
|
lt = fixWord(dh._lightTurns);
|
|
mn = _G(_maxMessages);
|
|
|
|
uint8_t *ptr = _G(_entireFile);
|
|
|
|
_G(_gameHeader)->_numItems = ni;
|
|
_G(_items).resize(ni + 1);
|
|
_G(_gameHeader)->_numActions = 0;
|
|
_G(_gameHeader)->_numWords = nw;
|
|
_G(_gameHeader)->_wordLength = wl;
|
|
_G(_verbs).resize(nw + 2);
|
|
_G(_nouns).resize(nw + 2);
|
|
_G(_gameHeader)->_numRooms = nr;
|
|
_G(_rooms).resize(nr + 1);
|
|
_G(_gameHeader)->_maxCarry = mc;
|
|
_G(_gameHeader)->_playerRoom = pr;
|
|
_G(_gameHeader)->_lightTime = lt;
|
|
_G(_lightRefill) = lt;
|
|
_G(_gameHeader)->_numMessages = mn;
|
|
_G(_messages).resize(mn + 1);
|
|
_G(_gameHeader)->_treasureRoom = trm;
|
|
|
|
int offset;
|
|
|
|
if (seekIfNeeded(fixAddress(fixWord(dh._pRoomDescr)), &offset, &ptr) == 0)
|
|
return 0;
|
|
|
|
ct = 0;
|
|
rp = &_G(_rooms)[0];
|
|
|
|
do {
|
|
char *res = getTI994AString(dh._pRoomDescr, ct);
|
|
rp->_text = res ? res : ".\0";
|
|
if (loud)
|
|
debug("Room %d: %s", ct, rp->_text.c_str());
|
|
rp->_image = 255;
|
|
ct++;
|
|
rp++;
|
|
} while (ct < nr + 1);
|
|
|
|
ct = 0;
|
|
while (ct < mn + 1) {
|
|
char *res = getTI994AString(dh._pMessage, ct);
|
|
_G(_messages)[ct] = res ? res : ".\0";
|
|
if (loud)
|
|
debug("Message %d: %s", ct, _G(_messages)[ct].c_str());
|
|
ct++;
|
|
}
|
|
|
|
ct = 0;
|
|
ip = &_G(_items)[0];
|
|
do {
|
|
char *res = getTI994AString(dh._pObjDescr, ct);
|
|
ip->_text = res ? res : ".\0";
|
|
if (ip->_text.size() && ip->_text[0] == '*')
|
|
tr++;
|
|
if (loud)
|
|
debug("Item %d: %s", ct, ip->_text.c_str());
|
|
ct++;
|
|
ip++;
|
|
} while (ct < ni + 1);
|
|
|
|
_G(_gameHeader)->_treasures = tr;
|
|
if (loud)
|
|
debug("Number of treasures %d", _G(_gameHeader)->_treasures);
|
|
|
|
if (seekIfNeeded(fixAddress(fixWord(dh._pRoomExit)), &offset, &ptr) == 0)
|
|
return 0;
|
|
|
|
ct = 0;
|
|
rp = &_G(_rooms)[0];
|
|
|
|
while (ct < nr + 1) {
|
|
for (int j = 0; j < 6; j++) {
|
|
rp->_exits[j] = *(ptr++ - _G(_fileBaselineOffset));
|
|
}
|
|
ct++;
|
|
rp++;
|
|
}
|
|
|
|
if (seekIfNeeded(fixAddress(fixWord(dh._pOrigItems)), &offset, &ptr) == 0)
|
|
return 0;
|
|
|
|
ct = 0;
|
|
ip = &_G(_items)[0];
|
|
while (ct < ni + 1) {
|
|
ip->_location = *(ptr++ - _G(_fileBaselineOffset));
|
|
ip->_initialLoc = ip->_location;
|
|
ip++;
|
|
ct++;
|
|
}
|
|
|
|
loadTI994ADict(0, dh._pVerbTable, dh._numVerbs + 1, _G(_verbs));
|
|
loadTI994ADict(1, dh._pNounTable, dh._numNouns + 1, _G(_nouns));
|
|
|
|
for (int i = 1; i <= dh._numNouns - dh._numVerbs; i++)
|
|
_G(_verbs)[dh._numVerbs + i] = ".\0";
|
|
|
|
for (int i = 1; i <= dh._numVerbs - dh._numNouns; i++)
|
|
_G(_nouns)[dh._numNouns + i] = ".\0";
|
|
|
|
if (loud) {
|
|
for (int i = 0; i <= _G(_gameHeader)->_numWords; i++)
|
|
debug("Verb %d: %s", i, _G(_verbs)[i].c_str());
|
|
for (int i = 0; i <= _G(_gameHeader)->_numWords; i++)
|
|
debug("Noun %d: %s", i, _G(_nouns)[i].c_str());
|
|
}
|
|
|
|
ct = 0;
|
|
ip = &_G(_items)[0];
|
|
|
|
if (seekIfNeeded(fixAddress(fixWord(dh._pObjLink)), &offset, &ptr) == 0) {
|
|
return 0;
|
|
}
|
|
|
|
int *objectlinks = new int[ni + 1];
|
|
|
|
do {
|
|
objectlinks[ct] = *(ptr++ - _G(_fileBaselineOffset));
|
|
if (objectlinks[ct] && objectlinks[ct] <= nw) {
|
|
ip->_autoGet = _G(_nouns)[objectlinks[ct]];
|
|
if (ct == 3 && scumm_strnicmp("bird", _G(_items)[ct]._text.c_str(), 4) == 0)
|
|
ip->_autoGet = "BIRD";
|
|
} else {
|
|
ip->_autoGet = "";
|
|
}
|
|
ct++;
|
|
ip++;
|
|
} while (ct < ni + 1);
|
|
|
|
delete[] objectlinks;
|
|
|
|
readTI99ImplicitActions(dh);
|
|
readTI99ExplicitActions(dh);
|
|
|
|
_G(_autoInventory) = 1;
|
|
_G(_sys)[INVENTORY] = "I'm carrying: ";
|
|
|
|
_G(_titleScreen) = (char *)loadTitleScreen();
|
|
delete[] _G(_entireFile);
|
|
|
|
for (int i = 0; i < MAX_SYSMESS && g_sysDictTI994A[i] != nullptr; i++) {
|
|
_G(_sys)[i] = g_sysDictTI994A[i];
|
|
}
|
|
|
|
_G(_options) |= TI994A_STYLE;
|
|
return TI994A;
|
|
}
|
|
|
|
void readHeader(Common::SeekableReadStream *f, DataHeader &dh) {
|
|
f->seek(0);
|
|
f->seek(_G(_fileBaselineOffset) + 0x8a0);
|
|
dh._numObjects = f->readByte();
|
|
dh._numVerbs = f->readByte();
|
|
dh._numNouns = f->readByte();
|
|
dh._redRoom = f->readByte();
|
|
dh._maxItemsCarried = f->readByte();
|
|
dh._beginLocn = f->readByte();
|
|
dh._numTreasures = f->readByte();
|
|
dh._cmdLength = f->readByte();
|
|
dh._lightTurns = f->readUint16LE();
|
|
dh._treasureLocn = f->readByte();
|
|
dh._strange = f->readByte();
|
|
|
|
dh._pObjTable = f->readUint16LE();
|
|
dh._pOrigItems = f->readUint16LE();
|
|
dh._pObjLink = f->readUint16LE();
|
|
dh._pObjDescr = f->readUint16LE();
|
|
dh._pMessage = f->readUint16LE();
|
|
dh._pRoomExit = f->readUint16LE();
|
|
dh._pRoomDescr = f->readUint16LE();
|
|
|
|
dh._pNounTable = f->readUint16LE();
|
|
dh._pVerbTable = f->readUint16LE();
|
|
|
|
dh._pExplicit = f->readUint16LE();
|
|
dh._pImplicit = f->readUint16LE();
|
|
}
|
|
|
|
GameIDType detectTI994A(Common::SeekableReadStream *f, uint8_t **sf, size_t *extent) {
|
|
int offset = findCode("\x30\x30\x30\x30\x00\x30\x30\x00\x28\x28", 0);
|
|
if (offset == -1)
|
|
return UNKNOWN_GAME;
|
|
|
|
_G(_fileBaselineOffset) = offset - 0x589;
|
|
|
|
DataHeader dh;
|
|
readHeader(f, dh);
|
|
|
|
getMaxTI99Messages(dh);
|
|
getMaxTI99Items(dh);
|
|
|
|
return static_cast<GameIDType>(tryLoadingTI994A(dh, 0));
|
|
}
|
|
|
|
} // End of namespace Scott
|
|
} // End of namespace Glk
|