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

1
engines/adl/POTFILES Normal file
View File

@@ -0,0 +1 @@
engines/adl/metaengine.cpp

1587
engines/adl/adl.cpp Normal file

File diff suppressed because it is too large Load Diff

482
engines/adl/adl.h Normal file
View File

@@ -0,0 +1,482 @@
/* 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 ADL_ADL_H
#define ADL_ADL_H
#include "common/debug-channels.h"
#include "common/array.h"
#include "common/rect.h"
#include "common/str.h"
#include "common/hashmap.h"
#include "common/hash-str.h"
#include "common/func.h"
#include "common/ptr.h"
#include "common/scummsys.h"
#include "common/keyboard.h"
#include "engines/engine.h"
#include "audio/mixer.h"
#include "audio/softsynth/pcspk.h"
#include "adl/console.h"
#include "adl/disk.h"
#include "adl/sound.h"
#include "adl/detection.h"
namespace Common {
class ReadStream;
class WriteStream;
class SeekableReadStream;
class File;
struct Event;
class RandomSource;
}
namespace Adl {
Common::Path getDiskImageName(const AdlGameDescription &adlDesc, byte volume);
GameType getGameType(const AdlGameDescription &desc);
GameVersion getGameVersion(const AdlGameDescription &desc);
Common::Language getLanguage(const AdlGameDescription &desc);
Common::Platform getPlatform(const AdlGameDescription &desc);
class Console;
class Display;
class GraphicsMan;
class ScriptEnv;
enum kDebugChannels {
kDebugChannelScript = 1,
};
enum ADLAction {
kADLActionNone,
kADLActionQuit,
kADLActionCount
};
// Save and restore opcodes
#define IDO_ACT_SAVE 0x0f
#define IDO_ACT_LOAD 0x10
#define IDI_CUR_ROOM 0xfc
#define IDI_VOID_ROOM 0xfd
#define IDI_ANY 0xfe
#define IDI_WORD_SIZE 8
enum Direction {
IDI_DIR_NORTH,
IDI_DIR_SOUTH,
IDI_DIR_EAST,
IDI_DIR_WEST,
IDI_DIR_UP,
IDI_DIR_DOWN,
IDI_DIR_TOTAL
};
struct Room {
Room() :
description(0),
picture(0),
curPicture(0),
isFirstTime(true) {
memset(connections, 0, sizeof(connections));
}
byte description;
byte connections[IDI_DIR_TOTAL];
Common::DataBlockPtr data;
byte picture;
byte curPicture;
bool isFirstTime;
};
typedef Common::HashMap<byte, Common::DataBlockPtr> PictureMap;
typedef Common::Array<byte> Script;
struct Command {
byte room;
byte verb, noun;
byte numCond, numAct;
Script script;
};
class ScriptEnv {
public:
ScriptEnv(const Command &cmd, byte room, byte verb, byte noun) :
_cmd(cmd), _room(room), _verb(verb), _noun(noun), _ip(0) { }
virtual ~ScriptEnv() { }
enum kOpType {
kOpTypeDone,
kOpTypeCond,
kOpTypeAct
};
byte op() const { return _cmd.script[_ip]; }
virtual kOpType getOpType() const = 0;
// We keep this 1-based for easier comparison with the original engine
byte arg(uint i) const { return _cmd.script[_ip + i]; }
virtual void next(uint numArgs) = 0;
bool isMatch() const {
return (_cmd.room == IDI_ANY || _cmd.room == _room) &&
(_cmd.verb == IDI_ANY || _cmd.verb == _verb) &&
(_cmd.noun == IDI_ANY || _cmd.noun == _noun);
}
byte getNoun() const { return _noun; }
const Command &getCommand() const { return _cmd; }
protected:
byte _ip;
private:
const Command &_cmd;
const byte _room, _verb, _noun;
};
enum {
IDI_ITEM_NOT_MOVED,
IDI_ITEM_DROPPED,
IDI_ITEM_DOESNT_MOVE
};
struct Item {
byte id;
byte noun;
byte region;
byte room;
byte picture;
bool isShape;
Common::Point position;
int state;
byte description;
Common::Array<byte> roomPictures;
bool isOnScreen;
Item() : id(0), noun(0), region(0), room(0), picture(0), isShape(false), state(0), description(0), isOnScreen(false) { }
};
struct Time {
byte hours, minutes;
Time() : hours(12), minutes(0) { }
};
struct RoomState {
byte picture;
byte isFirstTime;
};
struct Region {
Common::Array<byte> vars;
Common::Array<RoomState> rooms;
};
struct State {
Common::Array<Region> regions;
Common::Array<Room> rooms;
Common::List<Item> items;
Common::Array<byte> vars;
byte region, prevRegion;
byte room;
byte curPicture;
uint16 moves;
bool isDark;
Time time;
State() : region(0), prevRegion(0), room(1), curPicture(0), moves(1), isDark(false) { }
};
typedef Common::List<Command> Commands;
typedef Common::HashMap<Common::String, uint> WordMap;
struct RoomData {
Common::String description;
PictureMap pictures;
Commands commands;
};
// Opcode debugging macros
#define OP_DEBUG_0(F) do { \
if (DebugMan.isDebugChannelEnabled(kDebugChannelScript) && op_debug(F)) \
return 0; \
} while (0)
#define OP_DEBUG_1(F, P1) do { \
if (DebugMan.isDebugChannelEnabled(kDebugChannelScript) && op_debug(F, P1)) \
return 1; \
} while (0)
#define OP_DEBUG_2(F, P1, P2) do { \
if (DebugMan.isDebugChannelEnabled(kDebugChannelScript) && op_debug(F, P1, P2)) \
return 2; \
} while (0)
#define OP_DEBUG_3(F, P1, P2, P3) do { \
if (DebugMan.isDebugChannelEnabled(kDebugChannelScript) && op_debug(F, P1, P2, P3)) \
return 3; \
} while (0)
#define OP_DEBUG_4(F, P1, P2, P3, P4) do { \
if (DebugMan.isDebugChannelEnabled(kDebugChannelScript) && op_debug(F, P1, P2, P3, P4)) \
return 4; \
} while (0)
class AdlEngine : public Engine {
friend class Console;
public:
~AdlEngine() override;
bool pollEvent(Common::Event &event) const;
void bell(uint count = 1) const;
protected:
AdlEngine(OSystem *syst, const AdlGameDescription *gd);
// Engine
Common::Error loadGameState(int slot) override;
Common::Error saveGameState(int slot, const Common::String &desc, bool isAutosave = false) override;
bool canSaveGameStateCurrently(Common::U32String *msg = nullptr) override;
Common::String getSaveStateName(int slot) const override;
int getAutosaveSlot() const override { return 15; }
Common::Path getDiskImageName(byte volume) const { return Adl::getDiskImageName(*_gameDescription, volume); }
GameType getGameType() const { return Adl::getGameType(*_gameDescription); }
GameVersion getGameVersion() const { return Adl::getGameVersion(*_gameDescription); }
Common::Language getLanguage() const { return Adl::getLanguage(*_gameDescription); }
virtual void gameLoop();
virtual void loadState(Common::ReadStream &stream);
virtual void saveState(Common::WriteStream &stream);
Common::String readString(Common::ReadStream &stream, byte until = 0, const char *key = "") const;
Common::String readStringAt(Common::SeekableReadStream &stream, uint offset, byte until = 0) const;
void extractExeStrings(Common::ReadStream &stream, uint16 printAddr, Common::StringArray &strings) const;
virtual void printString(const Common::String &str) = 0;
virtual Common::String loadMessage(uint idx) const = 0;
virtual void printMessage(uint idx);
virtual Common::String getItemDescription(const Item &item) const;
void delay(uint32 ms) const;
virtual Common::String getLine();
Common::String inputString(byte prompt = 0) const;
byte inputKey(bool showCursor = true) const;
void waitKey(uint32 ms = 0, Common::KeyCode keycode = Common::KEYCODE_INVALID) const;
virtual void getInput(uint &verb, uint &noun);
Common::String getWord(const Common::String &line, uint &index) const;
virtual Common::String formatVerbError(const Common::String &verb) const;
virtual Common::String formatNounError(const Common::String &verb, const Common::String &noun) const;
void loadWords(Common::ReadStream &stream, WordMap &map, Common::StringArray &pri, uint count = 0) const;
void readCommands(Common::ReadStream &stream, Commands &commands);
void removeCommand(Commands &commands, uint idx);
Command &getCommand(Commands &commands, uint idx);
void removeMessage(uint idx);
void checkInput(byte verb, byte noun);
virtual bool isInputValid(byte verb, byte noun, bool &is_any);
virtual bool isInputValid(const Commands &commands, byte verb, byte noun, bool &is_any);
virtual void applyRoomWorkarounds(byte roomNr) { }
virtual void applyRegionWorkarounds() { }
virtual void setupOpcodeTables();
virtual void initState();
virtual void switchRoom(byte roomNr);
virtual byte roomArg(byte room) const;
virtual void advanceClock() { }
void loadDroppedItemOffsets(Common::ReadStream &stream, byte count);
void stopTextToSpeech() const;
// Opcodes
typedef Common::SharedPtr<Common::Functor1<ScriptEnv &, int> > Opcode;
template <class T>
Opcode opcode(int (T::*f)(ScriptEnv &)) {
return Opcode(new Common::Functor1Mem<ScriptEnv &, int, T>(static_cast<T *>(this), f));
}
virtual int o_isItemInRoom(ScriptEnv &e);
virtual int o_isMovesGT(ScriptEnv &e);
virtual int o_isVarEQ(ScriptEnv &e);
virtual int o_isCurPicEQ(ScriptEnv &e);
virtual int o_isItemPicEQ(ScriptEnv &e);
virtual int o_varAdd(ScriptEnv &e);
virtual int o_varSub(ScriptEnv &e);
virtual int o_varSet(ScriptEnv &e);
virtual int o_listInv(ScriptEnv &e);
virtual int o_moveItem(ScriptEnv &e);
virtual int o_setRoom(ScriptEnv &e);
virtual int o_setCurPic(ScriptEnv &e);
virtual int o_setPic(ScriptEnv &e);
virtual int o_printMsg(ScriptEnv &e);
virtual int o_setLight(ScriptEnv &e);
virtual int o_setDark(ScriptEnv &e);
virtual int o_save(ScriptEnv &e);
virtual int o_restore(ScriptEnv &e);
virtual int o_restart(ScriptEnv &e);
virtual int o_quit(ScriptEnv &e);
virtual int o_placeItem(ScriptEnv &e);
virtual int o_setItemPic(ScriptEnv &e);
virtual int o_resetPic(ScriptEnv &e);
virtual int o_takeItem(ScriptEnv &e);
virtual int o_dropItem(ScriptEnv &e);
virtual int o_setRoomPic(ScriptEnv &e);
virtual int goDirection(ScriptEnv &e, Direction D);
int o_goNorth(ScriptEnv &e) { return goDirection(e, IDI_DIR_NORTH); }
int o_goSouth(ScriptEnv &e) { return goDirection(e, IDI_DIR_SOUTH); }
int o_goEast(ScriptEnv &e) { return goDirection(e, IDI_DIR_EAST); }
int o_goWest(ScriptEnv &e) { return goDirection(e, IDI_DIR_WEST); }
int o_goUp(ScriptEnv &e) { return goDirection(e, IDI_DIR_UP); }
int o_goDown(ScriptEnv &e) { return goDirection(e, IDI_DIR_DOWN); }
// Graphics
void drawPic(byte pic, Common::Point pos = Common::Point()) const;
// Sound
bool playTones(const Tones &tones, bool isMusic, bool allowSkip = false) const;
// Game state functions
const Region &getRegion(uint i) const;
Region &getRegion(uint i);
const Room &getRoom(uint i) const;
Room &getRoom(uint i);
const Region &getCurRegion() const;
Region &getCurRegion();
const Room &getCurRoom() const;
Room &getCurRoom();
const Item &getItem(uint i) const;
Item &getItem(uint i);
byte getVar(uint i) const;
void setVar(uint i, byte value);
virtual void takeItem(byte noun);
virtual void dropItem(byte noun);
bool matchCommand(ScriptEnv &env) const;
void doActions(ScriptEnv &env);
bool doOneCommand(const Commands &commands, byte verb, byte noun);
void doAllCommands(const Commands &commands, byte verb, byte noun);
virtual ScriptEnv *createScriptEnv(const Command &cmd, byte room, byte verb, byte noun);
// Debug functions
static Common::String toAscii(const Common::String &str);
Common::String itemStr(uint i) const;
Common::String roomStr(uint i) const;
Common::String itemRoomStr(uint i) const;
Common::String verbStr(uint i) const;
Common::String nounStr(uint i) const;
Common::String msgStr(uint i) const;
Common::String dirStr(Direction dir) const;
bool op_debug(const char *fmt, ...) const;
Common::DumpFile *_dumpFile;
Display *_display;
GraphicsMan *_graphics;
bool _textMode;
// Opcodes
Common::Array<Opcode> _condOpcodes, _actOpcodes;
// Message strings in data file
Common::Array<Common::DataBlockPtr> _messages;
// Picture data
PictureMap _pictures;
// Dropped item screen offsets
Common::Array<Common::Point> _itemOffsets;
// <room, verb, noun, script> lists
Commands _roomCommands;
Commands _globalCommands;
// Data related to the current room
RoomData _roomData;
WordMap _verbs;
WordMap _nouns;
Common::StringArray _priVerbs;
Common::StringArray _priNouns;
struct {
Common::String enterCommand;
Common::String verbError;
Common::String nounError;
Common::String playAgain;
Common::String pressReturn;
Common::String lineFeeds;
} _strings;
uint32 _verbErrorPos, _nounErrorPos;
struct {
uint cantGoThere;
uint dontUnderstand;
uint itemDoesntMove;
uint itemNotHere;
uint thanksForPlaying;
} _messageIds;
// Game state
State _state;
uint _linesPrinted;
bool _isRestarting, _isRestoring, _isQuitting;
bool _canSaveNow, _canRestoreNow;
bool _abortScript;
Common::RandomSource *_random;
const AdlGameDescription *_gameDescription;
mutable Common::File *_inputScript;
mutable uint _scriptDelay;
mutable bool _scriptPaused;
private:
virtual void runIntro() { }
virtual void init() = 0;
virtual void initGameState() = 0;
virtual void drawItems() = 0;
virtual void drawItem(Item &item, const Common::Point &pos) = 0;
virtual void loadRoom(byte roomNr) = 0;
virtual void showRoom() = 0;
virtual void switchRegion(byte region) { }
void runScript(const char *filename) const;
void stopScript() const;
void setScriptDelay(uint scriptDelay) const { _scriptDelay = scriptDelay; }
Common::String getScriptLine() const;
// Engine
Common::Error run() override;
bool hasFeature(EngineFeature f) const override;
bool canLoadGameStateCurrently(Common::U32String *msg = nullptr) override;
// Text input
byte convertKey(uint16 ascii) const;
byte _saveVerb, _saveNoun, _restoreVerb, _restoreNoun;
};
} // End of namespace Adl
#endif

644
engines/adl/adl_v2.cpp Normal file
View File

@@ -0,0 +1,644 @@
/* 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 "common/random.h"
#include "common/error.h"
#include "adl/adl_v2.h"
#include "adl/display.h"
#include "adl/graphics.h"
#include "adl/detection.h"
namespace Adl {
AdlEngine_v2::~AdlEngine_v2() {
delete _disk;
}
AdlEngine_v2::AdlEngine_v2(OSystem *syst, const AdlGameDescription *gd) :
AdlEngine(syst, gd),
_maxLines(4),
_disk(nullptr),
_currentVolume(0),
_itemRemoved(false),
_roomOnScreen(0),
_picOnScreen(0),
_itemsOnScreen(0) { }
void AdlEngine_v2::mapExeStrings(const Common::StringArray &strings) {
if (strings.size() < 11)
error("Not enough strings found in executable");
// Parser messages
_strings.verbError = strings[2];
_strings.nounError = strings[3];
_strings.enterCommand = strings[4];
// Line feeds
_strings.lineFeeds = strings[0];
// Opcode strings
_strings_v2.saveInsert = strings[5];
_strings_v2.saveReplace = strings[6];
_strings_v2.restoreInsert = strings[7];
_strings_v2.restoreReplace = strings[8];
_strings.playAgain = strings[9];
_strings.pressReturn = strings[10];
}
void AdlEngine_v2::insertDisk(byte volume) {
delete _disk;
_disk = new Common::DiskImage();
if (!_disk->open(getDiskImageName(volume)))
error("Failed to open disk volume %d", volume);
_currentVolume = volume;
}
void AdlEngine_v2::setupOpcodeTables() {
AdlEngine::setupOpcodeTables();
_condOpcodes[0x01] = opcode(&AdlEngine_v2::o_isFirstTime);
_condOpcodes[0x02] = opcode(&AdlEngine_v2::o_isRandomGT);
_condOpcodes[0x04] = opcode(&AdlEngine_v2::o_isNounNotInRoom);
_condOpcodes[0x07] = opcode(&AdlEngine_v2::o_isCarryingSomething);
_actOpcodes.resize(0x21);
_actOpcodes[0x0c] = opcode(&AdlEngine_v2::o_moveAllItems);
_actOpcodes[0x1e] = opcode(&AdlEngine_v2::o_tellTime);
_actOpcodes[0x1f] = opcode(&AdlEngine_v2::o_setRoomFromVar);
_actOpcodes[0x20] = opcode(&AdlEngine_v2::o_initDisk);
}
void AdlEngine_v2::initState() {
AdlEngine::initState();
_linesPrinted = 0;
_picOnScreen = 0;
_roomOnScreen = 0;
_itemRemoved = false;
_itemsOnScreen = 0;
}
byte AdlEngine_v2::roomArg(byte room) const {
if (room == IDI_CUR_ROOM)
return _state.room;
return room;
}
void AdlEngine_v2::advanceClock() {
Time &time = _state.time;
time.minutes += 5;
if (time.minutes == 60) {
time.minutes = 0;
++time.hours;
if (time.hours == 13)
time.hours = 1;
}
}
void AdlEngine_v2::checkTextOverflow(char c) {
if (c != _display->asciiToNative('\r'))
return;
++_linesPrinted;
if (_linesPrinted >= _maxLines)
handleTextOverflow();
}
void AdlEngine_v2::handleTextOverflow() {
_linesPrinted = 0;
_display->renderText();
if (_inputScript) {
// Set pause flag to activate regular behaviour of delay and inputKey
_scriptPaused = true;
if (_scriptDelay > 0)
delay(_scriptDelay);
else
inputKey();
_scriptPaused = false;
return;
}
bell();
while (true) {
char key = inputKey(false);
if (shouldQuit())
return;
if (key == _display->asciiToNative('\r'))
break;
bell(3);
}
}
Common::String AdlEngine_v2::loadMessage(uint idx) const {
if (_messages[idx]) {
Common::StreamPtr strStream(_messages[idx]->createReadStream());
return readString(*strStream, 0xff);
}
return Common::String();
}
void AdlEngine_v2::printString(const Common::String &str) {
Common::String s(str);
const uint textWidth = _display->getTextWidth();
uint endPos = textWidth - 1;
uint startPos = 0;
uint pos = 0;
const char spaceChar = _display->asciiToNative(' ');
const char returnChar = _display->asciiToNative('\r');
while (pos < s.size()) {
s.setChar(_display->asciiToNative(s[pos]), pos);
if (pos == endPos) {
while (s[pos] != spaceChar && s[pos] != returnChar) {
if (pos-- == startPos)
error("Word wrapping failed");
}
s.setChar(returnChar, pos);
endPos = pos + textWidth;
startPos = pos + 1;
}
++pos;
}
for (pos = 0; pos < s.size(); ++pos) {
checkTextOverflow(s[pos]);
_display->printChar(s[pos]);
}
checkTextOverflow(returnChar);
_display->printChar(returnChar);
_display->renderText();
}
void AdlEngine_v2::drawItem(Item &item, const Common::Point &pos) {
item.isOnScreen = true;
if (item.picture == 0 || (uint)(item.picture - 1) >= _itemPics.size()) {
warning("Item picture %d not found", item.picture);
return;
}
Common::StreamPtr stream(_itemPics[item.picture - 1]->createReadStream());
stream->readByte(); // Skip clear opcode
_graphics->drawPic(*stream, pos);
}
void AdlEngine_v2::loadRoom(byte roomNr) {
if (Common::find(_brokenRooms.begin(), _brokenRooms.end(), roomNr) != _brokenRooms.end()) {
warning("Attempt to load non-existent room %d", roomNr);
_roomData.description.clear();
_roomData.pictures.clear();
_roomData.commands.clear();
return;
}
Room &room = getRoom(roomNr);
Common::StreamPtr stream(room.data->createReadStream());
uint16 descOffset = stream->readUint16LE();
uint16 commandOffset = stream->readUint16LE();
_roomData.pictures.clear();
// There's no picture count. The original engine always checks at most
// five pictures. We use the description offset to bound our search.
uint16 picCount = (descOffset - 4) / 5;
for (uint i = 0; i < picCount; ++i) {
byte nr = stream->readByte();
_roomData.pictures[nr] = readDataBlockPtr(*stream);
}
_roomData.description = readStringAt(*stream, descOffset, 0xff);
_roomData.commands.clear();
if (commandOffset != 0) {
stream->seek(commandOffset);
readCommands(*stream, _roomData.commands);
}
applyRoomWorkarounds(roomNr);
}
void AdlEngine_v2::showRoom() {
bool redrawPic = false;
_state.curPicture = getCurRoom().curPicture;
if (_state.room != _roomOnScreen) {
loadRoom(_state.room);
_graphics->clearScreen();
if (!_state.isDark)
redrawPic = true;
} else {
if (_state.curPicture != _picOnScreen || _itemRemoved)
redrawPic = true;
}
if (redrawPic) {
_roomOnScreen = _state.room;
_picOnScreen = _state.curPicture;
drawPic(_state.curPicture);
_itemRemoved = false;
_itemsOnScreen = 0;
for (auto &item : _state.items)
item.isOnScreen = false;
}
if (!_state.isDark)
drawItems();
_display->renderGraphics();
printString(_roomData.description);
}
// TODO: Merge this into AdlEngine?
void AdlEngine_v2::takeItem(byte noun) {
for (auto &item : _state.items) {
if (item.noun == noun && item.room == _state.room && item.region == _state.region) {
if (item.state == IDI_ITEM_DOESNT_MOVE) {
printMessage(_messageIds.itemDoesntMove);
return;
}
bool itemIsHere = false;
if (item.state == IDI_ITEM_DROPPED) {
itemIsHere = true;
} else {
for (const auto &pic : item.roomPictures) {
if (pic == getCurRoom().curPicture || pic == IDI_ANY) {
itemIsHere = true;
break;
}
}
}
if (itemIsHere) {
if (!isInventoryFull()) {
item.room = IDI_ANY;
_itemRemoved = true;
item.state = IDI_ITEM_DROPPED;
}
return;
}
}
}
printMessage(_messageIds.itemNotHere);
}
void AdlEngine_v2::drawItems() {
for (auto &item : _state.items) {
// Skip items not in this room
if (item.region == _state.region && item.room == _state.room && !item.isOnScreen) {
if (item.state == IDI_ITEM_DROPPED) {
// Draw dropped item if in normal view
if (getCurRoom().picture == getCurRoom().curPicture && _itemsOnScreen < _itemOffsets.size())
drawItem(item, _itemOffsets[_itemsOnScreen++]);
} else {
// Draw fixed item if current view is in the pic list
for (const auto &pic : item.roomPictures) {
if (pic == _state.curPicture || pic == IDI_ANY) {
drawItem(item, item.position);
break;
}
}
}
}
}
}
Common::DataBlockPtr AdlEngine_v2::readDataBlockPtr(Common::ReadStream &f) const {
byte track = f.readByte();
byte sector = f.readByte();
byte offset = f.readByte();
byte size = f.readByte();
if (f.eos() || f.err())
error("Error reading DataBlockPtr");
if (track == 0 && sector == 0 && offset == 0 && size == 0)
return Common::DataBlockPtr();
adjustDataBlockPtr(track, sector, offset, size);
return _disk->getDataBlock(track, sector, offset, size);
}
void AdlEngine_v2::loadItems(Common::ReadStream &stream) {
byte id;
while ((id = stream.readByte()) != 0xff && !stream.eos() && !stream.err()) {
Item item;
item.id = id;
item.noun = stream.readByte();
item.room = stream.readByte();
item.picture = stream.readByte();
item.region = stream.readByte();
item.position.x = stream.readByte();
item.position.y = stream.readByte();
item.state = stream.readByte();
item.description = stream.readByte();
stream.readByte(); // Struct size
byte picListSize = stream.readByte();
// Flag to keep track of what has been drawn on the screen
stream.readByte();
for (uint i = 0; i < picListSize; ++i)
item.roomPictures.push_back(stream.readByte());
_state.items.push_back(item);
}
if (stream.eos() || stream.err())
error("Error loading items");
}
void AdlEngine_v2::loadRooms(Common::ReadStream &stream, byte count) {
for (uint i = 0; i < count; ++i) {
Room room;
stream.readByte(); // number
for (uint j = 0; j < 6; ++j)
room.connections[j] = stream.readByte();
room.data = readDataBlockPtr(stream);
room.picture = stream.readByte();
room.curPicture = stream.readByte();
room.isFirstTime = stream.readByte();
_state.rooms.push_back(room);
}
if (stream.eos() || stream.err())
error("Error loading rooms");
}
void AdlEngine_v2::loadMessages(Common::ReadStream &stream, byte count) {
for (uint i = 0; i < count; ++i)
_messages.push_back(readDataBlockPtr(stream));
}
void AdlEngine_v2::loadPictures(Common::ReadStream &stream) {
byte picNr;
while ((picNr = stream.readByte()) != 0xff) {
if (stream.eos() || stream.err())
error("Error reading global pic list");
_pictures[picNr] = readDataBlockPtr(stream);
}
}
void AdlEngine_v2::loadItemPictures(Common::ReadStream &stream, byte count) {
for (uint i = 0; i < count; ++i) {
stream.readByte(); // number
_itemPics.push_back(readDataBlockPtr(stream));
}
}
int AdlEngine_v2::o_isFirstTime(ScriptEnv &e) {
OP_DEBUG_0("\t&& IS_FIRST_TIME()");
bool oldFlag = getCurRoom().isFirstTime;
getCurRoom().isFirstTime = false;
if (!oldFlag)
return -1;
return 0;
}
int AdlEngine_v2::o_isRandomGT(ScriptEnv &e) {
OP_DEBUG_1("\t&& RAND() > %d", e.arg(1));
byte rnd = _random->getRandomNumber(255);
if (rnd > e.arg(1))
return 1;
return -1;
}
int AdlEngine_v2::o_isNounNotInRoom(ScriptEnv &e) {
OP_DEBUG_1("\t&& NO_SUCH_ITEMS_IN_ROOM(%s)", itemRoomStr(e.arg(1)).c_str());
for (const auto &item : _state.items)
if (item.noun == e.getNoun() && (item.room == roomArg(e.arg(1))))
return -1;
return 1;
}
int AdlEngine_v2::o_isCarryingSomething(ScriptEnv &e) {
OP_DEBUG_0("\t&& IS_CARRYING_SOMETHING()");
for (const auto &item : _state.items)
if (item.room == IDI_ANY)
return 0;
return -1;
}
int AdlEngine_v2::o_moveItem(ScriptEnv &e) {
OP_DEBUG_2("\tSET_ITEM_ROOM(%s, %s)", itemStr(e.arg(1)).c_str(), itemRoomStr(e.arg(2)).c_str());
byte room = roomArg(e.arg(2));
Item &item = getItem(e.arg(1));
if (item.room == _roomOnScreen)
_picOnScreen = 0;
// Set items that move from inventory to a room to state "dropped"
if (item.room == IDI_ANY && room != IDI_VOID_ROOM)
item.state = IDI_ITEM_DROPPED;
item.room = room;
return 2;
}
int AdlEngine_v2::o_setCurPic(ScriptEnv &e) {
OP_DEBUG_1("\tSET_CURPIC(%d)", e.arg(1));
getCurRoom().curPicture = _state.curPicture = e.arg(1);
return 1;
}
int AdlEngine_v2::o_setPic(ScriptEnv &e) {
OP_DEBUG_1("\tSET_PIC(%d)", e.arg(1));
getCurRoom().picture = getCurRoom().curPicture = _state.curPicture = e.arg(1);
return 1;
}
int AdlEngine_v2::o_moveAllItems(ScriptEnv &e) {
OP_DEBUG_2("\tMOVE_ALL_ITEMS(%s, %s)", itemRoomStr(e.arg(1)).c_str(), itemRoomStr(e.arg(2)).c_str());
byte room1 = roomArg(e.arg(1));
if (room1 == _state.room)
_picOnScreen = 0;
byte room2 = roomArg(e.arg(2));
for (auto &item : _state.items)
if (item.room == room1) {
item.room = room2;
if (room1 == IDI_ANY)
item.state = IDI_ITEM_DROPPED;
}
return 2;
}
int AdlEngine_v2::o_save(ScriptEnv &e) {
OP_DEBUG_0("\tSAVE_GAME()");
int slot = askForSlot(_strings_v2.saveInsert);
if (slot < 0)
return -1;
saveGameState(slot, "");
_display->printString(_strings_v2.saveReplace);
inputString();
return 0;
}
int AdlEngine_v2::o_restore(ScriptEnv &e) {
OP_DEBUG_0("\tRESTORE_GAME()");
int slot = askForSlot(_strings_v2.restoreInsert);
if (slot < 0)
return -1;
loadGameState(slot);
_isRestoring = false;
_display->printString(_strings_v2.restoreReplace);
inputString();
_picOnScreen = 0;
_roomOnScreen = 0;
return 0;
}
int AdlEngine_v2::o_placeItem(ScriptEnv &e) {
OP_DEBUG_4("\tPLACE_ITEM(%s, %s, (%d, %d))", itemStr(e.arg(1)).c_str(), itemRoomStr(e.arg(2)).c_str(), e.arg(3), e.arg(4));
Item &item = getItem(e.arg(1));
item.room = roomArg(e.arg(2));
item.position.x = e.arg(3);
item.position.y = e.arg(4);
item.state = IDI_ITEM_NOT_MOVED;
return 4;
}
int AdlEngine_v2::o_tellTime(ScriptEnv &e) {
OP_DEBUG_0("\tTELL_TIME()");
Common::String time = _strings_v2.time;
if (time.size() <= 16)
error("Invalid time string");
const char zeroChar = _display->asciiToNative('0');
time.setChar(zeroChar + _state.time.hours / 10, 12);
time.setChar(zeroChar + _state.time.hours % 10, 13);
time.setChar(zeroChar + _state.time.minutes / 10, 15);
time.setChar(zeroChar + _state.time.minutes % 10, 16);
printString(time);
return 0;
}
int AdlEngine_v2::o_setRoomFromVar(ScriptEnv &e) {
OP_DEBUG_1("\tROOM = VAR[%d]", e.arg(1));
getCurRoom().curPicture = getCurRoom().picture;
_state.room = getVar(e.arg(1));
return 1;
}
int AdlEngine_v2::o_initDisk(ScriptEnv &e) {
OP_DEBUG_0("\tINIT_DISK()");
_display->printAsciiString("NOT REQUIRED\r");
return 0;
}
bool AdlEngine_v2::canSaveGameStateCurrently(Common::U32String *msg) {
if (!_canSaveNow)
return false;
// Back up first visit flag as it may be changed by this test
const bool isFirstTime = getCurRoom().isFirstTime;
const bool retval = AdlEngine::canSaveGameStateCurrently(msg);
getCurRoom().isFirstTime = isFirstTime;
return retval;
}
int AdlEngine_v2::askForSlot(const Common::String &question) {
while (1) {
_display->printString(question);
Common::String input = inputString();
if (shouldQuit())
return -1;
if (input.size() > 0 && input[0] >= _display->asciiToNative('A') && input[0] <= _display->asciiToNative('O'))
return input[0] - _display->asciiToNative('A');
}
}
} // End of namespace Adl

100
engines/adl/adl_v2.h Normal file
View File

@@ -0,0 +1,100 @@
/* 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 ADL_ADL_V2_H
#define ADL_ADL_V2_H
#include "adl/adl.h"
namespace Adl {
class AdlEngine_v2 : public AdlEngine {
public:
~AdlEngine_v2() override;
protected:
AdlEngine_v2(OSystem *syst, const AdlGameDescription *gd);
// AdlEngine
void setupOpcodeTables() override;
void initState() override;
byte roomArg(byte room) const override;
void advanceClock() override;
void printString(const Common::String &str) override;
Common::String loadMessage(uint idx) const override;
void drawItems() override;
void drawItem(Item &item, const Common::Point &pos) override;
void loadRoom(byte roomNr) override;
void showRoom() override;
void takeItem(byte noun) override;
// Engine
bool canSaveGameStateCurrently(Common::U32String *msg = nullptr) override;
void mapExeStrings(const Common::StringArray &strings);
void insertDisk(byte volume);
virtual Common::DataBlockPtr readDataBlockPtr(Common::ReadStream &f) const;
virtual void adjustDataBlockPtr(byte &track, byte &sector, byte &offset, byte &size) const { }
void loadItems(Common::ReadStream &stream);
void loadRooms(Common::ReadStream &stream, byte count);
void loadMessages(Common::ReadStream &stream, byte count);
void loadPictures(Common::ReadStream &stream);
void loadItemPictures(Common::ReadStream &stream, byte count);
virtual bool isInventoryFull() { return false; }
int askForSlot(const Common::String &question);
void checkTextOverflow(char c);
void handleTextOverflow();
virtual int o_isFirstTime(ScriptEnv &e);
virtual int o_isRandomGT(ScriptEnv &e);
virtual int o_isNounNotInRoom(ScriptEnv &e);
virtual int o_isCarryingSomething(ScriptEnv &e);
int o_moveItem(ScriptEnv &e) override;
int o_setCurPic(ScriptEnv &e) override;
int o_setPic(ScriptEnv &e) override;
virtual int o_moveAllItems(ScriptEnv &e);
int o_save(ScriptEnv &e) override;
int o_restore(ScriptEnv &e) override ;
int o_placeItem(ScriptEnv &e) override;
virtual int o_tellTime(ScriptEnv &e);
virtual int o_setRoomFromVar(ScriptEnv &e);
virtual int o_initDisk(ScriptEnv &e);
struct {
Common::String time;
Common::String saveInsert, saveReplace;
Common::String restoreInsert, restoreReplace;
} _strings_v2;
uint _maxLines;
Common::DiskImage *_disk;
byte _currentVolume;
Common::Array<Common::DataBlockPtr> _itemPics;
bool _itemRemoved;
byte _roomOnScreen, _picOnScreen, _itemsOnScreen;
Common::Array<byte> _brokenRooms;
};
} // End of namespace Adl
#endif

75
engines/adl/adl_v3.cpp Normal file
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 "adl/adl_v3.h"
namespace Adl {
AdlEngine_v3::AdlEngine_v3(OSystem *syst, const AdlGameDescription *gd) :
AdlEngine_v2(syst, gd) {
}
Common::String AdlEngine_v3::getItemDescription(const Item &item) const {
return _itemDesc[item.description - 1];
}
void AdlEngine_v3::loadItemDescriptions(Common::SeekableReadStream &stream, byte count) {
int32 startPos = stream.pos();
uint16 baseAddr = stream.readUint16LE();
// This code assumes that the first pointer points to a string that
// directly follows the pointer table
assert(baseAddr != 0);
baseAddr -= count * 2;
for (uint i = 0; i < count; ++i) {
stream.seek(startPos + i * 2);
uint16 offset = stream.readUint16LE();
if (offset > 0) {
stream.seek(startPos + offset - baseAddr);
_itemDesc.push_back(readString(stream, 0xff));
} else
_itemDesc.push_back(Common::String());
}
if (stream.eos() || stream.err())
error("Error loading item descriptions");
}
int AdlEngine_v3::o_isNounNotInRoom(ScriptEnv &e) {
OP_DEBUG_1("\t&& NO_SUCH_ITEMS_IN_ROOM(%s)", itemRoomStr(e.arg(1)).c_str());
bool isAnItem = false;
for (const auto &item : _state.items) {
if (item.noun == e.getNoun()) {
isAnItem = true;
if (item.room == roomArg(e.arg(1)))
return -1;
}
}
return (isAnItem ? 1 : -1);
}
} // End of namespace Adl

48
engines/adl/adl_v3.h Normal file
View File

@@ -0,0 +1,48 @@
/* 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 ADL_ADL_V3_H
#define ADL_ADL_V3_H
#include "adl/adl_v2.h"
namespace Adl {
class AdlEngine_v3 : public AdlEngine_v2 {
public:
~AdlEngine_v3() override { }
protected:
AdlEngine_v3(OSystem *syst, const AdlGameDescription *gd);
// AdlEngine
Common::String getItemDescription(const Item &item) const override;
void loadItemDescriptions(Common::SeekableReadStream &stream, byte count);
int o_isNounNotInRoom(ScriptEnv &e) override;
Common::Array<Common::String> _itemDesc;
};
} // End of namespace Adl
#endif

600
engines/adl/adl_v4.cpp Normal file
View File

@@ -0,0 +1,600 @@
/* 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 "common/error.h"
#include "adl/adl_v4.h"
#include "adl/display.h"
namespace Adl {
AdlEngine_v4::AdlEngine_v4(OSystem *syst, const AdlGameDescription *gd) :
AdlEngine_v3(syst, gd),
_itemPicIndex(nullptr) {
}
AdlEngine_v4::~AdlEngine_v4() {
delete _itemPicIndex;
}
void AdlEngine_v4::gameLoop() {
uint verb = 0, noun = 0;
_isRestarting = false;
if (_isRestoring) {
// Game restored from launcher. As this version of ADL long jumps to
// the game loop after restoring, no special action is required.
_isRestoring = false;
}
showRoom();
if (_isRestarting || shouldQuit())
return;
_canSaveNow = _canRestoreNow = true;
getInput(verb, noun);
_canSaveNow = _canRestoreNow = false;
if (_isRestoring) {
// Game restored from GMM. Move cursor to next line and jump to
// start of game loop.
_display->printAsciiString("\r");
_isRestoring = false;
return;
}
if (_isRestarting || shouldQuit())
return;
_linesPrinted = 0;
checkInput(verb, noun);
if (_isRestarting || shouldQuit())
return;
doAllCommands(_globalCommands, verb, noun);
if (_isRestarting || shouldQuit())
return;
_state.moves++;
}
void AdlEngine_v4::loadState(Common::ReadStream &stream) {
_state.room = stream.readByte();
_state.region = stream.readByte();
_state.prevRegion = stream.readByte();
uint32 size = stream.readUint32BE();
if (size != _state.regions.size())
error("Region count mismatch (expected %i; found %i)", _state.regions.size(), size);
for (auto &region : _state.regions) {
size = stream.readUint32BE();
if (size != region.rooms.size())
error("Room count mismatch (expected %i; found %i)", region.rooms.size(), size);
for (auto &room : region.rooms) {
room.picture = stream.readByte();
room.isFirstTime = stream.readByte();
}
size = stream.readUint32BE();
if (size != region.vars.size())
error("Variable count mismatch (expected %i; found %i)", region.vars.size(), size);
for (uint i = 0; i < region.vars.size(); ++i)
region.vars[i] = stream.readByte();
}
size = stream.readUint32BE();
if (size != _state.items.size())
error("Item count mismatch (expected %i; found %i)", _state.items.size(), size);
for (auto &item : _state.items) {
item.room = stream.readByte();
item.region = stream.readByte();
item.state = stream.readByte();
}
size = stream.readUint32BE();
const uint expectedSize = _state.vars.size() - getRegion(1).vars.size();
if (size != expectedSize)
error("Variable count mismatch (expected %i; found %i)", expectedSize, size);
for (uint i = getRegion(1).vars.size(); i < _state.vars.size(); ++i)
_state.vars[i] = stream.readByte();
if (stream.err() || stream.eos())
return;
loadRegion(_state.region);
restoreRoomState(_state.room);
_roomOnScreen = _picOnScreen = 0;
}
void AdlEngine_v4::saveState(Common::WriteStream &stream) {
getCurRoom().isFirstTime = false;
backupVars();
backupRoomState(_state.room);
stream.writeByte(_state.room);
stream.writeByte(_state.region);
stream.writeByte(_state.prevRegion);
stream.writeUint32BE(_state.regions.size());
for (const auto &region : _state.regions) {
stream.writeUint32BE(region.rooms.size());
for (const auto &room : region.rooms) {
stream.writeByte(room.picture);
stream.writeByte(room.isFirstTime);
}
stream.writeUint32BE(region.vars.size());
for (uint i = 0; i < region.vars.size(); ++i)
stream.writeByte(region.vars[i]);
}
stream.writeUint32BE(_state.items.size());
for (const auto &item : _state.items) {
stream.writeByte(item.room);
stream.writeByte(item.region);
stream.writeByte(item.state);
}
stream.writeUint32BE(_state.vars.size() - getRegion(1).vars.size());
for (uint i = getRegion(1).vars.size(); i < _state.vars.size(); ++i)
stream.writeByte(_state.vars[i]);
}
Common::String AdlEngine_v4::loadMessage(uint idx) const {
if (_messages[idx]) {
Common::StreamPtr strStream(_messages[idx]->createReadStream());
return readString(*strStream, 0xff, "AVISDURGAN");
}
return Common::String();
}
Common::String AdlEngine_v4::getItemDescription(const Item &item) const {
return _itemDesc[item.id - 1];
}
void AdlEngine_v4::loadRegionLocations(Common::ReadStream &stream, uint regions) {
for (uint r = 0; r < regions; ++r) {
RegionLocation loc;
loc.track = stream.readByte();
loc.sector = stream.readByte();
if (stream.eos() || stream.err())
error("Failed to read region locations");
_regionLocations.push_back(loc);
}
}
void AdlEngine_v4::loadRegionInitDataOffsets(Common::ReadStream &stream, uint regions) {
for (uint r = 0; r < regions; ++r) {
RegionInitDataOffset initOfs;
initOfs.track = stream.readByte();
initOfs.sector = stream.readByte();
initOfs.offset = stream.readByte();
initOfs.volume = stream.readByte();
if (stream.eos() || stream.err())
error("Failed to read region init data offsets");
_regionInitDataOffsets.push_back(initOfs);
}
}
void AdlEngine_v4::initRoomState(RoomState &roomState) const {
roomState.picture = 1;
roomState.isFirstTime = 1;
}
void AdlEngine_v4::initRegions(const byte *roomsPerRegion, uint regions) {
_state.regions.resize(regions);
for (uint r = 0; r < regions; ++r) {
Region &regn = _state.regions[r];
// Each region has 24 variables
regn.vars.resize(24);
regn.rooms.resize(roomsPerRegion[r]);
for (uint rm = 0; rm < roomsPerRegion[r]; ++rm)
initRoomState(regn.rooms[rm]);
}
}
void AdlEngine_v4::fixupDiskOffset(byte &track, byte &sector) const {
if (_state.region == 0)
return;
sector += _regionLocations[_state.region - 1].sector;
if (sector >= 16) {
sector -= 16;
++track;
}
track += _regionLocations[_state.region - 1].track;
}
void AdlEngine_v4::adjustDataBlockPtr(byte &track, byte &sector, byte &offset, byte &size) const {
fixupDiskOffset(track, sector);
}
AdlEngine_v4::RegionChunkType AdlEngine_v4::getRegionChunkType(const uint16 addr) const {
switch (addr) {
case 0x9000:
return kRegionChunkMessages;
case 0x4a80:
return kRegionChunkGlobalPics;
case 0x4000:
return kRegionChunkVerbs;
case 0x1800:
return kRegionChunkNouns;
case 0x0e00:
return kRegionChunkRooms;
case 0x7b00:
return kRegionChunkRoomCmds;
case 0x9500:
return kRegionChunkGlobalCmds;
default:
return kRegionChunkUnknown;
}
}
void AdlEngine_v4::loadRegion(byte region) {
if (_currentVolume != _regionInitDataOffsets[region - 1].volume) {
insertDisk(_regionInitDataOffsets[region - 1].volume);
// FIXME: This shouldn't be needed, but currently is, due to
// implementation choices made earlier on for DataBlockPtr and DiskImage.
_state.region = 0; // To avoid region offset being applied
_itemPics.clear();
_itemPicIndex->seek(0);
loadItemPictures(*_itemPicIndex, _itemPicIndex->size() / 5);
}
_state.region = region;
byte track = _regionInitDataOffsets[region - 1].track;
byte sector = _regionInitDataOffsets[region - 1].sector;
uint offset = _regionInitDataOffsets[region - 1].offset;
fixupDiskOffset(track, sector);
for (uint block = 0; block < 7; ++block) {
Common::StreamPtr stream(_disk->createReadStream(track, sector, offset, 1));
uint16 addr = stream->readUint16LE();
uint16 size = stream->readUint16LE();
stream.reset(_disk->createReadStream(track, sector, offset, size / 256 + 1));
stream->skip(4);
switch (getRegionChunkType(addr)) {
case kRegionChunkMessages: {
// Messages
_messages.clear();
uint count = size / 4;
loadMessages(*stream, count);
break;
}
case kRegionChunkGlobalPics: {
// Global pics
_pictures.clear();
loadPictures(*stream);
break;
}
case kRegionChunkVerbs:
// Verbs
loadWords(*stream, _verbs, _priVerbs);
break;
case kRegionChunkNouns:
// Nouns
loadWords(*stream, _nouns, _priNouns);
break;
case kRegionChunkRooms: {
// Rooms
uint count = size / 14 - 1;
stream->skip(14); // Skip invalid room 0
_state.rooms.clear();
loadRooms(*stream, count);
break;
}
case kRegionChunkRoomCmds:
// Room commands
readCommands(*stream, _roomCommands);
break;
case kRegionChunkGlobalCmds:
// Global commands
readCommands(*stream, _globalCommands);
break;
default:
error("Unknown data block found (addr %04x; size %04x)", addr, size);
}
offset += 4 + size;
while (offset >= 256) {
offset -= 256;
++sector;
if (sector >= 16) {
sector = 0;
++track;
}
}
}
applyRegionWorkarounds();
restoreVars();
}
void AdlEngine_v4::loadItemPicIndex(Common::ReadStream &stream, uint items) {
_itemPicIndex = stream.readStream(items * 5);
if (stream.eos() || stream.err())
error("Error reading item index");
}
void AdlEngine_v4::backupRoomState(byte room) {
RoomState &backup = getCurRegion().rooms[room - 1];
backup.isFirstTime = getRoom(room).isFirstTime;
backup.picture = getRoom(room).picture;
}
byte AdlEngine_v4::restoreRoomState(byte room) {
const RoomState &backup = getCurRegion().rooms[room - 1];
if (backup.isFirstTime != 1) {
getRoom(room).curPicture = getRoom(room).picture = backup.picture;
getRoom(room).isFirstTime = false;
return 0;
}
// WORKAROUND: Fix for bug #15379: "ADL: HIRES5: Game unsolvable after
// loading savegame. Save state not properly restored?".
// There's a problem in the original engine when a picture is set for a
// room that hasn't been visited yet. If the user then saves before
// visiting that room, the picture change will be ignored when play is
// resumed from that savegame.
if (backup.picture != 1) {
warning("Detected picture change for unvisited room %d in region %d", room, _state.region);
getRoom(room).curPicture = getRoom(room).picture = backup.picture;
}
return 1;
}
void AdlEngine_v4::backupVars() {
Region &region = getCurRegion();
for (uint i = 0; i < region.vars.size(); ++i)
region.vars[i] = getVar(i);
}
void AdlEngine_v4::restoreVars() {
const Region &region = getCurRegion();
for (uint i = 0; i < region.vars.size(); ++i)
setVar(i, region.vars[i]);
}
void AdlEngine_v4::switchRegion(byte region) {
backupVars();
backupRoomState(_state.room);
_state.prevRegion = _state.region;
_state.region = region;
loadRegion(region);
_state.room = 1;
_picOnScreen = _roomOnScreen = 0;
}
void AdlEngine_v4::switchRoom(byte roomNr) {
getCurRoom().curPicture = getCurRoom().picture;
getCurRoom().isFirstTime = false;
backupRoomState(_state.room);
_state.room = roomNr;
restoreRoomState(_state.room);
}
void AdlEngine_v4::setupOpcodeTables() {
AdlEngine_v3::setupOpcodeTables();
_condOpcodes[0x08] = opcode(&AdlEngine_v4::o_isVarGT);
_condOpcodes[0x0a].reset();
_actOpcodes[0x0a] = opcode(&AdlEngine_v4::o_setRegionToPrev);
_actOpcodes[0x0b].reset();
_actOpcodes[0x0e] = opcode(&AdlEngine_v4::o_setRegion);
_actOpcodes[0x12] = opcode(&AdlEngine_v4::o_setRegionRoom);
_actOpcodes[0x13].reset();
_actOpcodes[0x1e].reset();
_actOpcodes[0x1f].reset();
}
int AdlEngine_v4::o_isItemInRoom(ScriptEnv &e) {
OP_DEBUG_2("\t&& GET_ITEM_ROOM(%s) == %s", itemStr(e.arg(1)).c_str(), itemRoomStr(e.arg(2)).c_str());
const Item &item = getItem(e.arg(1));
if (e.arg(2) != IDI_ANY && item.region != _state.region)
return -1;
if (item.room == roomArg(e.arg(2)))
return 2;
return -1;
}
int AdlEngine_v4::o_isVarGT(ScriptEnv &e) {
OP_DEBUG_2("\t&& VARS[%d] > %d", e.arg(1), e.arg(2));
if (getVar(e.arg(1)) > e.arg(2))
return 2;
return -1;
}
int AdlEngine_v4::o_moveItem(ScriptEnv &e) {
AdlEngine_v3::o_moveItem(e);
getItem(e.arg(1)).region = _state.region;
return 2;
}
int AdlEngine_v4::o_setRegionToPrev(ScriptEnv &e) {
OP_DEBUG_0("\tREGION = PREV_REGION");
switchRegion(_state.prevRegion);
// Long jump
_isRestarting = true;
return -1;
}
int AdlEngine_v4::o_moveAllItems(ScriptEnv &e) {
OP_DEBUG_2("\tMOVE_ALL_ITEMS(%s, %s)", itemRoomStr(e.arg(1)).c_str(), itemRoomStr(e.arg(2)).c_str());
byte room1 = roomArg(e.arg(1));
if (room1 == _state.room)
_picOnScreen = 0;
byte room2 = roomArg(e.arg(2));
for (auto &item : _state.items) {
if (room1 != item.room)
continue;
if (room1 != IDI_ANY) {
if (_state.region != item.region)
continue;
if (room2 == IDI_ANY) {
if (isInventoryFull())
break;
if (item.state == IDI_ITEM_DOESNT_MOVE)
continue;
}
}
item.room = room2;
item.region = _state.region;
if (room1 == IDI_ANY)
item.state = IDI_ITEM_DROPPED;
}
return 2;
}
int AdlEngine_v4::o_setRegion(ScriptEnv &e) {
OP_DEBUG_1("\tREGION = %d", e.arg(1));
switchRegion(e.arg(1));
// Long jump
_isRestarting = true;
return -1;
}
int AdlEngine_v4::o_save(ScriptEnv &e) {
OP_DEBUG_0("\tSAVE_GAME()");
_display->printString(_strings_v2.saveReplace);
const char key = inputKey();
if (shouldQuit())
return -1;
if (key != _display->asciiToNative('Y'))
return 0;
const int slot = askForSlot(_strings_v2.saveInsert);
if (slot < 0)
return -1;
saveGameState(slot, "");
return 0;
}
int AdlEngine_v4::o_restore(ScriptEnv &e) {
OP_DEBUG_0("\tRESTORE_GAME()");
const int slot = askForSlot(_strings_v2.restoreInsert);
if (slot < 0)
return -1;
loadGameState(slot);
_isRestoring = false;
_picOnScreen = 0;
_roomOnScreen = 0;
// Long jump
_isRestarting = true;
return -1;
}
int AdlEngine_v4::o_restart(ScriptEnv &e) {
OP_DEBUG_0("\tRESTART_GAME()");
while (true) {
_display->printString(_strings.playAgain);
const Common::String input(inputString());
if (shouldQuit())
return -1;
if (input.firstChar() == _display->asciiToNative('N')) {
return o_quit(e);
} else if (input.firstChar() == _display->asciiToNative('Y')) {
// The original game loads a special save game from volume 3
initState();
// Long jump
_isRestarting = true;
return -1;
}
}
}
int AdlEngine_v4::o_setRegionRoom(ScriptEnv &e) {
OP_DEBUG_2("\tSET_REGION_ROOM(%d, %d)", e.arg(1), e.arg(2));
switchRegion(e.arg(1));
_state.room = e.arg(2);
// Long jump
_isRestarting = true;
return -1;
}
int AdlEngine_v4::o_setRoomPic(ScriptEnv &e) {
AdlEngine_v3::o_setRoomPic(e);
backupRoomState(e.arg(1));
return 2;
}
} // End of namespace Adl

106
engines/adl/adl_v4.h Normal file
View File

@@ -0,0 +1,106 @@
/* 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 ADL_ADL_V4_H
#define ADL_ADL_V4_H
#include "adl/adl_v3.h"
namespace Adl {
// Base track/sector for a region
struct RegionLocation {
byte track;
byte sector;
};
// Location of the 7 initial data blocks, relative to RegionLocation
struct RegionInitDataOffset {
byte track;
byte sector;
byte offset;
byte volume;
};
class AdlEngine_v4 : public AdlEngine_v3 {
public:
~AdlEngine_v4() override;
protected:
AdlEngine_v4(OSystem *syst, const AdlGameDescription *gd);
// AdlEngine
void setupOpcodeTables() override;
void gameLoop() override;
void loadState(Common::ReadStream &stream) override;
void saveState(Common::WriteStream &stream) override;
Common::String loadMessage(uint idx) const override;
Common::String getItemDescription(const Item &item) const override;
void switchRegion(byte region) override;
void switchRoom(byte roomNr) override;
// AdlEngine_v2
void adjustDataBlockPtr(byte &track, byte &sector, byte &offset, byte &size) const override;
enum RegionChunkType {
kRegionChunkUnknown,
kRegionChunkMessages,
kRegionChunkGlobalPics,
kRegionChunkVerbs,
kRegionChunkNouns,
kRegionChunkRooms,
kRegionChunkRoomCmds,
kRegionChunkGlobalCmds
};
void loadRegionLocations(Common::ReadStream &stream, uint regions);
void loadRegionInitDataOffsets(Common::ReadStream &stream, uint regions);
void initRegions(const byte *roomsPerRegion, uint regions);
void fixupDiskOffset(byte &track, byte &sector) const;
virtual RegionChunkType getRegionChunkType(const uint16 addr) const;
void loadRegion(byte region);
void loadItemPicIndex(Common::ReadStream &stream, uint items);
void backupRoomState(byte room);
virtual void initRoomState(RoomState &roomState) const;
virtual byte restoreRoomState(byte room);
void backupVars();
void restoreVars();
int o_isItemInRoom(ScriptEnv &e) override;
virtual int o_isVarGT(ScriptEnv &e);
int o_moveItem(ScriptEnv &e) override;
virtual int o_setRegionToPrev(ScriptEnv &e);
int o_moveAllItems(ScriptEnv &e) override;
virtual int o_setRegion(ScriptEnv &e);
int o_save(ScriptEnv &e) override;
int o_restore(ScriptEnv &e) override;
int o_restart(ScriptEnv &e) override;
virtual int o_setRegionRoom(ScriptEnv &e);
int o_setRoomPic(ScriptEnv &e) override;
Common::Array<RegionLocation> _regionLocations;
Common::Array<RegionInitDataOffset> _regionInitDataOffsets;
Common::SeekableReadStream *_itemPicIndex;
};
} // End of namespace Adl
#endif

162
engines/adl/adl_v5.cpp Normal file
View File

@@ -0,0 +1,162 @@
/* 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 "common/random.h"
#include "common/error.h"
#include "adl/adl_v5.h"
#include "adl/display.h"
#include "adl/graphics.h"
namespace Adl {
AdlEngine_v5::AdlEngine_v5(OSystem *syst, const AdlGameDescription *gd) :
AdlEngine_v4(syst, gd) {
}
void AdlEngine_v5::initRoomState(RoomState &roomState) const {
roomState.picture = 0xff;
roomState.isFirstTime = 0xff;
}
byte AdlEngine_v5::restoreRoomState(byte room) {
const RoomState &backup = getCurRegion().rooms[room - 1];
if (backup.isFirstTime != 0xff) {
getRoom(room).curPicture = getRoom(room).picture = backup.picture;
// CHECKME: Why doesn't this just copy the flag unconditionally?
if (backup.isFirstTime != 1) {
getRoom(room).isFirstTime = false;
return 0;
}
}
return backup.isFirstTime;
}
AdlEngine_v5::RegionChunkType AdlEngine_v5::getRegionChunkType(const uint16 addr) const {
switch (addr) {
case 0x7b00:
return kRegionChunkGlobalCmds;
case 0x9500:
return kRegionChunkRoomCmds;
default:
return AdlEngine_v4::getRegionChunkType(addr);
}
}
void AdlEngine_v5::setupOpcodeTables() {
AdlEngine_v4::setupOpcodeTables();
_condOpcodes[0x0a] = opcode(&AdlEngine_v5::o_abortScript);
_actOpcodes[0x0a] = opcode(&AdlEngine_v5::o_dummy);
_actOpcodes[0x0b] = opcode(&AdlEngine_v5::o_setTextMode);
_actOpcodes[0x0e] = opcode(&AdlEngine_v5::o_dummy);
_actOpcodes[0x13] = opcode(&AdlEngine_v5::o_dummy);
}
int AdlEngine_v5::o_isNounNotInRoom(ScriptEnv &e) {
OP_DEBUG_1("\t&& NO_SUCH_ITEMS_IN_ROOM(%s)", itemRoomStr(e.arg(1)).c_str());
setVar(24, 0);
for (const auto &item : _state.items)
if (item.noun == e.getNoun()) {
setVar(24, 1);
if (item.room == roomArg(e.arg(1)))
return -1;
}
return 1;
}
int AdlEngine_v5::o_abortScript(ScriptEnv &e) {
OP_DEBUG_0("\t&& ABORT_SCRIPT()");
_abortScript = true;
setVar(2, 0);
return -1;
}
int AdlEngine_v5::o_dummy(ScriptEnv &e) {
OP_DEBUG_0("\tDUMMY()");
return 0;
}
int AdlEngine_v5::o_setTextMode(ScriptEnv &e) {
OP_DEBUG_1("\tSET_TEXT_MODE(%d)", e.arg(1));
switch (e.arg(1)) {
case 1:
if (_linesPrinted != 0) {
_display->printChar(_display->asciiToNative(' '));
handleTextOverflow();
_display->moveCursorTo(Common::Point(0, 23));
_maxLines = 4;
}
return 1;
case 2:
_textMode = true;
_display->setMode(Display::kModeText);
_display->home();
_maxLines = 24;
_linesPrinted = 0;
return 1;
case 3:
// We re-use the restarting flag here, to simulate a long jump
_isRestarting = true;
return -1;
default:
error("Invalid text mode %d", e.arg(1));
}
}
int AdlEngine_v5::o_setRegionRoom(ScriptEnv &e) {
OP_DEBUG_2("\tSET_REGION_ROOM(%d, %d)", e.arg(1), e.arg(2));
getCurRoom().curPicture = getCurRoom().picture;
getCurRoom().isFirstTime = false;
switchRegion(e.arg(1));
_state.room = e.arg(2);
restoreRoomState(_state.room);
return -1;
}
int AdlEngine_v5::o_setRoomPic(ScriptEnv &e) {
const byte isFirstTime = restoreRoomState(e.arg(1));
// CHECKME: More peculiar isFirstTime handling (see also restoreRoomState).
// Is this here to prevent changing the backed up flag from 1 to 0? Since
// that could only happen if the room isFirstTime is 0 while the backed up flag
// is 1, is this scenario even possible?
if (isFirstTime != 0xff)
getRoom(e.arg(1)).isFirstTime = isFirstTime;
AdlEngine_v4::o_setRoomPic(e);
return 2;
}
} // End of namespace Adl

54
engines/adl/adl_v5.h Normal file
View File

@@ -0,0 +1,54 @@
/* 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 ADL_ADL_V5_H
#define ADL_ADL_V5_H
#include "adl/adl_v4.h"
namespace Adl {
class AdlEngine_v5 : public AdlEngine_v4 {
public:
~AdlEngine_v5() override { }
protected:
AdlEngine_v5(OSystem *syst, const AdlGameDescription *gd);
// AdlEngine
void setupOpcodeTables() override;
// AdlEngine_v4
RegionChunkType getRegionChunkType(const uint16 addr) const override;
void initRoomState(RoomState &roomState) const override;
byte restoreRoomState(byte room) override;
int o_isNounNotInRoom(ScriptEnv &e) override;
virtual int o_abortScript(ScriptEnv &e);
virtual int o_dummy(ScriptEnv &e);
virtual int o_setTextMode(ScriptEnv &e);
int o_setRegionRoom(ScriptEnv &e) override;
int o_setRoomPic(ScriptEnv &e) override;
};
} // End of namespace Adl
#endif

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 adl "ADL" yes "" "" "16bit highres"

460
engines/adl/console.cpp Normal file
View File

@@ -0,0 +1,460 @@
/* 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 "common/debug-channels.h"
#include "adl/console.h"
#include "adl/display.h"
#include "adl/graphics.h"
#include "adl/adl.h"
#include "adl/disk.h"
namespace Adl {
Console::Console(AdlEngine *engine) : GUI::Debugger() {
_engine = engine;
registerCmd("nouns", WRAP_METHOD(Console, Cmd_Nouns));
registerCmd("verbs", WRAP_METHOD(Console, Cmd_Verbs));
registerCmd("dump_scripts", WRAP_METHOD(Console, Cmd_DumpScripts));
registerCmd("valid_cmds", WRAP_METHOD(Console, Cmd_ValidCommands));
registerCmd("region", WRAP_METHOD(Console, Cmd_Region));
registerCmd("room", WRAP_METHOD(Console, Cmd_Room));
registerCmd("items", WRAP_METHOD(Console, Cmd_Items));
registerCmd("give_item", WRAP_METHOD(Console, Cmd_GiveItem));
registerCmd("vars", WRAP_METHOD(Console, Cmd_Vars));
registerCmd("var", WRAP_METHOD(Console, Cmd_Var));
registerCmd("convert_disk", WRAP_METHOD(Console, Cmd_ConvertDisk));
registerCmd("run_script", WRAP_METHOD(Console, Cmd_RunScript));
registerCmd("stop_script", WRAP_METHOD(Console, Cmd_StopScript));
registerCmd("set_script_delay", WRAP_METHOD(Console, Cmd_SetScriptDelay));
}
Common::String Console::toAscii(const Common::String &str) {
Common::String ascii(str);
for (uint i = 0; i < ascii.size(); ++i)
ascii.setChar(ascii[i] & 0x7f, i);
return ascii;
}
Common::String Console::toNative(const Common::String &str) {
Common::String native(str);
if (native.size() > IDI_WORD_SIZE)
native.erase(IDI_WORD_SIZE);
native.toUppercase();
for (uint i = 0; i < native.size(); ++i)
native.setChar(_engine->_display->asciiToNative(native[i]), i);
while (native.size() < IDI_WORD_SIZE)
native += _engine->_display->asciiToNative(' ');
return native;
}
bool Console::Cmd_Verbs(int argc, const char **argv) {
if (argc != 1) {
debugPrintf("Usage: %s\n", argv[0]);
return true;
}
debugPrintf("Verbs in alphabetical order:\n");
printWordMap(_engine->_verbs);
return true;
}
bool Console::Cmd_Nouns(int argc, const char **argv) {
if (argc != 1) {
debugPrintf("Usage: %s\n", argv[0]);
return true;
}
debugPrintf("Nouns in alphabetical order:\n");
printWordMap(_engine->_nouns);
return true;
}
bool Console::Cmd_ValidCommands(int argc, const char **argv) {
if (argc != 1) {
debugPrintf("Usage: %s\n", argv[0]);
return true;
}
bool is_any;
for (const auto &verb : _engine->_verbs) {
for (const auto &noun : _engine->_nouns) {
if (_engine->isInputValid(verb._value, noun._value, is_any) && !is_any)
debugPrintf("%s %s\n", toAscii(verb._key).c_str(), toAscii(noun._key).c_str());
}
if (_engine->isInputValid(verb._value, IDI_ANY, is_any))
debugPrintf("%s *\n", toAscii(verb._key).c_str());
}
if (_engine->isInputValid(IDI_ANY, IDI_ANY, is_any))
debugPrintf("* *\n");
return true;
}
void Console::dumpScripts(const Common::Path &prefix) {
for (byte roomNr = 1; roomNr <= _engine->_state.rooms.size(); ++roomNr) {
_engine->loadRoom(roomNr);
if (_engine->_roomData.commands.size() != 0) {
_engine->_dumpFile->open(prefix.append(Common::String::format("%03d.ADL", roomNr)));
_engine->doAllCommands(_engine->_roomData.commands, IDI_ANY, IDI_ANY);
_engine->_dumpFile->close();
}
}
_engine->loadRoom(_engine->_state.room);
_engine->_dumpFile->open(prefix.append("GLOBAL.ADL"));
_engine->doAllCommands(_engine->_globalCommands, IDI_ANY, IDI_ANY);
_engine->_dumpFile->close();
_engine->_dumpFile->open(prefix.append("RESPONSE.ADL"));
_engine->doAllCommands(_engine->_roomCommands, IDI_ANY, IDI_ANY);
_engine->_dumpFile->close();
}
bool Console::Cmd_DumpScripts(int argc, const char **argv) {
if (argc != 1) {
debugPrintf("Usage: %s\n", argv[0]);
return true;
}
bool oldFlag = DebugMan.isDebugChannelEnabled(kDebugChannelScript);
DebugMan.enableDebugChannel("Script");
_engine->_dumpFile = new Common::DumpFile();
if (_engine->_state.regions.empty()) {
dumpScripts();
} else {
const byte oldRegion = _engine->_state.region;
const byte oldPrevRegion = _engine->_state.prevRegion;
const byte oldRoom = _engine->_state.room;
for (byte regionNr = 1; regionNr <= _engine->_state.regions.size(); ++regionNr) {
_engine->switchRegion(regionNr);
dumpScripts(Common::Path(Common::String::format("%03d-", regionNr)));
}
_engine->switchRegion(oldRegion);
_engine->_state.prevRegion = oldPrevRegion;
_engine->_state.room = oldRoom;
_engine->loadRoom(oldRoom);
}
delete _engine->_dumpFile;
_engine->_dumpFile = nullptr;
if (!oldFlag)
DebugMan.disableDebugChannel("Script");
return true;
}
void Console::prepareGame() {
_engine->_graphics->clearScreen();
_engine->loadRoom(_engine->_state.room);
_engine->showRoom();
_engine->_display->renderText();
_engine->_display->renderGraphics();
}
bool Console::Cmd_Region(int argc, const char **argv) {
if (argc > 2) {
debugPrintf("Usage: %s [<new_region>]\n", argv[0]);
return true;
}
if (argc == 2) {
if (!_engine->_canRestoreNow) {
debugPrintf("Cannot change regions right now\n");
return true;
}
uint regionCount = _engine->_state.regions.size();
uint region = strtoul(argv[1], nullptr, 0);
if (region < 1 || region > regionCount) {
debugPrintf("Region %u out of valid range [1, %u]\n", region, regionCount);
return true;
}
_engine->switchRegion(region);
prepareGame();
}
debugPrintf("Current region: %u\n", _engine->_state.region);
return true;
}
bool Console::Cmd_Room(int argc, const char **argv) {
if (argc > 2) {
debugPrintf("Usage: %s [<new_room>]\n", argv[0]);
return true;
}
if (argc == 2) {
if (!_engine->_canRestoreNow) {
debugPrintf("Cannot change rooms right now\n");
return true;
}
uint roomCount = _engine->_state.rooms.size();
uint room = strtoul(argv[1], nullptr, 0);
if (room < 1 || room > roomCount) {
debugPrintf("Room %u out of valid range [1, %u]\n", room, roomCount);
return true;
}
_engine->switchRoom(room);
prepareGame();
}
debugPrintf("Current room: %u\n", _engine->_state.room);
return true;
}
bool Console::Cmd_Items(int argc, const char **argv) {
if (argc != 1) {
debugPrintf("Usage: %s\n", argv[0]);
return true;
}
for (const auto &item : _engine->_state.items)
printItem(item);
return true;
}
bool Console::Cmd_GiveItem(int argc, const char **argv) {
if (argc != 2) {
debugPrintf("Usage: %s <ID | name>\n", argv[0]);
return true;
}
char *end;
uint id = strtoul(argv[1], &end, 0);
if (*end != 0) {
Common::Array<Item *> matches;
Common::String name = toNative(argv[1]);
if (!_engine->_nouns.contains(name)) {
debugPrintf("Item '%s' not found\n", argv[1]);
return true;
}
byte noun = _engine->_nouns[name];
for (auto &item : _engine->_state.items) {
if (item.noun == noun)
matches.push_back(&item);
}
if (matches.size() == 0) {
debugPrintf("Item '%s' not found\n", argv[1]);
return true;
}
if (matches.size() > 1) {
debugPrintf("Multiple matches found, please use item ID:\n");
for (uint i = 0; i < matches.size(); ++i)
printItem(*matches[i]);
return true;
}
matches[0]->room = IDI_ANY;
debugPrintf("OK\n");
return true;
}
for (auto &item : _engine->_state.items)
if (item.id == id) {
item.room = IDI_ANY;
debugPrintf("OK\n");
return true;
}
debugPrintf("Item %i not found\n", id);
return true;
}
bool Console::Cmd_Vars(int argc, const char **argv) {
if (argc != 1) {
debugPrintf("Usage: %s\n", argv[0]);
return true;
}
Common::StringArray vars;
for (uint i = 0; i < _engine->_state.vars.size(); ++i)
vars.push_back(Common::String::format("%3d: %3d", i, _engine->_state.vars[i]));
debugPrintf("Variables:\n");
debugPrintColumns(vars);
return true;
}
bool Console::Cmd_Var(int argc, const char **argv) {
if (argc < 2 || argc > 3) {
debugPrintf("Usage: %s <index> [<value>]\n", argv[0]);
return true;
}
uint varCount = _engine->_state.vars.size();
uint var = strtoul(argv[1], nullptr, 0);
if (var >= varCount) {
debugPrintf("Variable %u out of valid range [0, %u]\n", var, varCount - 1);
return true;
}
if (argc == 3) {
uint value = strtoul(argv[2], nullptr, 0);
_engine->_state.vars[var] = value;
}
debugPrintf("%3d: %3d\n", var, _engine->_state.vars[var]);
return true;
}
void Console::printItem(const Item &item) {
Common::String name, desc, state;
if (item.noun > 0)
name = _engine->_priNouns[item.noun - 1];
desc = toAscii(_engine->getItemDescription(item));
if (desc.lastChar() == '\r')
desc.deleteLastChar();
switch (item.state) {
case IDI_ITEM_NOT_MOVED:
state = "PLACED";
break;
case IDI_ITEM_DROPPED:
state = "DROPPED";
break;
case IDI_ITEM_DOESNT_MOVE:
state = "FIXED";
break;
default:
state = "UNKNOWN";
break;
}
debugPrintf("%3d %s %-30s %-10s %-8s (%3d, %3d)\n", item.id, name.c_str(), desc.c_str(), _engine->itemRoomStr(item.room).c_str(), state.c_str(), item.position.x, item.position.y);
}
void Console::printWordMap(const WordMap &wordMap) {
Common::StringArray words;
for (const auto &verb : wordMap)
words.push_back(Common::String::format("%s: %3d", toAscii(verb._key).c_str(), wordMap[verb._key]));
Common::sort(words.begin(), words.end());
debugPrintColumns(words);
}
bool Console::Cmd_ConvertDisk(int argc, const char **argv) {
if (argc != 3) {
debugPrintf("Usage: %s <source> <dest>\n", argv[0]);
return true;
}
Common::DiskImage inDisk;
if (!inDisk.open(argv[1])) {
debugPrintf("Failed to open '%s' for reading\n", argv[1]);
return true;
}
Common::DumpFile outDisk;
if (!outDisk.open(argv[2])) {
debugPrintf("Failed to open '%s' for writing\n", argv[2]);
return true;
}
const uint sectors = inDisk.getTracks() * inDisk.getSectorsPerTrack();
const uint size = sectors * inDisk.getBytesPerSector();
byte *const buf = new byte[size];
Common::StreamPtr stream(inDisk.createReadStream(0, 0, 0, sectors - 1));
if (stream->read(buf, size) < size) {
debugPrintf("Failed to read from stream");
delete[] buf;
return true;
}
if (outDisk.write(buf, size) < size)
debugPrintf("Failed to write to '%s'", argv[2]);
delete[] buf;
return true;
}
bool Console::Cmd_RunScript(int argc, const char **argv) {
if (argc != 2) {
debugPrintf("Usage: %s <file>\n", argv[0]);
return true;
}
_engine->runScript(argv[1]);
return false;
}
bool Console::Cmd_StopScript(int argc, const char **argv) {
if (argc != 1) {
debugPrintf("Usage: %s\n", argv[0]);
return true;
}
_engine->stopScript();
return true;
}
bool Console::Cmd_SetScriptDelay(int argc, const char **argv) {
if (argc != 2) {
debugPrintf("Usage: %s <delay>\n", argv[0]);
debugPrintf("A delay of zero indicates wait-for-key\n");
return true;
}
Common::String value(argv[1]);
_engine->setScriptDelay((uint)value.asUint64());
return true;
}
} // End of namespace Adl

72
engines/adl/console.h Normal file
View File

@@ -0,0 +1,72 @@
/* 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 ADL_CONSOLE_H
#define ADL_CONSOLE_H
#include "gui/debugger.h"
#include "common/hashmap.h"
#include "common/path.h"
namespace Common {
class String;
}
namespace Adl {
class AdlEngine;
struct Item;
class Console : public GUI::Debugger {
public:
Console(AdlEngine *engine);
static Common::String toAscii(const Common::String &str);
Common::String toNative(const Common::String &str);
private:
bool Cmd_Nouns(int argc, const char **argv);
bool Cmd_Verbs(int argc, const char **argv);
bool Cmd_DumpScripts(int argc, const char **argv);
bool Cmd_ValidCommands(int argc, const char **argv);
bool Cmd_Region(int argc, const char **argv);
bool Cmd_Room(int argc, const char **argv);
bool Cmd_Items(int argc, const char **argv);
bool Cmd_GiveItem(int argc, const char **argv);
bool Cmd_Vars(int argc, const char **argv);
bool Cmd_Var(int argc, const char **argv);
bool Cmd_ConvertDisk(int argc, const char **argv);
bool Cmd_RunScript(int argc, const char **argv);
bool Cmd_StopScript(int argc, const char **argv);
bool Cmd_SetScriptDelay(int argc, const char **argv);
void printItem(const Item &item);
void printWordMap(const Common::HashMap<Common::String, uint> &wordMap);
void dumpScripts(const Common::Path &prefix = Common::Path());
void prepareGame();
AdlEngine *_engine;
};
} // End of namespace Adl
#endif

3
engines/adl/credits.pl Normal file
View File

@@ -0,0 +1,3 @@
begin_section("ADL");
add_person("Walter van Niftrik", "waltervn", "");
end_section();

613
engines/adl/detection.cpp Normal file
View File

@@ -0,0 +1,613 @@
/* 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 "common/file.h"
#include "common/md5.h"
#include "common/debug.h"
#include "engines/advancedDetector.h"
#include "adl/detection.h"
#include "adl/disk.h"
#include "adl/disk_image_helpers.h"
#include "adl/adl.h"
namespace Adl {
static const DebugChannelDef debugFlagList[] = {
{Adl::kDebugChannelScript, "Script", "Trace script execution"},
DEBUG_CHANNEL_END
};
#define DEFAULT_OPTIONS GUIO7(GAMEOPTION_NTSC, GAMEOPTION_COLOR_DEFAULT_ON, GAMEOPTION_MONO_TEXT, GAMEOPTION_SCANLINES, GAMEOPTION_APPLE2E_CURSOR, GUIO_NOMIDI, GAMEOPTION_TTS)
#define MH_OPTIONS GUIO7(GAMEOPTION_NTSC, GAMEOPTION_COLOR_DEFAULT_OFF, GAMEOPTION_MONO_TEXT, GAMEOPTION_SCANLINES, GAMEOPTION_APPLE2E_CURSOR, GUIO_NOMIDI, GAMEOPTION_TTS)
static const PlainGameDescriptor adlGames[] = {
{ "hires0", "Hi-Res Adventure #0: Mission Asteroid" },
{ "hires1", "Hi-Res Adventure #1: Mystery House" },
{ "hires2", "Hi-Res Adventure #2: Wizard and the Princess" },
{ "hires3", "Hi-Res Adventure #3: Cranston Manor" },
{ "hires4", "Hi-Res Adventure #4: Ulysses and the Golden Fleece" },
{ "hires5", "Hi-Res Adventure #5: Time Zone" },
{ "hires6", "Hi-Res Adventure #6: The Dark Crystal" },
{ nullptr, nullptr }
};
static const AdlGameDescription gameFileDescriptions[] = {
{ // Hi-Res Adventure #1: Mystery House - Apple II - Simi Valley
{
"hires1", "On-Line Systems [A]",
{
{ "ADVENTURE", 0, "22d9e63a11d69fa033ba1738715ad09a", 29952 },
{ "AUTO LOAD OBJ", 0, "a2ab7be25842e1fa9f1343b0894a8b6f", 4095 },
AD_LISTEND
},
Common::EN_ANY,
Common::kPlatformApple2,
ADGF_NO_FLAGS,
MH_OPTIONS
},
GAME_TYPE_HIRES1,
GAME_VER_HR1_SIMI
},
{ // Hi-Res Adventure #1: Mystery House - Apple II - Coarsegold - Without MIXEDON
{
"hires1", "On-Line Systems [B]",
{
{ "ADVENTURE", 0, "22d9e63a11d69fa033ba1738715ad09a", 29952 },
{ "AUTO LOAD OBJ", 0, "669b5f313ffdfb373ab8dce5961688d3", 12288 },
AD_LISTEND
},
Common::EN_ANY,
Common::kPlatformApple2,
ADGF_NO_FLAGS,
MH_OPTIONS
},
GAME_TYPE_HIRES1,
GAME_VER_HR1_COARSE
},
{ // Hi-Res Adventure #1: Mystery House - Apple II - French - Without MIXEDON
{
"hires1", "Malibu Microcomputing [A]",
{
{ "ADVENTURE", 0, "6e2245979871b44a9fec46b4b2ba590a", 29952 },
{ "AUTO LOAD OBJ", 0, "3d417e923e70abe9a82e51155974027d", 12288 },
AD_LISTEND
},
Common::FR_FRA,
Common::kPlatformApple2,
ADGF_NO_FLAGS,
MH_OPTIONS
},
GAME_TYPE_HIRES1,
GAME_VER_HR1_VF1
},
{ // Hi-Res Adventure #1: Mystery House - Apple II - French - Modified parser
{
"hires1", "Malibu Microcomputing [B]",
{
{ "ADVENTURE", 0, "f9a1add3609b4bc24b5dc4a9db1fec67", 29952 },
{ "AUTO LOAD OBJ", 0, "2a348058363da4c78a069ee5a2d81a31", 12287 },
AD_LISTEND
},
Common::FR_FRA,
Common::kPlatformApple2,
ADGF_NO_FLAGS,
MH_OPTIONS
},
GAME_TYPE_HIRES1,
GAME_VER_HR1_VF2
},
{ // Hi-Res Adventure #1: Mystery House - Apple II - Coarsegold - With MIXEDON
{
"hires1", "On-Line Systems [C]",
{
{ "ADVENTURE", 0, "22d9e63a11d69fa033ba1738715ad09a", 29952 },
{ "AUTO LOAD OBJ", 0, "f6a6ac60c04c6ba6dff68b92cc279ba2", 12291 },
AD_LISTEND
},
Common::EN_ANY,
Common::kPlatformApple2,
ADGF_NO_FLAGS,
MH_OPTIONS
},
GAME_TYPE_HIRES1,
GAME_VER_HR1_COARSE
},
{ // Hi-Res Adventure #1: Mystery House - Apple II - Public Domain
{
"hires1", "Public Domain",
{
{ "ADVENTURE", 0, "22d9e63a11d69fa033ba1738715ad09a", 29952 },
{ "AUTO LOAD OBJ", 0, "23bfccfe9fcff9b22cf6c41bde9078ac", 12291 },
AD_LISTEND
},
Common::EN_ANY,
Common::kPlatformApple2,
ADGF_NO_FLAGS,
MH_OPTIONS
},
GAME_TYPE_HIRES1,
GAME_VER_HR1_PD
},
{ AD_TABLE_END_MARKER, GAME_TYPE_NONE, GAME_VER_NONE }
};
static const AdlGameDescription gameDiskDescriptions[] = {
{ // Hi-Res Adventure #1: Mystery House - Apple II - Simi Valley - DOS 3.2 only
{
"hires1", "On-Line Systems [A]",
AD_ENTRY1s("mysthous", "629b9d034cbf8d8e3a612398f53a8dfc", 116480),
Common::EN_ANY,
Common::kPlatformApple2,
ADGF_NO_FLAGS,
MH_OPTIONS
},
GAME_TYPE_HIRES1,
GAME_VER_HR1_SIMI
},
{ // Hi-Res Adventure #1: Mystery House - Apple II - Coarsegold - Without MIXEDON
{
"hires1", "On-Line Systems [B]",
AD_ENTRY1s("mysthous", "b22561b5327c7dcdb659e2d649749310", 116480),
Common::EN_ANY,
Common::kPlatformApple2,
ADGF_NO_FLAGS,
MH_OPTIONS
},
GAME_TYPE_HIRES1,
GAME_VER_HR1_COARSE
},
{ // Hi-Res Adventure #1: Mystery House - Apple II - French - Modified parser
{
"hires1", "Malibu Microcomputing [B]",
AD_ENTRY1s("mysthous", "7bd1918ffc28e551e5b3baf610982bd3", 116480),
Common::FR_FRA,
Common::kPlatformApple2,
ADGF_NO_FLAGS,
MH_OPTIONS
},
GAME_TYPE_HIRES1,
GAME_VER_HR1_VF2
},
{ // Hi-Res Adventure #1: Mystery House - Apple II - Coarsegold - With MIXEDON
{
"hires1", "On-Line Systems [C]",
AD_ENTRY1s("mysthous", "8df0b3b3e609a2e40237e2419c1cb767", 116480),
Common::EN_ANY,
Common::kPlatformApple2,
ADGF_NO_FLAGS,
MH_OPTIONS
},
GAME_TYPE_HIRES1,
GAME_VER_HR1_COARSE
},
{ // Hi-Res Adventure #1: Mystery House - Apple II - Roberta Williams Anthology
{
"hires1", "Public Domain",
AD_ENTRY1s("mysthous", "54d20eb1ef0084ac3c2d16c31c5b7eb7", 143360),
Common::EN_ANY,
Common::kPlatformApple2,
ADGF_NO_FLAGS,
MH_OPTIONS
},
GAME_TYPE_HIRES1,
GAME_VER_HR1_PD
},
{ // Hi-Res Adventure #1: Mystery House - Apple II - Public Domain
{
"hires1", "Public Domain",
AD_ENTRY1s("mysthous", "bc0f34d153a530b9bb4e554b0a42f8d7", 143360),
Common::EN_ANY,
Common::kPlatformApple2,
ADGF_NO_FLAGS,
MH_OPTIONS
},
GAME_TYPE_HIRES1,
GAME_VER_HR1_PD
},
{ // Hi-Res Adventure #2: Wizard and the Princess - Apple II - DOS 3.2 only
{
"hires2", "On-Line Systems [A]",
AD_ENTRY1s("wizard", "5201c87db24ba7e3fa447471f4f2ec99", 116480),
Common::EN_ANY,
Common::kPlatformApple2,
ADGF_NO_FLAGS,
DEFAULT_OPTIONS
},
GAME_TYPE_HIRES2,
GAME_VER_NONE
},
{ // Hi-Res Adventure #2: Wizard and the Princess - Apple II
{
"hires2", "On-Line Systems [B]",
AD_ENTRY1s("wizard", "1de984859212ff11cf61b29cb9b55f8c", 116480),
Common::EN_ANY,
Common::kPlatformApple2,
ADGF_NO_FLAGS,
DEFAULT_OPTIONS
},
GAME_TYPE_HIRES2,
GAME_VER_NONE
},
{ // Hi-Res Adventure #2: Wizard and the Princess - Apple II - Green Valley Publishing
{
"hires2", "Green Valley [A]",
AD_ENTRY1s("wizard", "73cd373e9a2946c3181b72fdf7a32c77", 143360),
Common::EN_ANY,
Common::kPlatformApple2,
ADGF_NO_FLAGS,
DEFAULT_OPTIONS
},
GAME_TYPE_HIRES2,
GAME_VER_NONE
},
{ // Hi-Res Adventure #2: Wizard and the Princess - Apple II - Roberta Williams Anthology
{
"hires2", "Green Valley [B]",
AD_ENTRY1s("wizard", "72b114bf8f94fafe5672daac2a70c765", 143360),
Common::EN_ANY,
Common::kPlatformApple2,
ADGF_NO_FLAGS,
DEFAULT_OPTIONS
},
GAME_TYPE_HIRES2,
GAME_VER_NONE
},
{ // Hi-Res Adventure #0: Mission Asteroid - Apple II - Roberta Williams Anthology
{
"hires0", "",
AD_ENTRY1s("mission", "6bc53f51a3c8ee65c020af55fb8bd875", 116480),
Common::EN_ANY,
Common::kPlatformApple2,
ADGF_NO_FLAGS,
DEFAULT_OPTIONS
},
GAME_TYPE_HIRES0,
GAME_VER_NONE
},
{ // Hi-Res Adventure #3: Cranston Manor - Apple II
{
"hires3", "",
AD_ENTRY1s("cranston", "e4d35440791a36e55299c7be1ccd2b04", 116480),
Common::EN_ANY,
Common::kPlatformApple2,
ADGF_NO_FLAGS,
DEFAULT_OPTIONS
},
GAME_TYPE_HIRES3,
GAME_VER_NONE
},
{ // Hi-Res Adventure #4: Ulysses and the Golden Fleece - Apple II - Original release
{
"hires4", "On-Line Systems [A]",
{
{ "ulyssesa", 0, "fac225127a35cf2596d41e91647a532c", 143360 },
{ "ulyssesb", 1, "793a01392a094d5e2988deab5510e9fc", 143360 },
AD_LISTEND
},
Common::EN_ANY,
Common::kPlatformApple2,
ADGF_NO_FLAGS,
DEFAULT_OPTIONS
},
GAME_TYPE_HIRES4,
GAME_VER_HR4_V1_0
},
{ // Hi-Res Adventure #4: Ulysses and the Golden Fleece - Apple II - Version 1.1
{
"hires4", "On-Line Systems [B]",
{
{ "ulyssesa", 0, "420f515e64612d21446ede8078055f0e", 143360 },
{ "ulyssesb", 1, "9fa8552255ae651b252844168b8b6617", 143360 },
AD_LISTEND
},
Common::EN_ANY,
Common::kPlatformApple2,
ADGF_NO_FLAGS,
DEFAULT_OPTIONS
},
GAME_TYPE_HIRES4,
GAME_VER_HR4_V1_1
},
{ // Hi-Res Adventure #4: Ulysses and the Golden Fleece - Apple II - Green Valley Publishing - Version 0.0
{
"hires4", "Green Valley [A]",
{
{ "ulyssesa", 0, "1eaeb2f1a773ce2d1cb9f16b2ef09049", 143360 },
{ "ulyssesb", 1, "9fa8552255ae651b252844168b8b6617", 143360 },
AD_LISTEND
},
Common::EN_ANY,
Common::kPlatformApple2,
ADGF_NO_FLAGS,
DEFAULT_OPTIONS
},
GAME_TYPE_HIRES4,
GAME_VER_HR4_LNG
},
{ // Hi-Res Adventure #4: Ulysses and the Golden Fleece - Apple II - Green Valley Publishing - Version 1.1
{
"hires4", "Green Valley [B]",
{
{ "ulyssesa", 0, "35b6dce492c893327796645f481737ca", 143360 },
{ "ulyssesb", 1, "9fa8552255ae651b252844168b8b6617", 143360 },
AD_LISTEND
},
Common::EN_ANY,
Common::kPlatformApple2,
ADGF_NO_FLAGS,
DEFAULT_OPTIONS
},
GAME_TYPE_HIRES4,
GAME_VER_HR4_LNG
},
{ // Hi-Res Adventure #4: Ulysses and the Golden Fleece - Atari 8-bit - Re-release
{
"hires4", "",
{
{ "ulys1b", 0, "bb6aab9a35b41d160b6eefa088165f56", 92160 },
{ "ulys1a", 0, "c227eeee34d0bacd62b2d6231c409204", 92160 },
// Load 'N' Go Software release XAG-0646 appears to be missing the second disk
{ "ulys2c", 0, "8c6a76d1767e4ffa2f0118c9c56c0e90", 92160 },
AD_LISTEND
},
Common::EN_ANY,
Common::kPlatformAtari8Bit,
ADGF_UNSTABLE,
GUIO2(GAMEOPTION_COLOR_DEFAULT_ON, GAMEOPTION_SCANLINES)
},
GAME_TYPE_HIRES4,
GAME_VER_NONE
},
{ // Hi-Res Adventure #5: Time Zone - Apple II - On-Line Systems - Version 1.1
{
"hires5", "On-Line Systems",
{
{"tzone1a", 2, "6c1f4c9f774dbd9697e3b5b1fe2fb858", 143360},
{"tzone1b", 3, "4eaf8d790e3f93097cca9ddbe863df50", 143360},
{"tzone2c", 4, "e3aa4f56e727339b1ec00978ce9d435b", 143360},
{"tzone2d", 5, "77b8219a380410015c986fa192d4c3bf", 143360},
{"tzone3e", 6, "f7acc03edd8d8aecb90711cd5f9e5593", 143360},
{"tzone3f", 7, "ed74c056976ecea2eab07448c8a72eb8", 143360},
{"tzone4g", 8, "de7bda8a641169fc2dedd8a7b0b7e7de", 143360},
{"tzone4h", 9, "21cf76d97505ff09fff5d5e4711bc47c", 143360},
{"tzone5i", 10, "d665df374e594cd0978b73c3490e5de2", 143360},
{"tzone5j", 11, "5095be23d13201d0897b9169c4e473df", 143360},
{"tzone6k", 12, "bef044503f21af5f0a4088e99aa778b1", 143360},
{"tzone6l", 13, "84801b7c2ab6c09e62a2a0809b94d16a", 143360},
AD_LISTEND
},
Common::EN_ANY,
Common::kPlatformApple2,
ADGF_NO_FLAGS,
DEFAULT_OPTIONS
},
GAME_TYPE_HIRES5,
GAME_VER_NONE
},
{ // Hi-Res Adventure #5: Time Zone - Apple II - Roberta Williams Anthology - Version 1.1
{
"hires5", "Sierra On-Line",
{
{ "tzone1a", 2, "731844b1d19c2801e3a5bc61d109af54", 143360 },
{ "tzone1b", 3, "4eaf8d790e3f93097cca9ddbe863df50", 143360 },
{ "tzone2c", 4, "e3aa4f56e727339b1ec00978ce9d435b", 143360 },
{ "tzone2d", 5, "77b8219a380410015c986fa192d4c3bf", 143360 },
{ "tzone3e", 6, "f7acc03edd8d8aecb90711cd5f9e5593", 143360 },
{ "tzone3f", 7, "ed74c056976ecea2eab07448c8a72eb8", 143360 },
{ "tzone4g", 8, "de7bda8a641169fc2dedd8a7b0b7e7de", 143360 },
{ "tzone4h", 9, "21cf76d97505ff09fff5d5e4711bc47c", 143360 },
{ "tzone5i", 10, "d665df374e594cd0978b73c3490e5de2", 143360 },
{ "tzone5j", 11, "5095be23d13201d0897b9169c4e473df", 143360 },
{ "tzone6k", 12, "bef044503f21af5f0a4088e99aa778b1", 143360 },
{ "tzone6l", 13, "84801b7c2ab6c09e62a2a0809b94d16a", 143360 },
AD_LISTEND
},
Common::EN_ANY,
Common::kPlatformApple2,
ADGF_NO_FLAGS,
DEFAULT_OPTIONS
},
GAME_TYPE_HIRES5,
GAME_VER_NONE
},
{ // Hi-Res Adventure #6: The Dark Crystal - Apple II - Roberta Williams Anthology / SierraVenture
{
"hires6", "SierraVenture [A]",
{
{ "dark1a", 0, "9a5968a8f378c84454d88f4cd4e143a9", 143360 },
{ "dark1b", 3, "1271ff9c3e1bdb4942301dd37dd0ef87", 143360 },
{ "dark2a", 4, "090e77563add7b4c9ab25f444d727316", 143360 },
{ "dark2b", 5, "f2db96af0955324900b800505af4d91f", 143360 },
AD_LISTEND
},
Common::EN_ANY,
Common::kPlatformApple2,
ADGF_NO_FLAGS,
DEFAULT_OPTIONS
},
GAME_TYPE_HIRES6,
GAME_VER_NONE
},
{ // Hi-Res Adventure #6: The Dark Crystal - Apple II - SierraVenture
{
"hires6", "SierraVenture [B]",
{
{ "dark1a", 0, "d0b8e808b02564b6ce58b5ea5cc61ead", 143360 },
{ "dark1b", 3, "1271ff9c3e1bdb4942301dd37dd0ef87", 143360 },
{ "dark2a", 4, "090e77563add7b4c9ab25f444d727316", 143360 },
{ "dark2b", 5, "f2db96af0955324900b800505af4d91f", 143360 },
AD_LISTEND
},
Common::EN_ANY,
Common::kPlatformApple2,
ADGF_NO_FLAGS,
DEFAULT_OPTIONS
},
GAME_TYPE_HIRES6,
GAME_VER_NONE
},
{ AD_TABLE_END_MARKER, GAME_TYPE_NONE, GAME_VER_NONE }
};
class AdlMetaEngineDetection : public AdvancedMetaEngineDetection<AdlGameDescription> {
public:
AdlMetaEngineDetection() : AdvancedMetaEngineDetection(gameFileDescriptions, adlGames) { }
const char *getEngineName() const override {
return "ADL";
}
const char *getName() const override {
return "adl";
}
const char *getOriginalCopyright() const override {
return "Copyright (C) Sierra On-Line";
}
const DebugChannelDef *getDebugChannels() const override {
return debugFlagList;
}
ADDetectedGames detectGame(const Common::FSNode &parent, const FileMap &allFiles, Common::Language language, Common::Platform platform, const Common::String &extra, uint32 skipADFlags, bool skipIncomplete) override;
bool addFileProps(const FileMap &allFiles, const Common::Path &fname, FilePropertiesMap &filePropsMap) const;
};
bool AdlMetaEngineDetection::addFileProps(const FileMap &allFiles, const Common::Path &fname, FilePropertiesMap &filePropsMap) const {
if (filePropsMap.contains(fname))
return true;
if (!allFiles.contains(fname))
return false;
FileProperties fileProps;
fileProps.size = computeMD5(allFiles[fname], fileProps.md5, 16384);
if (fileProps.size != -1) {
debugC(3, kDebugGlobalDetection, "> '%s': '%s'", fname.toString().c_str(), fileProps.md5.c_str());
filePropsMap[fname] = fileProps;
}
return true;
}
// Based on AdvancedMetaEngine::detectGame
ADDetectedGames AdlMetaEngineDetection::detectGame(const Common::FSNode &parent, const FileMap &allFiles, Common::Language language, Common::Platform platform, const Common::String &extra, uint32 skipADFlags, bool skipIncomplete) {
// We run the file-based detector first, if it finds a match we do not search for disk images
ADDetectedGames matched = AdvancedMetaEngineDetection::detectGame(parent, allFiles, language, platform, extra, skipADFlags, skipIncomplete);
if (!matched.empty())
return matched;
debugC(3, kDebugGlobalDetection, "Starting disk image detection in dir '%s'", parent.getPath().toString(Common::Path::kNativeSeparator).c_str());
FilePropertiesMap filesProps;
for (uint g = 0; gameDiskDescriptions[g].desc.gameId != nullptr; ++g) {
ADDetectedGame game(&gameDiskDescriptions[g].desc);
// Skip games that don't meet the language/platform/extra criteria
if (language != Common::UNK_LANG && game.desc->language != Common::UNK_LANG) {
if (game.desc->language != language && !(language == Common::EN_ANY && (game.desc->flags & ADGF_ADDENGLISH)))
continue;
}
if (platform != Common::kPlatformUnknown && game.desc->platform != Common::kPlatformUnknown && game.desc->platform != platform)
continue;
if ((_flags & kADFlagUseExtraAsHint) && !extra.empty() && game.desc->extra != extra)
continue;
bool allFilesPresent = true;
for (uint f = 0; game.desc->filesDescriptions[f].fileName; ++f) {
const ADGameFileDescription &fDesc = game.desc->filesDescriptions[f];
Common::Path fileName;
bool foundDiskImage = false;
for (uint e = 0; e < ARRAYSIZE(diskImageExts); ++e) {
if (diskImageExts[e].platform == game.desc->platform) {
Common::Path testFileName(fDesc.fileName, Common::Path::kNoSeparator);
testFileName.appendInPlace(diskImageExts[e].extension);
if (addFileProps(allFiles, testFileName, filesProps)) {
if (foundDiskImage) {
warning("Ignoring '%s' (already found '%s')", testFileName.toString().c_str(), fileName.toString().c_str());
filesProps.erase(testFileName);
} else {
foundDiskImage = true;
fileName = testFileName;
}
}
}
}
if (!foundDiskImage) {
allFilesPresent = false;
break;
}
game.matchedFiles[fileName] = filesProps[fileName];
if (game.hasUnknownFiles)
continue;
if (fDesc.md5 && fDesc.md5 != filesProps[fileName].md5) {
debugC(3, kDebugGlobalDetection, "MD5 Mismatch. Skipping (%s) (%s)", fDesc.md5, filesProps[fileName].md5.c_str());
game.hasUnknownFiles = true;
continue;
}
if (fDesc.fileSize != AD_NO_SIZE && fDesc.fileSize != filesProps[fileName].size) {
debugC(3, kDebugGlobalDetection, "Size Mismatch. Skipping");
game.hasUnknownFiles = true;
continue;
}
debugC(3, kDebugGlobalDetection, "Matched file: %s", fileName.toString().c_str());
}
// This assumes that the detection table groups together games that have the same gameId and platform
if (allFilesPresent) {
if (!game.hasUnknownFiles) {
debugC(2, kDebugGlobalDetection, "Found game: %s (%s/%s) (%d)", game.desc->gameId, getPlatformDescription(game.desc->platform), getLanguageDescription(game.desc->language), g);
// If we just added an unknown variant for this game and platform, remove it
if (!matched.empty() && strcmp(matched.back().desc->gameId, game.desc->gameId) == 0 && matched.back().desc->platform == game.desc->platform)
matched.pop_back();
matched.push_back(game);
} else {
debugC(5, kDebugGlobalDetection, "Skipping game: %s (%s/%s) (%d)", game.desc->gameId, getPlatformDescription(game.desc->platform), getLanguageDescription(game.desc->language), g);
// If we already added a known or unknown variant for this game and platform, don't add another
if (matched.empty() || strcmp(matched.back().desc->gameId, game.desc->gameId) != 0 || matched.back().desc->platform != game.desc->platform)
matched.push_back(game);
}
}
}
return matched;
}
} // End of namespace Adl
REGISTER_PLUGIN_STATIC(ADL_DETECTION, PLUGIN_TYPE_ENGINE_DETECTION, Adl::AdlMetaEngineDetection);

91
engines/adl/detection.h Normal file
View File

@@ -0,0 +1,91 @@
/* 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 ADL_DETECTION_H
#define ADL_DETECTION_H
#include "engines/advancedDetector.h"
namespace Adl {
#define SAVEGAME_VERSION 0
#define SAVEGAME_NAME_LEN 32
enum GameType {
GAME_TYPE_NONE,
GAME_TYPE_HIRES0,
GAME_TYPE_HIRES1,
GAME_TYPE_HIRES2,
GAME_TYPE_HIRES3,
GAME_TYPE_HIRES4,
GAME_TYPE_HIRES5,
GAME_TYPE_HIRES6
};
/*
* ====== Mystery House supported versions ======
* GAME_VER_HR1_SIMI:
* - Instructions always shown (no prompt)
* - Instructions contain Simi Valley address
* - On-Line Systems title screen in main executable only and waits for key
* GAME_VER_HR1_COARSE:
* - Longer instructions, now containing Coarsegold address
* - On-Line Systems title screen with instructions prompt
* GAME_VER_HR1_PD:
* - Public Domain disclaimer on startup
* - Sierra On-Line title screen with instructions prompt
*
* Note: there are probably at least two or three more variants
*/
enum GameVersion {
GAME_VER_NONE = 0,
GAME_VER_HR1_SIMI = 0,
GAME_VER_HR1_COARSE,
GAME_VER_HR1_VF1,
GAME_VER_HR1_VF2,
GAME_VER_HR1_PD,
GAME_VER_HR4_V1_0,
GAME_VER_HR4_V1_1,
GAME_VER_HR4_LNG
};
struct AdlGameDescription {
AD_GAME_DESCRIPTION_HELPERS(desc);
ADGameDescription desc;
GameType gameType;
GameVersion version;
};
// Mystery House was designed for monochrome display, so we default to
// monochrome mode there. All the other games default to color mode.
#define GAMEOPTION_COLOR_DEFAULT_OFF GUIO_GAMEOPTIONS1
#define GAMEOPTION_SCANLINES GUIO_GAMEOPTIONS2
#define GAMEOPTION_COLOR_DEFAULT_ON GUIO_GAMEOPTIONS3
#define GAMEOPTION_NTSC GUIO_GAMEOPTIONS4
#define GAMEOPTION_MONO_TEXT GUIO_GAMEOPTIONS5
#define GAMEOPTION_APPLE2E_CURSOR GUIO_GAMEOPTIONS6
#define GAMEOPTION_TTS GUIO_GAMEOPTIONS7
} // End of namespace Adl
#endif // ADL_DETECTION_H

85
engines/adl/disk.cpp Normal file
View File

@@ -0,0 +1,85 @@
/* 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 "common/stream.h"
#include "common/substream.h"
#include "common/memstream.h"
#include "common/formats/disk_image.h"
#include "adl/disk.h"
namespace Adl {
void DataBlock_PC::read(Common::SeekableReadStream &stream, byte *const dataPtr, const uint32 size) const {
uint32 ofs = 0;
while (ofs < size) {
const uint bps = _disk->getBytesPerSector();
uint bytesToRead = bps - ((_offset + stream.pos()) % bps);
if (bytesToRead == bps) {
stream.readByte(); // Skip volume byte
--bytesToRead;
}
if (bytesToRead > size - ofs)
bytesToRead = size - ofs;
if (stream.read(dataPtr + ofs, bytesToRead) < bytesToRead)
error("Failed to read data block");
ofs += bytesToRead;
}
}
Common::SeekableReadStream *DataBlock_PC::createReadStream() const {
const uint bps = _disk->getBytesPerSector();
uint sectors = 0;
// Every data sector starts with a volume byte that we need to skip,
// so we need to take that into account during our computations here
if (_offset == bps - 1)
sectors = 1;
Common::StreamPtr diskStream(_disk->createReadStream(_track, _sector, _offset, sectors));
byte sizeBuf[2];
read(*diskStream, sizeBuf, 2);
uint16 blockSize = READ_LE_UINT16(sizeBuf);
sectors = 0;
const uint16 remSize = _disk->getBytesPerSector() - MAX<uint>(_offset, 1);
if (blockSize + 2 > remSize)
sectors = (blockSize + 2 - remSize - 1) / (_disk->getBytesPerSector() - 1) + 1;
diskStream.reset(_disk->createReadStream(_track, _sector, _offset, sectors));
read(*diskStream, sizeBuf, 2);
byte *buf = static_cast<byte *>(malloc(blockSize));
read(*diskStream, buf, blockSize);
return new Common::MemoryReadStream(buf, blockSize, DisposeAfterUse::YES);
}
} // End of namespace Common

65
engines/adl/disk.h Normal file
View File

@@ -0,0 +1,65 @@
/* 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 "common/ptr.h"
#include "common/file.h"
#include "common/debug.h"
#include "common/formats/disk_image.h"
#ifndef ADL_DISK_H
#define ADL_DISK_H
namespace Common {
class SeekableReadStream;
class String;
}
namespace Adl {
// On the Apple II, sector headers contain a disk volume number. This number
// is used by ADL multi-disk games. The PC port has the disk volume number
// as the first data byte of every sector that contains game data. We need
// to skip this number as we read in the data. Additionally, the data is now
// prefixed with an uint16 containing the data size.
class DataBlock_PC : public Common::DataBlock {
public:
DataBlock_PC(Common::DiskImage *disk, byte track, byte sector, uint16 offset = 0) :
_disk(disk),
_track(track),
_sector(sector),
_offset(offset) { }
~DataBlock_PC() override { }
Common::SeekableReadStream *createReadStream() const override;
private:
void read(Common::SeekableReadStream &stream, byte *const dataPtr, const uint32 size) const;
Common::DiskImage *_disk;
byte _track, _sector;
uint16 _offset;
};
} // End of namespace Adl
#endif

View File

@@ -0,0 +1,43 @@
/* 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 ADL_DISK_IMAGE_HELPERS_H
#define ADL_DISK_IMAGE_HELPERS_H
namespace Adl {
struct DiskImageExt {
Common::Platform platform;
const char *extension;
};
const DiskImageExt diskImageExts[] = {
{ Common::kPlatformApple2, ".woz" },
{ Common::kPlatformApple2, ".nib" },
{ Common::kPlatformApple2, ".dsk" },
{ Common::kPlatformApple2, ".d13" },
{ Common::kPlatformAtari8Bit, ".xfd" },
{ Common::kPlatformDOS, ".img" }
};
} // End of namespace Adl
#endif // ADL_DISK_IMAGE_HELPERS_H

137
engines/adl/display.cpp Normal file
View File

@@ -0,0 +1,137 @@
/* 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 "common/config-manager.h"
#include "common/debug.h"
#include "common/rect.h"
#include "common/str.h"
#include "common/system.h"
#include "adl/display.h"
namespace Adl {
Display::~Display() {
delete[] _textBuf;
}
void Display::createTextBuffer(uint textWidth, uint textHeight) {
_textWidth = textWidth;
_textHeight = textHeight;
_textBuf = new byte[textWidth * textHeight];
memset(_textBuf, (byte)asciiToNative(' '), textWidth * textHeight);
}
void Display::setMode(Display::Mode mode) {
_mode = mode;
if (_mode == Display::kModeText || _mode == Display::kModeMixed)
renderText();
if (_mode == Display::kModeGraphics || _mode == Display::kModeMixed)
renderGraphics();
}
void Display::home() {
memset(_textBuf, (byte)asciiToNative(' '), _textWidth * _textHeight);
_cursorPos = 0;
}
void Display::moveCursorForward() {
++_cursorPos;
if (_cursorPos >= _textWidth * _textHeight)
scrollUp();
}
void Display::moveCursorBackward() {
if (_cursorPos > 0)
--_cursorPos;
}
void Display::moveCursorTo(const Common::Point &pos) {
_cursorPos = pos.y * _textWidth + pos.x;
if (_cursorPos >= _textWidth * _textHeight)
error("Cursor position (%i, %i) out of bounds", pos.x, pos.y);
}
void Display::printString(const Common::String &str, bool voiceString) {
for (const auto &c : str)
printChar(c);
if (voiceString) {
sayText(str);
}
renderText();
}
void Display::printAsciiString(const Common::String &str, bool voiceString) {
for (const auto &c : str)
printChar(asciiToNative(c));
if (voiceString) {
sayText(str);
}
renderText();
}
void Display::sayText(const Common::String &text, Common::TextToSpeechManager::Action action) const {
Common::TextToSpeechManager *ttsMan = g_system->getTextToSpeechManager();
if (ttsMan != nullptr && ConfMan.getBool("tts_enabled")) {
ttsMan->say(convertText(text), action);
}
}
Common::U32String Display::convertText(const Common::String &text) const {
Common::String result(text);
bool startsWithDash = result[0] == '\xad';
for (uint i = 0; i < result.size(); i++) {
// Convert carriage returns and dashes to spaces
// Only convert dashes if the line starts with a dash (which is usually the case with the "enter command"
// prompt), to avoid converting mid-line dashes to spaces and causing odd voicing
// (i.e. if the dash in "Hi-Res Adventure" is replaced, English TTS pronounces it as "Hi Residential Adventure")
if (result[i] == '\x8d' || (startsWithDash && result[i] == '\xad')) {
result[i] = '\xa0';
}
result[i] &= 0x7f;
}
return Common::U32String(result, Common::CodePage::kASCII);
}
void Display::setCharAtCursor(byte c) {
_textBuf[_cursorPos] = c;
}
void Display::scrollUp() {
memmove(_textBuf, _textBuf + _textWidth, (_textHeight - 1) * _textWidth);
memset(_textBuf + (_textHeight - 1) * _textWidth, asciiToNative(' '), _textWidth);
if (_cursorPos >= _textWidth)
_cursorPos -= _textWidth;
}
} // End of namespace Adl

80
engines/adl/display.h Normal file
View File

@@ -0,0 +1,80 @@
/* 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 ADL_DISPLAY_H
#define ADL_DISPLAY_H
#include "common/text-to-speech.h"
#include "common/types.h"
namespace Common {
class String;
struct Point;
}
namespace Adl {
class Display {
public:
enum Mode {
kModeGraphics,
kModeText,
kModeMixed
};
virtual ~Display();
virtual void init() = 0;
void setMode(Mode mode);
virtual void renderText() = 0;
virtual void renderGraphics() = 0;
virtual char asciiToNative(char c) const = 0;
virtual void printChar(char c) = 0;
virtual void showCursor(bool enable) = 0;
void home();
void moveCursorTo(const Common::Point &pos);
void moveCursorForward();
void moveCursorBackward();
void printString(const Common::String &str, bool voiceString = true);
void printAsciiString(const Common::String &str, bool voiceString = true);
void sayText(const Common::String &text, Common::TextToSpeechManager::Action action = Common::TextToSpeechManager::QUEUE) const;
Common::U32String convertText(const Common::String &text) const;
void setCharAtCursor(byte c);
uint getTextWidth() const { return _textWidth; }
uint getTextHeight() const { return _textHeight; }
void scrollUp();
protected:
Display() : _textBuf(nullptr), _cursorPos(0), _mode(kModeText), _textWidth(0), _textHeight(0) { }
void createTextBuffer(uint textWidth, uint textHeight);
byte *_textBuf;
uint _cursorPos;
Mode _mode;
uint _textWidth;
uint _textHeight;
};
} // End of namespace Adl
#endif

668
engines/adl/display_a2.cpp Normal file
View File

@@ -0,0 +1,668 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
// Based on AppleWin's code for NTSC emulation and its RGB Monitor palette
// Copyright (C) 2010-2011, William S Simms
// Copyright (C) 2014-2016, Michael Pohoreski, Tom Charlesworth
// Licensed under GPLv2+
#include "common/stream.h"
#include "common/rect.h"
#include "common/system.h"
#include "common/str.h"
#include "common/config-manager.h"
#include "common/memstream.h"
#include "graphics/surface.h"
#include "graphics/thumbnail.h"
#include "math/utils.h"
#include "engines/util.h"
#include "adl/display_a2.h"
#include "adl/adl.h"
namespace Adl {
#define NTSC_REMOVE_BLACK_GHOSTING
// #define NTSC_REMOVE_WHITE_RINGING
// Uppercase-only Apple II font (manually created).
const byte Display_A2::_font[64][8] = {
{ 0x00, 0x1c, 0x22, 0x2a, 0x3a, 0x1a, 0x02, 0x3c }, { 0x00, 0x08, 0x14, 0x22, 0x22, 0x3e, 0x22, 0x22 }, // @A
{ 0x00, 0x1e, 0x22, 0x22, 0x1e, 0x22, 0x22, 0x1e }, { 0x00, 0x1c, 0x22, 0x02, 0x02, 0x02, 0x22, 0x1c }, // BC
{ 0x00, 0x1e, 0x22, 0x22, 0x22, 0x22, 0x22, 0x1e }, { 0x00, 0x3e, 0x02, 0x02, 0x1e, 0x02, 0x02, 0x3e }, // DE
{ 0x00, 0x3e, 0x02, 0x02, 0x1e, 0x02, 0x02, 0x02 }, { 0x00, 0x3c, 0x02, 0x02, 0x02, 0x32, 0x22, 0x3c }, // FG
{ 0x00, 0x22, 0x22, 0x22, 0x3e, 0x22, 0x22, 0x22 }, { 0x00, 0x1c, 0x08, 0x08, 0x08, 0x08, 0x08, 0x1c }, // HI
{ 0x00, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x1c }, { 0x00, 0x22, 0x12, 0x0a, 0x06, 0x0a, 0x12, 0x22 }, // JK
{ 0x00, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x3e }, { 0x00, 0x22, 0x36, 0x2a, 0x2a, 0x22, 0x22, 0x22 }, // LM
{ 0x00, 0x22, 0x22, 0x26, 0x2a, 0x32, 0x22, 0x22 }, { 0x00, 0x1c, 0x22, 0x22, 0x22, 0x22, 0x22, 0x1c }, // NO
{ 0x00, 0x1e, 0x22, 0x22, 0x1e, 0x02, 0x02, 0x02 }, { 0x00, 0x1c, 0x22, 0x22, 0x22, 0x2a, 0x12, 0x2c }, // PQ
{ 0x00, 0x1e, 0x22, 0x22, 0x1e, 0x0a, 0x12, 0x22 }, { 0x00, 0x1c, 0x22, 0x02, 0x1c, 0x20, 0x22, 0x1c }, // RS
{ 0x00, 0x3e, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08 }, { 0x00, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x1c }, // TU
{ 0x00, 0x22, 0x22, 0x22, 0x22, 0x22, 0x14, 0x08 }, { 0x00, 0x22, 0x22, 0x22, 0x2a, 0x2a, 0x36, 0x22 }, // VW
{ 0x00, 0x22, 0x22, 0x14, 0x08, 0x14, 0x22, 0x22 }, { 0x00, 0x22, 0x22, 0x14, 0x08, 0x08, 0x08, 0x08 }, // XY
{ 0x00, 0x3e, 0x20, 0x10, 0x08, 0x04, 0x02, 0x3e }, { 0x00, 0x3e, 0x06, 0x06, 0x06, 0x06, 0x06, 0x3e }, // Z[
{ 0x00, 0x00, 0x02, 0x04, 0x08, 0x10, 0x20, 0x00 }, { 0x00, 0x3e, 0x30, 0x30, 0x30, 0x30, 0x30, 0x3e }, // \]
{ 0x00, 0x00, 0x00, 0x08, 0x14, 0x22, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3e }, // ^_
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x08, 0x08, 0x08, 0x08, 0x08, 0x00, 0x08 }, // !
{ 0x00, 0x14, 0x14, 0x14, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x14, 0x14, 0x3e, 0x14, 0x3e, 0x14, 0x14 }, // "#
{ 0x00, 0x08, 0x3c, 0x0a, 0x1c, 0x28, 0x1e, 0x08 }, { 0x00, 0x06, 0x26, 0x10, 0x08, 0x04, 0x32, 0x30 }, // $%
{ 0x00, 0x04, 0x0a, 0x0a, 0x04, 0x2a, 0x12, 0x2c }, { 0x00, 0x08, 0x08, 0x08, 0x00, 0x00, 0x00, 0x00 }, // &'
{ 0x00, 0x08, 0x04, 0x02, 0x02, 0x02, 0x04, 0x08 }, { 0x00, 0x08, 0x10, 0x20, 0x20, 0x20, 0x10, 0x08 }, // ()
{ 0x00, 0x08, 0x2a, 0x1c, 0x08, 0x1c, 0x2a, 0x08 }, { 0x00, 0x00, 0x08, 0x08, 0x3e, 0x08, 0x08, 0x00 }, // *+
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x08, 0x04 }, { 0x00, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x00, 0x00 }, // ,-
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08 }, { 0x00, 0x00, 0x20, 0x10, 0x08, 0x04, 0x02, 0x00 }, // ./
{ 0x00, 0x1c, 0x22, 0x32, 0x2a, 0x26, 0x22, 0x1c }, { 0x00, 0x08, 0x0c, 0x08, 0x08, 0x08, 0x08, 0x1c }, // 01
{ 0x00, 0x1c, 0x22, 0x20, 0x18, 0x04, 0x02, 0x3e }, { 0x00, 0x3e, 0x20, 0x10, 0x18, 0x20, 0x22, 0x1c }, // 23
{ 0x00, 0x10, 0x18, 0x14, 0x12, 0x3e, 0x10, 0x10 }, { 0x00, 0x3e, 0x02, 0x1e, 0x20, 0x20, 0x22, 0x1c }, // 45
{ 0x00, 0x38, 0x04, 0x02, 0x1e, 0x22, 0x22, 0x1c }, { 0x00, 0x3e, 0x20, 0x10, 0x08, 0x04, 0x04, 0x04 }, // 67
{ 0x00, 0x1c, 0x22, 0x22, 0x1c, 0x22, 0x22, 0x1c }, { 0x00, 0x1c, 0x22, 0x22, 0x3c, 0x20, 0x10, 0x0e }, // 89
{ 0x00, 0x00, 0x00, 0x08, 0x00, 0x08, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x08, 0x00, 0x08, 0x08, 0x04 }, // :;
{ 0x00, 0x10, 0x08, 0x04, 0x02, 0x04, 0x08, 0x10 }, { 0x00, 0x00, 0x00, 0x3e, 0x00, 0x3e, 0x00, 0x00 }, // <=
{ 0x00, 0x04, 0x08, 0x10, 0x20, 0x10, 0x08, 0x04 }, { 0x00, 0x1c, 0x22, 0x10, 0x08, 0x08, 0x00, 0x08 } // >?
};
struct LineDoubleBright {
static uint8 blend(uint8 c1, uint8 c2) {
return c1;
}
};
struct LineDoubleDim {
static uint8 blend(uint8 c1, uint8 c2) {
return (c1 >> 1) + (c1 >> 2);
}
};
struct BlendBright {
static uint8 blend(uint8 c1, uint8 c2) {
return (c1 + c2) >> 1;
}
};
struct BlendDim {
static uint8 blend(uint8 c1, uint8 c2) {
// AppleWin does c1 >>= 2; return (c1 < c2 ? c2 - c1 : 0);
// I think the following looks a lot better:
return ((c1 + c2) >> 2) + ((c1 + c2) >> 3);
}
};
static const uint kColorPhases = 4;
// All PixelWriters have been adjusted to have 3 pixels of "pre-render" that
// will be cut off when blitting to the screen
static const uint kPreRender = 3;
template<typename ColorType, typename T>
class PixelWriter {
public:
PixelWriter() : _ptr(nullptr), _format(g_system->getScreenFormat()), _phase(0), _window(0) { }
void setupWrite(ColorType *dest) {
_ptr = dest;
_phase = 3;
_window = 0;
}
void writePixels(uint bits) {
for (uint b = 0; b < 14; ++b) {
_window <<= 1;
_window |= bits & 1;
bits >>= 1;
*_ptr++ = static_cast<T *>(this)->getColor();
_phase = (_phase + 1) & 3;
}
}
protected:
ColorType *_ptr;
Graphics::PixelFormat _format;
uint _phase;
uint _window;
};
template<typename ColorType>
class PixelWriterColor : public PixelWriter<ColorType, PixelWriterColor<ColorType> > {
public:
static const uint kColors = 16;
typedef LineDoubleBright BlendRegular;
typedef LineDoubleDim BlendScanlines;
PixelWriterColor() {
const byte palette[kColors][3] = {
{ 0x00, 0x00, 0x00 }, { 0x9d, 0x09, 0x66 }, { 0x2a, 0x2a, 0xe5 }, { 0xc7, 0x34, 0xff },
{ 0x00, 0x80, 0x00 }, { 0x80, 0x80, 0x80 }, { 0x0d, 0xa1, 0xff }, { 0xaa, 0xaa, 0xff },
{ 0x55, 0x55, 0x00 }, { 0xf2, 0x5e, 0x00 }, { 0xc0, 0xc0, 0xc0 }, { 0xff, 0x89, 0xe5 },
{ 0x38, 0xcb, 0x00 }, { 0xd5, 0xd5, 0x1a }, { 0x62, 0xf6, 0x99 }, { 0xff, 0xff, 0xff }
};
for (uint pattern = 0; pattern < kColors; ++pattern) {
uint color = ((pattern & 1) << 3) | ((pattern & 2) << 1) | ((pattern & 4) >> 1) | ((pattern & 8) >> 3);
for (uint phase = 0; phase < kColorPhases; ++phase) {
_colors[phase][pattern] = this->_format.RGBToColor(palette[color][0], palette[color][1], palette[color][2]);
color = ((color & 8) >> 3) | ((color << 1) & 0x0f);
}
}
}
// >> 2 to synchronize rendering output with NTSC
ColorType getColor() { return _colors[this->_phase][(this->_window >> 2) & (kColors - 1)]; }
private:
ColorType _colors[kColorPhases][kColors];
};
template<typename ColorType, uint8 R, uint8 G, uint8 B>
class PixelWriterMono : public PixelWriter<ColorType, PixelWriterMono<ColorType, R, G, B> > {
public:
static const uint kColors = 2;
typedef LineDoubleBright BlendRegular;
typedef LineDoubleDim BlendScanlines;
PixelWriterMono() {
_colors[0] = this->_format.RGBToColor(0, 0, 0);
_colors[1] = this->_format.RGBToColor(R, G, B);
}
ColorType getColor() { return _colors[(this->_window >> 3) & (kColors - 1)]; }
private:
ColorType _colors[kColors];
};
static double filterChroma(double z) {
static double x[3] = {0, 0, 0};
static double y[3] = {0, 0, 0};
x[0] = x[1];
x[1] = x[2];
x[2] = z / 7.438011255;
y[0] = y[1];
y[1] = y[2];
y[2] = -x[0] + x[2] + (-0.7318893645 * y[0]) + (1.2336442711 * y[1]);
return y[2];
}
static double filterLuma(double z) {
static double x[3] = {0, 0, 0};
static double y[3] = {0, 0, 0};
x[0] = x[1];
x[1] = x[2];
x[2] = z / 13.71331570;
y[0] = y[1];
y[1] = y[2];
y[2] = x[0] + x[2] + (2.f * x[1]) + (-0.3961075449 * y[0]) + (1.1044202472 * y[1]);
return y[2];
}
static double filterSignal(double z) {
static double x[3] = {0, 0, 0};
static double y[3] = {0, 0, 0};
x[0] = x[1];
x[1] = x[2];
x[2] = z / 7.614490548;
y[0] = y[1];
y[1] = y[2];
y[2] = x[0] + x[2] + (2.0 * x[1]) + (-0.2718798058 * y[0]) + (0.7465656072 * y[1]);
return y[2];
}
template<typename ColorType>
class PixelWriterColorNTSC : public PixelWriter<ColorType, PixelWriterColorNTSC<ColorType> > {
public:
static const uint kColors = 4096;
typedef BlendBright BlendRegular;
typedef BlendDim BlendScanlines;
PixelWriterColorNTSC() {
for (uint phase = 0; phase < kColorPhases; ++phase) {
double phi = Math::deg2rad(phase * 90.0 + 45.0);
for (uint s = 0; s < kColors; ++s) {
uint t = s;
double y;
double i = 0.0;
double q = 0.0;
for (uint n = 0; n < 12; ++n) {
double z = (double)(0 != (t & 0x800));
t = t << 1;
for (uint k = 0; k < 2; k++ ) {
const double zz = filterSignal(z);
double c = filterChroma(zz);
y = filterLuma(zz - c);
c = c * 2.0;
i = i + (c * cos(phi) - i) / 8.0;
q = q + (c * sin(phi) - q) / 8.0;
phi += Math::deg2rad(45.0);
}
}
// YIQ to RGB
const double r64 = y + (0.956 * i) + (0.621 * q);
const double g64 = y + (-0.272 * i) + (-0.647 * q);
const double b64 = y + (-1.105 * i) + (1.702 * q);
uint8 r = CLIP<double>(r64, 0.0, 1.0) * 255;
uint8 g = CLIP<double>(g64, 0.0, 1.0) * 255;
uint8 b = CLIP<double>(b64, 0.0, 1.0) * 255;
#ifdef NTSC_REMOVE_WHITE_RINGING
if ((s & 0xf) == 15) {
// white
r = 255;
g = 255;
b = 255;
}
#endif
#ifdef NTSC_REMOVE_BLACK_GHOSTING
if ((s & 0xf) == 0) {
// Black
r = 0;
g = 0;
b = 0;
}
#endif
_colors[phase][s] = this->_format.RGBToColor(r, g, b);
}
}
}
ColorType getColor() { return _colors[this->_phase][(this->_window >> 1) & (kColors - 1)]; }
private:
ColorType _colors[kColorPhases][kColors];
};
template<typename ColorType>
class PixelWriterMonoNTSC : public PixelWriter<ColorType, PixelWriterMonoNTSC<ColorType> > {
public:
static const uint kColors = 4096;
typedef BlendBright BlendRegular;
typedef BlendDim BlendScanlines;
PixelWriterMonoNTSC() {
for (uint s = 0; s < kColors; ++s) {
uint t = s;
double y;
for (uint n = 0; n < 12; ++n) {
double z = (double)(0 != (t & 0x800));
t = t << 1;
for (uint k = 0; k < 2; k++ ) {
const double zz = filterSignal(z);
double c = filterChroma(zz);
y = filterLuma(zz - c);
}
}
const uint8 brightness = CLIP<double>(y, 0.0, 1.0) * 255;
_colors[s] = this->_format.RGBToColor(brightness, brightness, brightness);
}
}
ColorType getColor() { return _colors[(this->_window >> 1) & (kColors - 1)]; }
private:
ColorType _colors[kColors];
};
template<typename ColorType, typename GfxWriter, typename TextWriter>
class DisplayImpl_A2 : public Display_A2 {
public:
DisplayImpl_A2();
~DisplayImpl_A2() override;
void renderText() override;
void renderGraphics() override;
private:
enum {
kRenderBufWidth = (kGfxPitch + 1) * 14, // one extra chunk to account for pre-render
kRenderBufHeight = (kGfxHeight * 2) + 1 // one extra line to simplify scanline mixing
};
template<typename BlendFunc>
void blendScanlines(uint yStart, uint yEnd);
template<typename Reader, typename Writer>
void render(Writer &writer);
ColorType *_renderBuf;
uint16 _doublePixelMasks[128];
GfxWriter _writerColor;
TextWriter _writerMono;
};
template<typename ColorType, typename GfxWriter, typename TextWriter>
DisplayImpl_A2<ColorType, GfxWriter, TextWriter>::DisplayImpl_A2() : _doublePixelMasks() {
_renderBuf = new ColorType[kRenderBufHeight * kRenderBufWidth]();
for (uint8 val = 0; val < ARRAYSIZE(_doublePixelMasks); ++val)
for (uint8 mask = 0; mask < 7; mask++)
if (val & (1 << mask))
_doublePixelMasks[val] |= 3 << (mask * 2);
}
template<typename ColorType, typename GfxWriter, typename TextWriter>
DisplayImpl_A2<ColorType, GfxWriter, TextWriter>::~DisplayImpl_A2() {
delete[] _renderBuf;
}
template<typename ColorType, typename GfxWriter, typename TextWriter>
template<typename Reader, typename Writer>
void DisplayImpl_A2<ColorType, GfxWriter, TextWriter>::render(Writer &writer) {
uint startY = Reader::getStartY(this);
const uint endY = Reader::getEndY(this);
ColorType *ptr = _renderBuf + startY * kRenderBufWidth * 2;
for (uint y = startY; y < endY; ++y) {
uint16 lastBit = 0;
writer.setupWrite(ptr);
for (uint x = 0; x < kGfxPitch; ++x) {
const uint8 m = Reader::getBits(this, y, x);
uint16 bits = _doublePixelMasks[m & 0x7F];
if (m & 0x80)
bits = (bits << 1) | lastBit;
lastBit = (bits >> 13) & 1;
writer.writePixels(bits);
}
// Because of the pre-render, we need to feed
// in some more bits to get the full picture
writer.writePixels(0);
// The odd lines will be filled in later, so skip a line
ptr += 2 * kRenderBufWidth;
}
if (_enableScanlines)
blendScanlines<typename Writer::BlendScanlines>(startY, endY);
else
blendScanlines<typename Writer::BlendRegular>(startY, endY);
// For the NTSC modes we need to redo the scanline that blends with our first line
if (GfxWriter::kColors == 4096 && startY > 0) {
--startY;
if (_enableScanlines)
blendScanlines<typename GfxWriter::BlendScanlines>(startY, startY + 1);
else
blendScanlines<typename GfxWriter::BlendRegular>(startY, startY + 1);
}
g_system->copyRectToScreen(_renderBuf + startY * 2 * kRenderBufWidth + kPreRender, kRenderBufWidth * sizeof(ColorType), 0, startY * 2, kGfxWidth * 2, (endY - startY) * 2);
g_system->updateScreen();
}
template<typename ColorType, typename GfxWriter, typename TextWriter>
template<typename BlendType>
void DisplayImpl_A2<ColorType, GfxWriter, TextWriter>::blendScanlines(uint yStart, uint yEnd) {
const Graphics::PixelFormat rgbFormat = g_system->getScreenFormat();
// Note: this reads line yEnd * 2 of _renderBuf!
for (uint y = yStart; y < yEnd; ++y) {
ColorType *buf = &_renderBuf[y * 2 * kRenderBufWidth];
for (uint x = 0; x < kRenderBufWidth; ++x) {
const ColorType color1 = buf[x];
const ColorType color2 = buf[2 * kRenderBufWidth + x];
uint8 r1, g1, b1, r2, g2, b2;
rgbFormat.colorToRGB(color1, r1, g1, b1);
rgbFormat.colorToRGB(color2, r2, g2, b2);
const uint8 r3 = BlendType::blend(r1, r2);
const uint8 g3 = BlendType::blend(g1, g2);
const uint8 b3 = BlendType::blend(b1, b2);
buf[kRenderBufWidth + x] = rgbFormat.RGBToColor(r3, g3, b3);
}
}
}
template<typename ColorType, typename GfxWriter, typename TextWriter>
void DisplayImpl_A2<ColorType, GfxWriter, TextWriter>::renderText() {
if (_mode == kModeGraphics)
return;
_blink = (g_system->getMillis() / 270) & 1;
if (_mode == kModeMixed && _enableColor && !_enableMonoText)
render<TextReader>(_writerColor);
else
render<TextReader>(_writerMono);
}
template<typename ColorType, typename GfxWriter, typename TextWriter>
void DisplayImpl_A2<ColorType, GfxWriter, TextWriter>::renderGraphics() {
if (_mode == kModeText)
return;
render<GfxReader>(_writerColor);
}
Display_A2::Display_A2() :
_frameBuf(nullptr),
_showCursor(false),
_enableColor(false),
_enableScanlines(false),
_enableMonoText(false),
_enableApple2eCursor(false),
_blink(false) { }
Display_A2::~Display_A2() {
delete[] _frameBuf;
}
void Display_A2::init() {
createTextBuffer(Display_A2::kTextWidth, Display_A2::kTextHeight);
_frameBuf = new byte[Display_A2::kGfxSize]();
_enableColor = ConfMan.getBool("color");
_enableScanlines = ConfMan.getBool("scanlines");
_enableMonoText = ConfMan.getBool("monotext");
_enableApple2eCursor = ConfMan.getBool("apple2e_cursor");
}
void Display_A2::loadFrameBuffer(Common::ReadStream &stream, byte *dst) const {
for (uint j = 0; j < 8; ++j) {
for (uint i = 0; i < 8; ++i) {
stream.read(dst, Display_A2::kGfxPitch);
dst += Display_A2::kGfxPitch * 64;
stream.read(dst, Display_A2::kGfxPitch);
dst += Display_A2::kGfxPitch * 64;
stream.read(dst, Display_A2::kGfxPitch);
stream.readUint32LE();
stream.readUint32LE();
dst -= Display_A2::kGfxPitch * 120;
}
dst -= Display_A2::kGfxPitch * 63;
}
if (stream.eos() || stream.err())
error("Failed to read frame buffer");
}
void Display_A2::loadFrameBuffer(Common::ReadStream &stream) {
loadFrameBuffer(stream, _frameBuf);
}
void Display_A2::putPixel(const Common::Point &p, byte color) {
const byte offset = p.x / 7;
byte mask = 0x80 | (1 << (p.x % 7));
// Since white and black are in both palettes, we leave
// the palette bit alone
if ((color & 0x7f) == 0x7f || (color & 0x7f) == 0)
mask &= 0x7f;
// Adjust colors starting with bits '01' or '10' for
// odd offsets
if (offset & 1) {
byte c = color << 1;
if (c >= 0x40 && c < 0xc0)
color ^= 0x7f;
}
writeFrameBuffer(p, color, mask);
}
void Display_A2::setPixelByte(const Common::Point &p, byte color) {
assert(p.x >= 0 && p.x < Display_A2::kGfxWidth && p.y >= 0 && p.y < Display_A2::kGfxHeight);
_frameBuf[p.y * Display_A2::kGfxPitch + p.x / 7] = color;
}
void Display_A2::setPixelBit(const Common::Point &p, byte color) {
writeFrameBuffer(p, color, 1 << (p.x % 7));
}
void Display_A2::setPixelPalette(const Common::Point &p, byte color) {
writeFrameBuffer(p, color, 0x80);
}
byte Display_A2::getPixelByte(const Common::Point &p) const {
assert(p.x >= 0 && p.x < Display_A2::kGfxWidth && p.y >= 0 && p.y < Display_A2::kGfxHeight);
return _frameBuf[p.y * Display_A2::kGfxPitch + p.x / 7];
}
bool Display_A2::getPixelBit(const Common::Point &p) const {
assert(p.x >= 0 && p.x < Display_A2::kGfxWidth && p.y >= 0 && p.y < Display_A2::kGfxHeight);
const byte *b = _frameBuf + p.y * Display_A2::kGfxPitch + p.x / 7;
return *b & (1 << (p.x % 7));
}
void Display_A2::clear(byte color) {
byte val = 0;
const byte c = color << 1;
if (c >= 0x40 && c < 0xc0)
val = 0x7f;
for (uint i = 0; i < Display_A2::kGfxSize; ++i) {
_frameBuf[i] = color;
color ^= val;
}
}
// FIXME: This does not currently update the surfaces
void Display_A2::printChar(char c) {
if (c == Display_A2::asciiToNative('\r'))
_cursorPos = (_cursorPos / Display_A2::kTextWidth + 1) * Display_A2::kTextWidth;
else if (c == Display_A2::asciiToNative('\a')) {
renderText();
static_cast<AdlEngine *>(g_engine)->bell();
} else if ((byte)c < 0x80 || (byte)c >= 0xa0) {
setCharAtCursor(c);
++_cursorPos;
}
if (_cursorPos == Display_A2::kTextWidth * Display_A2::kTextHeight)
scrollUp();
}
void Display_A2::showCursor(bool enable) {
_showCursor = enable;
}
void Display_A2::writeFrameBuffer(const Common::Point &p, byte color, byte mask) {
assert(p.x >= 0 && p.x < Display_A2::kGfxWidth && p.y >= 0 && p.y < Display_A2::kGfxHeight);
byte *b = _frameBuf + p.y * Display_A2::kGfxPitch + p.x / 7;
color ^= *b;
color &= mask;
*b ^= color;
}
template<typename ColorType>
static Display_A2 *Display_A2_create_helper() {
const bool ntsc = ConfMan.getBool("ntsc");
const bool color = ConfMan.getBool("color");
const bool monotext = ConfMan.getBool("monotext");
typedef PixelWriterMono<ColorType, 0xff, 0xff, 0xff> PixelWriterMonoWhite;
typedef PixelWriterMono<ColorType, 0x00, 0xc0, 0x00> PixelWriterMonoGreen;
if (ntsc) {
if (color) {
if (monotext)
return new DisplayImpl_A2<ColorType, PixelWriterColorNTSC<ColorType>, PixelWriterMonoWhite>;
else
return new DisplayImpl_A2<ColorType, PixelWriterColorNTSC<ColorType>, PixelWriterMonoNTSC<ColorType> >;
} else {
if (monotext)
return new DisplayImpl_A2<ColorType, PixelWriterMonoNTSC<ColorType>, PixelWriterMonoWhite>;
else
return new DisplayImpl_A2<ColorType, PixelWriterMonoNTSC<ColorType>, PixelWriterMonoNTSC<ColorType> >;
}
}
if (color)
return new DisplayImpl_A2<ColorType, PixelWriterColor<ColorType>, PixelWriterMonoWhite>;
else
return new DisplayImpl_A2<ColorType, PixelWriterMonoGreen, PixelWriterMonoGreen>;
}
Display_A2 *Display_A2_create() {
initGraphics(Display_A2::kGfxWidth * 2, Display_A2::kGfxHeight * 2, nullptr);
debugN(1, "Initialized graphics with format: %s", g_system->getScreenFormat().toString().c_str());
const uint bpp = g_system->getScreenFormat().bytesPerPixel;
if (bpp == 4)
return Display_A2_create_helper<uint32>();
else if (bpp == 2)
return Display_A2_create_helper<uint16>();
else
return nullptr;
}
} // End of namespace Adl

138
engines/adl/display_a2.h Normal file
View File

@@ -0,0 +1,138 @@
/* 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 "adl/display.h"
#ifndef ADL_DISPLAY_A2_H
#define ADL_DISPLAY_A2_H
namespace Adl {
class Display_A2 : public Display {
public:
Display_A2();
~Display_A2() override;
enum {
kGfxWidth = 280,
kGfxHeight = 192,
kGfxPitch = kGfxWidth / 7,
kGfxSize = kGfxPitch * kGfxHeight,
kTextWidth = 40,
kTextHeight = 24,
kSplitHeight = 32
};
void init() override;
// Graphics
uint getGfxWidth() const { return kGfxWidth; }
uint getGfxHeight() const { return kGfxHeight; }
uint getGfxPitch() const { return kGfxPitch; }
void loadFrameBuffer(Common::ReadStream &stream, byte *dst) const ;
void loadFrameBuffer(Common::ReadStream &stream);
void putPixel(const Common::Point &p, byte color);
void setPixelByte(const Common::Point &p, byte color);
void setPixelBit(const Common::Point &p, byte color);
void setPixelPalette(const Common::Point &p, byte color);
byte getPixelByte(const Common::Point &p) const;
bool getPixelBit(const Common::Point &p) const;
void clear(byte color);
// Text
char asciiToNative(char c) const override { return c | 0x80; }
void printChar(char c) override;
void showCursor(bool enable) override;
protected:
class TextReader {
public:
static uint16 getBits(const Display_A2 *display, uint y, uint x) {
const uint charPos = (y >> 3) * kTextWidth + x;
byte m = display->_textBuf[charPos];
byte b = _font[m & 0x3f][y % 8];
if (display->_showCursor && charPos == display->_cursorPos) {
if (!display->_enableApple2eCursor) {
m = (m & 0x3f) | 0x40;
} else {
if (display->_blink) {
byte cursor[] = {
0x00, 0x00, 0x2a, 0x14,
0x2a, 0x14, 0x2a, 0x00
};
b = cursor[y % 8];
}
}
}
if (!(m & 0x80) && (!(m & 0x40) || display->_blink))
b = ~b;
return b & 0x7f;
}
static uint8 getStartY(const Display_A2 *display) {
if (display->_mode == kModeText)
return 0;
else
return kGfxHeight - kSplitHeight;
}
static uint8 getEndY(const Display_A2 *display) { return kGfxHeight; }
};
class GfxReader {
public:
static uint16 getBits(const Display_A2 *display, uint y, uint x) {
return display->_frameBuf[y * kGfxPitch + x];
}
static uint8 getStartY(const Display_A2 *display) { return 0; }
static uint8 getEndY(const Display_A2 *display) {
if (display->_mode == kModeGraphics)
return kGfxHeight;
else
return kGfxHeight - kSplitHeight;
}
};
byte *_frameBuf;
bool _showCursor;
bool _enableColor;
bool _enableScanlines;
bool _enableMonoText;
bool _enableApple2eCursor;
bool _blink;
private:
void writeFrameBuffer(const Common::Point &p, byte color, byte mask);
static const byte _font[64][8];
};
Display_A2 *Display_A2_create();
} // End of namespace Adl
#endif

591
engines/adl/graphics.h Normal file
View File

@@ -0,0 +1,591 @@
/* 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 ADL_GRAPHICS_H
#define ADL_GRAPHICS_H
#include "common/rect.h"
#include "common/stream.h"
#include "adl/display.h"
namespace Adl {
class GraphicsMan {
public:
virtual ~GraphicsMan() { }
// Applesoft BASIC HLINE
virtual void drawLine(const Common::Point &p1, const Common::Point &p2, byte color) const = 0;
// Applesoft BASIC DRAW
virtual void drawShape(Common::ReadStream &shape, Common::Point &pos, byte rotation = 0, byte scaling = 1, byte color = 0x7f) const = 0;
virtual void drawPic(Common::SeekableReadStream &pic, const Common::Point &pos) = 0;
virtual void clearScreen() const = 0;
void setBounds(const Common::Rect &r) { _bounds = r; }
protected:
Common::Rect _bounds;
};
// Used in hires1
template <class T>
class GraphicsMan_v1 : public GraphicsMan {
public:
GraphicsMan_v1(T &display) : _display(display) { this->setBounds(Common::Rect(280, 160)); }
void drawLine(const Common::Point &p1, const Common::Point &p2, byte color) const override;
void drawShape(Common::ReadStream &shape, Common::Point &pos, byte rotation = 0, byte scaling = 1, byte color = 0x7f) const override;
void drawPic(Common::SeekableReadStream &pic, const Common::Point &pos) override;
void clearScreen() const override;
protected:
T &_display;
void putPixel(const Common::Point &p, byte color) const;
private:
void drawShapePixel(Common::Point &p, byte color, byte bits, byte quadrant) const;
virtual byte getClearColor() const { return 0x00; }
};
// Used in hires0 and hires2-hires4
template <class T>
class GraphicsMan_v2 : public GraphicsMan_v1<T> {
public:
GraphicsMan_v2(T &display) : GraphicsMan_v1<T>(display), _color(0) { }
void drawPic(Common::SeekableReadStream &pic, const Common::Point &pos) override;
protected:
bool canFillAt(const Common::Point &p, const bool stopBit = false);
void fillRow(Common::Point p, const byte pattern, const bool stopBit = false);
byte getPatternColor(const Common::Point &p, byte pattern);
private:
static bool readByte(Common::SeekableReadStream &pic, byte &b);
bool readPoint(Common::SeekableReadStream &pic, Common::Point &p);
void drawCorners(Common::SeekableReadStream &pic, bool yFirst);
void drawRelativeLines(Common::SeekableReadStream &pic);
void drawAbsoluteLines(Common::SeekableReadStream &pic);
void fill(Common::SeekableReadStream &pic);
virtual void fillRowLeft(Common::Point p, const byte pattern, const bool stopBit);
virtual void fillAt(Common::Point p, const byte pattern);
byte getClearColor() const override { return 0xff; }
byte _color;
Common::Point _offset;
};
// Used in hires5, hires6 and gelfling (possibly others as well)
template <class T>
class GraphicsMan_v3 : public GraphicsMan_v2<T> {
public:
GraphicsMan_v3(T &display) : GraphicsMan_v2<T>(display) { }
private:
void fillRowLeft(Common::Point p, const byte pattern, const bool stopBit) override;
void fillAt(Common::Point p, const byte pattern) override;
};
template <class T>
void GraphicsMan_v1<T>::clearScreen() const {
_display.setMode(Display::kModeMixed);
_display.clear(getClearColor());
}
// Draws a four-connected line
template <class T>
void GraphicsMan_v1<T>::drawLine(const Common::Point &p1, const Common::Point &p2, byte color) const {
int16 deltaX = p2.x - p1.x;
int8 xStep = 1;
if (deltaX < 0) {
deltaX = -deltaX;
xStep = -1;
}
int16 deltaY = p2.y - p1.y;
int8 yStep = -1;
if (deltaY > 0) {
deltaY = -deltaY;
yStep = 1;
}
Common::Point p(p1);
int16 steps = deltaX - deltaY + 1;
int16 err = deltaX + deltaY;
while (true) {
putPixel(p, color);
if (--steps == 0)
return;
if (err < 0) {
p.y += yStep;
err += deltaX;
} else {
p.x += xStep;
err += deltaY;
}
}
}
template <class T>
void GraphicsMan_v1<T>::putPixel(const Common::Point &p, byte color) const {
if (this->_bounds.contains(p))
_display.putPixel(p, color);
}
template <class T>
void GraphicsMan_v1<T>::drawShapePixel(Common::Point &p, byte color, byte bits, byte quadrant) const {
if (bits & 4)
putPixel(p, color);
bits += quadrant;
if (bits & 1)
p.x += (bits & 2 ? -1 : 1);
else
p.y += (bits & 2 ? 1 : -1);
}
template <class T>
void GraphicsMan_v1<T>::drawShape(Common::ReadStream &corners, Common::Point &pos, byte rotation, byte scaling, byte color) const {
const byte stepping[] = {
0xff, 0xfe, 0xfa, 0xf4, 0xec, 0xe1, 0xd4, 0xc5,
0xb4, 0xa1, 0x8d, 0x78, 0x61, 0x49, 0x31, 0x18,
0xff
};
byte quadrant = rotation >> 4;
rotation &= 0xf;
byte xStep = stepping[rotation];
byte yStep = stepping[(rotation ^ 0xf) + 1] + 1;
while (true) {
byte b = corners.readByte();
if (corners.eos() || corners.err())
error("Error reading corners");
if (b == 0)
return;
do {
byte xFrac = 0x80;
byte yFrac = 0x80;
for (uint j = 0; j < scaling; ++j) {
if (xFrac + xStep + 1 > 255)
drawShapePixel(pos, color, b, quadrant);
xFrac += xStep + 1;
if (yFrac + yStep > 255)
drawShapePixel(pos, color, b, quadrant + 1);
yFrac += yStep;
}
b >>= 3;
} while (b != 0);
}
}
template <class T>
void GraphicsMan_v1<T>::drawPic(Common::SeekableReadStream &pic, const Common::Point &pos) {
byte x, y;
bool bNewLine = false;
byte oldX = 0, oldY = 0;
while (1) {
x = pic.readByte();
y = pic.readByte();
if (pic.err() || pic.eos())
error("Error reading picture");
if (x == 0xff && y == 0xff)
return;
if (x == 0 && y == 0) {
bNewLine = true;
continue;
}
x += pos.x;
y += pos.y;
if (y > 160)
y = 160;
if (bNewLine) {
putPixel(Common::Point(x, y), 0x7f);
bNewLine = false;
} else {
drawLine(Common::Point(oldX, oldY), Common::Point(x, y), 0x7f);
}
oldX = x;
oldY = y;
}
}
template <class T>
bool GraphicsMan_v2<T>::readByte(Common::SeekableReadStream &pic, byte &b) {
b = pic.readByte();
if (pic.eos() || pic.err())
error("Error reading picture");
if (b >= 0xe0) {
pic.seek(-1, SEEK_CUR);
return false;
}
return true;
}
template <class T>
bool GraphicsMan_v2<T>::readPoint(Common::SeekableReadStream &pic, Common::Point &p) {
byte b;
if (!readByte(pic, b))
return false;
p.x = b + _offset.x;
p.x <<= 1;
if (!readByte(pic, b))
return false;
p.y = b + _offset.y;
return true;
}
template <class T>
byte GraphicsMan_v2<T>::getPatternColor(const Common::Point &p, byte pattern) {
const byte fillPatterns[][4] = {
{ 0x00, 0x00, 0x00, 0x00 },
{ 0x80, 0x80, 0x80, 0x80 },
{ 0xff, 0xff, 0xff, 0xff },
{ 0x7f, 0x7f, 0x7f, 0x7f },
{ 0x2a, 0x55, 0x2a, 0x55 },
{ 0xaa, 0xd5, 0xaa, 0xd5 },
{ 0x55, 0x2a, 0x55, 0x2a },
{ 0xd5, 0xaa, 0xd5, 0xaa },
{ 0x33, 0x66, 0x4c, 0x19 },
{ 0xb3, 0xe6, 0xcc, 0x99 },
{ 0x22, 0x44, 0x08, 0x11 },
{ 0xa2, 0xc4, 0x88, 0x91 },
{ 0x11, 0x22, 0x44, 0x08 },
{ 0x91, 0xa2, 0xc4, 0x88 },
{ 0x6e, 0x5d, 0x3b, 0x77 },
{ 0xee, 0xdd, 0xbb, 0xf7 },
{ 0x5d, 0x3b, 0x77, 0x6e },
{ 0xdd, 0xbb, 0xf7, 0xee },
{ 0x66, 0x4c, 0x19, 0x33 },
{ 0xe6, 0xcc, 0x99, 0xb3 },
{ 0x33, 0x66, 0x4c, 0x19 },
{ 0xb3, 0xe6, 0xcc, 0x99 }
};
if (pattern >= ARRAYSIZE(fillPatterns))
error("Invalid fill pattern %i encountered in picture", pattern);
byte offset = (p.y & 1) << 1;
offset += (p.x / 7) & 3;
return fillPatterns[pattern][offset % sizeof(fillPatterns[0])];
}
template <class T>
void GraphicsMan_v2<T>::drawCorners(Common::SeekableReadStream &pic, bool yFirst) {
Common::Point p;
if (!readPoint(pic, p))
return;
if (yFirst)
goto doYStep;
while (true) {
byte b;
int16 n;
if (!readByte(pic, b))
return;
n = b + _offset.x;
this->putPixel(p, _color);
n <<= 1;
this->drawLine(p, Common::Point(n, p.y), _color);
p.x = n;
doYStep:
if (!readByte(pic, b))
return;
n = b + _offset.y;
this->putPixel(p, _color);
this->drawLine(p, Common::Point(p.x, n), _color);
this->putPixel(Common::Point(p.x + 1, p.y), _color);
this->drawLine(Common::Point(p.x + 1, p.y), Common::Point(p.x + 1, n), _color);
p.y = n;
}
}
template <class T>
void GraphicsMan_v2<T>::drawRelativeLines(Common::SeekableReadStream &pic) {
Common::Point p1;
if (!readPoint(pic, p1))
return;
this->putPixel(p1, _color);
while (true) {
Common::Point p2(p1);
byte n;
if (!readByte(pic, n))
return;
byte h = (n & 0x70) >> 4;
byte l = n & 7;
if (n & 0x80)
p2.x -= (h << 1);
else
p2.x += (h << 1);
if (n & 8)
p2.y -= l;
else
p2.y += l;
this->drawLine(p1, p2, _color);
p1 = p2;
}
}
template <class T>
void GraphicsMan_v2<T>::drawAbsoluteLines(Common::SeekableReadStream &pic) {
Common::Point p1;
if (!readPoint(pic, p1))
return;
this->putPixel(p1, _color);
while (true) {
Common::Point p2;
if (!readPoint(pic, p2))
return;
this->drawLine(p1, p2, _color);
p1 = p2;
}
}
template <class T>
bool GraphicsMan_v2<T>::canFillAt(const Common::Point &p, const bool stopBit) {
return this->_display.getPixelBit(p) != stopBit && this->_display.getPixelBit(Common::Point(p.x + 1, p.y)) != stopBit;
}
template <class T>
void GraphicsMan_v2<T>::fillRowLeft(Common::Point p, const byte pattern, const bool stopBit) {
byte color = getPatternColor(p, pattern);
while (--p.x >= this->_bounds.left) {
if ((p.x % 7) == 6) {
color = getPatternColor(p, pattern);
this->_display.setPixelPalette(p, color);
}
if (this->_display.getPixelBit(p) == stopBit)
break;
this->_display.setPixelBit(p, color);
}
}
template <class T>
void GraphicsMan_v2<T>::fillRow(Common::Point p, const byte pattern, const bool stopBit) {
// Set pixel at p and palette
byte color = getPatternColor(p, pattern);
this->_display.setPixelPalette(p, color);
this->_display.setPixelBit(p, color);
// Fill left of p
fillRowLeft(p, pattern, stopBit);
// Fill right of p
while (++p.x < this->_bounds.right) {
if ((p.x % 7) == 0) {
color = getPatternColor(p, pattern);
// Palette is set before the first bit is tested
this->_display.setPixelPalette(p, color);
}
if (this->_display.getPixelBit(p) == stopBit)
break;
this->_display.setPixelBit(p, color);
}
}
template <class T>
void GraphicsMan_v2<T>::fillAt(Common::Point p, const byte pattern) {
const bool stopBit = !this->_display.getPixelBit(p);
// Move up into the open space above p
while (--p.y >= this->_bounds.top && canFillAt(p, stopBit)) {}
// Then fill by moving down
while (++p.y < this->_bounds.bottom && canFillAt(p, stopBit))
fillRow(p, pattern, stopBit);
}
template <class T>
void GraphicsMan_v2<T>::fill(Common::SeekableReadStream &pic) {
byte pattern;
if (!readByte(pic, pattern))
return;
while (true) {
Common::Point p;
if (!readPoint(pic, p))
return;
if (this->_bounds.contains(p))
fillAt(p, pattern);
}
}
template <class T>
void GraphicsMan_v2<T>::drawPic(Common::SeekableReadStream &pic, const Common::Point &pos) {
// NOTE: The original engine only resets the color for overlays. As a result, room
// pictures that draw without setting a color or clearing the screen, will use the
// last color set by the previous picture. We assume this is unintentional and do
// not copy this behavior.
_color = 0;
_offset = pos;
while (true) {
byte opcode = pic.readByte();
if (pic.eos() || pic.err())
error("Error reading picture");
switch (opcode) {
case 0xe0:
drawCorners(pic, false);
break;
case 0xe1:
drawCorners(pic, true);
break;
case 0xe2:
drawRelativeLines(pic);
break;
case 0xe3:
drawAbsoluteLines(pic);
break;
case 0xe4:
fill(pic);
break;
case 0xe5:
this->clearScreen();
_color = 0x00;
break;
case 0xf0:
_color = 0x00;
break;
case 0xf1:
_color = 0x2a;
break;
case 0xf2:
_color = 0x55;
break;
case 0xf3:
_color = 0x7f;
break;
case 0xf4:
_color = 0x80;
break;
case 0xf5:
_color = 0xaa;
break;
case 0xf6:
_color = 0xd5;
break;
case 0xf7:
_color = 0xff;
break;
case 0xff:
return;
default:
if (opcode >= 0xe0)
error("Invalid pic opcode %02x", opcode);
else
warning("Expected pic opcode, but found data byte %02x", opcode);
}
}
}
template <class T>
void GraphicsMan_v3<T>::fillRowLeft(Common::Point p, const byte pattern, const bool stopBit) {
byte color = this->getPatternColor(p, pattern);
while (--p.x >= this->_bounds.left) {
// In this version, when moving left, it no longer sets the palette first
if (!this->_display.getPixelBit(p))
return;
if ((p.x % 7) == 6) {
color = this->getPatternColor(p, pattern);
this->_display.setPixelPalette(p, color);
}
this->_display.setPixelBit(p, color);
}
}
template <class T>
void GraphicsMan_v3<T>::fillAt(Common::Point p, const byte pattern) {
// If the row at p cannot be filled, we do nothing
if (!this->canFillAt(p))
return;
this->fillRow(p, pattern);
Common::Point q(p);
// Fill up from p
while (--q.y >= this->_bounds.top && this->canFillAt(q))
this->fillRow(q, pattern);
// Fill down from p
while (++p.y < this->_bounds.bottom && this->canFillAt(p))
this->fillRow(p, pattern);
}
} // End of namespace Adl
#endif

673
engines/adl/hires1.cpp Normal file
View File

@@ -0,0 +1,673 @@
/* 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 "common/system.h"
#include "common/debug.h"
#include "common/error.h"
#include "common/file.h"
#include "common/stream.h"
#include "common/memstream.h"
#include "common/ptr.h"
#include "adl/adl.h"
#include "adl/graphics.h"
#include "adl/display_a2.h"
namespace Adl {
#define IDS_HR1_EXE_0 "AUTO LOAD OBJ"
#define IDS_HR1_EXE_1 "ADVENTURE"
#define IDS_HR1_MESSAGES "MESSAGES"
#define IDI_HR1_NUM_ROOMS 41
#define IDI_HR1_NUM_PICS 97
#define IDI_HR1_NUM_VARS 20
#define IDI_HR1_NUM_ITEM_OFFSETS 21
#define IDI_HR1_NUM_MESSAGES 168
// Messages used outside of scripts
#define IDI_HR1_MSG_CANT_GO_THERE 137
#define IDI_HR1_MSG_DONT_UNDERSTAND 37
#define IDI_HR1_MSG_ITEM_DOESNT_MOVE 151
#define IDI_HR1_MSG_ITEM_NOT_HERE 152
#define IDI_HR1_MSG_THANKS_FOR_PLAYING 140
#define IDI_HR1_MSG_DONT_HAVE_IT 127
#define IDI_HR1_MSG_GETTING_DARK 7
#define IDI_HR1_OFS_PD_TEXT_0 0x005d
#define IDI_HR1_OFS_PD_TEXT_1 0x012b
#define IDI_HR1_OFS_PD_TEXT_2 0x016d
#define IDI_HR1_OFS_PD_TEXT_3 0x0259
#define IDI_HR1_OFS_ITEMS 0x0100
#define IDI_HR1_OFS_ROOMS 0x050a
#define IDI_HR1_OFS_PICS 0x4b03
#define IDI_HR1_OFS_CMDS_0 0x3c00
#define IDI_HR1_OFS_CMDS_1 0x3d00
#define IDI_HR1_OFS_MSGS 0x4d00
#define IDI_HR1_OFS_ITEM_OFFSETS 0x68ff
#define IDI_HR1_OFS_SHAPES 0x4f00
class HiRes1Engine : public AdlEngine {
public:
HiRes1Engine(OSystem *syst, const AdlGameDescription *gd) :
AdlEngine(syst, gd),
_files(nullptr),
_messageDelay(true) { }
~HiRes1Engine() override { delete _files; }
protected:
// AdlEngine
void runIntro() override;
void init() override;
void initGameState() override;
void restartGame();
void printString(const Common::String &str) override;
Common::String loadMessage(uint idx) const override;
void printMessage(uint idx) override;
void drawItems() override;
void drawItem(Item &item, const Common::Point &pos) override;
void loadRoom(byte roomNr) override;
void showRoom() override;
void showInstructions(Common::SeekableReadStream &stream);
void wordWrap(Common::String &str) const;
Common::Files *_files;
Common::File _exe;
Common::Array<Common::DataBlockPtr> _corners;
Common::Array<byte> _roomDesc;
bool _messageDelay;
struct {
Common::String cantGoThere;
Common::String dontHaveIt;
Common::String dontUnderstand;
Common::String gettingDark;
} _gameStrings;
};
void HiRes1Engine::showInstructions(Common::SeekableReadStream &stream) {
_display->setMode(Display::kModeText);
Common::String ttsMessage;
for (;;) {
byte opc = stream.readByte();
if (opc != 0x20)
error("Error reading instructions");
uint16 addr = stream.readUint16BE();
if (addr == 0x58fc) {
// HOME
_display->home();
} else if (addr == 0x6ffd) {
_display->sayText(ttsMessage);
ttsMessage.clear();
// GETLN1
inputString();
if (shouldQuit())
return;
} else {
// We assume print string call (addr varies per game)
Common::String str = readString(stream);
ttsMessage += str;
// If the string ends in two carriage returns, add a newline to the end of the TTS message
// (since carriage returns will be replaced with spaces)
if (str.size() > 1 && str[str.size() - 1] == '\x8d' && str[str.size() - 2] == '\x8d') {
ttsMessage += '\n';
}
if (stream.err() || stream.eos())
error("Error reading instructions");
// Ctrl-D signifies system command (main binary would be loaded here)
size_t posChr4 = str.findFirstOf(_display->asciiToNative(4));
if (posChr4 != str.npos) {
_display->printString(str.substr(0, posChr4), false);
return;
}
_display->printString(str, false);
}
}
}
void HiRes1Engine::runIntro() {
Common::StreamPtr stream(_files->createReadStream(IDS_HR1_EXE_0));
// Early version have no bitmap in 'AUTO LOAD OBJ'
if (getGameVersion() >= GAME_VER_HR1_COARSE) {
// Later binaries have a MIXEDON prepended to it, by skipping it
// we can use the same offsets for both variants
if (stream->readUint16BE() == 0xad53)
stream.reset(_files->createReadStream(IDS_HR1_EXE_0, 3));
stream->seek(0x1000);
_display->setMode(Display::kModeGraphics);
static_cast<Display_A2 *>(_display)->loadFrameBuffer(*stream);
_display->renderGraphics();
if (getGameVersion() == GAME_VER_HR1_PD) {
// Only the PD version shows a title screen during the load
delay(4000);
if (shouldQuit())
return;
}
}
Common::String str;
// Show the PD disclaimer for the PD release
if (getGameVersion() == GAME_VER_HR1_PD) {
// The P.D.E. version on the Roberta Williams Anthology has a renamed HELLO file
const char *fileName = "MYSTERY.HELLO";
if (!_files->exists(fileName))
fileName = "HELLO";
Common::StreamPtr basic(_files->createReadStream(fileName));
_display->setMode(Display::kModeText);
_display->home();
str = readStringAt(*basic, IDI_HR1_OFS_PD_TEXT_0, '"');
_display->printAsciiString(str + '\r', false);
Common::String ttsMessage = str + ' ';
str = readStringAt(*basic, IDI_HR1_OFS_PD_TEXT_1, '"');
_display->printAsciiString(str + "\r\r", false);
ttsMessage += str + '\n';
str = readStringAt(*basic, IDI_HR1_OFS_PD_TEXT_2, '"');
_display->printAsciiString(str + "\r\r", false);
ttsMessage += str + '\n';
str = readStringAt(*basic, IDI_HR1_OFS_PD_TEXT_3, '"');
_display->printAsciiString(str + '\r', false);
_display->sayText(ttsMessage + ' ' + str);
inputKey();
stopTextToSpeech();
if (shouldQuit())
return;
}
_display->setMode(Display::kModeMixed);
str = readStringAt(*stream, 0xc);
if (getGameVersion() >= GAME_VER_HR1_COARSE) {
bool instructions = false;
char keyInstr = 'I';
char keyGame = 'G';
if (getLanguage() == Common::FR_FRA) {
keyInstr = 'M';
keyGame = 'J';
}
while (1) {
_display->printString(str);
Common::String s = inputString();
if (shouldQuit())
break;
if (s.empty())
continue;
if (s[0] == _display->asciiToNative(keyInstr)) {
instructions = true;
break;
} else if (s[0] == _display->asciiToNative(keyGame)) {
break;
}
}
if (instructions) {
stream->seek(0x5d);
showInstructions(*stream);
_display->printAsciiString("\r");
}
} else {
// This version shows the last page during the loading of the game
// We wait for a key instead (even though there's no prompt for that).
stream->seek(3);
showInstructions(*stream);
inputString();
}
stream.reset(_files->createReadStream(IDS_HR1_EXE_1));
stream->seek(0x1800);
static_cast<Display_A2 *>(_display)->loadFrameBuffer(*stream);
_display->renderGraphics();
// The title screen would normally be visible during loading.
// We add a short delay here to simulate that.
delay(2000);
_display->setMode(Display::kModeMixed);
if (getGameVersion() == GAME_VER_HR1_SIMI) {
// The original waits for the key after initializing the state.
// This causes it to also wait for a key on a blank screen when
// a game is restarted. We only wait for a key here during the
// intro.
// This does mean we need to push out some extra line feeds to clear the screen
_display->printString(_strings.lineFeeds);
inputKey();
}
}
void HiRes1Engine::init() {
if (Common::File::exists("ADVENTURE")) {
_files = new Common::Files_Plain();
} else {
Common::Files_AppleDOS *files = new Common::Files_AppleDOS();
if (!files->open(getDiskImageName(0)))
error("Failed to open '%s'", getDiskImageName(0).toString(Common::Path::kNativeSeparator).c_str());
_files = files;
}
_graphics = new GraphicsMan_v1<Display_A2>(*static_cast<Display_A2 *>(_display));
_display->moveCursorTo(Common::Point(0, 23)); // DOS 3.3
Common::StreamPtr stream(_files->createReadStream(IDS_HR1_EXE_1));
Common::StringArray exeStrings;
extractExeStrings(*stream, 0x1576, exeStrings);
if (exeStrings.size() != 18)
error("Failed to load strings from executable");
// Some messages have overrides inside the executable
_gameStrings.cantGoThere = exeStrings[12];
_gameStrings.dontHaveIt = exeStrings[13];
_gameStrings.dontUnderstand = exeStrings[14];
_gameStrings.gettingDark = exeStrings[15];
// Load other strings from executable
_strings.enterCommand = exeStrings[5];
_strings.verbError = exeStrings[3];
_strings.nounError = exeStrings[4];
_strings.playAgain = exeStrings[8];
_strings.pressReturn = exeStrings[10];
_strings.lineFeeds = exeStrings[1];
// Set message IDs
_messageIds.cantGoThere = IDI_HR1_MSG_CANT_GO_THERE;
_messageIds.dontUnderstand = IDI_HR1_MSG_DONT_UNDERSTAND;
_messageIds.itemDoesntMove = IDI_HR1_MSG_ITEM_DOESNT_MOVE;
_messageIds.itemNotHere = IDI_HR1_MSG_ITEM_NOT_HERE;
_messageIds.thanksForPlaying = IDI_HR1_MSG_THANKS_FOR_PLAYING;
if (getLanguage() == Common::FR_FRA) {
_verbErrorPos = 15;
_nounErrorPos = 31;
}
// Load message offsets
stream->seek(IDI_HR1_OFS_MSGS);
for (uint i = 0; i < IDI_HR1_NUM_MESSAGES; ++i)
_messages.push_back(_files->getDataBlock(IDS_HR1_MESSAGES, stream->readUint16LE()));
// The French version has 5 additional strings
if (getLanguage() == Common::FR_FRA)
for (uint i = 0; i < 5; ++i)
_messages.push_back(_files->getDataBlock(IDS_HR1_MESSAGES, stream->readUint16LE()));
// Load picture data from executable
stream->seek(IDI_HR1_OFS_PICS);
for (uint i = 1; i <= IDI_HR1_NUM_PICS; ++i) {
byte block = stream->readByte();
Common::String name = Common::String::format("BLOCK%i", block);
uint16 offset = stream->readUint16LE();
_pictures[i] = _files->getDataBlock(Common::Path(name), offset);
}
// Load commands from executable
stream->seek(IDI_HR1_OFS_CMDS_1);
readCommands(*stream, _roomCommands);
stream->seek(IDI_HR1_OFS_CMDS_0);
readCommands(*stream, _globalCommands);
// Load dropped item offsets
stream->seek(IDI_HR1_OFS_ITEM_OFFSETS);
loadDroppedItemOffsets(*stream, IDI_HR1_NUM_ITEM_OFFSETS);
// Load shapes
stream->seek(IDI_HR1_OFS_SHAPES);
uint16 cornersCount = stream->readUint16LE();
for (uint i = 0; i < cornersCount; ++i)
_corners.push_back(_files->getDataBlock(IDS_HR1_EXE_1, IDI_HR1_OFS_SHAPES + stream->readUint16LE()));
if (stream->eos() || stream->err())
error("Failed to read game data from '" IDS_HR1_EXE_1 "'");
stream->seek(getLanguage() == Common::FR_FRA ? 0x900 : 0x3800);
loadWords(*stream, _verbs, _priVerbs);
stream->seek(0xf00);
loadWords(*stream, _nouns, _priNouns);
}
void HiRes1Engine::initGameState() {
_state.vars.resize(IDI_HR1_NUM_VARS);
Common::StreamPtr stream(_files->createReadStream(IDS_HR1_EXE_1));
// Load room data from executable
_roomDesc.clear();
stream->seek(IDI_HR1_OFS_ROOMS);
for (uint i = 0; i < IDI_HR1_NUM_ROOMS; ++i) {
Room room;
stream->readByte();
_roomDesc.push_back(stream->readByte());
for (uint j = 0; j < 6; ++j)
room.connections[j] = stream->readByte();
room.picture = stream->readByte();
room.curPicture = stream->readByte();
_state.rooms.push_back(room);
}
// Load item data from executable
stream->seek(IDI_HR1_OFS_ITEMS);
byte id;
while ((id = stream->readByte()) != 0xff) {
Item item;
item.id = id;
item.noun = stream->readByte();
item.room = stream->readByte();
item.picture = stream->readByte();
item.isShape = stream->readByte();
item.position.x = stream->readByte();
item.position.y = stream->readByte();
item.state = stream->readByte();
item.description = stream->readByte();
stream->readByte();
byte size = stream->readByte();
for (uint i = 0; i < size; ++i)
item.roomPictures.push_back(stream->readByte());
_state.items.push_back(item);
}
}
void HiRes1Engine::restartGame() {
_display->printString(_strings.pressReturn);
initState();
_display->printAsciiString(_strings.lineFeeds);
}
void HiRes1Engine::printString(const Common::String &str) {
Common::String wrap = str;
wordWrap(wrap);
_display->printString(wrap);
if (_messageDelay)
delay(getLanguage() == Common::FR_FRA ? 2900 : 2250);
}
Common::String HiRes1Engine::loadMessage(uint idx) const {
const char returnChar = _display->asciiToNative('\r');
Common::StreamPtr stream(_messages[idx]->createReadStream());
return readString(*stream, returnChar) + returnChar;
}
void HiRes1Engine::printMessage(uint idx) {
// In the English version, messages with hardcoded overrides don't delay
// after printing. It's unclear if this is a bug or not. In most cases
// the slow drawing of the room will give the player a chance to read
// it. This isn't the case in ScummVM however, so we add a delay after
// these messages.
// In the French version, messages with hardcoded overrides delay
// based on string length. This leads to overly long delays on longer
// strings. This might be a bug, since other messages have a fixed
// delay (that is slightly longer than the English version).
// We've chosen to stick with fixed delays here as well.
// NOTE: Later games wait for a key when the text window overflows and
// don't use delays. It might be better to use that system for this game
// as well.
switch (idx) {
case IDI_HR1_MSG_CANT_GO_THERE:
_display->printString(_gameStrings.cantGoThere);
break;
case IDI_HR1_MSG_DONT_HAVE_IT:
_display->printString(_gameStrings.dontHaveIt);
break;
case IDI_HR1_MSG_DONT_UNDERSTAND:
_display->printString(_gameStrings.dontUnderstand);
break;
case IDI_HR1_MSG_GETTING_DARK:
_display->printString(_gameStrings.gettingDark);
break;
default:
return printString(loadMessage(idx));
}
delay(1500);
}
void HiRes1Engine::drawItems() {
uint dropped = 0;
for (auto &item : _state.items) {
// Skip items not in this room
if (item.room != _state.room)
continue;
if (item.state == IDI_ITEM_DROPPED) {
// Draw dropped item if in normal view
if (getCurRoom().picture == getCurRoom().curPicture)
drawItem(item, _itemOffsets[dropped++]);
} else {
// Draw fixed item if current view is in the pic list
for (const auto &pic : item.roomPictures) {
if (pic == getCurRoom().curPicture) {
drawItem(item, item.position);
break;
}
}
}
}
}
void HiRes1Engine::drawItem(Item &item, const Common::Point &pos) {
if (item.isShape) {
Common::StreamPtr stream(_corners[item.picture - 1]->createReadStream());
Common::Point p(pos);
_graphics->drawShape(*stream, p);
} else
drawPic(item.picture, pos);
}
void HiRes1Engine::loadRoom(byte roomNr) {
_roomData.description = loadMessage(_roomDesc[_state.room - 1]);
}
void HiRes1Engine::showRoom() {
_state.curPicture = getCurRoom().curPicture;
_graphics->clearScreen();
loadRoom(_state.room);
if (!_state.isDark) {
drawPic(getCurRoom().curPicture);
drawItems();
}
_display->renderGraphics();
_messageDelay = false;
printString(_roomData.description);
_messageDelay = true;
}
void HiRes1Engine::wordWrap(Common::String &str) const {
uint end = 39;
const char spaceChar = _display->asciiToNative(' ');
const char returnChar = _display->asciiToNative('\r');
while (1) {
if (str.size() <= end)
return;
while (str[end] != spaceChar)
--end;
str.setChar(returnChar, end);
end += 40;
}
}
class HiRes1Engine_VF : public HiRes1Engine {
public:
HiRes1Engine_VF(OSystem *syst, const AdlGameDescription *gd) :
HiRes1Engine(syst, gd) { }
private:
// AdlEngine
void runIntro() override;
void getInput(uint &verb, uint &noun) override;
};
void HiRes1Engine_VF::getInput(uint &verb, uint &noun) {
// This version has a modified "parser"
while (1) {
_display->printString(_strings.enterCommand);
const Common::String line = getLine();
if (shouldQuit() || _isRestoring)
return;
uint index = 0;
Common::String verbString = getWord(line, index);
if (!_verbs.contains(verbString)) {
// If the verb is not found and it looks like an imperative, try to build the infinitive
const size_t ezPos = verbString.find("\xc5\xda"); // "EZ"
bool found = false;
if (ezPos != verbString.npos) {
const char *suf[] = { "\xc5\xd2", "\xc9\xd2", "\xd2\xc5", nullptr }; // "ER", "IR", "RE"
for (uint i = 0; suf[i]; ++i) {
verbString.replace(ezPos, 2, suf[i]);
if (_verbs.contains(verbString)) {
found = true;
break;
}
}
}
if (!found) {
_display->printString(formatVerbError(verbString));
continue;
}
}
verb = _verbs[verbString];
while (1) {
// Go over all nouns to find one we know. At the end of the string,
// it will always match the empty word (which is in the noun list).
// The original has a code path to return a noun error here, but
// it appears to be non-functional.
const Common::String nounString = getWord(line, index);
if (_nouns.contains(nounString)) {
noun = _nouns[nounString];
return;
}
}
}
}
void HiRes1Engine_VF::runIntro() {
Common::StreamPtr stream(_files->createReadStream(IDS_HR1_EXE_0));
stream->seek(0x1000);
// Title image is one padding byte short, so we first read it into a buffer
const uint frameBufferSize = 0x2000;
byte *const frameBuffer = (byte *)malloc(frameBufferSize);
if (stream->read(frameBuffer, frameBufferSize - 1) < frameBufferSize - 1)
error("Failed to read title image");
// Set missing byte
frameBuffer[frameBufferSize - 1] = 0;
Common::MemoryReadStream frameBufferStream(frameBuffer, frameBufferSize, DisposeAfterUse::YES);
_display->setMode(Display::kModeGraphics);
static_cast<Display_A2 *>(_display)->loadFrameBuffer(frameBufferStream);
_display->renderGraphics();
_display->setMode(Display::kModeMixed);
Common::String str = readStringAt(*stream, 0xf);
while (1) {
_display->printString(str);
const char key = inputKey();
if (shouldQuit())
return;
if (key == _display->asciiToNative('M')) {
stream->seek(0x75);
_display->sayText("M", Common::TextToSpeechManager::INTERRUPT);
showInstructions(*stream);
return;
} else if (key == _display->asciiToNative('J')) {
_display->sayText("J", Common::TextToSpeechManager::INTERRUPT);
return;
}
}
}
Engine *HiRes1Engine_create(OSystem *syst, const AdlGameDescription *gd) {
if (gd->version == GAME_VER_HR1_VF2)
return new HiRes1Engine_VF(syst, gd);
return new HiRes1Engine(syst, gd);
}
} // End of namespace Adl

187
engines/adl/hires2.cpp Normal file
View File

@@ -0,0 +1,187 @@
/* 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 "common/system.h"
#include "common/debug.h"
#include "common/error.h"
#include "common/file.h"
#include "common/stream.h"
#include "adl/adl_v2.h"
#include "adl/display_a2.h"
#include "adl/graphics.h"
#include "adl/disk.h"
namespace Adl {
class HiResBaseEngine : public AdlEngine_v2 {
public:
HiResBaseEngine(OSystem *syst, const AdlGameDescription *gd, const byte numRooms, const byte numMsgs, const byte numItemPics);
private:
// AdlEngine
void init() override;
void initGameState() override;
const byte _numRooms, _numMsgs, _numItemPics;
};
HiResBaseEngine::HiResBaseEngine(OSystem *syst, const AdlGameDescription *gd, const byte numRooms, const byte numMsgs, const byte numItemPics) :
AdlEngine_v2(syst, gd),
_numRooms(numRooms),
_numMsgs(numMsgs),
_numItemPics(numItemPics) {
_messageIds.cantGoThere = 110;
_messageIds.dontUnderstand = 112;
_messageIds.itemDoesntMove = 114;
_messageIds.itemNotHere = 115;
_messageIds.thanksForPlaying = 113;
}
void HiResBaseEngine::init() {
_graphics = new GraphicsMan_v2<Display_A2>(*static_cast<Display_A2 *>(_display));
_disk = new Common::DiskImage();
if (!_disk->open(getDiskImageName(0)))
error("Failed to open disk image '%s'", getDiskImageName(0).toString(Common::Path::kNativeSeparator).c_str());
_disk->setSectorLimit(13);
Common::StreamPtr stream(_disk->createReadStream(0x1f, 0x2, 0x00, 4));
loadMessages(*stream, _numMsgs);
stream.reset(_disk->createReadStream(0x19, 0x0, 0x00, 25, 13));
Common::StringArray exeStrings;
extractExeStrings(*stream, 0x1566, exeStrings);
mapExeStrings(exeStrings);
// Heuristic to test for early versions that differ slightly
// Later versions have two additional strings for "INIT DISK"
const bool oldEngine = exeStrings.size() < 13;
if (!oldEngine) {
stream.reset(_disk->createReadStream(0x19, 0x7, 0xd7));
_strings_v2.time = readString(*stream, 0xff);
}
// Load global picture data
stream.reset(_disk->createReadStream(0x19, 0xa, 0x80, 0));
loadPictures(*stream);
// Load item picture data
stream.reset(_disk->createReadStream(0x1e, 0x9, 0x05));
loadItemPictures(*stream, _numItemPics);
// Load commands from executable
stream.reset(_disk->createReadStream(0x1d, 0x7, 0x00, 4));
readCommands(*stream, _roomCommands);
stream.reset(_disk->createReadStream((oldEngine ? 0x19 : 0x1f), 0x7, 0x00, 3));
readCommands(*stream, _globalCommands);
// Load dropped item offsets
stream.reset(_disk->createReadStream(0x1b, 0x4, 0x15));
loadDroppedItemOffsets(*stream, 16);
// Load verbs
stream.reset(_disk->createReadStream(0x19, 0x0, 0x00, 3));
loadWords(*stream, _verbs, _priVerbs);
// Load nouns
stream.reset(_disk->createReadStream(0x22, 0x2, 0x00, 7));
loadWords(*stream, _nouns, _priNouns);
}
void HiResBaseEngine::initGameState() {
_state.vars.resize(40);
Common::StreamPtr stream(_disk->createReadStream(0x21, 0x5, 0x0e, 7));
loadRooms(*stream, _numRooms);
stream.reset(_disk->createReadStream(0x21, 0x0, 0x00, 2));
loadItems(*stream);
}
class HiRes2Engine : public HiResBaseEngine {
public:
HiRes2Engine(OSystem *syst, const AdlGameDescription *gd);
private:
// AdlEngine
void runIntro() override;
};
HiRes2Engine::HiRes2Engine(OSystem *syst, const AdlGameDescription *gd) :
HiResBaseEngine(syst, gd, 135, 255, 38) {
_messageIds.cantGoThere = 123;
_messageIds.dontUnderstand = 19;
_messageIds.itemDoesntMove = 242;
_messageIds.itemNotHere = 4;
_messageIds.thanksForPlaying = 239;
}
void HiRes2Engine::runIntro() {
// Only the Green Valley version has a title screen
if (_disk->getSectorsPerTrack() != 16)
return;
Common::StreamPtr stream(_disk->createReadStream(0x00, 0xd, 0x17, 1));
_display->setMode(Display::kModeText);
Common::String str = readString(*stream);
if (stream->eos() || stream->err())
error("Error reading disk image");
_display->printString(str);
delay(2000);
}
class HiRes3Engine : public HiResBaseEngine {
public:
HiRes3Engine(OSystem *syst, const AdlGameDescription *gd);
};
HiRes3Engine::HiRes3Engine(OSystem *syst, const AdlGameDescription *gd) :
HiResBaseEngine(syst, gd, 138, 255, 36) {
const byte brokenRooms[] = { 18, 24, 54, 98, 102, 108 };
for (int i = 0; i < ARRAYSIZE(brokenRooms); ++i)
_brokenRooms.push_back(brokenRooms[i]);
}
Engine *HiRes2Engine_create(OSystem *syst, const AdlGameDescription *gd) {
return new HiRes2Engine(syst, gd);
}
Engine *HiRes0Engine_create(OSystem *syst, const AdlGameDescription *gd) {
return new HiResBaseEngine(syst, gd, 43, 142, 2);
}
Engine *HiRes3Engine_create(OSystem *syst, const AdlGameDescription *gd) {
return new HiRes3Engine(syst, gd);
}
} // End of namespace Adl

822
engines/adl/hires4.cpp Normal file
View File

@@ -0,0 +1,822 @@
/* 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 "common/system.h"
#include "common/debug.h"
#include "common/error.h"
#include "common/file.h"
#include "common/stream.h"
#include "common/events.h"
#include "common/memstream.h"
#include "adl/adl_v3.h"
#include "adl/detection.h"
#include "adl/display_a2.h"
#include "adl/graphics.h"
#include "adl/disk.h"
namespace Adl {
#define IDI_HR4_NUM_ROOMS 164
#define IDI_HR4_NUM_MESSAGES 255
#define IDI_HR4_NUM_VARS 40
#define IDI_HR4_NUM_ITEM_DESCS 44
#define IDI_HR4_NUM_ITEM_PICS 41
#define IDI_HR4_NUM_ITEM_OFFSETS 16
// Messages used outside of scripts
#define IDI_HR4_MSG_CANT_GO_THERE 110
#define IDI_HR4_MSG_DONT_UNDERSTAND 112
#define IDI_HR4_MSG_ITEM_DOESNT_MOVE 114
#define IDI_HR4_MSG_ITEM_NOT_HERE 115
#define IDI_HR4_MSG_THANKS_FOR_PLAYING 113
class HiRes4BaseEngine : public AdlEngine_v3 {
public:
HiRes4BaseEngine(OSystem *syst, const AdlGameDescription *gd);
~HiRes4BaseEngine() override;
protected:
// AdlEngine
void init() override;
void initGameState() override;
Common::DiskImage *_boot;
};
HiRes4BaseEngine::HiRes4BaseEngine(OSystem *syst, const AdlGameDescription *gd) :
AdlEngine_v3(syst, gd),
_boot(nullptr) {
_brokenRooms.push_back(121);
_messageIds.cantGoThere = IDI_HR4_MSG_CANT_GO_THERE;
_messageIds.dontUnderstand = IDI_HR4_MSG_DONT_UNDERSTAND;
_messageIds.itemDoesntMove = IDI_HR4_MSG_ITEM_DOESNT_MOVE;
_messageIds.itemNotHere = IDI_HR4_MSG_ITEM_NOT_HERE;
_messageIds.thanksForPlaying = IDI_HR4_MSG_THANKS_FOR_PLAYING;
}
HiRes4BaseEngine::~HiRes4BaseEngine() {
delete _boot;
}
void HiRes4BaseEngine::init() {
_graphics = new GraphicsMan_v2<Display_A2>(*static_cast<Display_A2 *>(_display));
_boot = new Common::DiskImage();
if (!_boot->open(getDiskImageName(0)))
error("Failed to open disk image '%s'", getDiskImageName(0).toString(Common::Path::kNativeSeparator).c_str());
insertDisk(1);
}
void HiRes4BaseEngine::initGameState() {
_state.vars.resize(IDI_HR4_NUM_VARS);
}
class HiRes4Engine_v1_0 : public HiRes4BaseEngine {
public:
HiRes4Engine_v1_0(OSystem *syst, const AdlGameDescription *gd) : HiRes4BaseEngine(syst, gd) { }
private:
// AdlEngine
void runIntro() override;
void init() override;
void initGameState() override;
};
void HiRes4Engine_v1_0::runIntro() {
Common::StreamPtr stream(_boot->createReadStream(0x06, 0x3, 0xb9, 1));
_display->setMode(Display::kModeText);
Common::String str = readString(*stream);
if (stream->eos() || stream->err())
error("Error reading disk image");
_display->printString(str);
waitKey(0, Common::KEYCODE_RETURN);
}
void HiRes4Engine_v1_0::init() {
HiRes4BaseEngine::init();
Common::StreamPtr stream(_boot->createReadStream(0x9, 0x1, 0x00, 13));
Common::StringArray exeStrings;
extractExeStrings(*stream, 0x1566, exeStrings);
mapExeStrings(exeStrings);
stream.reset(_boot->createReadStream(0x0e, 0x5, 0x00, 3, 13));
loadMessages(*stream, IDI_HR4_NUM_MESSAGES);
stream.reset(_boot->createReadStream(0x09, 0x0, 0x80, 0, 13));
loadPictures(*stream);
stream.reset(_boot->createReadStream(0x0d, 0xc, 0x05, 0, 13));
loadItemPictures(*stream, IDI_HR4_NUM_ITEM_PICS);
stream.reset(_boot->createReadStream(0x07, 0x0, 0x15, 2, 13));
loadItemDescriptions(*stream, IDI_HR4_NUM_ITEM_DESCS);
stream.reset(_boot->createReadStream(0x0c, 0x9, 0xa5, 5, 13));
readCommands(*stream, _roomCommands);
stream.reset(_boot->createReadStream(0x07, 0xc, 0x00, 3, 13));
readCommands(*stream, _globalCommands);
stream.reset(_boot->createReadStream(0x0a, 0x7, 0x15, 0, 13));
loadDroppedItemOffsets(*stream, IDI_HR4_NUM_ITEM_OFFSETS);
stream.reset(_boot->createReadStream(0x08, 0x3, 0x00, 3, 13));
loadWords(*stream, _verbs, _priVerbs, 80); // Missing terminator
stream.reset(_boot->createReadStream(0x05, 0x7, 0x00, 6, 13));
loadWords(*stream, _nouns, _priNouns, 109); // Missing terminator
}
void HiRes4Engine_v1_0::initGameState() {
HiRes4BaseEngine::initGameState();
Common::StreamPtr stream(_boot->createReadStream(0x04, 0xa, 0x0e, 9, 13));
loadRooms(*stream, IDI_HR4_NUM_ROOMS);
stream.reset(_boot->createReadStream(0x04, 0x5, 0x00, 12, 13));
loadItems(*stream);
}
class HiRes4Engine_v1_1 : public HiRes4BaseEngine {
public:
HiRes4Engine_v1_1(OSystem *syst, const AdlGameDescription *gd) : HiRes4BaseEngine(syst, gd) { }
private:
// AdlEngine
void runIntro() override;
void init() override;
void initGameState() override;
};
// TODO: It might be worth replacing this with a more generic variant that
// can be used in both hires4 and hires6
static Common::MemoryReadStream *readSkewedSectors(Common::DiskImage *disk, byte track, byte sector, byte count) {
const uint bytesPerSector = disk->getBytesPerSector();
const uint sectorsPerTrack = disk->getSectorsPerTrack();
const uint bufSize = count * bytesPerSector;
byte *const buf = (byte *)malloc(bufSize);
byte *p = buf;
while (count-- != 0) {
Common::StreamPtr stream(disk->createReadStream(track, sector));
stream->read(p, bytesPerSector);
if (stream->err() || stream->eos())
error("Error loading from disk image");
p += bytesPerSector;
sector += 5;
sector %= sectorsPerTrack;
if (sector == 0)
++track;
}
return new Common::MemoryReadStream(buf, bufSize, DisposeAfterUse::YES);
}
static Common::MemoryReadStream *decodeData(Common::SeekableReadStream &stream, const uint startOffset, uint endOffset, const byte xorVal) {
assert(stream.size() >= 0);
uint streamSize(stream.size());
if (endOffset > streamSize)
endOffset = streamSize;
byte *const buf = (byte *)malloc(streamSize);
stream.read(buf, streamSize);
if (stream.err() || stream.eos())
error("Failed to read data for decoding");
for (uint i = startOffset; i < endOffset; ++i)
buf[i] ^= xorVal;
return new Common::MemoryReadStream(buf, streamSize, DisposeAfterUse::YES);
}
void HiRes4Engine_v1_1::runIntro() {
Common::ScopedPtr<Common::Files_AppleDOS> files(new Common::Files_AppleDOS());
files->open(getDiskImageName(0));
Common::StreamPtr menu(files->createReadStream("\b\b\b\b\b\b\bULYSSES\r(C) 1982"));
menu->seek(0x2eb);
for (uint i = 0; i < 4; ++i) {
const int16 y[4] = { 0, 2, 4, 16 };
Common::String s = menu->readString(0, 39);
_display->moveCursorTo(Common::Point(0, y[i]));
_display->printString(s);
}
waitKey(3000);
}
void HiRes4Engine_v1_1::init() {
HiRes4BaseEngine::init();
Common::StreamPtr stream(readSkewedSectors(_boot, 0x05, 0x6, 1));
_strings.verbError = readStringAt(*stream, 0x4f);
_strings.nounError = readStringAt(*stream, 0x8e);
_strings.enterCommand = readStringAt(*stream, 0xbc);
stream.reset(readSkewedSectors(_boot, 0x05, 0x3, 1));
stream->skip(0xd7);
_strings_v2.time = readString(*stream, 0xff);
stream.reset(readSkewedSectors(_boot, 0x05, 0x7, 2));
_strings.lineFeeds = readStringAt(*stream, 0xf8);
stream.reset(readSkewedSectors(_boot, 0x06, 0xf, 3));
_strings_v2.saveInsert = readStringAt(*stream, 0x5f);
_strings_v2.saveReplace = readStringAt(*stream, 0xe5);
_strings_v2.restoreInsert = readStringAt(*stream, 0x132);
_strings_v2.restoreReplace = readStringAt(*stream, 0x1c2);
_strings.playAgain = readStringAt(*stream, 0x225);
stream.reset(readSkewedSectors(_boot, 0x0a, 0x0, 5));
loadMessages(*stream, IDI_HR4_NUM_MESSAGES);
stream.reset(readSkewedSectors(_boot, 0x05, 0x2, 1));
stream->skip(0x80);
loadPictures(*stream);
stream.reset(readSkewedSectors(_boot, 0x09, 0x2, 1));
stream->skip(0x05);
loadItemPictures(*stream, IDI_HR4_NUM_ITEM_PICS);
stream.reset(readSkewedSectors(_boot, 0x04, 0x0, 3));
stream->skip(0x15);
loadItemDescriptions(*stream, IDI_HR4_NUM_ITEM_DESCS);
stream.reset(readSkewedSectors(_boot, 0x08, 0x2, 6));
stream->skip(0xa5);
readCommands(*stream, _roomCommands);
stream.reset(readSkewedSectors(_boot, 0x04, 0xc, 4));
stream.reset(decodeData(*stream, 0x218, 0x318, 0xee));
readCommands(*stream, _globalCommands);
stream.reset(readSkewedSectors(_boot, 0x06, 0x6, 1));
stream->skip(0x15);
loadDroppedItemOffsets(*stream, IDI_HR4_NUM_ITEM_OFFSETS);
stream.reset(readSkewedSectors(_boot, 0x05, 0x0, 4));
loadWords(*stream, _verbs, _priVerbs);
stream.reset(readSkewedSectors(_boot, 0x0b, 0xb, 7));
loadWords(*stream, _nouns, _priNouns);
}
void HiRes4Engine_v1_1::initGameState() {
HiRes4BaseEngine::initGameState();
Common::StreamPtr stream(readSkewedSectors(_boot, 0x0b, 0x9, 10));
stream->skip(0x0e);
loadRooms(*stream, IDI_HR4_NUM_ROOMS);
stream.reset(readSkewedSectors(_boot, 0x0b, 0x0, 13));
stream.reset(decodeData(*stream, 0x43, 0x143, 0x91));
loadItems(*stream);
}
class HiRes4Engine_LNG : public HiRes4Engine_v1_1 {
public:
HiRes4Engine_LNG(OSystem *syst, const AdlGameDescription *gd) : HiRes4Engine_v1_1(syst, gd) { }
private:
// AdlEngine
void runIntro() override;
void putSpace(uint x, uint y) const;
void drawChar(byte c, Common::SeekableReadStream &shapeTable, Common::Point &pos) const;
void drawText(const Common::String &str, Common::SeekableReadStream &shapeTable, const float ht, const float vt) const;
void runIntroLogo(Common::SeekableReadStream &ms2);
void runIntroTitle(Common::SeekableReadStream &menu, Common::SeekableReadStream &ms2);
void runIntroInstructions(Common::SeekableReadStream &instructions);
void runIntroLoading(Common::SeekableReadStream &adventure);
static const uint kClock = 1022727; // Apple II CPU clock rate
};
void HiRes4Engine_LNG::putSpace(uint x, uint y) const {
if (shouldQuit())
return;
_display->moveCursorTo(Common::Point(x, y));
_display->printChar(' ');
_display->renderText();
delay(2);
}
void HiRes4Engine_LNG::drawChar(byte c, Common::SeekableReadStream &shapeTable, Common::Point &pos) const {
shapeTable.seek(0);
byte entries = shapeTable.readByte();
if (c >= entries)
error("Character %d is not in the shape table", c);
shapeTable.seek(c * 2 + 2);
uint16 offset = shapeTable.readUint16LE();
shapeTable.seek(offset);
_graphics->drawShape(shapeTable, pos);
}
void HiRes4Engine_LNG::drawText(const Common::String &str, Common::SeekableReadStream &shapeTable, const float ht, const float vt) const {
if (shouldQuit())
return;
Common::Point pos((int16)(ht * 7), (int16)(vt * 7.7f));
drawChar(99, shapeTable, pos);
for (uint i = 0; i < str.size(); ++i) {
const byte c = str[i] - 32;
drawChar(c, shapeTable, pos);
drawChar(98, shapeTable, pos);
_display->renderGraphics();
delay(15);
}
}
void HiRes4Engine_LNG::runIntroLogo(Common::SeekableReadStream &ms2) {
Display_A2 *display = static_cast<Display_A2 *>(_display);
const uint width = display->getGfxWidth();
const uint height = display->getGfxHeight();
const uint pitch = display->getGfxPitch();
display->clear(0x00);
display->setMode(Display::kModeGraphics);
byte *logo = new byte[pitch * height];
display->loadFrameBuffer(ms2, logo);
for (uint x = 0; x < width; ++x) {
for (uint y = 0; y < height; ++y) {
const byte p = logo[y * pitch + x / 7];
display->setPixelBit(Common::Point(x, y), p);
if (x % 7 == 6)
display->setPixelPalette(Common::Point(x, y), p);
}
display->renderGraphics();
if (shouldQuit()) {
delete[] logo;
return;
}
delay(7);
}
delete[] logo;
for (uint i = 38; i != 0; --i) {
Common::Point p;
for (p.y = 1; p.y < (int)height; ++p.y)
for (p.x = 0; p.x < (int)width; p.x += 7)
display->setPixelByte(Common::Point(p.x, p.y - 1), display->getPixelByte(p));
display->renderGraphics();
Tones tone;
tone.push_back(Tone(kClock / 2.0 / ((i * 4 + 1) * 10.0 + 10.0), 12.5));
playTones(tone, false, false);
if (shouldQuit())
return;
}
}
void HiRes4Engine_LNG::runIntroTitle(Common::SeekableReadStream &menu, Common::SeekableReadStream &ms2) {
ms2.seek(0x2290);
Common::StreamPtr shapeTable(ms2.readStream(0x450));
if (ms2.err() || ms2.eos())
error("Failed to read shape table");
Common::String titleString(readStringAt(menu, 0x1f5, '"'));
drawText(titleString, *shapeTable, 4.0f, 22.5f);
titleString = readStringAt(menu, 0x22b, '"');
drawText(titleString, *shapeTable, 5.0f, 24.0f);
// Draw "TM" with lines
_graphics->drawLine(Common::Point(200, 170), Common::Point(200, 174), 0x7f);
_graphics->drawLine(Common::Point(198, 170), Common::Point(202, 170), 0x7f);
_display->renderGraphics();
delay(7);
_graphics->drawLine(Common::Point(204, 170), Common::Point(204, 174), 0x7f);
_graphics->drawLine(Common::Point(204, 170), Common::Point(207, 173), 0x7f);
_graphics->drawLine(Common::Point(207, 173), Common::Point(209, 170), 0x7f);
_graphics->drawLine(Common::Point(209, 170), Common::Point(209, 174), 0x7f);
_display->renderGraphics();
delay(7);
titleString = readStringAt(menu, 0x46c);
drawText(titleString, *shapeTable, 20.0f - titleString.size() / 2.0f, 10.6f);
titleString = readStringAt(menu, 0x490);
drawText(titleString, *shapeTable, 20.0f - titleString.size() / 2.0f, 11.8f);
Common::StringArray menuStrings;
menuStrings.push_back(readStringAt(menu, 0x515));
menuStrings.push_back(readStringAt(menu, 0x52b));
for (uint i = 0; i < menuStrings.size(); ++i)
drawText(Common::String::format("%d) ", i + 1) + menuStrings[i], *shapeTable, 12.5f, 14.0f + i * 1.2f);
titleString = readStringAt(menu, 0x355, '"');
drawText(titleString, *shapeTable, 12.5f, 14.0f + menuStrings.size() * 1.2f + 2.0f);
}
void HiRes4Engine_LNG::runIntroInstructions(Common::SeekableReadStream &instructions) {
Common::String line;
Common::String pressKey(readStringAt(instructions, 0xad6, '"'));
instructions.seek(0);
_display->home();
_display->setMode(Display::kModeText);
// Search for PRINT commands in tokenized BASIC
while (1) {
char c;
do {
c = instructions.readByte();
if (instructions.err() || instructions.eos())
error("Error reading instructions file");
// GOSUB (calls "press any key" routine)
if (c == (char)0xb0) {
_display->moveCursorTo(Common::Point(6, 23));
_display->printAsciiString(pressKey);
inputKey();
if (shouldQuit())
return;
_display->home();
}
} while (c != (char)0xba); // PRINT
uint quotes = 0;
while (1) {
c = instructions.readByte();
if (instructions.err() || instructions.eos())
error("Error reading instructions file");
if (c == '"') {
++quotes;
continue;
}
if (c == 0)
break;
if (quotes == 1)
line += c;
else if (c == ':') // Separator
break;
else if (c == '4') // CTRL-D before "RUN MENU"
return;
};
line += '\r';
_display->printAsciiString(line);
line.clear();
}
}
void HiRes4Engine_LNG::runIntroLoading(Common::SeekableReadStream &adventure) {
_display->home();
_display->setMode(Display::kModeText);
const uint kStrings = 4;
const uint kStringLen = 39;
char text[kStrings][kStringLen];
adventure.seek(0x2eb);
if (adventure.read(text, sizeof(text)) < sizeof(text))
error("Failed to read loading screen text");
const uint yPos[kStrings] = { 2, 19, 8, 22 };
for (uint i = 0; i < kStrings; ++i) {
_display->moveCursorTo(Common::Point(0, yPos[i]));
_display->printString(Common::String(text[i], kStringLen));
}
waitKey(3000);
}
void HiRes4Engine_LNG::runIntro() {
Common::ScopedPtr<Common::Files_AppleDOS> files(new Common::Files_AppleDOS());
files->open(getDiskImageName(0));
while (!shouldQuit()) {
Common::StreamPtr menu(files->createReadStream("MENU"));
const bool oldVersion = files->exists("MS2");
if (oldVersion) {
// Version 0.0
Common::StreamPtr ms2(files->createReadStream("MS2"));
runIntroLogo(*ms2);
if (shouldQuit())
return;
_graphics->setBounds(Common::Rect(280, 192));
runIntroTitle(*menu, *ms2);
_graphics->setBounds(Common::Rect(280, 160));
} else {
// Version 1.1
// This version also has a publisher logo, but it uses BASIC
// graphics routines that have not been implemented, so we skip it.
// File offset, x and y coordinates for each line of text in the title screen
// We skip the "create data disk" menu option
const uint text[][3] = { { 0x13, 4, 1 }, { 0x42, 7, 9 }, { 0x66, 7, 11 }, { 0xaa, 7, 17 } };
for (uint i = 0; i < ARRAYSIZE(text); ++i) {
Common::String str = readStringAt(*menu, text[i][0], '"');
for (char &c : str) {
c = _display->asciiToNative(c);
if (i == 0)
c &= ~0xc0; // Invert first line
}
_display->moveCursorTo(Common::Point(text[i][1], text[i][2]));
_display->printString(str);
}
}
while (1) {
const char key = inputKey(false);
if (shouldQuit())
return;
if (key == _display->asciiToNative('1')) {
Common::StreamPtr instructions(files->createReadStream("INSTRUCTIONS"));
runIntroInstructions(*instructions);
break;
} else if (key == _display->asciiToNative('2')) {
Common::StreamPtr adventure(files->createReadStream("THE ADVENTURE"));
runIntroLoading(*adventure);
return;
}
};
}
}
class HiRes4Engine_Atari : public AdlEngine_v3 {
public:
HiRes4Engine_Atari(OSystem *syst, const AdlGameDescription *gd) :
AdlEngine_v3(syst, gd),
_boot(nullptr),
_curDisk(0) { _brokenRooms.push_back(121); }
~HiRes4Engine_Atari() override;
private:
// AdlEngine
void init() override;
void initGameState() override;
void loadRoom(byte roomNr) override;
Common::String formatVerbError(const Common::String &verb) const override;
Common::String formatNounError(const Common::String &verb, const Common::String &noun) const override;
// AdlEngine_v2
void adjustDataBlockPtr(byte &track, byte &sector, byte &offset, byte &size) const override;
Common::SeekableReadStream *createReadStream(Common::DiskImage *disk, byte track, byte sector, byte offset = 0, byte size = 0) const;
void loadCommonData();
void insertDisk(byte diskNr);
void rebindDisk();
Common::DiskImage *_boot;
byte _curDisk;
};
static const char *const atariDisks[] = { "ULYS1A.XFD", "ULYS1B.XFD", "ULYS2C.XFD" };
HiRes4Engine_Atari::~HiRes4Engine_Atari() {
delete _boot;
}
void HiRes4Engine_Atari::init() {
_graphics = new GraphicsMan_v2<Display_A2>(*static_cast<Display_A2 *>(_display));
_boot = new Common::DiskImage();
if (!_boot->open(atariDisks[0]))
error("Failed to open disk image '%s'", atariDisks[0]);
insertDisk(1);
loadCommonData();
Common::StreamPtr stream(createReadStream(_boot, 0x06, 0x2));
_strings.verbError = readStringAt(*stream, 0x4f);
_strings.nounError = readStringAt(*stream, 0x83);
_strings.enterCommand = readStringAt(*stream, 0xa6);
stream.reset(createReadStream(_boot, 0x05, 0xb, 0xd7));
_strings_v2.time = readString(*stream, 0xff);
stream.reset(createReadStream(_boot, 0x06, 0x7, 0x00, 2));
_strings_v2.saveInsert = readStringAt(*stream, 0x62);
_strings_v2.saveReplace = readStringAt(*stream, 0xdd);
_strings_v2.restoreInsert = readStringAt(*stream, 0x12a);
_strings_v2.restoreReplace = readStringAt(*stream, 0x1b8);
_strings.playAgain = readStringAt(*stream, 0x21b);
// TODO: restart sequence has "insert side a/b" strings
_messageIds.cantGoThere = IDI_HR4_MSG_CANT_GO_THERE;
_messageIds.dontUnderstand = IDI_HR4_MSG_DONT_UNDERSTAND;
_messageIds.itemDoesntMove = IDI_HR4_MSG_ITEM_DOESNT_MOVE;
_messageIds.itemNotHere = IDI_HR4_MSG_ITEM_NOT_HERE;
_messageIds.thanksForPlaying = IDI_HR4_MSG_THANKS_FOR_PLAYING;
stream.reset(createReadStream(_boot, 0x06, 0xd, 0x12, 2));
loadItemDescriptions(*stream, IDI_HR4_NUM_ITEM_DESCS);
stream.reset(createReadStream(_boot, 0x07, 0x1, 0xf4));
loadDroppedItemOffsets(*stream, IDI_HR4_NUM_ITEM_OFFSETS);
stream.reset(createReadStream(_boot, 0x08, 0xe, 0xa5, 5));
readCommands(*stream, _roomCommands);
stream.reset(createReadStream(_boot, 0x0a, 0x9, 0x00, 3));
readCommands(*stream, _globalCommands);
stream.reset(createReadStream(_boot, 0x05, 0x4, 0x00, 3));
loadWords(*stream, _verbs, _priVerbs);
stream.reset(createReadStream(_boot, 0x03, 0xb, 0x00, 6));
loadWords(*stream, _nouns, _priNouns);
}
void HiRes4Engine_Atari::loadRoom(byte roomNr) {
if (roomNr >= 59 && roomNr < 113) {
if (_curDisk != 2) {
insertDisk(2);
rebindDisk();
}
} else if (_curDisk != 1) {
insertDisk(1);
rebindDisk();
}
AdlEngine_v3::loadRoom(roomNr);
}
Common::String HiRes4Engine_Atari::formatVerbError(const Common::String &verb) const {
Common::String err = _strings.verbError;
for (uint i = 0; i < verb.size(); ++i)
err.setChar(verb[i], i + 8);
return err;
}
Common::String HiRes4Engine_Atari::formatNounError(const Common::String &verb, const Common::String &noun) const {
Common::String err = _strings.nounError;
for (uint i = 0; i < verb.size(); ++i)
err.setChar(verb[i], i + 8);
for (uint i = 0; i < noun.size(); ++i)
err.setChar(noun[i], i + 19);
return err;
}
void HiRes4Engine_Atari::insertDisk(byte diskNr) {
if (_curDisk == diskNr)
return;
_curDisk = diskNr;
delete _disk;
_disk = new Common::DiskImage();
if (!_disk->open(atariDisks[diskNr]))
error("Failed to open disk image '%s'", atariDisks[diskNr]);
}
void HiRes4Engine_Atari::rebindDisk() {
// As room.data is bound to the Common::DiskImage, we need to rebind them here
// We cannot simply reload the rooms as that would reset their state
// FIXME: Remove DataBlockPtr-Common::DiskImage coupling?
Common::StreamPtr stream(createReadStream(_boot, 0x03, 0x1, 0x0e, 9));
for (uint i = 0; i < IDI_HR4_NUM_ROOMS; ++i) {
stream->skip(7);
_state.rooms[i].data = readDataBlockPtr(*stream);
stream->skip(3);
}
// Rebind data that is on both side B and C
loadCommonData();
}
void HiRes4Engine_Atari::loadCommonData() {
_messages.clear();
Common::StreamPtr stream(createReadStream(_boot, 0x0a, 0x4, 0x00, 3));
loadMessages(*stream, IDI_HR4_NUM_MESSAGES);
_pictures.clear();
stream.reset(createReadStream(_boot, 0x05, 0xe, 0x80));
loadPictures(*stream);
_itemPics.clear();
stream.reset(createReadStream(_boot, 0x09, 0xe, 0x05));
loadItemPictures(*stream, IDI_HR4_NUM_ITEM_PICS);
}
void HiRes4Engine_Atari::initGameState() {
_state.vars.resize(IDI_HR4_NUM_VARS);
Common::StreamPtr stream(createReadStream(_boot, 0x03, 0x1, 0x0e, 9));
loadRooms(*stream, IDI_HR4_NUM_ROOMS);
stream.reset(createReadStream(_boot, 0x02, 0xc, 0x00, 12));
loadItems(*stream);
// FIXME
_display->moveCursorTo(Common::Point(0, 23));
}
Common::SeekableReadStream *HiRes4Engine_Atari::createReadStream(Common::DiskImage *disk, byte track, byte sector, byte offset, byte size) const {
adjustDataBlockPtr(track, sector, offset, size);
return disk->createReadStream(track, sector, offset, size);
}
void HiRes4Engine_Atari::adjustDataBlockPtr(byte &track, byte &sector, byte &offset, byte &size) const {
// Convert the Apple II disk offsets in the game, to Atari disk offsets
uint sectorIndex = (track * 16 + sector + 1) << 1;
// Atari uses 128 bytes per sector vs. 256 on the Apple II
// Note that size indicates *additional* sectors to read after reading one sector
size *= 2;
if (offset >= 128) {
// Offset in the second half of an Apple II sector, skip one sector and adjust offset
++sectorIndex;
offset -= 128;
} else {
// Offset in the first half of an Apple II sector, we need to read one additional sector
++size;
}
// Compute track/sector for Atari's 18 sectors per track (sectorIndex is 1-based)
track = (sectorIndex - 1) / 18;
sector = (sectorIndex - 1) % 18;
}
Engine *HiRes4Engine_create(OSystem *syst, const AdlGameDescription *gd) {
switch (getPlatform(*gd)) {
case Common::kPlatformApple2:
switch (getGameVersion(*gd)) {
case GAME_VER_HR4_LNG:
return new HiRes4Engine_LNG(syst, gd);
case GAME_VER_HR4_V1_1:
return new HiRes4Engine_v1_1(syst, gd);
default:
return new HiRes4Engine_v1_0(syst, gd);
}
case Common::kPlatformAtari8Bit:
return new HiRes4Engine_Atari(syst, gd);
default:
error("Unsupported platform");
}
}
} // End of namespace Adl

405
engines/adl/hires5.cpp Normal file
View File

@@ -0,0 +1,405 @@
/* 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 "common/system.h"
#include "common/debug.h"
#include "common/error.h"
#include "common/file.h"
#include "common/stream.h"
#include "adl/adl_v4.h"
#include "adl/detection.h"
#include "adl/display_a2.h"
#include "adl/graphics.h"
#include "adl/disk.h"
#include "adl/sound.h"
namespace Adl {
class HiRes5Engine : public AdlEngine_v4 {
public:
HiRes5Engine(OSystem *syst, const AdlGameDescription *gd) :
AdlEngine_v4(syst, gd),
_doAnimation(false) { }
private:
// AdlEngine
void setupOpcodeTables() override;
void runIntro() override;
void init() override;
void initGameState() override;
void applyRegionWorkarounds() override;
void applyRoomWorkarounds(byte roomNr) override;
Common::String getLine() override;
// AdlEngine_v4
bool isInventoryFull() override;
void loadSong(Common::ReadStream &stream);
void drawLight(uint index, byte color) const;
void animateLights() const;
int o_checkItemTimeLimits(ScriptEnv &e);
int o_startAnimation(ScriptEnv &e);
int o_winGame(ScriptEnv &e);
static const uint kClock = 1022727; // Apple II CPU clock rate
static const uint kRegions = 41;
static const uint kItems = 69;
Common::Array<byte> _itemTimeLimits;
Common::String _itemTimeLimitMsg;
Tones _song;
bool _doAnimation;
struct {
Common::String itemTimeLimit;
Common::String carryingTooMuch;
} _gameStrings;
};
Common::String HiRes5Engine::getLine() {
if (_doAnimation) {
animateLights();
_doAnimation = false;
}
return AdlEngine_v4::getLine();
}
void HiRes5Engine::drawLight(uint index, byte color) const {
Display_A2 *display = static_cast<Display_A2 *>(_display);
const byte xCoord[5] = { 189, 161, 133, 105, 77 };
const byte yCoord = 72;
assert(index < 5);
for (int yDelta = 0; yDelta < 4; ++yDelta)
for (int xDelta = 0; xDelta < 7; ++xDelta)
display->putPixel(Common::Point(xCoord[index] + xDelta, yCoord + yDelta), color);
display->renderGraphics();
}
void HiRes5Engine::animateLights() const {
// Skip this if we're running a debug script
if (_inputScript)
return;
int index;
byte color = 0x2a;
for (index = 4; index >= 0; --index)
drawLight(index, color);
index = 4;
while (!g_engine->shouldQuit()) {
drawLight(index, color ^ 0x7f);
// There's a delay here in the original engine. We leave it out as
// we're already slower than the original without any delay.
const uint kLoopCycles = 25;
const byte period = (index + 1) << 4;
const double freq = kClock / 2.0 / (period * kLoopCycles);
const double len = 128 * period * kLoopCycles * 1000 / (double)kClock;
Tones tone;
tone.push_back(Tone(freq, len));
if (playTones(tone, false, true))
break;
drawLight(index, color ^ 0xff);
if (--index < 0) {
index = 4;
color ^= 0xff;
}
}
}
void HiRes5Engine::setupOpcodeTables() {
AdlEngine_v4::setupOpcodeTables();
_actOpcodes[0x0b] = opcode(&HiRes5Engine::o_checkItemTimeLimits);
_actOpcodes[0x13] = opcode(&HiRes5Engine::o_startAnimation);
_actOpcodes[0x1e] = opcode(&HiRes5Engine::o_winGame);
}
bool HiRes5Engine::isInventoryFull() {
byte weight = 0;
for (const auto &item : _state.items) {
if (item.room == IDI_ANY)
weight += item.description;
}
if (weight >= 100) {
_display->printString(_gameStrings.carryingTooMuch);
inputString();
return true;
}
return false;
}
void HiRes5Engine::loadSong(Common::ReadStream &stream) {
while (true) {
const byte period = stream.readByte();
if (stream.err() || stream.eos())
error("Error loading song");
if (period == 0xff)
return;
byte length = stream.readByte();
if (stream.err() || stream.eos())
error("Error loading song");
const uint kLoopCycles = 20; // Delay loop cycles
double freq = 0.0;
// This computation ignores CPU cycles spent on overflow handling and
// speaker control. As a result, our tone frequencies will be slightly
// higher than those on original hardware.
if (period != 0)
freq = kClock / 2.0 / (period * kLoopCycles);
const double len = (length > 0 ? length - 1 : 255) * 256 * kLoopCycles * 1000 / (double)kClock;
_song.push_back(Tone(freq, len));
}
}
int HiRes5Engine::o_checkItemTimeLimits(ScriptEnv &e) {
OP_DEBUG_1("\tCHECK_ITEM_TIME_LIMITS(VARS[%d])", e.arg(1));
bool lostAnItem = false;
for (auto &item : _state.items) {
const byte room = item.room;
const byte region = item.region;
if (room == IDI_ANY || room == IDI_CUR_ROOM || (room == _state.room && region == _state.region)) {
if (getVar(e.arg(1)) < _itemTimeLimits[item.id - 1]) {
item.room = IDI_VOID_ROOM;
lostAnItem = true;
}
}
}
if (lostAnItem) {
_display->printString(_gameStrings.itemTimeLimit);
inputString();
}
return 1;
}
int HiRes5Engine::o_startAnimation(ScriptEnv &e) {
OP_DEBUG_0("\tSTART_ANIMATION()");
_doAnimation = true;
return 0;
}
int HiRes5Engine::o_winGame(ScriptEnv &e) {
OP_DEBUG_0("\tWIN_GAME()");
showRoom();
playTones(_song, true);
return o_quit(e);
}
void HiRes5Engine::runIntro() {
Display_A2 *display = static_cast<Display_A2 *>(_display);
insertDisk(2);
Common::StreamPtr stream(_disk->createReadStream(0x10, 0x0, 0x00, 31));
display->setMode(Display::kModeGraphics);
display->loadFrameBuffer(*stream);
display->renderGraphics();
inputKey();
display->home();
display->setMode(Display::kModeText);
stream.reset(_disk->createReadStream(0x03, 0xc, 0x34, 1));
Common::String menu(readString(*stream));
while (!g_engine->shouldQuit()) {
display->home();
display->printString(menu);
Common::String cmd(inputString());
// We ignore the backup and format menu options
if (!cmd.empty() && cmd[0] == _display->asciiToNative('1'))
break;
};
}
void HiRes5Engine::init() {
_graphics = new GraphicsMan_v3<Display_A2>(*static_cast<Display_A2 *>(_display));
insertDisk(2);
Common::StreamPtr stream(_disk->createReadStream(0x5, 0x0, 0x02));
loadRegionLocations(*stream, kRegions);
stream.reset(_disk->createReadStream(0xd, 0x2, 0x04));
loadRegionInitDataOffsets(*stream, kRegions);
stream.reset(_disk->createReadStream(0x7, 0xe));
_strings.verbError = readStringAt(*stream, 0x4f);
_strings.nounError = readStringAt(*stream, 0x8e);
_strings.enterCommand = readStringAt(*stream, 0xbc);
stream.reset(_disk->createReadStream(0x7, 0xc));
_strings.lineFeeds = readString(*stream);
stream.reset(_disk->createReadStream(0x8, 0x3, 0x00, 2));
_strings_v2.saveInsert = readStringAt(*stream, 0x66);
_strings_v2.saveReplace = readStringAt(*stream, 0x112);
_strings_v2.restoreInsert = readStringAt(*stream, 0x180);
_strings.playAgain = readStringAt(*stream, 0x247, 0xff);
_messageIds.cantGoThere = 110;
_messageIds.dontUnderstand = 112;
_messageIds.itemDoesntMove = 114;
_messageIds.itemNotHere = 115;
_messageIds.thanksForPlaying = 113;
stream.reset(_disk->createReadStream(0xe, 0x1, 0x13, 4));
loadItemDescriptions(*stream, kItems);
stream.reset(_disk->createReadStream(0x8, 0xd, 0xfd, 1));
loadDroppedItemOffsets(*stream, 16);
stream.reset(_disk->createReadStream(0xb, 0xa, 0x05, 1));
loadItemPicIndex(*stream, kItems);
stream.reset(_disk->createReadStream(0x7, 0x8, 0x01));
for (uint i = 0; i < kItems; ++i)
_itemTimeLimits.push_back(stream->readByte());
if (stream->eos() || stream->err())
error("Failed to read item time limits");
stream.reset(_disk->createReadStream(0x8, 0x2, 0x2d));
_gameStrings.itemTimeLimit = readString(*stream);
stream.reset(_disk->createReadStream(0x8, 0x7, 0x02));
_gameStrings.carryingTooMuch = readString(*stream);
stream.reset(_disk->createReadStream(0xc, 0xb, 0x20));
loadSong(*stream);
}
void HiRes5Engine::initGameState() {
_state.vars.resize(40);
insertDisk(2);
Common::StreamPtr stream(_disk->createReadStream(0x5, 0x1, 0x00, 3));
loadItems(*stream);
// A combined total of 1213 rooms
static const byte rooms[kRegions] = {
6, 16, 24, 57, 40, 30, 76, 40,
54, 38, 44, 21, 26, 42, 49, 32,
59, 69, 1, 1, 1, 1, 1, 18,
25, 13, 28, 28, 11, 23, 9, 31,
6, 29, 29, 34, 9, 10, 95, 86,
1
};
initRegions(rooms, kRegions);
loadRegion(1);
_state.room = 5;
_doAnimation = false;
}
void HiRes5Engine::applyRegionWorkarounds() {
// WORKAROUND: Remove/fix buggy commands
switch (_state.region) {
case 3:
// "USE PIN" references a missing message, but cannot
// be triggered due to shadowing of the "USE" verb.
// We remove it anyway to allow script dumping to proceed.
// TODO: Investigate if we should fix this command instead
// of removing it.
removeCommand(_roomCommands, 12);
break;
case 14:
// "WITH SHOVEL" references a missing message. This bug
// is game-breaking in the original, but unlikely to occur
// in practice due to the "DIG" command not asking for what
// to dig with. Probably a remnant of an earlier version
// of the script.
removeCommand(_roomCommands, 0);
break;
case 32:
// This broken message appears right before the game restarts,
// and should probably explain that the user fell down some
// stairs. We remove the broken message.
// TODO: Maybe we could put in a new string?
removeMessage(29);
break;
case 40:
// Locking the gate prints a broken message, followed by
// "O.K.". Maybe there was supposed to be a more elaborate
// message, in the style of the one printed when you unlock
// the gate. But "O.K." should be enough, so we remove the
// first one.
removeMessage(172);
break;
default:
break;
}
}
void HiRes5Engine::applyRoomWorkarounds(byte roomNr) {
// WORKAROUND: Remove/fix buggy commands
if (_state.region == 17 && roomNr == 49) {
// "GET WATER" references a missing message when you already
// have water. This message should be 117 instead of 17.
getCommand(_roomData.commands, 8).script[4] = 117;
}
}
Engine *HiRes5Engine_create(OSystem *syst, const AdlGameDescription *gd) {
return new HiRes5Engine(syst, gd);
}
} // End of namespace Adl

438
engines/adl/hires6.cpp Normal file
View File

@@ -0,0 +1,438 @@
/* 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 "common/system.h"
#include "common/debug.h"
#include "common/error.h"
#include "common/file.h"
#include "common/stream.h"
#include "common/memstream.h"
#include "adl/adl_v5.h"
#include "adl/display_a2.h"
#include "adl/graphics.h"
#include "adl/disk.h"
namespace Adl {
class HiRes6Engine : public AdlEngine_v5 {
public:
HiRes6Engine(OSystem *syst, const AdlGameDescription *gd) :
AdlEngine_v5(syst, gd),
_currVerb(0),
_currNoun(0) {
}
private:
// AdlEngine
void gameLoop() override;
void setupOpcodeTables() override;
void runIntro() override;
void init() override;
void initGameState() override;
void showRoom() override;
int goDirection(ScriptEnv &e, Direction dir) override;
Common::String formatVerbError(const Common::String &verb) const override;
Common::String formatNounError(const Common::String &verb, const Common::String &noun) const override;
void loadState(Common::ReadStream &stream) override;
void saveState(Common::WriteStream &stream) override;
// AdlEngine_v2
void printString(const Common::String &str) override;
// Engine
bool canSaveGameStateCurrently(Common::U32String *msg = nullptr) override;
int o_fluteSound(ScriptEnv &e);
static const uint kRegions = 3;
static const uint kItems = 15;
byte _currVerb, _currNoun;
};
void HiRes6Engine::gameLoop() {
AdlEngine_v5::gameLoop();
// Variable 25 starts at 5 and counts down every 160 moves.
// When it reaches 0, the game ends. This variable determines
// what you see when you "LOOK SUNS".
// Variable 39 is used to advance the suns based on game events,
// so even a fast player will see the suns getting closer together
// as he progresses.
if (getVar(39) != 0) {
if (getVar(39) < getVar(25))
setVar(25, getVar(39));
setVar(39, 0);
}
if (getVar(25) != 0) {
if (getVar(25) > 5)
error("Variable 25 has unexpected value %d", getVar(25));
if ((6 - getVar(25)) * 160 == _state.moves)
setVar(25, getVar(25) - 1);
}
}
void HiRes6Engine::setupOpcodeTables() {
AdlEngine_v5::setupOpcodeTables();
_actOpcodes[0x1e] = opcode(&HiRes6Engine::o_fluteSound);
}
int HiRes6Engine::goDirection(ScriptEnv &e, Direction dir) {
OP_DEBUG_0((Common::String("\tGO_") + dirStr(dir) + "()").c_str());
byte room = getCurRoom().connections[dir];
if (room == 0) {
// Don't penalize invalid directions at escapable Garthim encounter
if (getVar(33) == 2)
setVar(34, getVar(34) + 1);
printMessage(_messageIds.cantGoThere);
return -1;
}
switchRoom(room);
// Escapes an escapable Garthim encounter by going to a different room
if (getVar(33) == 2) {
printMessage(102);
setVar(33, 0);
}
return -1;
}
int HiRes6Engine::o_fluteSound(ScriptEnv &e) {
OP_DEBUG_0("\tFLUTE_SOUND()");
Tones tones;
tones.push_back(Tone(1072.0, 587.6));
tones.push_back(Tone(1461.0, 495.8));
tones.push_back(Tone(0.0, 1298.7));
playTones(tones, false);
_linesPrinted = 0;
return 0;
}
bool HiRes6Engine::canSaveGameStateCurrently(Common::U32String *msg) {
if (!_canSaveNow)
return false;
// Back up variables that may be changed by this test
const byte var2 = getVar(2);
const byte var24 = getVar(24);
const bool abortScript = _abortScript;
const bool retval = AdlEngine_v5::canSaveGameStateCurrently(msg);
setVar(2, var2);
setVar(24, var24);
_abortScript = abortScript;
return retval;
}
#define SECTORS_PER_TRACK 16
#define BYTES_PER_SECTOR 256
static Common::MemoryReadStream *loadSectors(Common::DiskImage *disk, byte track, byte sector = SECTORS_PER_TRACK - 1, byte count = SECTORS_PER_TRACK) {
const int bufSize = count * BYTES_PER_SECTOR;
byte *const buf = (byte *)malloc(bufSize);
byte *p = buf;
while (count-- > 0) {
Common::StreamPtr stream(disk->createReadStream(track, sector, 0, 0));
stream->read(p, BYTES_PER_SECTOR);
if (stream->err() || stream->eos())
error("Error loading from disk image");
p += BYTES_PER_SECTOR;
if (sector > 0)
--sector;
else {
++track;
// Skip VTOC track
if (track == 17)
++track;
sector = SECTORS_PER_TRACK - 1;
}
}
return new Common::MemoryReadStream(buf, bufSize, DisposeAfterUse::YES);
}
void HiRes6Engine::runIntro() {
Display_A2 *display = static_cast<Display_A2 *>(_display);
insertDisk(0);
Common::StreamPtr stream(loadSectors(_disk, 11, 1, 96));
display->setMode(Display::kModeGraphics);
display->loadFrameBuffer(*stream);
display->renderGraphics();
delay(256 * 8609 / 1000);
display->loadFrameBuffer(*stream);
display->renderGraphics();
delay(256 * 8609 / 1000);
display->loadFrameBuffer(*stream);
// Load copyright string from boot file
Common::Files_AppleDOS *files(new Common::Files_AppleDOS());
if (!files->open(getDiskImageName(0)))
error("Failed to open disk volume 0");
stream.reset(files->createReadStream("\010\010\010\010\010\010"));
Common::String copyright(readStringAt(*stream, 0x103, _display->asciiToNative('\r')));
delete files;
display->renderGraphics();
display->home();
display->setMode(Display::kModeMixed);
display->moveCursorTo(Common::Point(0, 21));
display->printString(copyright);
delay(256 * 8609 / 1000);
}
void HiRes6Engine::init() {
_graphics = new GraphicsMan_v3<Display_A2>(*static_cast<Display_A2 *>(_display));
insertDisk(0);
Common::StreamPtr stream(_disk->createReadStream(0x3, 0xf, 0x05));
loadRegionLocations(*stream, kRegions);
stream.reset(_disk->createReadStream(0x5, 0xa, 0x07));
loadRegionInitDataOffsets(*stream, kRegions);
stream.reset(loadSectors(_disk, 0x7));
_strings.verbError = readStringAt(*stream, 0x666);
_strings.nounError = readStringAt(*stream, 0x6bd);
_strings.enterCommand = readStringAt(*stream, 0x6e9);
_strings.lineFeeds = readStringAt(*stream, 0x408);
_strings_v2.saveInsert = readStringAt(*stream, 0xad8);
_strings_v2.saveReplace = readStringAt(*stream, 0xb95);
_strings_v2.restoreInsert = readStringAt(*stream, 0xc07);
_strings.playAgain = readStringAt(*stream, 0xcdf, 0xff);
_messageIds.cantGoThere = 249;
_messageIds.dontUnderstand = 247;
_messageIds.itemDoesntMove = 253;
_messageIds.itemNotHere = 254;
_messageIds.thanksForPlaying = 252;
stream.reset(loadSectors(_disk, 0x6, 0xb, 2));
stream->seek(0x16);
loadItemDescriptions(*stream, kItems);
stream.reset(_disk->createReadStream(0x8, 0x9, 0x16));
loadDroppedItemOffsets(*stream, 16);
stream.reset(_disk->createReadStream(0xb, 0xd, 0x08));
loadItemPicIndex(*stream, kItems);
}
void HiRes6Engine::initGameState() {
_state.vars.resize(40);
insertDisk(0);
Common::StreamPtr stream(_disk->createReadStream(0x3, 0xe, 0x03));
loadItems(*stream);
// A combined total of 91 rooms
static const byte rooms[kRegions] = { 35, 29, 27 };
initRegions(rooms, kRegions);
loadRegion(1);
_currVerb = _currNoun = 0;
}
void HiRes6Engine::showRoom() {
_state.curPicture = getCurRoom().curPicture;
bool redrawPic = false;
if (getVar(26) == 0xfe)
setVar(26, 0);
else if (getVar(26) != 0xff)
setVar(26, _state.room);
if (_state.room != _roomOnScreen) {
loadRoom(_state.room);
if (getVar(26) < 0x80 && getCurRoom().isFirstTime)
setVar(26, 0);
_graphics->clearScreen();
if (!_state.isDark)
redrawPic = true;
} else {
if (getCurRoom().curPicture != _picOnScreen || _itemRemoved)
redrawPic = true;
}
if (redrawPic) {
_roomOnScreen = _state.room;
_picOnScreen = getCurRoom().curPicture;
drawPic(getCurRoom().curPicture);
_itemRemoved = false;
_itemsOnScreen = 0;
for (auto &item : _state.items)
item.isOnScreen = false;
}
if (!_state.isDark)
drawItems();
_display->renderGraphics();
setVar(2, 0xff);
printString(_roomData.description);
}
Common::String HiRes6Engine::formatVerbError(const Common::String &verb) const {
Common::String err = _strings.verbError;
for (uint i = 0; i < verb.size(); ++i)
err.setChar(verb[i], i + 24);
const char spaceChar = _display->asciiToNative(' ');
err.setChar(spaceChar, 32);
uint i = 24;
while (err[i] != spaceChar)
++i;
err.setChar(_display->asciiToNative('.'), i);
return err;
}
Common::String HiRes6Engine::formatNounError(const Common::String &verb, const Common::String &noun) const {
Common::String err = _strings.nounError;
for (uint i = 0; i < noun.size(); ++i)
err.setChar(noun[i], i + 24);
const char spaceChar = _display->asciiToNative(' ');
for (uint i = 35; i > 31; --i)
err.setChar(spaceChar, i);
uint i = 24;
while (err[i] != spaceChar)
++i;
err.setChar(_display->asciiToNative('I'), i + 1);
err.setChar(_display->asciiToNative('S'), i + 2);
err.setChar(_display->asciiToNative('.'), i + 3);
return err;
}
void HiRes6Engine::loadState(Common::ReadStream &stream) {
AdlEngine_v5::loadState(stream);
_state.moves = (getVar(39) << 8) | getVar(24);
setVar(39, 0);
}
void HiRes6Engine::saveState(Common::WriteStream &stream) {
// Move counter is stuffed into variables, in order to save it
setVar(24, _state.moves & 0xff);
setVar(39, _state.moves >> 8);
AdlEngine_v5::saveState(stream);
setVar(39, 0);
}
void HiRes6Engine::printString(const Common::String &str) {
Common::String s;
uint found = 0;
// Variable 27 is 1 when Kira is present, 0 otherwise. It's used for choosing
// between singular and plural variants of a string.
// This does not emulate the corner cases of the original, hence this check
if (getVar(27) > 1)
error("Invalid value %i encountered for variable 27", getVar(27));
for (uint i = 0; i < str.size(); ++i) {
if (str[i] == '%') {
++found;
if (found == 3)
found = 0;
} else {
if (found == 0 || found - 1 == getVar(27))
s += str[i];
}
}
// Variables 2 and 26 are used for controlling the printing of room descriptions
if (getVar(2) == 0xff) {
if (getVar(26) == 0) {
// This checks for special room description string " "
if (str.size() == 1 && _display->asciiToNative(str[0]) == _display->asciiToNative(' ')) {
setVar(2, 160);
} else {
AdlEngine_v5::printString(s);
setVar(2, 1);
}
} else if (getVar(26) == 0xff) {
// Storing the room number in a variable allows for range comparisons
setVar(26, _state.room);
setVar(2, 1);
} else {
setVar(2, 80);
}
doAllCommands(_globalCommands, _currVerb, _currNoun);
} else {
AdlEngine_v5::printString(s);
}
}
Engine *HiRes6Engine_create(OSystem *syst, const AdlGameDescription *gd) {
return new HiRes6Engine(syst, gd);
}
} // End of namespace Adl

367
engines/adl/metaengine.cpp Normal file
View File

@@ -0,0 +1,367 @@
/* 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/system.h"
#include "common/savefile.h"
#include "common/file.h"
#include "common/translation.h"
#include "graphics/thumbnail.h"
#include "backends/keymapper/action.h"
#include "backends/keymapper/keymapper.h"
#include "adl/adl.h"
#include "adl/detection.h"
#include "adl/disk_image_helpers.h"
namespace Adl {
static const ADExtraGuiOptionsMap optionsList[] = {
{
GAMEOPTION_NTSC,
{
_s("TV emulation"),
_s("Emulate composite output to an NTSC TV"),
"ntsc",
true,
0,
0
}
},
{
GAMEOPTION_COLOR_DEFAULT_OFF,
{
_s("Color graphics"),
_s("Use color graphics instead of monochrome"),
"color",
false,
0,
0
}
},
{
GAMEOPTION_COLOR_DEFAULT_ON,
{
_s("Color graphics"),
_s("Use color graphics instead of monochrome"),
"color",
true,
0,
0
}
},
{
GAMEOPTION_SCANLINES,
{
_s("Show scanlines"),
_s("Darken every other scanline to mimic the look of a CRT"),
"scanlines",
false,
0,
0
}
},
{
GAMEOPTION_MONO_TEXT,
{
_s("Always use sharp monochrome text"),
_s("Do not emulate NTSC artifacts for text"),
"monotext",
true,
0,
0
}
},
{
GAMEOPTION_APPLE2E_CURSOR,
{
_s("Use checkered cursor"),
_s("Use the checkered cursor from later Apple II models"),
"apple2e_cursor",
false,
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
AD_EXTRA_GUI_OPTIONS_TERMINATOR
};
Common::Path getDiskImageName(const AdlGameDescription &adlDesc, byte volume) {
const ADGameDescription &desc = adlDesc.desc;
for (uint i = 0; desc.filesDescriptions[i].fileName; ++i) {
const ADGameFileDescription &fDesc = desc.filesDescriptions[i];
if (fDesc.fileType == volume) {
for (uint e = 0; e < ARRAYSIZE(diskImageExts); ++e) {
if (diskImageExts[e].platform == desc.platform) {
Common::Path testFileName(fDesc.fileName);
testFileName.appendInPlace(diskImageExts[e].extension);
if (Common::File::exists(testFileName))
return testFileName;
}
}
error("Failed to find disk image '%s'", fDesc.fileName);
}
}
error("Disk volume %d not found", volume);
}
GameType getGameType(const AdlGameDescription &adlDesc) {
return adlDesc.gameType;
}
GameVersion getGameVersion(const AdlGameDescription &adlDesc) {
return adlDesc.version;
}
Common::Language getLanguage(const AdlGameDescription &adlDesc) {
return adlDesc.desc.language;
}
Common::Platform getPlatform(const AdlGameDescription &adlDesc) {
return adlDesc.desc.platform;
}
class AdlMetaEngine : public AdvancedMetaEngine<AdlGameDescription> {
public:
const char *getName() const override {
return "adl";
}
const ADExtraGuiOptionsMap *getAdvancedExtraGuiOptions() const override {
return optionsList;
}
bool hasFeature(MetaEngineFeature f) const override;
SaveStateDescriptor querySaveMetaInfos(const char *target, int slot) const override;
int getAutosaveSlot() const override { return 15; }
int getMaximumSaveSlot() const override { return 15; }
SaveStateList listSaves(const char *target) const override;
bool removeSaveState(const char *target, int slot) const override;
Common::Error createInstance(OSystem *syst, Engine **engine, const AdlGameDescription *adlGd) const override;
Common::KeymapArray initKeymaps(const char *target) const override;
};
bool AdlMetaEngine::hasFeature(MetaEngineFeature f) const {
switch(f) {
case kSupportsListSaves:
case kSupportsLoadingDuringStartup:
case kSupportsDeleteSave:
case kSavesSupportMetaInfo:
case kSavesSupportThumbnail:
case kSavesSupportCreationDate:
case kSavesSupportPlayTime:
return true;
default:
return false;
}
}
SaveStateDescriptor AdlMetaEngine::querySaveMetaInfos(const char *target, int slot) const {
Common::String fileName = Common::String::format("%s.s%02d", target, slot);
Common::InSaveFile *inFile = g_system->getSavefileManager()->openForLoading(fileName);
if (!inFile)
return SaveStateDescriptor();
if (inFile->readUint32BE() != MKTAG('A', 'D', 'L', ':')) {
delete inFile;
return SaveStateDescriptor();
}
byte saveVersion = inFile->readByte();
if (saveVersion != SAVEGAME_VERSION) {
delete inFile;
return SaveStateDescriptor();
}
char name[SAVEGAME_NAME_LEN] = { };
inFile->read(name, sizeof(name) - 1);
inFile->readByte();
if (inFile->eos() || inFile->err()) {
delete inFile;
return SaveStateDescriptor();
}
SaveStateDescriptor sd(this, slot, name);
int year = inFile->readUint16BE();
int month = inFile->readByte();
int day = inFile->readByte();
sd.setSaveDate(year + 1900, month + 1, day);
int hour = inFile->readByte();
int minutes = inFile->readByte();
sd.setSaveTime(hour, minutes);
uint32 playTime = inFile->readUint32BE();
sd.setPlayTime(playTime);
if (inFile->eos() || inFile->err()) {
delete inFile;
return SaveStateDescriptor();
}
Graphics::Surface *thumbnail;
if (!Graphics::loadThumbnail(*inFile, thumbnail)) {
delete inFile;
return SaveStateDescriptor();
}
sd.setThumbnail(thumbnail);
delete inFile;
return sd;
}
SaveStateList AdlMetaEngine::listSaves(const char *target) const {
Common::SaveFileManager *saveFileMan = g_system->getSavefileManager();
Common::StringArray files = saveFileMan->listSavefiles(Common::String(target) + ".s##");
SaveStateList saveList;
for (uint i = 0; i < files.size(); ++i) {
const Common::String &fileName = files[i];
Common::InSaveFile *inFile = saveFileMan->openForLoading(fileName);
if (!inFile) {
warning("Cannot open save file '%s'", fileName.c_str());
continue;
}
if (inFile->readUint32BE() != MKTAG('A', 'D', 'L', ':')) {
warning("No header found in '%s'", fileName.c_str());
delete inFile;
continue;
}
byte saveVersion = inFile->readByte();
if (saveVersion != SAVEGAME_VERSION) {
warning("Unsupported save game version %i found in '%s'", saveVersion, fileName.c_str());
delete inFile;
continue;
}
char name[SAVEGAME_NAME_LEN] = { };
inFile->read(name, sizeof(name) - 1);
delete inFile;
int slotNum = atoi(fileName.c_str() + fileName.size() - 2);
SaveStateDescriptor sd(this, slotNum, name);
saveList.push_back(sd);
}
// Sort saves based on slot number.
Common::sort(saveList.begin(), saveList.end(), SaveStateDescriptorSlotComparator());
return saveList;
}
bool AdlMetaEngine::removeSaveState(const char *target, int slot) const {
Common::String fileName = Common::String::format("%s.s%02d", target, slot);
return g_system->getSavefileManager()->removeSavefile(fileName);
}
Engine *HiRes1Engine_create(OSystem *syst, const AdlGameDescription *gd);
Engine *HiRes2Engine_create(OSystem *syst, const AdlGameDescription *gd);
Engine *HiRes0Engine_create(OSystem *syst, const AdlGameDescription *gd);
Engine *HiRes3Engine_create(OSystem *syst, const AdlGameDescription *gd);
Engine *HiRes4Engine_create(OSystem *syst, const AdlGameDescription *gd);
Engine *HiRes5Engine_create(OSystem *syst, const AdlGameDescription *gd);
Engine *HiRes6Engine_create(OSystem *syst, const AdlGameDescription *gd);
Common::Error AdlMetaEngine::createInstance(OSystem *syst, Engine **engine, const AdlGameDescription *adlGd) const {
switch (adlGd->gameType) {
case GAME_TYPE_HIRES1:
*engine = HiRes1Engine_create(syst, adlGd);
break;
case GAME_TYPE_HIRES2:
*engine = HiRes2Engine_create(syst, adlGd);
break;
case GAME_TYPE_HIRES0:
*engine = HiRes0Engine_create(syst, adlGd);
break;
case GAME_TYPE_HIRES3:
*engine = HiRes3Engine_create(syst, adlGd);
break;
case GAME_TYPE_HIRES4:
*engine = HiRes4Engine_create(syst, adlGd);
break;
case GAME_TYPE_HIRES5:
*engine = HiRes5Engine_create(syst, adlGd);
break;
case GAME_TYPE_HIRES6:
*engine = HiRes6Engine_create(syst, adlGd);
break;
default:
return Common::kUnsupportedGameidError;
}
return Common::kNoError;
}
Common::KeymapArray AdlMetaEngine::initKeymaps(const char *target) const {
using namespace Common;
Keymap *engineKeymap = new Keymap(Keymap::kKeymapTypeGame, "adl", "ADL");
Action *act;
act = new Action("QUIT", _("Quit"));
act->setCustomEngineActionEvent(kADLActionQuit);
act->addDefaultInputMapping("C+q");
engineKeymap->addAction(act);
return Keymap::arrayOf(engineKeymap);
}
} // End of namespace Adl
#if PLUGIN_ENABLED_DYNAMIC(ADL)
REGISTER_PLUGIN_DYNAMIC(ADL, PLUGIN_TYPE_ENGINE, Adl::AdlMetaEngine);
#else
REGISTER_PLUGIN_STATIC(ADL, PLUGIN_TYPE_ENGINE, Adl::AdlMetaEngine);
#endif

40
engines/adl/module.mk Normal file
View File

@@ -0,0 +1,40 @@
MODULE := engines/adl
MODULE_OBJS := \
adl.o \
adl_v2.o \
adl_v3.o \
adl_v4.o \
adl_v5.o \
console.o \
disk.o \
display.o \
display_a2.o \
hires1.o \
hires2.o \
hires4.o \
hires5.o \
hires6.o \
metaengine.o \
sound.o
MODULE_DIRS += \
engines/adl
# This module can be built as a plugin
ifeq ($(ENABLE_ADL), DYNAMIC_PLUGIN)
PLUGIN := 1
endif
# Include common rules
include $(srcdir)/rules.mk
# Detection objects
DETECT_OBJS += $(MODULE)/detection.o
# Skip building the following objects if a static
# module is enabled, because it already has the contents.
ifneq ($(ENABLE_ADL), STATIC_PLUGIN)
# External dependencies for detection.
DETECT_OBJS += $(MODULE)/disk.o
endif

146
engines/adl/sound.cpp Normal file
View File

@@ -0,0 +1,146 @@
/* 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 "common/system.h"
#include "audio/mixer.h"
#include "adl/sound.h"
namespace Adl {
// Generic PC-speaker synth
// This produces more accurate frequencies than Audio::PCSpeaker, but only
// does square waves.
class Speaker {
public:
Speaker(int sampleRate);
void startTone(double freq);
void stopTone();
void generateSamples(int16 *buffer, int numSamples);
private:
int _rate;
frac_t _halfWaveLen, _halfWaveRem;
int16 _curSample;
};
Speaker::Speaker(int sampleRate) :
_rate(sampleRate),
_halfWaveLen(0),
_halfWaveRem(0),
_curSample(32767) { }
void Speaker::startTone(double freq) {
_halfWaveLen = _halfWaveRem = doubleToFrac(_rate / freq / 2);
if (_halfWaveLen < (frac_t)FRAC_ONE) {
// Tone out of range at this sample rate
stopTone();
}
}
void Speaker::stopTone() {
_halfWaveLen = 0;
}
void Speaker::generateSamples(int16 *buffer, int numSamples) {
if (_halfWaveLen == 0) {
// Silence
memset(buffer, 0, numSamples * sizeof(int16));
return;
}
int offset = 0;
while (offset < numSamples) {
if (_halfWaveRem >= 0 && _halfWaveRem < (frac_t)FRAC_ONE) {
// Rising/falling edge
// Switch level
_curSample = ~_curSample;
// Use transition point fraction for current sample value
buffer[offset++] = _halfWaveRem ^ _curSample;
// Compute next transition point
_halfWaveRem += _halfWaveLen - FRAC_ONE;
} else {
// Low/high level
// Generate as many samples as we can
const int samples = MIN(numSamples - offset, (int)fracToInt(_halfWaveRem));
Common::fill(buffer + offset, buffer + offset + samples, _curSample);
offset += samples;
// Count down to level transition point
_halfWaveRem -= intToFrac(samples);
}
}
}
Sound::Sound(const Tones &tones) :
_tones(tones),
_toneIndex(0),
_samplesRem(0) {
_rate = g_system->getMixer()->getOutputRate();
_speaker = new Speaker(_rate);
}
Sound::~Sound() {
delete _speaker;
}
bool Sound::endOfData() const {
return _samplesRem == 0 && _toneIndex == _tones.size();
}
int Sound::readBuffer(int16 *buffer, const int numSamples) {
int offset = 0;
while (offset < numSamples) {
if (_samplesRem == 0) {
// Set up next tone
if (_toneIndex == _tones.size()) {
// No more tones
return offset;
}
if (_tones[_toneIndex].freq == 0.0)
_speaker->stopTone();
else
_speaker->startTone(_tones[_toneIndex].freq);
// Compute length of tone
_samplesRem = _rate * _tones[_toneIndex++].len / 1000;
}
// Generate as many samples as we can
const int samples = MIN(numSamples - offset, _samplesRem);
_speaker->generateSamples(buffer + offset, samples);
_samplesRem -= samples;
offset += samples;
}
return numSamples;
}
} // End of namespace Adl

65
engines/adl/sound.h Normal file
View File

@@ -0,0 +1,65 @@
/* 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 ADL_SOUND_H
#define ADL_SOUND_H
#include "audio/audiostream.h"
#include "common/array.h"
#include "common/frac.h"
namespace Adl {
class Speaker;
struct Tone {
double freq; // Hz
double len; // ms
Tone(double frequency, double length) : freq(frequency), len(length) { }
};
typedef Common::Array<Tone> Tones;
class Sound : public Audio::AudioStream {
public:
Sound(const Tones &tones);
~Sound() override;
// AudioStream
int readBuffer(int16 *buffer, const int numSamples) override;
bool isStereo() const override { return false; }
bool endOfData() const override;
int getRate() const override { return _rate; }
private:
const Tones &_tones;
Speaker *_speaker;
int _rate;
uint _toneIndex;
int _samplesRem;
};
} // End of namespace Adl
#endif