ADL: Add support for three more hires4 versions

This commit is contained in:
Walter van Niftrik 2023-05-24 08:46:40 +02:00
parent b9adfbaa01
commit 01bb2bc689
8 changed files with 384 additions and 234 deletions

View file

@ -298,12 +298,46 @@ byte AdlEngine::inputKey(bool showCursor) const {
return key;
}
void AdlEngine::loadWords(Common::ReadStream &stream, WordMap &map, Common::StringArray &pri) const {
void AdlEngine::waitKey(uint32 ms, Common::KeyCode keycode) const {
uint32 start = g_system->getMillis();
while (!shouldQuit()) {
Common::Event event;
if (pollEvent(event)) {
if (event.type == Common::EVENT_KEYDOWN)
if (keycode == Common::KEYCODE_INVALID || keycode == event.kbd.keycode)
return;
}
if (ms && g_system->getMillis() - start >= ms)
return;
g_system->delayMillis(16);
}
}
void AdlEngine::loadWords(Common::ReadStream &stream, WordMap &map, Common::StringArray &pri, uint count) const {
uint index = 0;
map.clear();
pri.clear();
// WORKAROUND: Several games contain one or more word lists without a terminator
switch (getGameType()) {
case GAME_TYPE_HIRES3:
if (&map == &_verbs)
count = 72;
else
count = 113;
break;
case GAME_TYPE_HIRES5:
if (_state.region == 15 && &map == &_nouns)
count = 81;
break;
default:
break;
}
while (1) {
++index;
@ -327,17 +361,8 @@ void AdlEngine::loadWords(Common::ReadStream &stream, WordMap &map, Common::Stri
if (synonyms == 0xff)
break;
// WORKAROUND: Missing verb list terminator in hires3
if (getGameType() == GAME_TYPE_HIRES3 && index == 72 && synonyms == 0)
return;
// WORKAROUND: Missing noun list terminator in hires3
if (getGameType() == GAME_TYPE_HIRES3 && index == 113)
return;
// WORKAROUND: Missing noun list terminator in hires5 region 15
if (getGameType() == GAME_TYPE_HIRES5 && _state.region == 15 && index == 81)
return;
if (index == count)
break;
for (uint i = 0; i < synonyms; ++i) {
if (stream.read((char *)buf, IDI_WORD_SIZE) < IDI_WORD_SIZE)

View file

@ -31,6 +31,7 @@
#include "common/func.h"
#include "common/ptr.h"
#include "common/scummsys.h"
#include "common/keyboard.h"
#include "engines/engine.h"
@ -286,12 +287,13 @@ protected:
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) 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);

View file

@ -43,6 +43,27 @@ AdlEngine_v2::AdlEngine_v2(OSystem *syst, const AdlGameDescription *gd) :
_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 DiskImage();

View file

@ -49,6 +49,7 @@ protected:
// Engine
bool canSaveGameStateCurrently() override;
void mapExeStrings(const Common::StringArray &strings);
void insertDisk(byte volume);
virtual DataBlockPtr readDataBlockPtr(Common::ReadStream &f) const;
virtual void adjustDataBlockPtr(byte &track, byte &sector, byte &offset, byte &size) const { }

View file

@ -296,9 +296,41 @@ static const AdlGameDescription gameDiskDescriptions[] = {
GAME_TYPE_HIRES3,
GAME_VER_NONE
},
{ // Hi-Res Adventure #4: Ulysses and the Golden Fleece - Apple II - Load 'N' Go
{ // Hi-Res Adventure #4: Ulysses and the Golden Fleece - Apple II - Original release
{
"hires4", "",
"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 },
@ -310,7 +342,23 @@ static const AdlGameDescription gameDiskDescriptions[] = {
DEFAULT_OPTIONS
},
GAME_TYPE_HIRES4,
GAME_VER_NONE
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
{

View file

@ -62,7 +62,10 @@ enum GameVersion {
GAME_VER_HR1_COARSE,
GAME_VER_HR1_VF1,
GAME_VER_HR1_VF2,
GAME_VER_HR1_PD
GAME_VER_HR1_PD,
GAME_VER_HR4_V1_0,
GAME_VER_HR4_V1_1,
GAME_VER_HR4_LNG
};
struct AdlGameDescription {

View file

@ -72,35 +72,17 @@ void HiResBaseEngine::init() {
stream.reset(_disk->createReadStream(0x19, 0x0, 0x00, 25, 13));
Common::StringArray exeStrings;
extractExeStrings(*stream, 0x1566, exeStrings);
if (exeStrings.size() < 11)
error("Failed to load strings from executable");
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;
// Read parser messages
_strings.verbError = exeStrings[2];
_strings.nounError = exeStrings[3];
_strings.enterCommand = exeStrings[4];
if (!oldEngine) {
stream.reset(_disk->createReadStream(0x19, 0x7, 0xd7));
_strings_v2.time = readString(*stream, 0xff);
}
// Read line feeds
_strings.lineFeeds = exeStrings[0];
// Read opcode strings
_strings_v2.saveInsert = exeStrings[5];
_strings_v2.saveReplace = exeStrings[6];
_strings_v2.restoreInsert = exeStrings[7];
_strings_v2.restoreReplace = exeStrings[8];
_strings.playAgain = exeStrings[9];
_strings.pressReturn = exeStrings[10];
// Load global picture data
stream.reset(_disk->createReadStream(0x19, 0xa, 0x80, 0));
loadPictures(*stream);

View file

@ -40,7 +40,7 @@ namespace Adl {
#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 40
#define IDI_HR4_NUM_ITEM_OFFSETS 16
// Messages used outside of scripts
#define IDI_HR4_MSG_CANT_GO_THERE 110
@ -49,32 +49,130 @@ namespace Adl {
#define IDI_HR4_MSG_ITEM_NOT_HERE 115
#define IDI_HR4_MSG_THANKS_FOR_PLAYING 113
class HiRes4Engine : public AdlEngine_v3 {
class HiRes4BaseEngine : public AdlEngine_v3 {
public:
HiRes4Engine(OSystem *syst, const AdlGameDescription *gd) :
HiRes4BaseEngine(OSystem *syst, const AdlGameDescription *gd);
~HiRes4BaseEngine() override;
protected:
// AdlEngine
void init() override;
void initGameState() override;
DiskImage *_boot;
};
HiRes4BaseEngine::HiRes4BaseEngine(OSystem *syst, const AdlGameDescription *gd) :
AdlEngine_v3(syst, gd),
_boot(nullptr) { _brokenRooms.push_back(121); }
~HiRes4Engine() override;
_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 DiskImage();
if (!_boot->open(getDiskImageName(0)))
error("Failed to open disk image '%s'", getDiskImageName(0).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 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 HiRes4Engine_v1_0::runIntro() {
StreamPtr stream(_boot->createReadStream(0x06, 0x3, 0xb9, 1));
void runIntroAdvise(Common::SeekableReadStream &menu);
void runIntroLogo(Common::SeekableReadStream &ms2);
void runIntroTitle(Common::SeekableReadStream &menu, Common::SeekableReadStream &ms2);
void runIntroInstructions(Common::SeekableReadStream &instructions);
void runIntroLoading(Common::SeekableReadStream &adventure);
_display->setMode(Display::kModeText);
static const uint kClock = 1022727; // Apple II CPU clock rate
Common::String str = readString(*stream);
DiskImage *_boot;
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();
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();
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
@ -123,11 +221,112 @@ static Common::MemoryReadStream *decodeData(Common::SeekableReadStream &stream,
return new Common::MemoryReadStream(buf, streamSize, DisposeAfterUse::YES);
}
HiRes4Engine::~HiRes4Engine() {
delete _boot;
void HiRes4Engine_v1_1::runIntro() {
Common::ScopedPtr<Files_AppleDOS> files(new Files_AppleDOS());
files->open(getDiskImageName(0));
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);
}
void HiRes4Engine::putSpace(uint x, uint y) const {
waitKey(3000);
}
void HiRes4Engine_v1_1::init() {
HiRes4BaseEngine::init();
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();
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;
@ -137,7 +336,7 @@ void HiRes4Engine::putSpace(uint x, uint y) const {
delay(2);
}
void HiRes4Engine::drawChar(byte c, Common::SeekableReadStream &shapeTable, Common::Point &pos) const {
void HiRes4Engine_LNG::drawChar(byte c, Common::SeekableReadStream &shapeTable, Common::Point &pos) const {
shapeTable.seek(0);
byte entries = shapeTable.readByte();
@ -152,7 +351,7 @@ void HiRes4Engine::drawChar(byte c, Common::SeekableReadStream &shapeTable, Comm
_graphics->drawShape(shapeTable, pos);
}
void HiRes4Engine::drawText(const Common::String &str, Common::SeekableReadStream &shapeTable, const float ht, const float vt) const {
void HiRes4Engine_LNG::drawText(const Common::String &str, Common::SeekableReadStream &shapeTable, const float ht, const float vt) const {
if (shouldQuit())
return;
@ -171,86 +370,7 @@ void HiRes4Engine::drawText(const Common::String &str, Common::SeekableReadStrea
}
}
void HiRes4Engine::runIntroAdvise(Common::SeekableReadStream &menu) {
Common::StringArray backupText;
backupText.push_back(readStringAt(menu, 0x659, '"'));
backupText.push_back(readStringAt(menu, 0x682, '"'));
backupText.push_back(readStringAt(menu, 0x6a9, '"'));
backupText.push_back(readStringAt(menu, 0x6c6, '"'));
_display->setMode(Display::kModeText);
for (uint x = 2; x <= 36; ++x)
putSpace(x, 2);
for (uint y = 3; y <= 20; ++y) {
putSpace(2, y);
putSpace(36, y);
}
for (uint x = 2; x <= 36; ++x)
putSpace(x, 20);
for (uint x = 0; x <= 38; ++x)
putSpace(x, 0);
for (uint y = 1; y <= 21; ++y) {
putSpace(0, y);
putSpace(38, y);
}
for (uint x = 0; x <= 38; ++x)
putSpace(x, 22);
int y = 7;
for (uint i = 0; i < backupText.size(); ++i) {
uint x = 0;
do {
if (shouldQuit())
return;
++x;
Common::String left = backupText[i];
left.erase(x, Common::String::npos);
Common::String right = backupText[i];
right.erase(0, right.size() - x);
_display->moveCursorTo(Common::Point(19 - x, y));
_display->printAsciiString(left);
_display->moveCursorTo(Common::Point(19, y));
_display->printAsciiString(right);
_display->renderText();
delay(35);
} while (x != backupText[i].size() / 2);
if (i == 2)
y = 18;
else
y += 2;
}
Common::String cursor = readStringAt(menu, 0x781, '"');
uint cursorIdx = 0;
while (!shouldQuit()) {
Common::Event event;
if (pollEvent(event)) {
if (event.type == Common::EVENT_KEYDOWN)
break;
}
_display->moveCursorTo(Common::Point(32, 18));
_display->printChar(_display->asciiToNative(cursor[cursorIdx]));
_display->renderText();
g_system->delayMillis(25);
cursorIdx = (cursorIdx + 1) % cursor.size();
}
}
void HiRes4Engine::runIntroLogo(Common::SeekableReadStream &ms2) {
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();
@ -298,7 +418,7 @@ void HiRes4Engine::runIntroLogo(Common::SeekableReadStream &ms2) {
}
}
void HiRes4Engine::runIntroTitle(Common::SeekableReadStream &menu, Common::SeekableReadStream &ms2) {
void HiRes4Engine_LNG::runIntroTitle(Common::SeekableReadStream &menu, Common::SeekableReadStream &ms2) {
ms2.seek(0x2290);
StreamPtr shapeTable(ms2.readStream(0x450));
if (ms2.err() || ms2.eos())
@ -339,7 +459,7 @@ void HiRes4Engine::runIntroTitle(Common::SeekableReadStream &menu, Common::Seeka
drawText(titleString, *shapeTable, 12.5f, 14.0f + menuStrings.size() * 1.2f + 2.0f);
}
void HiRes4Engine::runIntroInstructions(Common::SeekableReadStream &instructions) {
void HiRes4Engine_LNG::runIntroInstructions(Common::SeekableReadStream &instructions) {
Common::String line;
Common::String pressKey(readStringAt(instructions, 0xad6, '"'));
instructions.seek(0);
@ -399,7 +519,7 @@ void HiRes4Engine::runIntroInstructions(Common::SeekableReadStream &instructions
}
}
void HiRes4Engine::runIntroLoading(Common::SeekableReadStream &adventure) {
void HiRes4Engine_LNG::runIntroLoading(Common::SeekableReadStream &adventure) {
_display->home();
_display->setMode(Display::kModeText);
@ -419,20 +539,20 @@ void HiRes4Engine::runIntroLoading(Common::SeekableReadStream &adventure) {
_display->printString(Common::String(text[i], kStringLen));
}
delay(4000);
waitKey(3000);
}
void HiRes4Engine::runIntro() {
void HiRes4Engine_LNG::runIntro() {
Common::ScopedPtr<Files_AppleDOS> files(new Files_AppleDOS());
files->open(getDiskImageName(0));
while (!shouldQuit()) {
StreamPtr menu(files->createReadStream("MENU"));
runIntroAdvise(*menu);
if (shouldQuit())
return;
const bool oldVersion = files->exists("MS2");
if (oldVersion) {
// Version 0.0
StreamPtr ms2(files->createReadStream("MS2"));
runIntroLogo(*ms2);
@ -440,11 +560,32 @@ void HiRes4Engine::runIntro() {
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) {
char key = inputKey();
const char key = inputKey(false);
if (shouldQuit())
return;
@ -462,86 +603,6 @@ void HiRes4Engine::runIntro() {
}
}
void HiRes4Engine::init() {
_graphics = new GraphicsMan_v2<Display_A2>(*static_cast<Display_A2 *>(_display));
_boot = new DiskImage();
if (!_boot->open(getDiskImageName(0)))
error("Failed to open disk image '%s'", getDiskImageName(0).c_str());
insertDisk(1);
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);
_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(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::initGameState() {
_state.vars.resize(IDI_HR4_NUM_VARS);
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_Atari : public AdlEngine_v3 {
public:
HiRes4Engine_Atari(OSystem *syst, const AdlGameDescription *gd) :
@ -743,7 +804,14 @@ void HiRes4Engine_Atari::adjustDataBlockPtr(byte &track, byte &sector, byte &off
Engine *HiRes4Engine_create(OSystem *syst, const AdlGameDescription *gd) {
switch (getPlatform(*gd)) {
case Common::kPlatformApple2:
return new HiRes4Engine(syst, gd);
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: