Initial commit

This commit is contained in:
2026-02-02 04:50:13 +01:00
commit 5b11698731
22592 changed files with 7677434 additions and 0 deletions

2
engines/made/POTFILES Normal file
View File

@@ -0,0 +1,2 @@
engines/made/detection_tables.h
engines/made/metaengine.cpp

View File

@@ -0,0 +1,3 @@
# This file is included from the main "configure" script
# add_engine [name] [desc] [build-by-default] [subengines] [base games] [deps] [components]
add_engine made "MADE" yes "" "" "" "midi"

34
engines/made/console.cpp Normal file
View File

@@ -0,0 +1,34 @@
/* 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/>.
*
*/
#include "made/console.h"
#include "made/made.h"
namespace Made {
MadeConsole::MadeConsole(MadeEngine *vm) : GUI::Debugger(), _vm(vm) {
assert(_vm);
}
MadeConsole::~MadeConsole() {
}
} // End of namespace Made

42
engines/made/console.h Normal file
View File

@@ -0,0 +1,42 @@
/* 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/>.
*
*/
#ifndef MADE_CONSOLE_H
#define MADE_CONSOLE_H
#include "gui/debugger.h"
namespace Made {
class MadeEngine;
class MadeConsole : public GUI::Debugger {
public:
MadeConsole(MadeEngine *vm);
~MadeConsole(void) override;
private:
MadeEngine *_vm;
};
} // End of namespace Made
#endif

4
engines/made/credits.pl Normal file
View File

@@ -0,0 +1,4 @@
begin_section("MADE");
add_person("Benjamin Haisch", "john_doe", "");
add_person("Filippos Karapetis", "bluegr", "");
end_section();

840
engines/made/database.cpp Normal file
View File

@@ -0,0 +1,840 @@
/* 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/>.
*
*/
#include "made/database.h"
#include "made/redreader.h"
#include "common/endian.h"
#include "common/stream.h"
#include "common/debug.h"
#include "common/file.h"
#include "common/savefile.h"
#include "common/system.h"
namespace Made {
/*
Class types:
0x7FFF byte array
0x7FFE word array
< 0x7FFE object
*/
Object::Object() : _objData(nullptr), _freeData(false) {
_objSize = 0;
}
Object::~Object() {
if (_freeData && _objData)
delete[] _objData;
}
const char *Object::getString() {
if (getClass() == 0x7FFF)
return (const char*)getData();
else
return nullptr;
}
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; // for compilers that don't support NORETURN
}
}
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 {
// should never reach here
error("Unknown object class");
return 0; // for compilers that don't support NORETURN
}
}
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 Common::String &filename) {
/*
FILE *o = fopen(filename, "wb");
fwrite(_objData, _objSize, 1, o);
fclose(o);
*/
}
int ObjectV2::load(Common::SeekableReadStream &source) {
if (_freeData && _objData)
delete[] _objData;
_freeData = true;
byte header[4];
source.read(header, 4);
uint16 type = READ_LE_UINT16(header);
if (type == 0x7FFF) {
_objSize = READ_LE_UINT16(header + 2);
} else if (type == 0x7FFE) {
_objSize = READ_LE_UINT16(header + 2) * 2;
} else if (type < 0x7FFE) {
byte count1 = header[2];
byte count2 = header[3];
_objSize = (count1 + count2) * 2;
}
_objSize += 4;
_objData = new byte[_objSize];
memcpy(_objData, header, 4);
source.read(_objData + 4, _objSize - 4);
return _objSize;
}
int ObjectV2::load(byte *source) {
// Not implemented/used for version 2 objects
return 0;
}
int ObjectV2::save(Common::WriteStream &dest) {
dest.write(_objData, _objSize);
return 0;
}
uint16 ObjectV2::getFlags() {
return 1;
}
uint16 ObjectV2::getClass() {
return READ_LE_UINT16(_objData);
}
uint16 ObjectV2::getSize() {
return READ_LE_UINT16(_objData + 2);
}
byte ObjectV2::getCount1() {
return _objData[2];
}
byte ObjectV2::getCount2() {
return _objData[3];
}
byte *ObjectV2::getData() {
return _objData + 4;
}
int ObjectV1::load(Common::SeekableReadStream &source) {
ObjectV2::load(source);
// Manhole EGA has the two property counts reversed
SWAP(_objData[2], _objData[3]);
return _objSize;
}
int ObjectV3::load(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 ObjectV3::load(byte *source) {
_objData = source;
_freeData = false;
if (getClass() < 0x7FFE) {
_objSize = (getCount1() + getCount2()) * 2;
} else {
_objSize = getSize();
}
_objSize += 6;
return _objSize;
}
int ObjectV3::save(Common::WriteStream &dest) {
// Not implemented/used for version 3 objects
return 0;
}
uint16 ObjectV3::getFlags() {
return READ_LE_UINT16(_objData);
}
uint16 ObjectV3::getClass() {
return READ_LE_UINT16(_objData + 2);
}
uint16 ObjectV3::getSize() {
return READ_LE_UINT16(_objData + 4);
}
byte ObjectV3::getCount1() {
return _objData[4];
}
byte ObjectV3::getCount2() {
return _objData[5];
}
byte *ObjectV3::getData() {
return _objData + 6;
}
GameDatabase::GameDatabase(MadeEngine *vm) : _vm(vm) {
_gameState = nullptr;
_gameStateSize = 0;
_mainCodeObjectIndex = 0;
_isRedSource = false;
}
GameDatabase::~GameDatabase() {
delete[] _gameState;
}
void GameDatabase::open(const char *filename) {
debug(1, "GameDatabase::open() Loading from %s", filename);
_isRedSource = false;
_filename = filename;
_redFilename = "";
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);
_isRedSource = true;
_filename = filename;
_redFilename = redFilename;
Common::SeekableReadStream *fileS = RedReader::loadFromRed(redFilename, filename);
if (!fileS)
error("GameDatabase::openFromRed() Could not load %s from %s", filename, redFilename);
load(*fileS);
delete fileS;
}
void GameDatabase::reload() {
if (!_isRedSource) {
Common::File fd;
if (!fd.open(_filename.c_str()))
error("GameDatabase::reload() Could not open %s", _filename.c_str());
reloadFromStream(fd);
} else {
Common::SeekableReadStream *fileS = RedReader::loadFromRed(_redFilename.c_str(), _filename.c_str());
if (!fileS)
error("GameDatabase::openFromRed() Could not load %s from %s", _filename.c_str(), _redFilename.c_str());
reloadFromStream(*fileS);
delete fileS;
}
}
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);
}
const char *GameDatabase::getObjectString(int16 index) {
Object *obj = getObject(index);
if (obj)
return obj->getString();
else
return "";
}
void GameDatabase::setObjectString(int16 index, const char *str) {
Object *obj = getObject(index);
if (obj)
obj->setString(str);
}
int16 *GameDatabase::findObjectPropertyCached(int16 objectIndex, int16 propertyId, int16 &propertyFlag) {
uint32 id = (objectIndex << 16) | propertyId;
ObjectPropertyCacheMap::iterator iter = _objectPropertyCache.find(id);
int16 *propertyPtr = nullptr;
if (iter != _objectPropertyCache.end()) {
propertyPtr = (*iter)._value;
} else {
propertyPtr = findObjectProperty(objectIndex, propertyId, propertyFlag);
_objectPropertyCache[id] = propertyPtr;
}
propertyFlag = 1;
return propertyPtr;
}
int16 GameDatabase::getObjectProperty(int16 objectIndex, int16 propertyId) {
if (objectIndex == 0)
return 0;
int16 propertyFlag;
//int16 *property = findObjectProperty(objectIndex, propertyId, propertyFlag);
int16 *property = findObjectPropertyCached(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 = findObjectProperty(objectIndex, propertyId, propertyFlag);
int16 *property = findObjectPropertyCached(objectIndex, propertyId, propertyFlag);
if (property) {
if (propertyFlag == 1) {
WRITE_LE_UINT16(property, value);
} else {
warning("GameDatabase::setObjectProperty(%04X, %04X, %04X) Trying to set constant property",
objectIndex, propertyId, value);
}
return value;
} else {
return 0;
}
}
void GameDatabase::dumpObject(int16 index) {
Object *obj = getObject(index);
obj->dump(Common::String::format("obj%04X.0", index));
}
/* GameDatabaseV2 */
GameDatabaseV2::GameDatabaseV2(MadeEngine *vm) : GameDatabase(vm), _gameText(nullptr) {
}
GameDatabaseV2::~GameDatabaseV2() {
delete[] _gameText;
}
void GameDatabaseV2::load(Common::SeekableReadStream &sourceS) {
int16 version = sourceS.readUint16LE();
// Manhole:NE, Rodney's Funscreen and LGOP2 are version 54
// The earlier EGA version of Manhole is version 40
if (version != 54 && version != 40)
warning("Unknown database version, known versions are 54 and 40");
char header[6];
sourceS.read(header, 6);
if (strncmp(header, "ADVSYS", 6))
warning ("Unexpected database header, expected ADVSYS");
uint32 textOffs = 0, objectsOffs = 0, objectsSize = 0, textSize;
uint16 objectCount = 0, varObjectCount = 0;
sourceS.readUint16LE(); // skip sub-version
sourceS.skip(18); // skip program name
if (version == 40) {
sourceS.readUint16LE(); // skip unused
objectCount = sourceS.readUint16LE();
_gameStateSize = sourceS.readUint16LE() * 2;
objectsOffs = sourceS.readUint16LE() * 512;
textOffs = sourceS.readUint16LE() * 512;
_mainCodeObjectIndex = sourceS.readUint16LE();
varObjectCount = 0; // unused in V1
objectsSize = 0; // unused in V1
} else if (version == 54) {
textOffs = sourceS.readUint16LE() * 512;
objectCount = sourceS.readUint16LE();
varObjectCount = sourceS.readUint16LE();
_gameStateSize = sourceS.readUint16LE() * 2;
sourceS.readUint16LE(); // unknown
objectsOffs = sourceS.readUint16LE() * 512;
sourceS.readUint16LE(); // unknown
_mainCodeObjectIndex = sourceS.readUint16LE();
sourceS.readUint16LE(); // unknown
objectsSize = sourceS.readUint32LE() * 2;
}
textSize = objectsOffs - textOffs;
debug(0, "textOffs = %08X; textSize = %08X; objectCount = %d; varObjectCount = %d; gameStateSize = %d; objectsOffs = %08X; objectsSize = %d; _mainCodeObjectIndex = %04X\n", textOffs, textSize, objectCount, varObjectCount, _gameStateSize, objectsOffs, objectsSize, _mainCodeObjectIndex);
_gameState = new byte[_gameStateSize + 2]();
setVar(1, objectCount);
sourceS.seek(textOffs);
_gameText = new char[textSize];
sourceS.read(_gameText, textSize);
// "Decrypt" the text data
for (uint32 i = 0; i < textSize; i++)
_gameText[i] += 0x1E;
sourceS.seek(objectsOffs);
if (version == 40) {
// Initialize the object array
for (uint32 i = 0; i < objectCount; i++)
_objects.push_back(NULL);
// Read two "sections" of objects
// It seems the first section is data while the second one is code.
// The interpreter however doesn't care which section the objects come from.
for (int section = 0; section < 2; section++) {
while (!sourceS.eos()) {
int16 objIndex = sourceS.readUint16LE();
debug(1, "objIndex = %04X; section = %d", objIndex, section);
if (objIndex == 0)
break;
Object *obj = new ObjectV1();
obj->load(sourceS);
_objects[objIndex - 1] = obj;
}
}
} else if (version == 54) {
for (uint32 i = 0; i < objectCount; i++) {
Object *obj = new ObjectV2();
int objSize = obj->load(sourceS);
// Objects are aligned on 2-byte-boundaries, skip unused bytes
sourceS.skip(objSize % 2);
_objects.push_back(obj);
}
}
}
void GameDatabaseV2::reloadFromStream(Common::SeekableReadStream &sourceS) {
// Not used in version 2 games
}
bool GameDatabaseV2::getSavegameDescription(const char *filename, Common::String &description, int16 version) {
// Not used in version 2 games
return false;
}
int16 GameDatabaseV2::savegame(const char *filename, const char *description, int16 version) {
Common::OutSaveFile *out;
int16 result = 0;
if (!(out = g_system->getSavefileManager()->openForSaving(filename))) {
warning("Can't create file '%s', game not saved", filename);
return 6;
}
// Variable 0 is not saved
out->write(_gameState + 2, _gameStateSize - 2);
for (uint i = 0; i < _objects.size(); i++)
_objects[i]->save(*out);
out->finalize();
delete out;
return result;
}
int16 GameDatabaseV2::loadgame(const char *filename, int16 version) {
Common::InSaveFile *in;
int16 result = 0;
if (!(in = g_system->getSavefileManager()->openForLoading(filename))) {
warning("Can't open file '%s', game not loaded", filename);
return 1;
}
// Variable 0 is not loaded
in->read(_gameState + 2, _gameStateSize - 2);
for (uint i = 0; i < _objects.size(); i++) {
_objects[i]->load(*in);
}
delete in;
_objectPropertyCache.clear(); // make sure to clear cache
return result;
}
int16 *GameDatabaseV2::findObjectProperty(int16 objectIndex, int16 propertyId, int16 &propertyFlag) {
Object *obj = getObject(objectIndex);
if (obj->getClass() >= 0x7FFE) {
error("GameDatabaseV2::findObjectProperty(%04X, %04X) Not an object", objectIndex, propertyId);
}
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--) {
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) {
return nullptr;
}
while (parentObjectIndex != 0) {
obj = getObject(parentObjectIndex);
prop = (int16 *)obj->getData();
count1 = obj->getCount1();
count2 = obj->getCount2();
propPtr1 = propPtr2 + count1 - count2;
int16 *propertyPtr = prop + count1;
while (count2--) {
if ((READ_LE_UINT16(prop) & 0x8000) == 0) {
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(1, "findObjectProperty(%04X, %04X) Property not found", objectIndex, propertyId);
return nullptr;
}
const char *GameDatabaseV2::getString(uint16 offset) {
return (const char*)&_gameText[offset * 4];
}
/* GameDatabaseV3 */
GameDatabaseV3::GameDatabaseV3(MadeEngine *vm) : GameDatabase(vm) {
_gameText = nullptr;
_gameStateOffs = 0;
}
void GameDatabaseV3::load(Common::SeekableReadStream &sourceS) {
char header[6];
sourceS.read(header, 6);
if (strncmp(header, "ADVSYS", 6))
warning ("Unexpected database header, expected ADVSYS");
/*uint32 unk = */sourceS.readUint32LE();
sourceS.skip(20);
uint32 objectIndexOffs = sourceS.readUint32LE();
uint16 objectCount = sourceS.readUint16LE();
_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 ObjectV3();
// 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.
if (objectOffsets[i] & 1) {
sourceS.seek(objectsOffs + objectOffsets[i] - 1);
obj->load(sourceS);
} else {
obj->load(_gameState + objectOffsets[i]);
}
_objects.push_back(obj);
}
}
void GameDatabaseV3::reloadFromStream(Common::SeekableReadStream &sourceS) {
sourceS.seek(_gameStateOffs);
sourceS.read(_gameState, _gameStateSize);
_objectPropertyCache.clear(); // make sure to clear cache
}
bool GameDatabaseV3::getSavegameDescription(const char *filename, Common::String &description, int16 version) {
Common::InSaveFile *in;
char desc[64];
if (!(in = g_system->getSavefileManager()->openForLoading(filename))) {
return false;
}
uint32 header = in->readUint32BE();
if (header != MKTAG('S','G','A','M')) {
warning("Save game header missing");
delete in;
return false;
}
int32 size = in->readUint32LE();
int16 saveVersion = in->readUint16LE();
if (saveVersion != version) {
warning("Save game %s was saved with a different version of the game. Game version is %d, save version is %d", filename, version, saveVersion);
delete in;
return false;
}
if (size != in->size() - 64) {
warning("Unexpected save game size. Expected %d, size is %d (file size - 64)", size, (int)in->size() - 64);
delete in;
return false;
}
in->read(desc, 64);
description = desc;
delete in;
return true;
}
int16 GameDatabaseV3::savegame(const char *filename, const char *description, int16 version) {
Common::OutSaveFile *out;
char desc[64];
int16 result = 0;
uint32 size = 4 + 4 + 2 + _gameStateSize;
if (!(out = g_system->getSavefileManager()->openForSaving(filename))) {
warning("Can't create file '%s', game not saved", filename);
return 6;
}
Common::strlcpy(desc, description, 64);
out->writeUint32BE(MKTAG('S','G','A','M'));
out->writeUint32LE(size);
out->writeUint16LE(version);
out->write(desc, 64);
out->write(_gameState, _gameStateSize);
out->finalize();
delete out;
return result;
}
int16 GameDatabaseV3::loadgame(const char *filename, int16 version) {
Common::InSaveFile *in;
uint32 expectedSize = 4 + 4 + 2 + _gameStateSize;
if (!(in = g_system->getSavefileManager()->openForLoading(filename))) {
warning("Can't open file '%s', game not loaded", filename);
return 1;
}
uint32 header = in->readUint32BE();
if (header != MKTAG('S','G','A','M')) {
warning("Save game header missing");
delete in;
return 1;
}
uint32 size = in->readUint32LE();
int16 saveVersion = in->readUint16LE();
if (saveVersion != version) {
warning("Save game %s was saved with a different version of the game. Game version is %d, save version is %d", filename, version, saveVersion);
delete in;
return 1;
}
if (size != expectedSize) {
warning("Unexpected save game size. Expected %d, size is %d", expectedSize, size);
delete in;
return 1;
}
in->skip(64); // skip savegame description
in->read(_gameState, _gameStateSize);
delete in;
_objectPropertyCache.clear(); // make sure to clear cache
return 0;
}
int16 *GameDatabaseV3::findObjectProperty(int16 objectIndex, int16 propertyId, int16 &propertyFlag) {
Object *obj = getObject(objectIndex);
if (obj->getClass() >= 0x7FFE) {
error("GameDatabaseV3::findObjectProperty(%04X, %04X) Not an object", objectIndex, propertyId);
}
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) {
return nullptr;
}
while (parentObjectIndex != 0) {
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();
}
return nullptr;
}
const char *GameDatabaseV3::getString(uint16 offset) {
// Not used in version 3 games
return nullptr;
}
} // End of namespace Made

198
engines/made/database.h Normal file
View File

@@ -0,0 +1,198 @@
/* 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/>.
*
*/
#ifndef MADE_DATABASE_H
#define MADE_DATABASE_H
#include "common/hashmap.h"
namespace Common {
class SeekableReadStream;
class WriteStream;
class String;
}
namespace Made {
class MadeEngine;
class Object {
public:
Object();
virtual ~Object();
virtual int load(Common::SeekableReadStream &source) = 0;
virtual int load(byte *source) = 0;
virtual int save(Common::WriteStream &dest) = 0;
virtual uint16 getFlags() = 0;
virtual uint16 getClass() = 0;
virtual uint16 getSize() = 0;
virtual byte getCount1() = 0;
virtual byte getCount2() = 0;
virtual byte *getData() = 0;
virtual bool isConstant() = 0;
const char *getString();
void setString(const char *str);
bool isObject();
bool isVector();
int16 getVectorSize();
int16 getVectorItem(int16 index);
void setVectorItem(int16 index, int16 value);
void dump(const Common::String &filename);
protected:
bool _freeData;
uint16 _objSize;
byte *_objData;
};
class ObjectV2 : public Object {
public:
int load(Common::SeekableReadStream &source) override;
int load(byte *source) override;
int save(Common::WriteStream &dest) override;
uint16 getFlags() override;
uint16 getClass() override;
uint16 getSize() override;
byte getCount1() override;
byte getCount2() override;
byte *getData() override;
bool isConstant() override {
return false;
}
};
class ObjectV1 : public ObjectV2 {
public:
int load(Common::SeekableReadStream &source) override;
};
class ObjectV3 : public Object {
public:
int load(Common::SeekableReadStream &source) override;
int load(byte *source) override;
int save(Common::WriteStream &dest) override;
uint16 getFlags() override;
uint16 getClass() override;
uint16 getSize() override;
byte getCount1() override;
byte getCount2() override;
byte *getData() override;
bool isConstant() override {
return !(getFlags() & 1);
}
};
class GameDatabase {
public:
GameDatabase(MadeEngine *vm);
virtual ~GameDatabase();
void open(const char *filename);
void openFromRed(const char *redFilename, const char *filename);
void reload();
Object *getObject(int16 index) const {
if (index >= 1)
return _objects[index - 1];
else
return NULL;
}
uint getObjectCount() const { return _objects.size(); }
int16 getMainCodeObjectIndex() const { return _mainCodeObjectIndex; }
int16 getVar(int16 index);
void setVar(int16 index, int16 value);
const char *getObjectString(int16 index);
void setObjectString(int16 index, const char *str);
virtual int16 *findObjectProperty(int16 objectIndex, int16 propertyId, int16 &propertyFlag) = 0;
int16 *findObjectPropertyCached(int16 objectIndex, int16 propertyId, int16 &propertyFlag);
virtual const char *getString(uint16 offset) = 0;
virtual bool getSavegameDescription(const char *filename, Common::String &description, int16 version) = 0;
virtual int16 savegame(const char *filename, const char *description, int16 version) = 0;
virtual int16 loadgame(const char *filename, int16 version) = 0;
int16 getObjectProperty(int16 objectIndex, int16 propertyId);
int16 setObjectProperty(int16 objectIndex, int16 propertyId, int16 value);
void dumpObject(int16 index);
protected:
typedef Common::HashMap<uint32, int16 *> ObjectPropertyCacheMap;
MadeEngine *_vm;
Common::Array<Object *> _objects;
ObjectPropertyCacheMap _objectPropertyCache;
byte *_gameState;
uint32 _gameStateSize;
int16 _mainCodeObjectIndex;
bool _isRedSource;
Common::String _filename, _redFilename;
virtual void load(Common::SeekableReadStream &sourceS) = 0;
virtual void reloadFromStream(Common::SeekableReadStream &sourceS) = 0;
};
class GameDatabaseV2 : public GameDatabase {
public:
GameDatabaseV2(MadeEngine *vm);
~GameDatabaseV2() override;
int16 *findObjectProperty(int16 objectIndex, int16 propertyId, int16 &propertyFlag) override;
const char *getString(uint16 offset) override;
bool getSavegameDescription(const char *filename, Common::String &description, int16 version) override;
int16 savegame(const char *filename, const char *description, int16 version) override;
int16 loadgame(const char *filename, int16 version) override;
protected:
char *_gameText;
void load(Common::SeekableReadStream &sourceS) override;
void reloadFromStream(Common::SeekableReadStream &sourceS) override;
};
class GameDatabaseV3 : public GameDatabase {
public:
GameDatabaseV3(MadeEngine *vm);
int16 *findObjectProperty(int16 objectIndex, int16 propertyId, int16 &propertyFlag) override;
const char *getString(uint16 offset) override;
bool getSavegameDescription(const char *filename, Common::String &description, int16 version) override;
int16 savegame(const char *filename, const char *description, int16 version) override;
int16 loadgame(const char *filename, int16 version) override;
protected:
char *_gameText;
uint32 _gameStateOffs;
void load(Common::SeekableReadStream &sourceS) override;
void reloadFromStream(Common::SeekableReadStream &sourceS) override;
};
} // End of namespace Made
#endif /* MADE_H */

View File

@@ -0,0 +1,75 @@
/* 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/>.
*
*/
#include "base/plugins.h"
#include "engines/advancedDetector.h"
#include "made/detection.h"
#include "common/config-manager.h"
static const PlainGameDescriptor madeGames[] = {
{"manhole", "The Manhole"},
{"rtz", "Return to Zork"},
{"lgop2", "Leather Goddesses of Phobos 2"},
{"rodney", "Rodney's Funscreen"},
{nullptr, nullptr}
};
#include "made/detection_tables.h"
class MadeMetaEngineDetection : public AdvancedMetaEngineDetection<Made::MadeGameDescription> {
public:
MadeMetaEngineDetection() : AdvancedMetaEngineDetection(Made::gameDescriptions, madeGames) {
_guiOptions = GUIO1(GAMEOPTION_TTS);
}
const char *getName() const override {
return "made";
}
const char *getEngineName() const override {
return "MADE";
}
const char *getOriginalCopyright() const override {
return "MADE Engine (C) Activision";
}
ADDetectedGame fallbackDetect(const FileMap &allFiles, const Common::FSList &fslist, ADDetectedGameExtraInfo **extra) const override;
};
ADDetectedGame MadeMetaEngineDetection::fallbackDetect(const FileMap &allFiles, const Common::FSList &fslist, ADDetectedGameExtraInfo **extra) const {
// Set the default values for the fallback descriptor's ADGameDescription part.
Made::g_fallbackDesc.desc.language = Common::UNK_LANG;
Made::g_fallbackDesc.desc.platform = Common::kPlatformDOS;
Made::g_fallbackDesc.desc.flags = ADGF_NO_FLAGS;
// Set default values for the fallback descriptor's MadeGameDescription part.
Made::g_fallbackDesc.gameID = 0;
Made::g_fallbackDesc.features = 0;
Made::g_fallbackDesc.version = 3;
//return (const ADGameDescription *)&Made::g_fallbackDesc;
return ADDetectedGame();
}
REGISTER_PLUGIN_STATIC(MADE_DETECTION, PLUGIN_TYPE_ENGINE_DETECTION, MadeMetaEngineDetection);

60
engines/made/detection.h Normal file
View File

@@ -0,0 +1,60 @@
/* 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/>.
*
*/
#ifndef MADE_DETECTION_H
#define MADE_DETECTION_H
#include "engines/advancedDetector.h"
namespace Made {
enum MadeGameID {
GID_RTZ = 0,
GID_MANHOLE = 1,
GID_LGOP2 = 2,
GID_RODNEY = 3
};
enum MadeGameFeatures {
GF_DEMO = 1 << 0,
GF_CD = 1 << 1,
GF_CD_COMPRESSED = 1 << 2,
GF_FLOPPY = 1 << 3
};
struct MadeGameDescription {
AD_GAME_DESCRIPTION_HELPERS(desc);
ADGameDescription desc;
int gameID;
int gameType;
uint32 features;
uint16 version;
};
#define GAMEOPTION_INTRO_MUSIC_DIGITAL GUIO_GAMEOPTIONS1
#define GAMEOPTION_TTS GUIO_GAMEOPTIONS2
#define GAMEOPTION_WINDOWS_CURSORS GUIO_GAMEOPTIONS3
} // End of namespace Made
#endif // MADE_DETECTION_H

View File

@@ -0,0 +1,660 @@
/* 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/>.
*
*/
#ifndef MADE_DETECTION_TABLES_H
#define MADE_DETECTION_TABLES_H
#include "made/detection.h"
#include "common/translation.h"
namespace Made {
static const MadeGameDescription gameDescriptions[] = {
{
// NOTE: Return to Zork entries with *.dat are used to detect the game via rtzcd.dat,
// which is packed inside rtzcd.red. Entries with *.red refer to the packed file
// directly, which is the "official" way.
// Return to Zork - English CD version 1.0 9/15/93 (installed)
// Ticket #8858 submitted by spookypeanut
{
"rtz",
"V1.0, 9/15/93, installed, CD",
AD_ENTRY1("rtzcd.dat", "e95c38ded389e39cfbf87a8cb250b12e"),
Common::EN_ANY,
Common::kPlatformDOS,
ADGF_NO_FLAGS,
GUIO1(GAMEOPTION_INTRO_MUSIC_DIGITAL)
},
GID_RTZ,
0,
GF_CD,
3,
},
{
// Return to Zork - English CD version 1.0 9/15/93
// Ticket #8858 submitted by spookypeanut
{
"rtz",
"V1.0, 9/15/93, CD",
AD_ENTRY1("rtzcd.red", "cd8b62ece4677c438688c1de3f5379b9"),
Common::EN_ANY,
Common::kPlatformDOS,
ADGF_CD,
GUIO1(GAMEOPTION_INTRO_MUSIC_DIGITAL)
},
GID_RTZ,
0,
GF_CD_COMPRESSED,
3,
},
{
// Return to Zork - English CD version 1.1 12/7/93 (installed)
{
"rtz",
"V1.1, 12/7/93, installed, CD",
AD_ENTRY1s("rtzcd.dat", "a1db8c97a78dae10f91d356f16ad07b8", 536064),
Common::EN_ANY,
Common::kPlatformDOS,
ADGF_CD,
GUIO1(GAMEOPTION_INTRO_MUSIC_DIGITAL)
},
GID_RTZ,
0,
GF_CD,
3,
},
{
// Return to Zork - English OEM CD version 1.1 12/7/93
{
"rtz",
"V1.1, 12/7/93, CD",
AD_ENTRY1s("rtzcd.red", "c4e2430e6b6c6ff1562a80fb4a9df24c", 276177),
Common::EN_ANY,
Common::kPlatformDOS,
ADGF_CD,
GUIO1(GAMEOPTION_INTRO_MUSIC_DIGITAL)
},
GID_RTZ,
0,
GF_CD_COMPRESSED,
3,
},
{
// Return to Zork - English Retail CD version 1.1 12/7/93
{
"rtz",
"V1.1, 12/7/93, CD",
AD_ENTRY1s("rtzcd.red", "c4e2430e6b6c6ff1562a80fb4a9df24c", 276466),
Common::EN_ANY,
Common::kPlatformDOS,
ADGF_CD,
GUIO1(GAMEOPTION_INTRO_MUSIC_DIGITAL)
},
GID_RTZ,
0,
GF_CD_COMPRESSED,
3,
},
{
// Return to Zork - English CD version 1.2 9/29/94 (installed)
// Supplied by Dark-Star in the ScummVM forums
{
"rtz",
"V1.2, 9/29/94, installed, CD",
AD_ENTRY1("rtzcd.dat", "9d740378da2d16e83d0d0efff01bf83a"),
Common::EN_ANY,
Common::kPlatformDOS,
ADGF_CD,
GUIO1(GAMEOPTION_INTRO_MUSIC_DIGITAL)
},
GID_RTZ,
0,
GF_CD,
3,
},
{
// Return to Zork - English CD version 1.2 9/29/94
{
"rtz",
"V1.2, 9/29/94, CD",
{
{ "rtzcd.red", 0, "946997d8b0aa6cb4e848bad02a1fc3d2", 276584 },
{ "rtzcd.prj", 0, "974d74410c3c29d50e857863e8bf40e2", 43016792 },
AD_LISTEND
},
Common::EN_ANY,
Common::kPlatformDOS,
ADGF_CD,
GUIO1(GAMEOPTION_INTRO_MUSIC_DIGITAL)
},
GID_RTZ,
0,
GF_CD_COMPRESSED,
3,
},
{
// Return to Zork - German CD version 1.2 9/29/94 (installed)
// Supplied by Dark-Star in the ScummVM forums
{
"rtz",
"V1.2, 9/29/94, installed, CD",
AD_ENTRY1s("rtzcd.dat", "9d740378da2d16e83d0d0efff01bf83a", 525824),
Common::DE_DEU,
Common::kPlatformDOS,
ADGF_CD,
GUIO1(GAMEOPTION_INTRO_MUSIC_DIGITAL)
},
GID_RTZ,
0,
GF_CD,
3,
},
{
// Return to Zork - German CD version 1.2 4/18/95
// Supplied by Dark-Star in the ScummVM forums
{
"rtz",
"V1.2, 4/18/95, CD",
AD_ENTRY1s("rtzcd.red", "946997d8b0aa6cb4e848bad02a1fc3d2", 355442),
Common::DE_DEU,
Common::kPlatformDOS,
ADGF_CD,
GUIO1(GAMEOPTION_INTRO_MUSIC_DIGITAL)
},
GID_RTZ,
0,
GF_CD_COMPRESSED,
3,
},
{
// Return to Zork - Italian CD version 1.2 3/31/95 (installed)
// Patch #4225 submitted by goodoldgeorg
{
"rtz",
"V1.2, 3/31/95, installed, CD",
AD_ENTRY1s("rtzcd.dat", "5b86035aed0277f96e3d173542b5364a", 523776),
Common::IT_ITA,
Common::kPlatformDOS,
ADGF_CD,
GUIO1(GAMEOPTION_INTRO_MUSIC_DIGITAL)
},
GID_RTZ,
0,
GF_CD,
3,
},
{
// Return to Zork - Italian CD version 1.2 3/31/95
// Patch #4225 submitted by goodoldgeorg
{
"rtz",
"V1.2, 3/31/95, CD",
AD_ENTRY1s("rtzcd.red", "946997d8b0aa6cb4e848bad02a1fc3d2", 354971),
Common::IT_ITA,
Common::kPlatformDOS,
ADGF_CD,
GUIO1(GAMEOPTION_INTRO_MUSIC_DIGITAL)
},
GID_RTZ,
0,
GF_CD_COMPRESSED,
3,
},
{
// Return to Zork - French CD version 1.2 5/13/95 (installed)
// Patch #4225 submitted by goodoldgeorg
{
"rtz",
"V1.2, 5/13/95, installed, CD",
AD_ENTRY1s("rtzcd.dat", "bde8251a8e34e87c54e3f93147d56c9e", 523776),
Common::FR_FRA,
Common::kPlatformDOS,
ADGF_CD,
GUIO1(GAMEOPTION_INTRO_MUSIC_DIGITAL)
},
GID_RTZ,
0,
GF_CD,
3,
},
{
// Return to Zork - French CD version 1.2 5/13/95
// Patch #4225 submitted by goodoldgeorg
{
"rtz",
"V1.2, 3/31/95, CD",
AD_ENTRY1s("rtzcd.red", "946997d8b0aa6cb4e848bad02a1fc3d2", 354614),
Common::FR_FRA,
Common::kPlatformDOS,
ADGF_CD,
GUIO1(GAMEOPTION_INTRO_MUSIC_DIGITAL)
},
GID_RTZ,
0,
GF_CD_COMPRESSED,
3,
},
{
// Return to Zork - Korean CD version 1.2 9/29/94
// Dub only. No text was translated, even in menus, so there are no font issues.
// submitted by trembyle
{
"rtz",
"V1.2, 9/29/94, CD",
{
{ "rtzcd.red", 0, "946997d8b0aa6cb4e848bad02a1fc3d2", 276584 },
{ "rtzcd.prj", 0, "3c8644f7ce77b74968637c035c3532d8", 48083511 },
AD_LISTEND
},
Common::KO_KOR,
Common::kPlatformDOS,
ADGF_CD,
GUIO1(GAMEOPTION_INTRO_MUSIC_DIGITAL)
},
GID_RTZ,
0,
GF_CD_COMPRESSED,
3,
},
{
// Return to Zork - English floppy version
{
"rtz",
"Floppy",
AD_ENTRY1("rtz.prj", "764d02f52ce1c219f2c0066677fba4ce"),
Common::EN_ANY,
Common::kPlatformDOS,
ADGF_NO_FLAGS,
GUIO1(GUIO_NOSPEECH)
},
GID_RTZ,
0,
GF_FLOPPY,
3,
},
{
// Return to Zork - Demo
{
"rtz",
"Demo",
{
{ "demo.dat", 0, "2a6a1354bd5346fad4aee08e5b56caaa", 34304 },
{ "demo.prj", 0, "46891bd6e5180228fe4b3253d500997b", 1675348 },
AD_LISTEND
},
Common::EN_ANY,
Common::kPlatformDOS,
ADGF_DEMO,
GUIO0()
},
GID_RTZ,
0,
GF_DEMO,
3,
},
{
// Return to Zork - Standalone CD Demo v1.1
{
"rtz",
"V1.1, 12/6/93, Demo CD",
AD_ENTRY1s("rtzcd.red", "827cfb323eae37b385985a2359fae3e9", 133784),
Common::EN_ANY,
Common::kPlatformDOS,
ADGF_DEMO | ADGF_CD,
GUIO1(GAMEOPTION_INTRO_MUSIC_DIGITAL)
},
GID_RTZ,
0,
GF_CD_COMPRESSED,
3,
},
{
// Return to Zork - Demo from Zork Anthology CD
// Bugreport #11202
{
"rtz",
"V1.2, 9/8/94, Demo CD",
AD_ENTRY1s("rtzcd.red", "946997d8b0aa6cb4e848bad02a1fc3d2", 130683),
Common::EN_ANY,
Common::kPlatformDOS,
ADGF_DEMO | ADGF_CD,
GUIO1(GAMEOPTION_INTRO_MUSIC_DIGITAL)
},
GID_RTZ,
0,
GF_CD_COMPRESSED,
3,
},
{
// Return to Zork - Mac Demo from Zork Anthology CD
// Same disc as DOS version (on ISO-9660)
// The only resource fork is in the executable
{
"rtz",
"V1.2, 5/4/94, Demo CD",
AD_ENTRY1s("Return To Zork", "0c1377afd4b6fc4ee900e1882ac13895", 1714064),
Common::EN_ANY,
Common::kPlatformMacintosh,
ADGF_DEMO | ADGF_CD | ADGF_MACRESFORK,
GUIO1(GAMEOPTION_INTRO_MUSIC_DIGITAL)
},
GID_RTZ,
0,
GF_CD_COMPRESSED,
3,
},
{
// Return to Zork - Japanese DOS
// This is the RTZCD.DAT in the base directory of the FM-Towns CD
{
"rtz",
"",
AD_ENTRY1("rtzcd.dat", "c4fccf67ad247f09b94c3c808b138576"),
Common::JA_JPN,
Common::kPlatformDOS,
ADGF_CD,
GUIO0()
},
GID_RTZ,
0,
GF_CD,
3,
},
{
// Return to Zork - Japanese FM-Towns
// This is in the RTZFM folder of the FM-Towns CD
{
"rtz",
"",
AD_ENTRY1("rtzcd.dat", "e949a6a42d82daabfa7d4dc0a87a9843"),
Common::JA_JPN,
Common::kPlatformFMTowns,
ADGF_CD,
GUIO1(GUIO_NOASPECT)
},
GID_RTZ,
0,
GF_CD,
3,
},
{
// Return to Zork - Japanese PC-98
// This is in the RTZ9821 folder of the FM-Towns CD
{
"rtz",
"",
AD_ENTRY1("rtzcd.dat", "0c0117e98530c736a141c2aad6834dc5"),
Common::JA_JPN,
Common::kPlatformPC98,
ADGF_CD,
GUIO0()
},
GID_RTZ,
0,
GF_CD,
3,
},
// The Manhole: Masterpiece Edition is not a MADE engine and cannot be
// supported by MADE. It is a HyperCard-like engine
{
// The Manhole: Masterpiece Edition (GOG/CD)
{
"manhole",
_s("The game is using unsupported engine"),
AD_ENTRY1("manhole.dat", "e8cec9bf21e4c50a7ebc193a4e0b48f5"),
Common::EN_ANY,
Common::kPlatformDOS,
ADGF_UNSUPPORTED,
GUIO1(GUIO_NOSPEECH)
},
GID_MANHOLE,
0,
GF_CD,
2,
},
// Bugreport #5855
{
{
"manhole",
_s("The game is using unsupported engine"),
AD_ENTRY1s("manhole.dat", "df77ad5232757d7149342fb6471de4ed", 99317),
Common::EN_ANY,
Common::kPlatformDOS,
ADGF_UNSUPPORTED,
GUIO1(GUIO_NOSPEECH)
},
GID_MANHOLE,
0,
GF_CD,
2,
},
{
// The Manhole: New and Enhanced
{
"manhole",
"",
AD_ENTRY1("manhole.dat", "cb21e31ed35c963208343bc995225b73"),
Common::EN_ANY,
Common::kPlatformDOS,
ADGF_CD,
GUIO1(GUIO_NOSPEECH)
},
GID_MANHOLE,
0,
GF_CD,
2,
},
{
// The Manhole (EGA, 5.25")
{
"manhole",
"EGA",
AD_ENTRY1("manhole.dat", "2b1658292599a861c4cd3cf6cdb3c581"),
Common::EN_ANY,
Common::kPlatformDOS,
ADGF_NO_FLAGS,
GUIO1(GUIO_NOSPEECH)
},
GID_MANHOLE,
0,
GF_FLOPPY,
1,
},
{
// The Manhole
{
"manhole",
"",
AD_ENTRY1s("manhole.dat", "2f14b5d87a862aad25701514dc282475", 119667),
Common::JA_JPN,
Common::kPlatformFMTowns,
ADGF_CD | ADGF_UNSTABLE,
GUIO1(GUIO_NOSPEECH)
},
GID_MANHOLE,
0,
GF_CD,
3,
},
{
// The Manhole DOS/V
// Platform: IBM PS-55, Sega TeraDrive or DOS/V-compatibles
// MADE v2.00a JAPAN PC - Copyright (c) 1990, MEDIAGENIC
{
"manhole",
"DOS-V",
AD_ENTRY1s("manhole.dat", "14522ee9139ca0823ac0cc15805e1fcc", 112303),
Common::JA_JPN,
Common::kPlatformDOS,
ADGF_UNSTABLE,
GUIO1(GUIO_NOSPEECH)
},
GID_MANHOLE,
0,
GF_FLOPPY,
3,
},
{
// Leather Goddesses of Phobos 2 (English)
{
"lgop2",
"",
AD_ENTRY1("lgop2.dat", "8137996db200ff67e8f172ff106f2e48"),
Common::EN_ANY,
Common::kPlatformDOS,
ADGF_NO_FLAGS,
GUIO1(GUIO_NOSPEECH)
},
GID_LGOP2,
0,
GF_FLOPPY,
2,
},
{
// Leather Goddesses of Phobos 2 (German)
// Supplied by windlepoons (bug tracker #4218)
{
"lgop2",
"",
AD_ENTRY1s("lgop2.dat", "a0ffea6a3b7e39bd861edd00c397641c", 299466),
Common::DE_DEU,
Common::kPlatformDOS,
ADGF_NO_FLAGS,
GUIO1(GUIO_NOSPEECH)
},
GID_LGOP2,
0,
GF_FLOPPY,
2,
},
{
// Leather Goddesses of Phobos 2 (French)
// Supplied by goodoldgeorg (bug tracker #4219)
{
"lgop2",
"",
AD_ENTRY1s("lgop2.dat", "f9e974087af7cf4b7ec2d8dc45d01e0c", 295366),
Common::FR_FRA,
Common::kPlatformDOS,
ADGF_NO_FLAGS,
GUIO1(GUIO_NOSPEECH)
},
GID_LGOP2,
0,
GF_FLOPPY,
2,
},
{
// Leather Goddesses of Phobos 2 (Spanish)
// Supplied by goodoldgeorg (bug tracker #4219)
{
"lgop2",
"",
AD_ENTRY1s("lgop2.dat", "96eb95b4d75b9a3da0b0d67e3b4a787d", 288984),
Common::ES_ESP,
Common::kPlatformDOS,
ADGF_NO_FLAGS,
GUIO1(GUIO_NOSPEECH)
},
GID_LGOP2,
0,
GF_FLOPPY,
2,
},
{
// Rodney's Funscreen
// MS-DOS, Win16 and Tandy VIS all share the same resource but a different player.
{
"rodney",
"",
AD_ENTRY1s("rodneys.dat", "a79887dbaa47689facd7c6f09258ba5a", 92990),
Common::EN_ANY,
Common::kPlatformDOS,
ADGF_NO_FLAGS,
GUIO2(GUIO_NOSPEECH, GAMEOPTION_WINDOWS_CURSORS)
},
GID_RODNEY,
0,
GF_FLOPPY,
2,
},
{ AD_TABLE_END_MARKER, 0, 0, 0, 0 }
};
/**
* The fallback game descriptor used by the Made engine's fallbackDetector.
* Contents of this struct are to be overwritten by the fallbackDetector.
*/
static MadeGameDescription g_fallbackDesc = {
{
"",
"",
AD_ENTRY1(0, 0), // This should always be AD_ENTRY1(0, 0) in the fallback descriptor
Common::UNK_LANG,
Common::kPlatformDOS,
ADGF_NO_FLAGS,
GUIO0()
},
0,
0,
0,
0,
};
} // End of namespace Made
#endif

302
engines/made/graphics.cpp Normal file
View File

@@ -0,0 +1,302 @@
/* 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/>.
*
*/
#include "made/graphics.h"
#include "common/endian.h"
#include "common/textconsole.h"
#include "common/debug.h"
#include "common/util.h"
#include "graphics/surface.h"
namespace Made {
byte ValueReader::readPixel() {
byte value;
if (_nibbleMode) {
if (_nibbleSwitch) {
value = (_buffer[0] >> 4) & 0x0F;
_buffer++;
} else {
value = _buffer[0] & 0x0F;
}
_nibbleSwitch = !_nibbleSwitch;
} else {
value = _buffer[0];
_buffer++;
}
return value;
}
uint16 ValueReader::readUint16() {
uint16 value = READ_LE_UINT16(_buffer);
_buffer += 2;
return value;
}
uint32 ValueReader::readUint32() {
uint32 value = READ_LE_UINT32(_buffer);
_buffer += 4;
return value;
}
void ValueReader::resetNibbleSwitch() {
_nibbleSwitch = false;
}
void decompressImage(byte *source, Graphics::Surface &surface, uint16 cmdOffs, uint16 pixelOffs, uint16 maskOffs, uint16 lineSize, byte cmdFlags, byte pixelFlags, byte maskFlags, bool deltaFrame) {
const int offsets[] = {
0, 1, 2, 3,
320, 321, 322, 323,
640, 641, 642, 643,
960, 961, 962, 963
};
uint16 pitch = surface.pitch;
uint16 width = surface.w;
uint16 height = surface.h;
byte *cmdBuffer = source + cmdOffs;
ValueReader maskReader(source + maskOffs, (maskFlags & 2) != 0);
ValueReader pixelReader(source + pixelOffs, (pixelFlags & 2) != 0);
if ((maskFlags != 0) && (maskFlags != 2) && (pixelFlags != 0) && (pixelFlags != 2) && (cmdFlags != 0))
error("decompressImage() Unsupported flags: cmdFlags = %02X; maskFlags = %02X, pixelFlags = %02X", cmdFlags, maskFlags, pixelFlags);
byte *destPtr = (byte *)surface.getPixels();
byte lineBuf[640 * 4];
byte bitBuf[40];
int bitBufLastOfs = (((lineSize + 1) >> 1) << 1) - 2;
int bitBufLastCount = ((width + 3) >> 2) & 7;
if (bitBufLastCount == 0)
bitBufLastCount = 8;
while (height > 0) {
int drawDestOfs = 0;
memset(lineBuf, 0, sizeof(lineBuf));
memcpy(bitBuf, cmdBuffer, lineSize);
cmdBuffer += lineSize;
for (uint16 bitBufOfs = 0; bitBufOfs < lineSize; bitBufOfs += 2) {
uint16 bits = READ_LE_UINT16(&bitBuf[bitBufOfs]);
int bitCount;
if (bitBufOfs == bitBufLastOfs)
bitCount = bitBufLastCount;
else
bitCount = 8;
for (int curCmd = 0; curCmd < bitCount; curCmd++) {
int cmd = bits & 3;
bits >>= 2;
byte pixels[4];
uint32 mask;
switch (cmd) {
case 0:
pixels[0] = pixelReader.readPixel();
for (int i = 0; i < 16; i++)
lineBuf[drawDestOfs + offsets[i]] = pixels[0];
break;
case 1:
pixels[0] = pixelReader.readPixel();
pixels[1] = pixelReader.readPixel();
mask = maskReader.readUint16();
for (int i = 0; i < 16; i++) {
lineBuf[drawDestOfs + offsets[i]] = pixels[mask & 1];
mask >>= 1;
}
break;
case 2:
pixels[0] = pixelReader.readPixel();
pixels[1] = pixelReader.readPixel();
pixels[2] = pixelReader.readPixel();
pixels[3] = pixelReader.readPixel();
mask = maskReader.readUint32();
for (int i = 0; i < 16; i++) {
lineBuf[drawDestOfs + offsets[i]] = pixels[mask & 3];
mask >>= 2;
}
break;
case 3:
if (!deltaFrame) {
// For EGA pictures: Pixels are read starting from a new byte
maskReader.resetNibbleSwitch();
// Yes, it reads from maskReader here
for (int i = 0; i < 16; i++)
lineBuf[drawDestOfs + offsets[i]] = maskReader.readPixel();
}
break;
default:
break;
}
drawDestOfs += 4;
}
}
if (deltaFrame) {
for (int y = 0; y < 4 && height > 0; y++, height--) {
for (int x = 0; x < width; x++) {
if (lineBuf[x + y * 320] != 0)
*destPtr = lineBuf[x + y * 320];
destPtr++;
}
destPtr += pitch - width;
}
} else {
for (int y = 0; y < 4 && height > 0; y++, height--) {
memcpy(destPtr, &lineBuf[y * 320], width);
destPtr += pitch;
}
}
}
}
void decompressMovieImage(byte *source, Graphics::Surface &surface, uint16 cmdOffs, uint16 pixelOffs, uint16 maskOffs, uint16 lineSize) {
uint16 width = surface.w;
uint16 height = surface.h;
uint16 bx = 0, by = 0, bw = ((width + 3) / 4) * 4;
byte *cmdBuffer = source + cmdOffs;
byte *maskBuffer = source + maskOffs;
byte *pixelBuffer = source + pixelOffs;
byte *destPtr = (byte *)surface.getPixels();
byte bitBuf[40];
int bitBufLastOfs = (((lineSize + 1) >> 1) << 1) - 2;
int bitBufLastCount = ((width + 3) >> 2) & 7;
if (bitBufLastCount == 0)
bitBufLastCount = 8;
debug(1, "width = %d; bw = %d", width, bw);
while (height > 0) {
memcpy(bitBuf, cmdBuffer, lineSize);
cmdBuffer += lineSize;
for (uint16 bitBufOfs = 0; bitBufOfs < lineSize; bitBufOfs += 2) {
uint16 bits = READ_LE_UINT16(&bitBuf[bitBufOfs]);
int bitCount;
if (bitBufOfs == bitBufLastOfs)
bitCount = bitBufLastCount;
else
bitCount = 8;
for (int curCmd = 0; curCmd < bitCount; curCmd++) {
uint cmd = bits & 3;
bits >>= 2;
byte pixels[4], block[16];
uint32 mask;
switch (cmd) {
case 0:
pixels[0] = *pixelBuffer++;
for (int i = 0; i < 16; i++)
block[i] = pixels[0];
break;
case 1:
pixels[0] = *pixelBuffer++;
pixels[1] = *pixelBuffer++;
mask = READ_LE_UINT16(maskBuffer);
maskBuffer += 2;
for (int i = 0; i < 16; i++) {
block[i] = pixels[mask & 1];
mask >>= 1;
}
break;
case 2:
pixels[0] = *pixelBuffer++;
pixels[1] = *pixelBuffer++;
pixels[2] = *pixelBuffer++;
pixels[3] = *pixelBuffer++;
mask = READ_LE_UINT32(maskBuffer);
maskBuffer += 4;
for (int i = 0; i < 16; i++) {
block[i] = pixels[mask & 3];
mask >>= 2;
}
break;
case 3:
break;
default:
break;
}
if (cmd != 3) {
uint16 blockPos = 0;
uint32 maxW = MIN(4, surface.w - bx);
uint32 maxH = (MIN(4, surface.h - by) + by) * width;
for (uint32 yc = by * width; yc < maxH; yc += width) {
for (uint32 xc = 0; xc < maxW; xc++) {
destPtr[(bx + xc) + yc] = block[xc + blockPos];
}
blockPos += 4;
}
}
bx += 4;
if (bx >= bw) {
bx = 0;
by += 4;
}
}
}
height -= 4;
}
}
} // End of namespace Made

51
engines/made/graphics.h Normal file
View File

@@ -0,0 +1,51 @@
/* 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/>.
*
*/
#ifndef MADE_GRAPHICS_H
#define MADE_GRAPHICS_H
#include "common/scummsys.h"
namespace Graphics {
struct Surface;
}
namespace Made {
class ValueReader {
public:
ValueReader(byte *source, bool nibbleMode) : _buffer(source), _nibbleBuf(0), _nibbleMode(nibbleMode), _nibbleSwitch(false) {}
byte readPixel();
uint16 readUint16();
uint32 readUint32();
void resetNibbleSwitch();
protected:
byte _nibbleBuf;
bool _nibbleMode, _nibbleSwitch;
byte *_buffer;
};
void decompressImage(byte *source, Graphics::Surface &surface, uint16 cmdOffs, uint16 pixelOffs, uint16 maskOffs, uint16 lineSize, byte cmdFlags, byte pixelFlags, byte maskFlags, bool deltaFrame = false);
void decompressMovieImage(byte *source, Graphics::Surface &surface, uint16 cmdOffs, uint16 pixelOffs, uint16 maskOffs, uint16 lineSize);
} // End of namespace Made
#endif /* MADE_H */

509
engines/made/made.cpp Normal file
View File

@@ -0,0 +1,509 @@
/* 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/>.
*
*/
#include "made/made.h"
#include "made/console.h"
#include "made/pmvplayer.h"
#include "made/resource.h"
#include "made/screen.h"
#include "made/database.h"
#include "made/script.h"
#include "made/music.h"
#include "common/config-manager.h"
#include "common/events.h"
#include "common/formats/winexe_ne.h"
#include "common/system.h"
#include "common/error.h"
#include "engines/util.h"
#include "graphics/wincursor.h"
#include "backends/audiocd/audiocd.h"
namespace Made {
MadeEngine::MadeEngine(OSystem *syst, const MadeGameDescription *gameDesc) : Engine(syst), _gameDescription(gameDesc) {
_eventNum = 0;
_eventMouseX = _eventMouseY = 0;
_eventKey = 0;
_useWinCursors = false;
_autoStopSound = false;
_soundEnergyIndex = 0;
_soundEnergyArray = nullptr;
_musicBeatStart = 0;
_cdTimeStart = 0;
_introMusicDigital = true;
if (ConfMan.hasKey("intro_music_digital"))
_introMusicDigital = ConfMan.getBool("intro_music_digital");
_rnd = new Common::RandomSource("made");
setDebugger(new MadeConsole(this));
_system->getAudioCDManager()->open();
_pmvPlayer = new PmvPlayer(this, _mixer);
_res = new ResourceReader();
_screen = new Screen(this);
if (getGameID() == GID_LGOP2 || getGameID() == GID_MANHOLE || getGameID() == GID_RODNEY) {
_dat = new GameDatabaseV2(this);
} else if (getGameID() == GID_RTZ) {
_dat = new GameDatabaseV3(this);
} else {
error("Unknown GameID");
}
_script = new ScriptInterpreter(this);
_music = nullptr;
_soundRate = 0;
_saveLoadScreenOpen = false;
_openingCreditsOpen = true;
_tapeRecorderOpen = false;
_previousRect = -1;
_previousTextBox = -1;
_voiceText = true;
_forceVoiceText = false;
_forceQueueText = false;
#ifdef USE_TTS
_rtzSaveLoadIndex = ARRAYSIZE(_rtzSaveLoadButtonText);
_rtzFirstSaveSlot = 0;
_tapeRecorderIndex = 0;
_playOMaticButtonIndex = ARRAYSIZE(_playOMaticButtonText);
#endif
// Set default sound frequency
switch (getGameID()) {
case GID_RODNEY:
_soundRate = 11025;
break;
case GID_MANHOLE:
_soundRate = 11025;
break;
case GID_LGOP2:
_soundRate = 8000;
break;
case GID_RTZ:
// Return to Zork sets it itself via a script function
break;
default:
break;
}
}
MadeEngine::~MadeEngine() {
_system->getAudioCDManager()->stop();
delete _rnd;
delete _pmvPlayer;
delete _res;
delete _screen;
delete _dat;
delete _script;
delete _music;
}
void MadeEngine::syncSoundSettings() {
Engine::syncSoundSettings();
if (_music)
_music->syncSoundSettings();
}
int16 MadeEngine::getTicks() {
return g_system->getMillis() * 30 / 1000;
}
int16 MadeEngine::getTimer(int16 timerNum) {
if (timerNum > 0 && timerNum <= ARRAYSIZE(_timers) && _timers[timerNum - 1] != -1) {
Common::TextToSpeechManager *ttsMan = g_system->getTextToSpeechManager();
if (getGameID() == GID_LGOP2 && ttsMan && ttsMan->isSpeaking()) {
return 1;
}
return (getTicks() - _timers[timerNum - 1]);
} else
return 32000;
}
void MadeEngine::setTimer(int16 timerNum, int16 value) {
if (timerNum > 0 && timerNum <= ARRAYSIZE(_timers))
_timers[timerNum - 1] = value;
}
void MadeEngine::resetTimer(int16 timerNum) {
if (timerNum > 0 && timerNum <= ARRAYSIZE(_timers))
_timers[timerNum - 1] = getTicks();
}
int16 MadeEngine::allocTimer() {
for (int i = 0; i < ARRAYSIZE(_timers); i++) {
if (_timers[i] == -1) {
_timers[i] = getTicks();
return i + 1;
}
}
return 0;
}
void MadeEngine::freeTimer(int16 timerNum) {
if (timerNum > 0 && timerNum <= ARRAYSIZE(_timers))
_timers[timerNum - 1] = -1;
}
void MadeEngine::resetAllTimers() {
for (int i = 0; i < ARRAYSIZE(_timers); i++)
_timers[i] = -1;
}
void MadeEngine::sayText(const Common::String &text, Common::TextToSpeechManager::Action action) const {
if (text.empty()) {
return;
}
Common::TextToSpeechManager *ttsMan = g_system->getTextToSpeechManager();
if (ttsMan != nullptr && ConfMan.getBool("tts_enabled")) {
ttsMan->say(text, action, _ttsTextEncoding);
}
}
void MadeEngine::stopTextToSpeech() const {
Common::TextToSpeechManager *ttsMan = g_system->getTextToSpeechManager();
if (ttsMan != nullptr && ConfMan.getBool("tts_enabled") && ttsMan->isSpeaking()) {
ttsMan->stop();
}
}
#ifdef USE_TTS
void MadeEngine::checkHoveringSaveLoadScreen() {
static const Common::Rect rtzSaveLoadScreenButtons[] = {
Common::Rect(184, 174, 241, 189), // Cancel button
Common::Rect(109, 174, 166, 189), // Save/load button
Common::Rect(25, 20, 297, 158) // Text entry box
};
static const uint8 kRtzSaveLoadButtonCount = ARRAYSIZE(rtzSaveLoadScreenButtons);
static const uint8 kRtzSaveBoxHeight = 14;
enum RtzSaveLoadScreenIndex {
kCancel = 0,
kSaveOrLoad = 1,
kTextBox = 2
};
if (_saveLoadScreenOpen && getGameID() == GID_RTZ) {
bool hoveringOverButton = false;
for (uint8 i = 0; i < kRtzSaveLoadButtonCount; ++i) {
if (rtzSaveLoadScreenButtons[i].contains(_eventMouseX, _eventMouseY)) {
if (_previousRect != i) {
if (i == kTextBox) {
int index = MIN((_eventMouseY - 20) / kRtzSaveBoxHeight, 9);
if (index != _previousTextBox) {
sayText(Common::String::format("%d", _rtzFirstSaveSlot + index));
_previousTextBox = index;
}
} else {
sayText(_rtzSaveLoadButtonText[i]);
_previousRect = i;
}
}
hoveringOverButton = true;
break;
}
}
if (!hoveringOverButton) {
_previousRect = -1;
_previousTextBox = -1;
}
}
}
void MadeEngine::checkHoveringPlayOMatic(int16 spriteY) {
static const Common::Rect lgop2PlayOMaticButtons[] = {
Common::Rect(105, 102, 225, 122),
Common::Rect(105, 127, 225, 147),
Common::Rect(105, 152, 225, 172),
Common::Rect(105, 177, 225, 197)
};
static const uint8 kLgop2PlayOMaticButtonCount = ARRAYSIZE(lgop2PlayOMaticButtons);
if (_saveLoadScreenOpen && getGameID() == GID_LGOP2) {
bool hoveringOverButton = false;
for (uint8 i = 0; i < kLgop2PlayOMaticButtonCount; ++i) {
if (lgop2PlayOMaticButtons[i].contains(_eventMouseX, _eventMouseY) || spriteY == lgop2PlayOMaticButtons[i].top) {
if (_previousRect != i || spriteY != -1) {
sayText(_playOMaticButtonText[i], Common::TextToSpeechManager::INTERRUPT);
_previousRect = i;
}
hoveringOverButton = true;
break;
}
}
if (!hoveringOverButton) {
_previousRect = -1;
}
}
}
#endif
Common::String MadeEngine::getSavegameFilename(int16 saveNum) {
return Common::String::format("%s.%03d", getTargetName().c_str(), saveNum);
}
void MadeEngine::handleEvents() {
Common::Event event;
Common::EventManager *eventMan = _system->getEventManager();
// NOTE: Don't reset _eventNum to 0 here or no events will get through to the scripts.
while (eventMan->pollEvent(event)) {
switch (event.type) {
case Common::EVENT_MOUSEMOVE:
_eventMouseX = event.mouse.x;
_eventMouseY = event.mouse.y;
#ifdef USE_TTS
checkHoveringSaveLoadScreen();
checkHoveringPlayOMatic();
#endif
break;
case Common::EVENT_LBUTTONDOWN:
_eventNum = 2;
if (_openingCreditsOpen) {
_openingCreditsOpen = false;
stopTextToSpeech();
}
break;
case Common::EVENT_LBUTTONUP:
_eventNum = 1;
break;
case Common::EVENT_RBUTTONDOWN:
_eventNum = 4;
break;
case Common::EVENT_RBUTTONUP:
_eventNum = 3;
break;
case Common::EVENT_KEYDOWN:
// Handle any special keys here
// Supported keys taken from https://web.archive.org/web/20141114142447/http://www.allgame.com/game.php?id=13542&tab=controls
if (event.kbd.keycode == Common::KEYCODE_BACKSPACE) {
_eventNum = 5;
_eventKey = 9;
} else {
_eventNum = 5;
_eventKey = event.kbd.ascii;
}
break;
case Common::EVENT_CUSTOM_ENGINE_ACTION_START:
switch (event.customType) {
case kActionCursorUp:
_eventMouseY = MAX<int16>(0, _eventMouseY - 1);
g_system->warpMouse(_eventMouseX, _eventMouseY);
break;
case kActionCursorDown:
_eventMouseY = MIN<int16>(199, _eventMouseY + 1);
g_system->warpMouse(_eventMouseX, _eventMouseY);
break;
case kActionCursorLeft:
_eventMouseX = MAX<int16>(0, _eventMouseX - 1);
g_system->warpMouse(_eventMouseX, _eventMouseY);
break;
case kActionCursorRight:
_eventMouseX = MIN<int16>(319, _eventMouseX + 1);
g_system->warpMouse(_eventMouseX, _eventMouseY);
break;
case kActionMenu:
_eventNum = 5;
_eventKey = 21; //KEYCODE F1
break;
case kActionSaveGame:
_eventNum = 5;
_eventKey = 22; //KEYCODE F2
break;
case kActionLoadGame:
_eventNum = 5;
_eventKey = 23; //KEYCODE F3
break;
case kActionRepeatMessage:
_eventNum = 5;
_eventKey = 24; //KEYCODE F4
break;
default:
break;
}
break;
default:
break;
}
}
_system->getAudioCDManager()->update();
}
Common::Error MadeEngine::run() {
if (getPlatform() == Common::kPlatformMacintosh)
_music = nullptr; // TODO: Macintosh music player
else
_music = new DOSMusicPlayer(this, getGameID() == GID_RTZ);
syncSoundSettings();
// Initialize backend
initGraphics(320, 200);
resetAllTimers();
Common::TextToSpeechManager *ttsMan = g_system->getTextToSpeechManager();
if (ttsMan != nullptr) {
ttsMan->enable(ConfMan.getBool("tts_enabled"));
if (getLanguage() == Common::KO_KOR) { // Korean version doesn't translate any text
ttsMan->setLanguage("en");
} else {
ttsMan->setLanguage(ConfMan.get("language"));
}
if (getLanguage() == Common::JA_JPN) {
_ttsTextEncoding = Common::CodePage::kWindows932;
} else {
_ttsTextEncoding = Common::CodePage::kDos850;
}
}
if (getGameID() == GID_RTZ) {
if (getFeatures() & GF_DEMO) {
_dat->open("demo.dat");
_res->open("demo.prj");
} else if (getFeatures() & GF_CD) {
_dat->open("rtzcd.dat");
_res->open("rtzcd.prj");
} else if (getFeatures() & GF_CD_COMPRESSED) {
_dat->openFromRed("rtzcd.red", "rtzcd.dat");
_res->open("rtzcd.prj");
} else if (getFeatures() & GF_FLOPPY) {
_dat->open("rtz.dat");
_res->open("rtz.prj");
} else {
error("Unknown RTZ game features");
}
} else if (getGameID() == GID_MANHOLE) {
_dat->open("manhole.dat");
if (getVersion() == 2) {
_res->open("manhole.prj");
} else {
_res->openResourceBlocks();
}
} else if (getGameID() == GID_LGOP2) {
_dat->open("lgop2.dat");
_res->open("lgop2.prj");
} else if (getGameID() == GID_RODNEY) {
_dat->open("rodneys.dat");
_res->open("rodneys.prj");
if (ConfMan.hasKey("windows_cursors") && ConfMan.getBool("windows_cursors")) {
// Try to open the EXE and get the hand cursor out
Common::WinResources *exe = Common::WinResources::createFromEXE("rodneysw.exe"); // Win16 executable
if (!exe)
exe = Common::WinResources::createFromEXE("rodneysv.exe"); // Tandy VIS executable
if (exe) {
Graphics::WinCursorGroup *_winCursor = Graphics::WinCursorGroup::createCursorGroup(exe, Common::WinResourceID("HANDCURSOR"));
if (_winCursor) {
if (_winCursor->cursors.size() > 0) {
_screen->setMouseCursor(_winCursor->cursors[0].cursor);
_useWinCursors = true;
}
delete _winCursor;
}
delete exe;
}
}
} else {
error ("Unknown MADE game");
}
if ((getFeatures() & GF_CD) || (getFeatures() & GF_CD_COMPRESSED)) {
if (!existExtractedCDAudioFiles()
&& !isDataAndCDAudioReadFromSameCD()) {
warnMissingExtractedCDAudio();
}
}
_autoStopSound = false;
_eventNum = _eventKey = _eventMouseX = _eventMouseY = 0;
#ifdef DUMP_SCRIPTS
_script->dumpAllScripts();
#else
if (! _useWinCursors)
_screen->setDefaultMouseCursor();
_script->runScript(_dat->getMainCodeObjectIndex());
#endif
if (_music)
_music->close();
return Common::kNoError;
}
void MadeEngine::pauseEngineIntern(bool pause) {
Engine::pauseEngineIntern(pause);
if (pause) {
if (_music)
_music->pause();
} else {
if (_music)
_music->resume();
}
}
} // End of namespace Made

163
engines/made/made.h Normal file
View File

@@ -0,0 +1,163 @@
/* 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/>.
*
*/
#ifndef MADE_MADE_H
#define MADE_MADE_H
#include "made/sound.h"
#include "made/detection.h"
#include "engines/engine.h"
#include "common/random.h"
#include "common/text-to-speech.h"
/**
* This is the namespace of the Made engine.
*
* Status of this engine: ???
*
* Games using this engine:
* - Return to Zork
* - Leather Goddesses of Phobos 2
* - The Manhole
* - Rodney's Funscreen
*/
namespace Made {
const uint32 kTimerResolution = 40;
class ResourceReader;
class PmvPlayer;
class Screen;
class ScriptInterpreter;
class GameDatabase;
class MusicPlayer;
class MadeConsole;
enum MADEAction {
kActionNone,
kActionCursorUp,
kActionCursorDown,
kActionCursorLeft,
kActionCursorRight,
kActionMenu,
kActionSaveGame,
kActionLoadGame,
kActionRepeatMessage
};
class MadeEngine : public ::Engine {
protected:
// Engine APIs
Common::Error run() override;
public:
MadeEngine(OSystem *syst, const MadeGameDescription *gameDesc);
~MadeEngine() override;
bool hasFeature(EngineFeature f) const override;
void syncSoundSettings() override;
Common::RandomSource *_rnd;
const MadeGameDescription *_gameDescription;
uint32 getGameID() const;
uint32 getFeatures() const;
uint16 getVersion() const;
Common::Platform getPlatform() const;
Common::Language getLanguage() const;
public:
PmvPlayer *_pmvPlayer;
ResourceReader *_res;
Screen *_screen;
GameDatabase *_dat;
ScriptInterpreter *_script;
MusicPlayer *_music;
bool _useWinCursors;
uint16 _eventNum;
int _eventMouseX, _eventMouseY;
uint16 _eventKey;
int _soundRate;
bool _autoStopSound;
uint _soundEnergyIndex;
SoundEnergyArray *_soundEnergyArray;
uint32 _musicBeatStart;
uint32 _cdTimeStart;
bool _introMusicDigital;
Common::CodePage _ttsTextEncoding;
int _previousRect;
int _previousTextBox;
bool _saveLoadScreenOpen;
bool _openingCreditsOpen;
bool _tapeRecorderOpen;
bool _voiceText;
bool _forceVoiceText;
bool _forceQueueText;
#ifdef USE_TTS
Common::String _rtzSaveLoadButtonText[2];
uint8 _rtzFirstSaveSlot;
uint8 _rtzSaveLoadIndex;
Common::String _tapeRecorderText[4];
uint8 _tapeRecorderIndex;
Common::String _playOMaticButtonText[4];
uint8 _playOMaticButtonIndex;
#endif
int32 _timers[50];
int16 getTicks();
int16 getTimer(int16 timerNum);
void setTimer(int16 timerNum, int16 value);
void resetTimer(int16 timerNum);
int16 allocTimer();
void freeTimer(int16 timerNum);
void resetAllTimers();
void sayText(const Common::String &text, Common::TextToSpeechManager::Action action = Common::TextToSpeechManager::INTERRUPT) const;
void stopTextToSpeech() const;
#ifdef USE_TTS
void checkHoveringSaveLoadScreen();
void checkHoveringPlayOMatic(int16 spriteY = -1);
#endif
const Common::String getTargetName() { return _targetName; }
Common::String getSavegameFilename(int16 saveNum);
void handleEvents();
protected:
void pauseEngineIntern(bool pause) override;
};
} // End of namespace Made
#endif /* MADE_H */

221
engines/made/metaengine.cpp Normal file
View File

@@ -0,0 +1,221 @@
/* 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/>.
*
*/
#include "engines/advancedDetector.h"
#include "common/translation.h"
#include "made/made.h"
#include "made/detection.h"
#include "backends/keymapper/action.h"
#include "backends/keymapper/keymapper.h"
#include "backends/keymapper/standard-actions.h"
namespace Made {
static const ADExtraGuiOptionsMap optionsList[] = {
{
GAMEOPTION_INTRO_MUSIC_DIGITAL,
{
_s("Play a digital soundtrack during the opening movie"),
_s("If selected, the game will use a digital soundtrack during the introduction. Otherwise, it will play MIDI music."),
"intro_music_digital",
true,
0,
0
}
},
#ifdef USE_TTS
{
GAMEOPTION_TTS,
{
_s("Enable Text to Speech"),
_s("Use TTS to read text in the game (if TTS is available)"),
"tts_enabled",
false,
0,
0
}
},
#endif
{
GAMEOPTION_WINDOWS_CURSORS,
{
_s("Use Windows cursors"),
_s("If selected, the game will use Windows mouse cursors bundled in the original .exe file. Otherwise, it will use lower resolution cursors from the data files."),
"windows_cursors",
true,
0,
0
}
},
AD_EXTRA_GUI_OPTIONS_TERMINATOR
};
uint32 MadeEngine::getGameID() const {
return _gameDescription->gameID;
}
uint32 MadeEngine::getFeatures() const {
return _gameDescription->features;
}
Common::Platform MadeEngine::getPlatform() const {
return _gameDescription->desc.platform;
}
uint16 MadeEngine::getVersion() const {
return _gameDescription->version;
}
Common::Language MadeEngine::getLanguage() const {
return _gameDescription->desc.language;
}
} // End of namespace Made
class MadeMetaEngine : public AdvancedMetaEngine<Made::MadeGameDescription> {
public:
const char *getName() const override {
return "made";
}
const ADExtraGuiOptionsMap *getAdvancedExtraGuiOptions() const override {
return Made::optionsList;
}
bool hasFeature(MetaEngineFeature f) const override;
Common::Error createInstance(OSystem *syst, Engine **engine, const Made::MadeGameDescription *desc) const override;
Common::KeymapArray initKeymaps(const char *target) const override;
};
bool MadeMetaEngine::hasFeature(MetaEngineFeature f) const {
return
false;
}
bool Made::MadeEngine::hasFeature(EngineFeature f) const {
return
(f == kSupportsReturnToLauncher);
}
Common::Error MadeMetaEngine::createInstance(OSystem *syst, Engine **engine, const Made::MadeGameDescription *desc) const {
*engine = new Made::MadeEngine(syst,desc);
return Common::kNoError;
}
Common::KeymapArray MadeMetaEngine::initKeymaps(const char *target) const {
using namespace Common;
using namespace Made;
Keymap *engineKeyMap = new Keymap(Keymap::kKeymapTypeGame, "made-default", _("Default keymappings"));
Keymap *gameKeyMap = new Keymap(Keymap::kKeymapTypeGame, "game-shortcuts", _("Game keymappings"));
Action *act;
act = new Action(kStandardActionLeftClick, _("Left click"));
act->setLeftClickEvent();
act->addDefaultInputMapping("MOUSE_LEFT");
act->addDefaultInputMapping("JOY_A");
act->addDefaultInputMapping("KP_PLUS");
engineKeyMap->addAction(act);
act = new Action(kStandardActionRightClick, _("Right click"));
act->setRightClickEvent();
act->addDefaultInputMapping("MOUSE_RIGHT");
act->addDefaultInputMapping("JOY_B");
act->addDefaultInputMapping("KP_MINUS");
engineKeyMap->addAction(act);
act = new Action(kStandardActionSkip, _("Skip"));
act->setKeyEvent(KeyState(KEYCODE_ESCAPE, ASCII_ESCAPE));
act->addDefaultInputMapping("ESCAPE");
act->addDefaultInputMapping("JOY_Y");
act->allowKbdRepeats();
engineKeyMap->addAction(act);
act = new Action("CRSORUP", _("Cursor up"));
act->setCustomEngineActionEvent(kActionCursorUp);
act->addDefaultInputMapping("UP");
act->addDefaultInputMapping("KP8");
act->allowKbdRepeats();
gameKeyMap->addAction(act);
act = new Action("CRSORDOWN", _("Cursor down"));
act->setCustomEngineActionEvent(kActionCursorDown);
act->addDefaultInputMapping("DOWN");
act->addDefaultInputMapping("KP2");
act->allowKbdRepeats();
gameKeyMap->addAction(act);
act = new Action("CRSORLEFT", _("Cursor left"));
act->setCustomEngineActionEvent(kActionCursorLeft);
act->addDefaultInputMapping("LEFT");
act->addDefaultInputMapping("KP4");
act->allowKbdRepeats();
gameKeyMap->addAction(act);
act = new Action("CRSORRIGHT", _("Cursor right"));
act->setCustomEngineActionEvent(kActionCursorRight);
act->addDefaultInputMapping("RIGHT");
act->addDefaultInputMapping("KP6");
act->allowKbdRepeats();
gameKeyMap->addAction(act);
act = new Action("MENU", _("Menu"));
act->setCustomEngineActionEvent(kActionMenu);
act->addDefaultInputMapping("F1");
act->addDefaultInputMapping("JOY_GUIDE");
gameKeyMap->addAction(act);
act = new Action("SAVEGAME", _("Save game"));
act->setCustomEngineActionEvent(kActionSaveGame);
act->addDefaultInputMapping("F2");
act->addDefaultInputMapping("JOY_LEFT_SHOULDER");
gameKeyMap->addAction(act);
act = new Action("LOADGAME", _("Load game"));
act->setCustomEngineActionEvent(kActionLoadGame);
act->addDefaultInputMapping("F3");
act->addDefaultInputMapping("JOY_RIGHT_SHOULDER");
gameKeyMap->addAction(act);
act = new Action("RPTMSG", _("Repeat last message"));
act->setCustomEngineActionEvent(kActionRepeatMessage);
act->addDefaultInputMapping("F4");
act->addDefaultInputMapping("JOY_X");
gameKeyMap->addAction(act);
KeymapArray keymaps(2);
keymaps[0] = engineKeyMap;
keymaps[1] = gameKeyMap;
return keymaps;
}
#if PLUGIN_ENABLED_DYNAMIC(MADE)
REGISTER_PLUGIN_DYNAMIC(MADE, PLUGIN_TYPE_ENGINE, MadeMetaEngine);
#else
REGISTER_PLUGIN_STATIC(MADE, PLUGIN_TYPE_ENGINE, MadeMetaEngine);
#endif

29
engines/made/module.mk Normal file
View File

@@ -0,0 +1,29 @@
MODULE := engines/made
MODULE_OBJS := \
console.o \
database.o \
graphics.o \
made.o \
metaengine.o \
music.o \
pmvplayer.o \
redreader.o \
resource.o \
screen.o \
screenfx.o \
script.o \
scriptfuncs.o \
sound.o
# This module can be built as a plugin
ifeq ($(ENABLE_MADE), DYNAMIC_PLUGIN)
PLUGIN := 1
endif
# Include common rules
include $(srcdir)/rules.mk
# Detection objects
DETECT_OBJS += $(MODULE)/detection.o

218
engines/made/music.cpp Normal file
View File

@@ -0,0 +1,218 @@
/* 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/>.
*
*/
// MIDI music class
#include "made/music.h"
#include "made/redreader.h"
#include "made/resource.h"
#include "audio/adlib_ms.h"
#include "audio/midiparser.h"
#include "audio/miles.h"
#include "common/config-manager.h"
#include "common/file.h"
#include "common/stream.h"
namespace Made {
const uint8 DOSMusicPlayer::MT32_GOODBYE_MSG[] = { 0x52, 0x65, 0x74, 0x75, 0x72, 0x6E, 0x20, 0x54, 0x6F, 0x20, 0x5A, 0x6F, 0x72, 0x6B, 0x20, 0x53, 0x6F, 0x6F, 0x6E, 0x21 };
DOSMusicPlayer::DOSMusicPlayer(MadeEngine *vm, bool milesAudio) : _vm(vm), _parser(nullptr) {
MidiDriver::DeviceHandle dev = MidiDriver::detectDevice(MDT_MIDI | MDT_ADLIB | MDT_PREFER_MT32);
_driverType = MidiDriver::getMusicType(dev);
if (_driverType == MT_GM && ConfMan.getBool("native_mt32"))
_driverType = MT_MT32;
Common::SeekableReadStream *adLibInstrumentStream = nullptr;
switch (_driverType) {
case MT_ADLIB:
if (milesAudio) {
if (Common::File::exists("rtzcd.red")) {
// Installing Return to Zork produces both a SAMPLE.AD and
// a SAMPLE.OPL file, but they are identical. The resource
// file appears to only contain SAMPLE.AD.
adLibInstrumentStream = RedReader::loadFromRed("rtzcd.red", "SAMPLE.AD");
}
_driver = Audio::MidiDriver_Miles_AdLib_create("SAMPLE.AD", "SAMPLE.OPL", adLibInstrumentStream);
delete adLibInstrumentStream;
} else {
_driver = new MidiDriver_ADLIB_MADE(OPL::Config::kOpl2);
}
break;
case MT_GM:
case MT_MT32:
if (milesAudio) {
_driver = Audio::MidiDriver_Miles_MIDI_create(MT_MT32, "");
} else {
_driver = new MidiDriver_MT32GM(MT_MT32);
}
break;
default:
_driver = new MidiDriver_NULL_Multisource();
break;
}
if (_driver) {
_driver->property(MidiDriver::PROP_USER_VOLUME_SCALING, true);
if (_driver->open() != 0)
error("Failed to open MIDI driver.");
_driver->setTimerCallback(this, &timerCallback);
}
syncSoundSettings();
}
DOSMusicPlayer::~DOSMusicPlayer() {
if (_parser) {
_parser->stopPlaying();
delete _parser;
}
if (_driver) {
_driver->setTimerCallback(nullptr, nullptr);
_driver->close();
delete _driver;
}
}
void DOSMusicPlayer::close() {
if (_parser)
_parser->stopPlaying();
if (_vm->getGameID() == GID_RTZ && _vm->getPlatform() == Common::kPlatformDOS && _driver) {
MidiDriver_MT32GM *mt32Driver = dynamic_cast<MidiDriver_MT32GM *>(_driver);
if (mt32Driver)
mt32Driver->sysExMT32(MT32_GOODBYE_MSG, MidiDriver_MT32GM::MT32_DISPLAY_NUM_CHARS,
MidiDriver_MT32GM::MT32_DISPLAY_MEMORY_ADDRESS, false, false);
}
}
bool DOSMusicPlayer::load(int16 musicNum) {
GenericResource *xmidi = _vm->_res->getXmidi(musicNum);
if (xmidi) {
_vm->_res->freeResource(xmidi);
return true;
}
return false;
}
void DOSMusicPlayer::play(int16 musicNum) {
if (_vm->getGameID() == GID_RTZ) {
if (musicNum > 0) {
_musicRes = _vm->_res->getXmidi(musicNum);
if (_musicRes)
playXMIDI(_musicRes);
}
} else {
// HACK: music number 2 in LGOP2 is file MT32SET.TON, which
// is used to set the MT32 instruments. This is not loaded
// correctly and the game freezes, and since we don't support
// MT32 music yet, we ignore it here
// FIXME: Remove this hack and handle this file properly
if (_vm->getGameID() == GID_LGOP2 && musicNum == 2)
return;
if (musicNum > 0) {
_musicRes = _vm->_res->getMidi(musicNum);
if (_musicRes)
playSMF(_musicRes);
}
}
}
void DOSMusicPlayer::playXMIDI(GenericResource *midiResource) {
if (_parser) {
_parser->unloadMusic();
} else {
_parser = MidiParser::createParser_XMIDI(nullptr, nullptr, 0);
_parser->setMidiDriver(_driver);
_parser->setTimerRate(_driver->getBaseTempo());
_parser->property(MidiParser::mpSendSustainOffOnNotesOff, 1);
}
// Load XMID resource data
_parser->loadMusic(midiResource->getData(), midiResource->getSize());
}
void DOSMusicPlayer::playSMF(GenericResource *midiResource) {
if (_parser) {
_parser->unloadMusic();
} else {
_parser = MidiParser::createParser_SMF(0);
_parser->setMidiDriver(_driver);
_parser->setTimerRate(_driver->getBaseTempo());
_parser->property(MidiParser::mpCenterPitchWheelOnUnload, 1);
}
// Load MIDI resource data
_parser->loadMusic(midiResource->getData(), midiResource->getSize());
}
void DOSMusicPlayer::pause() {
if (_parser)
_parser->pausePlaying();
}
void DOSMusicPlayer::resume() {
if (_parser)
_parser->resumePlaying();
}
void DOSMusicPlayer::stop() {
if (_parser)
_parser->stopPlaying();
if (_musicRes) {
_vm->_res->freeResource(_musicRes);
_musicRes = nullptr;
}
}
bool DOSMusicPlayer::isPlaying() {
return _parser ? _parser->isPlaying() : false;
}
void DOSMusicPlayer::syncSoundSettings() {
if (_driver)
_driver->syncSoundSettings();
}
void DOSMusicPlayer::onTimer() {
if (_parser)
_parser->onTimer();
}
void DOSMusicPlayer::timerCallback(void *data) {
((DOSMusicPlayer *)data)->onTimer();
}
MidiDriver_ADLIB_MADE::MidiDriver_ADLIB_MADE(OPL::Config::OplType oplType) : MidiDriver_ADLIB_Multisource(oplType) {
_modulationDepth = MODULATION_DEPTH_LOW;
_vibratoDepth = VIBRATO_DEPTH_LOW;
_defaultChannelVolume = 0x7F;
}
} // End of namespace Made

99
engines/made/music.h Normal file
View File

@@ -0,0 +1,99 @@
/* 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/>.
*
*/
// Music class
#ifndef MADE_MUSIC_H
#define MADE_MUSIC_H
#include "made.h"
#include "audio/adlib_ms.h"
#include "audio/mididrv.h"
#include "audio/mididrv_ms.h"
#include "audio/mt32gm.h"
#include "audio/midiparser.h"
namespace Made {
class GenericResource;
class MusicPlayer {
public:
virtual ~MusicPlayer() {}
virtual void close() = 0;
virtual bool load(int16 musicNum) = 0;
virtual void play(int16 musicNum) = 0;
virtual void stop() = 0;
virtual void pause() = 0;
virtual void resume() = 0;
virtual bool isPlaying() = 0;
virtual void syncSoundSettings() = 0;
};
class DOSMusicPlayer : public MusicPlayer {
private:
static const uint8 MT32_GOODBYE_MSG[MidiDriver_MT32GM::MT32_DISPLAY_NUM_CHARS];
public:
DOSMusicPlayer(MadeEngine *vm, bool milesAudio);
~DOSMusicPlayer();
void close() override;
bool load(int16 musicNum) override;
void play(int16 musicNum) override;
void stop() override;
void pause() override;
void resume() override;
bool isPlaying() override;
void syncSoundSettings() override;
private:
MadeEngine *_vm;
MidiParser *_parser;
MidiDriver_Multisource *_driver;
MusicType _driverType;
GenericResource *_musicRes;
void playXMIDI(GenericResource *midiResource);
void playSMF(GenericResource *midiResource);
static void timerCallback(void *refCon);
void onTimer();
};
class MidiDriver_ADLIB_MADE : public MidiDriver_ADLIB_Multisource {
public:
MidiDriver_ADLIB_MADE(OPL::Config::OplType oplType);
// TODO Implement AdLib driver logic for Manhole / LGoP2
};
} // End of namespace Made
#endif

425
engines/made/pmvplayer.cpp Normal file
View File

@@ -0,0 +1,425 @@
/* 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/>.
*
*/
#include "made/pmvplayer.h"
#include "made/made.h"
#include "made/screen.h"
#include "made/graphics.h"
#include "common/file.h"
#include "common/debug.h"
#include "common/system.h"
#include "common/events.h"
#include "audio/decoders/raw.h"
#include "audio/audiostream.h"
#include "graphics/surface.h"
namespace Made {
#ifdef USE_TTS
// English seems to be the only language that doesn't have voice clips for these lines
static const char *introOpeningLines[] = {
"You are standing by a white house",
"Behind House\nYou are standing behind the white house. In one corner is a small window which is slightly ajar.",
"Go southwest then go northwest",
"West of House\nYou are standing in a field west of a white house with a boarded front door. There is a small mailbox here.",
"Open mailbox"
};
static const char *openingCreditsEnglish[] = {
"Design: Doug Barnett",
"Art Direction: Joe Asperin",
"Technical Direction: William Volk",
"Screenplay: Michele Em",
"Music: Nathan Wang and Teri Mason",
"Producer: Eddie Dombrower"
};
static const char *openingCreditsGerman[] = {
"Entwurf: Doug Barnett",
"K\201nstlerischer Leitung: Joe Asperin",
"Technische Leitung: William Volk",
"Drehbuch: Michele Em",
"Musik: Nathan Wang und Teri Mason",
"Produzent: Eddie Dombrower"
};
static const char *openingCreditsItalian[] = {
"Disegno: Doug Barnett",
"Direzione Artistica: Joe Asperin",
"Direzione Tecnica: William Volk",
"Sceneggiatura: Michele Em",
"Musica: Nathan Wang e Teri Mason",
"Produttore: Eddie Dombrower"
};
static const char *openingCreditsFrench[] = {
"Conception: Doug Barnett",
"Direction Artistique: Joe Asperin",
"Direction Technique: William Volk",
"Sc\202nario: Michele Em",
"Musique: Nathan Wang et Teri Mason",
"Producteur: Eddie Dombrower"
};
static const char *openingCreditsJapanese[] = {
"\x83\x66\x83\x55\x83\x43\x83\x93\x81\x45\x83\x5f\x83\x4f\x81\x45\x83\x6f\x81\x5b\x83\x6c\x83\x62\x83\x67", // デザイン・ダグ・バーネット
"\x83\x41\x81\x5b\x83\x67\x83\x66\x83\x42\x83\x8c\x83\x4e\x83\x56\x83\x87\x83\x93:"
"\x83\x57\x83\x87\x81\x5b\x81\x45\x83\x41\x83\x58\x83\x79\x83\x8a\x83\x93", // アートディレクション:ジョー・アスペリン
"\x83\x65\x83\x4e\x83\x6a\x83\x4a\x83\x8b\x83\x66\x83\x42\x83\x8c\x83\x4e\x83\x56\x83\x87\x83\x93:"
"\x83\x45\x83\x42\x83\x8a\x83\x41\x83\x80\x81\x45\x83\x94\x83\x48\x83\x8b\x83\x4e", // テクニカルディレクション:ウィリアム・ヴォルク
"\x8b\x72\x96\x7b:\x83\x7e\x83\x56\x83\x46\x83\x8b\x81\x45\x83\x47\x83\x80", // 脚本:ミシェル・エム
"\x89\xb9\x8a\x79:\x83\x6c\x83\x43\x83\x54\x83\x93\x81\x45\x83\x8f\x83\x93\x83\x67\x83\x65\x83\x8a\x81\x5b"
"\x81\x45\x83\x81\x83\x43\x83\x5c\x83\x93", // 音楽:ネイサン・ワンとテリー・メイソン
"\x83\x76\x83\x8d\x83\x66\x83\x85\x81\x5b\x83\x54\x81\x5b:\x83\x47\x83\x66\x83\x42\x81\x45\x83\x68\x83\x93"
"\x83\x75\x83\x8d\x83\x8f\x81\x5b" // プロデューサー: エディ・ドンブロワー
};
enum IntroTextFrame {
kStandingByHouse = 20,
kBehindHouse = 53,
kGoSouthwest = 170,
kWestOfHouse = 312,
kOpenMailbox = 430,
kDesign = 716,
kArtDirection = 773,
kTechnicalDirection = 833,
kScreenplay = 892,
kMusic = 948,
kProducer = 1004
};
#endif
PmvPlayer::PmvPlayer(MadeEngine *vm, Audio::Mixer *mixer) : _fd(nullptr), _vm(vm), _mixer(mixer) {
_audioStream = nullptr;
_surface = nullptr;
_aborted = false;
}
PmvPlayer::~PmvPlayer() {
}
bool PmvPlayer::play(const char *filename) {
_aborted = false;
_surface = nullptr;
_fd = new Common::File();
if (!_fd->open(filename)) {
delete _fd;
return false;
}
uint32 chunkType, chunkSize, prevChunkSize = 0;
readChunk(chunkType, chunkSize); // "MOVE"
if (chunkType != MKTAG('M','O','V','E')) {
warning("Unexpected PMV video header, expected 'MOVE'");
delete _fd;
return false;
}
readChunk(chunkType, chunkSize); // "MHED"
if (chunkType != MKTAG('M','H','E','D')) {
warning("Unexpected PMV video header, expected 'MHED'");
delete _fd;
return false;
}
uint frameDelay = _fd->readUint16LE();
_fd->skip(4); // always 0?
uint frameCount = _fd->readUint16LE();
_fd->skip(4); // always 0?
uint soundFreq = _fd->readUint16LE();
// Note: There seem to be weird sound frequencies in PMV videos.
// Not sure why, but leaving those original frequencies intact
// results to sound being choppy. Therefore, we set them to more
// "common" values here (11025 instead of 11127 and 22050 instead
// of 22254)
if (soundFreq == 11127)
soundFreq = 11025;
if (soundFreq == 22254)
soundFreq = 22050;
for (int i = 0; i < 22; i++) {
int unk = _fd->readUint16LE();
debug(2, "%i ", unk);
}
_mixer->stopAll();
// Read palette
_fd->read(_paletteRGB, 768);
_vm->_screen->setRGBPalette(_paletteRGB);
uint32 frameNumber = 0;
uint16 chunkCount = 0;
uint32 soundSize = 0;
uint32 soundChunkOfs = 0, palChunkOfs = 0;
uint32 palSize = 0;
byte *frameData = nullptr, *audioData, *soundData, *palData, *imageData;
bool firstTime = true;
uint32 skipFrames = 0;
uint32 bytesRead;
uint16 width, height, cmdOffs, pixelOffs, maskOffs, lineSize;
// TODO: Sound can still be a little choppy. A bug in the decoder or -
// perhaps more likely - do we have to implement double buffering to
// get it to work well?
_audioStream = Audio::makeQueuingAudioStream(soundFreq, false);
SoundDecoderData *soundDecoderData = new SoundDecoderData();
// First cutscene after the opening credits finish
if (strcmp(filename, "FWIZ01X1.PMV") == 0) {
_vm->_openingCreditsOpen = false;
}
while (!_vm->shouldQuit() && !_aborted && !_fd->eos() && frameNumber < frameCount) {
int32 frameTime = _vm->getTotalPlayTime();
readChunk(chunkType, chunkSize);
if (chunkType != MKTAG('M','F','R','M')) {
warning("Unknown chunk type");
}
// Only reallocate the frame data buffer if its size has changed
if (prevChunkSize != chunkSize || !frameData) {
delete[] frameData;
frameData = new byte[chunkSize];
}
prevChunkSize = chunkSize;
bytesRead = _fd->read(frameData, chunkSize);
if (bytesRead < chunkSize || _fd->eos())
break;
soundChunkOfs = READ_LE_UINT32(frameData + 8);
palChunkOfs = READ_LE_UINT32(frameData + 16);
// Handle audio
if (soundChunkOfs) {
audioData = frameData + soundChunkOfs - 8;
chunkSize = READ_LE_UINT16(audioData + 4);
chunkCount = READ_LE_UINT16(audioData + 6);
debug(1, "chunkCount = %d; chunkSize = %d; total = %d\n", chunkCount, chunkSize, chunkCount * chunkSize);
soundSize = chunkCount * chunkSize;
soundData = (byte *)malloc(soundSize);
decompressSound(audioData + 8, soundData, chunkSize, chunkCount, nullptr, soundDecoderData);
_audioStream->queueBuffer(soundData, soundSize, DisposeAfterUse::YES, Audio::FLAG_UNSIGNED);
}
// Handle palette
if (palChunkOfs) {
palData = frameData + palChunkOfs - 8;
palSize = READ_LE_UINT32(palData + 4);
decompressPalette(palData + 8, _paletteRGB, palSize);
_vm->_screen->setRGBPalette(_paletteRGB);
}
// Handle video
imageData = frameData + READ_LE_UINT32(frameData + 12) - 8;
// frameNum @0
width = READ_LE_UINT16(imageData + 8);
height = READ_LE_UINT16(imageData + 10);
cmdOffs = READ_LE_UINT16(imageData + 12);
pixelOffs = READ_LE_UINT16(imageData + 16);
maskOffs = READ_LE_UINT16(imageData + 20);
lineSize = READ_LE_UINT16(imageData + 24);
debug(2, "width = %d; height = %d; cmdOffs = %04X; pixelOffs = %04X; maskOffs = %04X; lineSize = %d\n",
width, height, cmdOffs, pixelOffs, maskOffs, lineSize);
if (!_surface) {
_surface = new Graphics::Surface();
_surface->create(width, height, Graphics::PixelFormat::createFormatCLUT8());
}
decompressMovieImage(imageData, *_surface, cmdOffs, pixelOffs, maskOffs, lineSize);
if (firstTime) {
_mixer->playStream(Audio::Mixer::kSFXSoundType, &_audioStreamHandle, _audioStream);
skipFrames = 0;
firstTime = false;
}
handleEvents();
updateScreen();
if (skipFrames == 0) {
uint32 soundElapsedTime = _vm->_mixer->getElapsedTime(_audioStreamHandle).msecs();
int32 waitTime = (frameNumber * frameDelay) -
soundElapsedTime - (_vm->getTotalPlayTime() - frameTime);
if (waitTime < 0) {
skipFrames = -waitTime / frameDelay;
warning("Video A/V sync broken, skipping %d frame(s)", skipFrames + 1);
} else if (waitTime > 0)
g_system->delayMillis(waitTime);
} else
skipFrames--;
#ifdef USE_TTS
if (strcmp(filename, "fintro00.pmv") == 0 || strcmp(filename, "fintro01.pmv") == 0) {
const char **texts;
switch (_vm->getLanguage()) {
case Common::EN_ANY:
if (frameNumber < kDesign) {
texts = introOpeningLines;
} else {
texts = openingCreditsEnglish;
}
break;
case Common::DE_DEU:
texts = openingCreditsGerman;
break;
case Common::IT_ITA:
texts = openingCreditsItalian;
break;
case Common::FR_FRA:
texts = openingCreditsFrench;
break;
case Common::JA_JPN:
texts = openingCreditsJapanese;
break;
case Common::KO_KOR:
texts = openingCreditsEnglish;
break;
default:
texts = openingCreditsEnglish;
}
int index = -1;
switch (frameNumber) {
case kStandingByHouse:
case kDesign:
index = 0;
break;
case kBehindHouse:
case kArtDirection:
index = 1;
break;
case kGoSouthwest:
case kTechnicalDirection:
index = 2;
break;
case kWestOfHouse:
case kScreenplay:
index = 3;
break;
case kOpenMailbox:
case kMusic:
index = 4;
break;
case kProducer:
index = 5;
}
if (index != -1 && (_vm->getLanguage() == Common::EN_ANY || frameNumber >= kDesign)) {
_vm->sayText(texts[index], Common::TextToSpeechManager::QUEUE);
}
}
#endif
frameNumber++;
}
delete soundDecoderData;
delete[] frameData;
_audioStream->finish();
_mixer->stopHandle(_audioStreamHandle);
//delete _audioStream;
delete _fd;
if(_surface)
_surface->free();
delete _surface;
return !_aborted;
}
void PmvPlayer::readChunk(uint32 &chunkType, uint32 &chunkSize) {
chunkType = _fd->readUint32BE();
chunkSize = _fd->readUint32LE();
debug(2, "ofs = %08X; chunkType = %c%c%c%c; chunkSize = %d\n",
(int)_fd->pos(),
(chunkType >> 24) & 0xFF, (chunkType >> 16) & 0xFF, (chunkType >> 8) & 0xFF, chunkType & 0xFF,
chunkSize);
}
void PmvPlayer::handleEvents() {
Common::Event event;
while (_vm->_system->getEventManager()->pollEvent(event)) {
switch (event.type) {
case Common::EVENT_KEYDOWN:
if (event.kbd.keycode == Common::KEYCODE_ESCAPE) {
_aborted = true;
_vm->stopTextToSpeech();
}
break;
default:
break;
}
}
}
void PmvPlayer::updateScreen() {
_vm->_system->copyRectToScreen(_surface->getPixels(), _surface->pitch,
(320 - _surface->w) / 2, (200 - _surface->h) / 2, _surface->w, _surface->h);
_vm->_system->updateScreen();
}
void PmvPlayer::decompressPalette(byte *palData, byte *outPal, uint32 palDataSize) {
byte *palDataEnd = palData + palDataSize;
while (palData < palDataEnd) {
byte count = *palData++;
byte entry = *palData++;
if (count == 255 && entry == 255)
break;
memcpy(&outPal[entry * 3], palData, (count + 1) * 3);
palData += (count + 1) * 3;
}
}
}

66
engines/made/pmvplayer.h Normal file
View File

@@ -0,0 +1,66 @@
/* 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/>.
*
*/
#ifndef MADE_PMVPLAYER_H
#define MADE_PMVPLAYER_H
#include "audio/mixer.h"
namespace Common {
class File;
}
namespace Graphics {
struct Surface;
}
namespace Audio {
class QueuingAudioStream;
}
namespace Made {
class MadeEngine;
class PmvPlayer {
public:
PmvPlayer(MadeEngine *vm, Audio::Mixer *mixer);
~PmvPlayer();
// Returns true if the movie was played till the end
bool play(const char *filename);
protected:
MadeEngine *_vm;
Audio::Mixer *_mixer;
Common::File *_fd;
Audio::QueuingAudioStream *_audioStream;
Audio::SoundHandle _audioStreamHandle;
byte _paletteRGB[768];
Graphics::Surface *_surface;
bool _aborted;
void readChunk(uint32 &chunkType, uint32 &chunkSize);
void handleEvents();
void updateScreen();
void decompressPalette(byte *palData, byte *outPal, uint32 palDataSize);
};
}
#endif

447
engines/made/redreader.cpp Normal file
View File

@@ -0,0 +1,447 @@
/* 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/>.
*
*/
#include "made/redreader.h"
#include "common/file.h"
#include "common/memstream.h"
namespace Made {
Common::SeekableReadStream *RedReader::load(const char *redFilename, const char *filename) {
Common::File fd;
FileEntry fileEntry;
if (!fd.open(redFilename))
error("RedReader::RedReader() Could not open %s", redFilename);
if (!seekFile(fd, fileEntry, filename))
error("RedReader::RedReader() Could not find %s in archive %s", filename, redFilename);
byte *fileBuf = (byte *)malloc(fileEntry.origSize);
LzhDecompressor* lzhDec = new LzhDecompressor();
lzhDec->decompress(fd, fileBuf, fileEntry.compSize, fileEntry.origSize);
delete lzhDec;
return new Common::MemoryReadStream(fileBuf, fileEntry.origSize, DisposeAfterUse::YES);
}
Common::SeekableReadStream *RedReader::loadFromRed(const char *redFilename, const char *filename) {
RedReader* red = new RedReader();
Common::SeekableReadStream *stream = red->load(redFilename, filename);
delete red;
return stream;
}
bool RedReader::seekFile(Common::File &fd, FileEntry &fileEntry, const char *filename) {
char arcFilename[13];
while (true) {
fd.skip(8); // skip unknown
fileEntry.compSize = fd.readUint32LE();
if (fd.eos()) break;
fileEntry.origSize = fd.readUint32LE();
fd.skip(10); // skip unknown
fd.read(arcFilename, 13);
fd.skip(2); // skip unknown
// Check if we have found the file
if (!scumm_stricmp(arcFilename, filename))
return true;
// Skip compressed data
fd.skip(fileEntry.compSize);
}
return false;
}
LzhDecompressor::LzhDecompressor() {
freq = nullptr;
len_table = nullptr;
sortptr = nullptr;
_source = nullptr;
_compSize = 0;
_blockPos = 0;
_bitbuf = 0;
_subbitbuf = 0;
_bitcount = 0;
_blocksize = 0;
tree_n = 0;
heapsize = 0;
decode_i = 0;
decode_j = 0;
count_len_depth = 0;
}
LzhDecompressor::~LzhDecompressor() {
}
int LzhDecompressor::decompress(Common::SeekableReadStream &source, byte *dest, uint32 sourceLen, uint32 destLen) {
int bufsize;
byte* buffer;
buffer = (byte *)calloc(DICSIZ, 1);
_source = &source;
_compSize = sourceLen;
count_len_depth = 0;
_blockPos = 0;
decode_start();
while (destLen > 0) {
bufsize = ((destLen > DICSIZ) ? DICSIZ : destLen);
decode(bufsize, buffer);
memcpy(dest, buffer, bufsize);
dest += bufsize;
destLen -= bufsize;
}
free(buffer);
return 0;
}
byte LzhDecompressor::readByte() {
if (_blockPos == 0xFFE) {
_blockPos = 0;
_source->skip(2); // skip unknown value
}
byte temp = _source->readByte();
_blockPos++;
return temp;
}
void LzhDecompressor::fillbuf(int count) {
_bitbuf <<= count;
while (count > _bitcount) {
_bitbuf |= _subbitbuf << (count -= _bitcount);
if (_compSize != 0) {
_compSize--;
_subbitbuf = readByte();
} else _subbitbuf = 0;
_bitcount = 8;
}
_bitbuf |= _subbitbuf >> (_bitcount -= count);
}
uint LzhDecompressor::getbits(int count) {
uint x;
x = _bitbuf >> (BITBUFSIZ - count);
fillbuf(count);
return x;
}
void LzhDecompressor::init_getbits() {
_bitbuf = 0;
_subbitbuf = 0;
_bitcount = 0;
fillbuf(BITBUFSIZ);
}
void LzhDecompressor::decode_start() {
huf_decode_start();
decode_j = 0;
}
void LzhDecompressor::decode(uint count, byte buffer[]) {
uint r, c;
r = 0;
while (--decode_j >= 0) {
buffer[r] = buffer[decode_i];
decode_i = (decode_i + 1) & (DICSIZ - 1);
if (++r == count) return;
}
for ( ; ; ) {
c = decode_c();
if (c <= 255) {
buffer[r] = c;
if (++r == count) return;
} else {
decode_j = c - (255 + 1 - THRESHOLD);
decode_i = (r - decode_p() - 1) & (DICSIZ - 1);
while (--decode_j >= 0) {
buffer[r] = buffer[decode_i];
decode_i = (decode_i + 1) & (DICSIZ - 1);
if (++r == count) return;
}
}
}
}
void LzhDecompressor::read_pt_len(int nn, int nbit, int i_special) {
int i, c, v;
unsigned int mask;
v = getbits(nbit);
if (v == 0) {
c = getbits(nbit);
for (i = 0; i < nn; i++) _pt_len[i] = 0;
for (i = 0; i < 256; i++) _pt_table[i] = c;
} else {
i = 0;
while (i < v) {
c = _bitbuf >> (BITBUFSIZ - 3);
if (c == 7) {
mask = 1U << (BITBUFSIZ - 1 - 3);
while (mask & _bitbuf) { mask >>= 1; c++; }
}
fillbuf((c < 7) ? 3 : c - 3);
_pt_len[i++] = c;
if (i == i_special) {
c = getbits(2);
while (--c >= 0) _pt_len[i++] = 0;
}
}
while (i < nn) _pt_len[i++] = 0;
make_table(nn, _pt_len, 8, _pt_table);
}
}
void LzhDecompressor::read_c_len() {
uint i, v;
int c;
unsigned int mask;
v = getbits(CBIT);
if (v == 0) {
c = getbits(CBIT);
for (i = 0; i < NC; i++) _c_len[i] = 0;
for (i = 0; i < 4096; i++) _c_table[i] = c;
} else {
i = 0;
while (i < v) {
c = _pt_table[_bitbuf >> (BITBUFSIZ - 8)];
if (c >= NT) {
mask = 1U << (BITBUFSIZ - 1 - 8);
do {
if (_bitbuf & mask) c = _right[c];
else c = _left [c];
mask >>= 1;
} while (c >= NT);
}
fillbuf(_pt_len[c]);
if (c <= 2) {
if (c == 0) c = 1;
else if (c == 1) c = getbits(4) + 3;
else c = getbits(CBIT) + 20;
while (--c >= 0) _c_len[i++] = 0;
} else _c_len[i++] = c - 2;
}
while (i < NC) _c_len[i++] = 0;
make_table(NC, _c_len, 12, _c_table);
}
}
unsigned int LzhDecompressor::decode_c() {
uint j, mask;
if (_blocksize == 0) {
_blocksize = getbits(16);
read_pt_len(NT, TBIT, 3);
read_c_len();
read_pt_len(NP, PBIT, -1);
}
_blocksize--;
j = _c_table[_bitbuf >> (BITBUFSIZ - 12)];
if (j >= NC) {
mask = 1U << (BITBUFSIZ - 1 - 12);
do {
if (_bitbuf & mask) j = _right[j];
else j = _left [j];
mask >>= 1;
} while (j >= NC);
}
fillbuf(_c_len[j]);
return j;
}
unsigned int LzhDecompressor::decode_p() {
unsigned int j, mask;
j = _pt_table[_bitbuf >> (BITBUFSIZ - 8)];
if (j >= NP) {
mask = 1U << (BITBUFSIZ - 1 - 8);
do {
if (_bitbuf & mask) j = _right[j];
else j = _left [j];
mask >>= 1;
} while (j >= NP);
}
fillbuf(_pt_len[j]);
if (j != 0) j = (1U << (j - 1)) + getbits(j - 1);
return j;
}
void LzhDecompressor::huf_decode_start() {
init_getbits();
_blocksize = 0;
}
void LzhDecompressor::make_table(uint nchar, byte bitlen[], uint tablebits, uint16 table[]) {
uint16 count[17], weight[17], start[18], *p;
uint i, k, len, ch, jutbits, avail, nextcode, mask;
for (i = 1; i <= 16; i++) count[i] = 0;
for (i = 0; i < nchar; i++) count[bitlen[i]]++;
start[1] = 0;
for (i = 1; i <= 16; i++)
start[i + 1] = start[i] + (count[i] << (16 - i));
if (start[17] != (uint16)(1U << 16))
error("LzhDecompressor::make_table() Bad table");
jutbits = 16 - tablebits;
for (i = 1; i <= tablebits; i++) {
start[i] >>= jutbits;
weight[i] = 1U << (tablebits - i);
}
for (; i <= 16; i++) {
weight[i] = 1U << (16 - i);
}
i = start[tablebits + 1] >> jutbits;
if (i != (uint16)(1U << 16)) {
k = 1U << tablebits;
while (i != k) table[i++] = 0;
}
avail = nchar;
mask = 1U << (15 - tablebits);
for (ch = 0; ch < nchar; ch++) {
if ((len = bitlen[ch]) == 0) continue;
nextcode = start[len] + weight[len];
if (len <= tablebits) {
for (i = start[len]; i < nextcode; i++) table[i] = ch;
} else {
k = start[len];
p = &table[k >> jutbits];
i = len - tablebits;
while (i != 0) {
if (*p == 0) {
_right[avail] = _left[avail] = 0;
*p = avail++;
}
if (k & mask) p = &_right[*p];
else p = &_left[*p];
k <<= 1; i--;
}
*p = ch;
}
start[len] = nextcode;
}
}
/* call with i = root */
void LzhDecompressor::count_len(int i) {
if (i < tree_n)
len_cnt[(count_len_depth < 16) ? count_len_depth : 16]++;
else {
count_len_depth++;
count_len(_left [i]);
count_len(_right[i]);
count_len_depth--;
}
}
void LzhDecompressor::make_len(int root) {
int i, k;
uint cum;
for (i = 0; i <= 16; i++) len_cnt[i] = 0;
count_len(root);
cum = 0;
for (i = 16; i > 0; i--)
cum += len_cnt[i] << (16 - i);
while (cum != (1U << 16)) {
len_cnt[16]--;
for (i = 15; i > 0; i--) {
if (len_cnt[i] != 0) {
len_cnt[i]--;
len_cnt[i+1] += 2;
break;
}
}
cum--;
}
for (i = 16; i > 0; i--) {
k = len_cnt[i];
while (--k >= 0) len_table[*sortptr++] = i;
}
}
void LzhDecompressor::downheap(int i) {
int j, k;
k = heap[i];
while ((j = 2 * i) <= heapsize) {
if (j < heapsize && freq[heap[j]] > freq[heap[j + 1]])
j++;
if (freq[k] <= freq[heap[j]]) break;
heap[i] = heap[j]; i = j;
}
heap[i] = k;
}
void LzhDecompressor::make_code(int n, byte len[], uint16 code[]) {
int i;
uint16 start[18];
start[1] = 0;
for (i = 1; i <= 16; i++)
start[i + 1] = (start[i] + len_cnt[i]) << 1;
for (i = 0; i < n; i++) code[i] = start[len[i]]++;
}
/* make tree, calculate len[], return root */
int LzhDecompressor::make_tree(int nparm, uint16 freqparm[], byte lenparm[], uint16 codeparm[]) {
int i, j, k, avail;
tree_n = nparm;
freq = freqparm;
len_table = lenparm;
avail = tree_n;
heapsize = 0;
heap[1] = 0;
for (i = 0; i < tree_n; i++) {
len_table[i] = 0;
if (freq[i]) heap[++heapsize] = i;
}
if (heapsize < 2) {
codeparm[heap[1]] = 0;
return heap[1];
}
for (i = heapsize / 2; i >= 1; i--)
downheap(i); /* make priority queue */
sortptr = codeparm;
do { /* while queue has at least two entries */
i = heap[1]; /* take out least-freq entry */
if (i < tree_n) *sortptr++ = i;
heap[1] = heap[heapsize--];
downheap(1);
j = heap[1]; /* next least-freq entry */
if (j < tree_n) *sortptr++ = j;
k = avail++; /* generate new node */
freq[k] = freq[i] + freq[j];
heap[1] = k;
downheap(1); /* put into queue */
_left[k] = i;
_right[k] = j;
} while (heapsize > 1);
sortptr = codeparm;
make_len(k);
make_code(nparm, lenparm, codeparm);
return k; /* return root */
}
}

107
engines/made/redreader.h Normal file
View File

@@ -0,0 +1,107 @@
/* 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/>.
*
*/
#ifndef MADE_REDREADER_H
#define MADE_REDREADER_H
#include "common/scummsys.h"
namespace Common {
class SeekableReadStream;
class File;
}
namespace Made {
class RedReader {
public:
Common::SeekableReadStream *load(const char *redFilename, const char *filename);
static Common::SeekableReadStream *loadFromRed(const char *redFilename, const char *filename);
private:
struct FileEntry {
uint32 compSize, origSize;
};
bool seekFile(Common::File &fd, FileEntry &fileEntry, const char *filename);
};
const uint BITBUFSIZ = 16;
const uint DICBIT = 13;
const uint DICSIZ = 1 << DICBIT;
const uint MATCHBIT = 8;
const uint MAXMATCH = 256;
const uint THRESHOLD = 3;
const uint NC = 255 + MAXMATCH + 2 - THRESHOLD;
const uint CBIT = 9;
const uint CODE_BIT = 16;
const uint NP = DICBIT + 1;
const int NT = CODE_BIT + 3;
const uint PBIT = 4;
const uint TBIT = 5;
const uint NPT = NT;
class LzhDecompressor {
public:
LzhDecompressor();
~LzhDecompressor();
int decompress(Common::SeekableReadStream &source, byte *dest, uint32 compSize, uint32 origSize);
private:
Common::SeekableReadStream *_source;
uint32 _compSize, _blockPos;
uint16 _bitbuf;
uint _subbitbuf;
int _bitcount;
uint16 _left[2 * NC - 1], _right[2 * NC - 1];
byte _c_len[NC], _pt_len[NPT];
uint _blocksize;
uint16 _c_table[4096], _pt_table[256];
int tree_n, heapsize;
short heap[NC + 1];
uint16 *freq, *sortptr, len_cnt[17];
byte *len_table;
int decode_i, decode_j;
int count_len_depth;
byte readByte();
void fillbuf(int count);
uint getbits(int count);
void init_getbits();
void decode_start();
void decode(uint count, byte text[]);
void huf_decode_start();
unsigned int decode_c();
unsigned int decode_p();
void read_pt_len(int nn, int nbit, int i_special);
void read_c_len();
void count_len(int i);
void make_len(int root);
void downheap(int i);
void make_code(int n, byte len[], uint16 code[]);
void make_table(uint nchar, byte bitlen[], uint tablebits, uint16 table[]);
int make_tree(int nparm, uint16 freqparm[], byte lenparm[], uint16 codeparm[]);
};
} // End of namespace Made
#endif /* MADE_H */

593
engines/made/resource.cpp Normal file
View File

@@ -0,0 +1,593 @@
/* 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/>.
*
*/
#include "made/resource.h"
#include "made/graphics.h"
#include "common/file.h"
#include "common/memstream.h"
#include "common/debug.h"
#include "graphics/surface.h"
#include "audio/decoders/raw.h"
#include "audio/audiostream.h"
namespace Made {
/* Resource */
Resource::~Resource() {
}
/* PictureResource */
PictureResource::PictureResource() : _picture(nullptr), _picturePalette(nullptr) {
_hasPalette = false;
_paletteColorCount = 0;
}
PictureResource::~PictureResource() {
if (_picture) {
_picture->free();
delete _picture;
_picture = nullptr;
}
delete[] _picturePalette;
_picturePalette = nullptr;
}
void PictureResource::load(byte *source, int size) {
if (READ_BE_UINT32(source) == MKTAG('F','l','e','x')) {
loadChunked(source, size);
} else {
loadRaw(source, size);
}
}
void PictureResource::loadRaw(byte *source, int size) {
// Loads a "raw" picture as used in RtZ, LGoP2, Manhole:N&E and Rodney's Funscreen
Common::MemoryReadStream *sourceS = new Common::MemoryReadStream(source, size);
_hasPalette = (sourceS->readByte() != 0);
byte cmdFlags = sourceS->readByte();
byte pixelFlags = sourceS->readByte();
byte maskFlags = sourceS->readByte();
uint16 cmdOffs = sourceS->readUint16LE();
uint16 pixelOffs = sourceS->readUint16LE();
uint16 maskOffs = sourceS->readUint16LE();
uint16 lineSize = sourceS->readUint16LE();
/*uint16 u = */sourceS->readUint16LE();
uint16 width = sourceS->readUint16LE();
uint16 height = sourceS->readUint16LE();
if (cmdFlags || pixelFlags || maskFlags) {
warning("PictureResource::loadRaw() Graphic has flags set (%d, %d, %d)", cmdFlags, pixelFlags, maskFlags);
}
_paletteColorCount = (cmdOffs - 18) / 3; // 18 = sizeof header
debug(2, "width = %d; height = %d\n", width, height);
if (_hasPalette) {
_picturePalette = new byte[_paletteColorCount * 3];
sourceS->read(_picturePalette, _paletteColorCount * 3);
}
_picture = new Graphics::Surface();
_picture->create(width, height, Graphics::PixelFormat::createFormatCLUT8());
decompressImage(source, *_picture, cmdOffs, pixelOffs, maskOffs, lineSize, cmdFlags, pixelFlags, maskFlags);
delete sourceS;
}
void PictureResource::loadChunked(byte *source, int size) {
// Loads a "chunked" picture as used in Manhole EGA
Common::MemoryReadStream *sourceS = new Common::MemoryReadStream(source, size);
byte cmdFlags = 0, pixelFlags = 0, maskFlags = 0;
uint16 cmdOffs = 0, pixelOffs = 0, maskOffs = 0;
uint16 lineSize = 0, width = 0, height = 0;
sourceS->skip(36); // skip the "Flex" header
_hasPalette = false;
while (!sourceS->eos()) {
uint32 chunkType = sourceS->readUint32BE();
uint32 chunkSize = sourceS->readUint32BE();
if (sourceS->eos())
break;
debug(0, "chunkType = %08X; chunkSize = %d", chunkType, chunkSize);
if (chunkType == MKTAG('R','e','c','t')) {
debug(0, "Rect");
sourceS->skip(4);
height = sourceS->readUint16BE();
width = sourceS->readUint16BE();
debug(0, "width = %d; height = %d", width, height);
} else if (chunkType == MKTAG('f','M','a','p')) {
debug(0, "fMap");
lineSize = sourceS->readUint16BE();
sourceS->skip(11);
cmdFlags = sourceS->readByte();
cmdOffs = sourceS->pos();
sourceS->skip(chunkSize - 14);
debug(0, "lineSize = %d; cmdFlags = %d; cmdOffs = %04X", lineSize, cmdFlags, cmdOffs);
} else if (chunkType == MKTAG('f','L','C','o')) {
debug(0, "fLCo");
sourceS->skip(9);
pixelFlags = sourceS->readByte();
pixelOffs = sourceS->pos();
sourceS->skip(chunkSize - 10);
debug(0, "pixelFlags = %d; pixelOffs = %04X", pixelFlags, pixelOffs);
} else if (chunkType == MKTAG('f','P','i','x')) {
debug(0, "fPix");
sourceS->skip(9);
maskFlags = sourceS->readByte();
maskOffs = sourceS->pos();
sourceS->skip(chunkSize - 10);
debug(0, "maskFlags = %d; maskOffs = %04X", maskFlags, maskOffs);
} else if (chunkType == MKTAG('f','G','C','o')) {
debug(0, "fGCo");
_hasPalette = true;
_paletteColorCount = chunkSize / 3;
_picturePalette = new byte[_paletteColorCount * 3];
sourceS->read(_picturePalette, _paletteColorCount * 3);
} else {
error("PictureResource::loadChunked() Invalid chunk %08X at %08X", chunkType, (int)sourceS->pos());
}
}
if (!cmdOffs || !pixelOffs /*|| !maskOffs*/ || !lineSize || !width || !height) {
error("PictureResource::loadChunked() Error parsing the picture data, one or more chunks/parameters are missing");
}
_picture = new Graphics::Surface();
_picture->create(width, height, Graphics::PixelFormat::createFormatCLUT8());
decompressImage(source, *_picture, cmdOffs, pixelOffs, maskOffs, lineSize, cmdFlags, pixelFlags, maskFlags);
delete sourceS;
}
/* AnimationResource */
AnimationResource::AnimationResource() {
_flags = 0;
_width = 0;
_height = 0;
}
AnimationResource::~AnimationResource() {
for (uint i = 0; i < _frames.size(); i++) {
_frames[i]->free();
delete _frames[i];
}
}
void AnimationResource::load(byte *source, int size) {
Common::MemoryReadStream *sourceS = new Common::MemoryReadStream(source, size);
sourceS->readUint32LE();
sourceS->readUint32LE();
sourceS->readUint16LE();
_flags = sourceS->readUint16LE();
_width = sourceS->readUint16LE();
_height = sourceS->readUint16LE();
sourceS->readUint32LE();
uint16 frameCount = sourceS->readUint16LE();
sourceS->readUint16LE();
sourceS->readUint16LE();
for (uint16 i = 0; i < frameCount; i++) {
sourceS->seek(26 + i * 4);
uint32 frameOffs = sourceS->readUint32LE();
sourceS->seek(frameOffs);
sourceS->readUint32LE();
sourceS->readUint32LE();
uint16 frameWidth = sourceS->readUint16LE();
uint16 frameHeight = sourceS->readUint16LE();
uint16 cmdOffs = sourceS->readUint16LE();
sourceS->readUint16LE();
uint16 pixelOffs = sourceS->readUint16LE();
sourceS->readUint16LE();
uint16 maskOffs = sourceS->readUint16LE();
sourceS->readUint16LE();
uint16 lineSize = sourceS->readUint16LE();
Graphics::Surface *frame = new Graphics::Surface();
frame->create(frameWidth, frameHeight, Graphics::PixelFormat::createFormatCLUT8());
decompressImage(source + frameOffs, *frame, cmdOffs, pixelOffs, maskOffs, lineSize, 0, 0, 0, _flags & 1);
_frames.push_back(frame);
}
delete sourceS;
}
/* SoundResource */
SoundResource::SoundResource() : _soundSize(0), _soundData(nullptr) {
_soundEnergyArray = nullptr;
}
SoundResource::~SoundResource() {
delete[] _soundData;
delete _soundEnergyArray;
}
void SoundResource::load(byte *source, int size) {
uint16 chunkCount = READ_LE_UINT16(source + 8);
uint16 chunkSize = READ_LE_UINT16(source + 12);
_soundSize = chunkCount * chunkSize;
_soundData = new byte[_soundSize];
_soundEnergyArray = new SoundEnergyArray;
decompressSound(source + 14, _soundData, chunkSize, chunkCount, _soundEnergyArray);
}
Audio::AudioStream *SoundResource::getAudioStream(int soundRate, bool loop) {
Audio::RewindableAudioStream *stream =
Audio::makeRawStream(_soundData, _soundSize, soundRate, Audio::FLAG_UNSIGNED, DisposeAfterUse::NO);
if (loop)
return Audio::makeLoopingAudioStream(stream, 0);
else
return stream;
}
void SoundResourceV1::load(byte *source, int size) {
_soundSize = size * 4;
_soundData = new byte[_soundSize];
ManholeEgaSoundDecompressor dec;
dec.decompress(source, _soundData, size);
}
/* MenuResource */
MenuResource::MenuResource() {
}
MenuResource::~MenuResource() {
}
void MenuResource::load(byte *source, int size) {
_strings.clear();
Common::MemoryReadStream *sourceS = new Common::MemoryReadStream(source, size);
sourceS->skip(4); // skip "MENU"
uint16 count = sourceS->readUint16LE();
for (uint16 i = 0; i < count; i++) {
uint16 offs = sourceS->readUint16LE();
const char *string = (const char*)(source + offs);
_strings.push_back(string);
debug(2, "%02d: %s\n", i, string);
}
delete sourceS;
}
const char *MenuResource::getString(uint index) const {
if (index < _strings.size())
return _strings[index].c_str();
else
return nullptr;
}
/* FontResource */
FontResource::FontResource() : _data(nullptr), _size(0) {
}
FontResource::~FontResource() {
delete[] _data;
}
void FontResource::load(byte *source, int size) {
_data = new byte[size];
_size = size;
memcpy(_data, source, size);
}
int FontResource::getHeight() const {
return _data[0];
}
int FontResource::getCharWidth(uint c) const {
byte *charData = getCharData(c);
if (charData)
return charData[0];
else
return 0;
}
byte *FontResource::getChar(uint c) const {
byte *charData = getCharData(c);
if (charData)
return charData + 1;
else
return nullptr;
}
int FontResource::getTextWidth(const char *text) {
int width = 0;
if (text) {
int len = strlen(text);
for (int pos = 0; pos < len; pos++)
width += getCharWidth(text[pos]);
}
return width;
}
byte *FontResource::getCharData(uint c) const {
if (c < 28 || c > 255)
return nullptr;
return _data + 1 + (c - 28) * (getHeight() + 1);
}
/* GenericResource */
GenericResource::GenericResource() : _data(nullptr), _size(0) {
}
GenericResource::~GenericResource() {
delete[] _data;
}
void GenericResource::load(byte *source, int size) {
_data = new byte[size];
_size = size;
memcpy(_data, source, size);
}
/* ResourceReader */
ResourceReader::ResourceReader() {
_isV1 = false;
_cacheDataSize = 0;
_fd = _fdMusic = _fdPics = _fdSounds = nullptr;
_cacheCount = 0;
}
ResourceReader::~ResourceReader() {
if (!_isV1) {
delete _fd;
} else {
delete _fdPics;
delete _fdSounds;
delete _fdMusic;
}
}
// V2
void ResourceReader::open(const char *filename) {
_fd = new Common::File();
if (!_fd->open(filename))
error("ResourceReader::open() Could not open '%s'", filename);
_fd->skip(0x18); // skip header for now
uint16 indexCount = _fd->readUint16LE();
for (uint16 i = 0; i < indexCount; i++) {
uint32 resType = _fd->readUint32BE();
uint32 indexOffs = _fd->readUint32LE();
_fd->readUint32LE();
_fd->readUint32LE();
_fd->readUint32LE();
_fd->readUint16LE();
_fd->readUint16LE();
// We don't need ARCH, FREE and OMNI resources
if (resType == kResARCH || resType == kResFREE || resType == kResOMNI)
continue;
//debug(2, "resType = %08X; indexOffs = %d\n", resType, indexOffs);
uint32 oldOffs = _fd->pos();
ResourceSlots *resSlots = new ResourceSlots();
_fd->seek(indexOffs);
loadIndex(resSlots);
_resSlots[resType] = resSlots;
_fd->seek(oldOffs);
}
_cacheCount = 0;
}
// V1
void ResourceReader::openResourceBlocks() {
_isV1 = true;
_fdPics = new Common::File();
_fdSounds = new Common::File();
_fdMusic = new Common::File();
openResourceBlock("pics.blk", _fdPics, kResFLEX);
openResourceBlock("snds.blk", _fdSounds, kResSNDS);
openResourceBlock("music.blk", _fdMusic, kResMIDI);
}
void ResourceReader::openResourceBlock(const char *filename, Common::File *blockFile, uint32 resType) {
if (!blockFile->open(filename))
error("Failed to open '%s'", filename);
blockFile->readUint16LE(); // Skip unused
uint16 count = blockFile->readUint16LE();
blockFile->readUint16LE(); // Skip unused
uint32 type = blockFile->readUint32BE();
if (type != kResFLEX)
warning("openResourceBlocks: resource header is not 'FLEX'");
_resSlots[resType] = new ResourceSlots();
// Add dummy entry since the resources are 1-based
_resSlots[resType]->push_back(ResourceSlot(0, 0));
for (uint16 i = 0; i < count; i++) {
uint32 offset = blockFile->readUint32LE();
blockFile->readUint32LE();
uint32 size = blockFile->readUint32LE();
_resSlots[resType]->push_back(ResourceSlot(offset, size));
}
}
PictureResource *ResourceReader::getPicture(int index) {
return createResource<PictureResource>(kResFLEX, index);
}
AnimationResource *ResourceReader::getAnimation(int index) {
return createResource<AnimationResource>(kResANIM, index);
}
SoundResource *ResourceReader::getSound(int index) {
if (!_isV1)
return createResource<SoundResource>(kResSNDS, index);
else
return createResource<SoundResourceV1>(kResSNDS, index);
}
MenuResource *ResourceReader::getMenu(int index) {
return createResource<MenuResource>(kResMENU, index);
}
FontResource *ResourceReader::getFont(int index) {
return createResource<FontResource>(kResFONT, index);
}
GenericResource *ResourceReader::getXmidi(int index) {
return createResource<GenericResource>(kResXMID, index);
}
GenericResource *ResourceReader::getMidi(int index) {
return createResource<GenericResource>(kResMIDI, index);
}
void ResourceReader::loadIndex(ResourceSlots *slots) {
_fd->readUint32LE(); // skip INDX
_fd->readUint32LE(); // skip index size
_fd->readUint32LE(); // skip unknown
_fd->readUint32LE(); // skip res type
uint16 count1 = _fd->readUint16LE();
uint16 count2 = _fd->readUint16LE();
uint16 count = MAX(count1, count2);
_fd->readUint16LE(); // skip unknown count
for (uint16 i = 0; i < count; i++) {
uint32 offs = _fd->readUint32LE();
uint32 size = _fd->readUint32LE();
slots->push_back(ResourceSlot(offs, size));
}
}
void ResourceReader::freeResource(Resource *resource) {
tossResourceFromCache(resource->_slot);
}
bool ResourceReader::loadResource(ResourceSlot *slot, byte *&buffer, uint32 &size) {
int offset = !_isV1 ? 62 : 0;
if (slot && slot->size > 0) {
size = slot->size - offset;
buffer = new byte[size];
debug(2, "ResourceReader::loadResource() %08X", slot->offs + offset);
_fd->seek(slot->offs + offset);
_fd->read(buffer, size);
return true;
} else {
return false;
}
}
ResourceSlot *ResourceReader::getResourceSlot(uint32 resType, uint index) {
ResourceSlots *slots = _resSlots[resType];
if (!slots)
return nullptr;
if (index >= 1 && index < slots->size()) {
return &(*slots)[index];
} else {
return nullptr;
}
}
Resource *ResourceReader::getResourceFromCache(ResourceSlot *slot) {
if (slot->res)
slot->refCount++;
return slot->res;
}
void ResourceReader::addResourceToCache(ResourceSlot *slot, Resource *res) {
_cacheDataSize += slot->size;
if (_cacheDataSize >= kMaxResourceCacheSize) {
purgeCache();
}
slot->res = res;
slot->refCount = 1;
_cacheCount++;
}
void ResourceReader::tossResourceFromCache(ResourceSlot *slot) {
if (slot->res)
slot->refCount--;
}
void ResourceReader::purgeCache() {
debug(2, "ResourceReader::purgeCache()");
for (const auto &res : _resSlots) {
ResourceSlots *slots = res._value;
for (ResourceSlots::iterator slotIter = slots->begin(); slotIter != slots->end(); ++slotIter) {
ResourceSlot *slot = &(*slotIter);
if (slot->refCount <= 0 && slot->res) {
_cacheDataSize -= slot->size;
delete slot->res;
slot->res = nullptr;
slot->refCount = 0;
_cacheCount--;
}
}
}
}
} // End of namespace Made

269
engines/made/resource.h Normal file
View File

@@ -0,0 +1,269 @@
/* 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/>.
*
*/
#ifndef MADE_RESOURCE_H
#define MADE_RESOURCE_H
#include "made/sound.h"
#include "common/endian.h"
#include "common/array.h"
#include "common/hashmap.h"
namespace Common {
class File;
}
namespace Audio {
class AudioStream;
}
namespace Graphics {
struct Surface;
}
namespace Made {
/// This value specifies the size of the resource cache
/// which stores recently used resources. On the DS,
/// 400Kb is all we can spare, while 1Mb seems like a
/// good value for larger systems.
#ifndef __DS__
const int kMaxResourceCacheSize = 1000 * 1024;
#else
const int kMaxResourceCacheSize = 400 * 1024;
#endif
enum ResourceType {
kResARCH = MKTAG('A','R','C','H'),
kResFREE = MKTAG('F','R','E','E'),
kResOMNI = MKTAG('O','M','N','I'),
kResFLEX = MKTAG('F','L','E','X'),
kResSNDS = MKTAG('S','N','D','S'),
kResANIM = MKTAG('A','N','I','M'),
kResMENU = MKTAG('M','E','N','U'),
kResFONT = MKTAG('F','O','N','T'),
kResXMID = MKTAG('X','M','I','D'),
kResMIDI = MKTAG('M','I','D','I')
};
struct ResourceSlot;
class Resource {
public:
ResourceSlot *_slot;
virtual ~Resource();
};
class PictureResource : public Resource {
public:
PictureResource();
~PictureResource() override;
void load(byte *source, int size);
Graphics::Surface *getPicture() const { return _picture; }
byte *getPalette() const { return _picturePalette; }
bool hasPalette() const { return _hasPalette; }
int getPaletteColorCount() const { return _paletteColorCount; }
protected:
Graphics::Surface *_picture;
byte *_picturePalette;
int _paletteColorCount;
bool _hasPalette;
void loadRaw(byte *source, int size);
void loadChunked(byte *source, int size);
};
class AnimationResource : public Resource {
public:
AnimationResource();
~AnimationResource() override;
void load(byte *source, int size);
int getCount() const { return _frames.size(); }
Graphics::Surface *getFrame(int index) const {
if ((uint)index < _frames.size()) {
return _frames[index];
} else {
warning("getFrame: Tried to obtain invalid frame %i, array has %i frames", index, _frames.size());
return _frames[_frames.size() - 1];
}
}
uint16 getFlags() const { return _flags; }
int16 getWidth() const { return _width; }
int16 getHeight() const { return _height; }
protected:
Common::Array<Graphics::Surface *> _frames;
uint16 _flags;
int16 _width, _height;
};
class SoundResource : public Resource {
public:
SoundResource();
~SoundResource() override;
virtual void load(byte *source, int size);
Audio::AudioStream *getAudioStream(int soundRate, bool loop = false);
SoundEnergyArray *getSoundEnergyArray() const { return _soundEnergyArray; }
int getSoundSize() const { return _soundSize; }
protected:
byte *_soundData;
int _soundSize;
SoundEnergyArray *_soundEnergyArray;
};
class SoundResourceV1 : public SoundResource {
public:
SoundResourceV1() {}
~SoundResourceV1() override {}
void load(byte *source, int size) override;
};
class MenuResource : public Resource {
public:
MenuResource();
~MenuResource() override;
void load(byte *source, int size);
int getCount() const { return _strings.size(); }
const char *getString(uint index) const;
protected:
Common::Array<Common::String> _strings;
};
class FontResource : public Resource {
public:
FontResource();
~FontResource() override;
void load(byte *source, int size);
int getHeight() const;
int getCharWidth(uint c) const;
int getTextWidth(const char *text);
byte *getChar(uint c) const;
protected:
byte *_data;
int _size;
byte *getCharData(uint c) const;
};
class GenericResource : public Resource {
public:
GenericResource();
~GenericResource() override;
void load(byte *source, int size);
byte *getData() const { return _data; }
int getSize() const { return _size; }
protected:
byte *_data;
int _size;
};
struct ResourceSlot {
uint32 offs;
uint32 size;
Resource *res;
int refCount;
ResourceSlot() : offs(0), size(0), res(NULL), refCount(0) {
}
ResourceSlot(uint32 roffs, uint32 rsize) : offs(roffs), size(rsize), res(NULL), refCount(0) {
}
};
class ResourceReader {
public:
ResourceReader();
~ResourceReader();
void open(const char *filename);
void openResourceBlocks();
PictureResource *getPicture(int index);
AnimationResource *getAnimation(int index);
SoundResource *getSound(int index);
MenuResource *getMenu(int index);
FontResource *getFont(int index);
GenericResource *getXmidi(int index);
GenericResource *getMidi(int index);
void freeResource(Resource *resource);
protected:
Common::File *_fd;
Common::File *_fdPics, *_fdSounds, *_fdMusic; // V1
bool _isV1;
typedef Common::Array<ResourceSlot> ResourceSlots;
typedef Common::HashMap<uint32, ResourceSlots *> ResMap;
void openResourceBlock(const char *filename, Common::File *blockFile, uint32 resType);
ResMap _resSlots;
int _cacheCount;
int _cacheDataSize;
void loadIndex(ResourceSlots *slots);
template<class T>
T *createResource(uint32 resType, int index) {
ResourceSlot *slot = getResourceSlot(resType, index);
if (!slot)
return NULL;
T *res = (T *)getResourceFromCache(slot);
if (!res) {
byte *buffer;
uint32 size;
// Read from the correct file for V1 games
if (_isV1) {
switch (resType) {
case kResSNDS:
_fd = _fdSounds;
break;
case kResMIDI:
_fd = _fdMusic;
break;
default:
_fd = _fdPics;
break;
}
}
if (loadResource(slot, buffer, size)) {
res = new T();
res->_slot = slot;
res->load(buffer, size);
addResourceToCache(slot, res);
delete[] buffer;
}
}
return res;
}
bool loadResource(ResourceSlot *slot, byte *&buffer, uint32 &size);
ResourceSlot *getResourceSlot(uint32 resType, uint index);
Resource *getResourceFromCache(ResourceSlot *slot);
void addResourceToCache(ResourceSlot *slot, Resource *res);
void tossResourceFromCache(ResourceSlot *slot);
void purgeCache();
};
} // End of namespace Made
#endif /* MADE_H */

990
engines/made/screen.cpp Normal file
View File

@@ -0,0 +1,990 @@
/* 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/>.
*
*/
#include "made/screen.h"
#include "made/made.h"
#include "made/screenfx.h"
#include "made/database.h"
#include "common/system.h"
#include "graphics/surface.h"
#include "graphics/paletteman.h"
#include "graphics/cursorman.h"
namespace Made {
enum TextChannelIndex {
kTapeRecorderName = 84,
kTapeRecorderTrack = 85,
kTapeRecorderMaxTrack = 86,
kTapeRecorderTime = 87,
kTapeRecorderScan = 88,
kHoverOver = 89,
kClickMessage = 97
};
enum TapeRecorderIndex {
kTime = 0,
kMaxTrack = 1,
kTrack = 2,
kName = 3
};
Screen::Screen(MadeEngine *vm) : _vm(vm) {
_palette = new byte[768];
_newPalette = new byte[768];
_backgroundScreen = new Graphics::Surface();
_backgroundScreen->create(320, 200, Graphics::PixelFormat::createFormatCLUT8());
_workScreen = new Graphics::Surface();
_workScreen->create(320, 200, Graphics::PixelFormat::createFormatCLUT8());
_backgroundScreenDrawCtx.clipRect = Common::Rect(320, 200);
_workScreenDrawCtx.clipRect = Common::Rect(320, 200);
_backgroundScreenDrawCtx.destSurface = _backgroundScreen;
_workScreenDrawCtx.destSurface = _workScreen;
_clipArea.destSurface = _workScreen;
// Screen mask is only needed in v2 games
if (_vm->getGameID() != GID_RTZ) {
_screenMask = new Graphics::Surface();
_screenMask->create(320, 200, Graphics::PixelFormat::createFormatCLUT8());
_maskDrawCtx.clipRect = Common::Rect(320, 200);
_maskDrawCtx.destSurface = _screenMask;
}
for (int i = 0; i <= 3; i++)
_excludeClipAreaEnabled[i] = false;
_screenLock = false;
_paletteLock = false;
_paletteInitialized = false;
_needPalette = false;
_oldPaletteColorCount = 256;
_paletteColorCount = 256;
memset(_newPalette, 0, 768);
memset(_palette, 0, 768);
_ground = 1;
_clip = 0;
_exclude = 0;
_mask = 0;
_visualEffectNum = 0;
_fx = new ScreenEffects(this);
_textX = 0;
_textY = 0;
_textColor = 0;
_textRect.left = 0;
_textRect.top = 0;
_textRect.right = 320;
_textRect.bottom = 200;
_font = nullptr;
_currentFontNum = 0;
_fontDrawCtx.clipRect = Common::Rect(320, 200);
_fontDrawCtx.destSurface = _backgroundScreen;
_outlineColor = 0;
_dropShadowColor = 0;
_queueNextText = false;
_voiceTimeText = false;
clearChannels();
}
Screen::~Screen() {
delete[] _palette;
delete[] _newPalette;
delete _backgroundScreen;
delete _workScreen;
if (_vm->getGameID() != GID_RTZ)
delete _screenMask;
delete _fx;
}
void Screen::clearScreen() {
_backgroundScreen->fillRect(Common::Rect(0, 0, 320, 200), 0);
_workScreen->fillRect(Common::Rect(0, 0, 320, 200), 0);
if (_vm->getGameID() != GID_RTZ)
_screenMask->fillRect(Common::Rect(0, 0, 320, 200), 0);
_mask = 0;
_needPalette = true;
}
void Screen::setExcludeArea(uint16 x1, uint16 y1, uint16 x2, uint16 y2) {
_excludeClipAreaEnabled[0] = false;
_excludeClipAreaEnabled[1] = false;
_excludeClipAreaEnabled[2] = false;
_excludeClipAreaEnabled[3] = false;
if (x1 == 0 && y1 == 0 && x2 == 0 && y2 == 0) {
_excludeClipArea[0].clipRect = Common::Rect(320, 200);
_excludeClipAreaEnabled[0] = true;
return;
}
if (y1 > 0 && y2 > 0) {
_excludeClipArea[0].clipRect = Common::Rect(320, y1);
_excludeClipAreaEnabled[0] = true;
}
if (y1 < 200 && y2 < 200) {
_excludeClipArea[1].clipRect = Common::Rect(0, y2, 320, 200);
_excludeClipAreaEnabled[1] = true;
}
if (x1 > 0 && x2 > 0) {
_excludeClipArea[2].clipRect = Common::Rect(0, y1, x1, y2);
_excludeClipAreaEnabled[2] = true;
}
if (x1 < 320 && x2 < 320) {
_excludeClipArea[3].clipRect = Common::Rect(x2, y1, 320, y2);
_excludeClipAreaEnabled[3] = true;
}
}
void Screen::drawSurface(Graphics::Surface *sourceSurface, int x, int y, int16 flipX, int16 flipY, int16 mask, const ClipInfo &clipInfo) {
byte *source, *dest, *maskp = nullptr;
int startX = 0;
int startY = 0;
int clipWidth = sourceSurface->w;
int clipHeight = sourceSurface->h;
if (x < clipInfo.clipRect.left) {
startX = clipInfo.clipRect.left - x;
clipWidth -= startX;
x = clipInfo.clipRect.left;
}
if (y < clipInfo.clipRect.top) {
startY = clipInfo.clipRect.top - y;
clipHeight -= startY;
y = clipInfo.clipRect.top;
}
if (x + clipWidth > clipInfo.clipRect.right) {
clipWidth = clipInfo.clipRect.right - x;
}
if (y + clipHeight > clipInfo.clipRect.bottom) {
clipHeight = clipInfo.clipRect.bottom - y;
}
source = (byte *)sourceSurface->getBasePtr(0, startY);
dest = (byte *)clipInfo.destSurface->getBasePtr(x, y);
if (_vm->getGameID() != GID_RTZ)
maskp = (byte *)_maskDrawCtx.destSurface->getBasePtr(x, y);
int32 sourcePitch, linePtrAdd, sourceAdd;
byte *linePtr;
if (flipX) {
linePtrAdd = -1;
sourceAdd = sourceSurface->w - startX - 1;
} else {
linePtrAdd = 1;
sourceAdd = startX;
}
if (flipY) {
sourcePitch = -sourceSurface->pitch;
source += (clipHeight - 1) * sourceSurface->pitch;
} else {
sourcePitch = sourceSurface->pitch;
}
for (int16 yc = 0; yc < clipHeight; yc++) {
linePtr = source + sourceAdd;
for (int16 xc = 0; xc < clipWidth; xc++) {
if (*linePtr && (_vm->getGameID() == GID_RTZ || (mask == 0 || (maskp && maskp[xc] == 0)))) {
if (*linePtr)
dest[xc] = *linePtr;
}
linePtr += linePtrAdd;
}
source += sourcePitch;
dest += clipInfo.destSurface->pitch;
if (_vm->getGameID() != GID_RTZ)
maskp += _maskDrawCtx.destSurface->pitch;
}
}
void Screen::setRGBPalette(byte *palRGB, int start, int count) {
_vm->_system->getPaletteManager()->setPalette(palRGB, start, count);
}
uint16 Screen::updateChannel(uint16 channelIndex) {
return channelIndex;
}
void Screen::deleteChannel(uint16 channelIndex) {
if (channelIndex < 1 || channelIndex >= 100)
return;
if (_channels[channelIndex - 1].type == 2) {
_channels[channelIndex - 1].previousText.clear();
}
_channels[channelIndex - 1].type = 0;
_channels[channelIndex - 1].state = 0;
_channels[channelIndex - 1].index = 0;
}
int16 Screen::getChannelType(uint16 channelIndex) {
if (channelIndex < 1 || channelIndex >= 100)
return -1;
return _channels[channelIndex - 1].type;
}
int16 Screen::getChannelState(uint16 channelIndex) {
if (channelIndex < 1 || channelIndex >= 100)
return -1;
return _channels[channelIndex - 1].state;
}
void Screen::setChannelState(uint16 channelIndex, int16 state) {
if (channelIndex < 1 || channelIndex >= 100 || _channels[channelIndex - 1].type == 0)
return;
if (state != _channels[channelIndex - 1].state && _channels[channelIndex - 1].type == 2) {
_channels[channelIndex - 1].previousText.clear();
_queueNextText = true;
}
_channels[channelIndex - 1].state = state;
}
uint16 Screen::setChannelLocation(uint16 channelIndex, int16 x, int16 y) {
if (channelIndex < 1 || channelIndex >= 100 || _channels[channelIndex - 1].type == 0)
return 0;
_channels[channelIndex - 1].x = x;
_channels[channelIndex - 1].y = y;
return updateChannel(channelIndex - 1) + 1;
}
uint16 Screen::setChannelContent(uint16 channelIndex, uint16 index) {
if (channelIndex < 1 || channelIndex >= 100 || _channels[channelIndex - 1].type == 0)
return 0;
_channels[channelIndex - 1].index = index;
return updateChannel(channelIndex - 1) + 1;
}
void Screen::setChannelUseMask(uint16 channelIndex) {
if (channelIndex < 1 || channelIndex >= 100)
return;
_channels[channelIndex - 1].mask = _mask;
}
void Screen::drawSpriteChannels(const ClipInfo &clipInfo, int16 includeStateMask, int16 excludeStateMask) {
for (int i = 0; i <= 3; i++)
_excludeClipArea[i].destSurface = clipInfo.destSurface;
_clipArea.destSurface = clipInfo.destSurface;
for (uint16 i = 0; i < _channelsUsedCount; i++) {
debug(2, "drawSpriteChannels() i = %d\n", i);
if (((_channels[i].state & includeStateMask) == includeStateMask) && (_channels[i].state & excludeStateMask) == 0) {
int16 flipX = _channels[i].state & 0x10;
int16 flipY = _channels[i].state & 0x20;
debug(2, "drawSpriteChannels() type = %d; index = %04X\n", _channels[i].type, _channels[i].index);
switch (_channels[i].type) {
case 1: // drawFlex
if (_channels[i].state & 4) {
drawFlex(_channels[i].index, _channels[i].x, _channels[i].y, flipX, flipY, _channels[i].mask, _clipArea);
} else if (_channels[i].state & 8) {
for (int excludeIndex = 0; excludeIndex < 4; excludeIndex++) {
if (_excludeClipAreaEnabled[excludeIndex]) {
drawFlex(_channels[i].index, _channels[i].x, _channels[i].y, flipX, flipY, _channels[i].mask, _excludeClipArea[excludeIndex]);
}
}
} else {
drawFlex(_channels[i].index, _channels[i].x, _channels[i].y, flipX, flipY, _channels[i].mask, clipInfo);
}
break;
case 2: // drawObjectText
printObjectText(_channels[i].index, _channels[i].x, _channels[i].y, _channels[i].fontNum, _channels[i].textColor, _channels[i].outlineColor, clipInfo);
break;
case 3: // drawAnimFrame
if (_channels[i].state & 4) {
drawAnimFrame(_channels[i].index, _channels[i].x, _channels[i].y, _channels[i].frameNum, flipX, flipY, _clipArea);
} else if (_channels[i].state & 8) {
for (int excludeIndex = 0; excludeIndex < 4; excludeIndex++) {
if (_excludeClipAreaEnabled[excludeIndex]) {
drawAnimFrame(_channels[i].index, _channels[i].x, _channels[i].y, _channels[i].frameNum, flipX, flipY, _excludeClipArea[excludeIndex]);
}
}
} else {
drawAnimFrame(_channels[i].index, _channels[i].x, _channels[i].y, _channels[i].frameNum, flipX, flipY, clipInfo);
}
break;
case 4: // drawMenuText
// Never used in any game
break;
default:
break;
}
}
}
}
void Screen::updateSprites() {
// TODO: This needs some more work, dirty rectangles are currently not used
memcpy(_workScreen->getPixels(), _backgroundScreen->getPixels(), 64000);
drawSpriteChannels(_backgroundScreenDrawCtx, 3, 0);
drawSpriteChannels(_workScreenDrawCtx, 1, 2);
_vm->_system->copyRectToScreen(_workScreen->getPixels(), _workScreen->pitch, 0, 0, _workScreen->w, _workScreen->h);
_vm->_screen->updateScreenAndWait(10);
}
void Screen::clearChannels() {
for (uint16 i = 0; i < ARRAYSIZE(_channels); i++) {
_channels[i].type = 0;
_channels[i].index = 0;
_channels[i].mask = 0;
}
_channelsUsedCount = 0;
}
uint16 Screen::drawFlex(uint16 flexIndex, int16 x, int16 y, int16 flipX, int16 flipY, int16 mask, const ClipInfo &clipInfo) {
if (flexIndex == 0)
return 0;
PictureResource *flex = _vm->_res->getPicture(flexIndex);
if (!flex)
error("Failed to find picture %d", flexIndex);
Graphics::Surface *sourceSurface = flex->getPicture();
drawSurface(sourceSurface, x, y, flipX, flipY, mask, clipInfo);
// Palette is set in showPage
if (flex->hasPalette() && !_paletteLock && _needPalette) {
byte *flexPalette = flex->getPalette();
_oldPaletteColorCount = _paletteColorCount;
_paletteColorCount = flex->getPaletteColorCount();
memcpy(_newPalette, _palette, _oldPaletteColorCount * 3);
memcpy(_palette, flexPalette, _paletteColorCount * 3);
_needPalette = false;
}
_vm->_res->freeResource(flex);
return 0;
}
void Screen::drawAnimFrame(uint16 animIndex, int16 x, int16 y, int16 frameNum, int16 flipX, int16 flipY, const ClipInfo &clipInfo) {
if (frameNum < 0)
return;
AnimationResource *anim = _vm->_res->getAnimation(animIndex);
Graphics::Surface *sourceSurface = anim->getFrame(frameNum);
drawSurface(sourceSurface, x, y, flipX, flipY, 0, clipInfo);
_vm->_res->freeResource(anim);
}
uint16 Screen::drawPic(uint16 index, int16 x, int16 y, int16 flipX, int16 flipY) {
drawFlex(index, x, y, flipX, flipY, 0, _backgroundScreenDrawCtx);
#ifdef USE_TTS
if (_vm->getGameID() == GID_RTZ && index > 0) {
if (index == 843) { // Save/load screen
_vm->_saveLoadScreenOpen = true;
_vm->_rtzSaveLoadIndex = 0;
_vm->_rtzFirstSaveSlot = 0;
} else {
_vm->_saveLoadScreenOpen = false;
}
if (index == 1501) { // Tape recorder
_vm->_tapeRecorderOpen = true;
} else {
_vm->_tapeRecorderOpen = false;
}
} else if (_vm->getGameID() == GID_LGOP2) {
if (index == 465) { // Save/load screen (Play-O-Matic)
_vm->_playOMaticButtonIndex = 0;
_vm->_saveLoadScreenOpen = true;
} else if (index == 196) { // Play-O-Matic button highlights
_vm->checkHoveringPlayOMatic(y);
} else if (index != 463 && index != 0) {
// 1216 is drawn before "best interactive fiction" line, 757 before "choose your character", and 761 before the
// second copyright message, all of which need voicing
if (index == 1216 || index == 757 || index == 761) {
_vm->_forceVoiceText = true;
}
_vm->_saveLoadScreenOpen = false;
}
}
#endif
return 0;
}
uint16 Screen::drawMask(uint16 index, int16 x, int16 y) {
drawFlex(index, x, y, 0, 0, 0, _maskDrawCtx);
return 0;
}
uint16 Screen::drawAnimPic(uint16 animIndex, int16 x, int16 y, int16 frameNum, int16 flipX, int16 flipY) {
drawAnimFrame(animIndex, x, y, frameNum, flipX, flipY, _backgroundScreenDrawCtx);
return 0;
}
void Screen::addSprite(uint16 spriteIndex) {
bool oldScreenLock = _screenLock;
drawFlex(spriteIndex, 0, 0, 0, 0, 0, _backgroundScreenDrawCtx);
_screenLock = oldScreenLock;
}
uint16 Screen::drawSprite(uint16 flexIndex, int16 x, int16 y) {
return placeSprite(_channelsUsedCount + 1, flexIndex, x, y);
}
uint16 Screen::placeSprite(uint16 channelIndex, uint16 flexIndex, int16 x, int16 y) {
debug(2, "placeSprite(%d, %04X, %d, %d)\n", channelIndex, flexIndex, x, y);
if (channelIndex < 1 || channelIndex >= 100)
return 0;
channelIndex--;
PictureResource *flex = _vm->_res->getPicture(flexIndex);
if (flex) {
//Graphics::Surface *surf = flex->getPicture();
int16 state = 1;
/*int16 x1, y1, x2, y2;
x1 = x;
y1 = y;
x2 = x + surf->w + 1;
y2 = y + surf->h + 1;*/
if (_ground == 0)
state |= 2;
if (_clip != 0)
state |= 4;
if (_exclude != 0)
state |= 8;
_channels[channelIndex].state = state;
_channels[channelIndex].type = 1;
_channels[channelIndex].index = flexIndex;
_channels[channelIndex].x = x;
_channels[channelIndex].y = y;
if (_channelsUsedCount <= channelIndex)
_channelsUsedCount = channelIndex + 1;
_vm->_res->freeResource(flex);
} else {
_channels[channelIndex].type = 0;
_channels[channelIndex].state = 0;
}
return channelIndex + 1;
}
uint16 Screen::placeAnim(uint16 channelIndex, uint16 animIndex, int16 x, int16 y, int16 frameNum) {
if (channelIndex < 1 || channelIndex >= 100)
return 0;
channelIndex--;
AnimationResource *anim = _vm->_res->getAnimation(animIndex);
if (anim) {
int16 state = 1;
/*int16 x1, y1, x2, y2;
x1 = x;
y1 = y;
x2 = x + anim->getWidth();
y2 = y + anim->getHeight();*/
if (anim->getFlags() == 1 || _ground == 0)
state |= 2;
if (_clip != 0)
state |= 4;
if (_exclude != 0)
state |= 8;
_channels[channelIndex].state = state;
_channels[channelIndex].type = 3;
_channels[channelIndex].index = animIndex;
_channels[channelIndex].frameNum = frameNum;
_channels[channelIndex].x = x;
_channels[channelIndex].y = y;
if (_channelsUsedCount <= channelIndex)
_channelsUsedCount = channelIndex + 1;
_vm->_res->freeResource(anim);
} else {
_channels[channelIndex].type = 0;
_channels[channelIndex].state = 0;
}
return channelIndex + 1;
}
int16 Screen::setAnimFrame(uint16 channelIndex, int16 frameNum) {
if (channelIndex < 1 || channelIndex >= 100 || _channels[channelIndex - 1].type == 0)
return 0;
channelIndex--;
_channels[channelIndex].frameNum = frameNum;
return updateChannel(channelIndex) + 1;
}
int16 Screen::getAnimFrame(uint16 channelIndex) {
if (channelIndex < 1 || channelIndex >= 100 || _channels[channelIndex - 1].type == 0)
return -1;
return _channels[channelIndex - 1].frameNum;
}
uint16 Screen::placeText(uint16 channelIndex, uint16 textObjectIndex, int16 x, int16 y, uint16 fontNum, int16 textColor, int16 outlineColor) {
if (channelIndex < 1 || channelIndex >= 100 || textObjectIndex == 0 || fontNum == 0)
return 0;
channelIndex--;
Object *obj = _vm->_dat->getObject(textObjectIndex);
const char *text = obj->getString();
//int16 x1, y1, x2, y2;
setFont(fontNum);
int textWidth = _font->getTextWidth(text);
//int textHeight = _font->getHeight();
if (outlineColor != -1) {
textWidth += 2;
//textHeight += 2;
x--;
y--;
}
/*x1 = x;
y1 = y;
x2 = x + textWidth;
y2 = y + textHeight;*/
if (textWidth > 0 && outlineColor != -1) {
x++;
y++;
}
int16 state = 1;
if (_ground == 0)
state |= 2;
// The channel for this message isn't deleted until the text on screen disappears, but it gets refreshed
// if the player clicks again, so the previous text needs to be manually reset here to allow the message to be voiced
// whenever the player clicks
if (channelIndex == kClickMessage && (_channels[channelIndex].x != x || _channels[channelIndex].y != y)) {
_channels[channelIndex].previousText.clear();
_queueNextText = true;
}
_channels[channelIndex].state = state;
_channels[channelIndex].type = 2;
_channels[channelIndex].index = textObjectIndex;
_channels[channelIndex].x = x;
_channels[channelIndex].y = y;
_channels[channelIndex].textColor = textColor;
_channels[channelIndex].fontNum = fontNum;
_channels[channelIndex].outlineColor = outlineColor;
#ifdef USE_TTS
voiceChannelText(text, channelIndex);
#endif
if (_channelsUsedCount <= channelIndex)
_channelsUsedCount = channelIndex + 1;
return channelIndex + 1;
}
#ifdef USE_TTS
void Screen::voiceChannelText(const char *text, uint16 channelIndex) {
if ((channelIndex != kTapeRecorderTime && strcmp(_channels[channelIndex].previousText.c_str(), text)) ||
(channelIndex == kTapeRecorderTime && _voiceTimeText)) {
size_t len = strlen(text);
_channels[channelIndex].previousText = text;
if (len == 0) {
return;
}
if (channelIndex == kHoverOver && _queueNextText) {
_vm->sayText(text, Common::TextToSpeechManager::QUEUE);
_queueNextText = false;
} else {
bool voiceText = true;
Object *object = nullptr;
const char *message = nullptr;
switch (channelIndex) {
case kTapeRecorderName:
// Voice name, track, and max track all at once, so that they're all properly voiced
// when the player switches between entries on the tape recorder (the track and max track numbers
// aren't necessarily unique, and may not be voiced otherwise)
_vm->sayText(Common::String::format("%s: %s", _vm->_tapeRecorderText[kName].c_str(), text));
// Track
object = _vm->_dat->getObject(_channels[kTapeRecorderTrack].index);
if (object) {
message = object->getString();
}
_channels[kTapeRecorderTrack].previousText = message;
_vm->sayText(Common::String::format("%s: %s", _vm->_tapeRecorderText[kTrack].c_str(),
_channels[kTapeRecorderTrack].previousText.c_str()),
Common::TextToSpeechManager::QUEUE);
// Max track
object = _vm->_dat->getObject(_channels[kTapeRecorderMaxTrack].index);
if (object) {
message = object->getString();
}
_vm->sayText(Common::String::format("%s: %s", _vm->_tapeRecorderText[kMaxTrack].c_str(), message),
Common::TextToSpeechManager::QUEUE);
voiceText = false;
break;
case kTapeRecorderTrack:
if (!_channels[kTapeRecorderName].previousText.empty()) {
// Voice here in case the track is changed while the tape recorder is open
_vm->sayText(Common::String::format("%s: %s", _vm->_tapeRecorderText[kTrack].c_str(), text),
Common::TextToSpeechManager::QUEUE);
}
// fall through
case kTapeRecorderMaxTrack:
// Max track shouldn't change unless the player changes entries, in which case it'll be
// voiced under the kTapeRecorderName condition, so no need to voice it here
voiceText = false;
break;
case kTapeRecorderTime:
_voiceTimeText = false;
_vm->sayText(Common::String::format("%s: %s", _vm->_tapeRecorderText[kTime].c_str(), text), Common::TextToSpeechManager::QUEUE);
voiceText = false;
}
if (voiceText) {
if (_vm->_saveLoadScreenOpen) {
Common::String ttsText(text);
ttsText.replace('_', ' ');
_vm->sayText(ttsText, Common::TextToSpeechManager::QUEUE);
} else {
_vm->sayText(text);
}
}
}
}
}
#endif
void Screen::show() {
if (_screenLock)
return;
drawSpriteChannels(_backgroundScreenDrawCtx, 3, 0);
memcpy(_workScreen->getPixels(), _backgroundScreen->getPixels(), 64000);
drawSpriteChannels(_workScreenDrawCtx, 1, 2);
_fx->run(_visualEffectNum, _workScreen, _palette, _newPalette, _paletteColorCount);
_visualEffectNum = 0;
if (!_paletteInitialized) {
memcpy(_newPalette, _palette, _paletteColorCount * 3);
_oldPaletteColorCount = _paletteColorCount;
_paletteInitialized = true;
}
updateScreenAndWait(10);
}
void Screen::flash(int flashCount) {
_fx->flash(flashCount, _palette, _paletteColorCount);
}
void Screen::setFont(int16 fontNum) {
if (fontNum == _currentFontNum)
return;
if (_font)
_vm->_res->freeResource(_font);
_font = _vm->_res->getFont(fontNum);
_currentFontNum = fontNum;
}
void Screen::printChar(uint c, int16 x, int16 y, byte color) {
if (!_font)
return;
uint width = 8, height = _font->getHeight();
byte *charData = _font->getChar(c);
if (!charData)
return;
byte p;
byte *dest = (byte *)_fontDrawCtx.destSurface->getBasePtr(x, y);
for (uint yc = 0; yc < height; yc++) {
p = charData[yc];
for (uint xc = 0; xc < width; xc++) {
if (p & 0x80)
dest[xc] = color;
p <<= 1;
}
dest += _fontDrawCtx.destSurface->pitch;
}
}
void Screen::printText(const char *text) {
const int tabWidth = 5;
if (!_font)
return;
int textLen = strlen(text);
int textHeight = _font->getHeight();
int linePos = 1;
int16 x = _textX;
int16 y = _textY;
for (int textPos = 0; textPos < textLen; textPos++) {
uint c = ((const byte*)text)[textPos];
int charWidth = _font->getCharWidth(c);
if (c == 9) {
linePos = ((linePos / tabWidth) + 1) * tabWidth;
x = _textRect.left + linePos * _font->getCharWidth(32);
} else if (c == 10) {
linePos = 1;
x = _textRect.left;
y += textHeight;
} else if (c == 13) {
linePos = 1;
x = _textRect.left;
} else if (c == 32) {
int wrapPos = textPos + 1;
int wrapX = x + charWidth;
while (wrapPos < textLen && text[wrapPos] != 0 && text[wrapPos] != 32 && text[wrapPos] >= 28) {
wrapX += _font->getCharWidth(text[wrapPos]);
wrapPos++;
}
if (wrapX >= _textRect.right) {
linePos = 1;
x = _textRect.left;
y += textHeight;
charWidth = 0;
// TODO: text[textPos] = '\x01';
}
}
if (x + charWidth > _textRect.right) {
linePos = 1;
x = _textRect.left;
y += textHeight;
}
if (y + textHeight > _textRect.bottom) {
// TODO
}
if (c >= 28 && c <= 255) {
if (_dropShadowColor != -1) {
printChar(c, x + 1, y + 1, _dropShadowColor);
}
if (_outlineColor != -1) {
printChar(c, x, y - 1, _outlineColor);
printChar(c, x, y + 1, _outlineColor);
printChar(c, x - 1, y, _outlineColor);
printChar(c, x + 1, y, _outlineColor);
printChar(c, x - 1, y - 1, _outlineColor);
printChar(c, x - 1, y + 1, _outlineColor);
printChar(c, x + 1, y - 1, _outlineColor);
printChar(c, x + 1, y + 1, _outlineColor);
}
printChar(c, x, y, _textColor);
x += charWidth;
linePos++;
}
}
_textX = x;
_textY = y;
}
void Screen::printTextEx(const char *text, int16 x, int16 y, int16 fontNum, int16 textColor, int16 outlineColor, const ClipInfo &clipInfo) {
if (*text == 0 || x < 0 || y < 0)
return;
int16 oldFontNum = _currentFontNum;
Common::Rect oldTextRect;
ClipInfo oldFontDrawCtx = _fontDrawCtx;
_fontDrawCtx = clipInfo;
getTextRect(oldTextRect);
setFont(fontNum);
setTextColor(textColor);
setOutlineColor(outlineColor);
setTextXY(x, y);
printText(text);
setTextRect(oldTextRect);
setFont(oldFontNum);
_fontDrawCtx = oldFontDrawCtx;
if (_vm->getGameID() != GID_RTZ && _vm->getGameID() != GID_LGOP2) {
_vm->sayText(text);
}
}
void Screen::printObjectText(int16 objectIndex, int16 x, int16 y, int16 fontNum, int16 textColor, int16 outlineColor, const ClipInfo &clipInfo) {
if (objectIndex == 0)
return;
Object *obj = _vm->_dat->getObject(objectIndex);
const char *text = obj->getString();
printTextEx(text, x, y, fontNum, textColor, outlineColor, clipInfo);
}
int16 Screen::getTextWidth(int16 fontNum, const char *text) {
setFont(fontNum);
return _font->getTextWidth(text);
}
Graphics::Surface *Screen::lockScreen() {
return _vm->_system->lockScreen();
}
void Screen::unlockScreen() {
_vm->_system->unlockScreen();
}
void Screen::showWorkScreen() {
_vm->_system->copyRectToScreen(_workScreen->getPixels(), _workScreen->pitch, 0, 0, _workScreen->w, _workScreen->h);
}
void Screen::copyRectToScreen(const void *buf, int pitch, int x, int y, int w, int h) {
_vm->_system->copyRectToScreen(buf, pitch, x, y, w, h);
}
void Screen::updateScreenAndWait(int delay) {
_vm->_system->updateScreen();
uint32 startTime = _vm->_system->getMillis();
while (_vm->_system->getMillis() < startTime + delay) {
_vm->handleEvents();
_vm->_system->delayMillis(5);
}
}
int16 Screen::addToSpriteList(int16 index, int16 xofs, int16 yofs) {
SpriteListItem item;
item.index = index;
item.xofs = xofs;
item.yofs = yofs;
_spriteList.push_back(item);
return _spriteList.size();
}
SpriteListItem Screen::getFromSpriteList(int16 index) {
if (((uint) index) > _spriteList.size()) {
SpriteListItem emptyItem;
emptyItem.index = 0;
emptyItem.xofs = 0;
emptyItem.yofs = 0;
return emptyItem;
} else {
return _spriteList[index - 1];
}
}
void Screen::clearSpriteList() {
_spriteList.clear();
}
void Screen::setMouseCursor(const Graphics::Cursor *cursor) {
CursorMan.replaceCursor(cursor, true);
}
void Screen::setDefaultMouseCursor() {
CursorMan.replaceCursor(defaultMouseCursor, 16, 16, 9, 2, 0);
}
} // End of namespace Made

238
engines/made/screen.h Normal file
View File

@@ -0,0 +1,238 @@
/* 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/>.
*
*/
#ifndef MADE_SCREEN_H
#define MADE_SCREEN_H
#include "made/resource.h"
#include "common/rect.h"
#include "graphics/cursor.h"
namespace Made {
struct SpriteChannel {
int16 type;
int16 state;
uint16 index;
int16 x, y;
uint16 fontNum;
int16 textColor, outlineColor;
int16 frameNum;
int16 mask;
Common::String previousText;
};
struct ClipInfo {
Common::Rect clipRect;
Graphics::Surface *destSurface;
};
struct SpriteListItem {
int16 index, xofs, yofs;
};
class MadeEngine;
class ScreenEffects;
static const byte defaultMouseCursor[256] = {
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 1, 15, 15, 1, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 1, 15, 15, 1, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 1, 15, 15, 1, 0, 0, 0, 0, 0,
0, 1, 1, 1, 1, 1, 1, 1, 15, 15, 1, 0, 0, 0, 0, 0,
1, 1, 15, 1, 15, 1, 15, 1, 15, 15, 1, 0, 0, 0, 0, 0,
1, 15, 15, 1, 15, 1, 15, 1, 15, 15, 1, 0, 0, 0, 0, 0,
1, 15, 15, 15, 15, 15, 15, 15, 15, 15, 1, 0, 1, 1, 1, 0,
1, 15, 15, 15, 15, 15, 15, 15, 15, 15, 1, 1, 15, 15, 15, 1,
1, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 1, 1, 1,
1, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 1, 1, 0, 0,
1, 1, 15, 15, 15, 15, 15, 15, 15, 15, 15, 1, 1, 0, 0, 0,
0, 1, 1, 15, 15, 15, 15, 15, 15, 15, 1, 1, 0, 0, 0, 0,
0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
};
class Screen {
public:
Screen(MadeEngine *vm);
~Screen();
void clearScreen();
void drawSurface(Graphics::Surface *sourceSurface, int x, int y, int16 flipX, int16 flipY, int16 mask, const ClipInfo &clipInfo);
void setRGBPalette(byte *palRGB, int start = 0, int count = 256);
bool isPaletteLocked() { return _paletteLock; }
void setPaletteLock(bool lock) { _paletteLock = lock; }
bool isScreenLocked() { return _screenLock; }
void setScreenLock(bool lock) { _screenLock = lock; }
void setVisualEffectNum(int visualEffectNum) { _visualEffectNum = visualEffectNum; }
void setClipArea(uint16 x1, uint16 y1, uint16 x2, uint16 y2) {
_clipArea.clipRect = Common::Rect(x1, y1, x2, y2);
}
void setExcludeArea(uint16 x1, uint16 y1, uint16 x2, uint16 y2);
void setClip(int16 clip) { _clip = clip; }
void setExclude(int16 exclude) { _exclude = exclude; }
void setGround(int16 ground) { _ground = ground; }
void setMask(int16 mask) { _mask = mask; }
void setTextColor(int16 color) { _textColor = color; }
void setTextRect(const Common::Rect &textRect) {
_textRect = textRect;
_textX = _textRect.left;
_textY = _textRect.top;
}
void getTextRect(Common::Rect &textRect) {
textRect = _textRect;
}
void setOutlineColor(int16 color) {
_outlineColor = color;
_dropShadowColor = -1;
}
void setDropShadowColor(int16 color) {
_outlineColor = -1;
_dropShadowColor = color;
}
void setTextXY(int16 x, int16 y) {
_textX = x;
_textY = y;
}
void homeText() {
_textX = _textRect.left;
_textY = _textRect.top;
}
void setQueueNextText(bool value) { _queueNextText = value; }
void setVoiceTimeText(bool value) { _voiceTimeText = value; }
uint16 updateChannel(uint16 channelIndex);
void deleteChannel(uint16 channelIndex);
int16 getChannelType(uint16 channelIndex);
int16 getChannelState(uint16 channelIndex);
void setChannelState(uint16 channelIndex, int16 state);
uint16 setChannelLocation(uint16 channelIndex, int16 x, int16 y);
uint16 setChannelContent(uint16 channelIndex, uint16 index);
void setChannelUseMask(uint16 channelIndex);
void drawSpriteChannels(const ClipInfo &clipInfo, int16 includeStateMask, int16 excludeStateMask);
void updateSprites();
void clearChannels();
uint16 drawFlex(uint16 flexIndex, int16 x, int16 y, int16 flipX, int16 flipY, int16 mask, const ClipInfo &clipInfo);
void drawAnimFrame(uint16 animIndex, int16 x, int16 y, int16 frameNum, int16 flipX, int16 flipY, const ClipInfo &clipInfo);
uint16 drawPic(uint16 index, int16 x, int16 y, int16 flipX, int16 flipY);
uint16 drawMask(uint16 index, int16 x, int16 y);
uint16 drawAnimPic(uint16 animIndex, int16 x, int16 y, int16 frameNum, int16 flipX, int16 flipY);
void addSprite(uint16 spriteIndex);
uint16 drawSprite(uint16 flexIndex, int16 x, int16 y);
uint16 placeSprite(uint16 channelIndex, uint16 flexIndex, int16 x, int16 y);
uint16 placeAnim(uint16 channelIndex, uint16 animIndex, int16 x, int16 y, int16 frameNum);
int16 setAnimFrame(uint16 channelIndex, int16 frameNum);
int16 getAnimFrame(uint16 channelIndex);
uint16 placeText(uint16 channelIndex, uint16 textObjectIndex, int16 x, int16 y, uint16 fontNum, int16 textColor, int16 outlineColor);
#ifdef USE_TTS
void voiceChannelText(const char *text, uint16 channelIndex);
#endif
void show();
void flash(int count);
void setFont(int16 fontNum);
void printChar(uint c, int16 x, int16 y, byte color);
void printText(const char *text);
void printTextEx(const char *text, int16 x, int16 y, int16 fontNum, int16 textColor, int16 outlineColor, const ClipInfo &clipInfo);
void printObjectText(int16 objectIndex, int16 x, int16 y, int16 fontNum, int16 textColor, int16 outlineColor, const ClipInfo &clipInfo);
int16 getTextWidth(int16 fontNum, const char *text);
// Interface functions for the screen effects class
Graphics::Surface *lockScreen();
void unlockScreen();
void showWorkScreen();
void copyRectToScreen(const void *buf, int pitch, int x, int y, int w, int h);
void updateScreenAndWait(int delay);
int16 addToSpriteList(int16 index, int16 xofs, int16 yofs);
SpriteListItem getFromSpriteList(int16 index);
void clearSpriteList();
void setMouseCursor(const Graphics::Cursor *cursor);
void setDefaultMouseCursor();
protected:
MadeEngine *_vm;
ScreenEffects *_fx;
bool _screenLock;
bool _paletteLock;
byte *_palette, *_newPalette;
int _paletteColorCount, _oldPaletteColorCount;
bool _paletteInitialized, _needPalette;
int16 _textColor;
int16 _outlineColor;
int16 _dropShadowColor;
int16 _textX, _textY;
Common::Rect _textRect;
int16 _currentFontNum;
FontResource *_font;
ClipInfo _fontDrawCtx;
int16 _clip, _exclude, _ground, _mask;
int _visualEffectNum;
Graphics::Surface *_backgroundScreen, *_workScreen, *_screenMask;
ClipInfo _clipArea, _backgroundScreenDrawCtx, _workScreenDrawCtx, _maskDrawCtx;
ClipInfo _excludeClipArea[4];
bool _excludeClipAreaEnabled[4];
uint16 _channelsUsedCount;
SpriteChannel _channels[100];
bool _queueNextText;
bool _voiceTimeText;
Common::Array<SpriteListItem> _spriteList;
};
} // End of namespace Made
#endif /* MADE_H */

543
engines/made/screenfx.cpp Normal file
View File

@@ -0,0 +1,543 @@
/* 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/>.
*
*/
#include "made/screenfx.h"
#include "made/screen.h"
#include "graphics/surface.h"
namespace Made {
const byte ScreenEffects::vfxOffsTable[64] = {
5, 2, 6, 1, 4, 7, 3, 0,
7, 4, 0, 3, 6, 1, 5, 2,
2, 5, 1, 6, 3, 0, 4, 7,
0, 3, 7, 4, 1, 6, 2, 5,
4, 0, 2, 5, 7, 3, 1, 6,
1, 6, 4, 0, 2, 5, 7, 3,
6, 1, 3, 7, 5, 2, 0, 4,
3, 7, 5, 2, 0, 4, 6, 1
};
const byte ScreenEffects::vfxOffsIndexTable[8] = {
6, 7, 2, 3, 4, 5, 0, 1
};
ScreenEffects::ScreenEffects(Screen *screen) : _screen(screen) {
vfxOffsTablePtr = &vfxOffsTable[6 * 8];
vfxX1 = 0;
vfxY1 = 0;
vfxWidth = 0;
vfxHeight = 0;
_fxPalette = new byte[768];
_blendedPaletteStatus._active = false;
_blendedPaletteStatus._palette = _blendedPaletteStatus._newPalette = nullptr;
_blendedPaletteStatus._colorCount = 0;
_blendedPaletteStatus._value = _blendedPaletteStatus._maxValue = 0;
_blendedPaletteStatus._incr = 0;
}
ScreenEffects::~ScreenEffects() {
delete[] _fxPalette;
}
void ScreenEffects::run(int16 effectNum, Graphics::Surface *surface, byte *palette, byte *newPalette, int colorCount) {
// TODO: Put effect functions into an array
switch (effectNum) {
case 0: // No effect
vfx00(surface, palette, newPalette, colorCount);
break;
case 1:
vfx01(surface, palette, newPalette, colorCount);
break;
case 2:
vfx02(surface, palette, newPalette, colorCount);
break;
case 3:
vfx03(surface, palette, newPalette, colorCount);
break;
case 4:
vfx04(surface, palette, newPalette, colorCount);
break;
case 5:
vfx05(surface, palette, newPalette, colorCount);
break;
case 6: // "Curtain open" effect
vfx06(surface, palette, newPalette, colorCount);
break;
case 7: // "Curtain close" effect
vfx07(surface, palette, newPalette, colorCount);
break;
case 8:
vfx08(surface, palette, newPalette, colorCount);
break;
case 9: // "Checkerboard" effect
vfx09(surface, palette, newPalette, colorCount);
break;
case 10: // "Screen wipe in", left to right
vfx10(surface, palette, newPalette, colorCount);
break;
case 11: // "Screen wipe in", right to left
vfx11(surface, palette, newPalette, colorCount);
break;
case 12: // "Screen wipe in", top to bottom
vfx12(surface, palette, newPalette, colorCount);
break;
case 13: // "Screen wipe in", bottom to top
vfx13(surface, palette, newPalette, colorCount);
break;
case 14: // "Screen open" effect
vfx14(surface, palette, newPalette, colorCount);
break;
case 15:
vfx15(surface, palette, newPalette, colorCount);
break;
case 16:
vfx16(surface, palette, newPalette, colorCount);
break;
case 17: // Palette fadeout/fadein
vfx17(surface, palette, newPalette, colorCount);
break;
case 18:
vfx18(surface, palette, newPalette, colorCount);
break;
case 19:
vfx19(surface, palette, newPalette, colorCount);
break;
case 20:
vfx20(surface, palette, newPalette, colorCount);
break;
default:
vfx00(surface, palette, newPalette, colorCount);
warning("Unimplemented visual effect %d", effectNum);
}
}
void ScreenEffects::flash(int flashCount, byte *palette, int colorCount) {
int palSize = colorCount * 3;
if (flashCount < 1)
flashCount = 1;
for (int i = 0; i < palSize; i++)
_fxPalette[i] = CLIP<byte>(255 - palette[i], 0, 255);
while (flashCount--) {
_screen->setRGBPalette(_fxPalette, 0, colorCount);
_screen->updateScreenAndWait(20);
_screen->setRGBPalette(palette, 0, colorCount);
_screen->updateScreenAndWait(20);
}
}
void ScreenEffects::setPalette(byte *palette) {
if (!_screen->isPaletteLocked()) {
_screen->setRGBPalette(palette, 0, 256);
}
}
void ScreenEffects::setBlendedPalette(byte *palette, byte *newPalette, int colorCount, int16 value, int16 maxValue) {
if (!_screen->isPaletteLocked()) {
int32 mulValue = (value * 64) / maxValue;
for (int i = 0; i < colorCount * 3; i++)
_fxPalette[i] = CLIP<int32>(newPalette[i] - (newPalette[i] - palette[i]) * mulValue / 64, 0, 255);
_screen->setRGBPalette(_fxPalette, 0, 256);
}
}
void ScreenEffects::startBlendedPalette(byte *palette, byte *newPalette, int colorCount, int16 maxValue) {
_blendedPaletteStatus._palette = palette;
_blendedPaletteStatus._newPalette = newPalette;
_blendedPaletteStatus._colorCount = colorCount;
_blendedPaletteStatus._maxValue = maxValue;
_blendedPaletteStatus._incr = maxValue / 10; // ~10 palette updates
_blendedPaletteStatus._value = 0;
// Don't do anything if the two palettes are identical
_blendedPaletteStatus._active = memcmp(palette, newPalette, colorCount * 3) != 0;
}
void ScreenEffects::stepBlendedPalette() {
if (_blendedPaletteStatus._active && _blendedPaletteStatus._value <= _blendedPaletteStatus._maxValue) {
setBlendedPalette(_blendedPaletteStatus._palette, _blendedPaletteStatus._newPalette,
_blendedPaletteStatus._colorCount, _blendedPaletteStatus._value, _blendedPaletteStatus._maxValue);
if (_blendedPaletteStatus._value == _blendedPaletteStatus._maxValue)
_blendedPaletteStatus._value++;
else
_blendedPaletteStatus._value = MIN<int16>(_blendedPaletteStatus._value + _blendedPaletteStatus._incr, _blendedPaletteStatus._maxValue);
}
}
void ScreenEffects::copyFxRect(Graphics::Surface *surface, int16 x1, int16 y1, int16 x2, int16 y2) {
// TODO: Clean up
byte *src, *dst;
x1 = CLIP<int16>(x1, 0, 320);
y1 = CLIP<int16>(y1, 0, 200);
x2 = CLIP<int16>(x2, 0, 320);
y2 = CLIP<int16>(y2, 0, 200);
x2 -= x1;
y2 -= y1;
vfxX1 = x1 & 0x0E;
x1 += 16;
x1 = x1 & 0xFFF0;
x2 += vfxX1;
x2 -= 15;
if (x2 < 0)
x2 = 0;
vfxWidth = x2 & 0x0E;
x2 = x2 & 0xFFF0;
vfxY1 = y1 & 7;
byte *source = (byte *)surface->getBasePtr(x1, y1);
Graphics::Surface *vgaScreen = _screen->lockScreen();
byte *dest = (byte *)vgaScreen->getBasePtr(x1, y1);
int16 addX = x2 / 16;
while (y2-- > 0) {
int16 addVal = vfxOffsTablePtr[vfxY1] * 2;
int16 w = 0;
vfxY1 = (vfxY1 + 1) & 7;
src = source + addVal;
dst = dest + addVal;
if (addVal < vfxX1) {
if (addVal < vfxWidth)
w = 1;
else
w = 0;
} else {
src -= 16;
dst -= 16;
if (addVal < vfxWidth)
w = 2;
else
w = 1;
}
w += addX;
while (w-- > 0) {
*dst++ = *src++;
*dst++ = *src++;
src += 14;
dst += 14;
}
source += 320;
dest += 320;
}
vfxHeight = (vfxHeight + 1) & 7;
vfxOffsTablePtr = &vfxOffsTable[vfxOffsIndexTable[vfxHeight] * 8];
_screen->unlockScreen();
}
// No effect
void ScreenEffects::vfx00(Graphics::Surface *surface, byte *palette, byte *newPalette, int colorCount) {
setPalette(palette);
_screen->showWorkScreen();
// Workaround for The Manhole, else animations will be shown too fast
_screen->updateScreenAndWait(100);
}
void ScreenEffects::vfx01(Graphics::Surface *surface, byte *palette, byte *newPalette, int colorCount) {
startBlendedPalette(palette, newPalette, colorCount, 312);
for (int x = 0; x < 320; x += 8) {
_screen->copyRectToScreen(surface->getBasePtr(x, 0), surface->pitch, x, 0, 8, 200);
stepBlendedPalette();
_screen->updateScreenAndWait(25);
}
setPalette(palette);
}
void ScreenEffects::vfx02(Graphics::Surface *surface, byte *palette, byte *newPalette, int colorCount) {
startBlendedPalette(palette, newPalette, colorCount, 312);
for (int x = 312; x >= 0; x -= 8) {
_screen->copyRectToScreen(surface->getBasePtr(x, 0), surface->pitch, x, 0, 8, 200);
stepBlendedPalette();
_screen->updateScreenAndWait(25);
}
setPalette(palette);
}
void ScreenEffects::vfx03(Graphics::Surface *surface, byte *palette, byte *newPalette, int colorCount) {
startBlendedPalette(palette, newPalette, colorCount, 190);
for (int y = 0; y < 200; y += 10) {
_screen->copyRectToScreen(surface->getBasePtr(0, y), surface->pitch, 0, y, 320, 10);
stepBlendedPalette();
_screen->updateScreenAndWait(25);
}
setPalette(palette);
}
void ScreenEffects::vfx04(Graphics::Surface *surface, byte *palette, byte *newPalette, int colorCount) {
startBlendedPalette(palette, newPalette, colorCount, 190);
for (int y = 190; y >= 0; y -= 10) {
_screen->copyRectToScreen(surface->getBasePtr(0, y), surface->pitch, 0, y, 320, 10);
stepBlendedPalette();
_screen->updateScreenAndWait(25);
}
setPalette(palette);
}
void ScreenEffects::vfx05(Graphics::Surface *surface, byte *palette, byte *newPalette, int colorCount) {
startBlendedPalette(palette, newPalette, colorCount, 90);
for (int y = 0; y < 100; y += 10) {
_screen->copyRectToScreen(surface->getBasePtr(0, y + 100), surface->pitch, 0, y + 100, 320, 10);
_screen->copyRectToScreen(surface->getBasePtr(0, 90 - y), surface->pitch, 0, 90 - y, 320, 10);
stepBlendedPalette();
_screen->updateScreenAndWait(25);
}
setPalette(palette);
}
// "Curtain open" effect
void ScreenEffects::vfx06(Graphics::Surface *surface, byte *palette, byte *newPalette, int colorCount) {
startBlendedPalette(palette, newPalette, colorCount, 152);
for (int x = 0; x < 160; x += 8) {
_screen->copyRectToScreen(surface->getBasePtr(x + 160, 0), surface->pitch, x + 160, 0, 8, 200);
_screen->copyRectToScreen(surface->getBasePtr(152 - x, 0), surface->pitch, 152 - x, 0, 8, 200);
stepBlendedPalette();
_screen->updateScreenAndWait(25);
}
setPalette(palette);
}
// "Curtain close" effect
void ScreenEffects::vfx07(Graphics::Surface *surface, byte *palette, byte *newPalette, int colorCount) {
startBlendedPalette(palette, newPalette, colorCount, 152);
for (int x = 152; x >= 0; x -= 8) {
_screen->copyRectToScreen(surface->getBasePtr(x + 160, 0), surface->pitch, x + 160, 0, 8, 200);
_screen->copyRectToScreen(surface->getBasePtr(152 - x, 0), surface->pitch, 152 - x, 0, 8, 200);
stepBlendedPalette();
_screen->updateScreenAndWait(25);
}
setPalette(palette);
}
// "Screen slide in" right to left
void ScreenEffects::vfx08(Graphics::Surface *surface, byte *palette, byte *newPalette, int colorCount) {
for (int x = 8; x <= 320; x += 8) {
_screen->copyRectToScreen(surface->getPixels(), surface->pitch, 320 - x, 0, x, 200);
_screen->updateScreenAndWait(25);
}
setPalette(palette);
}
// "Checkerboard" effect
void ScreenEffects::vfx09(Graphics::Surface *surface, byte *palette, byte *newPalette, int colorCount) {
for (int i = 0; i < 8; i++) {
copyFxRect(surface, 0, 0, 320, 200);
// We set the final palette here, once
setBlendedPalette(palette, newPalette, colorCount, i * 4 + 3, 32);
// The original behavior follows - the end result is the same, though
//for (int j = 0; j < 4; j++)
// setBlendedPalette(palette, newPalette, colorCount, i * 4 + j, 32);
_screen->updateScreenAndWait(25);
}
setPalette(palette);
}
// "Screen wipe in", left to right
void ScreenEffects::vfx10(Graphics::Surface *surface, byte *palette, byte *newPalette, int colorCount) {
startBlendedPalette(palette, newPalette, colorCount, 368);
for (int x = -56; x < 312; x += 8) {
copyFxRect(surface, x, 0, x + 64, 200);
stepBlendedPalette();
_screen->updateScreenAndWait(25);
}
setPalette(palette);
}
// "Screen wipe in", right to left
void ScreenEffects::vfx11(Graphics::Surface *surface, byte *palette, byte *newPalette, int colorCount) {
startBlendedPalette(palette, newPalette, colorCount, 368);
for (int x = 312; x > -56; x -= 8) {
copyFxRect(surface, x, 0, x + 64, 200);
stepBlendedPalette();
_screen->updateScreenAndWait(25);
}
setPalette(palette);
}
// "Screen wipe in", top to bottom
void ScreenEffects::vfx12(Graphics::Surface *surface, byte *palette, byte *newPalette, int colorCount) {
startBlendedPalette(palette, newPalette, colorCount, 260);
for (int y = -70; y < 312; y += 10) {
copyFxRect(surface, 0, y, 320, y + 80);
stepBlendedPalette();
_screen->updateScreenAndWait(25);
}
setPalette(palette);
}
// "Screen wipe in", bottom to top
void ScreenEffects::vfx13(Graphics::Surface *surface, byte *palette, byte *newPalette, int colorCount) {
startBlendedPalette(palette, newPalette, colorCount, 260);
for (int y = 312; y > -70; y -= 10) {
copyFxRect(surface, 0, y, 320, y + 80);
stepBlendedPalette();
_screen->updateScreenAndWait(25);
}
setPalette(palette);
}
// "Screen open" effect
void ScreenEffects::vfx14(Graphics::Surface *surface, byte *palette, byte *newPalette, int colorCount) {
int16 x = 8, y = 5;
startBlendedPalette(palette, newPalette, colorCount, 27);
for (int i = 0; i < 27; i++) {
copyFxRect(surface, 160 - x, 100 - y, 160 + x, 100 + y);
x += 8;
y += 5;
stepBlendedPalette();
_screen->updateScreenAndWait(25);
}
setPalette(palette);
}
void ScreenEffects::vfx15(Graphics::Surface *surface, byte *palette, byte *newPalette, int colorCount) {
int16 x = 8;
startBlendedPalette(palette, newPalette, colorCount, 27);
for (int i = 0; i < 27; i++) {
copyFxRect(surface, 160 - x, 0, 160 + x, 200);
x += 8;
stepBlendedPalette();
_screen->updateScreenAndWait(25);
}
setPalette(palette);
}
void ScreenEffects::vfx16(Graphics::Surface *surface, byte *palette, byte *newPalette, int colorCount) {
int16 y = 8;
startBlendedPalette(palette, newPalette, colorCount, 27);
for (int i = 0; i < 27; i++) {
copyFxRect(surface, 0, 100 - y, 320, 100 + y);
y += 5;
stepBlendedPalette();
_screen->updateScreenAndWait(25);
}
setPalette(palette);
}
// Palette fadeout/fadein
void ScreenEffects::vfx17(Graphics::Surface *surface, byte *palette, byte *newPalette, int colorCount) {
byte tempPalette[768];
bool savedPaletteLock = _screen->isPaletteLocked();
_screen->setPaletteLock(false);
memcpy(tempPalette, palette, 768);
// Fade out to black
memset(palette, 0, 768);
startBlendedPalette(palette, newPalette, colorCount, 50);
for (int i = 0; i < 50; i++) {
stepBlendedPalette();
_screen->updateScreenAndWait(25);
}
_screen->setRGBPalette(palette, 0, colorCount);
memcpy(palette, tempPalette, 768);
_screen->showWorkScreen();
// Fade from black to palette
memset(newPalette, 0, 768);
startBlendedPalette(palette, newPalette, colorCount, 50);
for (int i = 0; i < 50; i++) {
stepBlendedPalette();
_screen->updateScreenAndWait(25);
}
_screen->setRGBPalette(palette, 0, colorCount);
_screen->setPaletteLock(savedPaletteLock);
}
// "Screen slide in" left to right
void ScreenEffects::vfx18(Graphics::Surface *surface, byte *palette, byte *newPalette, int colorCount) {
for (int x = 8; x <= 320; x += 8) {
_screen->copyRectToScreen(surface->getBasePtr(320 - x, 0), surface->pitch, 0, 0, x, 200);
_screen->updateScreenAndWait(25);
}
setPalette(palette);
}
// "Screen slide in" top to bottom
void ScreenEffects::vfx19(Graphics::Surface *surface, byte *palette, byte *newPalette, int colorCount) {
for (int y = 4; y <= 200; y += 4) {
_screen->copyRectToScreen(surface->getBasePtr(0, 200 - y), surface->pitch, 0, 0, 320, y);
_screen->updateScreenAndWait(25);
}
setPalette(palette);
}
// "Screen slide in" bottom to top
void ScreenEffects::vfx20(Graphics::Surface *surface, byte *palette, byte *newPalette, int colorCount) {
for (int y = 4; y <= 200; y += 4) {
_screen->copyRectToScreen(surface->getPixels(), surface->pitch, 0, 200 - y, 320, y);
_screen->updateScreenAndWait(25);
}
setPalette(palette);
}
} // End of namespace Made

88
engines/made/screenfx.h Normal file
View File

@@ -0,0 +1,88 @@
/* 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/>.
*
*/
#ifndef MADE_SCREENFX_H
#define MADE_SCREENFX_H
#include "common/scummsys.h"
namespace Graphics {
struct Surface;
}
namespace Made {
class Screen;
struct BlendedPaletteStatus {
bool _active;
byte *_palette, *_newPalette;
int _colorCount;
int16 _value, _maxValue, _incr;
};
class ScreenEffects {
public:
ScreenEffects(Screen *screen);
~ScreenEffects();
void run(int16 effectNum, Graphics::Surface *surface, byte *palette, byte *newPalette, int colorCount);
void flash(int count, byte *palette, int colorCount);
private:
Screen *_screen;
byte *_fxPalette;
static const byte vfxOffsTable[64];
static const byte vfxOffsIndexTable[8];
const byte *vfxOffsTablePtr;
int16 vfxX1, vfxY1, vfxWidth, vfxHeight;
BlendedPaletteStatus _blendedPaletteStatus;
void setPalette(byte *palette);
void setBlendedPalette(byte *palette, byte *newPalette, int colorCount, int16 value, int16 maxValue);
void startBlendedPalette(byte *palette, byte *newPalette, int colorCount, int16 maxValue);
void stepBlendedPalette();
void copyFxRect(Graphics::Surface *surface, int16 x1, int16 y1, int16 x2, int16 y2);
void vfx00(Graphics::Surface *surface, byte *palette, byte *newPalette, int colorCount);
void vfx01(Graphics::Surface *surface, byte *palette, byte *newPalette, int colorCount);
void vfx02(Graphics::Surface *surface, byte *palette, byte *newPalette, int colorCount);
void vfx03(Graphics::Surface *surface, byte *palette, byte *newPalette, int colorCount);
void vfx04(Graphics::Surface *surface, byte *palette, byte *newPalette, int colorCount);
void vfx05(Graphics::Surface *surface, byte *palette, byte *newPalette, int colorCount);
void vfx06(Graphics::Surface *surface, byte *palette, byte *newPalette, int colorCount);
void vfx07(Graphics::Surface *surface, byte *palette, byte *newPalette, int colorCount);
void vfx08(Graphics::Surface *surface, byte *palette, byte *newPalette, int colorCount);
void vfx09(Graphics::Surface *surface, byte *palette, byte *newPalette, int colorCount);
void vfx10(Graphics::Surface *surface, byte *palette, byte *newPalette, int colorCount);
void vfx11(Graphics::Surface *surface, byte *palette, byte *newPalette, int colorCount);
void vfx12(Graphics::Surface *surface, byte *palette, byte *newPalette, int colorCount);
void vfx13(Graphics::Surface *surface, byte *palette, byte *newPalette, int colorCount);
void vfx14(Graphics::Surface *surface, byte *palette, byte *newPalette, int colorCount);
void vfx15(Graphics::Surface *surface, byte *palette, byte *newPalette, int colorCount);
void vfx16(Graphics::Surface *surface, byte *palette, byte *newPalette, int colorCount);
void vfx17(Graphics::Surface *surface, byte *palette, byte *newPalette, int colorCount);
void vfx18(Graphics::Surface *surface, byte *palette, byte *newPalette, int colorCount);
void vfx19(Graphics::Surface *surface, byte *palette, byte *newPalette, int colorCount);
void vfx20(Graphics::Surface *surface, byte *palette, byte *newPalette, int colorCount);
};
} // End of namespace Made
#endif /* MADE_H */

749
engines/made/script.cpp Normal file
View File

@@ -0,0 +1,749 @@
/* 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/>.
*
*/
#include "made/script.h"
#include "made/scriptfuncs.h"
#include "made/made.h"
#include "made/database.h"
#include "made/screen.h"
#include "common/util.h"
namespace Made {
/* ScriptInterpreter */
ScriptInterpreter::ScriptInterpreter(MadeEngine *vm) : _vm(vm) {
#ifdef DUMP_SCRIPTS
#define COMMAND(x, sig) { &ScriptInterpreter::x, #x, sig }
#else
#define COMMAND(x, sig) { &ScriptInterpreter::x, #x}
#endif
static CommandEntry commandProcs[] = {
/* 01 */
COMMAND(cmd_branchTrue, "W"),
COMMAND(cmd_branchFalse, "W"),
COMMAND(cmd_branch, "W"),
COMMAND(cmd_true, ""),
/* 05 */
COMMAND(cmd_false, ""),
COMMAND(cmd_push, ""),
COMMAND(cmd_not, ""),
COMMAND(cmd_add, ""),
/* 09 */
COMMAND(cmd_sub, ""),
COMMAND(cmd_mul, ""),
COMMAND(cmd_div, ""),
COMMAND(cmd_mod, ""),
/* 13 */
COMMAND(cmd_band, ""),
COMMAND(cmd_bor, ""),
COMMAND(cmd_bnot, ""),
COMMAND(cmd_lt, ""),
/* 17 */
COMMAND(cmd_eq, ""),
COMMAND(cmd_gt, ""),
COMMAND(cmd_loadConstant, "w"),
COMMAND(cmd_loadVariable, "w"),
/* 21 */
COMMAND(cmd_getObjectProperty, ""),
COMMAND(cmd_setObjectProperty, ""),
COMMAND(cmd_set, "w"),
COMMAND(cmd_print, ""),
/* 25 */
COMMAND(cmd_terpri, ""),
COMMAND(cmd_printNumber, ""),
COMMAND(cmd_vref, ""),
COMMAND(cmd_vset, ""),
/* 29 */
COMMAND(cmd_vsize, ""),
COMMAND(cmd_exit, ""),
COMMAND(cmd_return, ""),
COMMAND(cmd_call, "b"),
/* 33 */
COMMAND(cmd_svar, ""),
COMMAND(cmd_sset, ""),
COMMAND(cmd_split, ""),
COMMAND(cmd_snlit, ""),
/* 37 */
COMMAND(cmd_yorn, ""),
COMMAND(cmd_save, ""),
COMMAND(cmd_restore, ""),
COMMAND(cmd_arg, "b"),
/* 41 */
COMMAND(cmd_aset, "b"),
COMMAND(cmd_tmp, "b"),
COMMAND(cmd_tset, "b"),
COMMAND(cmd_tspace, "b"),
/* 45 */
COMMAND(cmd_class, ""),
COMMAND(cmd_objectp, ""),
COMMAND(cmd_vectorp, ""),
COMMAND(cmd_restart, ""),
/* 49 */
COMMAND(cmd_rand, ""),
COMMAND(cmd_randomize, ""),
COMMAND(cmd_send, "b"),
COMMAND(cmd_extend, "Eb"),
/* 53 */
COMMAND(cmd_catch, ""),
COMMAND(cmd_cdone, ""),
COMMAND(cmd_throw, ""),
COMMAND(cmd_functionp, ""),
/* 57 */
COMMAND(cmd_le, ""),
COMMAND(cmd_ge, ""),
COMMAND(cmd_varx, ""),
COMMAND(cmd_setx, "")
};
_commands = commandProcs;
_commandsMax = ARRAYSIZE(commandProcs) + 1;
_functions = new ScriptFunctions(_vm);
_functions->setupExternalsTable();
_localStackPos = 0;
_runningScriptObjectIndex = 0;
_codeBase = nullptr;
_codeIp = nullptr;
#undef COMMAND
}
ScriptInterpreter::~ScriptInterpreter() {
delete _functions;
}
void ScriptInterpreter::runScript(int16 scriptObjectIndex) {
uint32 opcodeSleepCounter = 0;
_runningScriptObjectIndex = scriptObjectIndex;
_localStackPos = _stack.getStackPos();
_codeBase = _vm->_dat->getObject(_runningScriptObjectIndex)->getData();
_codeIp = _codeBase;
while (!_vm->shouldQuit()) {
byte opcode = readByte();
if (opcode >= 1 && opcode <= _commandsMax) {
debug(4, "[%04X:%04X] %s", _runningScriptObjectIndex, (uint) (_codeIp - _codeBase), _commands[opcode - 1].desc);
(this->*_commands[opcode - 1].proc)();
} else {
warning("ScriptInterpreter::runScript(%d) Unknown opcode %02X", _runningScriptObjectIndex, opcode);
}
/* We sleep a little after 500 opcodes to reduce the CPU load.
*/
if (++opcodeSleepCounter > 500) {
_vm->_screen->updateScreenAndWait(5);
opcodeSleepCounter = 0;
}
}
}
byte ScriptInterpreter::readByte() {
return *_codeIp++;
}
int16 ScriptInterpreter::readInt16() {
int16 temp = (int16)READ_LE_UINT16(_codeIp);
_codeIp += 2;
debug(4, "readInt16() value = %04X", temp);
return temp;
}
void ScriptInterpreter::cmd_branchTrue() {
int16 ofs = readInt16();
if (_stack.top() != 0)
_codeIp = _codeBase + ofs;
}
void ScriptInterpreter::cmd_branchFalse() {
int16 ofs = readInt16();
if (_stack.top() == 0)
_codeIp = _codeBase + ofs;
}
void ScriptInterpreter::cmd_branch() {
int16 ofs = readInt16();
_codeIp = _codeBase + ofs;
}
void ScriptInterpreter::cmd_true() {
_stack.setTop(-1);
}
void ScriptInterpreter::cmd_false() {
_stack.setTop(0);
}
void ScriptInterpreter::cmd_push() {
_stack.push();
}
void ScriptInterpreter::cmd_not() {
if (_stack.top() == 0)
_stack.setTop(-1);
else
_stack.setTop(0);
}
void ScriptInterpreter::cmd_add() {
int16 value = _stack.pop();
_stack.setTop(_stack.top() + value);
}
void ScriptInterpreter::cmd_sub() {
int16 value = _stack.pop();
_stack.setTop(_stack.top() - value);
}
void ScriptInterpreter::cmd_mul() {
int16 value = _stack.pop();
_stack.setTop(_stack.top() * value);
}
void ScriptInterpreter::cmd_div() {
int16 value = _stack.pop();
if (value == 0)
_stack.setTop(0);
else
_stack.setTop(_stack.top() / value);
}
void ScriptInterpreter::cmd_mod() {
int16 value = _stack.pop();
if (value == 0)
_stack.setTop(0);
else
_stack.setTop(_stack.top() % value);
}
void ScriptInterpreter::cmd_band() {
int16 value = _stack.pop();
_stack.setTop(_stack.top() & value);
}
void ScriptInterpreter::cmd_bor() {
int16 value = _stack.pop();
_stack.setTop(_stack.top() | value);
}
void ScriptInterpreter::cmd_bnot() {
_stack.setTop(~_stack.top());
}
void ScriptInterpreter::cmd_lt() {
int16 value = _stack.pop();
if (_stack.top() < value)
_stack.setTop(-1);
else
_stack.setTop(0);
}
void ScriptInterpreter::cmd_eq() {
int16 value = _stack.pop();
if (_stack.top() == value)
_stack.setTop(-1);
else
_stack.setTop(0);
}
void ScriptInterpreter::cmd_gt() {
int16 value = _stack.pop();
if (_stack.top() > value)
_stack.setTop(-1);
else
_stack.setTop(0);
}
void ScriptInterpreter::cmd_loadConstant() {
int16 value = readInt16();
debug(4, "value = %04X (%d)", value, value);
_stack.setTop(value);
}
void ScriptInterpreter::cmd_loadVariable() {
int16 variable = readInt16();
int16 value = _vm->_dat->getVar(variable);
debug(4, "variable = %d; value = %d (%04X)", variable, value, value);
_stack.setTop(value);
}
void ScriptInterpreter::cmd_getObjectProperty() {
int16 propertyId = _stack.pop();
int16 objectIndex = _stack.top();
int16 value = _vm->_dat->getObjectProperty(objectIndex, propertyId);
debug(4, "value = %04X(%d)", value, value);
_stack.setTop(value);
}
void ScriptInterpreter::cmd_setObjectProperty() {
int16 value = _stack.pop();
int16 propertyId = _stack.pop();
int16 objectIndex = _stack.top();
value = _vm->_dat->setObjectProperty(objectIndex, propertyId, value);
_stack.setTop(value);
}
void ScriptInterpreter::cmd_set() {
int16 variable = readInt16();
debug(4, "var(%d) = %04d (%d)", variable, _stack.top(), _stack.top());
_vm->_dat->setVar(variable, _stack.top());
}
void ScriptInterpreter::cmd_print() {
// TODO: This opcode was used for printing debug messages
const char *text = _vm->_dat->getObjectString(_stack.top());
debug(4, "%s", text);
_stack.setTop(0);
}
void ScriptInterpreter::cmd_terpri() {
// TODO: This opcode was used for printing debug messages
debug(4, "\n");
_stack.setTop(0);
}
void ScriptInterpreter::cmd_printNumber() {
// TODO: This opcode was used for printing debug messages
debug(4, "%d", _stack.top());
}
void ScriptInterpreter::cmd_vref() {
int16 value = 0;
int16 index = _stack.pop();
int16 objectIndex = _stack.top();
debug(4, "index = %d; objectIndex = %d", index, objectIndex);
if (objectIndex > 0) {
Object *obj = _vm->_dat->getObject(objectIndex);
value = obj->getVectorItem(index);
}
_stack.setTop(value);
debug(4, "--> value = %d", value);
}
void ScriptInterpreter::cmd_vset() {
int16 value = _stack.pop();
int16 index = _stack.pop();
int16 objectIndex = _stack.top();
debug(4, "index = %d; objectIndex = %d; value = %d", index, objectIndex, value);
if (objectIndex > 0) {
Object *obj = _vm->_dat->getObject(objectIndex);
obj->setVectorItem(index, value);
}
_stack.setTop(value);
}
void ScriptInterpreter::cmd_vsize() {
int16 objectIndex = _stack.top();
int16 size = 0;
if (objectIndex > 0) {
Object *obj = _vm->_dat->getObject(objectIndex);
size = obj->getVectorSize();
}
_stack.setTop(size);
}
void ScriptInterpreter::cmd_exit() {
_vm->quitGame();
// Make sure the "quit" event is handled immediately
_vm->handleEvents();
}
void ScriptInterpreter::cmd_return() {
// Check if returning from main function
if (_localStackPos == kScriptStackSize) {
_vm->quitGame();
// Make sure the "quit" event is handled immediately
_vm->handleEvents();
return;
}
int16 funcResult = _stack.top();
_stack.setStackPos(_localStackPos);
_localStackPos = kScriptStackLimit - _stack.pop();
_runningScriptObjectIndex = _stack.pop();
_codeBase = _vm->_dat->getObject(_runningScriptObjectIndex)->getData();
_codeIp = _codeBase + _stack.pop();
byte argc = _stack.pop();
_stack.free(argc);
_stack.setTop(funcResult);
debug(4, "LEAVE: stackPtr = %d; _localStackPos = %d\n", _stack.getStackPos(), _localStackPos);
}
void ScriptInterpreter::cmd_call() {
debug(4, "\nENTER: stackPtr = %d; _localStackPos = %d", _stack.getStackPos(), _localStackPos);
byte argc = readByte();
_stack.push(argc);
_stack.push(_codeIp - _codeBase);
_stack.push(_runningScriptObjectIndex);
_stack.push(kScriptStackLimit - _localStackPos);
_localStackPos = _stack.getStackPos();
_runningScriptObjectIndex = _stack.peek(_localStackPos + argc + 4);
debug(4, "argc = %d; _runningScriptObjectIndex = %04X", argc, _runningScriptObjectIndex);
_codeBase = _vm->_dat->getObject(_runningScriptObjectIndex)->getData();
_codeIp = _codeBase;
}
void ScriptInterpreter::cmd_svar() {
// Never used in LGOP2, RTZ, Manhole:NE, Rodney
warning("Unimplemented command: cmd_svar");
}
void ScriptInterpreter::cmd_sset() {
// Never used in LGOP2, RTZ, Manhole:NE, Rodney
warning("Unimplemented command: cmd_sset");
}
void ScriptInterpreter::cmd_split() {
// Never used in LGOP2, RTZ, Manhole:NE, Rodney
warning("Unimplemented command: cmd_split");
}
void ScriptInterpreter::cmd_snlit() {
// Never used in LGOP2, RTZ, Manhole:NE, Rodney
warning("Unimplemented command: cmd_snlit");
}
void ScriptInterpreter::cmd_yorn() {
// Never used in LGOP2, RTZ, Manhole:NE, Rodney
warning("Unimplemented command: cmd_yorn");
}
void ScriptInterpreter::cmd_save() {
int16 result = 0;
int16 stringOfs = _stack.top();
const char *filename = _vm->_dat->getString(stringOfs);
result = _vm->_dat->savegame(filename, "", 0);
_stack.setTop(result);
}
void ScriptInterpreter::cmd_restore() {
int16 result = 0;
int16 stringOfs = _stack.top();
const char *filename = _vm->_dat->getString(stringOfs);
result = _vm->_dat->loadgame(filename, 0);
_stack.setTop(result);
}
void ScriptInterpreter::cmd_arg() {
int16 argIndex = readByte();
debug(4, "argIndex = %d; value = %04X (%d)", argIndex, _stack.peek(_localStackPos + 4 + argIndex), _stack.peek(_localStackPos + 4 + argIndex));
_stack.setTop(_stack.peek(_localStackPos + 4 + argIndex));
}
void ScriptInterpreter::cmd_aset() {
int16 argIndex = readByte();
debug(4, "argIndex = %d; value = %d", argIndex, _stack.peek(_localStackPos + 4 + argIndex));
_stack.poke(_localStackPos + 4 + argIndex, _stack.top());
}
void ScriptInterpreter::cmd_tmp() {
int16 tempIndex = readByte();
debug(4, "tempIndex = %d; value = %d", tempIndex, _stack.peek(_localStackPos - tempIndex - 1));
_stack.setTop(_stack.peek(_localStackPos - tempIndex - 1));
}
void ScriptInterpreter::cmd_tset() {
int16 tempIndex = readByte();
debug(4, "tempIndex = %d; value = %d", tempIndex, _stack.top());
_stack.poke(_localStackPos - tempIndex - 1, _stack.top());
}
void ScriptInterpreter::cmd_tspace() {
int16 tempCount = readByte();
debug(4, "tempCount = %d", tempCount);
_stack.alloc(tempCount);
}
void ScriptInterpreter::cmd_class() {
// Never used in LGOP2, RTZ, Manhole:NE, Rodney
warning("Unimplemented command: cmd_class");
}
void ScriptInterpreter::cmd_objectp() {
Object *obj = _vm->_dat->getObject(_stack.top());
if (obj->isObject())
_stack.setTop(-1);
else
_stack.setTop(0);
}
void ScriptInterpreter::cmd_vectorp() {
// Never used in LGOP2, RTZ, Manhole:NE, Rodney
warning("Unimplemented command: cmd_vectorp");
}
void ScriptInterpreter::cmd_restart() {
_vm->_dat->reload();
_vm->_screen->clearChannels();
_vm->resetAllTimers();
_stack.setTop(0);
}
void ScriptInterpreter::cmd_rand() {
_stack.setTop(_vm->_rnd->getRandomNumber(_stack.top() - 1));
}
void ScriptInterpreter::cmd_randomize() {
_vm->_rnd->setSeed(Common::RandomSource::generateNewSeed());
_stack.setTop(0);
}
void ScriptInterpreter::cmd_send() {
debug(4, "\nENTER: stackPtr = %d; _localStackPos = %d", _stack.getStackPos(), _localStackPos);
byte argc = readByte();
debug(4, "argc = %d", argc);
_stack.push(argc);
_stack.push(_codeIp - _codeBase);
_stack.push(_runningScriptObjectIndex);
_stack.push(kScriptStackLimit - _localStackPos);
_localStackPos = _stack.getStackPos();
int16 propertyId = _stack.peek(_localStackPos + argc + 2);
int16 objectIndex = _stack.peek(_localStackPos + argc + 4);
debug(4, "objectIndex = %d (%04X); propertyId = %d(%04X)", objectIndex, objectIndex, propertyId, propertyId);
if (objectIndex != 0) {
objectIndex = _vm->_dat->getObject(objectIndex)->getClass();
} else {
objectIndex = _stack.peek(_localStackPos + argc + 3);
}
debug(4, "--> objectIndex = %d(%04X)", objectIndex, objectIndex);
if (objectIndex != 0) {
_runningScriptObjectIndex = _vm->_dat->getObjectProperty(objectIndex, propertyId);
if (_runningScriptObjectIndex != 0) {
_codeBase = _vm->_dat->getObject(_runningScriptObjectIndex)->getData();
_codeIp = _codeBase;
} else {
_stack.push(0);
cmd_return();
}
} else {
_stack.push(0);
cmd_return();
}
}
void ScriptInterpreter::cmd_extend() {
byte func = readByte();
byte argc = readByte();
int16 *argv = _stack.getStackPtr();
debug(4, "func = %d (%s); argc = %d", func, _functions->getFuncName(func), argc);
for (int i = 0; i < argc; i++)
debug(2, "argv[%02d] = %04X (%d)", i, argv[i], argv[i]);
int16 result = _functions->callFunction(func, argc, argv);
debug(2, "result = %04X (%d)", result, result);
_stack.free(argc);
_stack.setTop(result);
}
void ScriptInterpreter::cmd_catch() {
// Never used in LGOP2, RTZ, Manhole:NE, Rodney
warning("Unimplemented command: cmd_catch");
}
void ScriptInterpreter::cmd_cdone() {
// Never used in LGOP2, RTZ, Manhole:NE, Rodney
warning("Unimplemented command: cmd_cdone");
}
void ScriptInterpreter::cmd_throw() {
// Never used in LGOP2, RTZ, Manhole:NE, Rodney
warning("Unimplemented command: cmd_throw");
}
void ScriptInterpreter::cmd_functionp() {
// Never used in LGOP2, RTZ, Manhole:NE, Rodney
warning("Unimplemented command: cmd_functionp");
}
void ScriptInterpreter::cmd_le() {
int16 value = _stack.pop();
if (_stack.top() <= value)
_stack.setTop(-1);
else
_stack.setTop(0);
}
void ScriptInterpreter::cmd_ge() {
int16 value = _stack.pop();
if (_stack.top() >= value)
_stack.setTop(-1);
else
_stack.setTop(0);
}
void ScriptInterpreter::cmd_varx() {
// Never used in LGOP2, RTZ, Manhole:NE, Rodney
warning("Unimplemented command: cmd_varx");
}
void ScriptInterpreter::cmd_setx() {
// Never used in LGOP2, RTZ, Manhole:NE, Rodney
warning("Unimplemented command: cmd_setx");
}
#ifdef DUMP_SCRIPTS
void ScriptInterpreter::dumpScript(int16 objectIndex, int *opcodeStats, int *externStats) {
debug(1, "Dumping code for object %04X", objectIndex);
Object *obj = _vm->_dat->getObject(objectIndex);
byte *code = obj->getData(), *codeStart = code, *codeEnd = code + obj->getSize();
while (code < codeEnd) {
byte opcode = *code++;
if (opcode >= 1 && opcode <= _commandsMax) {
Common::String codeLine;
const char *desc = _commands[opcode - 1].desc;
const char *sig = _commands[opcode - 1].sig;
int valueType; /* 0: dec; 1: hex; 2: extended function */
int16 value;
opcodeStats[opcode - 1]++;
codeLine += Common::String::format("[%04X] ", (uint16)(code - codeStart - 1));
codeLine += desc;
for (; *sig != '\0'; sig++) {
codeLine += " ";
switch (*sig) {
case 'b':
valueType = 0;
value = *code++;
break;
case 'B':
valueType = 1;
value = *code++;
break;
case 'w':
valueType = 0;
value = READ_LE_UINT16(code);
code += 2;
break;
case 'W':
valueType = 1;
value = READ_LE_UINT16(code);
code += 2;
break;
case 'E':
valueType = 2;
value = *code++;
break;
}
Common::String tempStr;
switch (valueType) {
case 0:
tempStr = Common::String::format("%d", value);
break;
case 1:
tempStr = Common::String::format("0x%X", value);
break;
case 2:
if (value < _functions->getCount()) {
tempStr = Common::String::format("%s", _functions->getFuncName(value));
externStats[value]++;
} else {
tempStr = Common::String::format("invalid: %d", value);
}
break;
}
codeLine += tempStr;
}
debug(1, "%s", codeLine.c_str());
} else {
error("ScriptInterpreter::dumpScript(%d) Unknown opcode %02X", objectIndex, opcode);
}
}
debug(1, "-------------------------------------------");
}
void ScriptInterpreter::dumpAllScripts() {
int *opcodeStats = new int[_commandsMax - 1];
int *externStats = new int[_functions->getCount()];
for (int i = 0; i < _commandsMax; i++)
opcodeStats[i] = 0;
for (int i = 0; i < _functions->getCount(); i++)
externStats[i] = 0;
for (uint objectIndex = 1; objectIndex <= _vm->_dat->getObjectCount(); objectIndex++) {
Object *obj = _vm->_dat->getObject(objectIndex);
// Check if it's a byte array which might contain code
if (obj->getClass() != 0x7FFF)
continue;
// Code objects aren't excplicitly marked as such, we need to check if
// the last byte is a cmd_return opcode.
byte *retByte = obj->getData() + obj->getSize() - 1;
if (*retByte == 0x1F) {
dumpScript(objectIndex, opcodeStats, externStats);
}
}
debug(1, "OPCODE statistics:");
for (int i = 0; i < _commandsMax - 1; i++)
if (opcodeStats[i] > 0)
debug(1, "%-30s: %d", _commands[i].desc, opcodeStats[i]);
debug(1, "UNUSED OPCODE statistics:");
for (int i = 0; i < _commandsMax - 1; i++)
if (opcodeStats[i] == 0)
debug(1, "%-30s: %d", _commands[i].desc, opcodeStats[i]);
debug(1, ".");
debug(1, "EXTERN statistics (%d):", _functions->getCount());
for (int i = 0; i < _functions->getCount(); i++)
if (externStats[i] > 0)
debug(1, "%-30s: %d", _functions->getFuncName(i), externStats[i]);
debug(1, "UNUSED EXTERN statistics (%d):", _functions->getCount());
for (int i = 0; i < _functions->getCount(); i++)
if (externStats[i] == 0)
debug(1, "%-30s: %d", _functions->getFuncName(i), externStats[i]);
debug(1, ".");
delete[] opcodeStats;
delete[] externStats;
}
#endif
} // End of namespace Made

169
engines/made/script.h Normal file
View File

@@ -0,0 +1,169 @@
/* 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/>.
*
*/
#ifndef MADE_SCRIPT_H
#define MADE_SCRIPT_H
#include "common/textconsole.h"
namespace Made {
// Define this to dump all game scripts and a usage statistic of all
// opcodes/extended functions instead of running the actual game.
// Then run ScummVM with debuglevel 1.
//#define DUMP_SCRIPTS
class MadeEngine;
class ScriptFunctions;
const int kScriptStackSize = 1000;
const int kScriptStackLimit = kScriptStackSize + 1;
class ScriptStack {
public:
ScriptStack() {
for (int16 i = 0; i < kScriptStackSize; i++)
_stack[i] = 0;
_stackPos = kScriptStackSize;
}
~ScriptStack() {}
inline int16 top() { return _stack[_stackPos]; }
inline int16 pop() {
if (_stackPos == kScriptStackSize)
error("ScriptStack::pop() Stack underflow");
return _stack[_stackPos++];
}
inline void push(int16 value = 0) {
if (_stackPos == 0)
error("ScriptStack::push() Stack overflow");
_stack[--_stackPos] = value;
}
inline void setTop(int16 value) { _stack[_stackPos] = value; }
inline int16 peek(int16 index) { return _stack[index]; }
inline void poke(int16 index, int16 value) { _stack[index] = value; }
inline void alloc(int16 count) { _stackPos -= count; }
inline void free(int16 count) { _stackPos += count; }
inline int16 getStackPos() const { return _stackPos; }
inline void setStackPos(int16 stackPtr) { _stackPos = stackPtr; }
inline int16 *getStackPtr() { return &_stack[_stackPos]; }
protected:
int16 _stack[kScriptStackSize];
int16 _stackPos;
};
class ScriptInterpreter {
public:
ScriptInterpreter(MadeEngine *vm);
~ScriptInterpreter();
void runScript(int16 scriptObjectIndex);
void dumpScript(int16 objectIndex, int *opcodeStats, int *externStats);
void dumpAllScripts();
protected:
MadeEngine *_vm;
ScriptStack _stack;
int16 _localStackPos;
int16 _runningScriptObjectIndex;
byte *_codeBase, *_codeIp;
ScriptFunctions *_functions;
byte readByte();
int16 readInt16();
typedef void (ScriptInterpreter::*CommandProc)();
struct CommandEntry {
CommandProc proc;
const char *desc;
#ifdef DUMP_SCRIPTS
const char *sig;
#endif
};
const CommandEntry *_commands;
int16 _commandsMax;
void cmd_branchTrue();
void cmd_branchFalse();
void cmd_branch();
void cmd_true();
void cmd_false();
void cmd_push();
void cmd_not();
void cmd_add();
void cmd_sub();
void cmd_mul();
void cmd_div();
void cmd_mod();
void cmd_band();
void cmd_bor();
void cmd_bnot();
void cmd_lt();
void cmd_eq();
void cmd_gt();
void cmd_loadConstant();
void cmd_loadVariable();
void cmd_getObjectProperty();
void cmd_setObjectProperty();
void cmd_set();
void cmd_print();
void cmd_terpri();
void cmd_printNumber();
void cmd_vref();
void cmd_vset();
void cmd_vsize();
void cmd_exit();
void cmd_return();
void cmd_call();
void cmd_svar();
void cmd_sset();
void cmd_split();
void cmd_snlit();
void cmd_yorn();
void cmd_save();
void cmd_restore();
void cmd_arg();
void cmd_aset();
void cmd_tmp();
void cmd_tset();
void cmd_tspace();
void cmd_class();
void cmd_objectp();
void cmd_vectorp();
void cmd_restart();
void cmd_rand();
void cmd_randomize();
void cmd_send();
void cmd_extend();
void cmd_catch();
void cmd_cdone();
void cmd_throw();
void cmd_functionp();
void cmd_le();
void cmd_ge();
void cmd_varx();
void cmd_setx();
};
} // End of namespace Made
#endif /* MADE_H */

1151
engines/made/scriptfuncs.cpp Normal file

File diff suppressed because it is too large Load Diff

185
engines/made/scriptfuncs.h Normal file
View File

@@ -0,0 +1,185 @@
/* 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/>.
*
*/
#ifndef MADE_SCRIPTFUNCS_H
#define MADE_SCRIPTFUNCS_H
#include "made/resource.h"
#include "audio/mixer.h"
#include "common/debug.h"
#include "common/system.h"
namespace Audio {
class PCSpeaker;
}
namespace Made {
class MadeEngine;
typedef Common::Functor2<int16, int16*, int16> ExternalFunc;
class ScriptFunctions {
public:
ScriptFunctions(MadeEngine *vm);
virtual ~ScriptFunctions();
int16 callFunction(uint16 index, int16 argc, int16 *argv) {
if (index >= _externalFuncs.size())
error("ScriptFunctions::callFunction() Invalid function index %d", index);
debug(4, "%s", _externalFuncNames[index]);
return (*_externalFuncs[index])(argc, argv);
}
void setupExternalsTable();
const char* getFuncName(int index) { return _externalFuncNames[index]; }
int getCount() const { return _externalFuncs.size(); }
void stopSound();
protected:
MadeEngine *_vm;
Audio::SoundHandle _audioStreamHandle;
Audio::SoundHandle _voiceStreamHandle;
SoundResource* _soundResource;
bool _soundStarted;
bool _soundWasPlaying;
// The sound length in milliseconds for purpose of checking if the sound is
// still playing.
int _soundCheckLength;
// The audio volume set by the game scripts.
uint8 _gameAudioVolume;
// PlayNote/StopNote and PlayTele/StopTele wave generators
Audio::PCSpeaker *_pcSpeaker1, *_pcSpeaker2;
Common::Array<const ExternalFunc *> _externalFuncs;
Common::Array<const char *> _externalFuncNames;
int16 sfSystemCall(int16 argc, int16 *argv);
int16 sfInitGraf(int16 argc, int16 *argv);
int16 sfRestoreGraf(int16 argc, int16 *argv);
int16 sfDrawPicture(int16 argc, int16 *argv);
int16 sfClearScreen(int16 argc, int16 *argv);
int16 sfShowPage(int16 argc, int16 *argv);
int16 sfPollEvent(int16 argc, int16 *argv);
int16 sfGetMouseX(int16 argc, int16 *argv);
int16 sfGetMouseY(int16 argc, int16 *argv);
int16 sfGetKey(int16 argc, int16 *argv);
int16 sfSetVisualEffect(int16 argc, int16 *argv);
int16 sfPlaySound(int16 argc, int16 *argv);
int16 sfPlayMusic(int16 argc, int16 *argv);
int16 sfStopMusic(int16 argc, int16 *argv);
int16 sfIsMusicPlaying(int16 argc, int16 *argv);
int16 sfSetTextPos(int16 argc, int16 *argv);
int16 sfFlashScreen(int16 argc, int16 *argv);
int16 sfPlayNote(int16 argc, int16 *argv);
int16 sfStopNote(int16 argc, int16 *argv);
int16 sfPlayTele(int16 argc, int16 *argv);
int16 sfStopTele(int16 argc, int16 *argv);
int16 sfHideMouseCursor(int16 argc, int16 *argv);
int16 sfShowMouseCursor(int16 argc, int16 *argv);
int16 sfGetMusicBeat(int16 argc, int16 *argv);
int16 sfSetScreenLock(int16 argc, int16 *argv);
int16 sfAddSprite(int16 argc, int16 *argv);
int16 sfFreeAnim(int16 argc, int16 *argv);
int16 sfDrawSprite(int16 argc, int16 *argv);
int16 sfEraseSprites(int16 argc, int16 *argv);
int16 sfUpdateSprites(int16 argc, int16 *argv);
int16 sfGetTimer(int16 argc, int16 *argv);
int16 sfSetTimer(int16 argc, int16 *argv);
int16 sfResetTimer(int16 argc, int16 *argv);
int16 sfAllocTimer(int16 argc, int16 *argv);
int16 sfFreeTimer(int16 argc, int16 *argv);
int16 sfSetPaletteLock(int16 argc, int16 *argv);
int16 sfSetFont(int16 argc, int16 *argv);
int16 sfDrawText(int16 argc, int16 *argv);
int16 sfHomeText(int16 argc, int16 *argv);
int16 sfSetTextRect(int16 argc, int16 *argv);
int16 sfSetTextXY(int16 argc, int16 *argv);
int16 sfSetFontDropShadow(int16 argc, int16 *argv);
int16 sfSetFontColor(int16 argc, int16 *argv);
int16 sfSetFontOutline(int16 argc, int16 *argv);
int16 sfLoadMouseCursor(int16 argc, int16 *argv);
int16 sfSetSpriteGround(int16 argc, int16 *argv);
int16 sfLoadResText(int16 argc, int16 *argv);
int16 sfSetClipArea(int16 argc, int16 *argv);
int16 sfSetSpriteClip(int16 argc, int16 *argv);
int16 sfAddScreenMask(int16 argc, int16 *argv);
int16 sfSetSpriteMask(int16 argc, int16 *argv);
int16 sfSoundPlaying(int16 argc, int16 *argv);
int16 sfStopSound(int16 argc, int16 *argv);
int16 sfPlayVoice(int16 argc, int16 *argv);
int16 sfPlayCd(int16 argc, int16 *argv);
int16 sfStopCd(int16 argc, int16 *argv);
int16 sfGetCdStatus(int16 argc, int16 *argv);
int16 sfGetCdTime(int16 argc, int16 *argv);
int16 sfPlayCdSegment(int16 argc, int16 *argv);
int16 sfPrintf(int16 argc, int16 *argv);
int16 sfClearMono(int16 argc, int16 *argv);
int16 sfGetSoundEnergy(int16 argc, int16 *argv);
int16 sfClearText(int16 argc, int16 *argv);
int16 sfAnimText(int16 argc, int16 *argv);
int16 sfGetTextWidth(int16 argc, int16 *argv);
int16 sfPlayMovie(int16 argc, int16 *argv);
int16 sfLoadSound(int16 argc, int16 *argv);
int16 sfLoadMusic(int16 argc, int16 *argv);
int16 sfLoadPicture(int16 argc, int16 *argv);
int16 sfSetMusicVolume(int16 argc, int16 *argv);
int16 sfRestartEvents(int16 argc, int16 *argv);
int16 sfPlaceSprite(int16 argc, int16 *argv);
int16 sfPlaceText(int16 argc, int16 *argv);
int16 sfDeleteChannel(int16 argc, int16 *argv);
int16 sfGetChannelType(int16 argc, int16 *argv);
int16 sfSetChannelState(int16 argc, int16 *argv);
int16 sfSetChannelLocation(int16 argc, int16 *argv);
int16 sfSetChannelContent(int16 argc, int16 *argv);
int16 sfSetExcludeArea(int16 argc, int16 *argv);
int16 sfSetSpriteExclude(int16 argc, int16 *argv);
int16 sfGetChannelState(int16 argc, int16 *argv);
int16 sfPlaceAnim(int16 argc, int16 *argv);
int16 sfSetAnimFrame(int16 argc, int16 *argv);
int16 sfGetAnimFrame(int16 argc, int16 *argv);
int16 sfGetAnimFrameCount(int16 argc, int16 *argv);
int16 sfGetPictureWidth(int16 argc, int16 *argv);
int16 sfGetPictureHeight(int16 argc, int16 *argv);
int16 sfSetSoundRate(int16 argc, int16 *argv);
int16 sfDrawAnimPic(int16 argc, int16 *argv);
int16 sfLoadAnim(int16 argc, int16 *argv);
int16 sfReadText(int16 argc, int16 *argv);
int16 sfReadMenu(int16 argc, int16 *argv);
int16 sfDrawMenu(int16 argc, int16 *argv);
int16 sfGetMenuCount(int16 argc, int16 *argv);
int16 sfSaveGame(int16 argc, int16 *argv);
int16 sfLoadGame(int16 argc, int16 *argv);
int16 sfGetGameDescription(int16 argc, int16 *argv);
int16 sfShakeScreen(int16 argc, int16 *argv);
int16 sfPlaceMenu(int16 argc, int16 *argv);
int16 sfSetSoundVolume(int16 argc, int16 *argv);
int16 sfGetSynthType(int16 argc, int16 *argv);
int16 sfIsSlowSystem(int16 argc, int16 *argv);
};
} // End of namespace Made
#endif /* MADE_H */

282
engines/made/sound.cpp Normal file
View File

@@ -0,0 +1,282 @@
/* 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/>.
*
*/
#include "made/sound.h"
#include "common/endian.h"
namespace Made {
void ManholeEgaSoundDecompressor::decompress(byte *source, byte *dest, uint32 size) {
/* Some kind of ADPCM compression. I hope this works on BE machines. */
int newmode;
_source = source;
_dest = dest;
_size = size;
_bitBuffer = 0;
_bitsLeft = 0;
_writeFlag = false;
_eof = false;
_sample1 = 0x80000;
_sample2 = 0x800000;
_sample3 = 0x800000;
_sample4 = 0x800000;
_mode = getBit();
while (!_eof) {
update1();
update3();
update0();
newmode = getBit();
if (_eof)
break;
if (newmode == _mode) {
update1();
update3();
do {
update0();
newmode = getBit();
if (_eof || newmode != _mode)
break;
update2();
update3();
} while (1);
}
_mode = newmode;
}
}
int ManholeEgaSoundDecompressor::getBit() {
if (_bitsLeft == 0) {
if (_size == 0) {
_eof = true;
return 0;
}
_bitBuffer = READ_BE_UINT16(_source);
_source += 2;
_bitsLeft = 16;
_size -= 2;
}
int temp = _bitBuffer & 0x8000;
_bitBuffer <<= 1;
_bitsLeft--;
return temp;
}
void ManholeEgaSoundDecompressor::update0() {
SWAP(_sample1, _sample3);
if (_sample2 & 0x80000000) {
_sample2 -= (_sample2 >> 8) | 0xFF000000;
} else {
_sample2 -= _sample2 >> 8;
}
_sample2 += 0x8000;
if (_sample2 & 0x80000000) {
_sample2 = 0;
} else if ((_sample2 & 0xFFFF0000) > 0x00FF0000) {
_sample2 = 0xFF0000;
}
_sample1 += _sample2;
_sample1 >>= 1;
_sample1 -= _sample4;
_sample1 >>= 2;
_sample4 += _sample1;
if (_writeFlag) {
*_dest++ = (_sample4 & 0xFF0000) >> 16;
}
_writeFlag = !_writeFlag;
_sample1 = _sample2;
SWAP(_sample1, _sample3);
}
void ManholeEgaSoundDecompressor::update1() {
if (_sample1 & 0x80000000) {
_sample1 -= (_sample1 >> 8) | 0xFF000000;
} else {
_sample1 -= _sample1 >> 8;
}
_sample1 += 500;
}
void ManholeEgaSoundDecompressor::update2() {
uint32 temp = (_sample1 >> 6) | ((_sample1 & 0xFF) << 27) | ((_sample1 & 0xC0) >> 5);
if (_sample1 & 0x80000000) {
_sample1 += temp | 0xFC000000;
} else {
_sample1 += temp & 0x03FFFFFF;
}
_sample1 += 500;
}
void ManholeEgaSoundDecompressor::update3() {
if (_mode)
_sample2 -= _sample1;
else
_sample2 += _sample1;
}
void decompressSound(byte *source, byte *dest, uint16 chunkSize, uint16 chunkCount, SoundEnergyArray *soundEnergyArray, SoundDecoderData *soundDecoderData) {
int16 prevSample, workSample;
byte* soundBuffer;
byte deltaSoundBuffer[1024];
int16 soundBuffer2[16];
byte deltaType, type;
uint16 workChunkSize, byteCount, bitCount;
byte bitMask, bitShift;
uint16 ofs = 0;
uint16 i = 0, l = 0;
byte val;
SoundEnergyItem soundEnergyItem;
const int modeValues[3][4] = {
{ 2, 8, 0x01, 1},
{ 4, 4, 0x03, 2},
{16, 2, 0x0F, 4}
};
soundEnergyItem.position = 0;
memset(deltaSoundBuffer, 0, 1024);
if (soundEnergyArray)
soundEnergyArray->clear();
if (soundDecoderData) {
soundBuffer = soundDecoderData->_soundBuffer;
prevSample = soundDecoderData->_prevSample;
} else {
soundBuffer = new byte[1025];
memset(soundBuffer, 0x80, 1025);
prevSample = 0;
}
while (chunkCount--) {
deltaType = (*source) >> 6;
workChunkSize = chunkSize;
if (deltaType == 1)
workChunkSize /= 2;
else if (deltaType == 2)
workChunkSize /= 4;
type = (*source++) & 0x0F;
workSample = prevSample;
soundEnergyItem.position += chunkSize;
switch (type) {
case 0:
memset(soundBuffer, 0x80, workChunkSize);
workSample = 0;
soundEnergyItem.energy = 0;
if (soundEnergyArray)
soundEnergyArray->push_back(soundEnergyItem);
break;
case 1:
break;
case 2:
case 3:
case 4:
byteCount = modeValues[type - 2][0];
bitCount = modeValues[type - 2][1];
bitMask = modeValues[type - 2][2];
bitShift = modeValues[type - 2][3];
ofs = 0;
for (i = 0; i < byteCount; i++)
soundBuffer2[i] = (*source++) * 2 - 128;
while (ofs < workChunkSize) {
val = *source++;
for (i = 0; i < bitCount; i++) {
workSample = CLIP<int16>(workSample + soundBuffer2[val & bitMask], -127, 127);
val >>= bitShift;
soundBuffer[ofs++] = workSample + 128;
}
}
soundEnergyItem.energy = type - 1;
if (soundEnergyArray)
soundEnergyArray->push_back(soundEnergyItem);
break;
case 5:
for (i = 0; i < workChunkSize; i++)
soundBuffer[i] = *source++;
workSample = soundBuffer[workChunkSize - 1] - 128;
soundEnergyItem.energy = 4;
if (soundEnergyArray)
soundEnergyArray->push_back(soundEnergyItem);
break;
default:
delete[] soundBuffer;
return;
}
if (deltaType > 0) {
// NB: The original did not add this extra value at the end (as far
// as I can tell), and so technically read past the filled part of
// soundBuffer.
soundBuffer[workChunkSize] = soundBuffer[workChunkSize - 1];
if (deltaType == 1) {
for (i = 0; i < chunkSize - 1; i += 2) {
l = i / 2;
deltaSoundBuffer[i] = soundBuffer[l];
deltaSoundBuffer[i + 1] = (soundBuffer[l] + soundBuffer[l + 1]) / 2;
}
} else if (deltaType == 2) {
for (i = 0; i < chunkSize - 1; i += 4) {
l = i / 4;
deltaSoundBuffer[i] = soundBuffer[l];
deltaSoundBuffer[i + 2] = (soundBuffer[l] + soundBuffer[l + 1]) / 2;
deltaSoundBuffer[i + 1] = (deltaSoundBuffer[i + 2] + soundBuffer[l]) / 2;
deltaSoundBuffer[i + 3] = (deltaSoundBuffer[i + 2] + soundBuffer[l + 1]) / 2;
}
}
for (i = 0; i < chunkSize; i++)
soundBuffer[i] = deltaSoundBuffer[i];
}
prevSample = workSample;
memcpy(dest, soundBuffer, chunkSize);
dest += chunkSize;
}
if (soundDecoderData) {
soundDecoderData->_prevSample = prevSample;
} else {
delete[] soundBuffer;
}
}
} // End of namespace Made

74
engines/made/sound.h Normal file
View File

@@ -0,0 +1,74 @@
/* 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/>.
*
*/
#ifndef MADE_SOUND_H
#define MADE_SOUND_H
#include "common/array.h"
namespace Made {
class ManholeEgaSoundDecompressor {
public:
void decompress(byte *source, byte *dest, uint32 size);
protected:
byte *_source, *_dest;
uint32 _size;
uint16 _bitBuffer;
int _bitsLeft;
int32 _sample1, _sample2, _sample3, _sample4;
bool _writeFlag;
bool _eof;
int _mode;
int getBit();
void update0();
void update1();
void update2();
void update3();
};
struct SoundEnergyItem {
uint32 position;
byte energy;
};
typedef Common::Array<SoundEnergyItem> SoundEnergyArray;
// Persistent data for decompressSound(). When calling decompressSound()
// repeatedly (for the same stream), pass the same SoundDecoderData object to
// ensure decoding properly resumes.
class SoundDecoderData {
public:
SoundDecoderData() {
memset(_soundBuffer, 0x80, sizeof(_soundBuffer));
_prevSample = 0;
}
byte _soundBuffer[1025];
int16 _prevSample;
};
void decompressSound(byte *source, byte *dest, uint16 chunkSize, uint16 chunkCount, SoundEnergyArray *soundEnergyArray = NULL, SoundDecoderData *decoderData = NULL);
} // End of namespace Made
#endif /* MADE_H */