scummvm/engines/myst3/database.cpp
2014-07-05 12:28:47 +02:00

1216 lines
29 KiB
C++

/* ResidualVM - A 3D game interpreter
*
* ResidualVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the AUTHORS
* 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 "engines/myst3/database.h"
#include "engines/myst3/myst3.h"
#include "common/debug.h"
#include "common/file.h"
#include "common/hashmap.h"
#include "common/md5.h"
#include "common/memstream.h"
#include "common/substream.h"
#include "common/winexe_pe.h"
namespace Myst3 {
Database::Database(Myst3Engine *vm) :
_vm(vm),
_currentRoomID(0),
_executableVersion(0),
_currentRoomData(0) {
_executableVersion = _vm->getExecutableVersion();
if (_executableVersion != 0) {
debug("Initializing database from %s (Platform: %s) (%s)", _executableVersion->executable, getPlatformDescription(_vm->getPlatform()), _executableVersion->description);
} else {
error("Could not find any executable to load");
}
// Load the ages and rooms description
Common::SeekableSubReadStreamEndian *file = openDatabaseFile();
file->seek(_executableVersion->ageTableOffset);
_ages = loadAges(*file);
for (uint i = 0; i < _ages.size(); i++) {
file->seek(_ages[i].roomsOffset);
// Read the room offset table
Common::Array<uint32> roomsOffsets;
for (uint j = 0; j < _ages[i].roomCount; j++) {
uint32 offset = file->readUint32() - _executableVersion->baseOffset;
roomsOffsets.push_back(offset);
}
// Load the rooms
for (uint j = 0; j < roomsOffsets.size(); j++) {
file->seek(roomsOffsets[j]);
_ages[i].rooms.push_back(loadRoomDescription(*file));
}
}
file->seek(_executableVersion->nodeInitScriptOffset);
_nodeInitScript = loadOpcodes(*file);
file->seek(_executableVersion->soundNamesOffset);
loadSoundNames(file);
// TODO: Remove once the offset table is complete
if (!_executableVersion->ambientCuesOffset) {
error("The description for this executable (%s, %s) does not contain the ambient cues offset. Please contact the ResidualVM team.",
_executableVersion->executable, _executableVersion->description);
}
file->seek(_executableVersion->ambientCuesOffset);
loadAmbientCues(file);
delete file;
preloadCommonRooms();
}
void Database::preloadCommonRooms() {
// XXXX, MENU, JRNL
static const uint32 commonRooms[3] = { 101, 901, 902 };
for (uint i = 0; i < 3; i++) {
RoomData *data = findRoomData(commonRooms[i]);
_roomNodesCache.setVal(commonRooms[i], loadRoomScripts(data));
}
}
Common::Array<uint16> Database::listRoomNodes(uint32 roomID, uint32 ageID) {
Common::Array<NodePtr> nodes;
Common::Array<uint16> list;
if (roomID == 0)
roomID = _currentRoomID;
if (_roomNodesCache.contains(roomID)) {
nodes = _roomNodesCache.getVal(roomID);
} else {
RoomData *data = findRoomData(roomID);
nodes = loadRoomScripts(data);
}
for (uint i = 0; i < nodes.size(); i++) {
list.push_back(nodes[i]->id);
}
return list;
}
NodePtr Database::getNodeData(uint16 nodeID, uint32 roomID, uint32 ageID) {
Common::Array<NodePtr> nodes;
if (roomID == 0)
roomID = _currentRoomID;
if (_roomNodesCache.contains(roomID)) {
nodes = _roomNodesCache.getVal(roomID);
} else {
RoomData *data = findRoomData(roomID);
nodes = loadRoomScripts(data);
}
for (uint i = 0; i < nodes.size(); i++) {
if (nodes[i]->id == nodeID)
return nodes[i];
}
return NodePtr();
}
int32 Database::getNodeZipBitIndex(uint16 nodeID, uint32 roomID) {
static const struct {
uint16 id;
int16 zipBitIndex;
} roomsZipBitIndex[] = {
{ 101, 0 },
{ 201, 17 },
{ 301, 18 },
{ 401, 47 },
{ 501, 55 },
{ 502, 195 },
{ 503, 228 },
{ 504, 241 },
{ 505, 254 },
{ 506, 279 },
{ 601, 315 },
{ 602, 354 },
{ 603, 398 },
{ 604, 477 },
{ 605, 520 },
{ 703, 527 },
{ 701, 547 },
{ 704, 591 },
{ 707, 617 },
{ 706, 665 },
{ 705, 693 },
{ 708, 734 },
{ 801, 764 },
{ 903, 826 },
{ 901, 831 },
{ 902, 855 },
{ 904, 861 },
{ 1001, 865 },
{ 1002, 897 },
{ 1003, 944 },
{ 1004, 971 },
{ 1005, 1038 },
{ 1006, 1044 }
};
if (roomID == 0) {
roomID = _currentRoomID;
}
int16 roomZipBitIndex = -1;
for (uint i = 0; i < ARRAYSIZE(roomsZipBitIndex); i++) {
if (roomsZipBitIndex[i].id == roomID) {
roomZipBitIndex = roomsZipBitIndex[i].zipBitIndex;
break;
}
}
if (roomZipBitIndex == -1) {
error("Unable to find zip-bit index for room %d", roomID);
}
Common::Array<NodePtr> nodes;
if (_roomNodesCache.contains(roomID)) {
nodes = _roomNodesCache.getVal(roomID);
} else {
RoomData *data = findRoomData(roomID);
nodes = loadRoomScripts(data);
}
for (uint i = 0; i < nodes.size(); i++) {
if (nodes[i]->id == nodeID) {
return roomZipBitIndex + nodes[i]->zipBitIndex;
}
}
error("Unable to find zip-bit index for node (%d, %d)", nodeID, roomID);
}
RoomData *Database::findRoomData(const uint32 & roomID) {
for (uint i = 0; i < _ages.size(); i++) {
for (uint j = 0; j < _ages[i].rooms.size(); j++) {
if (_ages[i].rooms[j].id == roomID) {
return &_ages[i].rooms[j];
}
}
}
return 0;
}
Common::Array<NodePtr> Database::loadRoomScripts(RoomData *room) {
Common::Array<NodePtr> nodes;
Common::SeekableSubReadStreamEndian *file = openDatabaseFile();
// Load the node scripts
if (room->scriptsOffset) {
file->seek(room->scriptsOffset);
loadRoomNodeScripts(file, nodes);
}
// Load the ambient sound scripts, if any
if (room->ambSoundsOffset) {
file->seek(room->ambSoundsOffset);
loadRoomSoundScripts(file, nodes, false);
}
if (room->unkOffset) {
file->seek(room->unkOffset);
loadRoomSoundScripts(file, nodes, true);
}
delete file;
return nodes;
}
void Database::loadRoomNodeScripts(Common::SeekableSubReadStreamEndian *file, Common::Array<NodePtr> &nodes) {
uint zipIndex = 0;
while (1) {
int16 id = file->readUint16();
// End of list
if (id == 0)
break;
if (id <= -10)
error("Unimplemented node list command");
if (id > 0) {
// Normal node
NodePtr node = NodePtr(new NodeData());
node->id = id;
node->zipBitIndex = zipIndex;
node->scripts = loadCondScripts(*file);
node->hotspots = loadHotspots(*file);
nodes.push_back(node);
} else {
// Several nodes sharing the same scripts
Common::Array<int16> nodeIds;
for (int i = 0; i < -id; i++) {
nodeIds.push_back(file->readUint16());
}
Common::Array<CondScript> scripts = loadCondScripts(*file);
Common::Array<HotSpot> hotspots = loadHotspots(*file);
for (int i = 0; i < -id; i++) {
NodePtr node = NodePtr(new NodeData());
node->id = nodeIds[i];
node->zipBitIndex = zipIndex;
node->scripts = scripts;
node->hotspots = hotspots;
nodes.push_back(node);
}
}
zipIndex++;
}
}
void Database::loadRoomSoundScripts(Common::SeekableSubReadStreamEndian *file, Common::Array<NodePtr> &nodes, bool background) {
while (1) {
int16 id = file->readUint16();
// End of list
if (id == 0)
break;
if (id < -10)
error("Unimplemented node list command");
if (id > 0) {
// Normal node, find the node if existing
NodePtr node;
for (uint i = 0; i < nodes.size(); i++)
if (nodes[i]->id == id) {
node = nodes[i];
break;
}
// Node not found, create a new one
if (!node) {
node = NodePtr(new NodeData());
node->id = id;
nodes.push_back(node);
}
if (background)
node->backgroundSoundScripts.push_back(loadCondScripts(*file));
else
node->soundScripts.push_back(loadCondScripts(*file));
} else {
// Several nodes sharing the same scripts
// Find the node ids the script applies to
Common::Array<int16> nodeIds;
if (id == -10)
do {
id = file->readUint16();
if (id < 0) {
uint16 end = file->readUint16();
for (int i = -id; i < end; i++)
nodeIds.push_back(i);
} else if (id > 0) {
nodeIds.push_back(id);
}
} while (id);
else
for (int i = 0; i < -id; i++) {
nodeIds.push_back(file->readUint16());
}
// Load the script
Common::Array<CondScript> scripts = loadCondScripts(*file);
// Add the script to each matching node
for (uint i = 0; i < nodeIds.size(); i++) {
NodePtr node;
// Find the current node if existing
for (uint j = 0; j < nodes.size(); j++) {
if (nodes[j]->id == nodeIds[i]) {
node = nodes[j];
break;
}
}
// Node not found, skip it
if (!node)
continue;
if (background)
node->backgroundSoundScripts.push_back(scripts);
else
node->soundScripts.push_back(scripts);
}
}
}
}
void Database::setCurrentRoom(const uint32 roomID) {
if (roomID == _currentRoomID)
return;
_currentRoomData = findRoomData(roomID);
if (!_currentRoomData || !_currentRoomData->scriptsOffset)
return;
// Remove old room from cache and add the new one
_roomNodesCache.erase(_currentRoomID);
_roomNodesCache.setVal(roomID, loadRoomScripts(_currentRoomData));
_currentRoomID = roomID;
}
Common::Array<CondScript> Database::loadCondScripts(Common::ReadStreamEndian &s) {
Common::Array<CondScript> scripts;
while (1) {
CondScript script = loadCondScript(s);
if (!script.condition)
break;
scripts.push_back(script);
}
return scripts;
}
Common::Array<HotSpot> Database::loadHotspots(Common::ReadStreamEndian &s) {
Common::Array<HotSpot> scripts;
while (1) {
HotSpot hotspot = loadHotspot(s);
if (!hotspot.condition)
break;
scripts.push_back(hotspot);
}
return scripts;
}
Common::Array<Opcode> Database::loadOpcodes(Common::ReadStreamEndian &s) {
Common::Array<Opcode> script;
while (1) {
Opcode opcode;
uint16 code = s.readUint16();
opcode.op = code & 0xff;
uint8 count = code >> 8;
if (count == 0 && opcode.op == 0)
break;
// The v1.0 executables use a slightly different opcode set
// Since it's a simple conversion, we'll handle that here
if ((_executableVersion->flags & kFlagVersion10) && opcode.op >= 122)
opcode.op++;
for (int i = 0; i < count; i++) {
int16 value = s.readSint16();
opcode.args.push_back(value);
}
script.push_back(opcode);
}
return script;
}
CondScript Database::loadCondScript(Common::ReadStreamEndian &s) {
CondScript script;
script.condition = s.readUint16();
if(!script.condition)
return script;
script.script = loadOpcodes(s);
return script;
}
HotSpot Database::loadHotspot(Common::ReadStreamEndian &s) {
HotSpot hotspot;
hotspot.condition = s.readUint16();
if (hotspot.condition == 0)
return hotspot;
if (hotspot.condition != -1) {
hotspot.rects = loadRects(s);
hotspot.cursor = s.readUint16();
}
hotspot.script = loadOpcodes(s);
return hotspot;
}
Common::Array<PolarRect> Database::loadRects(Common::ReadStreamEndian &s) {
Common::Array<PolarRect> rects;
bool lastRect = false;
do {
PolarRect rect;
rect.centerPitch = s.readUint16();
rect.centerHeading = s.readUint16();
rect.width = s.readUint16();
rect.height = s.readUint16();
if (rect.width < 0) {
rect.width = -rect.width;
} else {
lastRect = true;
}
rects.push_back(rect);
} while (!lastRect);
return rects;
}
Common::Array<AgeData> Database::loadAges(Common::ReadStreamEndian &s) {
Common::Array<AgeData> ages;
for (uint i = 0; i < 10; i++) {
AgeData age;
if (_vm->getPlatform() == Common::kPlatformPS2) {
// Really 64-bit values
age.id = s.readUint32LE();
s.readUint32LE();
age.disk = s.readUint32LE();
s.readUint32LE();
age.roomCount = s.readUint32LE();
s.readUint32LE();
age.roomsOffset = s.readUint32LE() - _executableVersion->baseOffset;
s.readUint32LE();
age.labelId = s.readUint32LE();
s.readUint32LE();
} else {
age.id = s.readUint32();
age.disk = s.readUint32();
age.roomCount = s.readUint32();
age.roomsOffset = s.readUint32() - _executableVersion->baseOffset;
age.labelId = s.readUint32();
}
ages.push_back(age);
}
return ages;
}
RoomData Database::loadRoomDescription(Common::ReadStreamEndian &s) {
RoomData room;
if (_vm->getPlatform() == Common::kPlatformPS2) {
room.id = s.readUint32LE(); s.readUint32LE();
s.read(&room.name, 8);
room.scriptsOffset = s.readUint32LE(); s.readUint32LE();
room.ambSoundsOffset = s.readUint32LE(); s.readUint32LE();
room.unkOffset = s.readUint32LE(); // not 64-bit -- otherwise roomUnk5 is incorrect
s.readUint32LE(); // The zip-bit index is computed at runtime
room.roomUnk5 = s.readUint32LE();
} else {
room.id = s.readUint32();
s.read(&room.name, 8);
room.scriptsOffset = s.readUint32();
room.ambSoundsOffset = s.readUint32();
room.unkOffset = s.readUint32();
s.readUint32(); // The zip-bit index is computed at runtime
room.roomUnk5 = s.readUint32();
}
if (room.scriptsOffset != 0)
room.scriptsOffset -= _executableVersion->baseOffset;
if (room.ambSoundsOffset != 0)
room.ambSoundsOffset -= _executableVersion->baseOffset;
if (room.unkOffset != 0)
room.unkOffset -= _executableVersion->baseOffset;
return room;
}
void Database::getRoomName(char name[8], uint32 roomID) {
if (roomID != 0 && roomID != _currentRoomID) {
RoomData *data = findRoomData(roomID);
memcpy(&name[0], &data->name, 8);
} else if (_currentRoomData) {
memcpy(&name[0], &_currentRoomData->name, 8);
}
}
uint32 Database::getRoomId(const char *name) {
for (uint i = 0; i < _ages.size(); i++)
for (uint j = 0; j < _ages[i].rooms.size(); j++) {
if (!scumm_stricmp(_ages[i].rooms[j].name, name)) {
return _ages[i].rooms[j].id;
}
}
return 0;
}
uint32 Database::getAgeLabelId(uint32 ageID) {
for (uint i = 0; i < _ages.size(); i++)
if (_ages[i].id == ageID)
return _ages[i].labelId;
return 0;
}
Common::SeekableSubReadStreamEndian *Database::openDatabaseFile() const {
assert(_executableVersion);
Common::SeekableReadStream *stream = SearchMan.createReadStreamForMember(_executableVersion->executable);
bool bigEndian = false;
if (_vm->getPlatform() == Common::kPlatformMacintosh) {
// The data we need is always in segment 1
Common::SeekableReadStream *segment = decompressPEFDataSegment(stream, 1);
delete stream;
stream = segment;
bigEndian = true;
} else if (_vm->getDefaultLanguage() == Common::RU_RUS) {
stream = extractRussianM3R(stream);
} else if (_executableVersion->safeDiskKey) {
#ifdef USE_SAFEDISC
SafeDisc sd;
sd.setKey(_executableVersion->safeDiskKey);
sd.setDecodingFunctions(&safeDiscDecode1, &safeDiscDecode2);
Common::SeekableReadStream *decrypted = sd.decrypt(stream);
delete stream;
if (!decrypted) {
error("Error while decrypting SafeDisc executable");
}
stream = decrypted;
#else
error("Safedisc decryption is not enabled.");
#endif // USE_SAFEDISC
}
return new Common::SeekableSubReadStreamEndian(stream, 0, stream->size(), bigEndian, DisposeAfterUse::YES);
}
static uint32 getPEFArgument(Common::SeekableReadStream *stream, uint &pos) {
uint32 r = 0;
byte numEntries = 0;
for (;;) {
numEntries++;
byte in = stream->readByte();
pos++;
if (numEntries == 5) {
r <<= 4;
} else {
r <<= 7;
}
r += (in & 0x7f);
if (!(in & 0x80))
return r;
if (numEntries == 5)
error("bad argument in PEF");
}
}
// decompressPEFDataSegment is entirely based on https://github.com/fuzzie/unity/blob/master/data.cpp
Common::SeekableReadStream *Database::decompressPEFDataSegment(Common::SeekableReadStream *stream, uint segmentID) const {
// Read the header
if (stream->readUint32BE() != MKTAG('J','o','y','!'))
error("Bad PEF header tag 1");
if (stream->readUint32BE() != MKTAG('p','e','f','f'))
error("Bad PEF header tag 2");
if (stream->readUint32BE() != MKTAG('p','w','p','c'))
error("PEF header is not PowerPC");
if (stream->readUint32BE() != 1)
error("PEF header is not version 1");
stream->skip(16); // dateTimeStamp, oldDefVersion, oldImpVersion, currentVersion
uint16 sectionCount = stream->readUint16BE();
stream->skip(6); // instSectionCount, reservedA
if (segmentID >= sectionCount)
error("Not enough segments in PEF");
stream->skip(28 * segmentID);
stream->skip(8); // nameOffset, defaultAddress
uint32 totalSize = stream->readUint32BE();
uint32 unpackedSize = stream->readUint32BE();
uint32 packedSize = stream->readUint32BE();
assert(unpackedSize <= totalSize);
assert(packedSize <= unpackedSize);
uint32 containerOffset = stream->readUint32BE();
byte sectionKind = stream->readByte();
switch (sectionKind) {
case 2:
break; // pattern-initialized data
default:
error("Unsupported PEF sectionKind %d", sectionKind);
}
debug(1, "Unpacking PEF segment of size %d (total %d, packed %d) at 0x%x", unpackedSize, totalSize, packedSize, containerOffset);
bool r = stream->seek(containerOffset, SEEK_SET);
assert(r);
// note that we don't bother with the zero-initialised section..
byte *data = (byte *)malloc(unpackedSize);
// unpack the data
byte *targ = data;
unsigned int pos = 0;
while (pos < packedSize) {
byte next = stream->readByte();
byte opcode = next >> 5;
uint32 count = next & 0x1f;
pos++;
if (count == 0)
count = getPEFArgument(stream, pos);
switch (opcode) {
case 0: // Zero
memset(targ, 0, count);
targ += count;
break;
case 1: // blockCopy
stream->read(targ, count);
targ += count;
pos += count;
break;
case 2: { // repeatedBlock
uint32 repeatCount = getPEFArgument(stream, pos);
byte *src = targ;
stream->read(src, count);
targ += count;
pos += count;
for (uint i = 0; i < repeatCount; i++) {
memcpy(targ, src, count);
targ += count;
}
} break;
case 3: { // interleaveRepeatBlockWithBlockCopy
uint32 customSize = getPEFArgument(stream, pos);
uint32 repeatCount = getPEFArgument(stream, pos);
byte *commonData = targ;
stream->read(commonData, count);
targ += count;
pos += count;
for (uint i = 0; i < repeatCount; i++) {
stream->read(targ, customSize);
targ += customSize;
pos += customSize;
memcpy(targ, commonData, count);
targ += count;
}
} break;
case 4: { // interleaveRepeatBlockWithZero
uint32 customSize = getPEFArgument(stream, pos);
uint32 repeatCount = getPEFArgument(stream, pos);
for (uint i = 0; i < repeatCount; i++) {
memset(targ, 0, count);
targ += count;
stream->read(targ, customSize);
targ += customSize;
pos += customSize;
}
memset(targ, 0, count);
targ += count;
} break;
default:
error("Unknown opcode %d in PEF pattern-initialized section", opcode);
}
}
if (pos != packedSize)
error("Failed to parse PEF pattern-initialized section (parsed %d of %d)", pos, packedSize);
if (targ != data + unpackedSize)
error("Failed to unpack PEF pattern-initialized section");
return new Common::MemoryReadStream(data, unpackedSize, DisposeAfterUse::YES);
}
/**
* This class performs NRV 2d decompression
*
* The implementation is based upon the GPLv2 licensed UCL library
* from http://www.oberhumer.com/opensource/ucl/
*/
class NRV2D {
public :
static void uncompress(Common::SeekableReadStream *src, byte *dst) {
uint bc = 0;
uint32 bb = 0;
uint olen = 0, last_m_off = 1;
for (;;) {
uint32 m_off, m_len;
while (getBit(bb, bc, src)) {
dst[olen++] = src->readByte();
}
m_off = 1;
for (;;) {
m_off = m_off * 2 + getBit(bb, bc, src);
if (getBit(bb, bc, src))
break;
m_off = (m_off - 1) * 2 + getBit(bb, bc, src);
}
if (m_off == 2) {
m_off = last_m_off;
m_len = getBit(bb, bc, src);
} else {
m_off = (m_off - 3) * 256 + src->readByte();
if (m_off == 0xFFFFFFFF)
break;
m_len = (m_off ^ 0xFFFFFFFF) & 1;
m_off >>= 1;
last_m_off = ++m_off;
}
m_len = m_len * 2 + getBit(bb, bc, src);
if (m_len == 0) {
m_len++;
do {
m_len = m_len * 2 + getBit(bb, bc, src);
} while (!getBit(bb, bc, src));
m_len += 2;
}
m_len += (m_off > 0x500);
{
const byte *m_pos = dst + olen - m_off;
dst[olen++] = *m_pos++;
do
dst[olen++] = *m_pos++;
while (--m_len > 0);
}
}
}
private:
static uint getBit(uint32 &bb, uint &bc, Common::SeekableReadStream *src) {
if (bc == 0) {
bc = 32;
bb = src->readUint32LE();
}
return (bb >> --bc) & 1;
}
};
Common::SeekableReadStream *Database::extractRussianM3R(Common::SeekableReadStream *stream) const {
/*
* The Russian version comes with a weird packaging. It doesn't have the
* usual M3.exe but instead comes with M3R.exe and inst.dat.
*
* inst.dat is an UPX executable with the compressed section removed.
*
* M3R.exe is a Delphi programmed launcher. Its "INFO" resource contains
* the missing section from inst.dat
*
* Here is how the original works :
* - M3R.exe loads inst.dat in memory as an executable.
* - MR3.exe extracts the INFO resource from itself,
* and uses a very simple patch mechanism to modify the in-memory
* representation of inst.dat.
* - At this point, the inst.dat in memory representation is a hacked up UPX
* compressed M3.exe. MR3.exe resumes execution of the compressed M3.exe.
* - M3.exe uncompresses itself in memory using the NRV algorithm.
* - The game starts.
*/
// Extract the INFO resource from M3R.exe
Common::PEResources m3r;
m3r.loadFromEXE(stream);
Common::SeekableReadStream *compressed = m3r.getResource(Common::kPERCData, Common::String("INFO"));
// Skip the header
compressed->skip(4); // Patch offset
compressed->skip(4); // Patch size
// The uncompressed size is not stored anywhere, just allocate slightly
// more memory than actually needed
byte *data = (byte *)malloc(1100*1024);
// Perform the decompression
NRV2D::uncompress(compressed, data);
delete compressed;
return new Common::MemoryReadStream(data, 2*1024*1024, DisposeAfterUse::YES);
}
#define ROL32(x,b) (((x) << (b)) | ((x) >> (32 - (b))))
#define ROR32(x,b) (((x) >> (b)) | ((x) << (32 - (b))))
uint32 Database::safeDiscDecode1(uint32 data) {
data += 0x15770916;
data ^= 0x13932106;
data = ROL32(data, 0xE2 % 32);
data -= 0x407A1EF5;
data ^= 0x33784C64;
data -= 0x7BA359F3;
data = ROR32(data, 0xB1 % 32);
data += 0x0F5123F;
// data = ROR32(data, 0xE0 % 32); NOP
data += 0x50C52C7;
data -= 0x29256E14;
data ^= 0x64B95579;
data = ROL32(data, 0xC7 % 32);
data -= 0x4CF5171E;
data ^= 0x5A4D4CF1;
data = ROR32(data, 0x73 % 32);
data = ROL32(data, 0xE3 % 32);
data += 0x2D415B9B;
data = ROL32(data, 0x04 % 32);
data = ROL32(data, 0xC6 % 32);
data = ROL32(data, 0xD6 % 32);
data += 0x56A97DBD;
data += 0x714738B4;
return data;
}
uint32 Database::safeDiscDecode2(uint32 data) {
data--;
data++;
data = ROR32(data, 0xD2 % 32);
data = ROL32(data, 0xF3 % 32);
data = ROR32(data, 0x25 % 32);
data = ROL32(data, 0xDE % 32);
data++;
data = ROL32(data, 0xFF % 32);
data = ROR32(data, 0x6B % 32);
data--;
data = ROR32(data, 0x03 % 32);
data++;
data += 0x3824412A;
data -= 0x5C8137F7;
data++;
data -= 0x13932106;
data--;
data--;
data += 0x4C645056;
data = ROL32(data, 0xCC % 32);
data = ROL32(data, 0xB2 % 32);
data--;
// data = ROR32(data, 0xE0 % 32); NOP
data--;
data = ROR32(data, 0x14 % 32);
data = -data;
data = ROL32(data, 0xC7 % 32);
data -= 0x4CF5171E;
data--;
data = ROL32(data, 0x73 % 32);
data++;
data++;
data++;
data += 0x42C270B1;
data += 0x49A602E5;
data = ROL32(data, 0xA9 % 32);
data -= 0x714738B4;
return data;
}
#undef ROL32
#undef ROR32
void Database::loadSoundNames(Common::ReadStreamEndian *s) {
_soundNames.clear();
while (1) {
uint32 id = s->readUint32();
// 64-bit value in the PS2 binary
if (_vm->getPlatform() == Common::kPlatformPS2)
s->readUint32();
if (!id)
break;
char name[32];
s->read(name, sizeof(name));
_soundNames[id] = Common::String(name);
}
}
Common::String Database::getSoundName(uint32 id) {
const Common::String result = _soundNames.getVal(id, "");
if (result.empty())
error("Unable to find a sound with id %d", id);
return result;
}
void Database::loadAmbientCues(Common::ReadStreamEndian *s) {
_ambientCues.clear();
while (1) {
uint16 id = s->readUint16();
if (!id)
break;
AmbientCue cue;
cue.id = id;
cue.minFrames = s->readUint16();
cue.maxFrames = s->readUint16();
while (1) {
uint16 track = s->readUint16();
if (!track)
break;
cue.tracks.push_back(track);
}
_ambientCues[id] = cue;
}
}
const AmbientCue& Database::getAmbientCue(uint16 id) {
if (!_ambientCues.contains(id))
error("Unable to find an ambient cue with id %d", id);
return _ambientCues.getVal(id);
}
#ifdef USE_SAFEDISC
SafeDisc::SafeDisc() {
_decode1 = 0;
_decode2 = 0;
}
void SafeDisc::setKey(const SafeDiskKey *key) {
for (uint i = 0; i < 4; i++) {
_key[i] = (*key)[i];
}
}
void SafeDisc::setDecodingFunctions(DecodeFunc f1, DecodeFunc f2) {
_decode1 = f1;
_decode2 = f2;
}
Common::SeekableReadStream *SafeDisc::decrypt(Common::SeekableReadStream *stream) const {
if (!stream)
return 0;
// Check executable format
if (stream->readUint16BE() != MKTAG16('M', 'Z')) {
error("Not a valid Windows executable");
}
stream->skip(58);
uint32 peOffset = stream->readUint32LE();
if (!peOffset || peOffset >= (uint32)stream->size())
return 0;
stream->seek(peOffset);
if (stream->readUint32BE() != MKTAG('P','E',0,0)) {
error("Not a valid Windows executable");
}
stream->skip(2);
uint16 sectionCount = stream->readUint16LE();
stream->skip(12);
uint16 optionalHeaderSize = stream->readUint16LE();
stream->skip(optionalHeaderSize + 2);
Common::HashMap<Common::String, Section, Common::IgnoreCase_Hash, Common::IgnoreCase_EqualTo> sections;
// Read in all the sections
for (uint16 i = 0; i < sectionCount; i++) {
char sectionName[9];
stream->read(sectionName, 8);
sectionName[8] = 0;
Section section;
stream->skip(4);
section.virtualAddress = stream->readUint32LE();
section.size = stream->readUint32LE();
section.offset = stream->readUint32LE();
stream->skip(16);
sections[sectionName] = section;
}
//Check sections
static const char* sectionsToDecrypt[] = { ".text", ".data" };
for (uint i = 0; i < ARRAYSIZE(sectionsToDecrypt); i++) {
if (!sections.contains(sectionsToDecrypt[i])) {
error("Executable does not contain required section '%s'", sectionsToDecrypt[i]);
}
}
// Read SafeDisk version
Section &firstSection = sections[".text"];
stream->seek(firstSection.offset - 3 * sizeof(uint32));
uint32 maj = stream->readUint32LE();
uint32 min = stream->readUint32LE();
uint32 rev = stream->readUint32LE();
Common::String version = Common::String::format("v%d.%d.%d", maj, min, rev);
// Check SafeDisc version
bool known = false;
static const char* knownVersions[] = { "v2.10.30", "v2.30.31", "v2.40.10" };
for (uint i = 0; i < ARRAYSIZE(knownVersions); i++) {
if (version.equals(knownVersions[i])) {
known = true;
break;
}
}
if (!known) {
error("Unknown SafeDisk version %s", version.c_str());
}
// Initialize decrypted data buffer
uint32 dataSize = stream->size();
uint8 *data = new uint8[dataSize];
stream->seek(0);
stream->read(data, dataSize);
static const uint32 blockSize = 0x1000;
// Decrypt sections
for (uint i = 0; i < ARRAYSIZE(sectionsToDecrypt); i++) {
Section &section = sections[sectionsToDecrypt[i]];
// To ensure we can decrypt whole blocks
assert(section.size % blockSize == 0);
assert(section.offset + section.size < dataSize);
// Decrypt the section, by blocks
uint32 done = 0;
while (done < section.size) {
uint32 *block = (uint32*) &data[section.offset + done];
done += blockSize;
decryptBlock(block, blockSize >> 2);
}
}
return new Common::MemoryReadStream(data, dataSize, DisposeAfterUse::YES);
}
void SafeDisc::decryptBlock(uint32 *buffer, uint32 size) const {
assert(_decode1 && _decode2);
// Simple data modification decoding
for (uint32 i = 0; i < size; i++) {
uint32 data = buffer[i];
data ^= (i << 24 | i << 16 | i << 8 | i);
buffer[i] = _decode1(data);
}
// Another simple data modification decoding
for (uint32 i = 0; i < size; i++) {
uint32 data = buffer[i];
data ^= (i << 24 | i << 16 | i << 8 | i);
buffer[i] = _decode2(data);
}
// Tiny Encryption Algorithm decryption
static const uint32 teaMagic = 0x9E3779B9;
for (uint32 j = 0; j < size; j += 2) {
uint32 data1 = buffer[j ];
uint32 data2 = buffer[j + 1];
uint32 sum = 32 * teaMagic;
for (uint32 i = 0; i < 32; i++) {
data2 -= (_key[3] + (data1 >> 5)) ^ (sum + data1) ^ (_key[2] + (data1 << 4));
data1 -= (_key[1] + (data2 >> 5)) ^ (sum + data2) ^ (_key[0] + (data2 << 4));
sum -= teaMagic;
}
buffer[j ] = data1;
buffer[j + 1] = data2;
}
}
#endif // USE_SAFEDISC
} // End of namespace Myst3