mirror of
https://github.com/libretro/scummvm.git
synced 2024-12-22 18:02:05 +00:00
b6c7385eb4
- Added MIDI resource type - Added ScriptFunctionsLgop2 and ScriptFunctionsMhne (for Leather Goddesses of Phobos 2 and The Manhole: New and Enhanced, resp.) - Many changes for LGOP2 and The Manhole: N&E Note about the new ScriptFunctions classes: I copied the ScriptFunctionsRtz class and so duplicated a lot of code. Most of the opcode functions are the same in all games but there might be differences. Once all common opcode functions have been figured out, they'll be moved to a common place (like the ScriptFunctions class). svn-id: r31871
621 lines
15 KiB
C++
621 lines
15 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.
|
|
*
|
|
* $URL$
|
|
* $Id$
|
|
*
|
|
*/
|
|
|
|
#include "common/system.h"
|
|
#include "common/endian.h"
|
|
#include "common/util.h"
|
|
#include "common/savefile.h"
|
|
|
|
#include "made/database.h"
|
|
|
|
namespace Made {
|
|
|
|
/*
|
|
Class types:
|
|
0x7FFF byte array
|
|
0x7FFE word array
|
|
< 0x7FFE object
|
|
*/
|
|
|
|
Object::Object() {
|
|
}
|
|
|
|
Object::~Object() {
|
|
if (_freeData && _objData)
|
|
delete[] _objData;
|
|
}
|
|
|
|
int Object::loadVersion2(Common::SeekableReadStream &source) {
|
|
_freeData = true;
|
|
uint16 type = source.readUint16LE();
|
|
if (type == 0x7FFF) {
|
|
_objSize = source.readUint16LE();
|
|
} else if (type == 0x7FFE) {
|
|
_objSize = source.readUint16LE() * 2;
|
|
} else if (type < 0x7FFE) {
|
|
byte count1 = source.readByte();
|
|
byte count2 = source.readByte();
|
|
_objSize = (count1 + count2) * 2;
|
|
}
|
|
source.seek(-4, SEEK_CUR);
|
|
_objSize += 6;
|
|
_objData = new byte[_objSize];
|
|
WRITE_LE_UINT16(_objData, 1);
|
|
source.read(_objData + 2, _objSize - 2);
|
|
return _objSize - 2;
|
|
}
|
|
|
|
int Object::loadVersion3(Common::SeekableReadStream &source) {
|
|
_freeData = true;
|
|
source.readUint16LE(); // skip flags
|
|
uint16 type = source.readUint16LE();
|
|
if (type == 0x7FFF) {
|
|
_objSize = source.readUint16LE();
|
|
} else if (type == 0x7FFE) {
|
|
_objSize = source.readUint16LE() * 2;
|
|
} else if (type < 0x7FFE) {
|
|
byte count1 = source.readByte();
|
|
byte count2 = source.readByte();
|
|
_objSize = (count1 + count2) * 2;
|
|
}
|
|
source.seek(-6, SEEK_CUR);
|
|
_objSize += 6;
|
|
_objData = new byte[_objSize];
|
|
source.read(_objData, _objSize);
|
|
return _objSize;
|
|
}
|
|
|
|
int Object::loadVersion3(byte *source) {
|
|
_objData = source;
|
|
_freeData = false;
|
|
if (getClass() < 0x7FFE) {
|
|
_objSize = (getCount1() + getCount2()) * 2;
|
|
} else {
|
|
_objSize = getSize();
|
|
}
|
|
_objSize += 6;
|
|
return _objSize;
|
|
}
|
|
|
|
uint16 Object::getFlags() const {
|
|
return READ_LE_UINT16(_objData);
|
|
}
|
|
|
|
uint16 Object::getClass() const {
|
|
return READ_LE_UINT16(_objData + 2);
|
|
}
|
|
|
|
uint16 Object::getSize() const {
|
|
return READ_LE_UINT16(_objData + 4);
|
|
}
|
|
|
|
byte Object::getCount1() const {
|
|
return _objData[4];
|
|
}
|
|
|
|
byte Object::getCount2() const {
|
|
return _objData[5];
|
|
}
|
|
|
|
byte *Object::getData() {
|
|
return _objData + 6;
|
|
}
|
|
|
|
const char *Object::getString() {
|
|
if (getClass() == 0x7FFF)
|
|
return (const char*)getData();
|
|
else
|
|
return NULL;
|
|
}
|
|
|
|
void Object::setString(const char *str) {
|
|
if (getClass() == 0x7FFF) {
|
|
char *objStr = (char*)getData();
|
|
if (str)
|
|
strncpy(objStr, str, getSize());
|
|
else
|
|
objStr[0] = '\0';
|
|
}
|
|
}
|
|
|
|
bool Object::isObject() {
|
|
return getClass() < 0x7FFE;
|
|
}
|
|
|
|
bool Object::isVector() {
|
|
return getClass() == 0x7FFF;
|
|
}
|
|
|
|
int16 Object::getVectorSize() {
|
|
if (getClass() == 0x7FFF || getClass() == 0x7FFE) {
|
|
return getSize();
|
|
} else if (getClass() < 0x7FFE) {
|
|
return getCount1() + getCount2();
|
|
} else {
|
|
// should never reach here
|
|
error("Unknown object class");
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
int16 Object::getVectorItem(int16 index) {
|
|
if (getClass() == 0x7FFF) {
|
|
byte *vector = (byte*)getData();
|
|
return vector[index];
|
|
} else if (getClass() == 0x7FFE) {
|
|
int16 *vector = (int16*)getData();
|
|
return READ_LE_UINT16(&vector[index]);
|
|
} else if (getClass() < 0x7FFE) {
|
|
int16 *vector = (int16*)getData();
|
|
return READ_LE_UINT16(&vector[index]);
|
|
} else {
|
|
// should never reach here
|
|
error("Unknown object class");
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
void Object::setVectorItem(int16 index, int16 value) {
|
|
if (getClass() == 0x7FFF) {
|
|
byte *vector = (byte*)getData();
|
|
vector[index] = value;
|
|
} else if (getClass() <= 0x7FFE) {
|
|
int16 *vector = (int16*)getData();
|
|
WRITE_LE_UINT16(&vector[index], value);
|
|
}
|
|
}
|
|
|
|
void Object::dump(const char *filename) {
|
|
/*
|
|
FILE *o = fopen(filename, "wb");
|
|
fwrite(_objData, _objSize, 1, o);
|
|
fclose(o);
|
|
*/
|
|
}
|
|
|
|
GameDatabase::GameDatabase(MadeEngine *vm) : _vm(vm) {
|
|
_gameText = NULL;
|
|
}
|
|
|
|
GameDatabase::~GameDatabase() {
|
|
if (_gameState)
|
|
delete[] _gameState;
|
|
if (_gameText)
|
|
delete[] _gameText;
|
|
}
|
|
|
|
void GameDatabase::open(const char *filename) {
|
|
debug(1, "GameDatabase::open() Loading from %s", filename);
|
|
Common::File fd;
|
|
if (!fd.open(filename))
|
|
error("GameDatabase::open() Could not open %s", filename);
|
|
load(fd);
|
|
fd.close();
|
|
}
|
|
|
|
void GameDatabase::openFromRed(const char *redFilename, const char *filename) {
|
|
debug(1, "GameDatabase::openFromRed() Loading from %s->%s", redFilename, filename);
|
|
Common::MemoryReadStream *fileS = RedReader::loadFromRed(redFilename, filename);
|
|
if (!fileS)
|
|
error("GameDatabase::openFromRed() Could not load %s from %s", filename, redFilename);
|
|
load(*fileS);
|
|
delete fileS;
|
|
}
|
|
|
|
void GameDatabase::load(Common::SeekableReadStream &sourceS) {
|
|
|
|
if (_vm->getGameID() == GID_MANHOLE || _vm->getGameID() == GID_LGOP2) {
|
|
debug(2, "loading version 2 dat");
|
|
loadVersion2(sourceS);
|
|
} else if (_vm->getGameID() == GID_RTZ) {
|
|
debug(2, "loading version 3 dat");
|
|
loadVersion3(sourceS);
|
|
}
|
|
|
|
}
|
|
|
|
void GameDatabase::loadVersion2(Common::SeekableReadStream &sourceS) {
|
|
|
|
// TODO: Read/verifiy header
|
|
|
|
sourceS.seek(0x1C);
|
|
|
|
uint32 textOffs = sourceS.readUint16LE() * 512;
|
|
uint16 objectCount = sourceS.readUint16LE();
|
|
uint16 varObjectCount = sourceS.readUint16LE();
|
|
_gameStateSize = sourceS.readUint16LE() * 2;
|
|
sourceS.readUint16LE(); // unknown
|
|
uint32 objectsOffs = sourceS.readUint16LE() * 512;
|
|
sourceS.readUint16LE(); // unknown
|
|
_mainCodeObjectIndex = sourceS.readUint16LE();
|
|
sourceS.readUint16LE(); // unknown
|
|
uint32 objectsSize = sourceS.readUint32LE() * 2;
|
|
uint32 textSize = objectsOffs - textOffs;
|
|
|
|
debug(2, "textOffs = %08X; textSize = %08X; objectCount = %d; varObjectCount = %d; gameStateSize = %d; objectsOffs = %08X; objectsSize = %d\n", textOffs, textSize, objectCount, varObjectCount, _gameStateSize, objectsOffs, objectsSize);
|
|
|
|
_gameState = new byte[_gameStateSize];
|
|
memset(_gameState, 0, _gameStateSize);
|
|
|
|
sourceS.seek(textOffs);
|
|
_gameText = new char[textSize];
|
|
sourceS.read(_gameText, textSize);
|
|
// "Decrypt" the text data
|
|
for (int i = 0; i < textSize; i++)
|
|
_gameText[i] += 0x1E;
|
|
|
|
sourceS.seek(objectsOffs);
|
|
|
|
for (uint32 i = 0; i < objectCount; i++) {
|
|
Object *obj = new Object();
|
|
int objSize = obj->loadVersion2(sourceS);
|
|
objSize = objSize % 2;
|
|
// objects are aligned on 2-byte-boundaries, skip unused bytes
|
|
sourceS.skip(objSize);
|
|
_objects.push_back(obj);
|
|
}
|
|
|
|
}
|
|
|
|
void GameDatabase::loadVersion3(Common::SeekableReadStream &sourceS) {
|
|
|
|
// TODO: Read/verifiy header
|
|
|
|
sourceS.seek(0x1E);
|
|
|
|
uint32 objectIndexOffs = sourceS.readUint32LE();
|
|
uint16 objectCount = sourceS.readUint16LE();
|
|
uint32 gameStateOffs = sourceS.readUint32LE();
|
|
_gameStateSize = sourceS.readUint32LE();
|
|
uint32 objectsOffs = sourceS.readUint32LE();
|
|
uint32 objectsSize = sourceS.readUint32LE();
|
|
_mainCodeObjectIndex = sourceS.readUint16LE();
|
|
|
|
debug(2, "objectIndexOffs = %08X; objectCount = %d; gameStateOffs = %08X; gameStateSize = %d; objectsOffs = %08X; objectsSize = %d\n", objectIndexOffs, objectCount, gameStateOffs, _gameStateSize, objectsOffs, objectsSize);
|
|
|
|
_gameState = new byte[_gameStateSize];
|
|
sourceS.seek(gameStateOffs);
|
|
sourceS.read(_gameState, _gameStateSize);
|
|
|
|
Common::Array<uint32> objectOffsets;
|
|
sourceS.seek(objectIndexOffs);
|
|
for (uint32 i = 0; i < objectCount; i++)
|
|
objectOffsets.push_back(sourceS.readUint32LE());
|
|
|
|
for (uint32 i = 0; i < objectCount; i++) {
|
|
Object *obj = new Object();
|
|
|
|
// The LSB indicates if it's a constant or variable object.
|
|
// Constant objects are loaded from disk, while variable objects exist
|
|
// in the _gameState buffer.
|
|
|
|
debug(2, "obj(%04X) ofs = %08X\n", i, objectOffsets[i]);
|
|
|
|
if (objectOffsets[i] & 1) {
|
|
debug(2, "-> const %08X\n", objectsOffs + objectOffsets[i] - 1);
|
|
sourceS.seek(objectsOffs + objectOffsets[i] - 1);
|
|
obj->loadVersion3(sourceS);
|
|
} else {
|
|
debug(2, "-> var\n");
|
|
obj->loadVersion3(_gameState + objectOffsets[i]);
|
|
}
|
|
_objects.push_back(obj);
|
|
}
|
|
|
|
}
|
|
|
|
bool GameDatabase::getSavegameDescription(const char *filename, Common::String &description) {
|
|
|
|
Common::InSaveFile *in;
|
|
|
|
if (!(in = g_system->getSavefileManager()->openForLoading(filename))) {
|
|
return false;
|
|
}
|
|
|
|
char desc[64];
|
|
|
|
in->skip(4); // TODO: Verify marker 'SGAM'
|
|
in->skip(4); // TODO: Verify size
|
|
in->skip(2); // TODO: Verify version
|
|
in->read(desc, 64);
|
|
description = desc;
|
|
|
|
printf("description = %s\n", description.c_str()); fflush(stdout);
|
|
|
|
delete in;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
int16 GameDatabase::savegame(const char *filename, const char *description, int16 version) {
|
|
|
|
Common::OutSaveFile *out;
|
|
|
|
if (!(out = g_system->getSavefileManager()->openForSaving(filename))) {
|
|
warning("Can't create file '%s', game not saved", filename);
|
|
return 6;
|
|
}
|
|
|
|
uint32 size = 4 + 4 + 2 + _gameStateSize;
|
|
char desc[64];
|
|
|
|
strncpy(desc, description, 64);
|
|
|
|
out->writeUint32BE(MKID_BE('SGAM'));
|
|
out->writeUint32LE(size);
|
|
out->writeUint16LE(version);
|
|
out->write(desc, 64);
|
|
out->write(_gameState, _gameStateSize);
|
|
|
|
delete out;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
int16 GameDatabase::loadgame(const char *filename, int16 version) {
|
|
|
|
Common::InSaveFile *in;
|
|
|
|
if (!(in = g_system->getSavefileManager()->openForLoading(filename))) {
|
|
warning("Can't open file '%s', game not loaded", filename);
|
|
return 1;
|
|
}
|
|
|
|
//uint32 expectedSize = 4 + 4 + 2 + _gameStateSize;
|
|
|
|
in->skip(4); // TODO: Verify marker 'SGAM'
|
|
in->skip(4); // TODO: Verify size
|
|
in->skip(2); // TODO: Verify version
|
|
in->skip(64); // skip savegame description
|
|
in->read(_gameState, _gameStateSize);
|
|
|
|
delete in;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
int16 GameDatabase::getVar(int16 index) {
|
|
return (int16)READ_LE_UINT16(_gameState + index * 2);
|
|
}
|
|
|
|
void GameDatabase::setVar(int16 index, int16 value) {
|
|
WRITE_LE_UINT16(_gameState + index * 2, value);
|
|
}
|
|
|
|
int16 *GameDatabase::getObjectPropertyPtrV2(int16 objectIndex, int16 propertyId, int16 &propertyFlag) {
|
|
Object *obj = getObject(objectIndex);
|
|
|
|
int16 *prop = (int16*)obj->getData();
|
|
byte count1 = obj->getCount1();
|
|
byte count2 = obj->getCount2();
|
|
|
|
int16 *propPtr1 = prop + count1;
|
|
int16 *propPtr2 = prop + count2;
|
|
|
|
// First see if the property exists in the given object
|
|
while (count2-- > 0) {
|
|
if ((READ_LE_UINT16(prop) & 0x7FFF) == propertyId) {
|
|
propertyFlag = obj->getFlags() & 1;
|
|
return propPtr1;
|
|
}
|
|
prop++;
|
|
propPtr1++;
|
|
}
|
|
|
|
// Now check in the object hierarchy of the given object
|
|
int16 parentObjectIndex = obj->getClass();
|
|
if (parentObjectIndex == 0) {
|
|
//debug(2, "! NULL(np)\n");
|
|
return NULL;
|
|
}
|
|
|
|
while (parentObjectIndex != 0) {
|
|
|
|
//debug(2, "parentObjectIndex = %04X\n", parentObjectIndex);
|
|
|
|
obj = getObject(parentObjectIndex);
|
|
|
|
prop = (int16*)obj->getData();
|
|
count1 = obj->getCount1();
|
|
count2 = obj->getCount2();
|
|
|
|
propPtr1 = propPtr2 + count1 - count2;
|
|
int16 *propertyPtr = prop + count1;
|
|
|
|
while (count2-- > 0) {
|
|
if (!(READ_LE_UINT16(prop) & 0x8000)) {
|
|
if ((READ_LE_UINT16(prop) & 0x7FFF) == propertyId) {
|
|
propertyFlag = obj->getFlags() & 1;
|
|
return propPtr1;
|
|
} else {
|
|
propPtr1++;
|
|
}
|
|
} else {
|
|
if ((READ_LE_UINT16(prop) & 0x7FFF) == propertyId) {
|
|
propertyFlag = obj->getFlags() & 1;
|
|
return propertyPtr;
|
|
}
|
|
}
|
|
prop++;
|
|
propertyPtr++;
|
|
}
|
|
|
|
parentObjectIndex = obj->getClass();
|
|
|
|
}
|
|
|
|
//debug(2, "! NULL(nf)\n");
|
|
return NULL;
|
|
|
|
}
|
|
|
|
int16 *GameDatabase::getObjectPropertyPtrV3(int16 objectIndex, int16 propertyId, int16 &propertyFlag) {
|
|
Object *obj = getObject(objectIndex);
|
|
|
|
int16 *prop = (int16*)obj->getData();
|
|
byte count1 = obj->getCount1();
|
|
byte count2 = obj->getCount2();
|
|
|
|
int16 *propPtr1 = prop + count1;
|
|
int16 *propPtr2 = prop + count2;
|
|
|
|
// First see if the property exists in the given object
|
|
while (count2-- > 0) {
|
|
if ((READ_LE_UINT16(prop) & 0x3FFF) == propertyId) {
|
|
if (READ_LE_UINT16(prop) & 0x4000) {
|
|
propertyFlag = 1;
|
|
return (int16*)_gameState + READ_LE_UINT16(propPtr1);
|
|
} else {
|
|
propertyFlag = obj->getFlags() & 1;
|
|
return propPtr1;
|
|
}
|
|
}
|
|
prop++;
|
|
propPtr1++;
|
|
}
|
|
|
|
// Now check in the object hierarchy of the given object
|
|
int16 parentObjectIndex = obj->getClass();
|
|
if (parentObjectIndex == 0) {
|
|
//debug(2, "! NULL(np)\n");
|
|
return NULL;
|
|
}
|
|
|
|
while (parentObjectIndex != 0) {
|
|
|
|
//debug(2, "parentObjectIndex = %04X\n", parentObjectIndex);
|
|
|
|
obj = getObject(parentObjectIndex);
|
|
|
|
prop = (int16*)obj->getData();
|
|
count1 = obj->getCount1();
|
|
count2 = obj->getCount2();
|
|
|
|
propPtr1 = propPtr2 + count1 - count2;
|
|
int16 *propertyPtr = prop + count1;
|
|
|
|
while (count2-- > 0) {
|
|
if (!(READ_LE_UINT16(prop) & 0x8000)) {
|
|
if ((READ_LE_UINT16(prop) & 0x3FFF) == propertyId) {
|
|
if (READ_LE_UINT16(prop) & 0x4000) {
|
|
propertyFlag = 1;
|
|
return (int16*)_gameState + READ_LE_UINT16(propPtr1);
|
|
} else {
|
|
propertyFlag = obj->getFlags() & 1;
|
|
return propPtr1;
|
|
}
|
|
} else {
|
|
propPtr1++;
|
|
}
|
|
} else {
|
|
if ((READ_LE_UINT16(prop) & 0x3FFF) == propertyId) {
|
|
if (READ_LE_UINT16(prop) & 0x4000) {
|
|
propertyFlag = 1;
|
|
return (int16*)_gameState + READ_LE_UINT16(propertyPtr);
|
|
} else {
|
|
propertyFlag = obj->getFlags() & 1;
|
|
return propertyPtr;
|
|
}
|
|
}
|
|
}
|
|
prop++;
|
|
propertyPtr++;
|
|
}
|
|
|
|
parentObjectIndex = obj->getClass();
|
|
|
|
}
|
|
|
|
//debug(2, "! NULL(nf)\n");
|
|
return NULL;
|
|
|
|
}
|
|
|
|
int16 *GameDatabase::getObjectPropertyPtr(int16 objectIndex, int16 propertyId, int16 &propertyFlag) {
|
|
switch (_vm->_engineVersion) {
|
|
case 2:
|
|
return getObjectPropertyPtrV2(objectIndex, propertyId, propertyFlag);
|
|
case 3:
|
|
return getObjectPropertyPtrV3(objectIndex, propertyId, propertyFlag);
|
|
default:
|
|
error("GameDatabase::getObjectPropertyPtr() Unknown engine version");
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
int16 GameDatabase::getObjectProperty(int16 objectIndex, int16 propertyId) {
|
|
|
|
if (objectIndex == 0)
|
|
return 0;
|
|
|
|
int16 propertyFlag;
|
|
int16 *property = getObjectPropertyPtr(objectIndex, propertyId, propertyFlag);
|
|
|
|
if (property) {
|
|
return (int16)READ_LE_UINT16(property);
|
|
} else {
|
|
return 0;
|
|
}
|
|
|
|
}
|
|
|
|
int16 GameDatabase::setObjectProperty(int16 objectIndex, int16 propertyId, int16 value) {
|
|
|
|
if (objectIndex == 0)
|
|
return 0;
|
|
|
|
int16 propertyFlag;
|
|
int16 *property = getObjectPropertyPtr(objectIndex, propertyId, propertyFlag);
|
|
|
|
if (property) {
|
|
if (propertyFlag == 1) {
|
|
WRITE_LE_UINT16(property, value);
|
|
} else {
|
|
debug(2, "GameDatabase::setObjectProperty(%04X, %04X, %04X) Trying to set constant property\n",
|
|
objectIndex, propertyId, value);
|
|
}
|
|
return value;
|
|
} else {
|
|
return 0;
|
|
}
|
|
|
|
}
|
|
|
|
const char *GameDatabase::getString(uint16 offset) {
|
|
return (const char*)&_gameText[offset * 4];
|
|
}
|
|
|
|
void GameDatabase::dumpObject(int16 index) {
|
|
Object *obj = getObject(index);
|
|
char fn[512];
|
|
sprintf(fn, "obj%04X.0", index);
|
|
obj->dump(fn);
|
|
}
|
|
|
|
} // End of namespace Made
|