[Scummvm-git-logs] scummvm master -> 64b4f13b36030963b507be6e8fc4c4eb7bf22206
dreammaster
paulfgilbert at gmail.com
Sat Oct 26 20:16:30 CEST 2019
This automated email contains information about 15 new commits which have been
pushed to the 'scummvm' repo located at https://github.com/scummvm/scummvm .
Summary:
b8be17fd02 GLK: LEVEL9: Skeleton engine
d8048720fc GLK: LEVEL9: Added subengine files
8b0250cb09 GLK: LEVEL9: Janitorial
a6b1c9085f GLK: Safer interpreter tag generation for Quetzal save files
0a4d64f55e GLK: LEVEL9: Hooking up engine class to subengine code
25c682793d GLK: LEVEL9: Data loading fixes and cleanup
0ecae64fdc GLK: LEVEL9: Cleaning up initialization
d923ed3c5f GLK: LEVEL9: Fixes for exiting game by closing window
fa323c6187 GLK: LEVEL9: Further variable initialization
07c8437e26 GLK: LEVEL9: Title screen graphics now showing
4332df2bda GLK: LEVEL9: Allow for future graphic scaling
0facfc2b24 GLK: LEVEL9: Moved pre-existing detection code into separate class
51471940a9 GLK: LEVEL9: Move game scanner code to new Scanner class
d9c8237042 GLK: LEVEL9: Hook up new detection
64b4f13b36 GLK: LEVEL9: Compilation fixes
Commit: b8be17fd02fe32d6cea363cc302014090fba4348
https://github.com/scummvm/scummvm/commit/b8be17fd02fe32d6cea363cc302014090fba4348
Author: Paul Gilbert (dreammaster at scummvm.org)
Date: 2019-10-26T11:13:29-07:00
Commit Message:
GLK: LEVEL9: Skeleton engine
Changed paths:
A engines/glk/level9/detection.cpp
A engines/glk/level9/detection.h
A engines/glk/level9/detection_tables.h
A engines/glk/level9/level9.cpp
A engines/glk/level9/level9.h
engines/glk/configure.engine
engines/glk/detection.cpp
engines/glk/module.mk
diff --git a/engines/glk/configure.engine b/engines/glk/configure.engine
index e964787..727aa59 100644
--- a/engines/glk/configure.engine
+++ b/engines/glk/configure.engine
@@ -1,6 +1,6 @@
# This file is included from the main "configure" script
# add_engine [name] [desc] [build-by-default] [subengines] [base games] [deps]
-add_engine glk "Glk Interactive Fiction games" no "glk_adrift glk_advsys glk_alan2 glk_alan3 glk_frotz glk_glulxe glk_hugo glk_jacl glk_magnetic glk_quest glk_scott glk_tads" "Base" "16bit freetype2 jpeg png"
+add_engine glk "Glk Interactive Fiction games" no "glk_adrift glk_advsys glk_alan2 glk_alan3 glk_frotz glk_glulxe glk_hugo glk_jacl glk_level9 glk_magnetic glk_quest glk_scott glk_tads" "Base" "16bit freetype2 jpeg png"
add_engine glk_adrift "ADRIFT" no
add_engine glk_advsys "AdvSys" no
add_engine glk_alan2 "Alan2" no
@@ -9,6 +9,7 @@ add_engine glk_frotz "Frotz" no
add_engine glk_glulxe "Glulxe" no
add_engine glk_hugo "Hugo" no
add_engine glk_jacl "JACL" no
+add_engine glk_level9 "Level9" no
add_engine glk_magnetic "Magnetic" no
add_engine glk_quest "Quest" no
add_engine glk_scott "Scott" no
diff --git a/engines/glk/detection.cpp b/engines/glk/detection.cpp
index 6de2a6f..b4d5bff 100644
--- a/engines/glk/detection.cpp
+++ b/engines/glk/detection.cpp
@@ -64,6 +64,11 @@
#include "glk/jacl/jacl.h"
#endif
+#ifdef ENABLE_GLK_LEVEL9
+#include "glk/level9/detection.h"
+#include "glk/level9/level9.h"
+#endif
+
#ifdef ENABLE_GLK_MAGNETIC
#include "glk/magnetic/detection.h"
#include "glk/magnetic/magnetic.h"
@@ -228,6 +233,10 @@ Common::Error GlkMetaEngine::createInstance(OSystem *syst, Engine **engine) cons
if ((*engine = create<Glk::JACL::JACLMetaEngine, Glk::JACL::JACL>(syst, gameDesc)) != nullptr) {}
else
#endif
+#ifdef ENABLE_GLK_LEVEL9
+ if ((*engine = create<Glk::Level9::Level9MetaEngine, Glk::Level9::Level9>(syst, gameDesc)) != nullptr) {}
+ else
+#endif
#ifdef ENABLE_GLK_MAGNETIC
if ((*engine = create<Glk::Magnetic::MagneticMetaEngine, Glk::Magnetic::Magnetic>(syst, gameDesc)) != nullptr) {}
else
@@ -303,6 +312,9 @@ PlainGameList GlkMetaEngine::getSupportedGames() const {
#ifdef ENABLE_GLK_JACL
Glk::JACL::JACLMetaEngine::getSupportedGames(list);
#endif
+#ifdef ENABLE_GLK_LEVEL9
+ Glk::Level9::Level9MetaEngine::getSupportedGames(list);
+#endif
#ifdef ENABLE_GLK_MAGNETIC
Glk::Magnetic::MagneticMetaEngine::getSupportedGames(list);
#endif
@@ -348,6 +360,9 @@ PlainGameDescriptor GlkMetaEngine::findGame(const char *gameId) const {
#ifdef ENABLE_GLK_JACL
FIND_GAME(JACL);
#endif
+#ifdef ENABLE_GLK_LEVEL9
+ FIND_GAME(Level9);
+#endif
#ifdef ENABLE_GLK_MAGNETIC
FIND_GAME(Magnetic);
#endif
@@ -395,6 +410,9 @@ DetectedGames GlkMetaEngine::detectGames(const Common::FSList &fslist) const {
#ifdef ENABLE_GLK_JACL
Glk::JACL::JACLMetaEngine::detectGames(fslist, detectedGames);
#endif
+#ifdef ENABLE_GLK_LEVEL9
+ Glk::Level9::Level9MetaEngine::detectGames(fslist, detectedGames);
+#endif
#ifdef ENABLE_GLK_MAGNETIC
Glk::Magnetic::MagneticMetaEngine::detectGames(fslist, detectedGames);
#endif
@@ -437,6 +455,9 @@ void GlkMetaEngine::detectClashes() const {
#ifdef ENABLE_GLK_JACL
Glk::JACL::JACLMetaEngine::detectClashes(map);
#endif
+#ifdef ENABLE_GLK_LEVEL9
+ Glk::Level9::Level9MetaEngine::detectClashes(map);
+#endif
#ifdef ENABLE_GLK_MAGNETIC
Glk::Magnetic::MagneticMetaEngine::detectClashes(map);
#endif
diff --git a/engines/glk/level9/detection.cpp b/engines/glk/level9/detection.cpp
new file mode 100644
index 0000000..9904091
--- /dev/null
+++ b/engines/glk/level9/detection.cpp
@@ -0,0 +1,99 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "glk/level9/detection.h"
+#include "glk/level9/detection_tables.h"
+#include "glk/blorb.h"
+#include "common/debug.h"
+#include "common/file.h"
+#include "common/md5.h"
+#include "engines/game.h"
+
+namespace Glk {
+namespace Level9 {
+
+void Level9MetaEngine::getSupportedGames(PlainGameList &games) {
+ for (const PlainGameDescriptor *pd = LEVEL9_GAME_LIST; pd->gameId; ++pd) {
+ games.push_back(*pd);
+ }
+}
+
+GameDescriptor Level9MetaEngine::findGame(const char *gameId) {
+ for (const PlainGameDescriptor *pd = LEVEL9_GAME_LIST; pd->gameId; ++pd) {
+ if (!strcmp(gameId, pd->gameId))
+ return *pd;
+ }
+
+ return PlainGameDescriptor();
+}
+
+bool Level9MetaEngine::detectGames(const Common::FSList &fslist, DetectedGames &gameList) {
+ // Loop through the files of the folder
+ for (Common::FSList::const_iterator file = fslist.begin(); file != fslist.end(); ++file) {
+ // Check for a recognised filename
+ if (file->isDirectory())
+ continue;
+ Common::String filename = file->getName();
+ if (!filename.hasSuffixIgnoreCase(".l9"))
+ continue;
+
+ // Open up the file and calculate the md5
+ Common::File gameFile;
+ if (!gameFile.open(*file))
+ continue;
+
+ Common::String md5 = Common::computeStreamMD5AsString(gameFile, 5000);
+ size_t filesize = gameFile.size();
+ gameFile.seek(0);
+ bool isBlorb = Blorb::isBlorb(gameFile, ID_ADRI);
+ gameFile.close();
+
+ if (!isBlorb && Blorb::hasBlorbExt(filename))
+ continue;
+
+ // Check for known games
+ const GlkDetectionEntry *p = LEVEL9_GAMES;
+ while (p->_gameId && (md5 != p->_md5 || filesize != p->_filesize))
+ ++p;
+
+ if (!p->_gameId) {
+ const PlainGameDescriptor &desc = LEVEL9_GAME_LIST[0];
+ gameList.push_back(GlkDetectedGame(desc.gameId, desc.description, filename, md5, filesize));
+ } else {
+ PlainGameDescriptor gameDesc = findGame(p->_gameId);
+ gameList.push_back(GlkDetectedGame(p->_gameId, gameDesc.description, p->_extra, filename, p->_language));
+ }
+ }
+
+ return !gameList.empty();
+}
+
+void Level9MetaEngine::detectClashes(Common::StringMap &map) {
+ for (const PlainGameDescriptor *pd = LEVEL9_GAME_LIST; pd->gameId; ++pd) {
+ if (map.contains(pd->gameId))
+ error("Duplicate game Id found - %s", pd->gameId);
+ map[pd->gameId] = "";
+ }
+}
+
+} // End of namespace Level9
+} // End of namespace Glk
diff --git a/engines/glk/level9/detection.h b/engines/glk/level9/detection.h
new file mode 100644
index 0000000..386e475
--- /dev/null
+++ b/engines/glk/level9/detection.h
@@ -0,0 +1,63 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef GLK_LEVEL9_DETECTION
+#define GLK_LEVEL9_DETECTION
+
+#include "common/fs.h"
+#include "common/hash-str.h"
+#include "engines/game.h"
+#include "glk/detection.h"
+
+namespace Glk {
+namespace Level9 {
+
+/**
+ * Meta engine for Level 9 interpreter
+ */
+class Level9MetaEngine {
+public:
+ /**
+ * Get a list of supported games
+ */
+ static void getSupportedGames(PlainGameList &games);
+
+ /**
+ * Returns a game description for the given game Id, if it's supported
+ */
+ static GameDescriptor findGame(const char *gameId);
+
+ /**
+ * Detect supported games
+ */
+ static bool detectGames(const Common::FSList &fslist, DetectedGames &gameList);
+
+ /**
+ * Check for game Id clashes with other sub-engines
+ */
+ static void detectClashes(Common::StringMap &map);
+};
+
+} // End of namespace Level9
+} // End of namespace Glk
+
+#endif
diff --git a/engines/glk/level9/detection_tables.h b/engines/glk/level9/detection_tables.h
new file mode 100644
index 0000000..12b3b0e
--- /dev/null
+++ b/engines/glk/level9/detection_tables.h
@@ -0,0 +1,42 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "engines/game.h"
+#include "common/gui_options.h"
+#include "common/language.h"
+
+namespace Glk {
+namespace Level9 {
+
+// TODO: The list of Level 9 games and detection entries needs to be filled out
+const PlainGameDescriptor LEVEL9_GAME_LIST[] = {
+ { "level9", "Level 9 IF Game" },
+
+ { nullptr, nullptr }
+};
+
+const GlkDetectionEntry LEVEL9_GAMES[] = {
+ DT_END_MARKER
+};
+
+} // End of namespace Level9
+} // End of namespace Glk
diff --git a/engines/glk/level9/level9.cpp b/engines/glk/level9/level9.cpp
new file mode 100644
index 0000000..5cae3dc
--- /dev/null
+++ b/engines/glk/level9/level9.cpp
@@ -0,0 +1,49 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "glk/level9/level9.h"
+
+namespace Glk {
+namespace Level9 {
+
+Level9 *g_vm = nullptr;
+
+Level9::Level9(OSystem *syst, const GlkGameDescription &gameDesc) : GlkAPI(syst, gameDesc) {
+ g_vm = this;
+}
+
+void Level9::runGame() {
+ // TODO
+}
+
+Common::Error Level9::readSaveData(Common::SeekableReadStream *rs) {
+ // TODO
+ return Common::kNoError;
+}
+
+Common::Error Level9::writeGameData(Common::WriteStream *ws) {
+ // TODO
+ return Common::kNoError;
+}
+
+} // End of namespace Level9
+} // End of namespace Glk
diff --git a/engines/glk/level9/level9.h b/engines/glk/level9/level9.h
new file mode 100644
index 0000000..90c441e
--- /dev/null
+++ b/engines/glk/level9/level9.h
@@ -0,0 +1,84 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef GLK_LEVEL9_LEVEL9
+#define GLK_LEVEL9_LEVEL9
+
+#include "common/scummsys.h"
+#include "common/serializer.h"
+#include "common/stack.h"
+#include "glk/glk_api.h"
+
+namespace Glk {
+namespace Level9 {
+
+/**
+ * Level 9 game interpreter
+ */
+class Level9 : public GlkAPI {
+private:
+ /**
+ * Initialization
+ */
+ bool initialize();
+
+ /**
+ * Deinitialization
+ */
+ void deinitialize();
+public:
+ /**
+ * Constructor
+ */
+ Level9(OSystem *syst, const GlkGameDescription &gameDesc);
+
+ /**
+ * Run the game
+ */
+ void runGame();
+
+ /**
+ * Returns the running interpreter type
+ */
+ virtual InterpreterType getInterpreterType() const override {
+ return INTERPRETER_LEVEL9;
+ }
+
+ /**
+ * Load a savegame from the passed Quetzal file chunk stream
+ */
+ virtual Common::Error readSaveData(Common::SeekableReadStream *rs) override;
+
+ /**
+ * Save the game. The passed write stream represents access to the UMem chunk
+ * in the Quetzal save file that will be created
+ */
+ virtual Common::Error writeGameData(Common::WriteStream *ws) override;
+
+};
+
+extern Level9 *g_vm;
+
+} // End of namespace Alan2
+} // End of namespace Glk
+
+#endif
diff --git a/engines/glk/module.mk b/engines/glk/module.mk
index 4da1002..3e474cc 100644
--- a/engines/glk/module.mk
+++ b/engines/glk/module.mk
@@ -226,6 +226,12 @@ MODULE_OBJS += \
jacl/utils.o
endif
+ifdef ENABLE_GLK_LEVEL9
+MODULE_OBJS += \
+ level9/detection.o \
+ level9/level9.o
+endif
+
ifdef ENABLE_GLK_MAGNETIC
MODULE_OBJS += \
magnetic/detection.o \
Commit: d8048720fc6c1104368c995022285462cf9f32f9
https://github.com/scummvm/scummvm/commit/d8048720fc6c1104368c995022285462cf9f32f9
Author: Paul Gilbert (dreammaster at scummvm.org)
Date: 2019-10-26T11:13:29-07:00
Commit Message:
GLK: LEVEL9: Added subengine files
Changed paths:
A engines/glk/level9/bitmap.cpp
A engines/glk/level9/level9_main.cpp
A engines/glk/level9/level9_main.h
A engines/glk/level9/os_glk.cpp
engines/glk/module.mk
diff --git a/engines/glk/level9/bitmap.cpp b/engines/glk/level9/bitmap.cpp
new file mode 100644
index 0000000..976c8a1
--- /dev/null
+++ b/engines/glk/level9/bitmap.cpp
@@ -0,0 +1,1460 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "glk/level9/level9_main.h"
+#include "common/file.h"
+
+namespace Glk {
+namespace Level9 {
+
+extern Bitmap *bitmap;
+
+void L9Allocate(L9BYTE **ptr, L9UINT32 Size);
+
+L9BOOL bitmap_exists(char *file) {
+ return Common::File::exists(file);
+}
+
+L9BYTE *bitmap_load(char *file, L9UINT32 *size) {
+ L9BYTE *data = NULL;
+
+ Common::File f;
+ if (f.open(file)) {
+ *size = f.size();
+ L9Allocate(&data, *size);
+ f.read(data, *size);
+
+ f.close();
+ }
+ return data;
+}
+
+Bitmap *bitmap_alloc(int x, int y) {
+ Bitmap *b = NULL;
+ L9Allocate((L9BYTE **)&b, sizeof(Bitmap) + (x * y));
+
+ b->width = x;
+ b->height = y;
+ b->bitmap = ((L9BYTE *)b) + sizeof(Bitmap);
+ b->npalette = 0;
+ return b;
+}
+
+/*
+ A PC or ST palette colour is a sixteen bit value in which the low three nybbles
+ hold the rgb colour values. The lowest nybble holds the blue value, the
+ second nybble the blue value and the third nybble the red value. (The high
+ nybble is ignored). Within each nybble, only the low three bits are used
+ IE the value can only be 0-7 not the full possible 0-15 and so the MSbit in
+ each nybble is always 0.
+*/
+Colour bitmap_pcst_colour(int big, int small) {
+ Colour col;
+ L9UINT32 r = big & 0xF;
+ L9UINT32 g = (small >> 4) & 0xF;
+ L9UINT32 b = small & 0xF;
+
+ r *= 0x49;
+ r >>= 1;
+ g *= 0x49;
+ g >>= 1;
+ b *= 0x49;
+ b >>= 1;
+
+ col.red = (L9BYTE)(r & 0xFF);
+ col.green = (L9BYTE)(g & 0xFF);
+ col.blue = (L9BYTE)(b & 0xFF);
+ return col;
+}
+
+/*
+ ST Bitmaps
+
+ On the ST different graphics file formats were used for the early V4
+ games (Knight Orc, Gnome Ranger) and the later V4 games (Lancelot,
+ Ingrid's Back, Time & Magik and Scapeghost).
+*/
+
+/*
+ Extracts the number of pixels requested from an eight-byte data block (4 bit-
+ planes) passed to it.
+
+ Note: On entry each one of four pointers is set to point to the start of each
+ bit-plane in the block. The function then indexes through each byte in
+ each bit plane. and uses shift and mask operations to extract each four
+ bit pixel into an L9PIXEL.
+
+ The bit belonging to the pixel in the current byte of the current bit-
+ plane is moved to its position in an eight-bit pixel. The byte is then
+ masked by a value to select only that bit and added to the final pixel
+ value.
+*/
+L9UINT32 bitmap_st1_decode_pixels(L9BYTE *pic, L9BYTE *data, L9UINT32 count, L9UINT32 pixels) {
+ L9UINT32 bitplane_length = count / 4; /* length of each bitplane */
+ L9BYTE *bitplane0 = data; /* address of bit0 bitplane */
+ L9BYTE *bitplane1 = data + (bitplane_length); /* address of bit1 bitplane */
+ L9BYTE *bitplane2 = data + (bitplane_length * 2); /* address of bit2 bitplane */
+ L9BYTE *bitplane3 = data + (bitplane_length * 3); /* address of bit3 bitplane */
+ L9UINT32 bitplane_index, pixel_index = 0; /* index variables */
+
+ for (bitplane_index = 0; bitplane_index < bitplane_length; bitplane_index++) {
+ /* build the eight pixels from the current bitplane bytes, high bit to low */
+
+ /* bit7 byte */
+ pic[pixel_index] = ((bitplane3[bitplane_index] >> 4) & 0x08)
+ + ((bitplane2[bitplane_index] >> 5) & 0x04)
+ + ((bitplane1[bitplane_index] >> 6) & 0x02)
+ + ((bitplane0[bitplane_index] >> 7) & 0x01);
+ if (pixels == ++pixel_index)
+ break;
+
+ /* bit6 byte */
+ pic[pixel_index] = ((bitplane3[bitplane_index] >> 3) & 0x08)
+ + ((bitplane2[bitplane_index] >> 4) & 0x04)
+ + ((bitplane1[bitplane_index] >> 5) & 0x02)
+ + ((bitplane0[bitplane_index] >> 6) & 0x01);
+ if (pixels == ++pixel_index)
+ break;
+
+ /* bit5 byte */
+ pic[pixel_index] = ((bitplane3[bitplane_index] >> 2) & 0x08)
+ + ((bitplane2[bitplane_index] >> 3) & 0x04)
+ + ((bitplane1[bitplane_index] >> 4) & 0x02)
+ + ((bitplane0[bitplane_index] >> 5) & 0x01);
+ if (pixels == ++pixel_index)
+ break;
+
+ /* bit4 byte */
+ pic[pixel_index] = ((bitplane3[bitplane_index] >> 1) & 0x08)
+ + ((bitplane2[bitplane_index] >> 2) & 0x04)
+ + ((bitplane1[bitplane_index] >> 3) & 0x02)
+ + ((bitplane0[bitplane_index] >> 4) & 0x01);
+ if (pixels == ++pixel_index)
+ break;
+
+ /* bit3 byte */
+ pic[pixel_index] = ((bitplane3[bitplane_index]) & 0x08)
+ + ((bitplane2[bitplane_index] >> 1) & 0x04)
+ + ((bitplane1[bitplane_index] >> 2) & 0x02)
+ + ((bitplane0[bitplane_index] >> 3) & 0x01);
+ if (pixels == ++pixel_index)
+ break;
+
+ /* bit2 byte */
+ pic[pixel_index] = ((bitplane3[bitplane_index] << 1) & 0x08)
+ + ((bitplane2[bitplane_index]) & 0x04)
+ + ((bitplane1[bitplane_index] >> 1) & 0x02)
+ + ((bitplane0[bitplane_index] >> 2) & 0x01);
+ if (pixels == ++pixel_index)
+ break;
+
+ /* bit1 byte */
+ pic[pixel_index] = ((bitplane3[bitplane_index] << 2) & 0x08)
+ + ((bitplane2[bitplane_index] << 1) & 0x04)
+ + ((bitplane1[bitplane_index]) & 0x02)
+ + ((bitplane0[bitplane_index] >> 1) & 0x01);
+ if (pixels == ++pixel_index)
+ break;
+
+ /* bit0 byte */
+ pic[pixel_index] = ((bitplane3[bitplane_index] << 3) & 0x08)
+ + ((bitplane2[bitplane_index] << 2) & 0x04)
+ + ((bitplane1[bitplane_index] << 1) & 0x02)
+ + ((bitplane0[bitplane_index]) & 0x01);
+ if (pixels == ++pixel_index)
+ break;
+ }
+
+ return pixel_index;
+}
+
+/*
+ The ST image file has the following format. It consists of a 44 byte header
+ followed by the image data.
+
+ The header has the following format:
+ Bytes 0-31: sixteen entry ST palette
+ Bytes 32-33: padding
+ Bytes 34-35: big-endian word holding number of bitplanes needed to make
+ up a row of pixels*
+ Bytes 36-37: padding
+ Bytes 38-39: big-endian word holding number of rows in the image*
+ Bytes 40-41: padding**
+ Bytes 42-43: mask for pixels to show in last 16 pixel block. Again, this
+ is big endian
+
+ [*] these are probably big-endian unsigned longs but I have designated
+ the upper two bytes as padding because (a) Level 9 does not need
+ them as longs and (b) using unsigned shorts reduces byte sex induced
+ byte order juggling.
+ [**] not certain what this is for but I suspect that, like bytes 42-43
+ it is a mask to indicate which pixels to show, in this case in the
+ first 16 pixel block
+
+ The image data is essentially a memory dump of the video RAM representing
+ the image in lo-res mode. In lo-res mode each row is 320 pixels wide
+ and each pixel can be any one of sixteen colours - needs 4 bits to store.
+
+ In the ST video memory (in lo-res mode which we are dealing with here)
+ is organised as follows. The lowest point in memory in the frame buffer
+ represents the top-left of the screen, the highest the bottom-right.
+ Each row of pixels is stored in sequence.
+
+ Within each pixel row the pixels are stored as follows. Each row is
+ divided into groups of 16 pixels. Each sixteen pixel group is stored
+ in 8 bytes, logically four groups of two. Each two byte pair
+ is a bit-plane for that sixteen pixel group - that is it stores the
+ same bit of each pixel in that group. The first two bytes store the
+ lowest bit, the second pair the second bit &c.
+
+ The word at bytes 34-35 of the header stores the number of bitplanes
+ that make up each pixel row in the image. Multplying this number by
+ four gives the number of pixels in the row***. For title and frame
+ images that will be 320, for sub-images it will be less.
+
+ [***] Not always exactly. For GnomeRanger sub-images this value is 60
+ - implying there are 240 pixels per row. In fact there are only
+ 225 pixels in each row. To identify this situation look at the
+ big-endian word in bytes 42-43 of the header. This is a mask
+ telling you the pixels to use. Each bit represents one pixel in
+ the block, with the MSBit representing the first pixel and the
+ LSbit the last.
+
+ In this situation, the file does contain the entire sixteen
+ pixel block (it has to with the bitplane arrangement) but
+ the pixels which are not part of the image are just noise. When
+ decoding the image, the L9BITMAP produced has the actual pixel
+ dimensions - the surplus pixels are discarded.
+
+ I suspect, though I have not found an instance, that in theory
+ the same situation could apply at the start of a pixel row and that
+ in this case the big-endian word at bytes 40-41 is the mask.
+
+ Having obtained the pixel dimensions of the image the function uses
+ them to allocate memory for the bitmap and then extracts the pixel
+ information from the bitmap row by row. For each row eight byte blocks
+ are read from the image data and passed to UnpackSTv1Pixels along with
+ the number of pixels to extract (usually 16, possibly less for the last
+ block in a row.)
+*/
+L9BOOL bitmap_st1_decode(char *file, int x, int y) {
+ L9BYTE *data = NULL;
+ int i, xi, yi, max_x, max_y, last_block;
+ int bitplanes_row, bitmaps_row, pixel_count, get_pixels;
+
+ L9UINT32 size;
+ data = bitmap_load(file, &size);
+ if (data == NULL)
+ return FALSE;
+
+ bitplanes_row = data[35] + data[34] * 256;
+ bitmaps_row = bitplanes_row / 4;
+ max_x = bitplanes_row * 4;
+ max_y = data[39] + data[38] * 256;
+ last_block = data[43] + data[42] * 256;
+
+ /* Check if sub-image with rows shorter than max_x */
+ if (last_block != 0xFFFF) {
+ /* use last_block to adjust max_x */
+ i = 0;
+ while ((0x0001 & last_block) == 0) { /* test for ls bit set */
+ last_block >>= 1; /* if not, shift right one bit */
+ i++;
+ }
+ max_x = max_x - i;
+ }
+
+ if (max_x > MAX_BITMAP_WIDTH || max_y > MAX_BITMAP_HEIGHT) {
+ free(data);
+ return FALSE;
+ }
+
+ if ((x == 0) && (y == 0)) {
+ if (bitmap)
+ free(bitmap);
+ bitmap = bitmap_alloc(max_x, max_y);
+ }
+ if (bitmap == NULL) {
+ free(data);
+ return FALSE;
+ }
+
+ if (x + max_x > bitmap->width)
+ max_x = bitmap->width - x;
+ if (y + max_y > bitmap->height)
+ max_y = bitmap->height - y;
+
+ for (yi = 0; yi < max_y; yi++) {
+ pixel_count = 0;
+ for (xi = 0; xi < bitmaps_row; xi++) {
+ if ((max_x - pixel_count) < 16)
+ get_pixels = max_x - pixel_count;
+ else
+ get_pixels = 16;
+
+ pixel_count += bitmap_st1_decode_pixels(
+ bitmap->bitmap + ((y + yi) * bitmap->width) + x + (xi * 16),
+ data + 44 + (yi * bitplanes_row * 2) + (xi * 8), 8, get_pixels);
+ }
+ }
+
+ bitmap->npalette = 16;
+ for (i = 0; i < 16; i++)
+ bitmap->palette[i] = bitmap_pcst_colour(data[(i * 2)], data[1 + (i * 2)]);
+
+ free(data);
+ return TRUE;
+}
+
+void bitmap_st2_name(int num, char *dir, char *out) {
+ /* title picture is #30 */
+ if (num == 0)
+ num = 30;
+ sprintf(out, "%s%d.squ", dir, num);
+}
+
+/*
+ PC Bitmaps
+
+ On the PC different graphics file formats were used for the early V4
+ games (Knight Orc, Gnome Ranger) and the later V4 games (Lancelot,
+ Ingrid's Back, Time & Magik and Scapeghost).
+
+ The ST and the PC both use the same image file format for the later
+ V4 games (Lancelot, Ingrid's Back, Time & Magik and Scapeghost.)
+*/
+
+void bitmap_pc_name(int num, char *dir, char *out) {
+ /* title picture is #30 */
+ if (num == 0)
+ num = 30;
+ sprintf(out, "%s%d.pic", dir, num);
+}
+
+/*
+ The EGA standard for the IBM PCs and compatibles defines 64 colors, any
+ 16 of which can be mapped to the usable palette at any given time. If
+ you display these 64 colors in numerical order, 16 at a time, you get a
+ hodgepodge of colors in no logical order. The 64 EGA color numbers are
+ assigned in a way that the numbers can easily be converted to a relative
+ intensity of each of the three phosphor colors R,G,B. If the number is
+ converted to six bit binary, the most significant three bits represent
+ the 25% level of R,G,B in that order and the least significant three
+ bits represent the 75% level of R,G,B in that order. Take EGA color 53
+ for example. In binary, 53 is 110101. Since both R bits are on, R = 1.0.
+ Of the G bits only the 25% bit is on so G = 0.25. Of the B bits only the
+ 75% bit is on so B = 0.75.
+*/
+Colour bitmap_pc1_colour(int i) {
+ Colour col;
+ col.red = (((i & 4) >> 1) | ((i & 0x20) >> 5)) * 0x55;
+ col.green = ((i & 2) | ((i & 0x10) >> 4)) * 0x55;
+ col.blue = (((i & 1) << 1) | ((i & 8) >> 3)) * 0x55;
+ return col;
+}
+
+/*
+ The PC (v1) image file has the following format. It consists of a 22
+ byte header organised like this:
+
+ Byte 0: probably a file type flag
+ Byte 1: the MSB of the file's length as a word
+ Bytes 2-3: little-endian word with picture width in pixels
+ Bytes 4-5: little-endian word with picture height in pixel rows
+ Bytes 6-21: the image colour table. One EGA colour in each byte
+
+ The image data is extremely simple. The entire block is packed array
+ of 4-bit pixels - IE each byte holds two pixels - the first in the high
+ nybble, the second in the low. The pixel value is an index into the
+ image colour table. The pixels are organised with the top left first and
+ bottom left last, each row in turn.
+*/
+L9BOOL bitmap_pc1_decode(char *file, int x, int y) {
+ L9BYTE *data = NULL;
+ int i, xi, yi, max_x, max_y;
+
+ L9UINT32 size;
+ data = bitmap_load(file, &size);
+ if (data == NULL)
+ return FALSE;
+
+ max_x = data[2] + data[3] * 256;
+ max_y = data[4] + data[5] * 256;
+ if (max_x > MAX_BITMAP_WIDTH || max_y > MAX_BITMAP_HEIGHT) {
+ free(data);
+ return FALSE;
+ }
+
+ if ((x == 0) && (y == 0)) {
+ if (bitmap)
+ free(bitmap);
+ bitmap = bitmap_alloc(max_x, max_y);
+ }
+ if (bitmap == NULL) {
+ free(data);
+ return FALSE;
+ }
+
+ if (x + max_x > bitmap->width)
+ max_x = bitmap->width - x;
+ if (y + max_y > bitmap->height)
+ max_y = bitmap->height - y;
+
+ for (yi = 0; yi < max_y; yi++) {
+ for (xi = 0; xi < max_x; xi++) {
+ bitmap->bitmap[(bitmap->width * (y + yi)) + (x + xi)] =
+ (data[23 + ((yi * max_x) / 2) + (xi / 2)] >> ((1 - (xi & 1)) * 4)) & 0x0f;
+ }
+ }
+
+ bitmap->npalette = 16;
+ for (i = 0; i < 16; i++)
+ bitmap->palette[i] = bitmap_pc1_colour(data[6 + i]);
+
+ free(data);
+ return TRUE;
+}
+
+/*
+ The PC (v2) image file has the following format. It consists of a 44
+ byte header followed by the image data.
+
+ The header has the following format:
+ Bytes 0-1: "datalen": length of file -1 as a big-endian word*
+ Bytes 2-3: "flagbyte1 & flagbyte2": unknown, possibly type identifiers.
+ Usually 0xFF or 0xFE followed by 0x84, 0x72, 0xFF, 0xFE or
+ some other (of a fairly small range of possibles) byte.
+ Bytes 4-35: "colour_index[]": sixteen entry palette. Basically an ST
+ palette (even if in a PC image file. Each entry is a sixteen
+ bit value in which the low three nybbles hold the rgb colour
+ values. The lowest nybble holds the blue value, the second
+ nybble the blue value and the third nybble the red value. (The
+ high nybble is ignored). Within each nybble, only the low
+ three bits are used IE the value can only be 0-7 not the full
+ possible 0-15 and so the MSbit in each nybble is always 0.**,
+ Bytes 36-37: "width": image width in pixels as a big-endian word
+ Bytes 38-39: "numrows": image height in pixel rows as a big-endian word
+ Byte 40: "seedByte": seed byte to start picture decoding.
+ Byte 41: "padByte": unknown. Possibly padding to word align the next
+ element?
+ Bytes 42-297: "pixelTable": an array of 0x100 bytes used as a lookup table
+ for pixel values
+ Bytes 298-313: "bitStripTable": an array of 0x10 bytes used as a lookup table
+ for the number of bytes to strip from the bit stream for the pixel being
+ decoded
+ Bytes 314-569: "indexByteTable": an array of 0x100 bytes used as a lookup
+ table to index into bitStripTable and pixelTable****
+
+ The encoded image data then follows ending in a 0x00 at the file length stored
+ in the first two bytes of the file. there is then one extra byte holding a
+ checksum produced by the addition of all the bytes in the file (except the first
+ two and itself)*
+
+ [*] in some PC games the file is padded out beyond this length to the
+ nearest 0x80/0x00 boundary with the byte 0x1A. The valid data in the
+ file still finishes where this word says with the checkbyte following it.
+ [**] I imagine when a game was running on a PC this standard palette
+ was algorithimcally changed to suit the graphics mode being used
+ (Hercules, MDA, CGA, EGA, MCGA, VGA &c.)
+ [***] Note also, in image 1 of PC Time & Magik I think one palette entry
+ is bad as what should be white in the image is actually set to
+ a very pale yellow. This is corrected with the display of the next
+ sub-picture and I am pretty sure it is note a decoding problem
+ here as when run on the PC the same image has the same pale yellow
+ cast.
+ [****] for detail of how all this works see below
+
+ As this file format is intended for two very different platforms the decoded
+ imaged data is in a neutral, intermediate form. Each pixel is extracted as a
+ byte with only the low four bits significant. The pixel value is an index into
+ the sixteen entry palette.
+
+ The pixel data is compressed, presumably to allow a greater number of images
+ to be distributed on the (rather small) default ST & PC floppy disks (in both
+ cases about 370 Kbytes.)*****
+
+ Here's how to decode the data. The image data is actually a contiguous bit
+ stream with the byte structure on disk having almost no relevance to the
+ encoding. We access the bit stream via a two-byte buffer arranged as a word.
+
+ Preparation:
+
+ Initially, move the first byte from the image data into the low byte of
+ theBitStreamBuffer and move the second byte of the image data into the
+ high byte of theBitStreamBuffer.
+
+ Set a counter (theBufferBitCounter) to 8 which you will use to keep track
+ of when it is necesary to refill the buffer.
+
+ Set a L9BYTE variable (theNewPixel) to byte 40 (seedByte) of the header.
+ We need to do this because as part of identifying the pixel being
+ extracted we need to know the value of the previous pixel extracted. Since
+ none exists at this point we must prime this variable with the correct
+ value.
+
+ Extraction:
+
+ Set up a loop which you will execute once for each pixel to be extracted
+ and within that loop do as follows.
+
+ Copy the low byte of theBitStreamBuffer to an L9BYTE
+ (theNewPixelIndexSelector). Examine theNewPixelIndexSelector. If this
+ is 0xFF this flags that the index to the new pixel is present as a
+ literal in the bit stream; if it is NOT 0xFF then the new pixel index
+ value has to be decoded.
+
+ If theNewPixelIndexSelector is NOT 0xFF do as follows:
+
+ Set the variable theNewPixelIndex to the byte in the
+ indexByteTable array of the header indexed by
+ theNewPixelIndexSelector.
+
+ Set the variable theBufferBitStripCount to the value in the
+ bitStripTable array of the header indexed by theNewPixelIndex.
+
+ One-by-one use right bit shift (>>) to remove
+ theBufferBitStripCount bits from theBitStreamBuffer. After each
+ shift decrement theBufferBitCounter and check whether it has
+ reached 0. If it has, get the next byte from the image data and
+ insert it in the high byte of theBitStreamBuffer and reset
+ theBufferBitCounter to 8. What is happening here is as we remove
+ each bit from the bottom of the bit stream buffer we check to see
+ if there are any bits left in the high byte of the buffer. As soon
+ as we know there are none, we refill it with the next eight bits
+ from the image data.
+
+ When this 'bit-stripping' is finished, other than actually identifying
+ the new pixel we are nearly done. I will leave that for the moment and
+ look at what happens if the low byte of theBitStreamBuffer we put in
+ theNewPixelIndexSelector was actually 0xFF:
+
+ In this case, instead of the above routine we begin by removing
+ the low eight bits from the theBitStreamBuffer. We use the same
+ ono-by-one bit shift right process described above to do this,
+ again checking after each shift if it is necesary to refill the
+ buffer's high byte.
+
+ When the eight bits have been removed we set theNewPixelIndex to
+ the value of the low four bits of theBitStreamBuffer. Having done
+ that we again one-by-one strip off those low four bits from the
+ theBitStreamBuffer, again checking if we need to refill the buffer
+ high byte.
+
+ Irrespective of whether we initially had 0xFF in
+ theNewPixelIndexSelector we now have a new value in theNewPixelIndex.
+ This value is used as follows to obtain the new pixel value.
+
+ The variable theNewPixel contains either the seedByte or the value of
+ the previously extracted pixel. In either case this is a 4-bit value
+ in the lower 4 bits. Use the left bit shift operator (or multiply by
+ 16) to shift those four bits into the high four bits of theNewPixel.
+
+ Add the value in theNewPixelIndex (it is a 4-bit value) to
+ theNewPixel. The resulting value is used as an index into the
+ pixelTable array of the header to get the actual new pixel value so
+ theNewPixel = header.pixelTable[theNewPixel] gets us our new pixel and
+ primes theNewPixel for the same process next time around the loop.
+
+ Having got our new pixel it is stored in the next empty space in the
+ bitmap and we loop back and start again.
+
+ [*****] I am not sure how the compression was done - someone with a better
+ understanding of this area may be able to work out the method from the above.
+ I worked out how to decode it by spending many, many hours tracing through the
+ code in a debugger - thanks to the now defunct HiSoft for their DevPac ST and
+ Gerin Philippe for NoSTalgia <http://users.skynet.be/sky39147/>.
+*/
+L9BOOL bitmap_pc2_decode(char *file, int x, int y) {
+ L9BYTE *data = NULL;
+ int i, xi, yi, max_x, max_y;
+
+ L9BYTE theNewPixel, theNewPixelIndex;
+ L9BYTE theBufferBitCounter, theNewPixelIndexSelector, theBufferBitStripCount;
+ L9UINT16 theBitStreamBuffer, theImageDataIndex;
+ L9BYTE *theImageFileData;
+
+ L9UINT32 size;
+ data = bitmap_load(file, &size);
+ if (data == NULL)
+ return FALSE;
+
+ max_x = data[37] + data[36] * 256;
+ max_y = data[39] + data[38] * 256;
+ if (max_x > MAX_BITMAP_WIDTH || max_y > MAX_BITMAP_HEIGHT) {
+ free(data);
+ return FALSE;
+ }
+
+ if ((x == 0) && (y == 0)) {
+ if (bitmap)
+ free(bitmap);
+ bitmap = bitmap_alloc(max_x, max_y);
+ }
+ if (bitmap == NULL) {
+ free(data);
+ return FALSE;
+ }
+
+ if (x + max_x > bitmap->width)
+ max_x = bitmap->width - x;
+ if (y + max_y > bitmap->height)
+ max_y = bitmap->height - y;
+
+ /* prime the new pixel variable with the seed byte */
+ theNewPixel = data[40];
+ /* initialise the index to the image data */
+ theImageDataIndex = 0;
+ /* prime the bit stream buffer */
+ theImageFileData = data + 570;
+ theBitStreamBuffer = theImageFileData[theImageDataIndex++];
+ theBitStreamBuffer = theBitStreamBuffer +
+ (0x100 * theImageFileData[theImageDataIndex++]);
+ /* initialise the bit stream buffer bit counter */
+ theBufferBitCounter = 8;
+
+ for (yi = 0; yi < max_y; yi++) {
+ for (xi = 0; xi < max_x; xi++) {
+ theNewPixelIndexSelector = (theBitStreamBuffer & 0x00FF);
+ if (theNewPixelIndexSelector != 0xFF) {
+ /* get index for new pixel and bit strip count */
+ theNewPixelIndex = (data + 314)[theNewPixelIndexSelector];
+ /* get the bit strip count */
+ theBufferBitStripCount = (data + 298)[theNewPixelIndex];
+ /* strip theBufferBitStripCount bits from theBitStreamBuffer */
+ while (theBufferBitStripCount > 0) {
+ theBitStreamBuffer = theBitStreamBuffer >> 1;
+ theBufferBitStripCount--;
+ theBufferBitCounter--;
+ if (theBufferBitCounter == 0) {
+ /* need to refill the theBitStreamBuffer high byte */
+ theBitStreamBuffer = theBitStreamBuffer +
+ (0x100 * theImageFileData[theImageDataIndex++]);
+ /* re-initialise the bit stream buffer bit counter */
+ theBufferBitCounter = 8;
+ }
+ }
+ } else {
+ /* strip the 8 bits holding 0xFF from theBitStreamBuffer */
+ theBufferBitStripCount = 8;
+ while (theBufferBitStripCount > 0) {
+ theBitStreamBuffer = theBitStreamBuffer >> 1;
+ theBufferBitStripCount--;
+ theBufferBitCounter--;
+ if (theBufferBitCounter == 0) {
+ /* need to refill the theBitStreamBuffer high byte */
+ theBitStreamBuffer = theBitStreamBuffer +
+ (0x100 * theImageFileData[theImageDataIndex++]);
+ /* re-initialise the bit stream buffer bit counter */
+ theBufferBitCounter = 8;
+ }
+ }
+ /* get the literal pixel index value from the bit stream */
+ theNewPixelIndex = (0x000F & theBitStreamBuffer);
+ theBufferBitStripCount = 4;
+ /* strip 4 bits from theBitStreamBuffer */
+ while (theBufferBitStripCount > 0) {
+ theBitStreamBuffer = theBitStreamBuffer >> 1;
+ theBufferBitStripCount--;
+ theBufferBitCounter--;
+ if (theBufferBitCounter == 0) {
+ /* need to refill the theBitStreamBuffer high byte */
+ theBitStreamBuffer = theBitStreamBuffer +
+ (0x100 * theImageFileData[theImageDataIndex++]);
+ /* re-initialise the bit stream buffer bit counter */
+ theBufferBitCounter = 8;
+ }
+ }
+ }
+
+ /* shift the previous pixel into the high four bits of theNewPixel */
+ theNewPixel = (0xF0 & (theNewPixel << 4));
+ /* add the index to the new pixel to theNewPixel */
+ theNewPixel = theNewPixel + theNewPixelIndex;
+ /* extract the nex pixel from the table */
+ theNewPixel = (data + 42)[theNewPixel];
+ /* store new pixel in the bitmap */
+ bitmap->bitmap[(bitmap->width * (y + yi)) + (x + xi)] = theNewPixel;
+ }
+ }
+
+ bitmap->npalette = 16;
+ for (i = 0; i < 16; i++)
+ bitmap->palette[i] = bitmap_pcst_colour(data[4 + (i * 2)], data[5 + (i * 2)]);
+
+ free(data);
+ return TRUE;
+}
+
+BitmapType bitmap_pc_type(char *file) {
+ BitmapType type = PC2_BITMAPS;
+
+ Common::File f;
+ if (f.open(file)) {
+ L9BYTE data[6];
+ int x, y;
+
+ if (f.read(data, sizeof(data)) != sizeof(data) && !f.eos())
+ return NO_BITMAPS;
+ f.close();
+
+ x = data[2] + data[3] * 256;
+ y = data[4] + data[5] * 256;
+
+ if ((x == 0x0140) && (y == 0x0087))
+ type = PC1_BITMAPS;
+ if ((x == 0x00E0) && (y == 0x0074))
+ type = PC1_BITMAPS;
+ if ((x == 0x0140) && (y == 0x0087))
+ type = PC1_BITMAPS;
+ if ((x == 0x00E1) && (y == 0x0076))
+ type = PC1_BITMAPS;
+ }
+
+ return type;
+}
+
+/*
+ Amiga Bitmaps
+*/
+
+void bitmap_noext_name(int num, char *dir, char *out) {
+ if (num == 0) {
+ sprintf(out, "%stitle", dir);
+ if (Common::File::exists(out))
+ return;
+
+ num = 30;
+ }
+
+ sprintf(out, "%s%d", dir, num);
+}
+
+int bitmap_amiga_intensity(int col) {
+ return (int)(pow((double)col / 15, 1.0 / 0.8) * 0xff);
+}
+
+/*
+ Amiga palette colours are word length structures with the red, green and blue
+ values stored in the second, third and lowest nybles respectively. The high
+ nybble is always zero.
+*/
+Colour bitmap_amiga_colour(int i1, int i2) {
+ Colour col;
+ col.red = bitmap_amiga_intensity(i1 & 0xf);
+ col.green = bitmap_amiga_intensity(i2 >> 4);
+ col.blue = bitmap_amiga_intensity(i2 & 0xf);
+ return col;
+}
+
+/*
+ The Amiga image file has the following format. It consists of a 44 byte
+ header followed by the image data.
+
+ The header has the following format:
+ Bytes 0-63: thirty-two entry Amiga palette
+ Bytes 64-65: padding
+ Bytes 66-67: big-endian word holding picture width in pixels*
+ Bytes 68-69: padding
+ Bytes 70-71: big-endian word holding number of pixel rows in the image*
+
+ [*] these are probably big-endian unsigned longs but I have designated
+ the upper two bytes as padding because (a) Level 9 does not need
+ them as longs and (b) using unsigned shorts reduces byte sex induced
+ byte order juggling.
+
+ The images are designed for an Amiga low-res mode screen - that is they
+ assume a 320*256 (or 320 * 200 if NSTC display) screen with a palette of
+ 32 colours from the possible 4096.
+
+ The image data is organised the same way that Amiga video memory is. The
+ entire data block is divided into five equal length bit planes with the
+ first bit plane holding the low bit of each 5-bit pixel, the second bitplane
+ the second bit of the pixel and so on up to the fifth bit plane holding the
+ high bit of the f5-bit pixel.
+*/
+L9BOOL bitmap_amiga_decode(char *file, int x, int y) {
+ L9BYTE *data = NULL;
+ int i, xi, yi, max_x, max_y, p, b;
+
+ L9UINT32 size;
+ data = bitmap_load(file, &size);
+ if (data == NULL)
+ return FALSE;
+
+ max_x = (((((data[64] << 8) | data[65]) << 8) | data[66]) << 8) | data[67];
+ max_y = (((((data[68] << 8) | data[69]) << 8) | data[70]) << 8) | data[71];
+ if (max_x > MAX_BITMAP_WIDTH || max_y > MAX_BITMAP_HEIGHT) {
+ free(data);
+ return FALSE;
+ }
+
+ if ((x == 0) && (y == 0)) {
+ if (bitmap)
+ free(bitmap);
+ bitmap = bitmap_alloc(max_x, max_y);
+ }
+ if (bitmap == NULL) {
+ free(data);
+ return FALSE;
+ }
+
+ if (x + max_x > bitmap->width)
+ max_x = bitmap->width - x;
+ if (y + max_y > bitmap->height)
+ max_y = bitmap->height - y;
+
+ for (yi = 0; yi < max_y; yi++) {
+ for (xi = 0; xi < max_x; xi++) {
+ p = 0;
+ for (b = 0; b < 5; b++)
+ p |= ((data[72 + (max_x / 8) * (max_y * b + yi) + xi / 8] >> (7 - (xi % 8))) & 1) << b;
+ bitmap->bitmap[(bitmap->width * (y + yi)) + (x + xi)] = p;
+ }
+ }
+
+ bitmap->npalette = 32;
+ for (i = 0; i < 32; i++)
+ bitmap->palette[i] = bitmap_amiga_colour(data[i * 2], data[i * 2 + 1]);
+
+ free(data);
+ return TRUE;
+}
+
+BitmapType bitmap_noext_type(char *file) {
+ Common::File f;
+ if (f.open(file)) {
+ L9BYTE data[72];
+ int x, y;
+
+ if (f.read(data, sizeof(data)) != sizeof(data) && !f.eos())
+ return NO_BITMAPS;
+ f.close();
+
+ x = data[67] + data[66] * 256;
+ y = data[71] + data[70] * 256;
+
+ if ((x == 0x0140) && (y == 0x0088))
+ return AMIGA_BITMAPS;
+ if ((x == 0x0140) && (y == 0x0087))
+ return AMIGA_BITMAPS;
+ if ((x == 0x00E0) && (y == 0x0075))
+ return AMIGA_BITMAPS;
+ if ((x == 0x00E4) && (y == 0x0075))
+ return AMIGA_BITMAPS;
+ if ((x == 0x00E0) && (y == 0x0076))
+ return AMIGA_BITMAPS;
+ if ((x == 0x00DB) && (y == 0x0076))
+ return AMIGA_BITMAPS;
+
+ x = data[3] + data[2] * 256;
+ y = data[7] + data[6] * 256;
+
+ if ((x == 0x0200) && (y == 0x00D8))
+ return MAC_BITMAPS;
+ if ((x == 0x0168) && (y == 0x00BA))
+ return MAC_BITMAPS;
+ if ((x == 0x0168) && (y == 0x00BC))
+ return MAC_BITMAPS;
+ if ((x == 0x0200) && (y == 0x00DA))
+ return MAC_BITMAPS;
+ if ((x == 0x0168) && (y == 0x00DA))
+ return MAC_BITMAPS;
+
+ x = data[35] + data[34] * 256;
+ y = data[39] + data[38] * 256;
+
+ if ((x == 0x0050) && (y == 0x0087))
+ return ST1_BITMAPS;
+ if ((x == 0x0038) && (y == 0x0074))
+ return ST1_BITMAPS;
+ }
+
+ return NO_BITMAPS;
+}
+
+/*
+ Macintosh Bitmaps
+*/
+
+/*
+ The Mac image file format is very simple. The header is ten bytes
+ with the width of the image in pixels in the first long and the
+ height (in pixel rows) in the second long - both are big-endian.
+ (In both cases I treat these as unsigned shorts to minimise byte
+ twiddling when working around byte sex issues). There follow two
+ unidentified bytes - possibly image type identifiers or maybe
+ valid pixel masks for the beginning and end of pixel rows in
+ sub-images.
+
+ The image data is extremely simple. The entire block is a packed array
+ of 1-bit pixels - I.E. each byte holds eight pixels - with 1 representing
+ white and 0 representing black. The pixels are organised with the top
+ left first and bottom left last, each row in turn.
+
+ The image sizes are 512 * 216 pixels for main images and 360 * 186 pixels
+ for sub-images.
+*/
+L9BOOL bitmap_mac_decode(char *file, int x, int y) {
+ L9BYTE *data = NULL;
+ int xi, yi, max_x, max_y;
+
+ L9UINT32 size;
+ data = bitmap_load(file, &size);
+ if (data == NULL)
+ return FALSE;
+
+ max_x = data[3] + data[2] * 256;
+ max_y = data[7] + data[6] * 256;
+ if (max_x > MAX_BITMAP_WIDTH || max_y > MAX_BITMAP_HEIGHT) {
+ free(data);
+ return FALSE;
+ }
+
+ if (x > 0) /* Mac bug, apparently */
+ x = 78;
+
+ if ((x == 0) && (y == 0)) {
+ if (bitmap)
+ free(bitmap);
+ bitmap = bitmap_alloc(max_x, max_y);
+ }
+ if (bitmap == NULL) {
+ free(data);
+ return FALSE;
+ }
+
+ if (x + max_x > bitmap->width)
+ max_x = bitmap->width - x;
+ if (y + max_y > bitmap->height)
+ max_y = bitmap->height - y;
+
+ for (yi = 0; yi < max_y; yi++) {
+ for (xi = 0; xi < max_x; xi++) {
+ bitmap->bitmap[(bitmap->width * (y + yi)) + (x + xi)] =
+ (data[10 + (max_x / 8) * yi + xi / 8] >> (7 - (xi % 8))) & 1;
+ }
+ }
+
+ bitmap->npalette = 2;
+ bitmap->palette[0].red = 0;
+ bitmap->palette[0].green = 0;
+ bitmap->palette[0].blue = 0;
+ bitmap->palette[1].red = 0xff;
+ bitmap->palette[1].green = 0xff;
+ bitmap->palette[1].blue = 0xff;
+
+ free(data);
+ return TRUE;
+}
+
+/*
+ C64 Bitmaps, also related formats (BBC B, Amstrad CPC and Spectrum +3)
+*/
+
+/* Commodore 64 palette from Vice */
+const Colour bitmap_c64_colours[] = {
+ {0x00, 0x00, 0x00 },
+ {0xff, 0xff, 0xff },
+ {0x89, 0x40, 0x36 },
+ {0x7a, 0xbf, 0xc7 },
+ {0x8a, 0x46, 0xae },
+ {0x68, 0xa9, 0x41 },
+ {0x3e, 0x31, 0xa2 },
+ {0xd0, 0xdc, 0x71 },
+ {0x90, 0x5f, 0x25 },
+ {0x5c, 0x47, 0x00 },
+ {0xbb, 0x77, 0x6d },
+ {0x55, 0x55, 0x55 },
+ {0x80, 0x80, 0x80 },
+ {0xac, 0xea, 0x88 },
+ {0x7c, 0x70, 0xda },
+ {0xab, 0xab, 0xab }
+};
+
+const Colour bitmap_bbc_colours[] = {
+ {0x00, 0x00, 0x00 },
+ {0xff, 0x00, 0x00 },
+ {0x00, 0xff, 0x00 },
+ {0xff, 0xff, 0x00 },
+ {0x00, 0x00, 0xff },
+ {0xff, 0x00, 0xff },
+ {0x00, 0xff, 0xff },
+ {0xff, 0xff, 0xff }
+};
+
+void bitmap_c64_name(int num, char *dir, char *out) {
+ if (num == 0)
+ sprintf(out, "%stitle mpic", dir);
+ else
+ sprintf(out, "%spic%d", dir, num);
+}
+
+void bitmap_bbc_name(int num, char *dir, char *out) {
+ if (num == 0) {
+ sprintf(out, "%sP.Title", dir);
+ if (Common::File::exists(out))
+ return;
+
+ sprintf(out, "%stitle", dir);
+ } else {
+ sprintf(out, "%sP.Pic%d", dir, num);
+ if (Common::File::exists(out))
+ return;
+
+ sprintf(out, "%spic%d", dir, num);
+ }
+}
+
+void bitmap_cpc_name(int num, char *dir, char *out) {
+ if (num == 0)
+ sprintf(out, "%stitle.pic", dir);
+ else if (num == 1)
+ sprintf(out, "%s1.pic", dir);
+ else
+ sprintf(out, "%sallpics.pic", dir);
+}
+
+BitmapType bitmap_c64_type(char *file) {
+ BitmapType type = C64_BITMAPS;
+
+ Common::File f;
+ if (f.open(file)) {
+ L9UINT32 size = f.size();
+ f.close();
+
+ if (size == 10048)
+ type = BBC_BITMAPS;
+ if (size == 6494)
+ type = BBC_BITMAPS;
+ }
+
+ return type;
+}
+
+/*
+ The C64 graphics file format is (loosely) based on the layout of
+ C64 graphics memory. There are in fact two formats (i) the
+ standard game images and (ii) title pictures. For both formats
+ the file begins within the 2-byte pair 0x00 and 0x20.
+
+ The images are "multi-color bitmap mode" images which means they
+ have rows of 160 double width pixels and can be up to 200 rows
+ long. (The title images are 200 lines long, the game images are
+ 136 lines long.) Unlike Amiga, Mac, ST and PC graphics there are
+ no "main" and "sub" images. All game graphics have the same
+ dimensions and each completely replaces its predecessor.
+
+ The graphics files used on the Amstrad CPC and Spectrum +3 are also
+ virtually identical to C64 graphics files. This choice was presumably
+ made because although the CPC screen was more capable than the c64 it
+ was (in low resolution) the same size (160*200) and presumably
+ algorothmic conversion conversion of the colours was trivial for
+ the interpreter. In addition (a) the artwork already existed so no
+ extra expense would be incurred and (b) by accepting the C64's
+ limitation of only four colours in each 4*8 pixel block (but still
+ with sixteen colours on screen) they got a compressed file format
+ allowing more pictures on each disk.
+
+ The file organisation is rather different though. Only picture
+ one and the title picture are separate files. All the other
+ pictures (2-29) are stored in one large file "allpics.pic".
+
+ On these platforms the picture 1 file and title picture file have
+ an AMSDOS header (a 128 byte block of metadata) which contains a
+ checksum of the first 66 bytes of the header in a little-endian
+ word at bytes 67 & 68. On the original C64 platform there was a
+ simple two byte header. Following the header the data is organised
+ exactly as in the C64 game and title image files. The
+ 'allpics.pic" file has no header and consists of 0x139E blocks
+ each forming a picture, in the C64 game file format (minus the two
+ byte header).
+*/
+L9BOOL bitmap_c64_decode(char *file, BitmapType type, int num) {
+ L9BYTE *data = NULL;
+ int i = 0, xi, yi, max_x = 0, max_y = 0, cx, cy, px, py, p;
+ int off = 0, off_scr = 0, off_col = 0, off_bg = 0, col_comp = 0;
+
+ L9UINT32 size;
+ data = bitmap_load(file, &size);
+ if (data == NULL)
+ return FALSE;
+
+ if (type == C64_BITMAPS) {
+ if (size == 10018) { /* C64 title picture */
+ max_x = 320;
+ max_y = 200;
+ off = 2;
+ off_scr = 8002;
+ off_bg = 9003;
+ off_col = 9018;
+ col_comp = 0;
+ } else if (size == 6464) { /* C64 picture */
+ max_x = 320;
+ max_y = 136;
+ off = 2;
+ off_scr = 5442;
+ off_col = 6122;
+ off_bg = 6463;
+ col_comp = 1;
+ } else
+ return FALSE;
+ } else if (type == BBC_BITMAPS) {
+ if (size == 10058) { /* BBC title picture */
+ max_x = 320;
+ max_y = 200;
+ off = 10;
+ off_scr = 8010;
+ off_bg = 9011;
+ off_col = 9026;
+ col_comp = 0;
+ } else if (size == 10048) { /* BBC title picture */
+ max_x = 320;
+ max_y = 200;
+ off = 0;
+ off_scr = 8000;
+ off_bg = 9001;
+ off_col = 9016;
+ col_comp = 0;
+ } else if (size == 6504) { /* BBC picture */
+ max_x = 320;
+ max_y = 136;
+ off = 10;
+ off_scr = 5450;
+ off_col = 6130;
+ off_bg = 6471;
+ col_comp = 1;
+ } else if (size == 6494) { /* BBC picture */
+ max_x = 320;
+ max_y = 136;
+ off = 0;
+ off_scr = 5440;
+ off_col = 6120;
+ off_bg = 6461;
+ col_comp = 1;
+ } else
+ return FALSE;
+ } else if (type == CPC_BITMAPS) {
+ if (num == 0) { /* CPC/+3 title picture */
+ max_x = 320;
+ max_y = 200;
+ off = 128;
+ off_scr = 8128;
+ off_bg = 9128;
+ off_col = 9144;
+ col_comp = 0;
+ } else if (num == 1) { /* First CPC/+3 picture */
+ max_x = 320;
+ max_y = 136;
+ off = 128;
+ off_scr = 5568;
+ off_col = 6248;
+ off_bg = 6588;
+ col_comp = 1;
+ } else if (num >= 2 && num <= 29) { /* Subsequent CPC/+3 pictures */
+ max_x = 320;
+ max_y = 136;
+ off = ((num - 2) * 6462);
+ off_scr = 5440 + ((num - 2) * 6462);
+ off_col = 6120 + ((num - 2) * 6462);
+ off_bg = 6460 + ((num - 2) * 6462);
+ col_comp = 1;
+ } else
+ return FALSE;
+ }
+
+ if (bitmap)
+ free(bitmap);
+ bitmap = bitmap_alloc(max_x, max_y);
+ if (bitmap == NULL) {
+ free(data);
+ return FALSE;
+ }
+
+ for (yi = 0; yi < max_y; yi++) {
+ for (xi = 0; xi < max_x / 2; xi++) {
+ cx = xi / 4;
+ px = xi % 4;
+ cy = yi / 8;
+ py = yi % 8;
+
+ p = data[off + (cy * 40 + cx) * 8 + py];
+ p = (p >> ((3 - px) * 2)) & 3;
+
+ switch (p) {
+ case 0:
+ i = data[off_bg] & 0x0f;
+ break;
+ case 1:
+ i = data[off_scr + cy * 40 + cx] >> 4;
+ break;
+ case 2:
+ i = data[off_scr + cy * 40 + cx] & 0x0f;
+ break;
+ case 3:
+ if (col_comp)
+ i = (data[off_col + (cy * 40 + cx) / 2] >> ((1 - (cx % 2)) * 4)) & 0x0f;
+ else
+ i = data[off_col + (cy * 40 + cx)] & 0x0f;
+ break;
+ }
+
+ bitmap->bitmap[(bitmap->width * yi) + (xi * 2)] = i;
+ bitmap->bitmap[(bitmap->width * yi) + (xi * 2) + 1] = i;
+ }
+ }
+
+ bitmap->npalette = 16;
+ for (i = 0; i < 16; i++)
+ bitmap->palette[i] = bitmap_c64_colours[i];
+
+ free(data);
+ return TRUE;
+}
+
+/*
+ The graphics files used by the BBC B are virtually identical
+ to C64 graphics files. I assume that (as with the CPC and
+ Spectrum+3) this choice was made because the BBC mode 2 screen,
+ was nearly the same size (160*256) and had roughly the same capability
+ as the C64 screen (displays 16 colours, although eight of those ar
+ just the first eight flashing).
+
+ In addition (a) the artwork already existed so no extra expense would
+ be incurred and (b) by accepting the C64's limitation of only four
+ colours in each 4*8 pixel block (but still with sixteen colours on
+ screen) they got a compressed file format allowing more pictures
+ on each disk.
+
+ The file organisation is very close to the C64. The naming system
+ can be the same eg "PIC12", but another form is also used :
+ "P.Pic12". Unlike the C64 the BBC has well defined title images,
+ called "TITLE" or P.Title. All pictures are in separate files.
+
+ The only difference seems to be:
+
+ * There is either *no* header before the image data or a simple
+ 10 byte header which I think *may* be a file system header
+ left in place by the extractor system.
+
+ * There is an extra 32 bytes following the data at the end of
+ each file. These bytes encode a table to convert between the 16
+ C64 colours and 16, four-pixel pix-patterns used to let the BBC
+ (with only 8 colours) represent the sixteen possible C64 colours.
+
+ A pix-pattern looks like this:
+
+ | Even | Odd |
+ | Column | Column |
+ -----------------------------
+ Even Row |Pixel 1 | Pixel 2 |
+ ---------|--------|---------|
+ Odd Row |Pixel 3 | Pixel 4 |
+ -----------------------------
+
+ Each of the four pixel *can* be any of the eight BBC Mode 2
+ steady colours. In practice they seem either to be all the
+ same or a simple check of two colours - the pixels in the
+ odd row being in the reverse order to those in the even row.
+
+ When converting a C64 pixel to a BBC pixel the game uses the
+ value of the C64 pixel as an index into the array of sixteen
+ BBC pix-patterns. The game looks at the selected pattern and
+ chooses the BBC pixel colour thus: if the pixel is in an even
+ numbered row and an even numbered column, it uses Pixel 1 from
+ the pattern, if in an even row but an odd column, it uses Pixel 3
+ and so on.
+
+ The pix-pattern data is encoded thus: the first sixteen bytes
+ encode the even row pixels for the patterns, one byte per
+ pattern, and in the same way the second sixteen bytes encode
+ the odd row pixels for each pattern. For example for the
+ pattern representing C64 colour 0 the even row pixels are encoded
+ in the first byte and the odd row pixels in the sixteenth byte.
+
+ Within each byte the pixels are encoded in this way:
+
+ Bit 7 6 5 4 3 2 1 0
+ -------------------------------------
+ 0 0 1 0 0 1 1 1
+ | | | | | | | |
+ +---|---+---|---+---|---+---|----- Even Pixel 0101 (5)
+ | | | |
+ +-------+-------+-------+----- Odd Pixel 0011 (3)
+
+ This function calls the C64 decoding routines to do the actual
+ loading. See the comments to that function for details of how the
+ image is encoded and stored.
+*/
+L9BOOL bitmap_bbc_decode(char *file, BitmapType type, int num) {
+ unsigned char patRowData[32];
+ unsigned char patArray[16][2][2];
+ int i, j, k, isOddColumn, isOddRow;
+ L9BYTE pixel;
+
+ if (bitmap_c64_decode(file, type, num) == FALSE)
+ return FALSE;
+
+ Common::File f;
+ if (!f.open(file))
+ return FALSE;
+
+ /* Seek to the offset of the pixPat data and read in the data */
+ f.seek(f.size() - 32, SEEK_SET);
+ if (f.read(patRowData, 32) != 32 && !f.eos())
+ return FALSE;
+ f.close();
+
+ /* Extract the patterns */
+ i = 0;
+ for (k = 0; k < 2; k++) {
+ for (j = 0; j < 16; j++) {
+ /* Extract the even col pixel for this pattern row */
+ patArray[j][k][0] =
+ ((patRowData[i] >> 4) & 0x8) + ((patRowData[i] >> 3) & 0x4) +
+ ((patRowData[i] >> 2) & 0x2) + ((patRowData[i] >> 1) & 0x1);
+ /* Extract the odd col pixel for this pattern row */
+ patArray[j][k][1] =
+ ((patRowData[i] >> 3) & 0x8) + ((patRowData[i] >> 2) & 0x4) +
+ ((patRowData[i] >> 1) & 0x2) + (patRowData[i] & 0x1);
+ i++;
+ }
+ }
+
+ /* Convert the image. Each BBC pixel is represented by two pixels here */
+ i = 0;
+ isOddRow = 0;
+ for (j = 0; j < bitmap->height; j++) {
+ isOddColumn = 0;
+ for (k = 0; k < bitmap->width / 2; k++) {
+ pixel = bitmap->bitmap[i];
+ bitmap->bitmap[i] = patArray[pixel][isOddColumn][isOddRow];
+ bitmap->bitmap[i + 1] = patArray[pixel][isOddColumn][isOddRow];
+ isOddColumn ^= 1;
+ i += 2;
+ }
+ isOddRow ^= 1;
+ }
+
+ bitmap->npalette = 8;
+ for (i = 0; i < 8; i++)
+ bitmap->palette[i] = bitmap_bbc_colours[i];
+
+ return TRUE;
+}
+
+BitmapType DetectBitmaps(char *dir) {
+ char file[MAX_PATH];
+
+ bitmap_noext_name(2, dir, file);
+ if (bitmap_exists(file))
+ return bitmap_noext_type(file);
+
+ bitmap_pc_name(2, dir, file);
+ if (bitmap_exists(file))
+ return bitmap_pc_type(file);
+
+ bitmap_c64_name(2, dir, file);
+ if (bitmap_exists(file))
+ return bitmap_c64_type(file);
+
+ bitmap_bbc_name(2, dir, file);
+ if (bitmap_exists(file))
+ return BBC_BITMAPS;
+
+ bitmap_cpc_name(2, dir, file);
+ if (bitmap_exists(file))
+ return CPC_BITMAPS;
+
+ bitmap_st2_name(2, dir, file);
+ if (bitmap_exists(file))
+ return ST2_BITMAPS;
+
+ return NO_BITMAPS;
+}
+
+Bitmap *DecodeBitmap(char *dir, BitmapType type, int num, int x, int y) {
+ char file[MAX_PATH];
+
+ switch (type) {
+ case PC1_BITMAPS:
+ bitmap_pc_name(num, dir, file);
+ if (bitmap_pc1_decode(file, x, y))
+ return bitmap;
+ break;
+
+ case PC2_BITMAPS:
+ bitmap_pc_name(num, dir, file);
+ if (bitmap_pc2_decode(file, x, y))
+ return bitmap;
+ break;
+
+ case AMIGA_BITMAPS:
+ bitmap_noext_name(num, dir, file);
+ if (bitmap_amiga_decode(file, x, y))
+ return bitmap;
+ break;
+
+ case C64_BITMAPS:
+ bitmap_c64_name(num, dir, file);
+ if (bitmap_c64_decode(file, type, num))
+ return bitmap;
+ break;
+
+ case BBC_BITMAPS:
+ bitmap_bbc_name(num, dir, file);
+ if (bitmap_bbc_decode(file, type, num))
+ return bitmap;
+ break;
+
+ case CPC_BITMAPS:
+ bitmap_cpc_name(num, dir, file);
+ if (bitmap_c64_decode(file, type, num)) /* Nearly identical to C64 */
+ return bitmap;
+ break;
+
+ case MAC_BITMAPS:
+ bitmap_noext_name(num, dir, file);
+ if (bitmap_mac_decode(file, x, y))
+ return bitmap;
+ break;
+
+ case ST1_BITMAPS:
+ bitmap_noext_name(num, dir, file);
+ if (bitmap_st1_decode(file, x, y))
+ return bitmap;
+ break;
+
+ case ST2_BITMAPS:
+ bitmap_st2_name(num, dir, file);
+ if (bitmap_pc2_decode(file, x, y))
+ return bitmap;
+ break;
+
+ default:
+ break;
+ }
+
+ return NULL;
+}
+
+} // End of namespace Level9
+} // End of namespace Glk
diff --git a/engines/glk/level9/level9_main.cpp b/engines/glk/level9/level9_main.cpp
new file mode 100644
index 0000000..719d05b
--- /dev/null
+++ b/engines/glk/level9/level9_main.cpp
@@ -0,0 +1,3599 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+/*
+ * The input routine will repond to the following 'hash' commands
+ * #save saves position file directly (bypasses any
+ * disk change prompts)
+ * #restore restores position file directly (bypasses any
+ * protection code)
+ * #quit terminates current game, RunGame() will return FALSE
+ * #cheat tries to bypass restore protection on v3,4 games
+ * (can be slow)
+ * #dictionary lists game dictionary (press a key to interrupt)
+ * #picture <n> show picture <n>
+ * #seed <n> set the random number seed to the value <n>
+ * #play plays back a file as the input to the game
+ *
+\***********************************************************************/
+
+#include "glk/level9/level9_main.h"
+#include "glk/level9/level9.h"
+#include "common/file.h"
+#include "common/system.h"
+
+namespace Glk {
+namespace Level9 {
+
+/* #define L9DEBUG */
+/* #define CODEFOLLOW */
+/* #define FULLSCAN */
+
+/* "L901" */
+#define L9_ID 0x4c393031
+
+#define IBUFFSIZE 500
+#define RAMSAVESLOTS 10
+#define GFXSTACKSIZE 100
+#define FIRSTLINESIZE 96
+
+/* Typedefs */
+typedef struct {
+ L9UINT16 vartable[256];
+ L9BYTE listarea[LISTAREASIZE];
+} SaveStruct;
+
+/* Enumerations */
+enum L9GameTypes { L9_V1, L9_V2, L9_V3, L9_V4 };
+enum L9MsgTypes { MSGT_V1, MSGT_V2 };
+/*
+ Graphics type Resolution Scale stack reset
+ -------------------------------------------------
+ GFX_V2 160 x 128 yes
+ GFX_V3A 160 x 96 yes
+ GFX_V3B 160 x 96 no
+ GFX_V3C 320 x 96 no
+*/
+enum L9GfxTypes { GFX_V2, GFX_V3A, GFX_V3B, GFX_V3C };
+
+/* Global Variables */
+L9BYTE *startfile = NULL, *pictureaddress = NULL, *picturedata = NULL;
+L9BYTE *startdata;
+L9UINT32 FileSize, picturesize;
+
+L9BYTE *L9Pointers[12];
+L9BYTE *absdatablock, *list2ptr, *list3ptr, *list9startptr, *acodeptr;
+L9BYTE *startmd, *endmd, *endwdp5, *wordtable, *dictdata, *defdict;
+L9UINT16 dictdatalen;
+L9BYTE *startmdV2;
+
+int wordcase;
+int unpackcount;
+char unpackbuf[8];
+L9BYTE *dictptr;
+char threechars[34];
+int L9GameType;
+int L9MsgType;
+char LastGame[MAX_PATH];
+char FirstLine[FIRSTLINESIZE];
+int FirstLinePos = 0;
+int FirstPicture = -1;
+
+#if defined(AMIGA) && defined(_DCC)
+__far SaveStruct ramsavearea[RAMSAVESLOTS];
+#else
+SaveStruct ramsavearea[RAMSAVESLOTS];
+#endif
+
+GameState workspace;
+
+L9UINT16 randomseed;
+L9UINT16 constseed = 0;
+L9BOOL Running;
+
+char ibuff[IBUFFSIZE];
+L9BYTE *ibuffptr;
+char obuff[34];
+Common::SeekableReadStream *scriptfile = NULL;
+
+L9BOOL Cheating = FALSE;
+int CheatWord;
+GameState CheatWorkspace;
+
+int reflectflag, scale, gintcolour, option;
+int l9textmode = 0, drawx = 0, drawy = 0, screencalled = 0, showtitle = 1;
+L9BYTE *gfxa5 = NULL;
+Bitmap *bitmap = NULL;
+int gfx_mode = GFX_V2;
+
+L9BYTE *GfxA5Stack[GFXSTACKSIZE];
+int GfxA5StackPos = 0;
+int GfxScaleStack[GFXSTACKSIZE];
+int GfxScaleStackPos = 0;
+
+char lastchar = '.';
+char lastactualchar = 0;
+int d5;
+
+L9BYTE *codeptr; /* instruction codes */
+L9BYTE code;
+
+L9BYTE *list9ptr;
+
+int unpackd3;
+
+L9BYTE exitreversaltable[16] = {0x00, 0x04, 0x06, 0x07, 0x01, 0x08, 0x02, 0x03, 0x05, 0x0a, 0x09, 0x0c, 0x0b, 0xff, 0xff, 0x0f};
+
+L9UINT16 gnostack[128];
+L9BYTE gnoscratch[32];
+int object, gnosp, numobjectfound, searchdepth, inithisearchpos;
+
+
+struct L9V1GameInfo {
+ L9BYTE dictVal1, dictVal2;
+ int dictStart, L9Ptrs[5], absData, msgStart, msgLen;
+};
+struct L9V1GameInfo L9V1Games[] = {
+ 0x1a, 0x24, 301, 0x0000, -0x004b, 0x0080, -0x002b, 0x00d0, 0x03b0, 0x0f80, 0x4857, /* Colossal Adventure */
+ 0x20, 0x3b, 283, -0x0583, 0x0000, -0x0508, -0x04e0, 0x0000, 0x0800, 0x1000, 0x39d1, /* Adventure Quest */
+ 0x14, 0xff, 153, -0x00d6, 0x0000, 0x0000, 0x0000, 0x0000, 0x0a20, 0x16bf, 0x420d, /* Dungeon Adventure */
+ 0x15, 0x5d, 252, -0x3e70, 0x0000, -0x3d30, -0x3ca0, 0x0100, 0x4120, -0x3b9d, 0x3988, /* Lords of Time */
+ 0x15, 0x6c, 284, -0x00f0, 0x0000, -0x0050, -0x0050, -0x0050, 0x0300, 0x1930, 0x3c17, /* Snowball */
+};
+int L9V1Game = -1;
+
+
+/* Prototypes */
+L9BOOL LoadGame2(char *filename, char *picname);
+int getlongcode(void);
+L9BOOL GetWordV2(char *buff, int Word);
+L9BOOL GetWordV3(char *buff, int Word);
+void show_picture(int pic);
+
+
+#ifdef CODEFOLLOW
+#define CODEFOLLOWFILE "c:\\temp\\level9.txt"
+FILE *f;
+L9UINT16 *cfvar, *cfvar2;
+char *codes[] = {
+ "Goto",
+ "intgosub",
+ "intreturn",
+ "printnumber",
+ "messagev",
+ "messagec",
+ "function",
+ "input",
+ "varcon",
+ "varvar",
+ "_add",
+ "_sub",
+ "ilins",
+ "ilins",
+ "jump",
+ "Exit",
+ "ifeqvt",
+ "ifnevt",
+ "ifltvt",
+ "ifgtvt",
+ "screen",
+ "cleartg",
+ "picture",
+ "getnextobject",
+ "ifeqct",
+ "ifnect",
+ "ifltct",
+ "ifgtct",
+ "printinput",
+ "ilins",
+ "ilins",
+ "ilins",
+};
+char *functions[] = {
+ "calldriver",
+ "L9Random",
+ "save",
+ "restore",
+ "clearworkspace",
+ "clearstack"
+};
+char *drivercalls[] = {
+ "init",
+ "drivercalcchecksum",
+ "driveroswrch",
+ "driverosrdch",
+ "driverinputline",
+ "driversavefile",
+ "driverloadfile",
+ "settext",
+ "resettask",
+ "returntogem",
+ "10 *",
+ "loadgamedatafile",
+ "randomnumber",
+ "13 *",
+ "driver14",
+ "15 *",
+ "driverclg",
+ "line",
+ "fill",
+ "driverchgcol",
+ "20 *",
+ "21 *",
+ "ramsave",
+ "ramload",
+ "24 *",
+ "lensdisplay",
+ "26 *",
+ "27 *",
+ "28 *",
+ "29 *",
+ "allocspace",
+ "31 *",
+ "showbitmap",
+ "33 *",
+ "checkfordisc"
+};
+#endif
+
+void initdict(L9BYTE *ptr) {
+ dictptr = ptr;
+ unpackcount = 8;
+}
+
+char getdictionarycode(void) {
+ if (unpackcount != 8) return unpackbuf[unpackcount++];
+ else {
+ /* unpackbytes */
+ L9BYTE d1 = *dictptr++, d2;
+ unpackbuf[0] = d1 >> 3;
+ d2 = *dictptr++;
+ unpackbuf[1] = ((d2 >> 6) + (d1 << 2)) & 0x1f;
+ d1 = *dictptr++;
+ unpackbuf[2] = (d2 >> 1) & 0x1f;
+ unpackbuf[3] = ((d1 >> 4) + (d2 << 4)) & 0x1f;
+ d2 = *dictptr++;
+ unpackbuf[4] = ((d1 << 1) + (d2 >> 7)) & 0x1f;
+ d1 = *dictptr++;
+ unpackbuf[5] = (d2 >> 2) & 0x1f;
+ unpackbuf[6] = ((d2 << 3) + (d1 >> 5)) & 0x1f;
+ unpackbuf[7] = d1 & 0x1f;
+ unpackcount = 1;
+ return unpackbuf[0];
+ }
+}
+
+int getdictionary(int d0) {
+ if (d0 >= 0x1a) return getlongcode();
+ else return d0 + 0x61;
+}
+
+int getlongcode(void) {
+ int d0, d1;
+ d0 = getdictionarycode();
+ if (d0 == 0x10) {
+ wordcase = 1;
+ d0 = getdictionarycode();
+ return getdictionary(d0); /* reentrant? */
+ }
+ d1 = getdictionarycode();
+ return 0x80 | ((d0 << 5) & 0xe0) | (d1 & 0x1f);
+}
+
+void printchar(char c) {
+ if (Cheating) return;
+
+ if (c & 128)
+ lastchar = (c &= 0x7f);
+ else if (c != 0x20 && c != 0x0d && (c < '\"' || c >= '.')) {
+ if (lastchar == '!' || lastchar == '?' || lastchar == '.') c = toupper(c);
+ lastchar = c;
+ }
+ /* eat multiple CRs */
+ if (c != 0x0d || lastactualchar != 0x0d) {
+ os_printchar(c);
+ if (FirstLinePos < FIRSTLINESIZE - 1)
+ FirstLine[FirstLinePos++] = tolower(c);
+ }
+ lastactualchar = c;
+}
+
+void printstring(const char *buf) {
+ int i;
+ for (i = 0; i < (int) strlen(buf); i++) printchar(buf[i]);
+}
+
+void printdecimald0(int d0) {
+ char temp[12];
+ sprintf(temp, "%d", d0);
+ printstring(temp);
+}
+
+void printautocase(int d0) {
+ if (d0 & 128) printchar((char) d0);
+ else {
+ if (wordcase) printchar((char) toupper(d0));
+ else if (d5 < 6) printchar((char) d0);
+ else {
+ wordcase = 0;
+ printchar((char) toupper(d0));
+ }
+ }
+}
+
+void displaywordref(L9UINT16 Off) {
+ static int mdtmode = 0;
+
+ wordcase = 0;
+ d5 = (Off >> 12) & 7;
+ Off &= 0xfff;
+ if (Off < 0xf80) {
+ /* dwr01 */
+ L9BYTE *a0, *oPtr, *a3;
+ int d0, d2, i;
+
+ if (mdtmode == 1) printchar(0x20);
+ mdtmode = 1;
+
+ /* setindex */
+ a0 = dictdata;
+ d2 = dictdatalen;
+
+ /* dwr02 */
+ oPtr = a0;
+ while (d2 && Off >= L9WORD(a0 + 2)) {
+ a0 += 4;
+ d2--;
+ }
+ /* dwr04 */
+ if (a0 == oPtr) {
+ a0 = defdict;
+ } else {
+ a0 -= 4;
+ Off -= L9WORD(a0 + 2);
+ a0 = startdata + L9WORD(a0);
+ }
+ /* dwr04b */
+ Off++;
+ initdict(a0);
+ a3 = (L9BYTE *) threechars; /* a3 not set in original, prevent possible spam */
+
+ /* dwr05 */
+ while (TRUE) {
+ d0 = getdictionarycode();
+ if (d0 < 0x1c) {
+ /* dwr06 */
+ if (d0 >= 0x1a) d0 = getlongcode();
+ else d0 += 0x61;
+ *a3++ = d0;
+ } else {
+ d0 &= 3;
+ a3 = (L9BYTE *) threechars + d0;
+ if (--Off == 0) break;
+ }
+ }
+ for (i = 0; i < d0; i++) printautocase(threechars[i]);
+
+ /* dwr10 */
+ while (TRUE) {
+ d0 = getdictionarycode();
+ if (d0 >= 0x1b) return;
+ printautocase(getdictionary(d0));
+ }
+ }
+
+ else {
+ if (d5 & 2) printchar(0x20); /* prespace */
+ mdtmode = 2;
+ Off &= 0x7f;
+ if (Off != 0x7e) printchar((char)Off);
+ if (d5 & 1) printchar(0x20); /* postspace */
+ }
+}
+
+int getmdlength(L9BYTE **Ptr) {
+ int tot = 0, len;
+ do {
+ len = (*(*Ptr)++ -1) & 0x3f;
+ tot += len;
+ } while (len == 0x3f);
+ return tot;
+}
+
+void printmessage(int Msg) {
+ L9BYTE *Msgptr = startmd;
+ L9BYTE Data;
+
+ int len;
+ L9UINT16 Off;
+
+ while (Msg > 0 && Msgptr - endmd <= 0) {
+ Data = *Msgptr;
+ if (Data & 128) {
+ Msgptr++;
+ Msg -= Data & 0x7f;
+ } else {
+ len = getmdlength(&Msgptr);
+ Msgptr += len;
+ }
+ Msg--;
+ }
+ if (Msg < 0 || *Msgptr & 128) return;
+
+ len = getmdlength(&Msgptr);
+ if (len == 0) return;
+
+ while (len) {
+ Data = *Msgptr++;
+ len--;
+ if (Data & 128) {
+ /* long form (reverse word) */
+ Off = (Data << 8) + *Msgptr++;
+ len--;
+ } else {
+ Off = (wordtable[Data * 2] << 8) + wordtable[Data * 2 + 1];
+ }
+ if (Off == 0x8f80) break;
+ displaywordref(Off);
+ }
+}
+
+/* v2 message stuff */
+
+int msglenV2(L9BYTE **ptr) {
+ int i = 0;
+ L9BYTE a;
+
+ /* catch berzerking code */
+ if (*ptr >= startdata + FileSize) return 0;
+
+ while ((a = **ptr) == 0) {
+ (*ptr)++;
+
+ if (*ptr >= startdata + FileSize) return 0;
+
+ i += 255;
+ }
+ i += a;
+ return i;
+}
+
+void printcharV2(char c) {
+ if (c == 0x25) c = 0xd;
+ else if (c == 0x5f) c = 0x20;
+ printautocase(c);
+}
+
+void displaywordV2(L9BYTE *ptr, int msg) {
+ int n;
+ L9BYTE a;
+ if (msg == 0) return;
+ while (--msg) {
+ ptr += msglenV2(&ptr);
+ }
+ n = msglenV2(&ptr);
+
+ while (--n > 0) {
+ a = *++ptr;
+ if (a < 3) return;
+
+ if (a >= 0x5e) displaywordV2(startmdV2 - 1, a - 0x5d);
+ else printcharV2((char)(a + 0x1d));
+ }
+}
+
+int msglenV1(L9BYTE **ptr) {
+ L9BYTE *ptr2 = *ptr;
+ while (ptr2 < startdata + FileSize && *ptr2++ != 1);
+ return ptr2 - *ptr;
+}
+
+void displaywordV1(L9BYTE *ptr, int msg) {
+ int n;
+ L9BYTE a;
+ while (msg--) {
+ ptr += msglenV1(&ptr);
+ }
+ n = msglenV1(&ptr);
+
+ while (--n > 0) {
+ a = *ptr++;
+ if (a < 3) return;
+
+ if (a >= 0x5e) displaywordV1(startmdV2, a - 0x5e);
+ else printcharV2((char)(a + 0x1d));
+ }
+}
+
+L9BOOL amessageV2(L9BYTE *ptr, int msg, long *w, long *c) {
+ int n;
+ L9BYTE a;
+ static int depth = 0;
+ if (msg == 0) return FALSE;
+ while (--msg) {
+ ptr += msglenV2(&ptr);
+ }
+ if (ptr >= startdata + FileSize) return FALSE;
+ n = msglenV2(&ptr);
+
+ while (--n > 0) {
+ a = *++ptr;
+ if (a < 3) return TRUE;
+
+ if (a >= 0x5e) {
+ if (++depth > 10 || !amessageV2(startmdV2 - 1, a - 0x5d, w, c)) {
+ depth--;
+ return FALSE;
+ }
+ depth--;
+ } else {
+ char ch = a + 0x1d;
+ if (ch == 0x5f || ch == ' ')(*w)++;
+ else (*c)++;
+ }
+ }
+ return TRUE;
+}
+
+L9BOOL amessageV1(L9BYTE *ptr, int msg, long *w, long *c) {
+ int n;
+ L9BYTE a;
+ static int depth = 0;
+
+ while (msg--) {
+ ptr += msglenV1(&ptr);
+ }
+ if (ptr >= startdata + FileSize) return FALSE;
+ n = msglenV1(&ptr);
+
+ while (--n > 0) {
+ a = *ptr++;
+ if (a < 3) return TRUE;
+
+ if (a >= 0x5e) {
+ if (++depth > 10 || !amessageV1(startmdV2, a - 0x5e, w, c)) {
+ depth--;
+ return FALSE;
+ }
+ depth--;
+ } else {
+ char ch = a + 0x1d;
+ if (ch == 0x5f || ch == ' ')(*w)++;
+ else (*c)++;
+ }
+ }
+ return TRUE;
+}
+
+L9BOOL analyseV2(double *wl) {
+ long words = 0, chars = 0;
+ int i;
+ for (i = 1; i < 256; i++) {
+ long w = 0, c = 0;
+ if (amessageV2(startmd, i, &w, &c)) {
+ words += w;
+ chars += c;
+ } else return FALSE;
+ }
+ *wl = words ? (double) chars / words : 0.0;
+ return TRUE;
+}
+
+L9BOOL analyseV1(double *wl) {
+ long words = 0, chars = 0;
+ int i;
+ for (i = 0; i < 256; i++) {
+ long w = 0, c = 0;
+ if (amessageV1(startmd, i, &w, &c)) {
+ words += w;
+ chars += c;
+ } else return FALSE;
+ }
+
+ *wl = words ? (double) chars / words : 0.0;
+ return TRUE;
+}
+
+void printmessageV2(int Msg) {
+ if (L9MsgType == MSGT_V2) displaywordV2(startmd, Msg);
+ else displaywordV1(startmd, Msg);
+}
+
+void L9Allocate(L9BYTE **ptr, L9UINT32 Size) {
+ if (*ptr) free(*ptr);
+ *ptr = (L9BYTE *)malloc(Size);
+ if (*ptr == NULL) {
+ error("Unable to allocate memory for the game! Exiting...");
+ }
+}
+
+void FreeMemory(void) {
+ if (startfile) {
+ free(startfile);
+ startfile = NULL;
+ }
+ if (pictureaddress) {
+ free(pictureaddress);
+ pictureaddress = NULL;
+ }
+ if (bitmap) {
+ free(bitmap);
+ bitmap = NULL;
+ }
+ if (scriptfile) {
+ delete scriptfile;
+ scriptfile = NULL;
+ }
+ picturedata = NULL;
+ picturesize = 0;
+ gfxa5 = NULL;
+}
+
+L9BOOL load(const char *filename) {
+ Common::File f;
+ if (!f.open(filename))
+ return FALSE;
+
+ if ((FileSize = f.size()) < 256) {
+ f.close();
+ error("File is too small to contain a Level 9 game");
+ return FALSE;
+ }
+
+ L9Allocate(&startfile, FileSize);
+ if (f.read(startfile, FileSize) != FileSize) {
+ f.close();
+ return FALSE;
+ }
+
+ f.close();
+ return TRUE;
+}
+
+L9UINT16 scanmovewa5d0(L9BYTE *Base, L9UINT32 *Pos) {
+ L9UINT16 ret = L9WORD(Base + *Pos);
+ (*Pos) += 2;
+ return ret;
+}
+
+L9UINT32 scangetaddr(int Code, L9BYTE *Base, L9UINT32 *Pos, L9UINT32 acode, int *Mask) {
+ (*Mask) |= 0x20;
+ if (Code & 0x20) {
+ /* getaddrshort */
+ signed char diff = Base[*Pos];
+ (*Pos)++;
+ return (*Pos) + diff - 1;
+ } else {
+ return acode + scanmovewa5d0(Base, Pos);
+ }
+}
+
+void scangetcon(int Code, L9UINT32 *Pos, int *Mask) {
+ (*Pos)++;
+ if (!(Code & 64))(*Pos)++;
+ (*Mask) |= 0x40;
+}
+
+L9BOOL CheckCallDriverV4(L9BYTE *Base, L9UINT32 Pos) {
+ int i, j;
+
+ /* Look back for an assignment from a variable
+ * to list9[0], which is used to specify the
+ * driver call.
+ */
+ for (i = 0; i < 2; i++) {
+ int x = Pos - ((i + 1) * 3);
+ if ((Base[x] == 0x89) && (Base[x + 1] == 0x00)) {
+ /* Get the variable being copied to list9[0] */
+ int var = Base[x + 2];
+
+ /* Look back for an assignment to the variable. */
+ for (j = 0; j < 2; j++) {
+ int y = x - ((j + 1) * 3);
+ if ((Base[y] == 0x48) && (Base[y + 2] == var)) {
+ /* If this a V4 driver call? */
+ switch (Base[y + 1]) {
+ case 0x0E:
+ case 0x20:
+ case 0x22:
+ return TRUE;
+ }
+ return FALSE;
+ }
+ }
+ }
+ }
+ return FALSE;
+}
+
+L9BOOL ValidateSequence(L9BYTE *Base, L9BYTE *Image, L9UINT32 iPos, L9UINT32 acode, L9UINT32 *Size, L9UINT32 size, L9UINT32 *Min, L9UINT32 *Max, L9BOOL Rts, L9BOOL *JumpKill, L9BOOL *DriverV4) {
+ L9UINT32 Pos;
+ L9BOOL Finished = FALSE, Valid;
+ L9UINT32 Strange = 0;
+ int ScanCodeMask;
+ int Code;
+ *JumpKill = FALSE;
+
+ if (iPos >= size)
+ return FALSE;
+ Pos = iPos;
+ if (Pos < *Min) *Min = Pos;
+
+ if (Image[Pos]) return TRUE; /* hit valid code */
+
+ do {
+ Code = Base[Pos];
+ Valid = TRUE;
+ if (Image[Pos]) break; /* converged to found code */
+ Image[Pos++] = 2;
+ if (Pos > *Max) *Max = Pos;
+
+ ScanCodeMask = 0x9f;
+ if (Code & 0x80) {
+ ScanCodeMask = 0xff;
+ if ((Code & 0x1f) > 0xa)
+ Valid = FALSE;
+ Pos += 2;
+ } else switch (Code & 0x1f) {
+ case 0: { /* goto */
+ L9UINT32 Val = scangetaddr(Code, Base, &Pos, acode, &ScanCodeMask);
+ Valid = ValidateSequence(Base, Image, Val, acode, Size, size, Min, Max, TRUE/*Rts*/, JumpKill, DriverV4);
+ Finished = TRUE;
+ break;
+ }
+ case 1: { /* intgosub */
+ L9UINT32 Val = scangetaddr(Code, Base, &Pos, acode, &ScanCodeMask);
+ Valid = ValidateSequence(Base, Image, Val, acode, Size, size, Min, Max, TRUE, JumpKill, DriverV4);
+ break;
+ }
+ case 2: /* intreturn */
+ Valid = Rts;
+ Finished = TRUE;
+ break;
+ case 3: /* printnumber */
+ Pos++;
+ break;
+ case 4: /* messagev */
+ Pos++;
+ break;
+ case 5: /* messagec */
+ scangetcon(Code, &Pos, &ScanCodeMask);
+ break;
+ case 6: /* function */
+ switch (Base[Pos++]) {
+ case 2:/* random */
+ Pos++;
+ break;
+ case 1:/* calldriver */
+ if (DriverV4) {
+ if (CheckCallDriverV4(Base, Pos - 2))
+ *DriverV4 = TRUE;
+ }
+ break;
+ case 3:/* save */
+ case 4:/* restore */
+ case 5:/* clearworkspace */
+ case 6:/* clear stack */
+ break;
+ case 250: /* printstr */
+ while (Base[Pos++]);
+ break;
+
+ default:
+#ifdef L9DEBUG
+ /* printf("scan: illegal function call: %d",Base[Pos-1]); */
+#endif
+ Valid = FALSE;
+ break;
+ }
+ break;
+ case 7: /* input */
+ Pos += 4;
+ break;
+ case 8: /* varcon */
+ scangetcon(Code, &Pos, &ScanCodeMask);
+ Pos++;
+ break;
+ case 9: /* varvar */
+ Pos += 2;
+ break;
+ case 10: /* _add */
+ Pos += 2;
+ break;
+ case 11: /* _sub */
+ Pos += 2;
+ break;
+ case 14: /* jump */
+#ifdef L9DEBUG
+ /* printf("jmp at codestart: %ld",acode); */
+#endif
+ *JumpKill = TRUE;
+ Finished = TRUE;
+ break;
+ case 15: /* exit */
+ Pos += 4;
+ break;
+ case 16: /* ifeqvt */
+ case 17: /* ifnevt */
+ case 18: /* ifltvt */
+ case 19: { /* ifgtvt */
+ L9UINT32 Val;
+ Pos += 2;
+ Val = scangetaddr(Code, Base, &Pos, acode, &ScanCodeMask);
+ Valid = ValidateSequence(Base, Image, Val, acode, Size, size, Min, Max, Rts, JumpKill, DriverV4);
+ break;
+ }
+ case 20: /* screen */
+ if (Base[Pos++]) Pos++;
+ break;
+ case 21: /* cleartg */
+ Pos++;
+ break;
+ case 22: /* picture */
+ Pos++;
+ break;
+ case 23: /* getnextobject */
+ Pos += 6;
+ break;
+ case 24: /* ifeqct */
+ case 25: /* ifnect */
+ case 26: /* ifltct */
+ case 27: { /* ifgtct */
+ L9UINT32 Val;
+ Pos++;
+ scangetcon(Code, &Pos, &ScanCodeMask);
+ Val = scangetaddr(Code, Base, &Pos, acode, &ScanCodeMask);
+ Valid = ValidateSequence(Base, Image, Val, acode, Size, size, Min, Max, Rts, JumpKill, DriverV4);
+ break;
+ }
+ case 28: /* printinput */
+ break;
+ case 12: /* ilins */
+ case 13: /* ilins */
+ case 29: /* ilins */
+ case 30: /* ilins */
+ case 31: /* ilins */
+#ifdef L9DEBUG
+ /* printf("scan: illegal instruction"); */
+#endif
+ Valid = FALSE;
+ break;
+ }
+ if (Valid && (Code & ~ScanCodeMask))
+ Strange++;
+ } while (Valid && !Finished && Pos < size); /* && Strange==0); */
+ (*Size) += Pos - iPos;
+ return Valid; /* && Strange==0; */
+}
+
+L9BYTE calcchecksum(L9BYTE *ptr, L9UINT32 num) {
+ L9BYTE d1 = 0;
+ while (num-- != 0) d1 += *ptr++;
+ return d1;
+}
+
+/*
+L9BOOL Check(L9BYTE* StartFile,L9UINT32 FileSize,L9UINT32 Offset)
+{
+ L9UINT16 d0,num;
+ int i;
+ L9BYTE* Image;
+ L9UINT32 Size=0,Min,Max;
+ L9BOOL ret,JumpKill;
+
+ for (i=0;i<12;i++)
+ {
+ d0=L9WORD (StartFile+Offset+0x12 + i*2);
+ if (d0>=0x8000+LISTAREASIZE) return FALSE;
+ }
+
+ num=L9WORD(StartFile+Offset)+1;
+ if (Offset+num>FileSize) return FALSE;
+ if (calcchecksum(StartFile+Offset,num)) return FALSE;
+
+ Image=calloc(FileSize,1);
+
+ Min=Max=Offset+d0;
+ ret=ValidateSequence(StartFile,Image,Offset+d0,Offset+d0,&Size,FileSize,&Min,&Max,FALSE,&JumpKill,NULL);
+ free(Image);
+ return ret;
+}
+*/
+
+long Scan(L9BYTE *StartFile, L9UINT32 size) {
+ L9BYTE *Chk = (L9BYTE *)malloc(size + 1);
+ L9BYTE *Image = (L9BYTE *)calloc(size, 1);
+ L9UINT32 i, num, Size, MaxSize = 0;
+ int j;
+ L9UINT16 d0 = 0, l9, md, ml, dd, dl;
+ L9UINT32 Min, Max;
+ long Offset = -1;
+ L9BOOL JumpKill, DriverV4;
+
+ if ((Chk == NULL) || (Image == NULL)) {
+ error("Unable to allocate memory for game scan! Exiting...");
+ }
+
+ Chk[0] = 0;
+ for (i = 1; i <= size; i++)
+ Chk[i] = Chk[i - 1] + StartFile[i - 1];
+
+ for (i = 0; i < size - 33; i++) {
+ num = L9WORD(StartFile + i) + 1;
+ /*
+ Chk[i] = 0 +...+ i-1
+ Chk[i+n] = 0 +...+ i+n-1
+ Chk[i+n] - Chk[i] = i + ... + i+n
+ */
+ if (num > 0x2000 && i + num <= size && Chk[i + num] == Chk[i]) {
+ md = L9WORD(StartFile + i + 0x2);
+ ml = L9WORD(StartFile + i + 0x4);
+ dd = L9WORD(StartFile + i + 0xa);
+ dl = L9WORD(StartFile + i + 0xc);
+
+ if (ml > 0 && md > 0 && i + md + ml <= size && dd > 0 && dl > 0 && i + dd + dl * 4 <= size) {
+ /* v4 files may have acodeptr in 8000-9000, need to fix */
+ for (j = 0; j < 12; j++) {
+ d0 = L9WORD(StartFile + i + 0x12 + j * 2);
+ if (j != 11 && d0 >= 0x8000 && d0 < 0x9000) {
+ if (d0 >= 0x8000 + LISTAREASIZE) break;
+ } else if (i + d0 > size) break;
+ }
+ /* list9 ptr must be in listarea, acode ptr in data */
+ if (j < 12 /*|| (d0>=0x8000 && d0<0x9000)*/) continue;
+
+ l9 = L9WORD(StartFile + i + 0x12 + 10 * 2);
+ if (l9 < 0x8000 || l9 >= 0x8000 + LISTAREASIZE) continue;
+
+ Size = 0;
+ Min = Max = i + d0;
+ DriverV4 = 0;
+ if (ValidateSequence(StartFile, Image, i + d0, i + d0, &Size, size, &Min, &Max, FALSE, &JumpKill, &DriverV4)) {
+#ifdef L9DEBUG
+ printf("Found valid header at %ld, code size %ld", i, Size);
+#endif
+ if (Size > MaxSize && Size > 100) {
+ Offset = i;
+ MaxSize = Size;
+ L9GameType = DriverV4 ? L9_V4 : L9_V3;
+ }
+ }
+ }
+ }
+ }
+ free(Chk);
+ free(Image);
+ return Offset;
+}
+
+long ScanV2(L9BYTE *StartFile, L9UINT32 size) {
+ L9BYTE *Chk = (L9BYTE *)malloc(size + 1);
+ L9BYTE *Image = (L9BYTE *)calloc(size, 1);
+ L9UINT32 i, Size, MaxSize = 0, num;
+ int j;
+ L9UINT16 d0 = 0, l9;
+ L9UINT32 Min, Max;
+ long Offset = -1;
+ L9BOOL JumpKill;
+
+ if ((Chk == NULL) || (Image == NULL)) {
+ error("Unable to allocate memory for game scan! Exiting...");
+ }
+
+ Chk[0] = 0;
+ for (i = 1; i <= size; i++)
+ Chk[i] = Chk[i - 1] + StartFile[i - 1];
+
+ for (i = 0; i < size - 28; i++) {
+ num = L9WORD(StartFile + i + 28) + 1;
+ if (i + num <= size && ((Chk[i + num] - Chk[i + 32]) & 0xff) == StartFile[i + 0x1e]) {
+ for (j = 0; j < 14; j++) {
+ d0 = L9WORD(StartFile + i + j * 2);
+ if (j != 13 && d0 >= 0x8000 && d0 < 0x9000) {
+ if (d0 >= 0x8000 + LISTAREASIZE) break;
+ } else if (i + d0 > size) break;
+ }
+ /* list9 ptr must be in listarea, acode ptr in data */
+ if (j < 14 /*|| (d0>=0x8000 && d0<0x9000)*/) continue;
+
+ l9 = L9WORD(StartFile + i + 6 + 9 * 2);
+ if (l9 < 0x8000 || l9 >= 0x8000 + LISTAREASIZE) continue;
+
+ Size = 0;
+ Min = Max = i + d0;
+ if (ValidateSequence(StartFile, Image, i + d0, i + d0, &Size, size, &Min, &Max, FALSE, &JumpKill, NULL)) {
+#ifdef L9DEBUG
+ printf("Found valid V2 header at %ld, code size %ld", i, Size);
+#endif
+ if (Size > MaxSize && Size > 100) {
+ Offset = i;
+ MaxSize = Size;
+ }
+ }
+ }
+ }
+ free(Chk);
+ free(Image);
+ return Offset;
+}
+
+long ScanV1(L9BYTE *StartFile, L9UINT32 size) {
+ L9BYTE *Image = (L9BYTE *)calloc(size, 1);
+ L9UINT32 i, Size;
+ int Replace;
+ L9BYTE *ImagePtr;
+ long MaxPos = -1;
+ L9UINT32 MaxCount = 0;
+ L9UINT32 Min, Max; //, MaxMax, MaxMin;
+ L9BOOL JumpKill; // , MaxJK;
+
+ int dictOff1 = 0, dictOff2 = 0;
+ L9BYTE dictVal1 = 0xff, dictVal2 = 0xff;
+
+ if (Image == NULL) {
+ error("Unable to allocate memory for game scan! Exiting...");
+ }
+
+ for (i = 0; i < size; i++) {
+ if ((StartFile[i] == 0 && StartFile[i + 1] == 6) || (StartFile[i] == 32 && StartFile[i + 1] == 4)) {
+ Size = 0;
+ Min = Max = i;
+ Replace = 0;
+ if (ValidateSequence(StartFile, Image, i, i, &Size, size, &Min, &Max, FALSE, &JumpKill, NULL)) {
+ if (Size > MaxCount && Size > 100 && Size < 10000) {
+ MaxCount = Size;
+ //MaxMin = Min;
+ //MaxMax = Max;
+
+ MaxPos = i;
+ //MaxJK = JumpKill;
+ }
+ Replace = 0;
+ }
+ for (ImagePtr = Image + Min; ImagePtr <= Image + Max; ImagePtr++) {
+ if (*ImagePtr == 2)
+ *ImagePtr = Replace;
+ }
+ }
+ }
+#ifdef L9DEBUG
+ printf("V1scan found code at %ld size %ld", MaxPos, MaxCount);
+#endif
+
+ /* V1 dictionary detection from L9Cut by Paul David Doherty */
+ for (i = 0; i < size - 20; i++) {
+ if (StartFile[i] == 'A') {
+ if (StartFile[i + 1] == 'T' && StartFile[i + 2] == 'T' && StartFile[i + 3] == 'A' && StartFile[i + 4] == 'C' && StartFile[i + 5] == 0xcb) {
+ dictOff1 = i;
+ dictVal1 = StartFile[dictOff1 + 6];
+ break;
+ }
+ }
+ }
+ for (i = dictOff1; i < size - 20; i++) {
+ if (StartFile[i] == 'B') {
+ if (StartFile[i + 1] == 'U' && StartFile[i + 2] == 'N' && StartFile[i + 3] == 'C' && StartFile[i + 4] == 0xc8) {
+ dictOff2 = i;
+ dictVal2 = StartFile[dictOff2 + 5];
+ break;
+ }
+ }
+ }
+ L9V1Game = -1;
+ if (dictVal1 != 0xff || dictVal2 != 0xff) {
+ for (i = 0; i < sizeof L9V1Games / sizeof L9V1Games[0]; i++) {
+ if ((L9V1Games[i].dictVal1 == dictVal1) && (L9V1Games[i].dictVal2 == dictVal2)) {
+ L9V1Game = i;
+ dictdata = StartFile + dictOff1 - L9V1Games[i].dictStart;
+ }
+ }
+ }
+
+#ifdef L9DEBUG
+ if (L9V1Game >= 0)
+ printf("V1scan found known dictionary: %d", L9V1Game);
+#endif
+
+ free(Image);
+
+ if (MaxPos > 0) {
+ acodeptr = StartFile + MaxPos;
+ return 0;
+ }
+ return -1;
+}
+
+#ifdef FULLSCAN
+void FullScan(L9BYTE *StartFile, L9UINT32 size) {
+ L9BYTE *Image = (L9BYTE *)calloc(size, 1);
+ L9UINT32 i, Size;
+ int Replace;
+ L9BYTE *ImagePtr;
+ L9UINT32 MaxPos = 0;
+ L9UINT32 MaxCount = 0;
+ L9UINT32 Min, Max, MaxMin, MaxMax;
+ int Offset;
+ L9BOOL JumpKill, MaxJK;
+ for (i = 0; i < size; i++) {
+ Size = 0;
+ Min = Max = i;
+ Replace = 0;
+ if (ValidateSequence(StartFile, Image, i, i, &Size, size, &Min, &Max, FALSE, &JumpKill, NULL)) {
+ if (Size > MaxCount) {
+ MaxCount = Size;
+ MaxMin = Min;
+ MaxMax = Max;
+
+ MaxPos = i;
+ MaxJK = JumpKill;
+ }
+ Replace = 0;
+ }
+ for (ImagePtr = Image + Min; ImagePtr <= Image + Max; ImagePtr++) {
+ if (*ImagePtr == 2)
+ *ImagePtr = Replace;
+ }
+ }
+ printf("%ld %ld %ld %ld %s", MaxPos, MaxCount, MaxMin, MaxMax, MaxJK ? "jmp killed" : "");
+ /* search for reference to MaxPos */
+ Offset = 0x12 + 11 * 2;
+ for (i = 0; i < size - Offset - 1; i++) {
+ if ((L9WORD(StartFile + i + Offset)) + i == MaxPos) {
+ printf("possible v3,4 Code reference at : %ld", i);
+ /* startdata=StartFile+i; */
+ }
+ }
+ Offset = 13 * 2;
+ for (i = 0; i < size - Offset - 1; i++) {
+ if ((L9WORD(StartFile + i + Offset)) + i == MaxPos)
+ printf("possible v2 Code reference at : %ld", i);
+ }
+ free(Image);
+}
+#endif
+
+L9BOOL findsubs(L9BYTE *testptr, L9UINT32 testsize, L9BYTE **picdata, L9UINT32 *picsize) {
+ int i, j, length, count;
+ L9BYTE *picptr, *startptr, *tmpptr;
+
+ if (testsize < 16) return FALSE;
+
+ /*
+ Try to traverse the graphics subroutines.
+
+ Each subroutine starts with a header: nn | nl | ll
+ nnn : the subroutine number ( 0x000 - 0x7ff )
+ lll : the subroutine length ( 0x004 - 0x3ff )
+
+ The first subroutine usually has the number 0x000.
+ Each subroutine ends with 0xff.
+
+ findsubs() searches for the header of the second subroutine
+ (pattern: 0xff | nn | nl | ll) and then tries to find the
+ first and next subroutines by evaluating the length fields
+ of the subroutine headers.
+ */
+ for (i = 4; i < (int)(testsize - 4); i++) {
+ picptr = testptr + i;
+ if (*(picptr - 1) != 0xff || (*picptr & 0x80) || (*(picptr + 1) & 0x0c) || (*(picptr + 2) < 4))
+ continue;
+
+ count = 0;
+ startptr = picptr;
+
+ while (TRUE) {
+ length = ((*(picptr + 1) & 0x0f) << 8) + *(picptr + 2);
+ if (length > 0x3ff || picptr + length + 4 > testptr + testsize)
+ break;
+
+ picptr += length;
+ if (*(picptr - 1) != 0xff) {
+ picptr -= length;
+ break;
+ }
+ if ((*picptr & 0x80) || (*(picptr + 1) & 0x0c) || (*(picptr + 2) < 4))
+ break;
+
+ count++;
+ }
+
+ if (count > 10) {
+ /* Search for the start of the first subroutine */
+ for (j = 4; j < 0x3ff; j++) {
+ tmpptr = startptr - j;
+ if (*tmpptr == 0xff || tmpptr < testptr)
+ break;
+
+ length = ((*(tmpptr + 1) & 0x0f) << 8) + *(tmpptr + 2);
+ if (tmpptr + length == startptr) {
+ startptr = tmpptr;
+ break;
+ }
+ }
+
+ if (*tmpptr != 0xff) {
+ *picdata = startptr;
+ *picsize = picptr - startptr;
+ return TRUE;
+ }
+ }
+ }
+ return FALSE;
+}
+
+L9BOOL intinitialise(const char *filename, char *picname) {
+ /* init */
+ /* driverclg */
+
+ int i;
+ int hdoffset;
+ long Offset;
+ Common::File f;
+
+ if (pictureaddress) {
+ free(pictureaddress);
+ pictureaddress = NULL;
+ }
+ picturedata = NULL;
+ picturesize = 0;
+ gfxa5 = NULL;
+
+ if (!load(filename)) {
+ error("\rUnable to load: %s\r", filename);
+ return FALSE;
+ }
+
+ /* try to load graphics */
+ if (picname) {
+ if (f.open(picname)) {
+ picturesize = f.size();
+ L9Allocate(&pictureaddress, picturesize);
+ if (f.read(pictureaddress, picturesize) != picturesize) {
+ free(pictureaddress);
+ pictureaddress = NULL;
+ picturesize = 0;
+ }
+ f.close();
+ }
+ }
+ screencalled = 0;
+ l9textmode = 0;
+
+#ifdef FULLSCAN
+ FullScan(startfile, FileSize);
+#endif
+
+ Offset = Scan(startfile, FileSize);
+ if (Offset < 0) {
+ Offset = ScanV2(startfile, FileSize);
+ L9GameType = L9_V2;
+ if (Offset < 0) {
+ Offset = ScanV1(startfile, FileSize);
+ L9GameType = L9_V1;
+ if (Offset < 0) {
+ error("\rUnable to locate valid Level 9 game in file: %s\r", filename);
+ return FALSE;
+ }
+ }
+ }
+
+ startdata = startfile + Offset;
+ FileSize -= Offset;
+
+ /* setup pointers */
+ if (L9GameType == L9_V1) {
+ if (L9V1Game < 0) {
+ error("\rWhat appears to be V1 game data was found, but the game was not recognised.\rEither this is an unknown V1 game file or, more likely, it is corrupted.\r");
+ return FALSE;
+ }
+ for (i = 0; i < 6; i++) {
+ int off = L9V1Games[L9V1Game].L9Ptrs[i];
+ if (off < 0)
+ L9Pointers[i + 2] = acodeptr + off;
+ else
+ L9Pointers[i + 2] = workspace.listarea + off;
+ }
+ absdatablock = acodeptr - L9V1Games[L9V1Game].absData;
+ } else {
+ /* V2,V3,V4 */
+ hdoffset = L9GameType == L9_V2 ? 4 : 0x12;
+ for (i = 0; i < 12; i++) {
+ L9UINT16 d0 = L9WORD(startdata + hdoffset + i * 2);
+ L9Pointers[i] = (i != 11 && d0 >= 0x8000 && d0 <= 0x9000) ? workspace.listarea + d0 - 0x8000 : startdata + d0;
+ }
+ absdatablock = L9Pointers[0];
+ dictdata = L9Pointers[1];
+ list2ptr = L9Pointers[3];
+ list3ptr = L9Pointers[4];
+ /*list9startptr */
+ list9startptr = L9Pointers[10];
+ acodeptr = L9Pointers[11];
+ }
+
+ switch (L9GameType) {
+ case L9_V1: {
+ double a1;
+ startmd = acodeptr + L9V1Games[L9V1Game].msgStart;
+ startmdV2 = startmd + L9V1Games[L9V1Game].msgLen;
+
+ if (analyseV1(&a1) && a1 > 2 && a1 < 10) {
+ L9MsgType = MSGT_V1;
+#ifdef L9DEBUG
+ printf("V1 msg table: wordlen=%.2lf", a1);
+#endif
+ } else {
+ error("\rUnable to identify V1 message table in file: %s\r", filename);
+ return FALSE;
+ }
+ break;
+ }
+ case L9_V2: {
+ double a2, a1;
+ startmd = startdata + L9WORD(startdata + 0x0);
+ startmdV2 = startdata + L9WORD(startdata + 0x2);
+
+ /* determine message type */
+ if (analyseV2(&a2) && a2 > 2 && a2 < 10) {
+ L9MsgType = MSGT_V2;
+#ifdef L9DEBUG
+ printf("V2 msg table: wordlen=%.2lf", a2);
+#endif
+ } else if (analyseV1(&a1) && a1 > 2 && a1 < 10) {
+ L9MsgType = MSGT_V1;
+#ifdef L9DEBUG
+ printf("V1 msg table: wordlen=%.2lf", a1);
+#endif
+ } else {
+ error("\rUnable to identify V2 message table in file: %s\r", filename);
+ return FALSE;
+ }
+ break;
+ }
+ case L9_V3:
+ case L9_V4:
+ startmd = startdata + L9WORD(startdata + 0x2);
+ endmd = startmd + L9WORD(startdata + 0x4);
+ defdict = startdata + L9WORD(startdata + 6);
+ endwdp5 = defdict + 5 + L9WORD(startdata + 0x8);
+ dictdata = startdata + L9WORD(startdata + 0x0a);
+ dictdatalen = L9WORD(startdata + 0x0c);
+ wordtable = startdata + L9WORD(startdata + 0xe);
+ break;
+ }
+
+#ifndef NO_SCAN_GRAPHICS
+ /* If there was no graphics file, look in the game data */
+ if (pictureaddress) {
+ if (!findsubs(pictureaddress, picturesize, &picturedata, &picturesize)) {
+ picturedata = NULL;
+ picturesize = 0;
+ }
+ } else {
+ if (!findsubs(startdata, FileSize, &picturedata, &picturesize)
+ && !findsubs(startfile, startdata - startfile, &picturedata, &picturesize)) {
+ picturedata = NULL;
+ picturesize = 0;
+ }
+ }
+#endif
+
+ memset(FirstLine, 0, FIRSTLINESIZE);
+ FirstLinePos = 0;
+
+ return TRUE;
+}
+
+L9BOOL checksumgamedata(void) {
+ return calcchecksum(startdata, L9WORD(startdata) + 1) == 0;
+}
+
+L9UINT16 movewa5d0(void) {
+ L9UINT16 ret = L9WORD(codeptr);
+ codeptr += 2;
+ return ret;
+}
+
+L9UINT16 getcon(void) {
+ if (code & 64) {
+ /* getconsmall */
+ return *codeptr++;
+ } else return movewa5d0();
+}
+
+L9BYTE *getaddr(void) {
+ if (code & 0x20) {
+ /* getaddrshort */
+ signed char diff = *codeptr++;
+ return codeptr + diff - 1;
+ } else {
+ return acodeptr + movewa5d0();
+ }
+}
+
+L9UINT16 *getvar(void) {
+#ifndef CODEFOLLOW
+ return workspace.vartable + *codeptr++;
+#else
+ cfvar2 = cfvar;
+ return cfvar = workspace.vartable + *codeptr++;
+#endif
+}
+
+void Goto(void) {
+ L9BYTE *target = getaddr();
+ if (target == codeptr - 2)
+ Running = FALSE; /* Endless loop! */
+ else
+ codeptr = target;
+}
+
+void intgosub(void) {
+ L9BYTE *newcodeptr = getaddr();
+ if (workspace.stackptr == STACKSIZE) {
+ error("\rStack overflow error\r");
+ Running = FALSE;
+ return;
+ }
+ workspace.stack[workspace.stackptr++] = (L9UINT16)(codeptr - acodeptr);
+ codeptr = newcodeptr;
+}
+
+void intreturn(void) {
+ if (workspace.stackptr == 0) {
+ error("\rStack underflow error\r");
+ Running = FALSE;
+ return;
+ }
+ codeptr = acodeptr + workspace.stack[--workspace.stackptr];
+}
+
+void printnumber(void) {
+ printdecimald0(*getvar());
+}
+
+void messagec(void) {
+ if (L9GameType <= L9_V2)
+ printmessageV2(getcon());
+ else
+ printmessage(getcon());
+}
+
+void messagev(void) {
+ if (L9GameType <= L9_V2)
+ printmessageV2(*getvar());
+ else
+ printmessage(*getvar());
+}
+
+void init(L9BYTE *a6) {
+#ifdef L9DEBUG
+ printf("driver - init");
+#endif
+}
+
+void randomnumber(L9BYTE *a6) {
+#ifdef L9DEBUG
+ printf("driver - randomnumber");
+#endif
+ L9SETWORD(a6, g_vm->getRandomNumber(0xffff));
+}
+
+void driverclg(L9BYTE *a6) {
+#ifdef L9DEBUG
+ printf("driver - driverclg");
+#endif
+}
+
+void _line(L9BYTE *a6) {
+#ifdef L9DEBUG
+ printf("driver - line");
+#endif
+}
+
+void fill(L9BYTE *a6) {
+#ifdef L9DEBUG
+ printf("driver - fill");
+#endif
+}
+
+void driverchgcol(L9BYTE *a6) {
+#ifdef L9DEBUG
+ printf("driver - driverchgcol");
+#endif
+}
+
+void drivercalcchecksum(L9BYTE *a6) {
+#ifdef L9DEBUG
+ printf("driver - calcchecksum");
+#endif
+}
+
+void driveroswrch(L9BYTE *a6) {
+#ifdef L9DEBUG
+ printf("driver - driveroswrch");
+#endif
+}
+
+void driverosrdch(L9BYTE *a6) {
+#ifdef L9DEBUG
+ printf("driver - driverosrdch");
+#endif
+
+ os_flush();
+ if (Cheating) {
+ *a6 = '\r';
+ } else {
+ /* max delay of 1/50 sec */
+ *a6 = os_readchar(20);
+ }
+}
+
+void driversavefile(L9BYTE *a6) {
+#ifdef L9DEBUG
+ printf("driver - driversavefile");
+#endif
+}
+
+void driverloadfile(L9BYTE *a6) {
+#ifdef L9DEBUG
+ printf("driver - driverloadfile");
+#endif
+}
+
+void settext(L9BYTE *a6) {
+#ifdef L9DEBUG
+ printf("driver - settext");
+#endif
+}
+
+void resettask(L9BYTE *a6) {
+#ifdef L9DEBUG
+ printf("driver - resettask");
+#endif
+}
+
+void driverinputline(L9BYTE *a6) {
+#ifdef L9DEBUG
+ printf("driver - driverinputline");
+#endif
+}
+
+void returntogem(L9BYTE *a6) {
+#ifdef L9DEBUG
+ printf("driver - returntogem");
+#endif
+}
+
+void lensdisplay(L9BYTE *a6) {
+#ifdef L9DEBUG
+ printf("driver - lensdisplay");
+#endif
+
+ printstring("\rLenslok code is ");
+ printchar(*a6);
+ printchar(*(a6 + 1));
+ printchar('\r');
+}
+
+void allocspace(L9BYTE *a6) {
+#ifdef L9DEBUG
+ printf("driver - allocspace");
+#endif
+}
+
+void driver14(L9BYTE *a6) {
+#ifdef L9DEBUG
+ printf("driver - call 14");
+#endif
+
+ *a6 = 0;
+}
+
+void showbitmap(L9BYTE *a6) {
+#ifdef L9DEBUG
+ printf("driver - showbitmap");
+#endif
+
+ os_show_bitmap(a6[1], a6[3], a6[5]);
+}
+
+void checkfordisc(L9BYTE *a6) {
+#ifdef L9DEBUG
+ printf("driver - checkfordisc");
+#endif
+
+ *a6 = 0;
+ list9startptr[2] = 0;
+}
+
+void driver(int d0, L9BYTE *a6) {
+ switch (d0) {
+ case 0:
+ init(a6);
+ break;
+ case 0x0c:
+ randomnumber(a6);
+ break;
+ case 0x10:
+ driverclg(a6);
+ break;
+ case 0x11:
+ _line(a6);
+ break;
+ case 0x12:
+ fill(a6);
+ break;
+ case 0x13:
+ driverchgcol(a6);
+ break;
+ case 0x01:
+ drivercalcchecksum(a6);
+ break;
+ case 0x02:
+ driveroswrch(a6);
+ break;
+ case 0x03:
+ driverosrdch(a6);
+ break;
+ case 0x05:
+ driversavefile(a6);
+ break;
+ case 0x06:
+ driverloadfile(a6);
+ break;
+ case 0x07:
+ settext(a6);
+ break;
+ case 0x08:
+ resettask(a6);
+ break;
+ case 0x04:
+ driverinputline(a6);
+ break;
+ case 0x09:
+ returntogem(a6);
+ break;
+ /*
+ case 0x16: ramsave(a6); break;
+ case 0x17: ramload(a6); break;
+ */
+ case 0x19:
+ lensdisplay(a6);
+ break;
+ case 0x1e:
+ allocspace(a6);
+ break;
+ /* v4 */
+ case 0x0e:
+ driver14(a6);
+ break;
+ case 0x20:
+ showbitmap(a6);
+ break;
+ case 0x22:
+ checkfordisc(a6);
+ break;
+ }
+}
+
+void ramsave(int i) {
+#ifdef L9DEBUG
+ printf("driver - ramsave %d", i);
+#endif
+
+ memmove(ramsavearea + i, workspace.vartable, sizeof(SaveStruct));
+}
+
+void ramload(int i) {
+#ifdef L9DEBUG
+ printf("driver - ramload %d", i);
+#endif
+
+ memmove(workspace.vartable, ramsavearea + i, sizeof(SaveStruct));
+}
+
+void calldriver(void) {
+ L9BYTE *a6 = list9startptr;
+ int d0 = *a6++;
+#ifdef CODEFOLLOW
+ fprintf(f, " %s", drivercalls[d0]);
+#endif
+
+ if (d0 == 0x16 || d0 == 0x17) {
+ int d1 = *a6;
+ if (d1 > 0xfa) *a6 = 1;
+ else if (d1 + 1 >= RAMSAVESLOTS) *a6 = 0xff;
+ else {
+ *a6 = 0;
+ if (d0 == 0x16) ramsave(d1 + 1);
+ else ramload(d1 + 1);
+ }
+ *list9startptr = *a6;
+ } else if (d0 == 0x0b) {
+ char NewName[MAX_PATH];
+ strcpy(NewName, LastGame);
+ if (*a6 == 0) {
+ printstring("\rSearching for next sub-game file.\r");
+ if (!os_get_game_file(NewName, MAX_PATH)) {
+ printstring("\rFailed to load game.\r");
+ return;
+ }
+ } else {
+ os_set_filenumber(NewName, MAX_PATH, *a6);
+ }
+ LoadGame2(NewName, NULL);
+ } else driver(d0, a6);
+}
+
+void L9Random(void) {
+#ifdef CODEFOLLOW
+ fprintf(f, " %d", randomseed);
+#endif
+ randomseed = (((randomseed << 8) + 0x0a - randomseed) << 2) + randomseed + 1;
+ *getvar() = randomseed & 0xff;
+#ifdef CODEFOLLOW
+ fprintf(f, " %d", randomseed);
+#endif
+}
+
+void save(void) {
+ L9UINT16 checksum;
+ int i;
+#ifdef L9DEBUG
+ printf("function - save");
+#endif
+ /* does a full save, workpace, stack, codeptr, stackptr, game name, checksum */
+
+ workspace.Id = L9_ID;
+ workspace.codeptr = codeptr - acodeptr;
+ workspace.listsize = LISTAREASIZE;
+ workspace.stacksize = STACKSIZE;
+ workspace.filenamesize = MAX_PATH;
+ workspace.checksum = 0;
+ strcpy(workspace.filename, LastGame);
+
+ checksum = 0;
+ for (i = 0; i < (int)sizeof(GameState); i++) checksum += ((L9BYTE *) &workspace)[i];
+ workspace.checksum = checksum;
+
+ if (os_save_file((L9BYTE *) &workspace, sizeof(workspace))) printstring("\rGame saved.\r");
+ else printstring("\rUnable to save game.\r");
+}
+
+L9BOOL CheckFile(GameState *gs) {
+ L9UINT16 checksum;
+ int i;
+ char c = 'Y';
+
+ if (gs->Id != L9_ID) return FALSE;
+ checksum = gs->checksum;
+ gs->checksum = 0;
+ for (i = 0; i < (int)sizeof(GameState); i++)
+ checksum -= *((L9BYTE *) gs + i);
+ if (checksum) return FALSE;
+ if (scumm_stricmp(gs->filename, LastGame)) {
+ printstring("\rWarning: game path name does not match, you may be about to load this position file into the wrong story file.\r");
+ printstring("Are you sure you want to restore? (Y/N)");
+ os_flush();
+
+ c = '\0';
+ while ((c != 'y') && (c != 'Y') && (c != 'n') && (c != 'N'))
+ c = os_readchar(20);
+ }
+ if ((c == 'y') || (c == 'Y'))
+ return TRUE;
+ return FALSE;
+}
+
+void NormalRestore(void) {
+ GameState temp;
+ int Bytes;
+#ifdef L9DEBUG
+ printf("function - restore");
+#endif
+ if (Cheating) {
+ /* not really an error */
+ Cheating = FALSE;
+ error("\rWord is: %s\r", ibuff);
+ }
+
+ if (os_load_file((L9BYTE *) &temp, &Bytes, sizeof(GameState))) {
+ if (Bytes == V1FILESIZE) {
+ printstring("\rGame restored.\r");
+ memset(workspace.listarea, 0, LISTAREASIZE);
+ memmove(workspace.vartable, &temp, V1FILESIZE);
+ } else if (CheckFile(&temp)) {
+ printstring("\rGame restored.\r");
+ /* only copy in workspace */
+ memmove(workspace.vartable, temp.vartable, sizeof(SaveStruct));
+ } else {
+ printstring("\rSorry, unrecognised format. Unable to restore\r");
+ }
+ } else printstring("\rUnable to restore game.\r");
+}
+
+void restore(void) {
+ int Bytes;
+ GameState temp;
+ if (os_load_file((L9BYTE *) &temp, &Bytes, sizeof(GameState))) {
+ if (Bytes == V1FILESIZE) {
+ printstring("\rGame restored.\r");
+ /* only copy in workspace */
+ memset(workspace.listarea, 0, LISTAREASIZE);
+ memmove(workspace.vartable, &temp, V1FILESIZE);
+ } else if (CheckFile(&temp)) {
+ printstring("\rGame restored.\r");
+ /* full restore */
+ memmove(&workspace, &temp, sizeof(GameState));
+ codeptr = acodeptr + workspace.codeptr;
+ } else {
+ printstring("\rSorry, unrecognised format. Unable to restore\r");
+ }
+ } else printstring("\rUnable to restore game.\r");
+}
+
+void playback(void) {
+ if (scriptfile)
+ delete scriptfile;
+ scriptfile = os_open_script_file();
+
+ if (scriptfile)
+ printstring("\rPlaying back input from script file.\r");
+ else
+ printstring("\rUnable to play back script file.\r");
+}
+
+void l9_fgets(char *s, int n, Common::SeekableReadStream *f) {
+ int c = '\0';
+ int count = 0;
+
+ while ((c != '\n') && (c != '\r') && (c != EOF) && (count < n - 1)) {
+ c = f->readByte();
+ *s++ = c;
+ count++;
+ }
+ *s = '\0';
+
+ if (c == EOF) {
+ s--;
+ *s = '\n';
+ } else if (c == '\r') {
+ s--;
+ *s = '\n';
+
+ c = f->readByte();
+ if ((c != '\r') && (c != EOF))
+ f->seek(-1, SEEK_CUR);
+ }
+}
+
+L9BOOL scriptinput(char *buffer, int size) {
+ while (scriptfile != NULL) {
+ if (scriptfile->eos()) {
+ delete scriptfile;
+ scriptfile = NULL;
+ } else {
+ char *p = buffer;
+ *p = '\0';
+ l9_fgets(buffer, size, scriptfile);
+ while (*p != '\0') {
+ switch (*p) {
+ case '\n':
+ case '\r':
+ case '[':
+ case ';':
+ *p = '\0';
+ break;
+ case '#':
+ if ((p == buffer) && (scumm_strnicmp(p, "#seed ", 6) == 0))
+ p++;
+ else
+ *p = '\0';
+ break;
+ default:
+ p++;
+ break;
+ }
+ }
+ if (*buffer != '\0') {
+ printstring(buffer);
+ lastchar = lastactualchar = '.';
+ return TRUE;
+ }
+ }
+ }
+ return FALSE;
+}
+
+void clearworkspace(void) {
+ memset(workspace.vartable, 0, sizeof(workspace.vartable));
+}
+
+void ilins(int d0) {
+ error("\rIllegal instruction: %d\r", d0);
+ Running = FALSE;
+}
+
+void function(void) {
+ int d0 = *codeptr++;
+#ifdef CODEFOLLOW
+ fprintf(f, " %s", d0 == 250 ? "printstr" : functions[d0 - 1]);
+#endif
+
+ switch (d0) {
+ case 1:
+ if (L9GameType == L9_V1)
+ StopGame();
+ else
+ calldriver();
+ break;
+ case 2:
+ L9Random();
+ break;
+ case 3:
+ save();
+ break;
+ case 4:
+ NormalRestore();
+ break;
+ case 5:
+ clearworkspace();
+ break;
+ case 6:
+ workspace.stackptr = 0;
+ break;
+ case 250:
+ printstring((char *) codeptr);
+ while (*codeptr++);
+ break;
+
+ default:
+ ilins(d0);
+ }
+}
+
+void findmsgequiv(int d7) {
+ int d4 = -1, d0;
+ L9BYTE *a2 = startmd;
+
+ do {
+ d4++;
+ if (a2 > endmd) return;
+ d0 = *a2;
+ if (d0 & 0x80) {
+ a2++;
+ d4 += d0 & 0x7f;
+ } else if (d0 & 0x40) {
+ int d6 = getmdlength(&a2);
+ do {
+ int d1;
+ if (d6 == 0) break;
+
+ d1 = *a2++;
+ d6--;
+ if (d1 & 0x80) {
+ if (d1 < 0x90) {
+ a2++;
+ d6--;
+ } else {
+ d0 = (d1 << 8) + *a2++;
+ d6--;
+ if (d7 == (d0 & 0xfff)) {
+ d0 = ((d0 << 1) & 0xe000) | d4;
+ list9ptr[1] = d0;
+ list9ptr[0] = d0 >> 8;
+ list9ptr += 2;
+ if (list9ptr >= list9startptr + 0x20) return;
+ }
+ }
+ }
+ } while (TRUE);
+ } else {
+ int len = getmdlength(&a2);
+ a2 += len;
+ }
+ } while (TRUE);
+}
+
+L9BOOL unpackword(void) {
+ L9BYTE *a3;
+
+ if (unpackd3 == 0x1b) return TRUE;
+
+ a3 = (L9BYTE *) threechars + (unpackd3 & 3);
+
+ /*uw01 */
+ while (TRUE) {
+ L9BYTE d0 = getdictionarycode();
+ if (dictptr >= endwdp5) return TRUE;
+ if (d0 >= 0x1b) {
+ *a3 = 0;
+ unpackd3 = d0;
+ return FALSE;
+ }
+ *a3++ = getdictionary(d0);
+ }
+}
+
+L9BOOL initunpack(L9BYTE *ptr) {
+ initdict(ptr);
+ unpackd3 = 0x1c;
+ return unpackword();
+}
+
+int partword(char c) {
+ c = tolower(c);
+
+ if (c == 0x27 || c == 0x2d) return 0;
+ if (c < 0x30) return 1;
+ if (c < 0x3a) return 0;
+ if (c < 0x61) return 1;
+ if (c < 0x7b) return 0;
+ return 1;
+}
+
+L9UINT32 readdecimal(char *buff) {
+ return atol(buff);
+}
+
+void checknumber(void) {
+ if (*obuff >= 0x30 && *obuff < 0x3a) {
+ if (L9GameType == L9_V4) {
+ *list9ptr = 1;
+ L9SETWORD(list9ptr + 1, readdecimal(obuff));
+ L9SETWORD(list9ptr + 3, 0);
+ } else {
+ L9SETDWORD(list9ptr, readdecimal(obuff));
+ L9SETWORD(list9ptr + 4, 0);
+ }
+ } else {
+ L9SETWORD(list9ptr, 0x8000);
+ L9SETWORD(list9ptr + 2, 0);
+ }
+}
+
+void NextCheat(void) {
+ /* restore game status */
+ memmove(&workspace, &CheatWorkspace, sizeof(GameState));
+ codeptr = acodeptr + workspace.codeptr;
+
+ if (!((L9GameType <= L9_V2) ? GetWordV2(ibuff, CheatWord++) : GetWordV3(ibuff, CheatWord++))) {
+ Cheating = FALSE;
+ printstring("\rCheat failed.\r");
+ *ibuff = 0;
+ }
+}
+
+void StartCheat(void) {
+ Cheating = TRUE;
+ CheatWord = 0;
+
+ /* save current game status */
+ memmove(&CheatWorkspace, &workspace, sizeof(GameState));
+ CheatWorkspace.codeptr = codeptr - acodeptr;
+
+ NextCheat();
+}
+
+/* v3,4 input routine */
+
+L9BOOL GetWordV3(char *buff, int Word) {
+ int i;
+ int subdict = 0;
+ /* 26*4-1=103 */
+
+ initunpack(startdata + L9WORD(dictdata));
+ unpackword();
+
+ while (Word--) {
+ if (unpackword()) {
+ if (++subdict == dictdatalen) return FALSE;
+ initunpack(startdata + L9WORD(dictdata + (subdict << 2)));
+ Word++; /* force unpack again */
+ }
+ }
+ strcpy(buff, threechars);
+ for (i = 0; i < (int)strlen(buff); i++) buff[i] &= 0x7f;
+ return TRUE;
+}
+
+L9BOOL CheckHash(void) {
+ if (scumm_stricmp(ibuff, "#cheat") == 0) StartCheat();
+ else if (scumm_stricmp(ibuff, "#save") == 0) {
+ save();
+ return TRUE;
+ } else if (scumm_stricmp(ibuff, "#restore") == 0) {
+ restore();
+ return TRUE;
+ } else if (scumm_stricmp(ibuff, "#quit") == 0) {
+ StopGame();
+ printstring("\rGame Terminated\r");
+ return TRUE;
+ } else if (scumm_stricmp(ibuff, "#dictionary") == 0) {
+ CheatWord = 0;
+ printstring("\r");
+ while ((L9GameType <= L9_V2) ? GetWordV2(ibuff, CheatWord++) : GetWordV3(ibuff, CheatWord++)) {
+ error("%s ", ibuff);
+ if (os_stoplist() || !Running) break;
+ }
+ printstring("\r");
+ return TRUE;
+ } else if (scumm_strnicmp(ibuff, "#picture ", 9) == 0) {
+ int pic = 0;
+ if (sscanf(ibuff + 9, "%d", &pic) == 1) {
+ if (L9GameType == L9_V4)
+ os_show_bitmap(pic, 0, 0);
+ else
+ show_picture(pic);
+ }
+
+ lastactualchar = 0;
+ printchar('\r');
+ return TRUE;
+ } else if (scumm_strnicmp(ibuff, "#seed ", 6) == 0) {
+ int seed = 0;
+ if (sscanf(ibuff + 6, "%d", &seed) == 1)
+ randomseed = constseed = seed;
+ lastactualchar = 0;
+ printchar('\r');
+ return TRUE;
+ } else if (scumm_stricmp(ibuff, "#play") == 0) {
+ playback();
+ return TRUE;
+ }
+ return FALSE;
+}
+
+L9BOOL IsInputChar(char c) {
+ if (c == '-' || c == '\'')
+ return TRUE;
+ if ((L9GameType >= L9_V3) && (c == '.' || c == ','))
+ return TRUE;
+ return Common::isAlnum(c);
+}
+
+L9BOOL corruptinginput(void) {
+ L9BYTE *a0, *a2, *a6;
+ int d0, d1, d2, keywordnumber, abrevword;
+ char *iptr;
+
+ list9ptr = list9startptr;
+
+ if (ibuffptr == NULL) {
+ if (Cheating) NextCheat();
+ else {
+ /* flush */
+ os_flush();
+ lastchar = lastactualchar = '.';
+ /* get input */
+ if (!scriptinput(ibuff, IBUFFSIZE)) {
+ if (!os_input(ibuff, IBUFFSIZE))
+ return FALSE; /* fall through */
+ }
+ if (CheckHash())
+ return FALSE;
+
+ /* check for invalid chars */
+ for (iptr = ibuff; *iptr != 0; iptr++) {
+ if (!IsInputChar(*iptr))
+ *iptr = ' ';
+ }
+
+ /* force CR but prevent others */
+ os_printchar(lastactualchar = '\r');
+ }
+ ibuffptr = (L9BYTE *) ibuff;
+ }
+
+ a2 = (L9BYTE *) obuff;
+ a6 = ibuffptr;
+
+ /*ip05 */
+ while (TRUE) {
+ d0 = *a6++;
+ if (d0 == 0) {
+ ibuffptr = NULL;
+ L9SETWORD(list9ptr, 0);
+ return TRUE;
+ }
+ if (partword((char)d0) == 0) break;
+ if (d0 != 0x20) {
+ ibuffptr = a6;
+ L9SETWORD(list9ptr, 0);
+ L9SETWORD(list9ptr + 2, 0);
+ list9ptr[1] = d0;
+ *a2 = 0x20;
+ keywordnumber = -1;
+ return TRUE;
+ }
+ }
+
+ a6--;
+ /*ip06loop */
+ do {
+ d0 = *a6++;
+ if (partword((char)d0) == 1) break;
+ d0 = tolower(d0);
+ *a2++ = d0;
+ } while (a2 < (L9BYTE *) obuff + 0x1f);
+ /*ip06a */
+ *a2 = 0x20;
+ a6--;
+ ibuffptr = a6;
+ abrevword = -1;
+ keywordnumber = -1;
+ list9ptr = list9startptr;
+ /* setindex */
+ a0 = dictdata;
+ d2 = dictdatalen;
+ d0 = *obuff - 0x61;
+ if (d0 < 0) {
+ a6 = defdict;
+ d1 = 0;
+ } else {
+ /*ip10 */
+ d1 = 0x67;
+ if (d0 < 0x1a) {
+ d1 = d0 << 2;
+ d0 = obuff[1];
+ if (d0 != 0x20) d1 += ((d0 - 0x61) >> 3) & 3;
+ }
+ /*ip13 */
+ if (d1 >= d2) {
+ checknumber();
+ return TRUE;
+ }
+ a0 += d1 << 2;
+ a6 = startdata + L9WORD(a0);
+ d1 = L9WORD(a0 + 2);
+ }
+ /*ip13gotwordnumber */
+
+ initunpack(a6);
+ /*ip14 */
+ d1--;
+ do {
+ d1++;
+ if (unpackword()) {
+ /* ip21b */
+ if (abrevword == -1) break; /* goto ip22 */
+ else d0 = abrevword; /* goto ip18b */
+ } else {
+ L9BYTE *a1 = (L9BYTE *) threechars;
+ int d6 = -1;
+
+ a0 = (L9BYTE *) obuff;
+ /*ip15 */
+ do {
+ d6++;
+ d0 = tolower(*a1++ & 0x7f);
+ d2 = *a0++;
+ } while (d0 == d2);
+
+ if (d2 != 0x20) {
+ /* ip17 */
+ if (abrevword == -1) continue;
+ else d0 = -1;
+ } else if (d0 == 0) d0 = d1;
+ else if (abrevword != -1) break;
+ else if (d6 >= 4) d0 = d1;
+ else {
+ abrevword = d1;
+ continue;
+ }
+ }
+ /*ip18b */
+ findmsgequiv(d1);
+
+ abrevword = -1;
+ if (list9ptr != list9startptr) {
+ L9SETWORD(list9ptr, 0);
+ return TRUE;
+ }
+ } while (TRUE);
+ /* ip22 */
+ checknumber();
+ return TRUE;
+}
+
+/* version 2 stuff hacked from bbc v2 files */
+
+L9BOOL IsDictionaryChar(char c) {
+ switch (c) {
+ case '?':
+ case '-':
+ case '\'':
+ case '/':
+ return TRUE;
+ case '!':
+ case '.':
+ case ',':
+ return TRUE;
+ }
+ return Common::isUpper(c) || Common::isDigit(c);
+}
+
+L9BOOL GetWordV2(char *buff, int Word) {
+ L9BYTE *ptr = dictdata, x;
+
+ while (Word--) {
+ do {
+ x = *ptr++;
+ } while (x > 0 && x < 0x7f);
+ if (x == 0) return FALSE; /* no more words */
+ ptr++;
+ }
+ do {
+ x = *ptr++;
+ if (!IsDictionaryChar(x & 0x7f)) return FALSE;
+ *buff++ = x & 0x7f;
+ } while (x > 0 && x < 0x7f);
+ *buff = 0;
+ return TRUE;
+}
+
+L9BOOL inputV2(int *wordcount) {
+ L9BYTE a, x;
+ L9BYTE *ibuffp, *obuffptr, *ptr, *list0ptr;
+ char *iptr;
+
+ if (Cheating) NextCheat();
+ else {
+ os_flush();
+ lastchar = lastactualchar = '.';
+ /* get input */
+ if (!scriptinput(ibuff, IBUFFSIZE)) {
+ if (!os_input(ibuff, IBUFFSIZE))
+ return FALSE; /* fall through */
+ }
+ if (CheckHash())
+ return FALSE;
+
+ /* check for invalid chars */
+ for (iptr = ibuff; *iptr != 0; iptr++) {
+ if (!IsInputChar(*iptr))
+ *iptr = ' ';
+ }
+
+ /* force CR but prevent others */
+ os_printchar(lastactualchar = '\r');
+ }
+ /* add space onto end */
+ ibuffp = (L9BYTE *) strchr(ibuff, 0);
+ *ibuffp++ = 32;
+ *ibuffp = 0;
+
+ *wordcount = 0;
+ ibuffp = (L9BYTE *) ibuff;
+ obuffptr = (L9BYTE *) obuff;
+ /* ibuffp=76,77 */
+ /* obuffptr=84,85 */
+ /* list0ptr=7c,7d */
+ list0ptr = dictdata;
+
+ while (*ibuffp == 32) ++ibuffp;
+
+ ptr = ibuffp;
+ do {
+ while (*ptr == 32) ++ptr;
+ if (*ptr == 0) break;
+ (*wordcount)++;
+ do {
+ a = *++ptr;
+ } while (a != 32 && a != 0);
+ } while (*ptr > 0);
+
+ while (TRUE) {
+ ptr = ibuffp; /* 7a,7b */
+ while (*ibuffp == 32) ++ibuffp;
+
+ while (TRUE) {
+ a = *ibuffp;
+ x = *list0ptr++;
+
+ if (a == 32) break;
+ if (a == 0) {
+ *obuffptr++ = 0;
+ return TRUE;
+ }
+
+ ++ibuffp;
+ if (!IsDictionaryChar(x & 0x7f)) x = 0;
+ if (tolower(x & 0x7f) != tolower(a)) {
+ while (x > 0 && x < 0x7f) x = *list0ptr++;
+ if (x == 0) {
+ do {
+ a = *ibuffp++;
+ if (a == 0) {
+ *obuffptr = 0;
+ return TRUE;
+ }
+ } while (a != 32);
+ while (*ibuffp == 32) ++ibuffp;
+ list0ptr = dictdata;
+ ptr = ibuffp;
+ } else {
+ list0ptr++;
+ ibuffp = ptr;
+ }
+ } else if (x >= 0x7f) break;
+ }
+
+ a = *ibuffp;
+ if (a != 32) {
+ ibuffp = ptr;
+ list0ptr += 2;
+ continue;
+ }
+ --list0ptr;
+ while (*list0ptr++ < 0x7e);
+ *obuffptr++ = *list0ptr;
+ while (*ibuffp == 32) ++ibuffp;
+ list0ptr = dictdata;
+ }
+}
+
+void input(void) {
+ if (L9GameType == L9_V3 && FirstPicture >= 0) {
+ show_picture(FirstPicture);
+ FirstPicture = -1;
+ }
+
+ /* if corruptinginput() returns false then, input will be called again
+ next time around instructionloop, this is used when save() and restore()
+ are called out of line */
+
+ codeptr--;
+ if (L9GameType <= L9_V2) {
+ int wordcount;
+ if (inputV2(&wordcount)) {
+ L9BYTE *obuffptr = (L9BYTE *) obuff;
+ codeptr++;
+ *getvar() = *obuffptr++;
+ *getvar() = *obuffptr++;
+ *getvar() = *obuffptr;
+ *getvar() = wordcount;
+ }
+ } else if (corruptinginput()) codeptr += 5;
+}
+
+void varcon(void) {
+ L9UINT16 d6 = getcon();
+ *getvar() = d6;
+
+#ifdef CODEFOLLOW
+ fprintf(f, " Var[%d]=%d)", cfvar - workspace.vartable, *cfvar);
+#endif
+}
+
+void varvar(void) {
+ L9UINT16 d6 = *getvar();
+ *getvar() = d6;
+
+#ifdef CODEFOLLOW
+ fprintf(f, " Var[%d]=Var[%d] (=%d)", cfvar - workspace.vartable, cfvar2 - workspace.vartable, d6);
+#endif
+}
+
+void _add(void) {
+ L9UINT16 d0 = *getvar();
+ *getvar() += d0;
+
+#ifdef CODEFOLLOW
+ fprintf(f, " Var[%d]+=Var[%d] (+=%d)", cfvar - workspace.vartable, cfvar2 - workspace.vartable, d0);
+#endif
+}
+
+void _sub(void) {
+ L9UINT16 d0 = *getvar();
+ *getvar() -= d0;
+
+#ifdef CODEFOLLOW
+ fprintf(f, " Var[%d]-=Var[%d] (-=%d)", cfvar - workspace.vartable, cfvar2 - workspace.vartable, d0);
+#endif
+}
+
+void jump(void) {
+ L9UINT16 d0 = L9WORD(codeptr);
+ L9BYTE *a0;
+ codeptr += 2;
+
+ a0 = acodeptr + ((d0 + ((*getvar()) << 1)) & 0xffff);
+ codeptr = acodeptr + L9WORD(a0);
+}
+
+/* bug */
+void exit1(L9BYTE *d4, L9BYTE *d5p, L9BYTE d6, L9BYTE d7) {
+ L9BYTE *a0 = absdatablock;
+ L9BYTE d1 = d7, d0;
+ if (--d1) {
+ do {
+ d0 = *a0;
+ if (L9GameType == L9_V4) {
+ if ((d0 == 0) && (*(a0 + 1) == 0))
+ goto notfn4;
+ }
+ a0 += 2;
+ } while ((d0 & 0x80) == 0 || --d1);
+ }
+
+ do {
+ *d4 = *a0++;
+ if (((*d4) & 0xf) == d6) {
+ *d5p = *a0;
+ return;
+ }
+ a0++;
+ } while (((*d4) & 0x80) == 0);
+
+ /* notfn4 */
+notfn4:
+ d6 = exitreversaltable[d6];
+ a0 = absdatablock;
+ *d5p = 1;
+
+ do {
+ *d4 = *a0++;
+ if (((*d4) & 0x10) == 0 || ((*d4) & 0xf) != d6) a0++;
+ else if (*a0++ == d7) return;
+ /* exit6noinc */
+ if ((*d4) & 0x80)(*d5p)++;
+ } while (*d4);
+ *d5p = 0;
+}
+
+void Exit(void) {
+ L9BYTE d4, d5v;
+ L9BYTE d7 = (L9BYTE) * getvar();
+ L9BYTE d6 = (L9BYTE) * getvar();
+#ifdef CODEFOLLOW
+ fprintf(f, " d7=%d d6=%d", d7, d6);
+#endif
+ exit1(&d4, &d5v, d6, d7);
+
+ *getvar() = (d4 & 0x70) >> 4;
+ *getvar() = d5v;
+#ifdef CODEFOLLOW
+ fprintf(f, " Var[%d]=%d(d4=%d) Var[%d]=%d",
+ cfvar2 - workspace.vartable, (d4 & 0x70) >> 4, d4, cfvar - workspace.vartable, d5v);
+#endif
+}
+
+void ifeqvt(void) {
+ L9UINT16 d0 = *getvar();
+ L9UINT16 d1 = *getvar();
+ L9BYTE *a0 = getaddr();
+ if (d0 == d1) codeptr = a0;
+
+#ifdef CODEFOLLOW
+ fprintf(f, " if Var[%d]=Var[%d] goto %d (%s)", cfvar2 - workspace.vartable, cfvar - workspace.vartable, (L9UINT32)(a0 - acodeptr), d0 == d1 ? "Yes" : "No");
+#endif
+}
+
+void ifnevt(void) {
+ L9UINT16 d0 = *getvar();
+ L9UINT16 d1 = *getvar();
+ L9BYTE *a0 = getaddr();
+ if (d0 != d1) codeptr = a0;
+
+#ifdef CODEFOLLOW
+ fprintf(f, " if Var[%d]!=Var[%d] goto %d (%s)", cfvar2 - workspace.vartable, cfvar - workspace.vartable, (L9UINT32)(a0 - acodeptr), d0 != d1 ? "Yes" : "No");
+#endif
+}
+
+void ifltvt(void) {
+ L9UINT16 d0 = *getvar();
+ L9UINT16 d1 = *getvar();
+ L9BYTE *a0 = getaddr();
+ if (d0 < d1) codeptr = a0;
+
+#ifdef CODEFOLLOW
+ fprintf(f, " if Var[%d]<Var[%d] goto %d (%s)", cfvar2 - workspace.vartable, cfvar - workspace.vartable, (L9UINT32)(a0 - acodeptr), d0 < d1 ? "Yes" : "No");
+#endif
+}
+
+void ifgtvt(void) {
+ L9UINT16 d0 = *getvar();
+ L9UINT16 d1 = *getvar();
+ L9BYTE *a0 = getaddr();
+ if (d0 > d1) codeptr = a0;
+
+#ifdef CODEFOLLOW
+ fprintf(f, " if Var[%d]>Var[%d] goto %d (%s)", cfvar2 - workspace.vartable, cfvar - workspace.vartable, (L9UINT32)(a0 - acodeptr), d0 > d1 ? "Yes" : "No");
+#endif
+}
+
+int scalex(int x) {
+ return (gfx_mode != GFX_V3C) ? (x >> 6) : (x >> 5);
+}
+
+int scaley(int y) {
+ return (gfx_mode == GFX_V2) ? 127 - (y >> 7) : 95 - (((y >> 5) + (y >> 6)) >> 3);
+}
+
+void detect_gfx_mode(void) {
+ if (L9GameType == L9_V3) {
+ /* These V3 games use graphics logic similar to the V2 games */
+ if (strstr(FirstLine, "price of magik") != 0)
+ gfx_mode = GFX_V3A;
+ else if (strstr(FirstLine, "the archers") != 0)
+ gfx_mode = GFX_V3A;
+ else if (strstr(FirstLine, "secret diary of adrian mole") != 0)
+ gfx_mode = GFX_V3A;
+ else if ((strstr(FirstLine, "worm in paradise") != 0)
+ && (strstr(FirstLine, "silicon dreams") == 0))
+ gfx_mode = GFX_V3A;
+ else if (strstr(FirstLine, "growing pains of adrian mole") != 0)
+ gfx_mode = GFX_V3B;
+ else if (strstr(FirstLine, "jewels of darkness") != 0 && picturesize < 11000)
+ gfx_mode = GFX_V3B;
+ else if (strstr(FirstLine, "silicon dreams") != 0) {
+ if (picturesize > 11000
+ || (startdata[0] == 0x14 && startdata[1] == 0x7d) /* Return to Eden /SD (PC) */
+ || (startdata[0] == 0xd7 && startdata[1] == 0x7c)) /* Worm in Paradise /SD (PC) */
+ gfx_mode = GFX_V3C;
+ else
+ gfx_mode = GFX_V3B;
+ } else
+ gfx_mode = GFX_V3C;
+ } else
+ gfx_mode = GFX_V2;
+}
+
+void _screen(void) {
+ int mode = 0;
+
+ if (L9GameType == L9_V3 && strlen(FirstLine) == 0) {
+ if (*codeptr++)
+ codeptr++;
+ return;
+ }
+
+ detect_gfx_mode();
+ l9textmode = *codeptr++;
+ if (l9textmode) {
+ if (L9GameType == L9_V4)
+ mode = 2;
+ else if (picturedata)
+ mode = 1;
+ }
+ os_graphics(mode);
+
+ screencalled = 1;
+
+#ifdef L9DEBUG
+ printf("screen %s", l9textmode ? "graphics" : "text");
+#endif
+
+ if (l9textmode) {
+ codeptr++;
+ /* clearg */
+ /* gintclearg */
+ os_cleargraphics();
+
+ /* title pic */
+ if (showtitle == 1 && mode == 2) {
+ showtitle = 0;
+ os_show_bitmap(0, 0, 0);
+ }
+ }
+ /* screent */
+}
+
+void cleartg(void) {
+ int d0 = *codeptr++;
+#ifdef L9DEBUG
+ printf("cleartg %s", d0 ? "graphics" : "text");
+#endif
+
+ if (d0) {
+ /* clearg */
+ if (l9textmode)
+ /* gintclearg */
+ os_cleargraphics();
+ }
+ /* cleart */
+ /* oswrch(0x0c) */
+}
+
+L9BOOL validgfxptr(L9BYTE *a5) {
+ return ((a5 >= picturedata) && (a5 < picturedata + picturesize));
+}
+
+L9BOOL findsub(int d0, L9BYTE **a5) {
+ int d1, d2, d3, d4;
+
+ d1 = d0 << 4;
+ d2 = d1 >> 8;
+ *a5 = picturedata;
+ /* findsubloop */
+ while (TRUE) {
+ d3 = *(*a5)++;
+ if (!validgfxptr(*a5))
+ return FALSE;
+ if (d3 & 0x80)
+ return FALSE;
+ if (d2 == d3) {
+ if ((d1 & 0xff) == (*(*a5) & 0xf0)) {
+ (*a5) += 2;
+ return TRUE;
+ }
+ }
+
+ d3 = *(*a5)++ & 0x0f;
+ if (!validgfxptr(*a5))
+ return FALSE;
+
+ d4 = **a5;
+ if ((d3 | d4) == 0)
+ return FALSE;
+
+ (*a5) += (d3 << 8) + d4 - 2;
+ if (!validgfxptr(*a5))
+ return FALSE;
+ }
+}
+
+void gosubd0(int d0, L9BYTE **a5) {
+ if (GfxA5StackPos < GFXSTACKSIZE) {
+ GfxA5Stack[GfxA5StackPos] = *a5;
+ GfxA5StackPos++;
+ GfxScaleStack[GfxScaleStackPos] = scale;
+ GfxScaleStackPos++;
+
+ if (findsub(d0, a5) == FALSE) {
+ GfxA5StackPos--;
+ *a5 = GfxA5Stack[GfxA5StackPos];
+ GfxScaleStackPos--;
+ scale = GfxScaleStack[GfxScaleStackPos];
+ }
+ }
+}
+
+void newxy(int x, int y) {
+ drawx += (x * scale) & ~7;
+ drawy += (y * scale) & ~7;
+}
+
+/* sdraw instruction plus arguments are stored in an 8 bit word.
+ 76543210
+ iixxxyyy
+ where i is instruction code
+ x is x argument, high bit is sign
+ y is y argument, high bit is sign
+*/
+void sdraw(int d7) {
+ int x, y, x1, y1;
+
+ /* getxy1 */
+ x = (d7 & 0x18) >> 3;
+ if (d7 & 0x20)
+ x = (x | 0xfc) - 0x100;
+ y = (d7 & 0x3) << 2;
+ if (d7 & 0x4)
+ y = (y | 0xf0) - 0x100;
+
+ if (reflectflag & 2)
+ x = -x;
+ if (reflectflag & 1)
+ y = -y;
+
+ /* gintline */
+ x1 = drawx;
+ y1 = drawy;
+ newxy(x, y);
+
+#ifdef L9DEBUG
+ printf("gfx - sdraw (%d,%d) (%d,%d) colours %d,%d",
+ x1, y1, drawx, drawy, gintcolour & 3, option & 3);
+#endif
+
+ os_drawline(scalex(x1), scaley(y1), scalex(drawx), scaley(drawy),
+ gintcolour & 3, option & 3);
+}
+
+/* smove instruction plus arguments are stored in an 8 bit word.
+ 76543210
+ iixxxyyy
+ where i is instruction code
+ x is x argument, high bit is sign
+ y is y argument, high bit is sign
+*/
+void smove(int d7) {
+ int x, y;
+
+ /* getxy1 */
+ x = (d7 & 0x18) >> 3;
+ if (d7 & 0x20)
+ x = (x | 0xfc) - 0x100;
+ y = (d7 & 0x3) << 2;
+ if (d7 & 0x4)
+ y = (y | 0xf0) - 0x100;
+
+ if (reflectflag & 2)
+ x = -x;
+ if (reflectflag & 1)
+ y = -y;
+ newxy(x, y);
+}
+
+void sgosub(int d7, L9BYTE **a5) {
+ int d0 = d7 & 0x3f;
+#ifdef L9DEBUG
+ printf("gfx - sgosub 0x%.2x", d0);
+#endif
+ gosubd0(d0, a5);
+}
+
+/* draw instruction plus arguments are stored in a 16 bit word.
+ FEDCBA9876543210
+ iiiiixxxxxxyyyyy
+ where i is instruction code
+ x is x argument, high bit is sign
+ y is y argument, high bit is sign
+*/
+void draw(int d7, L9BYTE **a5) {
+ int xy, x, y, x1, y1;
+
+ /* getxy2 */
+ xy = (d7 << 8) + (*(*a5)++);
+ x = (xy & 0x3e0) >> 5;
+ if (xy & 0x400)
+ x = (x | 0xe0) - 0x100;
+ y = (xy & 0xf) << 2;
+ if (xy & 0x10)
+ y = (y | 0xc0) - 0x100;
+
+ if (reflectflag & 2)
+ x = -x;
+ if (reflectflag & 1)
+ y = -y;
+
+ /* gintline */
+ x1 = drawx;
+ y1 = drawy;
+ newxy(x, y);
+
+#ifdef L9DEBUG
+ printf("gfx - draw (%d,%d) (%d,%d) colours %d,%d",
+ x1, y1, drawx, drawy, gintcolour & 3, option & 3);
+#endif
+
+ os_drawline(scalex(x1), scaley(y1), scalex(drawx), scaley(drawy),
+ gintcolour & 3, option & 3);
+}
+
+/* move instruction plus arguments are stored in a 16 bit word.
+ FEDCBA9876543210
+ iiiiixxxxxxyyyyy
+ where i is instruction code
+ x is x argument, high bit is sign
+ y is y argument, high bit is sign
+*/
+void _move(int d7, L9BYTE **a5) {
+ int xy, x, y;
+
+ /* getxy2 */
+ xy = (d7 << 8) + (*(*a5)++);
+ x = (xy & 0x3e0) >> 5;
+ if (xy & 0x400)
+ x = (x | 0xe0) - 0x100;
+ y = (xy & 0xf) << 2;
+ if (xy & 0x10)
+ y = (y | 0xc0) - 0x100;
+
+ if (reflectflag & 2)
+ x = -x;
+ if (reflectflag & 1)
+ y = -y;
+ newxy(x, y);
+}
+
+void icolour(int d7) {
+ gintcolour = d7 & 3;
+#ifdef L9DEBUG
+ printf("gfx - icolour 0x%.2x", gintcolour);
+#endif
+}
+
+void size(int d7) {
+ static int sizetable[7] = { 0x02, 0x04, 0x06, 0x07, 0x09, 0x0c, 0x10 };
+
+ d7 &= 7;
+ if (d7) {
+ int d0 = (scale * sizetable[d7 - 1]) >> 3;
+ scale = (d0 < 0x100) ? d0 : 0xff;
+ } else {
+ /* sizereset */
+ scale = 0x80;
+ if (gfx_mode == GFX_V2 || gfx_mode == GFX_V3A)
+ GfxScaleStackPos = 0;
+ }
+
+#ifdef L9DEBUG
+ printf("gfx - size 0x%.2x", scale);
+#endif
+}
+
+void gintfill(int d7) {
+ if ((d7 & 7) == 0)
+ /* filla */
+ d7 = gintcolour;
+ else
+ d7 &= 3;
+ /* fillb */
+
+#ifdef L9DEBUG
+ printf("gfx - gintfill (%d,%d) colours %d,%d", drawx, drawy, d7 & 3, option & 3);
+#endif
+
+ os_fill(scalex(drawx), scaley(drawy), d7 & 3, option & 3);
+}
+
+void gosub(int d7, L9BYTE **a5) {
+ int d0 = ((d7 & 7) << 8) + (*(*a5)++);
+#ifdef L9DEBUG
+ printf("gfx - gosub 0x%.2x", d0);
+#endif
+ gosubd0(d0, a5);
+}
+
+void reflect(int d7) {
+#ifdef L9DEBUG
+ printf("gfx - reflect 0x%.2x", d7);
+#endif
+
+ if (d7 & 4) {
+ d7 &= 3;
+ d7 ^= reflectflag;
+ }
+ /* reflect1 */
+ reflectflag = d7;
+}
+
+void notimp(void) {
+#ifdef L9DEBUG
+ printf("gfx - notimp");
+#endif
+}
+
+void gintchgcol(L9BYTE **a5) {
+ int d0 = *(*a5)++;
+
+#ifdef L9DEBUG
+ printf("gfx - gintchgcol %d %d", (d0 >> 3) & 3, d0 & 7);
+#endif
+
+ os_setcolour((d0 >> 3) & 3, d0 & 7);
+}
+
+void amove(L9BYTE **a5) {
+ drawx = 0x40 * (*(*a5)++);
+ drawy = 0x40 * (*(*a5)++);
+#ifdef L9DEBUG
+ printf("gfx - amove (%d,%d)", drawx, drawy);
+#endif
+}
+
+void opt(L9BYTE **a5) {
+ int d0 = *(*a5)++;
+#ifdef L9DEBUG
+ printf("gfx - opt 0x%.2x", d0);
+#endif
+
+ if (d0)
+ d0 = (d0 & 3) | 0x80;
+ /* optend */
+ option = d0;
+}
+
+void restorescale(void) {
+#ifdef L9DEBUG
+ printf("gfx - restorescale");
+#endif
+ if (GfxScaleStackPos > 0)
+ scale = GfxScaleStack[GfxScaleStackPos - 1];
+}
+
+L9BOOL rts(L9BYTE **a5) {
+ if (GfxA5StackPos > 0) {
+ GfxA5StackPos--;
+ *a5 = GfxA5Stack[GfxA5StackPos];
+ if (GfxScaleStackPos > 0) {
+ GfxScaleStackPos--;
+ scale = GfxScaleStack[GfxScaleStackPos];
+ }
+ return TRUE;
+ }
+ return FALSE;
+}
+
+L9BOOL getinstruction(L9BYTE **a5) {
+ int d7 = *(*a5)++;
+ if ((d7 & 0xc0) != 0xc0) {
+ switch ((d7 >> 6) & 3) {
+ case 0:
+ sdraw(d7);
+ break;
+ case 1:
+ smove(d7);
+ break;
+ case 2:
+ sgosub(d7, a5);
+ break;
+ }
+ } else if ((d7 & 0x38) != 0x38) {
+ switch ((d7 >> 3) & 7) {
+ case 0:
+ draw(d7, a5);
+ break;
+ case 1:
+ _move(d7, a5);
+ break;
+ case 2:
+ icolour(d7);
+ break;
+ case 3:
+ size(d7);
+ break;
+ case 4:
+ gintfill(d7);
+ break;
+ case 5:
+ gosub(d7, a5);
+ break;
+ case 6:
+ reflect(d7);
+ break;
+ }
+ } else {
+ switch (d7 & 7) {
+ case 0:
+ notimp();
+ break;
+ case 1:
+ gintchgcol(a5);
+ break;
+ case 2:
+ notimp();
+ break;
+ case 3:
+ amove(a5);
+ break;
+ case 4:
+ opt(a5);
+ break;
+ case 5:
+ restorescale();
+ break;
+ case 6:
+ notimp();
+ break;
+ case 7:
+ return rts(a5);
+ }
+ }
+ return TRUE;
+}
+
+void absrunsub(int d0) {
+ L9BYTE *a5;
+ if (!findsub(d0, &a5))
+ return;
+ while (getinstruction(&a5));
+}
+
+void show_picture(int pic) {
+ if (L9GameType == L9_V3 && strlen(FirstLine) == 0) {
+ FirstPicture = pic;
+ return;
+ }
+
+ if (picturedata) {
+ /* Some games don't call the screen() opcode before drawing
+ graphics, so here graphics are enabled if necessary. */
+ if ((screencalled == 0) && (l9textmode == 0)) {
+ detect_gfx_mode();
+ l9textmode = 1;
+ os_graphics(1);
+ }
+
+#ifdef L9DEBUG
+ printf("picture %d", pic);
+#endif
+
+ os_cleargraphics();
+ /* gintinit */
+ gintcolour = 3;
+ option = 0x80;
+ reflectflag = 0;
+ drawx = 0x1400;
+ drawy = 0x1400;
+ /* sizereset */
+ scale = 0x80;
+
+ GfxA5StackPos = 0;
+ GfxScaleStackPos = 0;
+ absrunsub(0);
+ if (!findsub(pic, &gfxa5))
+ gfxa5 = NULL;
+ }
+}
+
+void picture(void) {
+ show_picture(*getvar());
+}
+
+void GetPictureSize(int *width, int *height) {
+ if (L9GameType == L9_V4) {
+ if (width != NULL)
+ *width = 0;
+ if (height != NULL)
+ *height = 0;
+ } else {
+ if (width != NULL)
+ *width = (gfx_mode != GFX_V3C) ? 160 : 320;
+ if (height != NULL)
+ *height = (gfx_mode == GFX_V2) ? 128 : 96;
+ }
+}
+
+L9BOOL RunGraphics(void) {
+ if (gfxa5) {
+ if (!getinstruction(&gfxa5))
+ gfxa5 = NULL;
+ return TRUE;
+ }
+ return FALSE;
+}
+
+void initgetobj(void) {
+ int i;
+ numobjectfound = 0;
+ object = 0;
+ for (i = 0; i < 32; i++) gnoscratch[i] = 0;
+}
+
+void getnextobject(void) {
+ int d2, d3, d4;
+ L9UINT16 *hisearchposvar, *searchposvar;
+
+#ifdef L9DEBUG
+ printf("getnextobject");
+#endif
+
+ d2 = *getvar();
+ hisearchposvar = getvar();
+ searchposvar = getvar();
+ d3 = *hisearchposvar;
+ d4 = *searchposvar;
+
+ /* gnoabs */
+ do {
+ if ((d3 | d4) == 0) {
+ /* initgetobjsp */
+ gnosp = 128;
+ searchdepth = 0;
+ initgetobj();
+ break;
+ }
+
+ if (numobjectfound == 0) inithisearchpos = d3;
+
+ /* gnonext */
+ do {
+ if (d4 == list2ptr[++object]) {
+ /* gnomaybefound */
+ int d6 = list3ptr[object] & 0x1f;
+ if (d6 != d3) {
+ if (d6 == 0 || d3 == 0) continue;
+ if (d3 != 0x1f) {
+ gnoscratch[d6] = d6;
+ continue;
+ }
+ d3 = d6;
+ }
+ /* gnofound */
+ numobjectfound++;
+ gnostack[--gnosp] = object;
+ gnostack[--gnosp] = 0x1f;
+
+ *hisearchposvar = d3;
+ *searchposvar = d4;
+ *getvar() = object;
+ *getvar() = numobjectfound;
+ *getvar() = searchdepth;
+ return;
+ }
+ } while (object <= d2);
+
+ if (inithisearchpos == 0x1f) {
+ gnoscratch[d3] = 0;
+ d3 = 0;
+
+ /* gnoloop */
+ do {
+ if (gnoscratch[d3]) {
+ gnostack[--gnosp] = d4;
+ gnostack[--gnosp] = d3;
+ }
+ } while (++d3 < 0x1f);
+ }
+ /* gnonewlevel */
+ if (gnosp != 128) {
+ d3 = gnostack[gnosp++];
+ d4 = gnostack[gnosp++];
+ } else d3 = d4 = 0;
+
+ numobjectfound = 0;
+ if (d3 == 0x1f) searchdepth++;
+
+ initgetobj();
+ } while (d4);
+
+ /* gnofinish */
+ /* gnoreturnargs */
+ *hisearchposvar = 0;
+ *searchposvar = 0;
+ *getvar() = object = 0;
+ *getvar() = numobjectfound;
+ *getvar() = searchdepth;
+}
+
+void ifeqct(void) {
+ L9UINT16 d0 = *getvar();
+ L9UINT16 d1 = getcon();
+ L9BYTE *a0 = getaddr();
+ if (d0 == d1) codeptr = a0;
+#ifdef CODEFOLLOW
+ fprintf(f, " if Var[%d]=%d goto %d (%s)", cfvar - workspace.vartable, d1, (L9UINT32)(a0 - acodeptr), d0 == d1 ? "Yes" : "No");
+#endif
+}
+
+void ifnect(void) {
+ L9UINT16 d0 = *getvar();
+ L9UINT16 d1 = getcon();
+ L9BYTE *a0 = getaddr();
+ if (d0 != d1) codeptr = a0;
+#ifdef CODEFOLLOW
+ fprintf(f, " if Var[%d]!=%d goto %d (%s)", cfvar - workspace.vartable, d1, (L9UINT32)(a0 - acodeptr), d0 != d1 ? "Yes" : "No");
+#endif
+}
+
+void ifltct(void) {
+ L9UINT16 d0 = *getvar();
+ L9UINT16 d1 = getcon();
+ L9BYTE *a0 = getaddr();
+ if (d0 < d1) codeptr = a0;
+#ifdef CODEFOLLOW
+ fprintf(f, " if Var[%d]<%d goto %d (%s)", cfvar - workspace.vartable, d1, (L9UINT32)(a0 - acodeptr), d0 < d1 ? "Yes" : "No");
+#endif
+}
+
+void ifgtct(void) {
+ L9UINT16 d0 = *getvar();
+ L9UINT16 d1 = getcon();
+ L9BYTE *a0 = getaddr();
+ if (d0 > d1) codeptr = a0;
+#ifdef CODEFOLLOW
+ fprintf(f, " if Var[%d]>%d goto %d (%s)", cfvar - workspace.vartable, d1, (L9UINT32)(a0 - acodeptr), d0 > d1 ? "Yes" : "No");
+#endif
+}
+
+void printinput(void) {
+ L9BYTE *ptr = (L9BYTE *) obuff;
+ char c;
+ while ((c = *ptr++) != ' ') printchar(c);
+
+#ifdef L9DEBUG
+ printf("printinput");
+#endif
+}
+
+void listhandler(void) {
+ L9BYTE *a4, *MinAccess, *MaxAccess;
+ L9UINT16 val;
+ L9UINT16 *var;
+#ifdef CODEFOLLOW
+ int offset;
+#endif
+
+ if ((code & 0x1f) > 0xa) {
+ error("\rillegal list access %d\r", code & 0x1f);
+ Running = FALSE;
+ return;
+ }
+ a4 = L9Pointers[1 + (code & 0x1f)];
+
+ if (a4 >= workspace.listarea && a4 < workspace.listarea + LISTAREASIZE) {
+ MinAccess = workspace.listarea;
+ MaxAccess = workspace.listarea + LISTAREASIZE;
+ } else {
+ MinAccess = startdata;
+ MaxAccess = startdata + FileSize;
+ }
+
+ if (code >= 0xe0) {
+ /* listvv */
+#ifndef CODEFOLLOW
+ a4 += *getvar();
+ val = *getvar();
+#else
+ offset = *getvar();
+ a4 += offset;
+ var = getvar();
+ val = *var;
+ fprintf(f, " list %d [%d]=Var[%d] (=%d)", code & 0x1f, offset, var - workspace.vartable, val);
+#endif
+
+ if (a4 >= MinAccess && a4 < MaxAccess) *a4 = (L9BYTE) val;
+#ifdef L9DEBUG
+ else printf("Out of range list access");
+#endif
+ } else if (code >= 0xc0) {
+ /* listv1c */
+#ifndef CODEFOLLOW
+ a4 += *codeptr++;
+ var = getvar();
+#else
+ offset = *codeptr++;
+ a4 += offset;
+ var = getvar();
+ fprintf(f, " Var[%d]= list %d [%d])", var - workspace.vartable, code & 0x1f, offset);
+ if (a4 >= MinAccess && a4 < MaxAccess) fprintf(f, " (=%d)", *a4);
+#endif
+
+ if (a4 >= MinAccess && a4 < MaxAccess) *var = *a4;
+ else {
+ *var = 0;
+#ifdef L9DEBUG
+ printf("Out of range list access");
+#endif
+ }
+ } else if (code >= 0xa0) {
+ /* listv1v */
+#ifndef CODEFOLLOW
+ a4 += *getvar();
+ var = getvar();
+#else
+ offset = *getvar();
+ a4 += offset;
+ var = getvar();
+
+ fprintf(f, " Var[%d] =list %d [%d]", var - workspace.vartable, code & 0x1f, offset);
+ if (a4 >= MinAccess && a4 < MaxAccess) fprintf(f, " (=%d)", *a4);
+#endif
+
+ if (a4 >= MinAccess && a4 < MaxAccess) *var = *a4;
+ else {
+ *var = 0;
+#ifdef L9DEBUG
+ printf("Out of range list access");
+#endif
+ }
+ } else {
+#ifndef CODEFOLLOW
+ a4 += *codeptr++;
+ val = *getvar();
+#else
+ offset = *codeptr++;
+ a4 += offset;
+ var = getvar();
+ val = *var;
+ fprintf(f, " list %d [%d]=Var[%d] (=%d)", code & 0x1f, offset, var - workspace.vartable, val);
+#endif
+
+ if (a4 >= MinAccess && a4 < MaxAccess) *a4 = (L9BYTE) val;
+#ifdef L9DEBUG
+ else printf("Out of range list access");
+#endif
+ }
+}
+
+void executeinstruction(void) {
+#ifdef CODEFOLLOW
+ f = fopen(CODEFOLLOWFILE, "a");
+ fprintf(f, "%ld (s:%d) %x", (L9UINT32)(codeptr - acodeptr) - 1, workspace.stackptr, code);
+ if (!(code & 0x80))
+ fprintf(f, " = %s", codes[code & 0x1f]);
+#endif
+
+ if (code & 0x80)
+ listhandler();
+ else {
+ switch (code & 0x1f) {
+ case 0:
+ Goto();
+ break;
+ case 1:
+ intgosub();
+ break;
+ case 2:
+ intreturn();
+ break;
+ case 3:
+ printnumber();
+ break;
+ case 4:
+ messagev();
+ break;
+ case 5:
+ messagec();
+ break;
+ case 6:
+ function();
+ break;
+ case 7:
+ input();
+ break;
+ case 8:
+ varcon();
+ break;
+ case 9:
+ varvar();
+ break;
+ case 10:
+ _add();
+ break;
+ case 11:
+ _sub();
+ break;
+ case 12:
+ ilins(code & 0x1f);
+ break;
+ case 13:
+ ilins(code & 0x1f);
+ break;
+ case 14:
+ jump();
+ break;
+ case 15:
+ Exit();
+ break;
+ case 16:
+ ifeqvt();
+ break;
+ case 17:
+ ifnevt();
+ break;
+ case 18:
+ ifltvt();
+ break;
+ case 19:
+ ifgtvt();
+ break;
+ case 20:
+ _screen();
+ break;
+ case 21:
+ cleartg();
+ break;
+ case 22:
+ picture();
+ break;
+ case 23:
+ getnextobject();
+ break;
+ case 24:
+ ifeqct();
+ break;
+ case 25:
+ ifnect();
+ break;
+ case 26:
+ ifltct();
+ break;
+ case 27:
+ ifgtct();
+ break;
+ case 28:
+ printinput();
+ break;
+ case 29:
+ ilins(code & 0x1f);
+ break;
+ case 30:
+ ilins(code & 0x1f);
+ break;
+ case 31:
+ ilins(code & 0x1f);
+ break;
+ }
+ }
+#ifdef CODEFOLLOW
+ fprintf(f, "\n");
+ f.close();
+#endif
+}
+
+L9BOOL LoadGame2(const char *filename, char *picname) {
+#ifdef CODEFOLLOW
+ f = fopen(CODEFOLLOWFILE, "w");
+ fprintf(f, "Code follow file...\n");
+ f.close();
+#endif
+
+ /* may be already running a game, maybe in input routine */
+ Running = FALSE;
+ ibuffptr = NULL;
+
+ /* intstart */
+ if (!intinitialise(filename, picname)) return FALSE;
+ /* if (!checksumgamedata()) return FALSE; */
+
+ codeptr = acodeptr;
+ if (constseed > 0)
+ randomseed = constseed;
+ else
+ randomseed = (L9UINT16)g_system->getMillis();
+ strcpy(LastGame, filename);
+ return Running = TRUE;
+}
+
+L9BOOL LoadGame(const char *filename, char *picname) {
+ L9BOOL ret = LoadGame2(filename, picname);
+ showtitle = 1;
+ clearworkspace();
+ workspace.stackptr = 0;
+ /* need to clear listarea as well */
+ memset((L9BYTE *) workspace.listarea, 0, LISTAREASIZE);
+ return ret;
+}
+
+/* can be called from input to cause fall through for exit */
+void StopGame(void) {
+ Running = FALSE;
+}
+
+L9BOOL RunGame(void) {
+ code = *codeptr++;
+ /* printf("%d",code); */
+ executeinstruction();
+ return Running;
+}
+
+void RestoreGame(char *filename) {
+ int Bytes;
+ GameState temp;
+ Common::File f;
+
+ if (f.open(filename)) {
+ // TODO: This is horribly unportable
+ Bytes = f.read(&temp, sizeof(GameState));
+ if (Bytes == V1FILESIZE) {
+ printstring("\rGame restored.\r");
+ /* only copy in workspace */
+ memset(workspace.listarea, 0, LISTAREASIZE);
+ memmove(workspace.vartable, &temp, V1FILESIZE);
+ } else if (CheckFile(&temp)) {
+ printstring("\rGame restored.\r");
+ /* full restore */
+ memmove(&workspace, &temp, sizeof(GameState));
+ codeptr = acodeptr + workspace.codeptr;
+ } else
+ printstring("\rSorry, unrecognised format. Unable to restore\r");
+ } else
+ printstring("\rUnable to restore game.\r");
+}
+
+} // End of namespace Level9
+} // End of namespace Glk
diff --git a/engines/glk/level9/level9_main.h b/engines/glk/level9/level9_main.h
new file mode 100644
index 0000000..ab30242
--- /dev/null
+++ b/engines/glk/level9/level9_main.h
@@ -0,0 +1,123 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef GLK_LEVEL9_LEVEL9_MAIN
+#define GLK_LEVEL9_LEVEL9_MAIN
+
+#include "common/scummsys.h"
+#include "common/endian.h"
+#include "common/stream.h"
+
+namespace Glk {
+namespace Level9 {
+
+typedef byte L9BYTE;
+typedef uint16 L9UINT16;
+typedef uint32 L9UINT32;
+typedef bool L9BOOL;
+
+#define FALSE false
+#define TRUE true
+
+#define LISTAREASIZE 0x800
+#define STACKSIZE 1024
+#define V1FILESIZE 0x600
+
+#ifndef MAX_PATH
+#define MAX_PATH 256
+#endif
+
+struct GameState {
+ L9UINT32 Id;
+ L9UINT16 codeptr, stackptr, listsize, stacksize, filenamesize, checksum;
+ L9UINT16 vartable[256];
+ L9BYTE listarea[LISTAREASIZE];
+ L9UINT16 stack[STACKSIZE];
+ char filename[MAX_PATH];
+};
+
+enum BitmapType {
+ NO_BITMAPS,
+ AMIGA_BITMAPS,
+ PC1_BITMAPS,
+ PC2_BITMAPS,
+ C64_BITMAPS,
+ BBC_BITMAPS,
+ CPC_BITMAPS,
+ MAC_BITMAPS,
+ ST1_BITMAPS,
+ ST2_BITMAPS
+};
+
+struct Colour {
+ L9BYTE red, green, blue;
+};
+
+struct Bitmap {
+ L9UINT16 width, height;
+ L9BYTE *bitmap;
+ Colour palette[32];
+ L9UINT16 npalette;
+};
+
+#define MAX_BITMAP_WIDTH 512
+#define MAX_BITMAP_HEIGHT 218
+
+#define L9WORD(x) READ_LE_UINT16(x)
+#define L9SETWORD(x,val) WRITE_LE_UINT16(x, val)
+#define L9SETDWORD(x,val) WRITE_LE_UINT32(x, val)
+
+/* routines provided by os dependent code */
+void os_printchar(char c);
+L9BOOL os_input(char *ibuff, int size);
+char os_readchar(int millis);
+L9BOOL os_stoplist(void);
+void os_flush(void);
+L9BOOL os_save_file(L9BYTE *Ptr, int Bytes);
+L9BOOL os_load_file(L9BYTE *Ptr, int *Bytes, int Max);
+L9BOOL os_get_game_file(char *NewName, int Size);
+void os_set_filenumber(char *NewName, int Size, int n);
+void os_graphics(int mode);
+void os_cleargraphics(void);
+void os_setcolour(int colour, int index);
+void os_drawline(int x1, int y1, int x2, int y2, int colour1, int colour2);
+void os_fill(int x, int y, int colour1, int colour2);
+void os_show_bitmap(int pic, int x, int y);
+Common::SeekableReadStream *os_open_script_file(void);
+
+/* routines provided by level9 interpreter */
+L9BOOL LoadGame(const char *filename, char *picname);
+L9BOOL RunGame(void);
+void StopGame(void);
+void RestoreGame(char *filename);
+void FreeMemory(void);
+void GetPictureSize(int *width, int *height);
+L9BOOL RunGraphics(void);
+
+/* bitmap routines provided by level9 interpreter */
+BitmapType DetectBitmaps(char *dir);
+Bitmap *DecodeBitmap(char *dir, BitmapType type, int num, int x, int y);
+
+} // End of namespace Level9
+} // End of namespace Glk
+
+#endif
diff --git a/engines/glk/level9/os_glk.cpp b/engines/glk/level9/os_glk.cpp
new file mode 100644
index 0000000..ef5499c
--- /dev/null
+++ b/engines/glk/level9/os_glk.cpp
@@ -0,0 +1,6084 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "glk/level9/level9_main.h"
+#include "glk/level9/level9.h"
+#include "common/textconsole.h"
+
+namespace Glk {
+namespace Level9 {
+
+#define BYTE_MAX 0xff
+#define BITS_PER_CHAR 8
+
+/* File path delimiter, used to be #defined in v2 interpreter. */
+#if defined(_Windows) || defined(__MSDOS__) \
+ || defined (_WIN32) || defined (__WIN32__)
+static const char GLN_FILE_DELIM = '\\';
+#else
+static const char GLN_FILE_DELIM = '/';
+#endif
+
+/*---------------------------------------------------------------------*/
+/* Module variables, miscellaneous other stuff */
+/*---------------------------------------------------------------------*/
+
+/* Glk Level 9 port version number. */
+static const glui32 GLN_PORT_VERSION = 0x00020201;
+
+/*
+ * We use a maximum of three Glk windows, one for status, one for pictures,
+ * and one for everything else. The status and pictures windows may be
+ * NULL, depending on user selections and the capabilities of the Glk
+ * library.
+ */
+static winid_t gln_main_window = NULL,
+ gln_status_window = NULL,
+ gln_graphics_window = NULL;
+
+/*
+ * Transcript stream and input log. These are NULL if there is no current
+ * collection of these strings.
+ */
+static strid_t gln_transcript_stream = NULL,
+ gln_inputlog_stream = NULL;
+
+/* Input read log stream, for reading back an input log. */
+static strid_t gln_readlog_stream = NULL;
+
+/* Note about whether graphics is possible, or not. */
+static int gln_graphics_possible = TRUE;
+
+/* Options that may be turned off by command line flags. */
+static int gln_graphics_enabled = TRUE,
+ gln_intercept_enabled = TRUE,
+ gln_prompt_enabled = TRUE,
+ gln_loopcheck_enabled = TRUE,
+ gln_abbreviations_enabled = TRUE,
+ gln_commands_enabled = TRUE;
+
+/* Reason for stopping the game, used to detect restarts and ^C exits. */
+enum StopReason {
+ STOP_NONE, STOP_FORCE, STOP_RESTART, STOP_EXIT
+};
+static StopReason gln_stop_reason = STOP_NONE;
+
+/* Level 9 standard input prompt string. */
+static const char *const GLN_INPUT_PROMPT = "> ";
+
+/*
+ * Typedef equivalents for interpreter types (uncapitalized to avoid appearing
+ * as macros), and some internal interpreter symbols symbols used for our own
+ * deviant purposes.
+ */
+typedef L9BOOL gln_bool;
+typedef L9BYTE gln_byte;
+typedef L9UINT16 gln_uint16;
+typedef L9UINT32 gln_uint32;
+
+extern void save(void);
+extern void restore(void);
+extern gln_bool Cheating;
+extern gln_byte *startdata;
+extern gln_uint32 FileSize;
+
+/* Forward declarations of event wait and other miscellaneous functions. */
+static void gln_event_wait(glui32 wait_type, event_t *event);
+static void gln_event_wait_2(glui32 wait_type_1,
+ glui32 wait_type_2, event_t *event);
+
+static void gln_watchdog_tick(void);
+static void gln_standout_string(const char *message);
+
+static int gln_confirm(const char *prompt);
+
+
+/*---------------------------------------------------------------------*/
+/* Glk port utility functions */
+/*---------------------------------------------------------------------*/
+
+/*
+ * gln_fatal()
+ *
+ * Fatal error handler. The function returns, expecting the caller to
+ * abort() or otherwise handle the error.
+ */
+static void gln_fatal(const char *string) {
+ /*
+ * If the failure happens too early for us to have a window, print
+ * the message to stderr.
+ */
+ if (!gln_main_window) {
+ warning("INTERNAL ERROR: %s", string);
+ return;
+ }
+
+ /* Cancel all possible pending window input events. */
+ g_vm->glk_cancel_line_event(gln_main_window, NULL);
+ g_vm->glk_cancel_char_event(gln_main_window);
+
+ /* Print a message indicating the error. */
+ g_vm->glk_set_window(gln_main_window);
+ g_vm->glk_set_style(style_Normal);
+ g_vm->glk_put_string("\n\nINTERNAL ERROR: ");
+ g_vm->glk_put_string(string);
+
+ g_vm->glk_put_string("\n\nPlease record the details of this error, try to"
+ " note down everything you did to cause it, and email"
+ " this information to simon_baldwin at yahoo.com.\n\n");
+}
+
+
+/*
+ * gln_malloc()
+ * gln_realloc()
+ *
+ * Non-failing malloc and realloc; call gln_fatal and exit if memory
+ * allocation fails.
+ */
+static void *
+gln_malloc(size_t size) {
+ void *pointer;
+
+ pointer = malloc(size);
+ if (!pointer) {
+ gln_fatal("GLK: Out of system memory");
+ g_vm->glk_exit();
+ }
+
+ return pointer;
+}
+
+static void *
+gln_realloc(void *ptr, size_t size) {
+ void *pointer;
+
+ pointer = realloc(ptr, size);
+ if (!pointer) {
+ gln_fatal("GLK: Out of system memory");
+ g_vm->glk_exit();
+ }
+
+ return pointer;
+}
+
+
+/*
+ * gln_strncasecmp()
+ * gln_strcasecmp()
+ *
+ * Strncasecmp and strcasecmp are not ANSI functions, so here are local
+ * definitions to do the same jobs.
+ *
+ * They're global here so that the core interpreter can use them; otherwise
+ * it tries to use the non-ANSI str[n]icmp() functions.
+ */
+int
+gln_strncasecmp(const char *s1, const char *s2, size_t n) {
+ size_t index;
+
+ for (index = 0; index < n; index++) {
+ int diff;
+
+ diff = g_vm->glk_char_to_lower(s1[index]) - g_vm->glk_char_to_lower(s2[index]);
+ if (diff < 0 || diff > 0)
+ return diff < 0 ? -1 : 1;
+ }
+
+ return 0;
+}
+
+int
+gln_strcasecmp(const char *s1, const char *s2) {
+ size_t s1len, s2len;
+ int result;
+
+ s1len = strlen(s1);
+ s2len = strlen(s2);
+
+ result = gln_strncasecmp(s1, s2, s1len < s2len ? s1len : s2len);
+ if (result < 0 || result > 0)
+ return result;
+ else
+ return s1len < s2len ? -1 : s1len > s2len ? 1 : 0;
+}
+
+
+/*---------------------------------------------------------------------*/
+/* Glk port stub graphics functions */
+/*---------------------------------------------------------------------*/
+
+/*
+ * If we're working with a very stripped down, old, or lazy Glk library
+ * that neither offers Glk graphics nor graphics stubs functions, here
+ * we define our own stubs, to avoid link-time errors.
+ */
+#ifndef GLK_MODULE_IMAGE
+static glui32
+g_vm->glk_image_draw(winid_t win, glui32 image, glsi32 val1, glsi32 val2) {
+ return FALSE;
+}
+static glui32
+g_vm->glk_image_draw_scaled(winid_t win, glui32 image, glsi32 val1, glsi32 val2,
+ glui32 width, glui32 height) {
+ return FALSE;
+}
+static glui32
+g_vm->glk_image_get_info(glui32 image, glui32 *width, glui32 *height) {
+ return FALSE;
+}
+static void
+g_vm->glk_window_flow_break(winid_t win) {
+}
+static void
+g_vm->glk_window_erase_rect(winid_t win, glsi32 left, glsi32 top,
+ glui32 width, glui32 height) {
+}
+static void
+g_vm->glk_window_fill_rect(winid_t win, glui32 color, glsi32 left, glsi32 top,
+ glui32 width, glui32 height) {
+}
+static void
+g_vm->glk_window_set_background_color(winid_t win, glui32 color) {
+}
+#endif
+
+
+/*---------------------------------------------------------------------*/
+/* Glk port CRC functions */
+/*---------------------------------------------------------------------*/
+
+/* CRC table initialization polynomial. */
+static const gln_uint16 GLN_CRC_POLYNOMIAL = 0xa001;
+
+
+/*
+ * gln_get_buffer_crc()
+ *
+ * Return the CRC of the bytes buffer[0..length-1].
+ *
+ * This algorithm is selected to match the CRCs used in L9cut. Because of
+ * the odd way CRCs are padded when L9cut calculates the CRC, this function
+ * allows a count of NUL padding bytes to be included within the return CRC.
+ */
+static gln_uint16 gln_get_buffer_crc(const void *void_buffer, size_t length, size_t padding) {
+ static int is_initialized = FALSE;
+ static gln_uint16 crc_table[BYTE_MAX + 1];
+
+ const char *buffer = (const char *) void_buffer;
+ gln_uint16 crc;
+ size_t index;
+
+ /* Build the static CRC lookup table on first call. */
+ if (!is_initialized) {
+ for (index = 0; index < BYTE_MAX + 1; index++) {
+ int bit;
+
+ crc = (gln_uint16) index;
+ for (bit = 0; bit < BITS_PER_CHAR; bit++)
+ crc = crc & 1 ? GLN_CRC_POLYNOMIAL ^ (crc >> 1) : crc >> 1;
+
+ crc_table[index] = crc;
+ }
+
+ is_initialized = TRUE;
+
+ /* CRC lookup table self-test, after is_initialized set -- recursion. */
+ assert(gln_get_buffer_crc("123456789", 9, 0) == 0xbb3d);
+ }
+
+ /* Start with zero in the crc, then update using table entries. */
+ crc = 0;
+ for (index = 0; index < length; index++)
+ crc = crc_table[(crc ^ buffer[index]) & BYTE_MAX] ^ (crc >> BITS_PER_CHAR);
+
+ /* Add in any requested NUL padding bytes. */
+ for (index = 0; index < padding; index++)
+ crc = crc_table[crc & BYTE_MAX] ^ (crc >> BITS_PER_CHAR);
+
+ return crc;
+}
+
+
+/*---------------------------------------------------------------------*/
+/* Glk port game identification data and identification functions */
+/*---------------------------------------------------------------------*/
+
+/*
+ * The game's name, suitable for printing out on a status line, or other
+ * location where game information is relevant. It's generated on demand,
+ * and may be re-requested when, say, the game changes, perhaps by moving to
+ * the next part of a multipart game.
+ */
+static const char *gln_gameid_game_name = NULL;
+
+
+/*
+ * The following game database is obtained from L9cut's l9data_d.h, and
+ * lets us find a game's name from its data CRC. Entries marked "WANTED" in
+ * l9data_d.h, and file commentary, have been removed for brevity, and the
+ * file has been reformatted (patchlevel data removed).
+ *
+ * The version of l9data_d.h used is 050 (22 Oct 2002).
+ */
+typedef const struct {
+ const gln_uint16 length; /* Datafile length in bytes */
+ const gln_byte checksum; /* 8-bit checksum, last datafile byte */
+ const gln_uint16 crc; /* 16-bit CRC, L9cut-internal */
+ const char *const name; /* Game title and platform */
+} gln_game_table_t;
+typedef gln_game_table_t *gln_game_tableref_t;
+
+static gln_game_table_t GLN_GAME_TABLE[] = {
+ {0x5323, 0xb7, 0x8af7, "Adventure Quest (Amstrad CPC/Spectrum)"},
+
+ {0x630e, 0x8d, 0x7d7d, "Dungeon Adventure (Amstrad CPC)"},
+ {0x630e, 0xbe, 0x3374, "Dungeon Adventure (MSX)"},
+
+ {0x5eb9, 0x30, 0xe99a, "Lords of Time (Amstrad CPC)"},
+ {0x5eb9, 0x5d, 0xc098, "Lords of Time (MSX)"},
+ {0x5eb9, 0x6e, 0xc689, "Lords of Time (Spectrum)"},
+
+ {0x5fab, 0x5c, 0xa309, "Snowball (Amstrad CPC)"},
+ {0x5fab, 0x2f, 0x8aa2, "Snowball (MSX)"},
+
+ {0x60c4, 0x28, 0x0154, "Return to Eden (Amstrad CPC/Commodore 64[v1])"},
+ {0x6064, 0x01, 0x5b3c, "Return to Eden (BBC[v1])"},
+ {0x6064, 0x95, 0x510c, "Return to Eden (Commodore 64[v2])"},
+ {0x6064, 0xda, 0xe610, "Return to Eden (Commodore 64[v2] *corrupt*)"},
+ {0x6064, 0xbd, 0x73ec, "Return to Eden (Atari *corrupt*)"},
+ {0x6047, 0x6c, 0x17ab, "Return to Eden (BBC[v2])"},
+ {0x5ca1, 0x33, 0x1c43, "Return to Eden (Spectrum[v1])"},
+ {0x5cb7, 0x64, 0x0790, "Return to Eden (Spectrum[v2])"},
+ {0x5cb7, 0xfe, 0x3533, "Return to Eden (MSX)"},
+
+ {0x34b3, 0x20, 0xccda, "Erik the Viking (BBC/Commodore 64)"},
+ {0x34b3, 0x53, 0x8f00, "Erik the Viking (Spectrum)"},
+ {0x34b3, 0xc7, 0x9058, "Erik the Viking (Amstrad CPC)"},
+
+ {
+ 0x63be, 0xd6, 0xcf5d,
+ "Emerald Isle (Atari/Commodore 64/Amstrad CPC/Spectrum)"
+ },
+ {0x63be, 0x0a, 0x21ed, "Emerald Isle (MSX *corrupt*)"},
+ {0x378c, 0x8d, 0x3a21, "Emerald Isle (BBC)"},
+
+ {0x506c, 0xf0, 0xba72, "Red Moon (BBC/Commodore 64/Amstrad CPC/MSX)"},
+ {0x505d, 0x32, 0x2dcf, "Red Moon (Spectrum)"},
+
+ {0x772b, 0xcd, 0xa503, "Worm in Paradise (Spectrum 128)"},
+ {0x546c, 0xb7, 0x9420, "Worm in Paradise (Spectrum 48)"},
+ {0x6d84, 0xf9, 0x49ae, "Worm in Paradise (Commodore 64 *corrupt*)"},
+ {0x6d84, 0xc8, 0x943f, "Worm in Paradise (Commodore 64 *fixed*)"},
+ {0x6030, 0x47, 0x46ad, "Worm in Paradise (Amstrad CPC)"},
+ {0x5828, 0xbd, 0xe7cb, "Worm in Paradise (BBC)"},
+
+ {0x7410, 0x5e, 0x60be, "Price of Magik (Spectrum 128)"},
+ {0x5aa4, 0xc1, 0x10a0, "Price of Magik (Spectrum 48[v1])"},
+ {0x5aa4, 0xc1, 0xeda4, "Price of Magik (Spectrum 48[v2])"},
+ {0x6fc6, 0x14, 0xf9b6, "Price of Magik (Commodore 64)"},
+ {0x5aa4, 0xc1, 0xbbf4, "Price of Magik (Amstrad CPC)"},
+ {0x5671, 0xbc, 0xff35, "Price of Magik (BBC)"},
+
+ {0x76f4, 0x5e, 0x1fe5, "Colossal Adventure /JoD (Amiga/PC)"},
+ {0x76f4, 0x5a, 0xcf4b, "Colossal Adventure /JoD (ST)"},
+ {0x6e60, 0x83, 0x18e0, "Adventure Quest /JoD (Amiga/PC)"},
+ {0x6e5c, 0xf6, 0xd356, "Adventure Quest /JoD (ST)"},
+ {0x6f0c, 0x95, 0x1f64, "Dungeon Adventure /JoD (Amiga/PC/ST)"},
+
+ {0x6f70, 0x40, 0xbd91, "Colossal Adventure /JoD (MSX)"},
+
+ {0x6f6e, 0x78, 0x28cd, "Colossal Adventure /JoD (Spectrum 128)"},
+ {0x6970, 0xd6, 0xa820, "Adventure Quest /JoD (Spectrum 128)"},
+ {0x6de8, 0x4c, 0xd795, "Dungeon Adventure /JoD (Spectrum 128)"},
+
+ {
+ 0x6f4d, 0xcb, 0xe8f2,
+ "Colossal Adventure /JoD (Amstrad CPC128[v1]/Spectrum +3)"
+ },
+ {0x6f6a, 0xa5, 0x8dd2, "Colossal Adventure /JoD (Amstrad CPC128[v2])"},
+ {0x6968, 0x32, 0x0c01, "Adventure Quest /JoD (Amstrad CPC128/Spectrum +3)"},
+ {0x6dc0, 0x63, 0x5d95, "Dungeon Adventure /JoD (Amstrad CPC128/Spectrum +3)"},
+
+ {0x5e31, 0x7c, 0xaa54, "Colossal Adventure /JoD (Amstrad CPC64)"},
+ {0x5b50, 0x66, 0x1800, "Adventure Quest /JoD (Amstrad CPC64)"},
+ {0x58a6, 0x24, 0xb50f, "Dungeon Adventure /JoD (Amstrad CPC64)"},
+
+ {0x6c8e, 0xb6, 0x9be3, "Colossal Adventure /JoD (Commodore 64)"},
+ {0x63b6, 0x2e, 0xef38, "Adventure Quest /JoD (Commodore 64)"},
+ {0x6bd2, 0x65, 0xa41f, "Dungeon Adventure /JoD (Commodore 64)"},
+
+ {0x5b16, 0x3b, 0xe2aa, "Colossal Adventure /JoD (Atari)"},
+ {0x5b58, 0x50, 0x332e, "Adventure Quest /JoD (Atari)"},
+ {0x593a, 0x80, 0x7a34, "Dungeon Adventure /JoD (Atari)"},
+
+ {0x5a8e, 0xf2, 0x7cca, "Colossal Adventure /JoD (Spectrum 48)"},
+ {0x5ace, 0x11, 0xdc12, "Adventure Quest /JoD (Spectrum 48)"},
+ {0x58a3, 0x38, 0x8ce4, "Dungeon Adventure /JoD (Spectrum 48)"},
+
+ {0x7b31, 0x6e, 0x2e2b, "Snowball /SD (Amiga/ST)"},
+ {0x7d16, 0xe6, 0x5438, "Return to Eden /SD (Amiga/ST)"},
+ {0x7cd9, 0x0c, 0x4df1, "Worm in Paradise /SD (Amiga/ST)"},
+
+ {0x7b2f, 0x70, 0x6955, "Snowball /SD (Mac/PC/Spectrum 128)"},
+ {0x7b2f, 0x70, 0x6f6c, "Snowball /SD (Amstrad CPC/Spectrum +3)"},
+ {0x7d14, 0xe8, 0xfbab, "Return to Eden /SD (PC)"},
+ {0x7cff, 0xf8, 0x6044, "Return to Eden /SD (Amstrad CPC/Spectrum +3)"},
+ {0x7cf8, 0x24, 0x9c1c, "Return to Eden /SD (Mac)"},
+ {0x7c55, 0x18, 0xdaee, "Return to Eden /SD (Spectrum 128)"},
+ {
+ 0x7cd7, 0x0e, 0x4feb,
+ "Worm in Paradise /SD (Amstrad CPC/Mac/PC/Spectrum 128/Spectrum +3)"
+ },
+
+ {0x7363, 0x65, 0xa0ab, "Snowball /SD (Commodore 64)"},
+ {0x772f, 0xca, 0x8602, "Return to Eden /SD (Commodore 64)"},
+ {0x788d, 0x72, 0x888a, "Worm in Paradise /SD (Commodore 64)"},
+
+ {0x6bf8, 0x3f, 0xc9f7, "Snowball /SD (Atari)"},
+ {0x60f7, 0x68, 0xc2bc, "Return to Eden /SD (Atari)"},
+ {0x6161, 0xf3, 0xe6d7, "Worm in Paradise /SD (Atari)"},
+
+ {0x67a3, 0x9d, 0x1d05, "Snowball /SD (Apple ][)"},
+ {0x639c, 0x8b, 0x06e2, "Return to Eden /SD (Apple ][)"},
+ {0x60dd, 0xf2, 0x5bb8, "Worm in Paradise /SD (Apple ][)"},
+
+ {0x6541, 0x02, 0x2e6c, "Snowball /SD (Spectrum 48)"},
+ {0x5f43, 0xca, 0x828c, "Return to Eden /SD (Spectrum 48)"},
+ {0x5ebb, 0xf1, 0x4dec, "Worm in Paradise /SD (Spectrum 48)"},
+
+ {0x8333, 0xb7, 0xe2ac, "Adrian Mole I, pt. 1 (Commodore 64)"},
+ {0x844d, 0x50, 0x5353, "Adrian Mole I, pt. 2 (Commodore 64)"},
+ {0x8251, 0x5f, 0x862a, "Adrian Mole I, pt. 3 (Commodore 64)"},
+ {0x7a78, 0x5e, 0x6ea3, "Adrian Mole I, pt. 4 (Commodore 64)"},
+
+ {0x7c6f, 0x0f, 0xba24, "Adrian Mole I, pt. 1 (Amstrad CPC)"},
+
+ {0x72fa, 0x8b, 0x6f12, "Adrian Mole I, pt. 1 (Spectrum)"},
+ {0x738e, 0x5b, 0x7e3d, "Adrian Mole I, pt. 2 (Spectrum)"},
+ {0x7375, 0xe5, 0x3f3e, "Adrian Mole I, pt. 3 (Spectrum)"},
+ {0x78d5, 0xe3, 0xcd7d, "Adrian Mole I, pt. 4 (Spectrum)"},
+
+ {0x3a31, 0xe5, 0x0bdb, "Adrian Mole I, pt. 1 (BBC)"},
+ {0x37f1, 0x77, 0xd231, "Adrian Mole I, pt. 2 (BBC)"},
+ {0x3900, 0x1c, 0x5d9a, "Adrian Mole I, pt. 3 (BBC)"},
+ {0x3910, 0xac, 0x07f9, "Adrian Mole I, pt. 4 (BBC)"},
+ {0x3ad6, 0xa7, 0x95d2, "Adrian Mole I, pt. 5 (BBC)"},
+ {0x38a5, 0x0f, 0xdefc, "Adrian Mole I, pt. 6 (BBC)"},
+ {0x361e, 0x7e, 0xfd9f, "Adrian Mole I, pt. 7 (BBC)"},
+ {0x3934, 0x75, 0xe141, "Adrian Mole I, pt. 8 (BBC)"},
+ {0x3511, 0xcc, 0xd829, "Adrian Mole I, pt. 9 (BBC)"},
+ {0x38dd, 0x31, 0x2534, "Adrian Mole I, pt. 10 (BBC)"},
+ {0x39c0, 0x44, 0x89df, "Adrian Mole I, pt. 11 (BBC)"},
+ {0x3a12, 0x8f, 0xc2bd, "Adrian Mole I, pt. 12 (BBC)"},
+
+ {0x7931, 0xb9, 0xc51b, "Adrian Mole II, pt. 1 (Commodore 64/Amstrad CPC)"},
+ {0x7cdf, 0xa5, 0x43e3, "Adrian Mole II, pt. 2 (Commodore 64/Amstrad CPC)"},
+ {0x7a0c, 0x97, 0x4bea, "Adrian Mole II, pt. 3 (Commodore 64/Amstrad CPC)"},
+ {0x7883, 0xe2, 0xee0e, "Adrian Mole II, pt. 4 (Commodore 64/Amstrad CPC)"},
+
+ {0x6841, 0x4a, 0x94e7, "Adrian Mole II, pt. 1 (Spectrum)"},
+ {0x6bc0, 0x62, 0xab3d, "Adrian Mole II, pt. 2 (Spectrum)"},
+ {0x692c, 0x21, 0x2015, "Adrian Mole II, pt. 3 (Spectrum)"},
+ {0x670a, 0x94, 0xa2a6, "Adrian Mole II, pt. 4 (Spectrum)"},
+
+ {0x593a, 0xaf, 0x30e9, "Adrian Mole II, pt. 1 (BBC)"},
+ {0x57e6, 0x8a, 0xc41a, "Adrian Mole II, pt. 2 (BBC)"},
+ {0x5819, 0xcd, 0x1ba0, "Adrian Mole II, pt. 3 (BBC)"},
+ {0x579b, 0xad, 0xa723, "Adrian Mole II, pt. 4 (BBC)"},
+
+ {0x765d, 0xcd, 0xfc02, "The Archers, pt. 1 (Commodore 64)"},
+ {0x6e58, 0x07, 0xbffc, "The Archers, pt. 2 (Commodore 64)"},
+ {0x7e98, 0x6a, 0x95e5, "The Archers, pt. 3 (Commodore 64)"},
+ {0x81e2, 0xd5, 0xb278, "The Archers, pt. 4 (Commodore 64)"},
+
+ {0x6ce5, 0x58, 0x46de, "The Archers, pt. 1 (Spectrum)"},
+ {0x68da, 0xc1, 0x3b8e, "The Archers, pt. 2 (Spectrum)"},
+ {0x6c67, 0x9a, 0x9a6a, "The Archers, pt. 3 (Spectrum)"},
+ {0x6d91, 0xb9, 0x12a7, "The Archers, pt. 4 (Spectrum)"},
+
+ {0x5834, 0x42, 0xcc9d, "The Archers, pt. 1 (BBC)"},
+ {0x56dd, 0x51, 0xe582, "The Archers, pt. 2 (BBC)"},
+ {0x5801, 0x53, 0xf2ef, "The Archers, pt. 3 (BBC)"},
+ {0x54a4, 0x01, 0xc0ab, "The Archers, pt. 4 (BBC)"},
+
+ {0x579e, 0x97, 0x9faa, "Lords of Time /T&M GD (BBC)"},
+ {0x5500, 0x50, 0xca75, "Red Moon /T&M GD (BBC)"},
+ {0x579a, 0x2a, 0x9373, "Price of Magik /T&M GD (BBC)"},
+
+ {0x4fd2, 0x9d, 0x799a, "Lancelot, pt. 1 GD (BBC)"},
+ {0x4dac, 0xa8, 0x86ed, "Lancelot, pt. 2 GD (BBC)"},
+ {0x4f96, 0x22, 0x30f8, "Lancelot, pt. 3 GD (BBC)"},
+
+ {0x55ce, 0xa1, 0xba12, "Scapeghost, pt. 1 GD (BBC)"},
+ {0x54a6, 0xa9, 0xc9f3, "Scapeghost, pt. 2 GD (BBC)"},
+ {0x51bc, 0xe3, 0x89c3, "Scapeghost, pt. 3 GD (BBC)"},
+
+ {0x46ec, 0x64, 0x2300, "Knight Orc, pt. 1 GD (Amstrad CPC/Spectrum +3)"},
+ {0x6140, 0x18, 0x4f66, "Knight Orc, pt. 2 GD (Amstrad CPC/Spectrum +3)"},
+ {0x640e, 0xc1, 0xfc69, "Knight Orc, pt. 3 GD (Amstrad CPC/Spectrum +3)"},
+
+ {0x5ff0, 0xf8, 0x3a13, "Gnome Ranger, pt. 1 GD (Amstrad CPC/Spectrum +3)"},
+ {0x6024, 0x01, 0xaaa9, "Gnome Ranger, pt. 2 GD (Amstrad CPC/Spectrum +3)"},
+ {0x6036, 0x3d, 0x6c6c, "Gnome Ranger, pt. 3 GD (Amstrad CPC/Spectrum +3)"},
+
+ {0x69fe, 0x56, 0xecfb, "Lords of Time /T&M GD (Amstrad CPC/Spectrum +3)"},
+ {0x6888, 0x8d, 0x7f6a, "Red Moon /T&M GD (Amstrad CPC/Spectrum +3)"},
+ {0x5a50, 0xa9, 0xa5fa, "Price of Magik /T&M GD (Amstrad CPC/Spectrum +3)"},
+
+ {0x5c7a, 0x44, 0x460e, "Lancelot, pt. 1 GD (Amstrad CPC/Spectrum +3)"},
+ {0x53a2, 0x1e, 0x2fae, "Lancelot, pt. 2 GD (Amstrad CPC/Spectrum +3)"},
+ {0x5914, 0x22, 0x4a31, "Lancelot, pt. 3 GD (Amstrad CPC/Spectrum +3)"},
+
+ {0x5a38, 0xf7, 0x876e, "Ingrid's Back, pt. 1 GD (Amstrad CPC/Spectrum +3)"},
+ {0x531a, 0xed, 0xcf3f, "Ingrid's Back, pt. 2 GD (Amstrad CPC/Spectrum +3)"},
+ {0x57e4, 0x19, 0xb354, "Ingrid's Back, pt. 3 GD (Amstrad CPC/Spectrum +3)"},
+
+ {0x5cbc, 0xa5, 0x0dbe, "Scapeghost, pt. 1 GD (Amstrad CPC/Spectrum +3)"},
+ {0x5932, 0x4e, 0xb2f5, "Scapeghost, pt. 2 GD (Amstrad CPC/Spectrum +3)"},
+ {0x5860, 0x95, 0x3227, "Scapeghost, pt. 3 GD (Amstrad CPC/Spectrum +3)"},
+
+ {0x74e0, 0x92, 0x885e, "Knight Orc, pt. 1 GD (Spectrum 128)"},
+ {0x6dbc, 0x97, 0x6f55, "Knight Orc, pt. 2 GD (Spectrum 128)"},
+ {0x7402, 0x07, 0x385f, "Knight Orc, pt. 3 GD (Spectrum 128)"},
+
+ {0x52aa, 0xdf, 0x7b5b, "Gnome Ranger, pt. 1 GD (Spectrum 128)"},
+ {0x6ffa, 0xdb, 0xdde2, "Gnome Ranger, pt. 2 GD (Spectrum 128)"},
+ {0x723a, 0x69, 0x039b, "Gnome Ranger, pt. 3 GD (Spectrum 128)"},
+
+ {0x6f1e, 0xda, 0x2ce0, "Lords of Time /T&M GD (Spectrum 128)"},
+ {0x6da0, 0xb8, 0x3802, "Red Moon /T&M GD (Spectrum 128)"},
+ {0x6108, 0xdd, 0xefe7, "Price of Magik /T&M GD (Spectrum 128)"},
+
+ {0x768c, 0xe8, 0x8fc6, "Lancelot, pt. 1 GD (Spectrum 128)"},
+ {0x76b0, 0x1d, 0x0fcd, "Lancelot, pt. 2 GD (Spectrum 128)"},
+ {0x765e, 0x4f, 0x3b73, "Lancelot, pt. 3 GD (Spectrum 128)"},
+
+ {0x76a0, 0x3a, 0xb803, "Ingrid's Back, pt. 1 GD (Spectrum 128)"},
+ {0x7674, 0x0b, 0xe92f, "Ingrid's Back, pt. 2 GD (Spectrum 128)"},
+ {0x765e, 0xba, 0x086d, "Ingrid's Back, pt. 3 GD (Spectrum 128)"},
+
+ {0x762e, 0x82, 0x8848, "Scapeghost, pt. 1 GD (Spectrum 128)"},
+ {0x5bd6, 0x35, 0x79ef, "Scapeghost, pt. 2 GD (Spectrum 128)"},
+ {0x6fa8, 0xa4, 0x62c2, "Scapeghost, pt. 3 GD (Spectrum 128)"},
+
+ {0xbb93, 0x36, 0x6a05, "Knight Orc, pt. 1 (Amiga)"},
+ {0xbb6e, 0xad, 0x4d40, "Knight Orc, pt. 1 (ST)"},
+ {0xc58e, 0x4a, 0x4e9d, "Knight Orc, pt. 2 (Amiga/ST)"},
+ {0xcb9a, 0x0f, 0x0804, "Knight Orc, pt. 3 (Amiga/ST)"},
+
+ {0xbb6e, 0xa6, 0x9753, "Knight Orc, pt. 1 (PC)"},
+ {0xc58e, 0x43, 0xe9ce, "Knight Orc, pt. 2 (PC)"},
+ {0xcb9a, 0x08, 0x6c36, "Knight Orc, pt. 3 (PC)"},
+
+ {0x898a, 0x43, 0xfc8b, "Knight Orc, pt. 1 (Apple ][)"},
+ {0x8b9f, 0x61, 0x7288, "Knight Orc, pt. 2 (Apple ][)"},
+ {0x8af9, 0x61, 0x7542, "Knight Orc, pt. 3 (Apple ][)"},
+
+ {0x8970, 0x6b, 0x3c7b, "Knight Orc, pt. 1 (Commodore 64 Gfx)"},
+ {0x8b90, 0x4e, 0x098c, "Knight Orc, pt. 2 (Commodore 64 Gfx)"},
+ {0x8aea, 0x4e, 0xca54, "Knight Orc, pt. 3 (Commodore 64 Gfx)"},
+
+ {0x86d0, 0xb7, 0xadbd, "Knight Orc, pt. 1 (Spectrum 48)"},
+ {0x8885, 0x22, 0xe293, "Knight Orc, pt. 2 (Spectrum 48)"},
+ {0x87e5, 0x0e, 0xdc33, "Knight Orc, pt. 3 (Spectrum 48)"},
+
+ {0xb1a9, 0x80, 0x5fb7, "Gnome Ranger, pt. 1 (Amiga/ST)"},
+ {0xab9d, 0x31, 0xbe6d, "Gnome Ranger, pt. 2 (Amiga/ST)"},
+ {0xae28, 0x87, 0xb6b6, "Gnome Ranger, pt. 3 (Amiga/ST)"},
+
+ {0xb0ec, 0xc2, 0x0053, "Gnome Ranger, pt. 1 (ST[v1])"},
+ {0xaf82, 0x83, 0x19f7, "Gnome Ranger, pt. 2 (ST[v1])"},
+
+ {0xb1aa, 0xad, 0xaf47, "Gnome Ranger, pt. 1 (PC)"},
+ {0xb19e, 0x92, 0x8f96, "Gnome Ranger, pt. 1 (ST[v2])"},
+ {0xab8b, 0xbf, 0x31e6, "Gnome Ranger, pt. 2 (PC/ST[v2])"},
+ {0xae16, 0x81, 0x8741, "Gnome Ranger, pt. 3 (PC/ST[v2])"},
+
+ {0xad41, 0xa8, 0x42c5, "Gnome Ranger, pt. 1 (Commodore 64 TO)"},
+ {0xa735, 0xf7, 0x2e08, "Gnome Ranger, pt. 2 (Commodore 64 TO)"},
+ {0xa9c0, 0x9e, 0x0d70, "Gnome Ranger, pt. 3 (Commodore 64 TO)"},
+
+ {0x908e, 0x0d, 0x58a7, "Gnome Ranger, pt. 1 (Commodore 64 Gfx)"},
+ {0x8f6f, 0x0a, 0x411a, "Gnome Ranger, pt. 2 (Commodore 64 Gfx)"},
+ {0x9060, 0xbb, 0xe75d, "Gnome Ranger, pt. 3 (Commodore 64 Gfx)"},
+
+ {0x8aab, 0xc0, 0xde5f, "Gnome Ranger, pt. 1 (Spectrum 48)"},
+ {0x8ac8, 0x9a, 0xc89b, "Gnome Ranger, pt. 2 (Spectrum 48)"},
+ {0x8a93, 0x4f, 0x10cc, "Gnome Ranger, pt. 3 (Spectrum 48)"},
+
+ {0xb57c, 0x44, 0x7779, "Lords of Time /T&M (PC)"},
+ {0xa69e, 0x6c, 0xb268, "Red Moon /T&M (PC)"},
+ {0xbac7, 0x7f, 0xddb2, "Price of Magik /T&M (PC)"},
+
+ {0xb579, 0x89, 0x3e89, "Lords of Time /T&M (ST)"},
+ {0xa698, 0x41, 0xcaca, "Red Moon /T&M (ST)"},
+ {0xbac4, 0x80, 0xa750, "Price of Magik /T&M (ST)"},
+
+ {0xb576, 0x2a, 0x7239, "Lords of Time /T&M (Amiga)"},
+ {0xa692, 0xd1, 0x6a99, "Red Moon /T&M (Amiga)"},
+ {0xbaca, 0x3a, 0x221b, "Price of Magik /T&M (Amiga)"},
+
+ {0xb563, 0x6a, 0x0c5c, "Lords of Time /T&M (Mac)"},
+ {0xa67c, 0xb8, 0xff41, "Red Moon /T&M (Mac)"},
+ {0xbab2, 0x87, 0x09f5, "Price of Magik /T&M (Mac)"},
+
+ {0xb38c, 0x37, 0x9f8e, "Lords of Time /T&M (Commodore 64 TO)"},
+ {0xa4e2, 0xa6, 0x016d, "Red Moon /T&M (Commodore 64 TO)"},
+ {0xb451, 0xa8, 0x2682, "Price of Magik /T&M (Commodore 64 TO)"},
+
+ {0x9070, 0x43, 0x45d4, "Lords of Time /T&M (Commodore 64 Gfx)"},
+ {0x903f, 0x6b, 0x603e, "Red Moon /T&M (Commodore 64 Gfx)"},
+ {0x8f51, 0xb2, 0x6c9a, "Price of Magik /T&M (Commodore 64 Gfx)"},
+
+ {0x8950, 0xa1, 0xbb16, "Lords of Time /T&M (Spectrum 48)"},
+ {0x8813, 0x11, 0x22de, "Red Moon /T&M (Spectrum 48)"},
+ {0x8a60, 0x2a, 0x29ed, "Price of Magik /T&M (Spectrum 48)"},
+
+ {0xb260, 0xe5, 0xc5b2, "Lords of Time /T&M (PC/ST *USA*)"},
+ {0xa3a4, 0xdf, 0x6732, "Red Moon /T&M (PC/ST *USA*)"},
+ {0xb7a0, 0x7e, 0x2226, "Price of Magik /T&M (PC/ST *USA*)"},
+
+ {0xb257, 0xf8, 0xfbd5, "Lords of Time /T&M (Amiga *USA*)"},
+ {0xa398, 0x82, 0xd031, "Red Moon /T&M (Amiga *USA*)"},
+ {0xb797, 0x1f, 0x84a9, "Price of Magik /T&M (Amiga *USA*)"},
+
+ {0x8d78, 0x3a, 0xba6e, "Lords of Time /T&M (Commodore 64 Gfx *USA*)"},
+ {0x8d56, 0xd3, 0x146a, "Red Moon /T&M (Commodore 64 Gfx *USA*)"},
+ {0x8c46, 0xf0, 0xcaf6, "Price of Magik /T&M (Commodore 64 Gfx *USA*)"},
+
+ {0xc0cf, 0x4e, 0xb7fa, "Lancelot, pt. 1 (Amiga/PC/ST)"},
+ {0xd5e9, 0x6a, 0x4192, "Lancelot, pt. 2 (Amiga/PC/ST)"},
+ {0xbb8f, 0x1a, 0x7487, "Lancelot, pt. 3 (Amiga/PC/ST)"},
+
+ {0xc0bd, 0x57, 0x6ef1, "Lancelot, pt. 1 (Mac)"},
+ {0xd5d7, 0x99, 0x770b, "Lancelot, pt. 2 (Mac)"},
+ {0xbb7d, 0x17, 0xbc42, "Lancelot, pt. 3 (Mac)"},
+
+ {0xb4c9, 0x94, 0xd784, "Lancelot, pt. 1 (Commodore 64 TO)"},
+ {0xb729, 0x51, 0x8ee5, "Lancelot, pt. 2 (Commodore 64 TO)"},
+ {0xb702, 0xe4, 0x1809, "Lancelot, pt. 3 (Commodore 64 TO)"},
+
+ {0x8feb, 0xba, 0xa800, "Lancelot, pt. 1 (Commodore 64 Gfx)"},
+ {0x8f6b, 0xfa, 0x0f7e, "Lancelot, pt. 2 (Commodore 64 Gfx)"},
+ {0x8f71, 0x2f, 0x0ddc, "Lancelot, pt. 3 (Commodore 64 Gfx)"},
+
+ {0x8ade, 0xf2, 0xfffb, "Lancelot, pt. 1 (Spectrum 48)"},
+ {0x8b0e, 0xfb, 0x0bab, "Lancelot, pt. 2 (Spectrum 48)"},
+ {0x8ab3, 0xc1, 0xcb62, "Lancelot, pt. 3 (Spectrum 48)"},
+
+ {0xbba4, 0x94, 0x0871, "Lancelot, pt. 1 (Amiga/PC *USA*)"},
+ {0xd0c0, 0x56, 0x8c48, "Lancelot, pt. 2 (Amiga/PC *USA*)"},
+ {0xb6ac, 0xc6, 0xaea0, "Lancelot, pt. 3 (Amiga/PC *USA*)"},
+
+ {0x8afc, 0x07, 0x8321, "Lancelot, pt. 1 (Commodore 64 Gfx *USA*)"},
+ {0x8aec, 0x13, 0x6791, "Lancelot, pt. 2 (Commodore 64 Gfx *USA*)"},
+ {0x8aba, 0x0d, 0x5602, "Lancelot, pt. 3 (Commodore 64 Gfx *USA*)"},
+
+ {0xd19b, 0xad, 0x306d, "Ingrid's Back, pt. 1 (PC)"},
+ {0xc5a5, 0xfe, 0x3c98, "Ingrid's Back, pt. 2 (PC)"},
+ {0xd7ae, 0x9e, 0x1878, "Ingrid's Back, pt. 3 (PC)"},
+
+ {0xd188, 0x13, 0xdc60, "Ingrid's Back, pt. 1 (Amiga)"},
+ {0xc594, 0x03, 0xea95, "Ingrid's Back, pt. 2 (Amiga)"},
+ {0xd79f, 0xb5, 0x1661, "Ingrid's Back, pt. 3 (Amiga)"},
+
+ {0xd183, 0x83, 0xef72, "Ingrid's Back, pt. 1 (ST)"},
+ {0xc58f, 0x65, 0xf337, "Ingrid's Back, pt. 2 (ST)"},
+ {0xd79a, 0x57, 0x49c5, "Ingrid's Back, pt. 3 (ST)"},
+
+ {0xb770, 0x03, 0x9a03, "Ingrid's Back, pt. 1 (Commodore 64 TO)"},
+ {0xb741, 0xb6, 0x2aa5, "Ingrid's Back, pt. 2 (Commodore 64 TO)"},
+ {0xb791, 0xa1, 0xd065, "Ingrid's Back, pt. 3 (Commodore 64 TO)"},
+
+ {0x9089, 0xce, 0xc5e2, "Ingrid's Back, pt. 1 (Commodore 64 Gfx)"},
+ {0x908d, 0x80, 0x30c7, "Ingrid's Back, pt. 2 (Commodore 64 Gfx)"},
+ {0x909e, 0x9f, 0xdecc, "Ingrid's Back, pt. 3 (Commodore 64 Gfx)"},
+
+ {0x8ab7, 0x68, 0xee57, "Ingrid's Back, pt. 1 (Spectrum 48)"},
+ {0x8b1e, 0x84, 0x2538, "Ingrid's Back, pt. 2 (Spectrum 48)"},
+ {0x8b1c, 0xa8, 0x9262, "Ingrid's Back, pt. 3 (Spectrum 48)"},
+
+ {0xbeab, 0x2d, 0x94d9, "Scapeghost, pt. 1 (Amiga)"},
+ {0xc132, 0x14, 0x7adc, "Scapeghost, pt. 1 (Amiga *bak*)"},
+ {0xbe94, 0xcc, 0x04b8, "Scapeghost, pt. 1 (PC/ST)"},
+ {0x99bd, 0x65, 0x032e, "Scapeghost, pt. 2 (Amiga/PC/ST)"},
+ {0xbcb6, 0x7a, 0x7d4f, "Scapeghost, pt. 3 (Amiga/PC/ST)"},
+
+ {0x9058, 0xcf, 0x9748, "Scapeghost, pt. 1 (Commodore 64 Gfx)"},
+ {0x8f43, 0xc9, 0xeefd, "Scapeghost, pt. 2 (Commodore 64 Gfx)"},
+ {0x90ac, 0x68, 0xb4a8, "Scapeghost, pt. 3 (Commodore 64 Gfx)"},
+
+ {0x8a21, 0xf4, 0xd9e4, "Scapeghost, pt. 1 (Spectrum 48)"},
+ {0x8a12, 0xe3, 0xc2ff, "Scapeghost, pt. 2 (Spectrum 48)"},
+ {0x8a16, 0xcc, 0x4f3b, "Scapeghost, pt. 3 (Spectrum 48)"},
+
+ {0x3ebb, 0x00, 0xf6dc, "Champion of the Raj (English) 1/2 GD (Amiga)"},
+ {0x0fd8, 0x00, 0xf250, "Champion of the Raj (English) 2/2 GD (Amiga)"},
+
+ {0x3e8f, 0x00, 0x5599, "Champion of the Raj (English) 1/2 GD (ST)"},
+
+ {0x3e4f, 0x00, 0xb202, "Champion of the Raj (English) 1/2 GD (PC)"},
+ {0x14a3, 0x00, 0xa288, "Champion of the Raj (English) 2/2 GD (PC)"},
+
+ {0x1929, 0x00, 0xd4b2, "Champion of the Raj (demo), 1/2 GD (ST)"},
+ {0x40e0, 0x02, 0x080d, "Champion of the Raj (demo), 2/2 GD (ST)"},
+
+ {0x4872, 0x00, 0x9515, "Champion of the Raj (German) 1/2 GD (Amiga)"},
+ {0x11f5, 0x00, 0xbf39, "Champion of the Raj (German) 2/2 GD (Amiga)"},
+
+ {0x4846, 0x00, 0xd9c1, "Champion of the Raj (German) 1/2 GD (ST)"},
+ {0x11f5, 0x00, 0x7aa4, "Champion of the Raj (German) 2/2 GD (ST)"},
+
+ {0x110f, 0x00, 0x4b57, "Champion of the Raj (French) 2/2 GD (ST)"},
+
+ {0x0000, 0x00, 0x0000, NULL}
+};
+
+
+/*
+ * The following patch database is obtained from L9cut's l9data_p.h, and
+ * allows CRCs from patched games to be translated into original CRCs for
+ * lookup in the game database above. Some file commentary has been removed
+ * for brevity, and unused patch fields deleted.
+ *
+ * The version of l9data_p.h used is 012 (22 May 2001).
+ */
+typedef const struct {
+ const gln_uint16 length; /* Datafile length in bytes */
+ const gln_byte orig_checksum; /* 8-bit checksum, last datafile byte */
+ const gln_uint16 orig_crc; /* 16-bit CRC, L9cut-internal */
+ const gln_byte patch_checksum; /* 8-bit checksum, last datafile byte */
+ const gln_uint16 patch_crc; /* 16-bit CRC, L9cut-internal */
+} gln_patch_table_t;
+typedef gln_patch_table_t *gln_patch_tableref_t;
+
+static gln_patch_table_t GLN_PATCH_TABLE[] = {
+ /* Price of Magik (Spectrum128) */
+ {0x7410, 0x5e, 0x60be, 0x70, 0x6cef},
+
+ /* Price of Magik (Commodore 64) */
+ {0x6fc6, 0x14, 0xf9b6, 0x26, 0x3326},
+
+ /* Price of Magik (Spectrum48) */
+ {0x5aa4, 0xc1, 0xeda4, 0xd3, 0xed35},
+ {0x5aa4, 0xc1, 0xeda4, 0xc1, 0x8a65},
+
+ /* Colossal Adventure /JoD (Amiga/PC) */
+ {0x76f4, 0x5e, 0x1fe5, 0xea, 0x1305},
+ {0x76f4, 0x5e, 0x1fe5, 0xb5, 0x901f},
+ {0x76f4, 0x5e, 0x1fe5, 0x5e, 0x6ea1},
+
+ /* Colossal Adventure /JoD (ST) */
+ {0x76f4, 0x5a, 0xcf4b, 0xe6, 0x012a},
+ {0x76f4, 0x5a, 0xcf4b, 0xb1, 0x40b1},
+
+ /* Adventure Quest /JoD (Amiga/PC) */
+ {0x6e60, 0x83, 0x18e0, 0x4c, 0xcfb0},
+ {0x6e60, 0x83, 0x18e0, 0xfa, 0x9b3b},
+ {0x6e60, 0x83, 0x18e0, 0x83, 0x303d},
+
+ /* Adventure Quest /JoD (ST) */
+ {0x6e5c, 0xf6, 0xd356, 0xbf, 0xede7},
+ {0x6e5c, 0xf6, 0xd356, 0x6d, 0x662d},
+
+ /* Dungeon Adventure /JoD (Amiga/PC/ST) */
+ {0x6f0c, 0x95, 0x1f64, 0x6d, 0x2443},
+ {0x6f0c, 0x95, 0x1f64, 0x0c, 0x6066},
+ {0x6f0c, 0x95, 0x1f64, 0x96, 0xdaca},
+ {0x6f0c, 0x95, 0x1f64, 0x95, 0x848d},
+
+ /* Colossal Adventure /JoD (Spectrum128) */
+ {0x6f6e, 0x78, 0x28cd, 0xf8, 0xda5f},
+ {0x6f6e, 0x78, 0x28cd, 0x77, 0x5b4e},
+
+ /* Adventure Quest /JoD (Spectrum128) */
+ {0x6970, 0xd6, 0xa820, 0x3b, 0x1870},
+ {0x6970, 0xd6, 0xa820, 0xd5, 0x13c4},
+
+ /* Dungeon Adventure /JoD (Spectrum128) */
+ {0x6de8, 0x4c, 0xd795, 0xa2, 0x3eea},
+ {0x6de8, 0x4c, 0xd795, 0x4b, 0xad30},
+
+ /* Colossal Adventure /JoD (Amstrad CPC) */
+ {0x6f4d, 0xcb, 0xe8f2, 0x4b, 0xb384},
+ {0x6f4d, 0xcb, 0xe8f2, 0xca, 0x96e7},
+
+ /* Adventure Quest /JoD (Amstrad CPC) */
+ {0x6968, 0x32, 0x0c01, 0x97, 0xdded},
+ {0x6968, 0x32, 0x0c01, 0x31, 0xe8c2},
+
+ /* Dungeon Adventure /JoD (Amstrad CPC) */
+ {0x6dc0, 0x63, 0x5d95, 0xb9, 0xc963},
+ {0x6dc0, 0x63, 0x5d95, 0x62, 0x79f7},
+
+ /* Colossal Adventure /JoD (Commodore 64) */
+ {0x6c8e, 0xb6, 0x9be3, 0x36, 0x6971},
+ {0x6c8e, 0xb6, 0x9be3, 0xb5, 0xeba0},
+
+ /* Adventure Quest /JoD (Commodore 64) */
+ {0x63b6, 0x2e, 0xef38, 0x93, 0x4e68},
+ {0x63b6, 0x2e, 0xef38, 0x2d, 0x54dc},
+
+ /* Dungeon Adventure /JoD (Commodore 64) */
+ {0x6bd2, 0x65, 0xa41f, 0xbb, 0x4260},
+ {0x6bd2, 0x65, 0xa41f, 0x64, 0xdf5a},
+
+ /* Colossal Adventure /JoD (Spectrum48) */
+ {0x5a8e, 0xf2, 0x7cca, 0x72, 0x8e58},
+ {0x5a8e, 0xf2, 0x7cca, 0xf1, 0x0c89},
+ {0x5a8e, 0xf2, 0x7cca, 0xf2, 0x2c96},
+
+ /* Adventure Quest /JoD (Spectrum48) */
+ {0x5ace, 0x11, 0xdc12, 0x76, 0x8663},
+ {0x5ace, 0x11, 0xdc12, 0x10, 0xa757},
+ {0x5ace, 0x11, 0xdc12, 0x11, 0xf118},
+
+ /* Dungeon Adventure /JoD (Spectrum48) */
+ {0x58a3, 0x38, 0x8ce4, 0x8e, 0xb61a},
+ {0x58a3, 0x38, 0x8ce4, 0x37, 0x34c0},
+ {0x58a3, 0x38, 0x8ce4, 0x38, 0xa1ee},
+
+ /* Snowball /SD (Amiga/ST) */
+ {0x7b31, 0x6e, 0x2e2b, 0xe5, 0x6017},
+
+ /* Return to Eden /SD (Amiga/ST) */
+ {0x7d16, 0xe6, 0x5438, 0x5d, 0xc770},
+
+ /* Worm in Paradise /SD (Amiga/ST) */
+ {0x7cd9, 0x0c, 0x4df1, 0x83, 0xe997},
+
+ /* Snowball /SD (PC/Spectrum128) */
+ {0x7b2f, 0x70, 0x6955, 0xe7, 0x0af4},
+ {0x7b2f, 0x70, 0x6955, 0x70, 0x1179},
+
+ /* Return to Eden /SD (PC) */
+ {0x7d14, 0xe8, 0xfbab, 0x5f, 0xeab9},
+ {0x7d14, 0xe8, 0xfbab, 0xe8, 0xe216},
+
+ /* Return to Eden /SD (Amstrad CPC) */
+ {0x7cff, 0xf8, 0x6044, 0x6f, 0xbb57},
+
+ /* Return to Eden /SD (Spectrum128) */
+ {0x7c55, 0x18, 0xdaee, 0x8f, 0x01fd},
+
+ /* Worm in Paradise /SD (Amstrad CPC/PC/Spectrum128) */
+ {0x7cd7, 0x0e, 0x4feb, 0x85, 0x4eae},
+ {0x7cd7, 0x0e, 0x4feb, 0x0e, 0xb02c},
+
+ /* Snowball /SD (Commodore 64) */
+ {0x7363, 0x65, 0xa0ab, 0xdc, 0xca6a},
+
+ /* Return to Eden /SD (Commodore 64) */
+ {0x772f, 0xca, 0x8602, 0x41, 0x9bd0},
+
+ /* Worm in Paradise /SD (Commodore 64) */
+ {0x788d, 0x72, 0x888a, 0xe9, 0x4cce},
+
+ /* Snowball /SD (Atari) */
+ {0x6bf8, 0x3f, 0xc9f7, 0x96, 0x1908},
+
+ /* Return to Eden /SD (Atari) */
+ {0x60f7, 0x68, 0xc2bc, 0xdf, 0xd3ae},
+
+ /* Worm in Paradise /SD (Atari) */
+ {0x6161, 0xf3, 0xe6d7, 0x6a, 0xe232},
+
+ /* Snowball /SD (Spectrum48) */
+ {0x6541, 0x02, 0x2e6c, 0x79, 0xb80c},
+ {0x6541, 0x02, 0x2e6c, 0x02, 0x028a},
+
+ /* Return to Eden /SD (Spectrum48) */
+ {0x5f43, 0xca, 0x828c, 0x41, 0x9f5e},
+ {0x5f43, 0xca, 0x828c, 0xca, 0x6e1b},
+
+ /* Worm in Paradise /SD (Spectrum48) */
+ {0x5ebb, 0xf1, 0x4dec, 0x68, 0x4909},
+ {0x5ebb, 0xf1, 0x4dec, 0xf1, 0xcc1a},
+
+ /* Knight Orc, pt. 1 (Amiga) */
+ {0xbb93, 0x36, 0x6a05, 0xad, 0xe52d},
+
+ /* Knight Orc, pt. 1 (ST) */
+ {0xbb6e, 0xad, 0x4d40, 0x24, 0x3bcd},
+
+ /* Knight Orc, pt. 2 (Amiga/ST) */
+ {0xc58e, 0x4a, 0x4e9d, 0xc1, 0xe2bf},
+
+ /* Knight Orc, pt. 3 (Amiga/ST) */
+ {0xcb9a, 0x0f, 0x0804, 0x86, 0x6487},
+
+ /* Knight Orc, pt. 1 (PC) */
+ {0xbb6e, 0xa6, 0x9753, 0x1d, 0x2e7f},
+ {0xbb6e, 0xa6, 0x9753, 0xa6, 0x001d},
+
+ /* Knight Orc, pt. 2 (PC) */
+ {0xc58e, 0x43, 0xe9ce, 0xba, 0x5e4c},
+ {0xc58e, 0x43, 0xe9ce, 0x43, 0xa8f0},
+
+ /* Knight Orc, pt. 3 (PC) */
+ {0xcb9a, 0x08, 0x6c36, 0x7f, 0xf0d4},
+ {0xcb9a, 0x08, 0x6c36, 0x08, 0x2d08},
+
+ /* Knight Orc, pt. 1 (Commodore 64 Gfx) */
+ {0x8970, 0x6b, 0x3c7b, 0xe2, 0xb6f3},
+
+ /* Knight Orc, pt. 1 (Spectrum48) */
+ {0x86d0, 0xb7, 0xadbd, 0x2e, 0x43e1},
+
+ /* Gnome Ranger, pt. 1 (Amiga/ST) */
+ {0xb1a9, 0x80, 0x5fb7, 0xf7, 0x5c6c},
+
+ /* Gnome Ranger, pt. 2 (Amiga/ST) */
+ {0xab9d, 0x31, 0xbe6d, 0xa8, 0xcb96},
+
+ /* Gnome Ranger, pt. 3 (Amiga/ST) */
+ {0xae28, 0x87, 0xb6b6, 0xfe, 0x760c},
+
+ /* Gnome Ranger, pt. 1 (PC) */
+ {0xb1aa, 0xad, 0xaf47, 0x24, 0x5cfd},
+ {0xb1aa, 0xad, 0xaf47, 0xad, 0xe0ed},
+
+ /* Gnome Ranger, pt. 1 (ST-var) */
+ {0xb19e, 0x92, 0x8f96, 0x09, 0x798c},
+
+ /* Gnome Ranger, pt. 2 (PC/ST-var) */
+ {0xab8b, 0xbf, 0x31e6, 0x36, 0x811c},
+ {0xab8b, 0xbf, 0x31e6, 0xbf, 0x8ff3},
+
+ /* Gnome Ranger, pt. 3 (PC/ST-var) */
+ {0xae16, 0x81, 0x8741, 0xf8, 0x47fb},
+ {0xae16, 0x81, 0x8741, 0x81, 0xc8eb},
+
+ /* Gnome Ranger, pt. 1 (Commodore 64 TO) */
+ {0xad41, 0xa8, 0x42c5, 0x1f, 0x7d1e},
+
+ /* Gnome Ranger, pt. 2 (Commodore 64 TO) */
+ {0xa735, 0xf7, 0x2e08, 0x6e, 0x780e},
+
+ /* Gnome Ranger, pt. 3 (Commodore 64 TO) */
+ {0xa9c0, 0x9e, 0x0d70, 0x15, 0x3e6b},
+
+ /* Gnome Ranger, pt. 1 (Commodore 64 Gfx) */
+ {0x908e, 0x0d, 0x58a7, 0x84, 0xab1d},
+
+ /* Gnome Ranger, pt. 2 (Commodore 64 Gfx) */
+ {0x8f6f, 0x0a, 0x411a, 0x81, 0x12bc},
+
+ /* Gnome Ranger, pt. 3 (Commodore 64 Gfx) */
+ {0x9060, 0xbb, 0xe75d, 0x32, 0x14e7},
+
+ /* Lords of Time /T&M (PC) */
+ {0xb57c, 0x44, 0x7779, 0xbb, 0x31a6},
+ {0xb57c, 0x44, 0x7779, 0x44, 0xea72},
+
+ /* Red Moon /T&M (PC) */
+ {0xa69e, 0x6c, 0xb268, 0xe3, 0x4cef},
+ {0xa69e, 0x6c, 0xb268, 0x6c, 0x3799},
+
+ /* Price of Magik /T&M (PC) */
+ {0xbac7, 0x7f, 0xddb2, 0xf6, 0x6ab3},
+ {0xbac7, 0x7f, 0xddb2, 0x7f, 0x905c},
+
+ /* Lords of Time /T&M (ST) */
+ {0xb579, 0x89, 0x3e89, 0x00, 0xa2b7},
+
+ /* Red Moon /T&M (ST) */
+ {0xa698, 0x41, 0xcaca, 0xb8, 0xeeac},
+
+ /* Price of Magik /T&M (ST) */
+ {0xbac4, 0x80, 0xa750, 0xf7, 0xe030},
+
+ /* Lords of Time /T&M (Amiga) */
+ {0xb576, 0x2a, 0x7239, 0xa1, 0x2ea6},
+
+ /* Red Moon /T&M (Amiga) */
+ {0xa692, 0xd1, 0x6a99, 0x48, 0x50ff},
+
+ /* Price of Magik /T&M (Amiga) */
+ {0xbaca, 0x3a, 0x221b, 0xb1, 0x55bb},
+
+ /* Lords of Time /T&M (Commodore 64 TO) */
+ {0xb38c, 0x37, 0x9f8e, 0xae, 0xc6b1},
+
+ /* Red Moon /T&M (Commodore 64 TO) */
+ {0xa4e2, 0xa6, 0x016d, 0x1d, 0x31ab},
+
+ /* Price of Magik /T&M (Commodore 64 TO) */
+ {0xb451, 0xa8, 0x2682, 0x1f, 0x5de2},
+
+ /* Lords of Time /T&M (Commodore 64 Gfx) */
+ {0x9070, 0x43, 0x45d4, 0xba, 0x02eb},
+
+ /* Red Moon /T&M (Commodore 64 Gfx) */
+ {0x903f, 0x6b, 0x603e, 0xe2, 0x9f59},
+
+ /* Price of Magik /T&M (Commodore 64 Gfx) */
+ {0x8f51, 0xb2, 0x6c9a, 0x29, 0xde3b},
+
+ /* Lords of Time /T&M (Spectrum48) */
+ {0x8950, 0xa1, 0xbb16, 0x18, 0x2828},
+ {0x8950, 0xa1, 0xbb16, 0xa1, 0x1ea2},
+
+ /* Red Moon /T&M (Spectrum48) */
+ {0x8813, 0x11, 0x22de, 0x88, 0x18b8},
+ {0x8813, 0x11, 0x22de, 0x11, 0xd0cd},
+
+ /* Price of Magik /T&M (Spectrum48) */
+ {0x8a60, 0x2a, 0x29ed, 0xa1, 0x5e4d},
+
+ /* Lancelot, pt. 1 (Amiga/PC/ST) */
+ {0xc0cf, 0x4e, 0xb7fa, 0xc5, 0x4400},
+
+ /* Lancelot, pt. 2 (Amiga/PC/ST) */
+ {0xd5e9, 0x6a, 0x4192, 0xe1, 0x3b1e},
+
+ /* Lancelot, pt. 3 (Amiga/PC/ST) */
+ {0xbb8f, 0x1a, 0x7487, 0x91, 0x877d},
+
+ /* Lancelot, pt. 1 (Commodore 64 TO) */
+ {0xb4c9, 0x94, 0xd784, 0x0b, 0x203e},
+
+ /* Lancelot, pt. 2 (Commodore 64 TO) */
+ {0xb729, 0x51, 0x8ee5, 0xc8, 0xf1c9},
+
+ /* Lancelot, pt. 3 (Commodore 64 TO) */
+ {0xb702, 0xe4, 0x1809, 0x5b, 0x25b2},
+
+ /* Lancelot, pt. 1 (Commodore 64 Gfx) */
+ {0x8feb, 0xba, 0xa800, 0x31, 0x5bfa},
+
+ /* Lancelot, pt. 2 (Commodore 64 Gfx) */
+ {0x8f6b, 0xfa, 0x0f7e, 0x71, 0x75f2},
+
+ /* Lancelot, pt. 3 (Commodore 64 Gfx) */
+ {0x8f71, 0x2f, 0x0ddc, 0xa6, 0x3e87},
+
+ /* Ingrid's Back, pt. 1 (PC) */
+ {0xd19b, 0xad, 0x306d, 0x24, 0x4504},
+ {0xd19b, 0xad, 0x306d, 0xad, 0x878e},
+
+ /* Ingrid's Back, pt. 2 (PC) */
+ {0xc5a5, 0xfe, 0x3c98, 0x75, 0x8950},
+ {0xc5a5, 0xfe, 0x3c98, 0xfe, 0x8b7b},
+
+ /* Ingrid's Back, pt. 3 (PC) */
+ {0xd7ae, 0x9e, 0x1878, 0x15, 0xadb0},
+ {0xd7ae, 0x9e, 0x1878, 0x9e, 0xaf9b},
+
+ /* Ingrid's Back, pt. 1 (Amiga) */
+ {0xd188, 0x13, 0xdc60, 0x8a, 0x755c},
+
+ /* Ingrid's Back, pt. 2 (Amiga) */
+ {0xc594, 0x03, 0xea95, 0x7a, 0xb5a8},
+
+ /* Ingrid's Back, pt. 3 (Amiga) */
+ {0xd79f, 0xb5, 0x1661, 0x2c, 0xbf5d},
+
+ /* Ingrid's Back, pt. 1 (ST) */
+ {0xd183, 0x83, 0xef72, 0xfa, 0xb04f},
+
+ /* Ingrid's Back, pt. 2 (ST) */
+ {0xc58f, 0x65, 0xf337, 0xdc, 0x900a},
+
+ /* Ingrid's Back, pt. 3 (ST) */
+ {0xd79a, 0x57, 0x49c5, 0xce, 0xe0f9},
+
+ /* Ingrid's Back, pt. 1 (Commodore 64 TO) */
+ {0xb770, 0x03, 0x9a03, 0x7a, 0xdc6a},
+
+ /* Ingrid's Back, pt. 2 (Commodore 64 TO) */
+ {0xb741, 0xb6, 0x2aa5, 0x2d, 0x5a6c},
+
+ /* Ingrid's Back, pt. 3 (Commodore 64 TO) */
+ {0xb791, 0xa1, 0xd065, 0x18, 0xaa0c},
+
+ /* Ingrid's Back, pt. 1 (Commodore 64 Gfx) */
+ {0x9089, 0xce, 0xc5e2, 0x44, 0xeff4},
+
+ /* Ingrid's Back, pt. 2 (Commodore 64 Gfx) */
+ {0x908d, 0x80, 0x30c7, 0xf6, 0x2a11},
+
+ /* Ingrid's Back, pt. 3 (Commodore 64 Gfx) */
+ {0x909e, 0x9f, 0xdecc, 0x15, 0xf4da},
+
+ {0x0000, 0x00, 0x0000, 0x00, 0x0000},
+};
+
+
+/*
+ * gln_gameid_lookup_game()
+ * gln_gameid_lookup_patch()
+ *
+ * Look up and return game table and patch table entries given a game's
+ * length, checksum, and CRC. Returns the entry, or NULL if not found.
+ */
+static gln_game_tableref_t
+gln_gameid_lookup_game(gln_uint16 length, gln_byte checksum,
+ gln_uint16 crc, int ignore_crc) {
+ gln_game_tableref_t game;
+
+ for (game = GLN_GAME_TABLE; game->length; game++) {
+ if (game->length == length && game->checksum == checksum
+ && (ignore_crc || game->crc == crc))
+ break;
+ }
+
+ return game->length ? game : NULL;
+}
+
+static gln_patch_tableref_t
+gln_gameid_lookup_patch(gln_uint16 length, gln_byte checksum,
+ gln_uint16 crc) {
+ gln_patch_tableref_t patch;
+
+ for (patch = GLN_PATCH_TABLE; patch->length; patch++) {
+ if (patch->length == length && patch->patch_checksum == checksum
+ && patch->patch_crc == crc)
+ break;
+ }
+
+ return patch->length ? patch : NULL;
+}
+
+
+/*
+ * gln_gameid_identify_game()
+ *
+ * Identify a game from its data length, checksum, and CRC. Returns the
+ * entry of the game in the game table, or NULL if not found.
+ *
+ * This function uses startdata and FileSize from the core interpreter.
+ * These aren't advertised symbols, so be warned.
+ */
+static gln_game_tableref_t
+gln_gameid_identify_game(void) {
+ gln_uint16 length, crc;
+ gln_byte checksum;
+ int is_version2;
+ gln_game_tableref_t game;
+ gln_patch_tableref_t patch;
+
+ /* If the data file appears too short for a header, give up now. */
+ if (FileSize < 30)
+ return NULL;
+
+ /*
+ * Find the version of the game, and the length of game data. This logic
+ * is taken from L9cut, with calcword() replaced by simple byte comparisons.
+ * If the length exceeds the available data, fail.
+ */
+ assert(startdata);
+ is_version2 = startdata[4] == 0x20 && startdata[5] == 0x00
+ && startdata[10] == 0x00 && startdata[11] == 0x80
+ && startdata[20] == startdata[22]
+ && startdata[21] == startdata[23];
+
+ length = is_version2
+ ? startdata[28] | startdata[29] << BITS_PER_CHAR
+ : startdata[0] | startdata[1] << BITS_PER_CHAR;
+ if (length >= FileSize)
+ return NULL;
+
+ /* Calculate or retrieve the checksum, in a version specific way. */
+ if (is_version2) {
+ int index;
+
+ checksum = 0;
+ for (index = 0; index < length + 1; index++)
+ checksum += startdata[index];
+ } else
+ checksum = startdata[length];
+
+ /*
+ * Generate a CRC for this data. When L9cut calculates a CRC, it's using a
+ * copy taken up to length + 1 and then padded with two NUL bytes, so we
+ * mimic that here.
+ */
+ crc = gln_get_buffer_crc(startdata, length + 1, 2);
+
+ /*
+ * See if this is a patched file. If it is, look up the game based on the
+ * original CRC and checksum. If not, use the current CRC and checksum.
+ */
+ patch = gln_gameid_lookup_patch(length, checksum, crc);
+ game = gln_gameid_lookup_game(length,
+ patch ? patch->orig_checksum : checksum,
+ patch ? patch->orig_crc : crc,
+ FALSE);
+
+ /* If no game identified, retry without the CRC. This is guesswork. */
+ if (!game)
+ game = gln_gameid_lookup_game(length, checksum, crc, TRUE);
+
+ return game;
+}
+
+
+/*
+ * gln_gameid_get_game_name()
+ *
+ * Return the name of the game, or NULL if not identifiable.
+ *
+ * This function uses startdata from the core interpreter. This isn't an
+ * advertised symbol, so be warned.
+ */
+static const char *
+gln_gameid_get_game_name(void) {
+ /*
+ * If no game name yet known, attempt to identify the game. If it can't
+ * be identified, set the cached game name to "" -- this special value
+ * indicates that the game is an unknown one, but suppresses repeated
+ * attempts to identify it on successive calls.
+ */
+ if (!gln_gameid_game_name) {
+ gln_game_tableref_t game;
+
+ /*
+ * If the interpreter hasn't yet loaded a game, startdata is NULL
+ * (uninitialized, global). In this case, we return NULL, allowing
+ * for retries until a game is loaded.
+ */
+ if (!startdata)
+ return NULL;
+
+ game = gln_gameid_identify_game();
+ gln_gameid_game_name = game ? game->name : "";
+ }
+
+ /* Return the game's name, or NULL if it was unidentifiable. */
+ assert(gln_gameid_game_name);
+ return strlen(gln_gameid_game_name) > 0 ? gln_gameid_game_name : NULL;
+}
+
+
+/*
+ * gln_gameid_game_name_reset()
+ *
+ * Clear the saved game name, forcing a new lookup when next queried. This
+ * function should be called by actions that may cause the interpreter to
+ * change game file, for example os_set_filenumber().
+ */
+static void
+gln_gameid_game_name_reset(void) {
+ gln_gameid_game_name = NULL;
+}
+
+
+/*---------------------------------------------------------------------*/
+/* Glk port bitmap picture functions */
+/*---------------------------------------------------------------------*/
+
+/* R,G,B color triple definition. */
+typedef struct {
+ int red, green, blue;
+} gln_rgb_t;
+typedef gln_rgb_t *gln_rgbref_t;
+
+/*
+ * Maximum number of regions to consider in a single repaint pass. A
+ * couple of hundred seems to strike the right balance between not too
+ * sluggardly picture updates, and responsiveness to input during graphics
+ * rendering, when combined with short timeouts.
+ */
+static const int GLN_REPAINT_LIMIT = 256;
+
+/*
+ * Graphics timeout; we like an update call after this period (ms). In
+ * practice, this timeout may actually be shorter than the time taken
+ * to reach the limit on repaint regions, but because Glk guarantees that
+ * user interactions (in this case, line events) take precedence over
+ * timeouts, this should be okay; we'll still see a game that responds to
+ * input each time the background repaint function yields.
+ *
+ * Setting this value is tricky. We'd like it to be the shortest possible
+ * consistent with getting other stuff done, say 10ms. However, Xglk has
+ * a granularity of 50ms on checking for timeouts, as it uses a 1/20s
+ * timeout on X select. This means that the shortest timeout we'll ever
+ * get from Xglk will be 50ms, so there's no point in setting this shorter
+ * than that. With luck, other Glk libraries will be more efficient than
+ * this, and can give us higher timer resolution; we'll set 50ms here, and
+ * hope that no other Glk library is worse.
+ */
+static const glui32 GLN_GRAPHICS_TIMEOUT = 50;
+
+/*
+ * Count of timeouts to wait on. Waiting after a repaint smooths the
+ * display where the frame is being resized, by helping to avoid graphics
+ * output while more resize events are received; around 1/2 second seems
+ * okay.
+ */
+static const int GLN_GRAPHICS_REPAINT_WAIT = 10;
+
+/* Pixel size multiplier for image size scaling. */
+static const int GLN_GRAPHICS_PIXEL = 1;
+
+/* Proportion of the display to use for graphics. */
+static const glui32 GLN_GRAPHICS_PROPORTION = 30;
+
+/*
+ * Special title picture number, requiring its own handling, and count of
+ * timeouts to wait on after fully rendering the title picture (~2 seconds).
+ */
+static const int GLN_GRAPHICS_TITLE_PICTURE = 0,
+ GLN_GRAPHICS_TITLE_WAIT = 40;
+
+/*
+ * Border and shading control. For cases where we can't detect the back-
+ * ground color of the main window, there's a default, white, background.
+ * Bordering is black, with a 1 pixel border, 2 pixel shading, and 8 steps
+ * of shading fade.
+ */
+static const glui32 GLN_GRAPHICS_DEFAULT_BACKGROUND = 0x00ffffff,
+ GLN_GRAPHICS_BORDER_COLOR = 0x00000000;
+static const int GLN_GRAPHICS_BORDER = 1,
+ GLN_GRAPHICS_SHADING = 2,
+ GLN_GRAPHICS_SHADE_STEPS = 8;
+
+/*
+ * Guaranteed unused pixel value. This value is used to fill the on-screen
+ * buffer on new pictures or repaints, resulting in a full paint of all
+ * pixels since no off-screen, real picture, pixel will match it.
+ */
+static const int GLN_GRAPHICS_UNUSED_PIXEL = 0xff;
+
+/* Graphics file directory, and type of graphics found in it. */
+static char *gln_graphics_bitmap_directory = NULL;
+static BitmapType gln_graphics_bitmap_type = NO_BITMAPS;
+
+/* The current picture id being displayed. */
+enum { GLN_PALETTE_SIZE = 32 };
+static gln_byte *gln_graphics_bitmap = NULL;
+static gln_uint16 gln_graphics_width = 0,
+ gln_graphics_height = 0;
+static Colour gln_graphics_palette[GLN_PALETTE_SIZE]; /* = { 0, ... }; */
+static int gln_graphics_picture = -1;
+
+/*
+ * Flags set on new picture, and on resize or arrange events, and a flag
+ * to indicate whether background repaint is stopped or active.
+ */
+static int gln_graphics_new_picture = FALSE,
+ gln_graphics_repaint = FALSE,
+ gln_graphics_active = FALSE;
+
+/*
+ * State to monitor the state of interpreter graphics. The values of the
+ * enumerations match the modes supplied by os_graphics().
+ */
+enum GraphicsState {
+ GLN_GRAPHICS_OFF = 0,
+ GLN_GRAPHICS_LINE_MODE = 1,
+ GLN_GRAPHICS_BITMAP_MODE = 2
+};
+static GraphicsState gln_graphics_interpreter_state = GLN_GRAPHICS_OFF;
+
+
+/*
+ * Pointer to the two graphics buffers, one the off-screen representation
+ * of pixels, and the other tracking on-screen data. These are temporary
+ * graphics malloc'ed memory, and should be free'd on exit.
+ */
+static gln_byte *gln_graphics_off_screen = NULL,
+ *gln_graphics_on_screen = NULL;
+
+/*
+ * The number of colors used in the palette by the current picture. Because
+ * of the way it's queried, we risk a race, with admittedly a very low
+ * probability, with the updater. So, it's initialized instead to the
+ * largest possible value. The real value in use is inserted on the first
+ * picture update timeout call for a new picture.
+ */
+static int gln_graphics_color_count = GLN_PALETTE_SIZE;
+
+
+/*
+ * gln_graphics_open()
+ *
+ * If it's not open, open the graphics window. Returns TRUE if graphics
+ * was successfully started, or already on.
+ */
+static int gln_graphics_open(void) {
+ if (!gln_graphics_window) {
+ gln_graphics_window = g_vm->glk_window_open(gln_main_window,
+ winmethod_Above
+ | winmethod_Proportional,
+ GLN_GRAPHICS_PROPORTION,
+ wintype_Graphics, 0);
+ }
+
+ return gln_graphics_window != NULL;
+}
+
+
+/*
+ * gln_graphics_close()
+ *
+ * If open, close the graphics window and set back to NULL.
+ */
+static void gln_graphics_close(void) {
+ if (gln_graphics_window) {
+ g_vm->glk_window_close(gln_graphics_window, NULL);
+ gln_graphics_window = NULL;
+ }
+}
+
+
+/*
+ * gln_graphics_start()
+ *
+ * If graphics enabled, start any background picture update processing.
+ */
+static void gln_graphics_start(void) {
+ if (gln_graphics_enabled) {
+ /* If not running, start the updating "thread". */
+ if (!gln_graphics_active) {
+ g_vm->glk_request_timer_events(GLN_GRAPHICS_TIMEOUT);
+ gln_graphics_active = TRUE;
+ }
+ }
+}
+
+
+/*
+ * gln_graphics_stop()
+ *
+ * Stop any background picture update processing.
+ */
+static void gln_graphics_stop(void) {
+ /* If running, stop the updating "thread". */
+ if (gln_graphics_active) {
+ g_vm->glk_request_timer_events(0);
+ gln_graphics_active = FALSE;
+ }
+}
+
+
+/*
+ * gln_graphics_are_displayed()
+ *
+ * Return TRUE if graphics are currently being displayed, FALSE otherwise.
+ */
+static int gln_graphics_are_displayed(void) {
+ return gln_graphics_window != NULL;
+}
+
+
+/*
+ * gln_graphics_paint()
+ *
+ * Set up a complete repaint of the current picture in the graphics window.
+ * This function should be called on the appropriate Glk window resize and
+ * arrange events.
+ */
+static void gln_graphics_paint(void) {
+ if (gln_graphics_enabled && gln_graphics_are_displayed()) {
+ /* Set the repaint flag, and start graphics. */
+ gln_graphics_repaint = TRUE;
+ gln_graphics_start();
+ }
+}
+
+
+/*
+ * gln_graphics_restart()
+ *
+ * Restart graphics as if the current picture is a new picture. This
+ * function should be called whenever graphics is re-enabled after being
+ * disabled.
+ */
+static void gln_graphics_restart(void) {
+ if (gln_graphics_enabled && gln_graphics_are_displayed()) {
+ /* Set the new picture flag, and start graphics. */
+ gln_graphics_new_picture = TRUE;
+ gln_graphics_start();
+ }
+}
+
+
+/*
+ * gln_graphics_count_colors()
+ *
+ * Analyze an image, and return an overall count of how many colors out of
+ * the palette are used.
+ */
+static int gln_graphics_count_colors(gln_byte bitmap[],
+ gln_uint16 width, gln_uint16 height) {
+ int x, y, count;
+ long usage[GLN_PALETTE_SIZE], index_row;
+ assert(bitmap);
+
+ /*
+ * Traverse the image, counting each pixel usage. For the y iterator,
+ * maintain an index row as an optimization to avoid multiplications in
+ * the loop.
+ */
+ count = 0;
+ memset(usage, 0, sizeof(usage));
+ for (y = 0, index_row = 0; y < height; y++, index_row += width) {
+ for (x = 0; x < width; x++) {
+ long index;
+
+ /* Get the pixel index, and update the count for this color. */
+ index = index_row + x;
+ usage[bitmap[index]]++;
+
+ /* If color usage is now 1, note new color encountered. */
+ if (usage[bitmap[index]] == 1)
+ count++;
+ }
+ }
+
+ return count;
+}
+
+
+/*
+ * gln_graphics_split_color()
+ * gln_graphics_combine_color()
+ *
+ * General graphics helper functions, to convert between RGB and Glk glui32
+ * color representations.
+ */
+static void gln_graphics_split_color(glui32 color, gln_rgbref_t rgb_color) {
+ assert(rgb_color);
+
+ rgb_color->red = (color >> 16) & 0xff;
+ rgb_color->green = (color >> 8) & 0xff;
+ rgb_color->blue = color & 0xff;
+}
+
+static glui32 gln_graphics_combine_color(gln_rgbref_t rgb_color) {
+ glui32 color;
+ assert(rgb_color);
+
+ color = (rgb_color->red << 16) | (rgb_color->green << 8) | rgb_color->blue;
+ return color;
+}
+
+
+/*
+ * gln_graphics_clear_and_border()
+ *
+ * Clear the graphics window, and border and shade the area where the
+ * picture is going to be rendered. This attempts a small raised effect
+ * for the picture, in keeping with modern trends.
+ */
+static void gln_graphics_clear_and_border(winid_t glk_window,
+ int x_offset, int y_offset,
+ int pixel_size, gln_uint16 width,
+ gln_uint16 height) {
+ uint background;
+ glui32 fade_color, shading_color;
+ gln_rgb_t rgb_background, rgb_border, rgb_fade;
+ int index;
+ assert(glk_window);
+
+ /*
+ * Try to detect the background color of the main window, by getting the
+ * background for Normal style (Glk offers no way to directly get a window's
+ * background color). If we can get it, we'll match the graphics window
+ * background to it. If we can't, we'll default the color to white.
+ */
+ if (!g_vm->glk_style_measure(gln_main_window,
+ style_Normal, stylehint_BackColor, &background)) {
+ /*
+ * Unable to get the main window background, so assume, and default
+ * graphics to white.
+ */
+ background = GLN_GRAPHICS_DEFAULT_BACKGROUND;
+ }
+
+ /*
+ * Set the graphics window background to match the main window background,
+ * as best as we can tell, and clear the window.
+ */
+ g_vm->glk_window_set_background_color(glk_window, background);
+ g_vm->glk_window_clear(glk_window);
+#ifndef GARGLK
+ /*
+ * For very small pictures, just border them, but don't try and
+ * do any shading. Failing this check is probably highly unlikely.
+ */
+ if (width < 2 * GLN_GRAPHICS_SHADE_STEPS
+ || height < 2 * GLN_GRAPHICS_SHADE_STEPS) {
+ /* Paint a rectangle bigger than the picture by border pixels. */
+ g_vm->glk_window_fill_rect(glk_window,
+ GLN_GRAPHICS_BORDER_COLOR,
+ x_offset - GLN_GRAPHICS_BORDER,
+ y_offset - GLN_GRAPHICS_BORDER,
+ width * pixel_size + GLN_GRAPHICS_BORDER * 2,
+ height * pixel_size + GLN_GRAPHICS_BORDER * 2);
+ return;
+ }
+#endif
+ /*
+ * Paint a rectangle bigger than the picture by border pixels all round,
+ * and with additional shading pixels right and below. Some of these
+ * shading pixels are later overwritten by the fading loop below. The
+ * picture will sit over this rectangle.
+ */
+ g_vm->glk_window_fill_rect(glk_window,
+ GLN_GRAPHICS_BORDER_COLOR,
+ x_offset - GLN_GRAPHICS_BORDER,
+ y_offset - GLN_GRAPHICS_BORDER,
+ width * pixel_size + GLN_GRAPHICS_BORDER * 2
+ + GLN_GRAPHICS_SHADING,
+ height * pixel_size + GLN_GRAPHICS_BORDER * 2
+ + GLN_GRAPHICS_SHADING);
+
+ /*
+ * Split the main window background color and the border color into
+ * components.
+ */
+ gln_graphics_split_color(background, &rgb_background);
+ gln_graphics_split_color(GLN_GRAPHICS_BORDER_COLOR, &rgb_border);
+
+ /*
+ * Generate the incremental color to use in fade steps. Here we're
+ * assuming that the border is always darker than the main window
+ * background (currently valid, as we're using black).
+ */
+ rgb_fade.red = (rgb_background.red - rgb_border.red)
+ / GLN_GRAPHICS_SHADE_STEPS;
+ rgb_fade.green = (rgb_background.green - rgb_border.green)
+ / GLN_GRAPHICS_SHADE_STEPS;
+ rgb_fade.blue = (rgb_background.blue - rgb_border.blue)
+ / GLN_GRAPHICS_SHADE_STEPS;
+
+ /* Combine RGB fade into a single incremental Glk color. */
+ fade_color = gln_graphics_combine_color(&rgb_fade);
+
+ /* Fade in edge, from background to border, shading in stages. */
+ shading_color = background;
+ for (index = 0; index < GLN_GRAPHICS_SHADE_STEPS; index++) {
+ /* Shade the two border areas with this color. */
+ g_vm->glk_window_fill_rect(glk_window, shading_color,
+ x_offset + width * pixel_size
+ + GLN_GRAPHICS_BORDER,
+ y_offset + index - GLN_GRAPHICS_BORDER,
+ GLN_GRAPHICS_SHADING, 1);
+ g_vm->glk_window_fill_rect(glk_window, shading_color,
+ x_offset + index - GLN_GRAPHICS_BORDER,
+ y_offset + height * pixel_size
+ + GLN_GRAPHICS_BORDER,
+ 1, GLN_GRAPHICS_SHADING);
+
+ /* Update the shading color for the fade next iteration. */
+ shading_color -= fade_color;
+ }
+}
+
+
+/*
+ * gln_graphics_convert_palette()
+ *
+ * Convert a Level 9 bitmap color palette to a Glk one.
+ */
+static void gln_graphics_convert_palette(Colour ln_palette[], glui32 glk_palette[]) {
+ int index;
+ assert(ln_palette && glk_palette);
+
+ for (index = 0; index < GLN_PALETTE_SIZE; index++) {
+ Colour colour;
+ gln_rgb_t gln_color;
+
+ /* Convert color from Level 9 to internal RGB, then to Glk color. */
+ colour = ln_palette[index];
+ gln_color.red = colour.red;
+ gln_color.green = colour.green;
+ gln_color.blue = colour.blue;
+ glk_palette[index] = gln_graphics_combine_color(&gln_color);
+ }
+}
+
+
+/*
+ * gln_graphics_position_picture()
+ *
+ * Given a picture width and height, return the x and y offsets to center
+ * this picture in the current graphics window.
+ */
+static void gln_graphics_position_picture(winid_t glk_window, int pixel_size,
+ gln_uint16 width, gln_uint16 height,
+ int *x_offset, int *y_offset) {
+ uint window_width, window_height;
+ assert(glk_window && x_offset && y_offset);
+
+ /* Measure the current graphics window dimensions. */
+ g_vm->glk_window_get_size(glk_window, &window_width, &window_height);
+
+ /*
+ * Calculate and return an x and y offset to use on point plotting, so that
+ * the image centers inside the graphical window.
+ */
+ *x_offset = ((int) window_width - width * pixel_size) / 2;
+ *y_offset = ((int) window_height - height * pixel_size) / 2;
+}
+
+
+/*
+ * gln_graphics_is_vertex()
+ *
+ * Given a point, return TRUE if that point is the vertex of a fillable
+ * region. This is a helper function for layering pictures. When assign-
+ * ing layers, we want to weight the colors that have the most complex
+ * shapes, or the largest count of isolated areas, heavier than simpler
+ * areas.
+ *
+ * By painting the colors with the largest number of isolated areas or
+ * the most complex shapes first, we help to minimize the number of fill
+ * regions needed to render the complete picture.
+ */
+static int gln_graphics_is_vertex(gln_byte off_screen[],
+ gln_uint16 width, gln_uint16 height, int x, int y) {
+ gln_byte pixel;
+ int above, below, left, right;
+ long index_row;
+ assert(off_screen);
+
+ /* Use an index row to cut down on multiplications. */
+ index_row = y * width;
+
+ /* Find the color of the reference pixel. */
+ pixel = off_screen[index_row + x];
+ assert(pixel < GLN_PALETTE_SIZE);
+
+ /*
+ * Detect differences between the reference pixel and its upper, lower, left
+ * and right neighbors. Mark as different if the neighbor doesn't exist,
+ * that is, at the edge of the picture.
+ */
+ above = (y == 0 || off_screen[index_row - width + x] != pixel);
+ below = (y == height - 1 || off_screen[index_row + width + x] != pixel);
+ left = (x == 0 || off_screen[index_row + x - 1] != pixel);
+ right = (x == width - 1 || off_screen[index_row + x + 1] != pixel);
+
+ /*
+ * Return TRUE if this pixel lies at the vertex of a rectangular, fillable,
+ * area. That is, if two adjacent neighbors aren't the same color (or if
+ * absent -- at the edge of the picture).
+ */
+ return ((above || below) && (left || right));
+}
+
+
+/*
+ * gms_graphics_compare_layering_inverted()
+ * gln_graphics_assign_layers()
+ *
+ * Given two sets of image bitmaps, and a palette, this function will
+ * assign layers palette colors.
+ *
+ * Layers are assigned by first counting the number of vertices in the
+ * color plane, to get a measure of the complexity of shapes displayed in
+ * this color, and also the raw number of times each palette color is
+ * used. This is then sorted, so that layers are assigned to colors, with
+ * the lowest layer being the color with the most complex shapes, and
+ * within this (or where the count of vertices is zero) the most used color.
+ *
+ * The function compares pixels in the two image bitmaps given, these
+ * being the off-screen and on-screen buffers, and generates counts only
+ * where these bitmaps differ. This ensures that only pixels not yet
+ * painted are included in layering.
+ *
+ * As well as assigning layers, this function returns a set of layer usage
+ * flags, to help the rendering loop to terminate as early as possible.
+ *
+ * By painting lower layers first, the paint can take in larger areas if
+ * it's permitted to include not-yet-validated higher levels. This helps
+ * minimize the amount of Glk areas fills needed to render a picture.
+ */
+struct gln_layering_t {
+ long complexity; /* Count of vertices for this color. */
+ long usage; /* Color usage count. */
+ int color; /* Color index into palette. */
+};
+
+static int gln_graphics_compare_layering_inverted(const void *void_first,
+ const void *void_second) {
+ const gln_layering_t *first = (const gln_layering_t *)void_first;
+ const gln_layering_t *second = (const gln_layering_t *)void_second;
+
+ /*
+ * Order by complexity first, then by usage, putting largest first. Some
+ * colors may have no vertices at all when doing animation frames, but
+ * rendering optimization relies on the first layer that contains no areas
+ * to fill halting the rendering loop. So it's important here that we order
+ * indexes so that colors that render complex shapes come first, non-empty,
+ * but simpler shaped colors next, and finally all genuinely empty layers.
+ */
+ return second->complexity > first->complexity ? 1 :
+ first->complexity > second->complexity ? -1 :
+ second->usage > first->usage ? 1 :
+ first->usage > second->usage ? -1 : 0;
+}
+
+static void gln_graphics_assign_layers(gln_byte off_screen[], gln_byte on_screen[],
+ gln_uint16 width, gln_uint16 height,
+ int layers[], long layer_usage[]) {
+ int index, x, y;
+ long index_row;
+ gln_layering_t layering[GLN_PALETTE_SIZE];
+ assert(off_screen && on_screen && layers && layer_usage);
+
+ /* Clear initial complexity and usage counts, and set initial colors. */
+ for (index = 0; index < GLN_PALETTE_SIZE; index++) {
+ layering[index].complexity = 0;
+ layering[index].usage = 0;
+ layering[index].color = index;
+ }
+
+ /*
+ * Traverse the image, counting vertices and pixel usage where the pixels
+ * differ between the off-screen and on-screen buffers. Optimize by
+ * maintaining an index row to avoid multiplications.
+ */
+ for (y = 0, index_row = 0; y < height; y++, index_row += width) {
+ for (x = 0; x < width; x++) {
+ long idx;
+
+ /*
+ * Get the idx for this pixel, and update complexity and usage
+ * if off-screen and on-screen pixels differ.
+ */
+ idx = index_row + x;
+ if (on_screen[idx] != off_screen[idx]) {
+ if (gln_graphics_is_vertex(off_screen, width, height, x, y))
+ layering[off_screen[idx]].complexity++;
+
+ layering[off_screen[idx]].usage++;
+ }
+ }
+ }
+
+ /*
+ * Sort counts to form color indexes. The primary sort is on the shape
+ * complexity, and within this, on color usage.
+ */
+ qsort(layering, GLN_PALETTE_SIZE,
+ sizeof(*layering), gln_graphics_compare_layering_inverted);
+
+ /*
+ * Assign a layer to each palette color, and also return the layer usage
+ * for each layer.
+ */
+ for (index = 0; index < GLN_PALETTE_SIZE; index++) {
+ layers[layering[index].color] = index;
+ layer_usage[index] = layering[index].usage;
+ }
+}
+
+
+/*
+ * gln_graphics_paint_region()
+ *
+ * This is a partially optimized point plot. Given a point in the graphics
+ * bitmap, it tries to extend the point to a color region, and fill a number
+ * of pixels in a single Glk rectangle fill. The goal here is to reduce the
+ * number of Glk rectangle fills, which tend to be extremely inefficient
+ * operations for generalized point plotting.
+ *
+ * The extension works in image layers; each palette color is assigned* a
+ * layer, and we paint each layer individually, starting at the lowest. So,
+ * the region is free to fill any invalidated pixel in a higher layer, and
+ * all pixels, invalidated or already validated, in the same layer. In
+ * practice, it is good enough to look for either invalidated pixels or pixels
+ * in the same layer, and construct a region as large as possible from these,
+ * then on marking points as validated, mark only those in the same layer as
+ * the initial point.
+ *
+ * The optimization here is not the best possible, but is reasonable. What
+ * we do is to try and stretch the region horizontally first, then vertically.
+ * In practice, we might find larger areas by stretching vertically and then
+ * horizontally, or by stretching both dimensions at the same time. In
+ * mitigation, the number of colors in a picture is small (16), and the
+ * aspect ratio of pictures makes them generally wider than they are tall.
+ *
+ * Once we've found the region, we render it with a single Glk rectangle fill,
+ * and mark all the pixels in this region that match the layer of the initial
+ * given point as validated.
+ */
+static void gln_graphics_paint_region(winid_t glk_window, glui32 palette[], int layers[],
+ gln_byte off_screen[], gln_byte on_screen[],
+ int x, int y, int x_offset, int y_offset,
+ int pixel_size, gln_uint16 width, gln_uint16 height) {
+ gln_byte pixel;
+ int layer, x_min, x_max, y_min, y_max, x_index, y_index;
+ long index_row;
+ assert(glk_window && palette && layers && off_screen && on_screen);
+
+ /* Find the color and layer for the initial pixel. */
+ pixel = off_screen[y * width + x];
+ layer = layers[pixel];
+ assert(pixel < GLN_PALETTE_SIZE);
+
+ /*
+ * Start by finding the extent to which we can pull the x coordinate and
+ * still find either invalidated pixels, or pixels in this layer.
+ *
+ * Use an index row to remove multiplications from the loops.
+ */
+ index_row = y * width;
+ for (x_min = x; x_min - 1 >= 0; x_min--) {
+ long index = index_row + x_min - 1;
+
+ if (on_screen[index] == off_screen[index]
+ && layers[off_screen[index]] != layer)
+ break;
+ }
+ for (x_max = x; x_max + 1 < width; x_max++) {
+ long index = index_row + x_max + 1;
+
+ if (on_screen[index] == off_screen[index]
+ && layers[off_screen[index]] != layer)
+ break;
+ }
+
+ /*
+ * Now try to stretch the height of the region, by extending the y
+ * coordinate as much as possible too. Again, we're looking for pixels
+ * that are invalidated or ones in the same layer. We need to check
+ * across the full width of the current region.
+ *
+ * As above, an index row removes multiplications from the loops.
+ */
+ for (y_min = y, index_row = (y - 1) * width;
+ y_min - 1 >= 0; y_min--, index_row -= width) {
+ for (x_index = x_min; x_index <= x_max; x_index++) {
+ long index = index_row + x_index;
+
+ if (on_screen[index] == off_screen[index]
+ && layers[off_screen[index]] != layer)
+ goto break_y_min;
+ }
+ }
+break_y_min:
+
+ for (y_max = y, index_row = (y + 1) * width;
+ y_max + 1 < height; y_max++, index_row += width) {
+ for (x_index = x_min; x_index <= x_max; x_index++) {
+ long index = index_row + x_index;
+
+ if (on_screen[index] == off_screen[index]
+ && layers[off_screen[index]] != layer)
+ goto break_y_max;
+ }
+ }
+break_y_max:
+
+ /* Fill the region using Glk's rectangle fill. */
+ g_vm->glk_window_fill_rect(glk_window, palette[pixel],
+ x_min * pixel_size + x_offset,
+ y_min * pixel_size + y_offset,
+ (x_max - x_min + 1) * pixel_size,
+ (y_max - y_min + 1) * pixel_size);
+
+ /*
+ * Validate each pixel in the reference layer that was rendered by the
+ * rectangle fill. We don't validate pixels that are not in this layer
+ * (and are by definition in higher layers, as we've validated all lower
+ * layers), since although we colored them, we did it for optimization
+ * reasons, and they're not yet colored correctly.
+ *
+ * Maintain an index row as an optimization to avoid multiplication.
+ */
+ index_row = y_min * width;
+ for (y_index = y_min; y_index <= y_max; y_index++) {
+ for (x_index = x_min; x_index <= x_max; x_index++) {
+ long index;
+
+ /*
+ * Get the index for x_index,y_index. If the layers match, update
+ * the on-screen buffer.
+ */
+ index = index_row + x_index;
+ if (layers[off_screen[index]] == layer) {
+ assert(off_screen[index] == pixel);
+ on_screen[index] = off_screen[index];
+ }
+ }
+
+ /* Update row index component on change of y. */
+ index_row += width;
+ }
+}
+
+static void gln_graphics_paint_everything(winid_t glk_window,
+ glui32 palette[],
+ gln_byte off_screen[],
+ int x_offset, int y_offset,
+ gln_uint16 width, gln_uint16 height) {
+ gln_byte pixel; /* Reference pixel color */
+ int x, y;
+
+ for (y = 0; y < height; y++) {
+ for (x = 0; x < width; x ++) {
+ pixel = off_screen[ y * width + x ];
+ g_vm->glk_window_fill_rect(glk_window,
+ palette[ pixel ],
+ x * GLN_GRAPHICS_PIXEL + x_offset,
+ y * GLN_GRAPHICS_PIXEL + y_offset,
+ GLN_GRAPHICS_PIXEL, GLN_GRAPHICS_PIXEL);
+ }
+ }
+}
+
+/*
+ * gln_graphics_timeout()
+ *
+ * This is a background function, called on Glk timeouts. Its job is to
+ * repaint some of the current graphics image. On successive calls, it
+ * does a part of the repaint, then yields to other processing. This is
+ * useful since the Glk primitive to plot points in graphical windows is
+ * extremely slow; this way, the repaint doesn't block game play.
+ *
+ * The function should be called on Glk timeout events. When the repaint
+ * is complete, the function will turn off Glk timers.
+ *
+ * The function uses double-buffering to track how much of the graphics
+ * buffer has been rendered. This helps to minimize the amount of point
+ * plots required, as only the differences between the two buffers need
+ * to be rendered.
+ */
+static void gln_graphics_timeout(void) {
+ static glui32 palette[GLN_PALETTE_SIZE]; /* Precomputed Glk palette */
+
+ static int deferred_repaint = FALSE; /* Local delayed repaint flag */
+ static int ignore_counter; /* Count of calls ignored */
+
+ static int x_offset, y_offset; /* Point plot offsets */
+ static int yield_counter; /* Yields in rendering */
+ static int saved_layer; /* Saved current layer */
+ static int saved_x, saved_y; /* Saved x,y coord */
+
+ static int total_regions; /* Debug statistic */
+
+ gln_byte *on_screen; /* On-screen image buffer */
+ gln_byte *off_screen; /* Off-screen image buffer */
+ long picture_size; /* Picture size in pixels */
+
+ /* Ignore the call if the current graphics state is inactive. */
+ if (!gln_graphics_active)
+ return;
+ assert(gln_graphics_window);
+
+ /*
+ * On detecting a repaint request, note the flag in a local static variable,
+ * then set up a graphics delay to wait until, hopefully, the resize, if
+ * that's what caused it, is complete, and return. This makes resizing the
+ * window a lot smoother, since it prevents unnecessary region paints where
+ * we are receiving consecutive Glk arrange or redraw events.
+ */
+ if (gln_graphics_repaint) {
+ deferred_repaint = TRUE;
+ gln_graphics_repaint = FALSE;
+ ignore_counter = GLN_GRAPHICS_REPAINT_WAIT - 1;
+ return;
+ }
+
+ /*
+ * If asked to ignore a given number of calls, decrement the ignore counter
+ * and return having done nothing more. This lets us delay graphics
+ * operations by a number of timeouts, providing partial protection from
+ * resize event "storms".
+ *
+ * Note -- to wait for N timeouts, set the count of timeouts to be ignored
+ * to N-1.
+ */
+ assert(ignore_counter >= 0);
+ if (ignore_counter > 0) {
+ ignore_counter--;
+ return;
+ }
+
+ /* Calculate the picture size, and synchronize screen buffer pointers. */
+ picture_size = gln_graphics_width * gln_graphics_height;
+ off_screen = gln_graphics_off_screen;
+ on_screen = gln_graphics_on_screen;
+
+ /*
+ * If we received a new picture, set up the local static variables for that
+ * picture -- convert the color palette, and initialize the off_screen
+ * buffer to be the base picture.
+ */
+ if (gln_graphics_new_picture) {
+ /* Initialize the off_screen buffer to be a copy of the base picture. */
+ free(off_screen);
+ off_screen = (gln_byte *)gln_malloc(picture_size * sizeof(*off_screen));
+ memcpy(off_screen, gln_graphics_bitmap,
+ picture_size * sizeof(*off_screen));
+
+ /* Note the buffer for freeing on cleanup. */
+ gln_graphics_off_screen = off_screen;
+
+ /*
+ * Pre-convert all the picture palette colors into their corresponding
+ * Glk colors.
+ */
+ gln_graphics_convert_palette(gln_graphics_palette, palette);
+
+ /* Save the color count for possible queries later. */
+ gln_graphics_color_count =
+ gln_graphics_count_colors(off_screen,
+ gln_graphics_width, gln_graphics_height);
+ }
+
+ /*
+ * For a new picture, or a repaint of a prior one, calculate new values for
+ * the x and y offsets used to draw image points, and set the on-screen
+ * buffer to an unused pixel value, in effect invalidating all on-screen
+ * data. Also, reset the saved image scan coordinates so that we scan for
+ * unpainted pixels from top left starting at layer zero, and clear the
+ * graphics window.
+ */
+ if (gln_graphics_new_picture || deferred_repaint) {
+ /*
+ * Calculate the x and y offset to center the picture in the graphics
+ * window.
+ */
+ gln_graphics_position_picture(gln_graphics_window,
+ GLN_GRAPHICS_PIXEL,
+ gln_graphics_width, gln_graphics_height,
+ &x_offset, &y_offset);
+
+ /*
+ * Reset all on-screen pixels to an unused value, guaranteed not to
+ * match any in a real picture. This forces all pixels to be repainted
+ * on a buffer/on-screen comparison.
+ */
+ free(on_screen);
+ on_screen = (gln_byte *)gln_malloc(picture_size * sizeof(*on_screen));
+ memset(on_screen, GLN_GRAPHICS_UNUSED_PIXEL,
+ picture_size * sizeof(*on_screen));
+
+ /* Note the buffer for freeing on cleanup. */
+ gln_graphics_on_screen = on_screen;
+
+ /*
+ * Assign new layers to the current image. This sorts colors by usage
+ * and puts the most used colors in the lower layers. It also hands us
+ * a count of pixels in each layer, useful for knowing when to stop
+ * scanning for layers in the rendering loop.
+ */
+#ifndef GARGLK
+ gln_graphics_assign_layers(off_screen, on_screen,
+ gln_graphics_width, gln_graphics_height,
+ layers, layer_usage);
+#endif
+
+ /* Clear the graphics window. */
+ gln_graphics_clear_and_border(gln_graphics_window,
+ x_offset, y_offset,
+ GLN_GRAPHICS_PIXEL,
+ gln_graphics_width, gln_graphics_height);
+
+ /* Start a fresh picture rendering pass. */
+ yield_counter = 0;
+ saved_layer = 0;
+ saved_x = 0;
+ saved_y = 0;
+ total_regions = 0;
+
+ /* Clear the new picture and deferred repaint flags. */
+ gln_graphics_new_picture = FALSE;
+ deferred_repaint = FALSE;
+ }
+
+#ifndef GARGLK
+ int layer; /* Image layer iterator */
+ int x, y; /* Image iterators */
+ int regions; /* Count of regions painted */
+ static int layers[GLN_PALETTE_SIZE]; /* Assigned image layers */
+ static long layer_usage[GLN_PALETTE_SIZE]; /* Image layer occupancies */
+
+ /*
+ * Make a portion of an image pass, from lower to higher image layers,
+ * scanning for invalidated pixels that are in the current image layer we
+ * are painting. Each invalidated pixel gives rise to a region paint,
+ * which equates to one Glk rectangle fill.
+ *
+ * When the limit on regions is reached, save the current image pass layer
+ * and coordinates, and yield control to the main game playing code by
+ * returning. On the next call, pick up where we left off.
+ *
+ * As an optimization, we can leave the loop on the first empty layer we
+ * encounter. Since layers are ordered by complexity and color usage, all
+ * layers higher than the first unused one will also be empty, so we don't
+ * need to scan them.
+ */
+ regions = 0;
+ for (layer = saved_layer;
+ layer < GLN_PALETTE_SIZE && layer_usage[layer] > 0; layer++) {
+ long index_row;
+
+ /*
+ * As an optimization to avoid multiplications in the loop, maintain a
+ * separate index row.
+ */
+ index_row = saved_y * gln_graphics_width;
+ for (y = saved_y; y < gln_graphics_height; y++) {
+ for (x = saved_x; x < gln_graphics_width; x++) {
+ long index;
+
+ /* Get the index for this pixel. */
+ index = index_row + x;
+ assert(index < picture_size * sizeof(*off_screen));
+
+ /*
+ * Ignore pixels not in the current layer, and pixels not
+ * currently invalid (that is, ones whose on-screen represen-
+ * tation matches the off-screen buffer).
+ */
+ if (layers[off_screen[index]] == layer
+ && on_screen[index] != off_screen[index]) {
+ /*
+ * Rather than painting just one pixel, here we try to
+ * paint the maximal region we can for the layer of the
+ * given pixel.
+ */
+ gln_graphics_paint_region(gln_graphics_window,
+ palette, layers,
+ off_screen, on_screen,
+ x, y, x_offset, y_offset,
+ GLN_GRAPHICS_PIXEL,
+ gln_graphics_width,
+ gln_graphics_height);
+
+ /*
+ * Increment count of regions handled, and yield, by
+ * returning, if the limit on paint regions is reached.
+ * Before returning, save the current layer and scan
+ * coordinates, so we can pick up here on the next call.
+ */
+ regions++;
+ if (regions >= GLN_REPAINT_LIMIT) {
+ yield_counter++;
+ saved_layer = layer;
+ saved_x = x;
+ saved_y = y;
+ total_regions += regions;
+ return;
+ }
+ }
+ }
+
+ /* Reset the saved x coordinate on y increment. */
+ saved_x = 0;
+
+ /* Update the index row on change of y. */
+ index_row += gln_graphics_width;
+ }
+
+ /* Reset the saved y coordinate on layer change. */
+ saved_y = 0;
+ }
+
+ /*
+ * If we reach this point, then we didn't get to the limit on regions
+ * painted on this pass. In that case, we've finished rendering the
+ * image.
+ */
+ assert(regions < GLN_REPAINT_LIMIT);
+ total_regions += regions;
+
+#else
+ gln_graphics_paint_everything
+ (gln_graphics_window,
+ palette, off_screen,
+ x_offset, y_offset,
+ gln_graphics_width,
+ gln_graphics_height);
+#endif
+
+ /* Stop graphics; there's no more to be done until something restarts us. */
+ gln_graphics_stop();
+}
+
+/*
+ * gln_graphics_locate_bitmaps()
+ *
+ * Given the name of the game file being run, try to set up the graphics
+ * directory and bitmap type for that game. If none available, set the
+ * directory to NULL, and bitmap type to NO_BITMAPS.
+ */
+static void gln_graphics_locate_bitmaps(const char *gamefile) {
+ const char *basename;
+ char *dirname;
+ BitmapType bitmap_type;
+
+ /* Find the start of the last element of the filename passed in. */
+ basename = strrchr(gamefile, GLN_FILE_DELIM);
+ basename = basename ? basename + 1 : gamefile;
+
+ /* Take a copy of the directory part of the filename. */
+ dirname = (char *)gln_malloc(basename - gamefile + 1);
+ strncpy(dirname, gamefile, basename - gamefile);
+ dirname[basename - gamefile] = '\0';
+
+ /*
+ * Use the core interpreter to search for suitable bitmaps. If none found,
+ * free allocated memory and return noting none available.
+ */
+ bitmap_type = DetectBitmaps(dirname);
+ if (bitmap_type == NO_BITMAPS) {
+ free(dirname);
+ gln_graphics_bitmap_directory = NULL;
+ gln_graphics_bitmap_type = NO_BITMAPS;
+ return;
+ }
+
+ /* Record the bitmap details for later use. */
+ gln_graphics_bitmap_directory = dirname;
+ gln_graphics_bitmap_type = bitmap_type;
+}
+
+
+/*
+ * gln_graphics_handle_title_picture()
+ *
+ * Picture 0 is special, normally the title picture. Unless we handle it
+ * specially, the next picture comes along and instantly overwrites it.
+ * Here, then, we try to delay until the picture has rendered, allowing the
+ * delay to be broken with a keypress.
+ */
+static void gln_graphics_handle_title_picture(void) {
+ event_t event;
+ int count;
+
+ gln_standout_string("\n[ Press any key to skip the title picture... ]\n\n");
+
+ /* Wait until a keypress or graphics rendering is complete. */
+ g_vm->glk_request_char_event(gln_main_window);
+ do {
+ gln_event_wait_2(evtype_CharInput, evtype_Timer, &event);
+
+ /*
+ * If a character was pressed, return. This will let the game
+ * progress, probably into showing the next bitmap.
+ */
+ if (event.type == evtype_CharInput) {
+ gln_watchdog_tick();
+ return;
+ }
+ } while (gln_graphics_active);
+
+ /*
+ * Now wait another couple of seconds, or until a keypress. We'll do this
+ * in graphics timeout chunks, so that if graphics restarts while we're
+ * delaying, and it requests timer events and overwrites ours, we wind up
+ * with the identical timer event period to the one we're expecting anyway.
+ */
+ g_vm->glk_request_timer_events(GLN_GRAPHICS_TIMEOUT);
+ for (count = 0; count < GLN_GRAPHICS_TITLE_WAIT; count++) {
+ gln_event_wait_2(evtype_CharInput, evtype_Timer, &event);
+
+ if (event.type == evtype_CharInput)
+ break;
+ }
+
+ /*
+ * While we waited, a Glk arrange or redraw event could have triggered
+ * graphics into repainting, and using timers. To handle this, stop timers
+ * only if graphics is inactive. If active, graphics will stop timers
+ * itself when it finishes rendering. We can't stop timers here while
+ * graphics is active; that will hang the graphics "thread".
+ */
+ if (!gln_graphics_active)
+ g_vm->glk_request_timer_events(0);
+
+ /* Cancel possible pending character event, and continue on. */
+ g_vm->glk_cancel_char_event(gln_main_window);
+ gln_watchdog_tick();
+}
+
+
+/*
+ * os_show_bitmap()
+ *
+ * Called by the main interpreter when it wants us to display a picture.
+ *
+ * The function gets the picture bitmap, palette, and dimensions, and saves
+ * them, and the picture id, in module variables for the background rendering
+ * function.
+ */
+void os_show_bitmap(int picture, int x, int y) {
+ Bitmap *bitmap;
+ long picture_bytes;
+
+ /*
+ * If interpreter graphics are disabled, the only way we can get into here
+ * is using #picture. It seems that the interpreter won't always deliver
+ * correct bitmaps with #picture when in text mode, so it's simplest here
+ * if we just ignore those calls.
+ */
+ if (gln_graphics_interpreter_state != GLN_GRAPHICS_BITMAP_MODE)
+ return;
+
+ /* Ignore repeat calls for the currently displayed picture. */
+ if (picture == gln_graphics_picture)
+ return;
+
+ /*
+ * Get the core interpreter's bitmap for the requested picture. If this
+ * returns NULL, the picture doesn't exist, so ignore the call silently.
+ */
+ bitmap = DecodeBitmap(gln_graphics_bitmap_directory,
+ gln_graphics_bitmap_type, picture, x, y);
+ if (!bitmap)
+ return;
+
+ /*
+ * Note the last thing passed to os_show_bitmap, to avoid possible repaints
+ * of the current picture.
+ */
+ gln_graphics_picture = picture;
+
+ /* Calculate the picture size in bytes. */
+ picture_bytes = bitmap->width * bitmap->height * sizeof(*bitmap->bitmap);
+
+ /*
+ * Save the picture details for the update code. Here we take a complete
+ * local copy of the bitmap, dimensions, and palette. The core interpreter
+ * may return a palette with fewer colors than our maximum, so unused local
+ * palette entries are set to zero.
+ */
+ free(gln_graphics_bitmap);
+ gln_graphics_bitmap = (gln_byte *)gln_malloc(picture_bytes);
+ memcpy(gln_graphics_bitmap, bitmap->bitmap, picture_bytes);
+ gln_graphics_width = bitmap->width;
+ gln_graphics_height = bitmap->height;
+ memset(gln_graphics_palette, 0, sizeof(gln_graphics_palette));
+ memcpy(gln_graphics_palette, bitmap->palette,
+ bitmap->npalette * sizeof(bitmap->palette[0]));
+
+ /*
+ * If graphics are enabled, both at the Glk level and in the core
+ * interpreter, ensure the window is displayed, set the appropriate flags,
+ * and start graphics update. If they're not enabled, the picture details
+ * will simply stick around in module variables until they are required.
+ */
+ if (gln_graphics_enabled
+ && gln_graphics_interpreter_state == GLN_GRAPHICS_BITMAP_MODE) {
+ /*
+ * Ensure graphics on, then set the new picture flag and start the
+ * updating "thread". If this is the title picture, start special
+ * handling.
+ */
+ if (gln_graphics_open()) {
+ gln_graphics_new_picture = TRUE;
+ gln_graphics_start();
+
+ if (picture == GLN_GRAPHICS_TITLE_PICTURE)
+ gln_graphics_handle_title_picture();
+ }
+ }
+}
+
+
+/*
+ * gln_graphics_picture_is_available()
+ *
+ * Return TRUE if the graphics module data is loaded with a usable picture,
+ * FALSE if there is no picture available to display.
+ */
+static int gln_graphics_picture_is_available(void) {
+ return gln_graphics_bitmap != NULL;
+}
+
+
+/*
+ * gln_graphics_get_picture_details()
+ *
+ * Return the width and height of the currently loaded picture. The function
+ * returns FALSE if no picture is loaded, otherwise TRUE, with picture details
+ * in the return arguments.
+ */
+static int gln_graphics_get_picture_details(int *width, int *height) {
+ if (gln_graphics_picture_is_available()) {
+ if (width)
+ *width = gln_graphics_width;
+ if (height)
+ *height = gln_graphics_height;
+
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+
+/*
+ * gln_graphics_get_rendering_details()
+ *
+ * Returns the type of bitmap in use (if any), as a string, the count of
+ * colors in the picture, and a flag indicating if graphics is active (busy).
+ * The function return FALSE if graphics is not enabled or if not being
+ * displayed, otherwise TRUE with the bitmap type, color count, and active
+ * flag in the return arguments.
+ *
+ * This function races with the graphics timeout, as it returns information
+ * set up by the first timeout following a new picture. There's a very
+ * very small chance that it might win the race, in which case out-of-date
+ * values are returned.
+ */
+static int gln_graphics_get_rendering_details(const char **bitmap_type,
+ int *color_count, int *is_active) {
+ if (gln_graphics_enabled && gln_graphics_are_displayed()) {
+ /*
+ * Convert the detected bitmap type into a string and return it.
+ * A NULL bitmap string implies no bitmaps.
+ */
+ if (bitmap_type) {
+ const char *return_type;
+
+ switch (gln_graphics_bitmap_type) {
+ case AMIGA_BITMAPS:
+ return_type = "Amiga";
+ break;
+ case PC1_BITMAPS:
+ return_type = "IBM PC(1)";
+ break;
+ case PC2_BITMAPS:
+ return_type = "IBM PC(2)";
+ break;
+ case C64_BITMAPS:
+ return_type = "Commodore 64";
+ break;
+ case BBC_BITMAPS:
+ return_type = "BBC B";
+ break;
+ case CPC_BITMAPS:
+ return_type = "Amstrad CPC/Spectrum";
+ break;
+ case MAC_BITMAPS:
+ return_type = "Macintosh";
+ break;
+ case ST1_BITMAPS:
+ return_type = "Atari ST(1)";
+ break;
+ case ST2_BITMAPS:
+ return_type = "Atari ST(2)";
+ break;
+ case NO_BITMAPS:
+ default:
+ return_type = NULL;
+ break;
+ }
+
+ *bitmap_type = return_type;
+ }
+
+ /*
+ * Return the color count noted by timeouts on the first timeout
+ * following a new picture. We might return the one for the prior
+ * picture.
+ */
+ if (color_count)
+ *color_count = gln_graphics_color_count;
+
+ /* Return graphics active flag. */
+ if (is_active)
+ *is_active = gln_graphics_active;
+
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+
+/*
+ * gln_graphics_interpreter_enabled()
+ *
+ * Return TRUE if it looks like interpreter graphics are turned on, FALSE
+ * otherwise.
+ */
+static int gln_graphics_interpreter_enabled(void) {
+ return gln_graphics_interpreter_state != GLN_GRAPHICS_OFF;
+}
+
+
+/*
+ * gln_graphics_cleanup()
+ *
+ * Free memory resources allocated by graphics functions. Called on game
+ * end.
+ */
+static void gln_graphics_cleanup(void) {
+ free(gln_graphics_bitmap);
+ gln_graphics_bitmap = NULL;
+ free(gln_graphics_off_screen);
+ gln_graphics_off_screen = NULL;
+ free(gln_graphics_on_screen);
+ gln_graphics_on_screen = NULL;
+ free(gln_graphics_bitmap_directory);
+ gln_graphics_bitmap_directory = NULL;
+
+ gln_graphics_bitmap_type = NO_BITMAPS;
+ gln_graphics_picture = -1;
+}
+
+
+/*---------------------------------------------------------------------*/
+/* Glk port line drawing picture adapter functions */
+/*---------------------------------------------------------------------*/
+
+/*
+ * Graphics color table. These eight colors are selected into the four-
+ * color palette by os_setcolour(). The standard Amiga palette is rather
+ * over-vibrant, so to soften it a bit this table uses non-primary colors.
+ */
+static const gln_rgb_t GLN_LINEGRAPHICS_COLOR_TABLE[] = {
+ { 47, 79, 79}, /* DarkSlateGray [Black] */
+ {238, 44, 44}, /* Firebrick2 [Red] */
+ { 67, 205, 128}, /* SeaGreen3 [Green] */
+ {238, 201, 0}, /* Gold2 [Yellow] */
+ { 92, 172, 238}, /* SteelBlue2 [Blue] */
+ {139, 87, 66}, /* LightSalmon4 [Brown] */
+ {175, 238, 238}, /* PaleTurquoise [Cyan] */
+ {245, 245, 245}, /* WhiteSmoke [White] */
+};
+
+/*
+ * Structure of a Seed Fill segment entry, and a growable stack-based array
+ * of segments pending fill. When length exceeds size, size is increased
+ * and the array grown.
+ */
+struct gln_linegraphics_segment_t {
+ int y; /* Segment y coordinate */
+ int xl; /* Segment x left hand side coordinate */
+ int xr; /* Segment x right hand side coordinate */
+ int dy; /* Segment y delta */
+};
+
+static gln_linegraphics_segment_t *gln_linegraphics_fill_segments = NULL;
+static int gln_linegraphics_fill_segments_allocation = 0,
+ gln_linegraphics_fill_segments_length = 0;
+
+
+/*
+ * gln_linegraphics_create_context()
+ *
+ * Initialize a new constructed bitmap graphics context for line drawn
+ * graphics.
+ */
+static void gln_linegraphics_create_context(void) {
+ int width, height;
+ long picture_bytes;
+
+ /* Get the picture size, and calculate the bytes in the bitmap. */
+ GetPictureSize(&width, &height);
+ picture_bytes = width * height * sizeof(*gln_graphics_bitmap);
+
+ /*
+ * Destroy any current bitmap, and begin a fresh one. Here we set the
+ * bitmap and the palette to all zeroes; this equates to all black.
+ */
+ free(gln_graphics_bitmap);
+ gln_graphics_bitmap = (gln_byte *)gln_malloc(picture_bytes);
+ memset(gln_graphics_bitmap, 0, picture_bytes);
+ gln_graphics_width = width;
+ gln_graphics_height = height;
+ memset(gln_graphics_palette, 0, sizeof(gln_graphics_palette));
+
+ /* Set graphics picture number to -1; this is not a real game bitmap. */
+ gln_graphics_picture = -1;
+}
+
+
+/*
+ * gln_linegraphics_clear_context()
+ *
+ * Clear the complete graphical drawing area, setting all pixels to zero,
+ * and resetting the palette to all black as well.
+ */
+static void gln_linegraphics_clear_context(void) {
+ long picture_bytes;
+
+ /* Get the picture size, and zero all bytes in the bitmap. */
+ picture_bytes = gln_graphics_width
+ * gln_graphics_height * sizeof(*gln_graphics_bitmap);
+ memset(gln_graphics_bitmap, 0, picture_bytes);
+
+ /* Clear palette colors to all black. */
+ memset(gln_graphics_palette, 0, sizeof(gln_graphics_palette));
+}
+
+
+/*
+ * gln_linegraphics_set_palette_color()
+ *
+ * Copy the indicated main color table entry into the palette.
+ */
+static void gln_linegraphics_set_palette_color(int colour, int index) {
+ const gln_rgb_t *entry;
+ assert(colour < GLN_PALETTE_SIZE);
+ assert(index < (int)sizeof(GLN_LINEGRAPHICS_COLOR_TABLE)
+ / (int)sizeof(GLN_LINEGRAPHICS_COLOR_TABLE[0]));
+
+ /* Copy the color table entry to the constructed game palette. */
+ entry = GLN_LINEGRAPHICS_COLOR_TABLE + index;
+ gln_graphics_palette[colour].red = entry->red;
+ gln_graphics_palette[colour].green = entry->green;
+ gln_graphics_palette[colour].blue = entry->blue;
+}
+
+
+/*
+ * gln_linegraphics_get_pixel()
+ * gln_linegraphics_set_pixel()
+ *
+ * Return and set the bitmap pixel at x,y.
+ */
+static gln_byte gln_linegraphics_get_pixel(int x, int y) {
+ assert(x >= 0 && x < gln_graphics_width
+ && y >= 0 && y < gln_graphics_height);
+
+ return gln_graphics_bitmap[y * gln_graphics_width + x];
+}
+
+static void gln_linegraphics_set_pixel(int x, int y, gln_byte color) {
+ assert(x >= 0 && x < gln_graphics_width
+ && y >= 0 && y < gln_graphics_height);
+
+ gln_graphics_bitmap[y * gln_graphics_width + x] = color;
+}
+
+
+/*
+ * gln_linegraphics_plot_clip()
+ * gln_linegraphics_draw_line_if()
+ *
+ * Draw a line from x1,y1 to x2,y2 in colour1, where the existing pixel
+ * colour is colour2. The function uses Bresenham's algorithm. The second
+ * function, gln_graphics_plot_clip, is a line drawing helper; it handles
+ * clipping, and the requirement to plot a point only if it matches colour2.
+ */
+static void gln_linegraphics_plot_clip(int x, int y, int colour1, int colour2) {
+ /*
+ * Clip the plot if the value is outside the context. Otherwise, plot the
+ * pixel as colour1 if it is currently colour2.
+ */
+ if (x >= 0 && x < gln_graphics_width && y >= 0 && y < gln_graphics_height) {
+ if (gln_linegraphics_get_pixel(x, y) == colour2)
+ gln_linegraphics_set_pixel(x, y, colour1);
+ }
+}
+
+static void gln_linegraphics_draw_line_if(int x1, int y1, int x2, int y2,
+ int colour1, int colour2) {
+ int x, y, dx, dy, incx, incy, balance;
+
+ /* Ignore any odd request where there will be no colour changes. */
+ if (colour1 == colour2)
+ return;
+
+ /* Normalize the line into deltas and increments. */
+ if (x2 >= x1) {
+ dx = x2 - x1;
+ incx = 1;
+ } else {
+ dx = x1 - x2;
+ incx = -1;
+ }
+
+ if (y2 >= y1) {
+ dy = y2 - y1;
+ incy = 1;
+ } else {
+ dy = y1 - y2;
+ incy = -1;
+ }
+
+ /* Start at x1,y1. */
+ x = x1;
+ y = y1;
+
+ /* Decide on a direction to progress in. */
+ if (dx >= dy) {
+ dy <<= 1;
+ balance = dy - dx;
+ dx <<= 1;
+
+ /* Loop until we reach the end point of the line. */
+ while (x != x2) {
+ gln_linegraphics_plot_clip(x, y, colour1, colour2);
+ if (balance >= 0) {
+ y += incy;
+ balance -= dx;
+ }
+ balance += dy;
+ x += incx;
+ }
+ gln_linegraphics_plot_clip(x, y, colour1, colour2);
+ } else {
+ dx <<= 1;
+ balance = dx - dy;
+ dy <<= 1;
+
+ /* Loop until we reach the end point of the line. */
+ while (y != y2) {
+ gln_linegraphics_plot_clip(x, y, colour1, colour2);
+ if (balance >= 0) {
+ x += incx;
+ balance -= dy;
+ }
+ balance += dx;
+ y += incy;
+ }
+ gln_linegraphics_plot_clip(x, y, colour1, colour2);
+ }
+}
+
+
+/*
+ * gln_linegraphics_push_fill_segment()
+ * gln_linegraphics_pop_fill_segment()
+ * gln_linegraphics_fill_4way_if()
+ *
+ * Area fill algorithm, set a region to colour1 if it is currently set to
+ * colour2. This function is a derivation of Paul Heckbert's Seed Fill,
+ * from "Graphics Gems", Academic Press, 1990, which fills 4-connected
+ * neighbors.
+ *
+ * The main modification is to make segment stacks growable, through the
+ * helper push and pop functions. There is also a small adaptation to
+ * check explicitly for color2, to meet the Level 9 API.
+ */
+static void gln_linegraphics_push_fill_segment(int y, int xl, int xr, int dy) {
+ /* Clip points outside the graphics context. */
+ if (!(y + dy < 0 || y + dy >= gln_graphics_height)) {
+ int length, allocation;
+
+ length = ++gln_linegraphics_fill_segments_length;
+ allocation = gln_linegraphics_fill_segments_allocation;
+
+ /* Grow the segments stack if required, successively doubling. */
+ if (length > allocation) {
+ size_t bytes;
+
+ allocation = allocation == 0 ? 1 : allocation << 1;
+
+ bytes = allocation * sizeof(*gln_linegraphics_fill_segments);
+ gln_linegraphics_fill_segments =
+ (gln_linegraphics_segment_t *)gln_realloc(gln_linegraphics_fill_segments, bytes);
+ }
+
+ /* Push top of segments stack. */
+ gln_linegraphics_fill_segments[length - 1].y = y;
+ gln_linegraphics_fill_segments[length - 1].xl = xl;
+ gln_linegraphics_fill_segments[length - 1].xr = xr;
+ gln_linegraphics_fill_segments[length - 1].dy = dy;
+
+ /* Write back local dimensions copies. */
+ gln_linegraphics_fill_segments_length = length;
+ gln_linegraphics_fill_segments_allocation = allocation;
+ }
+}
+
+static void gln_linegraphics_pop_fill_segment(int *y, int *xl, int *xr, int *dy) {
+ int length;
+ assert(gln_linegraphics_fill_segments_length > 0);
+
+ length = --gln_linegraphics_fill_segments_length;
+
+ /* Pop top of segments stack. */
+ *y = gln_linegraphics_fill_segments[length].y;
+ *xl = gln_linegraphics_fill_segments[length].xl;
+ *xr = gln_linegraphics_fill_segments[length].xr;
+ *dy = gln_linegraphics_fill_segments[length].dy;
+}
+
+static void gln_linegraphics_fill_4way_if(int x, int y, int colour1, int colour2) {
+ /* Ignore any odd request where there will be no colour changes. */
+ if (colour1 == colour2)
+ return;
+
+ /* Clip fill requests to visible graphics region. */
+ if (x >= 0 && x < gln_graphics_width && y >= 0 && y < gln_graphics_height) {
+ int left, x1, x2, dy, x_lo, x_hi;
+
+ /*
+ * Level 9 API; explicit check for a match against colour2. This also
+ * covers the standard Seed Fill check that old pixel value should not
+ * equal colour1, because of the color1 == colour2 comparison above.
+ */
+ if (gln_linegraphics_get_pixel(x, y) != colour2)
+ return;
+
+ /*
+ * Set up inclusive window dimension to ease algorithm translation.
+ * The original worked with inclusive rectangle limits.
+ */
+ x_lo = 0;
+ x_hi = gln_graphics_width - 1;
+
+ /*
+ * The first of these is "needed in some cases", the second is the seed
+ * segment, popped first.
+ */
+ gln_linegraphics_push_fill_segment(y, x, x, 1);
+ gln_linegraphics_push_fill_segment(y + 1, x, x, -1);
+
+ while (gln_linegraphics_fill_segments_length > 0) {
+ /* Pop segment off stack and add delta to y coord. */
+ gln_linegraphics_pop_fill_segment(&y, &x1, &x2, &dy);
+ y += dy;
+
+ /*
+ * Segment of scan line y-dy for x1<=x<=x2 was previously filled,
+ * now explore adjacent pixels in scan line y.
+ */
+ for (x = x1;
+ x >= x_lo && gln_linegraphics_get_pixel(x, y) == colour2;
+ x--) {
+ gln_linegraphics_set_pixel(x, y, colour1);
+ }
+
+ if (x >= x1)
+ goto skip;
+
+ left = x + 1;
+ if (left < x1) {
+ /* Leak on left? */
+ gln_linegraphics_push_fill_segment(y, left, x1 - 1, -dy);
+ }
+
+ x = x1 + 1;
+ do {
+ for (;
+ x <= x_hi && gln_linegraphics_get_pixel(x, y) == colour2;
+ x++) {
+ gln_linegraphics_set_pixel(x, y, colour1);
+ }
+
+ gln_linegraphics_push_fill_segment(y, left, x - 1, dy);
+
+ if (x > x2 + 1) {
+ /* Leak on right? */
+ gln_linegraphics_push_fill_segment(y, x2 + 1, x - 1, -dy);
+ }
+
+skip:
+ for (x++;
+ x <= x2 && gln_linegraphics_get_pixel(x, y) != colour2;
+ x++)
+ ;
+
+ left = x;
+ } while (x <= x2);
+ }
+ }
+}
+
+
+/*
+ * os_cleargraphics()
+ * os_setcolour()
+ * os_drawline()
+ * os_fill()
+ *
+ * Interpreter entry points for line drawing graphics. All calls to these
+ * are ignored if line drawing mode is not set.
+ */
+void os_cleargraphics(void) {
+ if (gln_graphics_interpreter_state == GLN_GRAPHICS_LINE_MODE)
+ gln_linegraphics_clear_context();
+}
+
+void os_setcolour(int colour, int index) {
+ if (gln_graphics_interpreter_state == GLN_GRAPHICS_LINE_MODE)
+ gln_linegraphics_set_palette_color(colour, index);
+}
+
+void os_drawline(int x1, int y1, int x2, int y2, int colour1, int colour2) {
+ if (gln_graphics_interpreter_state == GLN_GRAPHICS_LINE_MODE)
+ gln_linegraphics_draw_line_if(x1, y1, x2, y2, colour1, colour2);
+}
+
+void os_fill(int x, int y, int colour1, int colour2) {
+ if (gln_graphics_interpreter_state == GLN_GRAPHICS_LINE_MODE)
+ gln_linegraphics_fill_4way_if(x, y, colour1, colour2);
+}
+
+
+/*
+ * gln_linegraphics_process()
+ *
+ * Process as many graphics opcodes as are available, constructing the
+ * resulting image as a bitmap. When complete, treat as normal bitmaps.
+ */
+static void gln_linegraphics_process(void) {
+ /*
+ * If interpreter graphics are not set to line mode, ignore any call that
+ * arrives here.
+ */
+ if (gln_graphics_interpreter_state == GLN_GRAPHICS_LINE_MODE) {
+ int opcodes_count;
+
+ /* Run all the available graphics opcodes. */
+ for (opcodes_count = 0; RunGraphics();) {
+ opcodes_count++;
+ g_vm->glk_tick();
+ }
+
+ /*
+ * If graphics is enabled and we created an image with graphics
+ * opcodes above, open a graphics window and start bitmap display.
+ */
+ if (gln_graphics_enabled && opcodes_count > 0) {
+ if (gln_graphics_open()) {
+ /* Set the new picture flag, and start the updating "thread". */
+ gln_graphics_new_picture = TRUE;
+ gln_graphics_start();
+ }
+ }
+ }
+}
+
+
+/*
+ * gln_linegraphics_cleanup()
+ *
+ * Free memory resources allocated by line graphics functions. Called on
+ * game end.
+ */
+static void gln_linegraphics_cleanup(void) {
+ free(gln_linegraphics_fill_segments);
+ gln_linegraphics_fill_segments = NULL;
+
+ gln_linegraphics_fill_segments_allocation = 0;
+ gln_linegraphics_fill_segments_length = 0;
+}
+
+
+/*---------------------------------------------------------------------*/
+/* Glk picture dispatch (bitmap or line), and timer arbitration */
+/*---------------------------------------------------------------------*/
+
+/*
+ * Note of the current set graphics mode, to detect changes in mode from
+ * the core interpreter.
+ */
+static int gln_graphics_current_mode = -1;
+
+/* Note indicating if the graphics "thread" is temporarily suspended. */
+static int gln_graphics_suspended = FALSE;
+
+
+/*
+ * os_graphics()
+ *
+ * Called by the main interpreter to turn graphics on and off. Mode 0
+ * turns graphics off, mode 1 is line drawing graphics, and mode 2 is
+ * bitmap graphics.
+ *
+ * This function tracks the current state of interpreter graphics setting
+ * using gln_graphics_interpreter_state.
+ */
+void os_graphics(int mode) {
+ /* Ignore the call unless it changes the graphics mode. */
+ if (mode != gln_graphics_current_mode) {
+ /* Set tracked interpreter state given the input mode. */
+ switch (mode) {
+ case 0:
+ gln_graphics_interpreter_state = GLN_GRAPHICS_OFF;
+ break;
+
+ case 1:
+ gln_graphics_interpreter_state = GLN_GRAPHICS_LINE_MODE;
+ break;
+
+ case 2:
+ /* If no graphics bitmaps were detected, ignore this call. */
+ if (!gln_graphics_bitmap_directory
+ || gln_graphics_bitmap_type == NO_BITMAPS)
+ return;
+
+ gln_graphics_interpreter_state = GLN_GRAPHICS_BITMAP_MODE;
+ break;
+ }
+
+ /* Given the interpreter state, update graphics activities. */
+ switch (gln_graphics_interpreter_state) {
+ case GLN_GRAPHICS_OFF:
+
+ /* If currently displaying graphics, stop and close window. */
+ if (gln_graphics_enabled && gln_graphics_are_displayed()) {
+ gln_graphics_stop();
+ gln_graphics_close();
+ }
+ break;
+
+ case GLN_GRAPHICS_LINE_MODE:
+ case GLN_GRAPHICS_BITMAP_MODE:
+
+ /* Create a new graphics context on switch to line mode. */
+ if (gln_graphics_interpreter_state == GLN_GRAPHICS_LINE_MODE)
+ gln_linegraphics_create_context();
+
+ /*
+ * If we have a picture loaded already, restart graphics. If not,
+ * we'll delay this until one is supplied by a call to
+ * os_show_bitmap().
+ */
+ if (gln_graphics_enabled && gln_graphics_bitmap) {
+ if (gln_graphics_open())
+ gln_graphics_restart();
+ }
+ break;
+ }
+
+ /* Note the current mode so changes can be detected. */
+ gln_graphics_current_mode = mode;
+ }
+}
+
+
+/*
+ * gln_arbitrate_request_timer_events()
+ *
+ * Shim function for g_vm->glk_request_timer_events(), this function should be
+ * called by other functional areas in place of the main timer event setting
+ * function. It suspends graphics if busy when setting timer events, and
+ * resumes graphics if necessary when clearing timer events.
+ *
+ * On resuming, it calls the graphics timeout function to simulate the
+ * timeout that has (probably) been missed. This also ensures that tight
+ * loops that enable then disable timers using this function don't lock out
+ * the graphics completely.
+ *
+ * Use only in paired calls, the first non-zero, the second zero, and use
+ * no graphics functions between calls.
+ */
+static void gln_arbitrate_request_timer_events(glui32 millisecs) {
+ if (millisecs > 0) {
+ /* Setting timer events; suspend graphics if currently active. */
+ if (gln_graphics_active) {
+ gln_graphics_suspended = TRUE;
+ gln_graphics_stop();
+ }
+
+ /* Set timer events as requested. */
+ g_vm->glk_request_timer_events(millisecs);
+ } else {
+ /*
+ * Resume graphics if currently suspended, otherwise cancel timer
+ * events as requested by the caller.
+ */
+ if (gln_graphics_suspended) {
+ gln_graphics_suspended = FALSE;
+ gln_graphics_start();
+
+ /* Simulate the "missed" graphics timeout. */
+ gln_graphics_timeout();
+ } else
+ g_vm->glk_request_timer_events(0);
+ }
+}
+
+
+/*---------------------------------------------------------------------*/
+/* Glk port infinite loop detection functions */
+/*---------------------------------------------------------------------*/
+
+/* Short timeout to wait purely in order to get the display updated. */
+static const glui32 GLN_WATCHDOG_FIXUP = 50;
+
+/*
+ * Timestamp of the last watchdog tick call, and timeout. This is used to
+ * monitor the elapsed time since the interpreter made an I/O call. If it
+ * remains silent for long enough, set by the timeout, we'll offer the
+ * option to end the game. A timeout of zero disables the watchdog.
+ */
+static time_t gln_watchdog_monitor = 0;
+static double gln_watchdog_timeout_secs = 0.0;
+
+/*
+ * To save thrashing in time(), we want to check for timeouts less frequently
+ * than we're polled. Here's the control for that.
+ */
+static int gln_watchdog_check_period = 0,
+ gln_watchdog_check_counter = 0;
+
+
+/*
+ * gln_watchdog_start()
+ * gln_watchdog_stop()
+ *
+ * Start and stop watchdog monitoring.
+ */
+static void gln_watchdog_start(int timeout, int period) {
+ assert(timeout > 0 && period > 0);
+
+ gln_watchdog_timeout_secs = (double) timeout;
+ gln_watchdog_check_period = period;
+ gln_watchdog_check_counter = period;
+ gln_watchdog_monitor = g_system->getMillis();
+}
+
+static void gln_watchdog_stop(void) {
+ gln_watchdog_timeout_secs = 0;
+}
+
+
+/*
+ * gln_watchdog_tick()
+ *
+ * Set the watchdog timestamp to the current system time.
+ *
+ * This function should be called just before almost every os_* function
+ * returns to the interpreter, as a means of timing how long the interpreter
+ * dwells in running game code.
+ */
+static void gln_watchdog_tick(void) {
+ gln_watchdog_monitor = g_system->getMillis();
+}
+
+
+/*
+ * gln_watchdog_has_timed_out()
+ *
+ * Check to see if too much time has elapsed since the last tick. If it has,
+ * offer the option to stop the game, and if accepted, return TRUE. Otherwise,
+ * if no timeout, or if the watchdog is disabled, return FALSE.
+ *
+ * This function only checks every N calls; it's called extremely frequently
+ * from opcode handling, and will thrash in time() if it checks on each call.
+ */
+static int gln_watchdog_has_timed_out(void) {
+ /* If loop detection is off or the timeout is set to zero, do nothing. */
+ if (gln_loopcheck_enabled && gln_watchdog_timeout_secs > 0) {
+ time_t now;
+ double delta_time;
+
+ /*
+ * Wait until we've seen enough calls to make a timeout check. If we
+ * haven't, return FALSE, otherwise reset the counter and continue.
+ */
+ if (--gln_watchdog_check_counter > 0)
+ return FALSE;
+ else
+ gln_watchdog_check_counter = gln_watchdog_check_period;
+
+ /*
+ * Determine how much time has passed, and offer to end the game if it
+ * exceeds the allowable timeout.
+ */
+ now = g_system->getMillis();
+ delta_time = (now - gln_watchdog_monitor) / 1000;
+
+ if (delta_time >= gln_watchdog_timeout_secs) {
+ if (gln_confirm("\nThe game may be in an infinite loop. Do you"
+ " want to stop it? [Y or N] ")) {
+ gln_watchdog_tick();
+ return TRUE;
+ }
+
+ /*
+ * If we have timers, set a really short timeout and let it expire.
+ * This is to force a display update with the response of the
+ * confirm -- without this, we may not get a screen update for a
+ * while since at this point the game isn't, by definition, doing
+ * any input or output. If we don't have timers, no biggie.
+ */
+ if (g_vm->glk_gestalt(gestalt_Timer, 0)) {
+ event_t event;
+
+ gln_arbitrate_request_timer_events(GLN_WATCHDOG_FIXUP);
+ gln_event_wait(evtype_Timer, &event);
+ gln_arbitrate_request_timer_events(0);
+ }
+
+ /* Reset the monitor and drop into FALSE return -- stop rejected. */
+ gln_watchdog_tick();
+ }
+ }
+
+ /* No timeout indicated, or offer rejected by the user. */
+ return FALSE;
+}
+
+
+/*---------------------------------------------------------------------*/
+/* Glk port status line functions */
+/*---------------------------------------------------------------------*/
+
+/* Default width used for non-windowing Glk status lines. */
+static const int GLN_DEFAULT_STATUS_WIDTH = 74;
+
+
+/*
+ * gln_status_update()
+ *
+ * Update the information in the status window with the current contents of
+ * the current game identity string, or a default string if no game identity
+ * could be established.
+ */
+static void gln_status_update(void) {
+ uint width, height;
+ assert(gln_status_window);
+
+ g_vm->glk_window_get_size(gln_status_window, &width, &height);
+ if (height > 0) {
+ const char *game_name;
+
+ g_vm->glk_window_clear(gln_status_window);
+ g_vm->glk_window_move_cursor(gln_status_window, 0, 0);
+ g_vm->glk_set_window(gln_status_window);
+
+ /*
+ * Try to establish a game identity to display; if none, use a standard
+ * message instead.
+ */
+ game_name = gln_gameid_get_game_name();
+ g_vm->glk_put_string(game_name ? game_name : "ScummVM GLK Level 9 Game");
+
+ g_vm->glk_set_window(gln_main_window);
+ }
+}
+
+
+/*
+ * gln_status_print()
+ *
+ * Print the current contents of the game identity out in the main window,
+ * if it has changed since the last call. This is for non-windowing Glk
+ * libraries.
+ *
+ * To save memory management hassles, this function uses the CRC functions
+ * to detect changes of game identity string, and gambles a little on the
+ * belief that two games' strings won't have the same CRC.
+ */
+static void gln_status_print(void) {
+ static int is_initialized = FALSE;
+ static gln_uint16 crc = 0;
+
+ const char *game_name;
+
+ /* Get the current game name, and do nothing if none available. */
+ game_name = gln_gameid_get_game_name();
+ if (game_name) {
+ gln_uint16 new_crc;
+
+ /*
+ * If not the first call and the game identity string has not changed,
+ * again, do nothing.
+ */
+ new_crc = gln_get_buffer_crc(game_name, strlen(game_name), 0);
+ if (!is_initialized || new_crc != crc) {
+ int index;
+
+#ifndef GARGLK
+ /* Set fixed width font to try to preserve status line formatting. */
+ g_vm->glk_set_style(style_Preformatted);
+#endif
+
+ /* Bracket, and output the extracted game name. */
+ g_vm->glk_put_string("[ ");
+ g_vm->glk_put_string((char *) game_name);
+
+ for (index = strlen(game_name);
+ index <= GLN_DEFAULT_STATUS_WIDTH; index++)
+ g_vm->glk_put_char(' ');
+ g_vm->glk_put_string(" ]\n");
+
+ crc = new_crc;
+ is_initialized = TRUE;
+ }
+ }
+}
+
+
+/*
+ * gln_status_notify()
+ *
+ * Front end function for updating status. Either updates the status window
+ * or prints the status line to the main window.
+ */
+static void gln_status_notify(void) {
+ if (gln_status_window)
+ gln_status_update();
+ else
+ gln_status_print();
+}
+
+
+/*
+ * gln_status_redraw()
+ *
+ * Redraw the contents of any status window with the buffered status string.
+ * This function should be called on the appropriate Glk window resize and
+ * arrange events.
+ */
+static void gln_status_redraw(void) {
+ if (gln_status_window) {
+ winid_t parent;
+
+ /*
+ * Rearrange the status window, without changing its actual arrangement
+ * in any way. This is a hack to work round incorrect window repainting
+ * in Xglk; it forces a complete repaint of affected windows on Glk
+ * window resize and arrange events, and works in part because Xglk
+ * doesn't check for actual arrangement changes in any way before
+ * invalidating its windows. The hack should be harmless to Glk
+ * libraries other than Xglk, moreover, we're careful to activate it
+ * only on resize and arrange events.
+ */
+ parent = g_vm->glk_window_get_parent(gln_status_window);
+ g_vm->glk_window_set_arrangement(parent,
+ winmethod_Above | winmethod_Fixed, 1, NULL);
+
+ gln_status_update();
+ }
+}
+
+
+/*---------------------------------------------------------------------*/
+/* Glk port output functions */
+/*---------------------------------------------------------------------*/
+
+/*
+ * Flag for if the user entered "help" as their last input, or if hints have
+ * been silenced as a result of already using a Glk command.
+ */
+static int gln_help_requested = FALSE,
+ gln_help_hints_silenced = FALSE;
+
+/*
+ * Output buffer. We receive characters one at a time, and it's a bit
+ * more efficient for everyone if we buffer them, and output a complete
+ * string on a flush call.
+ */
+static char *gln_output_buffer = NULL;
+static int gln_output_allocation = 0,
+ gln_output_length = 0;
+
+/*
+ * Output activity flag. Set when os_printchar() is called, and queried
+ * periodically by os_readchar(). Helps os_readchar() judge whether it must
+ * request input, or when it's being used as a crude scroll control.
+ */
+static int gln_output_activity = FALSE;
+
+/*
+ * Flag to indicate if the last buffer flushed looked like it ended in a
+ * "> " prompt. Some later games switch to this mode after a while, and
+ * it's nice not to duplicate this prompt with our own.
+ */
+static int gln_output_prompt = FALSE;
+
+
+/*
+ * gln_output_notify()
+ *
+ * Register recent text output from the interpreter. This function is
+ * called by os_printchar().
+ */
+static void gln_output_notify(void) {
+ gln_output_activity = TRUE;
+}
+
+
+/*
+ * gln_recent_output()
+ *
+ * Return TRUE if the interpreter has recently output text, FALSE otherwise.
+ * Clears the flag, so that more output text is required before the next
+ * call returns TRUE.
+ */
+static int gln_recent_output(void) {
+ int result;
+
+ result = gln_output_activity;
+ gln_output_activity = FALSE;
+
+ return result;
+}
+
+
+/*
+ * gln_output_register_help_request()
+ * gln_output_silence_help_hints()
+ * gln_output_provide_help_hint()
+ *
+ * Register a request for help, and print a note of how to get Glk command
+ * help from the interpreter unless silenced.
+ */
+static void gln_output_register_help_request(void) {
+ gln_help_requested = TRUE;
+}
+
+static void gln_output_silence_help_hints(void) {
+ gln_help_hints_silenced = TRUE;
+}
+
+static void gln_output_provide_help_hint(void) {
+ if (gln_help_requested && !gln_help_hints_silenced) {
+ g_vm->glk_set_style(style_Emphasized);
+ g_vm->glk_put_string("[Try 'glk help' for help on special interpreter"
+ " commands]\n");
+
+ gln_help_requested = FALSE;
+ g_vm->glk_set_style(style_Normal);
+ }
+}
+
+
+/*
+ * gln_game_prompted()
+ *
+ * Return TRUE if the last game output appears to have been a "> " prompt.
+ * Once called, the flag is reset to FALSE, and requires more game output
+ * to set it again.
+ */
+static int gln_game_prompted(void) {
+ int result;
+
+ result = gln_output_prompt;
+ gln_output_prompt = FALSE;
+
+ return result;
+}
+
+
+/*
+ * gln_detect_game_prompt()
+ *
+ * See if the last non-newline-terminated line in the output buffer seems
+ * to be a prompt, and set the game prompted flag if it does, otherwise
+ * clear it.
+ */
+static void gln_detect_game_prompt(void) {
+ int index;
+
+ gln_output_prompt = FALSE;
+
+ /*
+ * Search for a prompt across any last unterminated buffered line; a prompt
+ * is any non-space character on that line.
+ */
+ for (index = gln_output_length - 1;
+ index >= 0 && gln_output_buffer[index] != '\n'; index--) {
+ if (gln_output_buffer[index] != ' ') {
+ gln_output_prompt = TRUE;
+ break;
+ }
+ }
+}
+
+
+/*
+ * gln_output_delete()
+ *
+ * Delete all buffered output text. Free all malloc'ed buffer memory, and
+ * return the buffer variables to their initial values.
+ */
+static void gln_output_delete(void) {
+ free(gln_output_buffer);
+ gln_output_buffer = NULL;
+ gln_output_allocation = gln_output_length = 0;
+}
+
+
+/*
+ * gln_output_flush()
+ *
+ * Flush any buffered output text to the Glk main window, and clear the
+ * buffer. Check in passing for game prompts that duplicate our's.
+ */
+static void gln_output_flush(void) {
+ assert(g_vm->glk_stream_get_current());
+
+ if (gln_output_length > 0) {
+ /*
+ * See if the game issued a standard prompt, then print the buffer to
+ * the main window. If providing a help hint, position that before
+ * the game's prompt (if any).
+ */
+ gln_detect_game_prompt();
+
+ if (gln_output_prompt) {
+ int index;
+
+ for (index = gln_output_length - 1;
+ index >= 0 && gln_output_buffer[index] != '\n';)
+ index--;
+
+ g_vm->glk_put_buffer(gln_output_buffer, index + 1);
+ gln_output_provide_help_hint();
+ g_vm->glk_put_buffer(gln_output_buffer + index + 1,
+ gln_output_length - index - 1);
+ } else {
+ g_vm->glk_put_buffer(gln_output_buffer, gln_output_length);
+ gln_output_provide_help_hint();
+ }
+
+ gln_output_delete();
+ }
+}
+
+
+/*
+ * os_printchar()
+ *
+ * Buffer a character for eventual printing to the main window.
+ */
+void os_printchar(char c) {
+ int bytes;
+ assert(gln_output_length <= gln_output_allocation);
+
+ /* Grow the output buffer if necessary. */
+ for (bytes = gln_output_allocation; bytes < gln_output_length + 1;)
+ bytes = bytes == 0 ? 1 : bytes << 1;
+
+ if (bytes > gln_output_allocation) {
+ gln_output_buffer = (char *)gln_realloc(gln_output_buffer, bytes);
+ gln_output_allocation = bytes;
+ }
+
+ /*
+ * Add the character to the buffer, handling return as a newline, and
+ * note that the game created some output.
+ */
+ gln_output_buffer[gln_output_length++] = (c == '\r' ? '\n' : c);
+ gln_output_notify();
+}
+
+
+/*
+ * gln_styled_string()
+ * gln_styled_char()
+ * gln_standout_string()
+ * gln_standout_char()
+ * gln_normal_string()
+ * gln_normal_char()
+ * gln_header_string()
+ * gln_banner_string()
+ *
+ * Convenience functions to print strings in assorted styles. A standout
+ * string is one that hints that it's from the interpreter, not the game.
+ */
+static void gln_styled_string(glui32 style, const char *message) {
+ assert(message);
+
+ g_vm->glk_set_style(style);
+ g_vm->glk_put_string(message);
+ g_vm->glk_set_style(style_Normal);
+}
+
+static void gln_styled_char(glui32 style, char c) {
+ char buffer[2];
+
+ buffer[0] = c;
+ buffer[1] = '\0';
+ gln_styled_string(style, buffer);
+}
+
+static void gln_standout_string(const char *message) {
+ gln_styled_string(style_Emphasized, message);
+}
+
+static void gln_standout_char(char c) {
+ gln_styled_char(style_Emphasized, c);
+}
+
+static void gln_normal_string(const char *message) {
+ gln_styled_string(style_Normal, message);
+}
+
+static void gln_normal_char(char c) {
+ gln_styled_char(style_Normal, c);
+}
+
+static void gln_header_string(const char *message) {
+ gln_styled_string(style_Header, message);
+}
+
+static void gln_banner_string(const char *message) {
+ gln_styled_string(style_Subheader, message);
+}
+
+
+/*
+ * os_flush()
+ *
+ * Handle a core interpreter call to flush the output buffer. Because Glk
+ * only flushes its buffers and displays text on g_vm->glk_select(), we can ignore
+ * these calls as long as we call g_vm->glk_output_flush() when reading line or
+ * character input.
+ *
+ * Taking os_flush() at face value can cause game text to appear before status
+ * line text where we are working with a non-windowing Glk, so it's best
+ * ignored where we can.
+ */
+void os_flush(void) {
+}
+
+
+/*---------------------------------------------------------------------*/
+/* Glk command escape functions */
+/*---------------------------------------------------------------------*/
+
+/*
+ * gln_command_script()
+ *
+ * Turn game output scripting (logging) on and off.
+ */
+static void gln_command_script(const char *argument) {
+ assert(argument);
+
+ if (gln_strcasecmp(argument, "on") == 0) {
+ frefid_t fileref;
+
+ if (gln_transcript_stream) {
+ gln_normal_string("Glk transcript is already on.\n");
+ return;
+ }
+
+ fileref = g_vm->glk_fileref_create_by_prompt(fileusage_Transcript
+ | fileusage_TextMode,
+ filemode_WriteAppend, 0);
+ if (!fileref) {
+ gln_standout_string("Glk transcript failed.\n");
+ return;
+ }
+
+ gln_transcript_stream = g_vm->glk_stream_open_file(fileref,
+ filemode_WriteAppend, 0);
+ g_vm->glk_fileref_destroy(fileref);
+ if (!gln_transcript_stream) {
+ gln_standout_string("Glk transcript failed.\n");
+ return;
+ }
+
+ g_vm->glk_window_set_echo_stream(gln_main_window, gln_transcript_stream);
+
+ gln_normal_string("Glk transcript is now on.\n");
+ }
+
+ else if (gln_strcasecmp(argument, "off") == 0) {
+ if (!gln_transcript_stream) {
+ gln_normal_string("Glk transcript is already off.\n");
+ return;
+ }
+
+ g_vm->glk_stream_close(gln_transcript_stream, NULL);
+ gln_transcript_stream = NULL;
+
+ g_vm->glk_window_set_echo_stream(gln_main_window, NULL);
+
+ gln_normal_string("Glk transcript is now off.\n");
+ }
+
+ else if (strlen(argument) == 0) {
+ gln_normal_string("Glk transcript is ");
+ gln_normal_string(gln_transcript_stream ? "on" : "off");
+ gln_normal_string(".\n");
+ }
+
+ else {
+ gln_normal_string("Glk transcript can be ");
+ gln_standout_string("on");
+ gln_normal_string(", or ");
+ gln_standout_string("off");
+ gln_normal_string(".\n");
+ }
+}
+
+
+/*
+ * gln_command_inputlog()
+ *
+ * Turn game input logging on and off.
+ */
+static void gln_command_inputlog(const char *argument) {
+ assert(argument);
+
+ if (gln_strcasecmp(argument, "on") == 0) {
+ frefid_t fileref;
+
+ if (gln_inputlog_stream) {
+ gln_normal_string("Glk input logging is already on.\n");
+ return;
+ }
+
+ fileref = g_vm->glk_fileref_create_by_prompt(fileusage_InputRecord
+ | fileusage_BinaryMode,
+ filemode_WriteAppend, 0);
+ if (!fileref) {
+ gln_standout_string("Glk input logging failed.\n");
+ return;
+ }
+
+ gln_inputlog_stream = g_vm->glk_stream_open_file(fileref,
+ filemode_WriteAppend, 0);
+ g_vm->glk_fileref_destroy(fileref);
+ if (!gln_inputlog_stream) {
+ gln_standout_string("Glk input logging failed.\n");
+ return;
+ }
+
+ gln_normal_string("Glk input logging is now on.\n");
+ }
+
+ else if (gln_strcasecmp(argument, "off") == 0) {
+ if (!gln_inputlog_stream) {
+ gln_normal_string("Glk input logging is already off.\n");
+ return;
+ }
+
+ g_vm->glk_stream_close(gln_inputlog_stream, NULL);
+ gln_inputlog_stream = NULL;
+
+ gln_normal_string("Glk input log is now off.\n");
+ }
+
+ else if (strlen(argument) == 0) {
+ gln_normal_string("Glk input logging is ");
+ gln_normal_string(gln_inputlog_stream ? "on" : "off");
+ gln_normal_string(".\n");
+ }
+
+ else {
+ gln_normal_string("Glk input logging can be ");
+ gln_standout_string("on");
+ gln_normal_string(", or ");
+ gln_standout_string("off");
+ gln_normal_string(".\n");
+ }
+}
+
+
+/*
+ * gln_command_readlog()
+ *
+ * Set the game input log, to read input from a file.
+ */
+static void gln_command_readlog(const char *argument) {
+ assert(argument);
+
+ if (gln_strcasecmp(argument, "on") == 0) {
+ frefid_t fileref;
+
+ if (gln_readlog_stream) {
+ gln_normal_string("Glk read log is already on.\n");
+ return;
+ }
+
+ fileref = g_vm->glk_fileref_create_by_prompt(fileusage_InputRecord
+ | fileusage_BinaryMode,
+ filemode_Read, 0);
+ if (!fileref) {
+ gln_standout_string("Glk read log failed.\n");
+ return;
+ }
+
+ if (!g_vm->glk_fileref_does_file_exist(fileref)) {
+ g_vm->glk_fileref_destroy(fileref);
+ gln_standout_string("Glk read log failed.\n");
+ return;
+ }
+
+ gln_readlog_stream = g_vm->glk_stream_open_file(fileref, filemode_Read, 0);
+ g_vm->glk_fileref_destroy(fileref);
+ if (!gln_readlog_stream) {
+ gln_standout_string("Glk read log failed.\n");
+ return;
+ }
+
+ gln_normal_string("Glk read log is now on.\n");
+ }
+
+ else if (gln_strcasecmp(argument, "off") == 0) {
+ if (!gln_readlog_stream) {
+ gln_normal_string("Glk read log is already off.\n");
+ return;
+ }
+
+ g_vm->glk_stream_close(gln_readlog_stream, NULL);
+ gln_readlog_stream = NULL;
+
+ gln_normal_string("Glk read log is now off.\n");
+ }
+
+ else if (strlen(argument) == 0) {
+ gln_normal_string("Glk read log is ");
+ gln_normal_string(gln_readlog_stream ? "on" : "off");
+ gln_normal_string(".\n");
+ }
+
+ else {
+ gln_normal_string("Glk read log can be ");
+ gln_standout_string("on");
+ gln_normal_string(", or ");
+ gln_standout_string("off");
+ gln_normal_string(".\n");
+ }
+}
+
+
+/*
+ * gln_command_abbreviations()
+ *
+ * Turn abbreviation expansions on and off.
+ */
+static void gln_command_abbreviations(const char *argument) {
+ assert(argument);
+
+ if (gln_strcasecmp(argument, "on") == 0) {
+ if (gln_abbreviations_enabled) {
+ gln_normal_string("Glk abbreviation expansions are already on.\n");
+ return;
+ }
+
+ gln_abbreviations_enabled = TRUE;
+ gln_normal_string("Glk abbreviation expansions are now on.\n");
+ }
+
+ else if (gln_strcasecmp(argument, "off") == 0) {
+ if (!gln_abbreviations_enabled) {
+ gln_normal_string("Glk abbreviation expansions are already off.\n");
+ return;
+ }
+
+ gln_abbreviations_enabled = FALSE;
+ gln_normal_string("Glk abbreviation expansions are now off.\n");
+ }
+
+ else if (strlen(argument) == 0) {
+ gln_normal_string("Glk abbreviation expansions are ");
+ gln_normal_string(gln_abbreviations_enabled ? "on" : "off");
+ gln_normal_string(".\n");
+ }
+
+ else {
+ gln_normal_string("Glk abbreviation expansions can be ");
+ gln_standout_string("on");
+ gln_normal_string(", or ");
+ gln_standout_string("off");
+ gln_normal_string(".\n");
+ }
+}
+
+
+/*
+ * gln_command_graphics()
+ *
+ * Enable or disable graphics more permanently than is done by the main
+ * interpreter. Also, print out a few brief details about the graphics
+ * state of the program.
+ */
+static void gln_command_graphics(const char *argument) {
+ assert(argument);
+
+ if (!gln_graphics_possible) {
+ gln_normal_string("Glk graphics are not available.\n");
+ return;
+ }
+
+ if (gln_strcasecmp(argument, "on") == 0) {
+ if (gln_graphics_enabled) {
+ gln_normal_string("Glk graphics are already on.\n");
+ return;
+ }
+
+ gln_graphics_enabled = TRUE;
+
+ /* If a picture is loaded, call the restart function to repaint it. */
+ if (gln_graphics_picture_is_available()) {
+ if (!gln_graphics_open()) {
+ gln_normal_string("Glk graphics error.\n");
+ return;
+ }
+ gln_graphics_restart();
+ }
+
+ gln_normal_string("Glk graphics are now on.\n");
+ }
+
+ else if (gln_strcasecmp(argument, "off") == 0) {
+ if (!gln_graphics_enabled) {
+ gln_normal_string("Glk graphics are already off.\n");
+ return;
+ }
+
+ /*
+ * Set graphics to disabled, and stop any graphics processing. Close
+ * the graphics window.
+ */
+ gln_graphics_enabled = FALSE;
+ gln_graphics_stop();
+ gln_graphics_close();
+
+ gln_normal_string("Glk graphics are now off.\n");
+ }
+
+ else if (strlen(argument) == 0) {
+ gln_normal_string("Glk graphics are available,");
+ gln_normal_string(gln_graphics_enabled
+ ? " and enabled.\n" : " but disabled.\n");
+
+ if (gln_graphics_picture_is_available()) {
+ int width, height;
+
+ if (gln_graphics_get_picture_details(&width, &height)) {
+ char buffer[16];
+
+ gln_normal_string("There is a picture loaded, ");
+
+ sprintf(buffer, "%d", width);
+ gln_normal_string(buffer);
+ gln_normal_string(" by ");
+
+ sprintf(buffer, "%d", height);
+ gln_normal_string(buffer);
+
+ gln_normal_string(" pixels.\n");
+ }
+ }
+
+ if (!gln_graphics_interpreter_enabled())
+ gln_normal_string("Interpreter graphics are disabled.\n");
+
+ if (gln_graphics_enabled && gln_graphics_are_displayed()) {
+ const char *bitmap_type;
+ int color_count, is_active;
+
+ if (gln_graphics_get_rendering_details(&bitmap_type,
+ &color_count, &is_active)) {
+ char buffer[16];
+
+ gln_normal_string("Graphics are ");
+ gln_normal_string(is_active ? "active, " : "displayed, ");
+
+ sprintf(buffer, "%d", color_count);
+ gln_normal_string(buffer);
+ gln_normal_string(" colours");
+
+ if (bitmap_type) {
+ gln_normal_string(", ");
+ gln_normal_string(bitmap_type);
+ gln_normal_string(" bitmaps");
+ }
+ gln_normal_string(".\n");
+ } else
+ gln_normal_string("Graphics are being displayed.\n");
+ }
+
+ if (gln_graphics_enabled && !gln_graphics_are_displayed())
+ gln_normal_string("Graphics are not being displayed.\n");
+ }
+
+ else {
+ gln_normal_string("Glk graphics can be ");
+ gln_standout_string("on");
+ gln_normal_string(", or ");
+ gln_standout_string("off");
+ gln_normal_string(".\n");
+ }
+}
+
+
+/*
+ * gln_command_loopchecks()
+ *
+ * Turn loop checking (for game infinite loops) on and off.
+ */
+static void gln_command_loopchecks(const char *argument) {
+ assert(argument);
+
+ if (gln_strcasecmp(argument, "on") == 0) {
+ if (gln_loopcheck_enabled) {
+ gln_normal_string("Glk loop detection is already on.\n");
+ return;
+ }
+
+ gln_loopcheck_enabled = TRUE;
+ gln_normal_string("Glk loop detection is now on.\n");
+ }
+
+ else if (gln_strcasecmp(argument, "off") == 0) {
+ if (!gln_loopcheck_enabled) {
+ gln_normal_string("Glk loop detection is already off.\n");
+ return;
+ }
+
+ gln_loopcheck_enabled = FALSE;
+ gln_normal_string("Glk loop detection is now off.\n");
+ }
+
+ else if (strlen(argument) == 0) {
+ gln_normal_string("Glk loop detection is ");
+ gln_normal_string(gln_loopcheck_enabled ? "on" : "off");
+ gln_normal_string(".\n");
+ }
+
+ else {
+ gln_normal_string("Glk loop detection can be ");
+ gln_standout_string("on");
+ gln_normal_string(", or ");
+ gln_standout_string("off");
+ gln_normal_string(".\n");
+ }
+}
+
+
+/*
+ * gln_command_locals()
+ *
+ * Turn local interpretation of "quit" etc. on and off.
+ */
+static void gln_command_locals(const char *argument) {
+ assert(argument);
+
+ if (gln_strcasecmp(argument, "on") == 0) {
+ if (gln_intercept_enabled) {
+ gln_normal_string("Glk local commands are already on.\n");
+ return;
+ }
+
+ gln_intercept_enabled = TRUE;
+ gln_normal_string("Glk local commands are now on.\n");
+ }
+
+ else if (gln_strcasecmp(argument, "off") == 0) {
+ if (!gln_intercept_enabled) {
+ gln_normal_string("Glk local commands are already off.\n");
+ return;
+ }
+
+ gln_intercept_enabled = FALSE;
+ gln_normal_string("Glk local commands are now off.\n");
+ }
+
+ else if (strlen(argument) == 0) {
+ gln_normal_string("Glk local commands are ");
+ gln_normal_string(gln_intercept_enabled ? "on" : "off");
+ gln_normal_string(".\n");
+ }
+
+ else {
+ gln_normal_string("Glk local commands can be ");
+ gln_standout_string("on");
+ gln_normal_string(", or ");
+ gln_standout_string("off");
+ gln_normal_string(".\n");
+ }
+}
+
+
+/*
+ * gln_command_prompts()
+ *
+ * Turn the extra "> " prompt output on and off.
+ */
+static void gln_command_prompts(const char *argument) {
+ assert(argument);
+
+ if (gln_strcasecmp(argument, "on") == 0) {
+ if (gln_prompt_enabled) {
+ gln_normal_string("Glk extra prompts are already on.\n");
+ return;
+ }
+
+ gln_prompt_enabled = TRUE;
+ gln_normal_string("Glk extra prompts are now on.\n");
+
+ /* Check for a game prompt to clear the flag. */
+ gln_game_prompted();
+ }
+
+ else if (gln_strcasecmp(argument, "off") == 0) {
+ if (!gln_prompt_enabled) {
+ gln_normal_string("Glk extra prompts are already off.\n");
+ return;
+ }
+
+ gln_prompt_enabled = FALSE;
+ gln_normal_string("Glk extra prompts are now off.\n");
+ }
+
+ else if (strlen(argument) == 0) {
+ gln_normal_string("Glk extra prompts are ");
+ gln_normal_string(gln_prompt_enabled ? "on" : "off");
+ gln_normal_string(".\n");
+ }
+
+ else {
+ gln_normal_string("Glk extra prompts can be ");
+ gln_standout_string("on");
+ gln_normal_string(", or ");
+ gln_standout_string("off");
+ gln_normal_string(".\n");
+ }
+}
+
+
+/*
+ * gln_command_print_version_number()
+ * gln_command_version()
+ *
+ * Print out the Glk library version number.
+ */
+static void gln_command_print_version_number(glui32 version) {
+ char buffer[64];
+
+ sprintf(buffer, "%lu.%lu.%lu",
+ (unsigned long) version >> 16,
+ (unsigned long)(version >> 8) & 0xff,
+ (unsigned long) version & 0xff);
+ gln_normal_string(buffer);
+}
+
+static void gln_command_version(const char *argument) {
+ glui32 version;
+ assert(argument);
+
+ gln_normal_string("This is version ");
+ gln_command_print_version_number(GLN_PORT_VERSION);
+ gln_normal_string(" of the Glk Level 9 port.\n");
+
+ version = g_vm->glk_gestalt(gestalt_Version, 0);
+ gln_normal_string("The Glk library version is ");
+ gln_command_print_version_number(version);
+ gln_normal_string(".\n");
+}
+
+
+/*
+ * gln_command_commands()
+ *
+ * Turn command escapes off. Once off, there's no way to turn them back on.
+ * Commands must be on already to enter this function.
+ */
+static void gln_command_commands(const char *argument) {
+ assert(argument);
+
+ if (gln_strcasecmp(argument, "on") == 0) {
+ gln_normal_string("Glk commands are already on.\n");
+ }
+
+ else if (gln_strcasecmp(argument, "off") == 0) {
+ gln_commands_enabled = FALSE;
+ gln_normal_string("Glk commands are now off.\n");
+ }
+
+ else if (strlen(argument) == 0) {
+ gln_normal_string("Glk commands are ");
+ gln_normal_string(gln_commands_enabled ? "on" : "off");
+ gln_normal_string(".\n");
+ }
+
+ else {
+ gln_normal_string("Glk commands can be ");
+ gln_standout_string("on");
+ gln_normal_string(", or ");
+ gln_standout_string("off");
+ gln_normal_string(".\n");
+ }
+}
+
+
+/* Glk subcommands and handler functions. */
+typedef const struct {
+ const char *const command; /* Glk subcommand. */
+ void (* const handler)(const char *argument); /* Subcommand handler. */
+ const int takes_argument; /* Argument flag. */
+} gln_command_t;
+typedef gln_command_t *gln_commandref_t;
+
+static void gln_command_summary(const char *argument);
+static void gln_command_help(const char *argument);
+
+static gln_command_t GLN_COMMAND_TABLE[] = {
+ {"summary", gln_command_summary, FALSE},
+ {"script", gln_command_script, TRUE},
+ {"inputlog", gln_command_inputlog, TRUE},
+ {"readlog", gln_command_readlog, TRUE},
+ {"abbreviations", gln_command_abbreviations, TRUE},
+ {"graphics", gln_command_graphics, TRUE},
+ {"loopchecks", gln_command_loopchecks, TRUE},
+ {"locals", gln_command_locals, TRUE},
+ {"prompts", gln_command_prompts, TRUE},
+ {"version", gln_command_version, FALSE},
+ {"commands", gln_command_commands, TRUE},
+ {"help", gln_command_help, TRUE},
+ {NULL, NULL, FALSE}
+};
+
+
+/*
+ * gln_command_summary()
+ *
+ * Report all current Glk settings.
+ */
+static void
+gln_command_summary(const char *argument) {
+ gln_commandref_t entry;
+ assert(argument);
+
+ /*
+ * Call handlers that have status to report with an empty argument,
+ * prompting each to print its current setting.
+ */
+ for (entry = GLN_COMMAND_TABLE; entry->command; entry++) {
+ if (entry->handler == gln_command_summary
+ || entry->handler == gln_command_help)
+ continue;
+
+ entry->handler("");
+ }
+}
+
+
+/*
+ * gln_command_help()
+ *
+ * Document the available Glk commands.
+ */
+static void
+gln_command_help(const char *command) {
+ gln_commandref_t entry, matched;
+ assert(command);
+
+ if (strlen(command) == 0) {
+ gln_normal_string("Glk commands are");
+ for (entry = GLN_COMMAND_TABLE; entry->command; entry++) {
+ gln_commandref_t next;
+
+ next = entry + 1;
+ gln_normal_string(next->command ? " " : " and ");
+ gln_standout_string(entry->command);
+ gln_normal_string(next->command ? "," : ".\n\n");
+ }
+
+ gln_normal_string("Glk commands may be abbreviated, as long as"
+ " the abbreviation is unambiguous. Use ");
+ gln_standout_string("glk help");
+ gln_normal_string(" followed by a Glk command name for help on that"
+ " command.\n");
+ return;
+ }
+
+ matched = NULL;
+ for (entry = GLN_COMMAND_TABLE; entry->command; entry++) {
+ if (gln_strncasecmp(command, entry->command, strlen(command)) == 0) {
+ if (matched) {
+ gln_normal_string("The Glk command ");
+ gln_standout_string(command);
+ gln_normal_string(" is ambiguous. Try ");
+ gln_standout_string("glk help");
+ gln_normal_string(" for more information.\n");
+ return;
+ }
+ matched = entry;
+ }
+ }
+ if (!matched) {
+ gln_normal_string("The Glk command ");
+ gln_standout_string(command);
+ gln_normal_string(" is not valid. Try ");
+ gln_standout_string("glk help");
+ gln_normal_string(" for more information.\n");
+ return;
+ }
+
+ if (matched->handler == gln_command_summary) {
+ gln_normal_string("Prints a summary of all the current Glk Level 9"
+ " settings.\n");
+ }
+
+ else if (matched->handler == gln_command_script) {
+ gln_normal_string("Logs the game's output to a file.\n\nUse ");
+ gln_standout_string("glk script on");
+ gln_normal_string(" to begin logging game output, and ");
+ gln_standout_string("glk script off");
+ gln_normal_string(" to end it. Glk Level 9 will ask you for a file"
+ " when you turn scripts on.\n");
+ }
+
+ else if (matched->handler == gln_command_inputlog) {
+ gln_normal_string("Records the commands you type into a game.\n\nUse ");
+ gln_standout_string("glk inputlog on");
+ gln_normal_string(", to begin recording your commands, and ");
+ gln_standout_string("glk inputlog off");
+ gln_normal_string(" to turn off input logs. You can play back"
+ " recorded commands into a game with the ");
+ gln_standout_string("glk readlog");
+ gln_normal_string(" command.\n");
+ }
+
+ else if (matched->handler == gln_command_readlog) {
+ gln_normal_string("Plays back commands recorded with ");
+ gln_standout_string("glk inputlog on");
+ gln_normal_string(".\n\nUse ");
+ gln_standout_string("glk readlog on");
+ gln_normal_string(". Command play back stops at the end of the"
+ " file. You can also play back commands from a"
+ " text file created using any standard editor.\n");
+ }
+
+ else if (matched->handler == gln_command_abbreviations) {
+ gln_normal_string("Controls abbreviation expansion.\n\nGlk Level 9"
+ " automatically expands several standard single"
+ " letter abbreviations for you; for example, \"x\""
+ " becomes \"examine\". Use ");
+ gln_standout_string("glk abbreviations on");
+ gln_normal_string(" to turn this feature on, and ");
+ gln_standout_string("glk abbreviations off");
+ gln_normal_string(" to turn it off. While the feature is on, you"
+ " can bypass abbreviation expansion for an"
+ " individual game command by prefixing it with a"
+ " single quote.\n");
+ }
+
+ else if (matched->handler == gln_command_graphics) {
+ gln_normal_string("Turns interpreter graphics on and off.\n\nUse ");
+ gln_standout_string("glk graphics on");
+ gln_normal_string(" to enable interpreter graphics, and ");
+ gln_standout_string("glk graphics off");
+ gln_normal_string(" to turn graphics off and close the graphics window."
+ " This control works slightly differently to the"
+ " 'graphics' command in Level 9 games themselves; the"
+ " game's 'graphics' command may disable new images,"
+ " but leave old ones displayed. For graphics to be"
+ " displayed, they must be turned on in both the game"
+ " and the interpreter.\n");
+ }
+
+ else if (matched->handler == gln_command_loopchecks) {
+ gln_normal_string("Controls game infinite loop monitoring.\n\n"
+ "Some Level 9 games can enter an infinite loop if they"
+ " have nothing better to do. A game might do this"
+ " after it has ended, should you decline its offer"
+ " to rerun. To avoid the need to kill the interpreter"
+ " completely if a game does this, Glk Level 9 monitors"
+ " a game's input and output, and offers the option to"
+ " end the program gracefully if a game is silent for"
+ " a few seconds. Use ");
+ gln_standout_string("glk loopchecks on");
+ gln_normal_string(" to turn this feature on, and ");
+ gln_standout_string("glk loopchecks off");
+ gln_normal_string(" to turn it off.\n");
+ }
+
+ else if (matched->handler == gln_command_locals) {
+ gln_normal_string("Controls interception of selected game commands.\n\n"
+ "Some Level 9 games were written for cassette tape"
+ " based microprocessor systems, and the way in which"
+ " they save, restore, and restart games can reflect"
+ " this. There is also often no straightforward way"
+ " to quit from a game.\n\nTo make playing a Level 9"
+ " game appear similar to other systems, Glk Level 9"
+ " will trap the commands 'quit', 'restart', 'save',"
+ " 'restore', and 'load' (a synonym for 'restore') and"
+ " handle them locally within the interpreter. Use ");
+ gln_standout_string("glk locals on");
+ gln_normal_string(" to turn this feature on, and ");
+ gln_standout_string("glk locals off");
+ gln_normal_string(" to turn it off.\n");
+ }
+
+ else if (matched->handler == gln_command_prompts) {
+ gln_normal_string("Controls extra input prompting.\n\n"
+ "Glk Level 9 can issue a replacement '>' input"
+ " prompt if it detects that the game hasn't prompted"
+ " after, say, an empty input line. Use ");
+ gln_standout_string("glk prompts on");
+ gln_normal_string(" to turn this feature on, and ");
+ gln_standout_string("glk prompts off");
+ gln_normal_string(" to turn it off.\n");
+ }
+
+ else if (matched->handler == gln_command_version) {
+ gln_normal_string("Prints the version numbers of the Glk library"
+ " and the Glk Level 9 port.\n");
+ }
+
+ else if (matched->handler == gln_command_commands) {
+ gln_normal_string("Turn off Glk commands.\n\nUse ");
+ gln_standout_string("glk commands off");
+ gln_normal_string(" to disable all Glk commands, including this one."
+ " Once turned off, there is no way to turn Glk"
+ " commands back on while inside the game.\n");
+ }
+
+ else if (matched->handler == gln_command_help)
+ gln_command_help("");
+
+ else
+ gln_normal_string("There is no help available on that Glk command."
+ " Sorry.\n");
+}
+
+
+/*
+ * gln_command_escape()
+ *
+ * This function is handed each input line. If the line contains a specific
+ * Glk port command, handle it and return TRUE, otherwise return FALSE.
+ */
+static int gln_command_escape(const char *string) {
+ int posn;
+ char *string_copy, *command, *argument;
+ assert(string);
+
+ /*
+ * Return FALSE if the string doesn't begin with the Glk command escape
+ * introducer.
+ */
+ posn = strspn(string, "\t ");
+ if (gln_strncasecmp(string + posn, "glk", strlen("glk")) != 0)
+ return FALSE;
+
+ /* Take a copy of the string, without any leading space or introducer. */
+ string_copy = (char *)gln_malloc(strlen(string + posn) + 1 - strlen("glk"));
+ strcpy(string_copy, string + posn + strlen("glk"));
+
+ /*
+ * Find the subcommand; the first word in the string copy. Find its end,
+ * and ensure it terminates with a NUL.
+ */
+ posn = strspn(string_copy, "\t ");
+ command = string_copy + posn;
+ posn += strcspn(string_copy + posn, "\t ");
+ if (string_copy[posn] != '\0')
+ string_copy[posn++] = '\0';
+
+ /*
+ * Now find any argument data for the command, ensuring it too terminates
+ * with a NUL.
+ */
+ posn += strspn(string_copy + posn, "\t ");
+ argument = string_copy + posn;
+ posn += strcspn(string_copy + posn, "\t ");
+ string_copy[posn] = '\0';
+
+ /*
+ * Try to handle the command and argument as a Glk subcommand. If it
+ * doesn't run unambiguously, print command usage. Treat an empty command
+ * as "help".
+ */
+ if (strlen(command) > 0) {
+ gln_commandref_t entry, matched;
+ int matches;
+
+ /*
+ * Search for the first unambiguous table command string matching
+ * the command passed in.
+ */
+ matches = 0;
+ matched = NULL;
+ for (entry = GLN_COMMAND_TABLE; entry->command; entry++) {
+ if (gln_strncasecmp(command, entry->command, strlen(command)) == 0) {
+ matches++;
+ matched = entry;
+ }
+ }
+
+ /* If the match was unambiguous, call the command handler. */
+ if (matches == 1) {
+ gln_normal_char('\n');
+ matched->handler(argument);
+
+ if (!matched->takes_argument && strlen(argument) > 0) {
+ gln_normal_string("[The ");
+ gln_standout_string(matched->command);
+ gln_normal_string(" command ignores arguments.]\n");
+ }
+ }
+
+ /* No match, or the command was ambiguous. */
+ else {
+ gln_normal_string("\nThe Glk command ");
+ gln_standout_string(command);
+ gln_normal_string(" is ");
+ gln_normal_string(matches == 0 ? "not valid" : "ambiguous");
+ gln_normal_string(". Try ");
+ gln_standout_string("glk help");
+ gln_normal_string(" for more information.\n");
+ }
+ } else {
+ gln_normal_char('\n');
+ gln_command_help("");
+ }
+
+ /* The string contained a Glk command; return TRUE. */
+ free(string_copy);
+ return TRUE;
+}
+
+
+/*
+ * gln_command_intercept()
+ *
+ * The Level 9 games handle the commands "quit" and "restart" oddly, and
+ * somewhat similarly. Both prompt "Press SPACE to play again", and then
+ * ignore all characters except space. This makes it especially hard to exit
+ * from a game without killing the interpreter process. They also handle
+ * "restore" via an odd security mechanism which has no real place here (the
+ * base Level 9 interpreter sidesteps this with its "#restore" command, and
+ * has some bugs in "save").
+ *
+ * To try to improve these, here we'll catch and special case the input lines
+ * "quit", "save", "restore", and "restart". "Load" is a synonym for
+ * "restore".
+ *
+ * On "quit" or "restart", the function sets the interpreter stop reason
+ * code, stops the current game run. On "save" or "restore" it calls the
+ * appropriate internal interpreter function.
+ *
+ * The return value is TRUE if an intercepted command was found, otherwise
+ * FALSE.
+ */
+static int gln_command_intercept(char *string) {
+ int posn, result;
+ char *string_copy, *trailing;
+ assert(string);
+
+ result = FALSE;
+
+ /* Take a copy of the string, excluding any leading whitespace. */
+ posn = strspn(string, "\t ");
+ string_copy = (char *)gln_malloc(strlen(string + posn) + 1);
+ strcpy(string_copy, string + posn);
+
+ /*
+ * Find the space or NUL after the first word, and check that anything
+ * after it the first word is whitespace only.
+ */
+ posn = strcspn(string_copy, "\t ");
+ trailing = string_copy + posn;
+ if (trailing[strspn(trailing, "\t ")] == '\0') {
+ /* Terminate the string copy for easy comparisons. */
+ string_copy[posn] = '\0';
+
+ /* If this command was "quit", confirm, then call StopGame(). */
+ if (gln_strcasecmp(string_copy, "quit") == 0) {
+ if (gln_confirm("\nDo you really want to stop? [Y or N] ")) {
+ gln_stop_reason = STOP_EXIT;
+ StopGame();
+ }
+ result = TRUE;
+ }
+
+ /* If this command was "restart", confirm, then call StopGame(). */
+ else if (gln_strcasecmp(string_copy, "restart") == 0) {
+ if (gln_confirm("\nDo you really want to restart? [Y or N] ")) {
+ gln_stop_reason = STOP_RESTART;
+ StopGame();
+ }
+ result = TRUE;
+ }
+
+ /* If this command was "save", simply call save(). */
+ else if (gln_strcasecmp(string_copy, "save") == 0) {
+ gln_standout_string("\nSaving using interpreter\n\n");
+ save();
+ result = TRUE;
+ }
+
+ /* If this command was "restore" or "load", call restore(). */
+ else if (gln_strcasecmp(string_copy, "restore") == 0
+ || gln_strcasecmp(string_copy, "load") == 0) {
+ gln_standout_string("\nRestoring using interpreter\n\n");
+ restore();
+ result = TRUE;
+ }
+ }
+
+ free(string_copy);
+ return result;
+}
+
+
+/*---------------------------------------------------------------------*/
+/* Glk port input functions */
+/*---------------------------------------------------------------------*/
+
+/* Ctrl-C and Ctrl-U character constants. */
+static const char GLN_CONTROL_C = '\003',
+ GLN_CONTROL_U = '\025';
+
+/*
+ * os_readchar() call count limit, after which we really read a character.
+ * Also, call count limit on os_stoplist calls, after which we poll for a
+ * character press to stop the listing, and a stoplist poll timeout.
+ */
+static const int GLN_READCHAR_LIMIT = 1024,
+ GLN_STOPLIST_LIMIT = 10;
+static const glui32 GLN_STOPLIST_TIMEOUT = 50;
+
+/* Quote used to suppress abbreviation expansion and local commands. */
+static const char GLN_QUOTED_INPUT = '\'';
+
+
+/*
+ * Note of when the interpreter is in list output. The last element of any
+ * list generally lacks a terminating newline, and unless we do something
+ * special with it, it'll look like a valid prompt to us.
+ */
+static int gln_inside_list = FALSE;
+
+
+/* Table of single-character command abbreviations. */
+typedef const struct {
+ const char abbreviation; /* Abbreviation character. */
+ const char *const expansion; /* Expansion string. */
+} gln_abbreviation_t;
+typedef gln_abbreviation_t *gln_abbreviationref_t;
+
+static gln_abbreviation_t GLN_ABBREVIATIONS[] = {
+ {'c', "close"}, {'g', "again"}, {'i', "inventory"},
+ {'k', "attack"}, {'l', "look"}, {'p', "open"},
+ {'q', "quit"}, {'r', "drop"}, {'t', "take"},
+ {'x', "examine"}, {'y', "yes"}, {'z', "wait"},
+ {'\0', NULL}
+};
+
+
+/*
+ * gln_expand_abbreviations()
+ *
+ * Expand a few common one-character abbreviations commonly found in other
+ * game systems, but not always normal in Level 9 games.
+ */
+static void gln_expand_abbreviations(char *buffer, int size) {
+ char *command, abbreviation;
+ const char *expansion;
+ gln_abbreviationref_t entry;
+ assert(buffer);
+
+ /* Ignore anything that isn't a single letter command. */
+ command = buffer + strspn(buffer, "\t ");
+ if (!(strlen(command) == 1
+ || (strlen(command) > 1 && Common::isSpace(command[1]))))
+ return;
+
+ /* Scan the abbreviations table for a match. */
+ abbreviation = g_vm->glk_char_to_lower((unsigned char) command[0]);
+ expansion = NULL;
+ for (entry = GLN_ABBREVIATIONS; entry->expansion; entry++) {
+ if (entry->abbreviation == abbreviation) {
+ expansion = entry->expansion;
+ break;
+ }
+ }
+
+ /*
+ * If a match found, check for a fit, then replace the character with the
+ * expansion string.
+ */
+ if (expansion) {
+ if (strlen(buffer) + strlen(expansion) - 1 >= (uint)size)
Commit: 8b0250cb09629c72aad6e23701b9fc40c8bc7a3a
https://github.com/scummvm/scummvm/commit/8b0250cb09629c72aad6e23701b9fc40c8bc7a3a
Author: Paul Gilbert (dreammaster at scummvm.org)
Date: 2019-10-26T11:13:29-07:00
Commit Message:
GLK: LEVEL9: Janitorial
Changed paths:
engines/glk/level9/level9_main.cpp
engines/glk/level9/os_glk.cpp
diff --git a/engines/glk/level9/level9_main.cpp b/engines/glk/level9/level9_main.cpp
index 719d05b..7aa95a7 100644
--- a/engines/glk/level9/level9_main.cpp
+++ b/engines/glk/level9/level9_main.cpp
@@ -152,7 +152,7 @@ struct L9V1GameInfo {
L9BYTE dictVal1, dictVal2;
int dictStart, L9Ptrs[5], absData, msgStart, msgLen;
};
-struct L9V1GameInfo L9V1Games[] = {
+const L9V1GameInfo L9V1Games[] = {
0x1a, 0x24, 301, 0x0000, -0x004b, 0x0080, -0x002b, 0x00d0, 0x03b0, 0x0f80, 0x4857, /* Colossal Adventure */
0x20, 0x3b, 283, -0x0583, 0x0000, -0x0508, -0x04e0, 0x0000, 0x0800, 0x1000, 0x39d1, /* Adventure Quest */
0x14, 0xff, 153, -0x00d6, 0x0000, 0x0000, 0x0000, 0x0000, 0x0a20, 0x16bf, 0x420d, /* Dungeon Adventure */
@@ -164,7 +164,7 @@ int L9V1Game = -1;
/* Prototypes */
L9BOOL LoadGame2(char *filename, char *picname);
-int getlongcode(void);
+int getlongcode();
L9BOOL GetWordV2(char *buff, int Word);
L9BOOL GetWordV3(char *buff, int Word);
void show_picture(int pic);
@@ -174,7 +174,7 @@ void show_picture(int pic);
#define CODEFOLLOWFILE "c:\\temp\\level9.txt"
FILE *f;
L9UINT16 *cfvar, *cfvar2;
-char *codes[] = {
+const char *codes[] = {
"Goto",
"intgosub",
"intreturn",
@@ -208,7 +208,7 @@ char *codes[] = {
"ilins",
"ilins",
};
-char *functions[] = {
+const char *functions[] = {
"calldriver",
"L9Random",
"save",
@@ -216,7 +216,7 @@ char *functions[] = {
"clearworkspace",
"clearstack"
};
-char *drivercalls[] = {
+const char *drivercalls[] = {
"init",
"drivercalcchecksum",
"driveroswrch",
@@ -260,7 +260,7 @@ void initdict(L9BYTE *ptr) {
unpackcount = 8;
}
-char getdictionarycode(void) {
+char getdictionarycode() {
if (unpackcount != 8) return unpackbuf[unpackcount++];
else {
/* unpackbytes */
@@ -287,7 +287,7 @@ int getdictionary(int d0) {
else return d0 + 0x61;
}
-int getlongcode(void) {
+int getlongcode() {
int d0, d1;
d0 = getdictionarycode();
if (d0 == 0x10) {
@@ -626,7 +626,7 @@ void L9Allocate(L9BYTE **ptr, L9UINT32 Size) {
}
}
-void FreeMemory(void) {
+void FreeMemory() {
if (startfile) {
free(startfile);
startfile = NULL;
@@ -1402,24 +1402,24 @@ L9BOOL intinitialise(const char *filename, char *picname) {
return TRUE;
}
-L9BOOL checksumgamedata(void) {
+L9BOOL checksumgamedata() {
return calcchecksum(startdata, L9WORD(startdata) + 1) == 0;
}
-L9UINT16 movewa5d0(void) {
+L9UINT16 movewa5d0() {
L9UINT16 ret = L9WORD(codeptr);
codeptr += 2;
return ret;
}
-L9UINT16 getcon(void) {
+L9UINT16 getcon() {
if (code & 64) {
/* getconsmall */
return *codeptr++;
} else return movewa5d0();
}
-L9BYTE *getaddr(void) {
+L9BYTE *getaddr() {
if (code & 0x20) {
/* getaddrshort */
signed char diff = *codeptr++;
@@ -1429,7 +1429,7 @@ L9BYTE *getaddr(void) {
}
}
-L9UINT16 *getvar(void) {
+L9UINT16 *getvar() {
#ifndef CODEFOLLOW
return workspace.vartable + *codeptr++;
#else
@@ -1438,7 +1438,7 @@ L9UINT16 *getvar(void) {
#endif
}
-void Goto(void) {
+void Goto() {
L9BYTE *target = getaddr();
if (target == codeptr - 2)
Running = FALSE; /* Endless loop! */
@@ -1446,7 +1446,7 @@ void Goto(void) {
codeptr = target;
}
-void intgosub(void) {
+void intgosub() {
L9BYTE *newcodeptr = getaddr();
if (workspace.stackptr == STACKSIZE) {
error("\rStack overflow error\r");
@@ -1457,7 +1457,7 @@ void intgosub(void) {
codeptr = newcodeptr;
}
-void intreturn(void) {
+void intreturn() {
if (workspace.stackptr == 0) {
error("\rStack underflow error\r");
Running = FALSE;
@@ -1466,18 +1466,18 @@ void intreturn(void) {
codeptr = acodeptr + workspace.stack[--workspace.stackptr];
}
-void printnumber(void) {
+void printnumber() {
printdecimald0(*getvar());
}
-void messagec(void) {
+void messagec() {
if (L9GameType <= L9_V2)
printmessageV2(getcon());
else
printmessage(getcon());
}
-void messagev(void) {
+void messagev() {
if (L9GameType <= L9_V2)
printmessageV2(*getvar());
else
@@ -1711,7 +1711,7 @@ void ramload(int i) {
memmove(workspace.vartable, ramsavearea + i, sizeof(SaveStruct));
}
-void calldriver(void) {
+void calldriver() {
L9BYTE *a6 = list9startptr;
int d0 = *a6++;
#ifdef CODEFOLLOW
@@ -1744,7 +1744,7 @@ void calldriver(void) {
} else driver(d0, a6);
}
-void L9Random(void) {
+void L9Random() {
#ifdef CODEFOLLOW
fprintf(f, " %d", randomseed);
#endif
@@ -1755,7 +1755,7 @@ void L9Random(void) {
#endif
}
-void save(void) {
+void save() {
L9UINT16 checksum;
int i;
#ifdef L9DEBUG
@@ -1804,7 +1804,7 @@ L9BOOL CheckFile(GameState *gs) {
return FALSE;
}
-void NormalRestore(void) {
+void NormalRestore() {
GameState temp;
int Bytes;
#ifdef L9DEBUG
@@ -1831,7 +1831,7 @@ void NormalRestore(void) {
} else printstring("\rUnable to restore game.\r");
}
-void restore(void) {
+void restore() {
int Bytes;
GameState temp;
if (os_load_file((L9BYTE *) &temp, &Bytes, sizeof(GameState))) {
@@ -1851,7 +1851,7 @@ void restore(void) {
} else printstring("\rUnable to restore game.\r");
}
-void playback(void) {
+void playback() {
if (scriptfile)
delete scriptfile;
scriptfile = os_open_script_file();
@@ -1924,7 +1924,7 @@ L9BOOL scriptinput(char *buffer, int size) {
return FALSE;
}
-void clearworkspace(void) {
+void clearworkspace() {
memset(workspace.vartable, 0, sizeof(workspace.vartable));
}
@@ -1933,7 +1933,7 @@ void ilins(int d0) {
Running = FALSE;
}
-void function(void) {
+void function() {
int d0 = *codeptr++;
#ifdef CODEFOLLOW
fprintf(f, " %s", d0 == 250 ? "printstr" : functions[d0 - 1]);
@@ -2014,7 +2014,7 @@ void findmsgequiv(int d7) {
} while (TRUE);
}
-L9BOOL unpackword(void) {
+L9BOOL unpackword() {
L9BYTE *a3;
if (unpackd3 == 0x1b) return TRUE;
@@ -2055,7 +2055,7 @@ L9UINT32 readdecimal(char *buff) {
return atol(buff);
}
-void checknumber(void) {
+void checknumber() {
if (*obuff >= 0x30 && *obuff < 0x3a) {
if (L9GameType == L9_V4) {
*list9ptr = 1;
@@ -2071,7 +2071,7 @@ void checknumber(void) {
}
}
-void NextCheat(void) {
+void NextCheat() {
/* restore game status */
memmove(&workspace, &CheatWorkspace, sizeof(GameState));
codeptr = acodeptr + workspace.codeptr;
@@ -2083,7 +2083,7 @@ void NextCheat(void) {
}
}
-void StartCheat(void) {
+void StartCheat() {
Cheating = TRUE;
CheatWord = 0;
@@ -2116,7 +2116,7 @@ L9BOOL GetWordV3(char *buff, int Word) {
return TRUE;
}
-L9BOOL CheckHash(void) {
+L9BOOL CheckHash() {
if (scumm_stricmp(ibuff, "#cheat") == 0) StartCheat();
else if (scumm_stricmp(ibuff, "#save") == 0) {
save();
@@ -2171,7 +2171,7 @@ L9BOOL IsInputChar(char c) {
return Common::isAlnum(c);
}
-L9BOOL corruptinginput(void) {
+L9BOOL corruptinginput() {
L9BYTE *a0, *a2, *a6;
int d0, d1, d2, keywordnumber, abrevword;
char *iptr;
@@ -2452,7 +2452,7 @@ L9BOOL inputV2(int *wordcount) {
}
}
-void input(void) {
+void input() {
if (L9GameType == L9_V3 && FirstPicture >= 0) {
show_picture(FirstPicture);
FirstPicture = -1;
@@ -2476,7 +2476,7 @@ void input(void) {
} else if (corruptinginput()) codeptr += 5;
}
-void varcon(void) {
+void varcon() {
L9UINT16 d6 = getcon();
*getvar() = d6;
@@ -2485,7 +2485,7 @@ void varcon(void) {
#endif
}
-void varvar(void) {
+void varvar() {
L9UINT16 d6 = *getvar();
*getvar() = d6;
@@ -2494,7 +2494,7 @@ void varvar(void) {
#endif
}
-void _add(void) {
+void _add() {
L9UINT16 d0 = *getvar();
*getvar() += d0;
@@ -2503,7 +2503,7 @@ void _add(void) {
#endif
}
-void _sub(void) {
+void _sub() {
L9UINT16 d0 = *getvar();
*getvar() -= d0;
@@ -2512,7 +2512,7 @@ void _sub(void) {
#endif
}
-void jump(void) {
+void jump() {
L9UINT16 d0 = L9WORD(codeptr);
L9BYTE *a0;
codeptr += 2;
@@ -2561,7 +2561,7 @@ notfn4:
*d5p = 0;
}
-void Exit(void) {
+void Exit() {
L9BYTE d4, d5v;
L9BYTE d7 = (L9BYTE) * getvar();
L9BYTE d6 = (L9BYTE) * getvar();
@@ -2578,7 +2578,7 @@ void Exit(void) {
#endif
}
-void ifeqvt(void) {
+void ifeqvt() {
L9UINT16 d0 = *getvar();
L9UINT16 d1 = *getvar();
L9BYTE *a0 = getaddr();
@@ -2589,7 +2589,7 @@ void ifeqvt(void) {
#endif
}
-void ifnevt(void) {
+void ifnevt() {
L9UINT16 d0 = *getvar();
L9UINT16 d1 = *getvar();
L9BYTE *a0 = getaddr();
@@ -2600,7 +2600,7 @@ void ifnevt(void) {
#endif
}
-void ifltvt(void) {
+void ifltvt() {
L9UINT16 d0 = *getvar();
L9UINT16 d1 = *getvar();
L9BYTE *a0 = getaddr();
@@ -2611,7 +2611,7 @@ void ifltvt(void) {
#endif
}
-void ifgtvt(void) {
+void ifgtvt() {
L9UINT16 d0 = *getvar();
L9UINT16 d1 = *getvar();
L9BYTE *a0 = getaddr();
@@ -2630,7 +2630,7 @@ int scaley(int y) {
return (gfx_mode == GFX_V2) ? 127 - (y >> 7) : 95 - (((y >> 5) + (y >> 6)) >> 3);
}
-void detect_gfx_mode(void) {
+void detect_gfx_mode() {
if (L9GameType == L9_V3) {
/* These V3 games use graphics logic similar to the V2 games */
if (strstr(FirstLine, "price of magik") != 0)
@@ -2659,7 +2659,7 @@ void detect_gfx_mode(void) {
gfx_mode = GFX_V2;
}
-void _screen(void) {
+void _screen() {
int mode = 0;
if (L9GameType == L9_V3 && strlen(FirstLine) == 0) {
@@ -2699,7 +2699,7 @@ void _screen(void) {
/* screent */
}
-void cleartg(void) {
+void cleartg() {
int d0 = *codeptr++;
#ifdef L9DEBUG
printf("cleartg %s", d0 ? "graphics" : "text");
@@ -2970,7 +2970,7 @@ void reflect(int d7) {
reflectflag = d7;
}
-void notimp(void) {
+void notimp() {
#ifdef L9DEBUG
printf("gfx - notimp");
#endif
@@ -3006,7 +3006,7 @@ void opt(L9BYTE **a5) {
option = d0;
}
-void restorescale(void) {
+void restorescale() {
#ifdef L9DEBUG
printf("gfx - restorescale");
#endif
@@ -3139,7 +3139,7 @@ void show_picture(int pic) {
}
}
-void picture(void) {
+void picture() {
show_picture(*getvar());
}
@@ -3157,7 +3157,7 @@ void GetPictureSize(int *width, int *height) {
}
}
-L9BOOL RunGraphics(void) {
+L9BOOL RunGraphics() {
if (gfxa5) {
if (!getinstruction(&gfxa5))
gfxa5 = NULL;
@@ -3166,14 +3166,14 @@ L9BOOL RunGraphics(void) {
return FALSE;
}
-void initgetobj(void) {
+void initgetobj() {
int i;
numobjectfound = 0;
object = 0;
for (i = 0; i < 32; i++) gnoscratch[i] = 0;
}
-void getnextobject(void) {
+void getnextobject() {
int d2, d3, d4;
L9UINT16 *hisearchposvar, *searchposvar;
@@ -3259,7 +3259,7 @@ void getnextobject(void) {
*getvar() = searchdepth;
}
-void ifeqct(void) {
+void ifeqct() {
L9UINT16 d0 = *getvar();
L9UINT16 d1 = getcon();
L9BYTE *a0 = getaddr();
@@ -3269,7 +3269,7 @@ void ifeqct(void) {
#endif
}
-void ifnect(void) {
+void ifnect() {
L9UINT16 d0 = *getvar();
L9UINT16 d1 = getcon();
L9BYTE *a0 = getaddr();
@@ -3279,7 +3279,7 @@ void ifnect(void) {
#endif
}
-void ifltct(void) {
+void ifltct() {
L9UINT16 d0 = *getvar();
L9UINT16 d1 = getcon();
L9BYTE *a0 = getaddr();
@@ -3289,7 +3289,7 @@ void ifltct(void) {
#endif
}
-void ifgtct(void) {
+void ifgtct() {
L9UINT16 d0 = *getvar();
L9UINT16 d1 = getcon();
L9BYTE *a0 = getaddr();
@@ -3299,7 +3299,7 @@ void ifgtct(void) {
#endif
}
-void printinput(void) {
+void printinput() {
L9BYTE *ptr = (L9BYTE *) obuff;
char c;
while ((c = *ptr++) != ' ') printchar(c);
@@ -3309,7 +3309,7 @@ void printinput(void) {
#endif
}
-void listhandler(void) {
+void listhandler() {
L9BYTE *a4, *MinAccess, *MaxAccess;
L9UINT16 val;
L9UINT16 *var;
@@ -3409,7 +3409,7 @@ void listhandler(void) {
}
}
-void executeinstruction(void) {
+void executeinstruction() {
#ifdef CODEFOLLOW
f = fopen(CODEFOLLOWFILE, "a");
fprintf(f, "%ld (s:%d) %x", (L9UINT32)(codeptr - acodeptr) - 1, workspace.stackptr, code);
@@ -3560,11 +3560,11 @@ L9BOOL LoadGame(const char *filename, char *picname) {
}
/* can be called from input to cause fall through for exit */
-void StopGame(void) {
+void StopGame() {
Running = FALSE;
}
-L9BOOL RunGame(void) {
+L9BOOL RunGame() {
code = *codeptr++;
/* printf("%d",code); */
executeinstruction();
diff --git a/engines/glk/level9/os_glk.cpp b/engines/glk/level9/os_glk.cpp
index ef5499c..be63ae5 100644
--- a/engines/glk/level9/os_glk.cpp
+++ b/engines/glk/level9/os_glk.cpp
@@ -95,8 +95,8 @@ typedef L9BYTE gln_byte;
typedef L9UINT16 gln_uint16;
typedef L9UINT32 gln_uint32;
-extern void save(void);
-extern void restore(void);
+extern void save();
+extern void restore();
extern gln_bool Cheating;
extern gln_byte *startdata;
extern gln_uint32 FileSize;
@@ -106,7 +106,7 @@ static void gln_event_wait(glui32 wait_type, event_t *event);
static void gln_event_wait_2(glui32 wait_type_1,
glui32 wait_type_2, event_t *event);
-static void gln_watchdog_tick(void);
+static void gln_watchdog_tick();
static void gln_standout_string(const char *message);
static int gln_confirm(const char *prompt);
@@ -155,8 +155,7 @@ static void gln_fatal(const char *string) {
* Non-failing malloc and realloc; call gln_fatal and exit if memory
* allocation fails.
*/
-static void *
-gln_malloc(size_t size) {
+static void *gln_malloc(size_t size) {
void *pointer;
pointer = malloc(size);
@@ -168,8 +167,7 @@ gln_malloc(size_t size) {
return pointer;
}
-static void *
-gln_realloc(void *ptr, size_t size) {
+static void *gln_realloc(void *ptr, size_t size) {
void *pointer;
pointer = realloc(ptr, size);
@@ -192,8 +190,7 @@ gln_realloc(void *ptr, size_t size) {
* They're global here so that the core interpreter can use them; otherwise
* it tries to use the non-ANSI str[n]icmp() functions.
*/
-int
-gln_strncasecmp(const char *s1, const char *s2, size_t n) {
+int gln_strncasecmp(const char *s1, const char *s2, size_t n) {
size_t index;
for (index = 0; index < n; index++) {
@@ -207,8 +204,7 @@ gln_strncasecmp(const char *s1, const char *s2, size_t n) {
return 0;
}
-int
-gln_strcasecmp(const char *s1, const char *s2) {
+int gln_strcasecmp(const char *s1, const char *s2) {
size_t s1len, s2len;
int result;
@@ -222,47 +218,6 @@ gln_strcasecmp(const char *s1, const char *s2) {
return s1len < s2len ? -1 : s1len > s2len ? 1 : 0;
}
-
-/*---------------------------------------------------------------------*/
-/* Glk port stub graphics functions */
-/*---------------------------------------------------------------------*/
-
-/*
- * If we're working with a very stripped down, old, or lazy Glk library
- * that neither offers Glk graphics nor graphics stubs functions, here
- * we define our own stubs, to avoid link-time errors.
- */
-#ifndef GLK_MODULE_IMAGE
-static glui32
-g_vm->glk_image_draw(winid_t win, glui32 image, glsi32 val1, glsi32 val2) {
- return FALSE;
-}
-static glui32
-g_vm->glk_image_draw_scaled(winid_t win, glui32 image, glsi32 val1, glsi32 val2,
- glui32 width, glui32 height) {
- return FALSE;
-}
-static glui32
-g_vm->glk_image_get_info(glui32 image, glui32 *width, glui32 *height) {
- return FALSE;
-}
-static void
-g_vm->glk_window_flow_break(winid_t win) {
-}
-static void
-g_vm->glk_window_erase_rect(winid_t win, glsi32 left, glsi32 top,
- glui32 width, glui32 height) {
-}
-static void
-g_vm->glk_window_fill_rect(winid_t win, glui32 color, glsi32 left, glsi32 top,
- glui32 width, glui32 height) {
-}
-static void
-g_vm->glk_window_set_background_color(winid_t win, glui32 color) {
-}
-#endif
-
-
/*---------------------------------------------------------------------*/
/* Glk port CRC functions */
/*---------------------------------------------------------------------*/
@@ -340,15 +295,15 @@ static const char *gln_gameid_game_name = NULL;
*
* The version of l9data_d.h used is 050 (22 Oct 2002).
*/
-typedef const struct {
+struct gln_game_table_t {
const gln_uint16 length; /* Datafile length in bytes */
const gln_byte checksum; /* 8-bit checksum, last datafile byte */
const gln_uint16 crc; /* 16-bit CRC, L9cut-internal */
const char *const name; /* Game title and platform */
-} gln_game_table_t;
-typedef gln_game_table_t *gln_game_tableref_t;
+};
+typedef const gln_game_table_t *gln_game_tableref_t;
-static gln_game_table_t GLN_GAME_TABLE[] = {
+static const gln_game_table_t GLN_GAME_TABLE[] = {
{0x5323, 0xb7, 0x8af7, "Adventure Quest (Amstrad CPC/Spectrum)"},
{0x630e, 0x8d, 0x7d7d, "Dungeon Adventure (Amstrad CPC)"},
@@ -763,16 +718,16 @@ static gln_game_table_t GLN_GAME_TABLE[] = {
*
* The version of l9data_p.h used is 012 (22 May 2001).
*/
-typedef const struct {
+struct gln_patch_table_t {
const gln_uint16 length; /* Datafile length in bytes */
const gln_byte orig_checksum; /* 8-bit checksum, last datafile byte */
const gln_uint16 orig_crc; /* 16-bit CRC, L9cut-internal */
const gln_byte patch_checksum; /* 8-bit checksum, last datafile byte */
const gln_uint16 patch_crc; /* 16-bit CRC, L9cut-internal */
-} gln_patch_table_t;
-typedef gln_patch_table_t *gln_patch_tableref_t;
+};
+typedef const gln_patch_table_t *gln_patch_tableref_t;
-static gln_patch_table_t GLN_PATCH_TABLE[] = {
+static const gln_patch_table_t GLN_PATCH_TABLE[] = {
/* Price of Magik (Spectrum128) */
{0x7410, 0x5e, 0x60be, 0x70, 0x6cef},
@@ -1132,28 +1087,26 @@ static gln_patch_table_t GLN_PATCH_TABLE[] = {
* Look up and return game table and patch table entries given a game's
* length, checksum, and CRC. Returns the entry, or NULL if not found.
*/
-static gln_game_tableref_t
-gln_gameid_lookup_game(gln_uint16 length, gln_byte checksum,
- gln_uint16 crc, int ignore_crc) {
+static gln_game_tableref_t gln_gameid_lookup_game(gln_uint16 length, gln_byte checksum,
+ gln_uint16 crc, int ignore_crc) {
gln_game_tableref_t game;
for (game = GLN_GAME_TABLE; game->length; game++) {
if (game->length == length && game->checksum == checksum
- && (ignore_crc || game->crc == crc))
+ && (ignore_crc || game->crc == crc))
break;
}
return game->length ? game : NULL;
}
-static gln_patch_tableref_t
-gln_gameid_lookup_patch(gln_uint16 length, gln_byte checksum,
- gln_uint16 crc) {
+static gln_patch_tableref_t gln_gameid_lookup_patch(gln_uint16 length, gln_byte checksum,
+ gln_uint16 crc) {
gln_patch_tableref_t patch;
for (patch = GLN_PATCH_TABLE; patch->length; patch++) {
if (patch->length == length && patch->patch_checksum == checksum
- && patch->patch_crc == crc)
+ && patch->patch_crc == crc)
break;
}
@@ -1170,8 +1123,7 @@ gln_gameid_lookup_patch(gln_uint16 length, gln_byte checksum,
* This function uses startdata and FileSize from the core interpreter.
* These aren't advertised symbols, so be warned.
*/
-static gln_game_tableref_t
-gln_gameid_identify_game(void) {
+static gln_game_tableref_t gln_gameid_identify_game() {
gln_uint16 length, crc;
gln_byte checksum;
int is_version2;
@@ -1242,8 +1194,7 @@ gln_gameid_identify_game(void) {
* This function uses startdata from the core interpreter. This isn't an
* advertised symbol, so be warned.
*/
-static const char *
-gln_gameid_get_game_name(void) {
+static const char *gln_gameid_get_game_name() {
/*
* If no game name yet known, attempt to identify the game. If it can't
* be identified, set the cached game name to "" -- this special value
@@ -1278,8 +1229,7 @@ gln_gameid_get_game_name(void) {
* function should be called by actions that may cause the interpreter to
* change game file, for example os_set_filenumber().
*/
-static void
-gln_gameid_game_name_reset(void) {
+static void gln_gameid_game_name_reset() {
gln_gameid_game_name = NULL;
}
@@ -1289,9 +1239,9 @@ gln_gameid_game_name_reset(void) {
/*---------------------------------------------------------------------*/
/* R,G,B color triple definition. */
-typedef struct {
+struct gln_rgb_t {
int red, green, blue;
-} gln_rgb_t;
+};
typedef gln_rgb_t *gln_rgbref_t;
/*
@@ -1417,7 +1367,7 @@ static int gln_graphics_color_count = GLN_PALETTE_SIZE;
* If it's not open, open the graphics window. Returns TRUE if graphics
* was successfully started, or already on.
*/
-static int gln_graphics_open(void) {
+static int gln_graphics_open() {
if (!gln_graphics_window) {
gln_graphics_window = g_vm->glk_window_open(gln_main_window,
winmethod_Above
@@ -1435,7 +1385,7 @@ static int gln_graphics_open(void) {
*
* If open, close the graphics window and set back to NULL.
*/
-static void gln_graphics_close(void) {
+static void gln_graphics_close() {
if (gln_graphics_window) {
g_vm->glk_window_close(gln_graphics_window, NULL);
gln_graphics_window = NULL;
@@ -1448,7 +1398,7 @@ static void gln_graphics_close(void) {
*
* If graphics enabled, start any background picture update processing.
*/
-static void gln_graphics_start(void) {
+static void gln_graphics_start() {
if (gln_graphics_enabled) {
/* If not running, start the updating "thread". */
if (!gln_graphics_active) {
@@ -1464,7 +1414,7 @@ static void gln_graphics_start(void) {
*
* Stop any background picture update processing.
*/
-static void gln_graphics_stop(void) {
+static void gln_graphics_stop() {
/* If running, stop the updating "thread". */
if (gln_graphics_active) {
g_vm->glk_request_timer_events(0);
@@ -1478,7 +1428,7 @@ static void gln_graphics_stop(void) {
*
* Return TRUE if graphics are currently being displayed, FALSE otherwise.
*/
-static int gln_graphics_are_displayed(void) {
+static int gln_graphics_are_displayed() {
return gln_graphics_window != NULL;
}
@@ -1490,7 +1440,7 @@ static int gln_graphics_are_displayed(void) {
* This function should be called on the appropriate Glk window resize and
* arrange events.
*/
-static void gln_graphics_paint(void) {
+static void gln_graphics_paint() {
if (gln_graphics_enabled && gln_graphics_are_displayed()) {
/* Set the repaint flag, and start graphics. */
gln_graphics_repaint = TRUE;
@@ -1506,7 +1456,7 @@ static void gln_graphics_paint(void) {
* function should be called whenever graphics is re-enabled after being
* disabled.
*/
-static void gln_graphics_restart(void) {
+static void gln_graphics_restart() {
if (gln_graphics_enabled && gln_graphics_are_displayed()) {
/* Set the new picture flag, and start graphics. */
gln_graphics_new_picture = TRUE;
@@ -1521,8 +1471,7 @@ static void gln_graphics_restart(void) {
* Analyze an image, and return an overall count of how many colors out of
* the palette are used.
*/
-static int gln_graphics_count_colors(gln_byte bitmap[],
- gln_uint16 width, gln_uint16 height) {
+static int gln_graphics_count_colors(gln_byte bitmap[], gln_uint16 width, gln_uint16 height) {
int x, y, count;
long usage[GLN_PALETTE_SIZE], index_row;
assert(bitmap);
@@ -1583,10 +1532,8 @@ static glui32 gln_graphics_combine_color(gln_rgbref_t rgb_color) {
* picture is going to be rendered. This attempts a small raised effect
* for the picture, in keeping with modern trends.
*/
-static void gln_graphics_clear_and_border(winid_t glk_window,
- int x_offset, int y_offset,
- int pixel_size, gln_uint16 width,
- gln_uint16 height) {
+static void gln_graphics_clear_and_border(winid_t glk_window, int x_offset, int y_offset,
+ int pixel_size, gln_uint16 width, gln_uint16 height) {
uint background;
glui32 fade_color, shading_color;
gln_rgb_t rgb_background, rgb_border, rgb_fade;
@@ -1620,7 +1567,7 @@ static void gln_graphics_clear_and_border(winid_t glk_window,
* do any shading. Failing this check is probably highly unlikely.
*/
if (width < 2 * GLN_GRAPHICS_SHADE_STEPS
- || height < 2 * GLN_GRAPHICS_SHADE_STEPS) {
+ || height < 2 * GLN_GRAPHICS_SHADE_STEPS) {
/* Paint a rectangle bigger than the picture by border pixels. */
g_vm->glk_window_fill_rect(glk_window,
GLN_GRAPHICS_BORDER_COLOR,
@@ -1719,8 +1666,7 @@ static void gln_graphics_convert_palette(Colour ln_palette[], glui32 glk_palette
* this picture in the current graphics window.
*/
static void gln_graphics_position_picture(winid_t glk_window, int pixel_size,
- gln_uint16 width, gln_uint16 height,
- int *x_offset, int *y_offset) {
+ gln_uint16 width, gln_uint16 height, int *x_offset, int *y_offset) {
uint window_width, window_height;
assert(glk_window && x_offset && y_offset);
@@ -1749,8 +1695,8 @@ static void gln_graphics_position_picture(winid_t glk_window, int pixel_size,
* the most complex shapes first, we help to minimize the number of fill
* regions needed to render the complete picture.
*/
-static int gln_graphics_is_vertex(gln_byte off_screen[],
- gln_uint16 width, gln_uint16 height, int x, int y) {
+static int gln_graphics_is_vertex(gln_byte off_screen[], gln_uint16 width, gln_uint16 height,
+ int x, int y) {
gln_byte pixel;
int above, below, left, right;
long index_row;
@@ -1834,8 +1780,7 @@ static int gln_graphics_compare_layering_inverted(const void *void_first,
}
static void gln_graphics_assign_layers(gln_byte off_screen[], gln_byte on_screen[],
- gln_uint16 width, gln_uint16 height,
- int layers[], long layer_usage[]) {
+ gln_uint16 width, gln_uint16 height, int layers[], long layer_usage[]) {
int index, x, y;
long index_row;
gln_layering_t layering[GLN_PALETTE_SIZE];
@@ -1919,9 +1864,8 @@ static void gln_graphics_assign_layers(gln_byte off_screen[], gln_byte on_screen
* given point as validated.
*/
static void gln_graphics_paint_region(winid_t glk_window, glui32 palette[], int layers[],
- gln_byte off_screen[], gln_byte on_screen[],
- int x, int y, int x_offset, int y_offset,
- int pixel_size, gln_uint16 width, gln_uint16 height) {
+ gln_byte off_screen[], gln_byte on_screen[], int x, int y, int x_offset, int y_offset,
+ int pixel_size, gln_uint16 width, gln_uint16 height) {
gln_byte pixel;
int layer, x_min, x_max, y_min, y_max, x_index, y_index;
long index_row;
@@ -2023,11 +1967,8 @@ break_y_max:
}
}
-static void gln_graphics_paint_everything(winid_t glk_window,
- glui32 palette[],
- gln_byte off_screen[],
- int x_offset, int y_offset,
- gln_uint16 width, gln_uint16 height) {
+static void gln_graphics_paint_everything(winid_t glk_window, glui32 palette[],
+ gln_byte off_screen[], int x_offset, int y_offset, gln_uint16 width, gln_uint16 height) {
gln_byte pixel; /* Reference pixel color */
int x, y;
@@ -2060,7 +2001,7 @@ static void gln_graphics_paint_everything(winid_t glk_window,
* plots required, as only the differences between the two buffers need
* to be rendered.
*/
-static void gln_graphics_timeout(void) {
+static void gln_graphics_timeout() {
static glui32 palette[GLN_PALETTE_SIZE]; /* Precomputed Glk palette */
static int deferred_repaint = FALSE; /* Local delayed repaint flag */
@@ -2361,7 +2302,7 @@ static void gln_graphics_locate_bitmaps(const char *gamefile) {
* Here, then, we try to delay until the picture has rendered, allowing the
* delay to be broken with a keypress.
*/
-static void gln_graphics_handle_title_picture(void) {
+static void gln_graphics_handle_title_picture() {
event_t event;
int count;
@@ -2501,7 +2442,7 @@ void os_show_bitmap(int picture, int x, int y) {
* Return TRUE if the graphics module data is loaded with a usable picture,
* FALSE if there is no picture available to display.
*/
-static int gln_graphics_picture_is_available(void) {
+static int gln_graphics_picture_is_available() {
return gln_graphics_bitmap != NULL;
}
@@ -2542,7 +2483,7 @@ static int gln_graphics_get_picture_details(int *width, int *height) {
* values are returned.
*/
static int gln_graphics_get_rendering_details(const char **bitmap_type,
- int *color_count, int *is_active) {
+ int *color_count, int *is_active) {
if (gln_graphics_enabled && gln_graphics_are_displayed()) {
/*
* Convert the detected bitmap type into a string and return it.
@@ -2613,7 +2554,7 @@ static int gln_graphics_get_rendering_details(const char **bitmap_type,
* Return TRUE if it looks like interpreter graphics are turned on, FALSE
* otherwise.
*/
-static int gln_graphics_interpreter_enabled(void) {
+static int gln_graphics_interpreter_enabled() {
return gln_graphics_interpreter_state != GLN_GRAPHICS_OFF;
}
@@ -2624,7 +2565,7 @@ static int gln_graphics_interpreter_enabled(void) {
* Free memory resources allocated by graphics functions. Called on game
* end.
*/
-static void gln_graphics_cleanup(void) {
+static void gln_graphics_cleanup() {
free(gln_graphics_bitmap);
gln_graphics_bitmap = NULL;
free(gln_graphics_off_screen);
@@ -2682,7 +2623,7 @@ static int gln_linegraphics_fill_segments_allocation = 0,
* Initialize a new constructed bitmap graphics context for line drawn
* graphics.
*/
-static void gln_linegraphics_create_context(void) {
+static void gln_linegraphics_create_context() {
int width, height;
long picture_bytes;
@@ -2712,7 +2653,7 @@ static void gln_linegraphics_create_context(void) {
* Clear the complete graphical drawing area, setting all pixels to zero,
* and resetting the palette to all black as well.
*/
-static void gln_linegraphics_clear_context(void) {
+static void gln_linegraphics_clear_context() {
long picture_bytes;
/* Get the picture size, and zero all bytes in the bitmap. */
@@ -2786,7 +2727,7 @@ static void gln_linegraphics_plot_clip(int x, int y, int colour1, int colour2) {
}
static void gln_linegraphics_draw_line_if(int x1, int y1, int x2, int y2,
- int colour1, int colour2) {
+ int colour1, int colour2) {
int x, y, dx, dy, incx, incy, balance;
/* Ignore any odd request where there will be no colour changes. */
@@ -3001,7 +2942,7 @@ skip:
* Interpreter entry points for line drawing graphics. All calls to these
* are ignored if line drawing mode is not set.
*/
-void os_cleargraphics(void) {
+void os_cleargraphics() {
if (gln_graphics_interpreter_state == GLN_GRAPHICS_LINE_MODE)
gln_linegraphics_clear_context();
}
@@ -3028,7 +2969,7 @@ void os_fill(int x, int y, int colour1, int colour2) {
* Process as many graphics opcodes as are available, constructing the
* resulting image as a bitmap. When complete, treat as normal bitmaps.
*/
-static void gln_linegraphics_process(void) {
+static void gln_linegraphics_process() {
/*
* If interpreter graphics are not set to line mode, ignore any call that
* arrives here.
@@ -3063,7 +3004,7 @@ static void gln_linegraphics_process(void) {
* Free memory resources allocated by line graphics functions. Called on
* game end.
*/
-static void gln_linegraphics_cleanup(void) {
+static void gln_linegraphics_cleanup() {
free(gln_linegraphics_fill_segments);
gln_linegraphics_fill_segments = NULL;
@@ -3237,7 +3178,7 @@ static void gln_watchdog_start(int timeout, int period) {
gln_watchdog_monitor = g_system->getMillis();
}
-static void gln_watchdog_stop(void) {
+static void gln_watchdog_stop() {
gln_watchdog_timeout_secs = 0;
}
@@ -3251,7 +3192,7 @@ static void gln_watchdog_stop(void) {
* returns to the interpreter, as a means of timing how long the interpreter
* dwells in running game code.
*/
-static void gln_watchdog_tick(void) {
+static void gln_watchdog_tick() {
gln_watchdog_monitor = g_system->getMillis();
}
@@ -3266,7 +3207,7 @@ static void gln_watchdog_tick(void) {
* This function only checks every N calls; it's called extremely frequently
* from opcode handling, and will thrash in time() if it checks on each call.
*/
-static int gln_watchdog_has_timed_out(void) {
+static int gln_watchdog_has_timed_out() {
/* If loop detection is off or the timeout is set to zero, do nothing. */
if (gln_loopcheck_enabled && gln_watchdog_timeout_secs > 0) {
time_t now;
@@ -3335,7 +3276,7 @@ static const int GLN_DEFAULT_STATUS_WIDTH = 74;
* the current game identity string, or a default string if no game identity
* could be established.
*/
-static void gln_status_update(void) {
+static void gln_status_update() {
uint width, height;
assert(gln_status_window);
@@ -3370,7 +3311,7 @@ static void gln_status_update(void) {
* to detect changes of game identity string, and gambles a little on the
* belief that two games' strings won't have the same CRC.
*/
-static void gln_status_print(void) {
+static void gln_status_print() {
static int is_initialized = FALSE;
static gln_uint16 crc = 0;
@@ -3416,7 +3357,7 @@ static void gln_status_print(void) {
* Front end function for updating status. Either updates the status window
* or prints the status line to the main window.
*/
-static void gln_status_notify(void) {
+static void gln_status_notify() {
if (gln_status_window)
gln_status_update();
else
@@ -3431,7 +3372,7 @@ static void gln_status_notify(void) {
* This function should be called on the appropriate Glk window resize and
* arrange events.
*/
-static void gln_status_redraw(void) {
+static void gln_status_redraw() {
if (gln_status_window) {
winid_t parent;
@@ -3495,7 +3436,7 @@ static int gln_output_prompt = FALSE;
* Register recent text output from the interpreter. This function is
* called by os_printchar().
*/
-static void gln_output_notify(void) {
+static void gln_output_notify() {
gln_output_activity = TRUE;
}
@@ -3507,7 +3448,7 @@ static void gln_output_notify(void) {
* Clears the flag, so that more output text is required before the next
* call returns TRUE.
*/
-static int gln_recent_output(void) {
+static int gln_recent_output() {
int result;
result = gln_output_activity;
@@ -3525,15 +3466,15 @@ static int gln_recent_output(void) {
* Register a request for help, and print a note of how to get Glk command
* help from the interpreter unless silenced.
*/
-static void gln_output_register_help_request(void) {
+static void gln_output_register_help_request() {
gln_help_requested = TRUE;
}
-static void gln_output_silence_help_hints(void) {
+static void gln_output_silence_help_hints() {
gln_help_hints_silenced = TRUE;
}
-static void gln_output_provide_help_hint(void) {
+static void gln_output_provide_help_hint() {
if (gln_help_requested && !gln_help_hints_silenced) {
g_vm->glk_set_style(style_Emphasized);
g_vm->glk_put_string("[Try 'glk help' for help on special interpreter"
@@ -3552,7 +3493,7 @@ static void gln_output_provide_help_hint(void) {
* Once called, the flag is reset to FALSE, and requires more game output
* to set it again.
*/
-static int gln_game_prompted(void) {
+static int gln_game_prompted() {
int result;
result = gln_output_prompt;
@@ -3569,7 +3510,7 @@ static int gln_game_prompted(void) {
* to be a prompt, and set the game prompted flag if it does, otherwise
* clear it.
*/
-static void gln_detect_game_prompt(void) {
+static void gln_detect_game_prompt() {
int index;
gln_output_prompt = FALSE;
@@ -3594,7 +3535,7 @@ static void gln_detect_game_prompt(void) {
* Delete all buffered output text. Free all malloc'ed buffer memory, and
* return the buffer variables to their initial values.
*/
-static void gln_output_delete(void) {
+static void gln_output_delete() {
free(gln_output_buffer);
gln_output_buffer = NULL;
gln_output_allocation = gln_output_length = 0;
@@ -3607,7 +3548,7 @@ static void gln_output_delete(void) {
* Flush any buffered output text to the Glk main window, and clear the
* buffer. Check in passing for game prompts that duplicate our's.
*/
-static void gln_output_flush(void) {
+static void gln_output_flush() {
assert(g_vm->glk_stream_get_current());
if (gln_output_length > 0) {
@@ -3732,7 +3673,7 @@ static void gln_banner_string(const char *message) {
* line text where we are working with a non-windowing Glk, so it's best
* ignored where we can.
*/
-void os_flush(void) {
+void os_flush() {
}
@@ -4301,17 +4242,17 @@ static void gln_command_commands(const char *argument) {
/* Glk subcommands and handler functions. */
-typedef const struct {
+struct gln_command_t {
const char *const command; /* Glk subcommand. */
void (* const handler)(const char *argument); /* Subcommand handler. */
const int takes_argument; /* Argument flag. */
-} gln_command_t;
-typedef gln_command_t *gln_commandref_t;
+};
+typedef const gln_command_t *gln_commandref_t;
static void gln_command_summary(const char *argument);
static void gln_command_help(const char *argument);
-static gln_command_t GLN_COMMAND_TABLE[] = {
+static const gln_command_t GLN_COMMAND_TABLE[] = {
{"summary", gln_command_summary, FALSE},
{"script", gln_command_script, TRUE},
{"inputlog", gln_command_inputlog, TRUE},
@@ -4333,8 +4274,7 @@ static gln_command_t GLN_COMMAND_TABLE[] = {
*
* Report all current Glk settings.
*/
-static void
-gln_command_summary(const char *argument) {
+static void gln_command_summary(const char *argument) {
gln_commandref_t entry;
assert(argument);
@@ -4357,8 +4297,7 @@ gln_command_summary(const char *argument) {
*
* Document the available Glk commands.
*/
-static void
-gln_command_help(const char *command) {
+static void gln_command_help(const char *command) {
gln_commandref_t entry, matched;
assert(command);
@@ -4743,13 +4682,13 @@ static int gln_inside_list = FALSE;
/* Table of single-character command abbreviations. */
-typedef const struct {
+struct gln_abbreviation_t {
const char abbreviation; /* Abbreviation character. */
const char *const expansion; /* Expansion string. */
-} gln_abbreviation_t;
-typedef gln_abbreviation_t *gln_abbreviationref_t;
+};
+typedef const gln_abbreviation_t *gln_abbreviationref_t;
-static gln_abbreviation_t GLN_ABBREVIATIONS[] = {
+static const gln_abbreviation_t GLN_ABBREVIATIONS[] = {
{'c', "close"}, {'g', "again"}, {'i', "inventory"},
{'k', "attack"}, {'l', "look"}, {'p', "open"},
{'q', "quit"}, {'r', "drop"}, {'t', "take"},
@@ -4814,7 +4753,7 @@ static void gln_expand_abbreviations(char *buffer, int size) {
* The core interpreter doesn't terminate lists with a newline, so we take
* care of that here; a fixup for input functions.
*/
-static void gln_output_endlist(void) {
+static void gln_output_endlist() {
if (gln_inside_list) {
/*
* Supply the missing newline, using os_printchar() so that list output
@@ -5163,7 +5102,7 @@ char os_readchar(int millis) {
* can't check for keypresses without blocking, so we do no checks at all,
* and let lists always run to completion.
*/
-gln_bool os_stoplist(void) {
+gln_bool os_stoplist() {
static int call_count = 0;
event_t event;
@@ -5805,7 +5744,7 @@ static int gln_startup_code(int argc, char *argv[]) {
return TRUE;
}
-static void gln_main(void) {
+static void gln_main() {
char *graphics_file = NULL;
int is_running;
@@ -6006,7 +5945,7 @@ static int gln_startup_called = FALSE,
* Main entry point for Glk. Here, all startup is done, and we call our
* function to run the game.
*/
-void glk_main(void) {
+void glk_main() {
assert(gln_startup_called && !gln_main_called);
gln_main_called = TRUE;
Commit: a6b1c9085f4edd6c09d93228170971f5a7b0952e
https://github.com/scummvm/scummvm/commit/a6b1c9085f4edd6c09d93228170971f5a7b0952e
Author: Paul Gilbert (dreammaster at scummvm.org)
Date: 2019-10-26T11:13:29-07:00
Commit Message:
GLK: Safer interpreter tag generation for Quetzal save files
Changed paths:
engines/glk/glk.cpp
engines/glk/quetzal.cpp
engines/glk/quetzal.h
diff --git a/engines/glk/glk.cpp b/engines/glk/glk.cpp
index cffdd8b..b546299 100644
--- a/engines/glk/glk.cpp
+++ b/engines/glk/glk.cpp
@@ -204,7 +204,7 @@ Common::Error GlkEngine::loadGameState(int slot) {
Common::String md5 = QuetzalReader::readString(rs);
delete rs;
- if (interpType != INTERPRETER_IDS[getInterpreterType()] ||
+ if (interpType != QuetzalBase::getInterpreterTag(getInterpreterType()) ||
parseLanguage(langCode) !=getLanguage() || md5 != getGameMD5())
errCode = Common::kReadingFailed;
}
diff --git a/engines/glk/quetzal.cpp b/engines/glk/quetzal.cpp
index 60f683b..7a11a57 100644
--- a/engines/glk/quetzal.cpp
+++ b/engines/glk/quetzal.cpp
@@ -30,26 +30,50 @@
namespace Glk {
-const uint32 INTERPRETER_IDS[INTERPRETER_TADS3 + 1] = {
- MKTAG('A', 'D', 'R', 'I'),
- MKTAG('A', 'S', 'Y', 'S'),
- MKTAG('A', 'G', 'I', 'L'),
- MKTAG('A', 'L', 'N', '2'),
- MKTAG('A', 'L', 'N', '3'),
- MKTAG('B', 'O', 'C', 'F'),
- MKTAG('Z', 'C', 'O', 'D'),
- MKTAG('G', 'E', 'A', 'S'),
- MKTAG('G', 'L', 'U', 'L'),
- MKTAG('H', 'U', 'G', 'O'),
- MKTAG('J', 'A', 'C', 'L'),
- MKTAG('L', 'V', 'L', '9'),
- MKTAG('M', 'A', 'G', 'N'),
- MKTAG('Q', 'U', 'E', 'S'),
- MKTAG('S', 'C', 'A', 'R'),
- MKTAG('S', 'C', 'O', 'T'),
- MKTAG('T', 'A', 'D', '2'),
- MKTAG('T', 'A', 'D', '3')
-};
+/*--------------------------------------------------------------------------*/
+
+uint32 QuetzalBase::getInterpreterTag(InterpreterType interpType) {
+ switch (interpType) {
+ case INTERPRETER_ADRIFT:
+ return MKTAG('A', 'D', 'R', 'I');
+ case INTERPRETER_ADVSYS:
+ return MKTAG('A', 'S', 'Y', 'S');
+ case INTERPRETER_AGILITY:
+ return MKTAG('A', 'G', 'I', 'L');
+ case INTERPRETER_ALAN2:
+ return MKTAG('A', 'L', 'N', '2');
+ case INTERPRETER_ALAN3:
+ return MKTAG('A', 'L', 'N', '3');
+ case INTERPRETER_FROTZ:
+ return MKTAG('Z', 'C', 'O', 'D');
+ case INTERPRETER_GEAS:
+ return MKTAG('G', 'E', 'A', 'S');
+ case INTERPRETER_GLULXE:
+ return MKTAG('G', 'L', 'U', 'L');
+ case INTERPRETER_HUGO:
+ return MKTAG('H', 'U', 'G', 'O');
+ case INTERPRETER_JACL:
+ return MKTAG('J', 'A', 'C', 'L');
+ case INTERPRETER_LEVEL9:
+ return MKTAG('L', 'V', 'L', '9');
+ case INTERPRETER_MAGNETIC:
+ return MKTAG('M', 'A', 'G', 'N');
+ case INTERPRETER_QUEST:
+ return MKTAG('Q', 'U', 'E', 'S');
+ case INTERPRETER_SCARE:
+ return MKTAG('S', 'C', 'A', 'R');
+ case INTERPRETER_SCOTT:
+ return MKTAG('S', 'C', 'O', 'T');
+ case INTERPRETER_TADS2:
+ return MKTAG('T', 'A', 'D', '2');
+ case INTERPRETER_TADS3:
+ return MKTAG('T', 'A', 'D', '3');
+ default:
+ error("Invalid interpreter type");
+ }
+}
+
+/*--------------------------------------------------------------------------*/
void QuetzalReader::clear() {
_chunks.clear();
@@ -222,7 +246,7 @@ void QuetzalWriter::addCommonChunks(const Common::String &saveName) {
ws.writeUint32BE(g_vm->_events->getTotalPlayTicks());
// Write out intrepreter type, language, and game Id
- ws.writeUint32BE(INTERPRETER_IDS[g_vm->getInterpreterType()]);
+ ws.writeUint32BE(getInterpreterTag(g_vm->getInterpreterType()));
const char *langCode = getLanguageCode(g_vm->getLanguage());
ws.write(langCode, strlen(langCode) + 1);
Common::String md5 = g_vm->getGameMD5();
diff --git a/engines/glk/quetzal.h b/engines/glk/quetzal.h
index c9452b6..b7468a5 100644
--- a/engines/glk/quetzal.h
+++ b/engines/glk/quetzal.h
@@ -43,12 +43,15 @@ enum QueztalTag {
ID_SCVM = MKTAG('S', 'C', 'V', 'M')
};
-extern const uint32 INTERPRETER_IDS[INTERPRETER_TADS3 + 1];
+class QuetzalBase {
+public:
+ static uint32 getInterpreterTag(InterpreterType interpType);
+};
/**
* Quetzal save file reader
*/
-class QuetzalReader {
+class QuetzalReader : public QuetzalBase {
struct Chunk {
uint32 _id;
size_t _offset, _size;
@@ -156,7 +159,7 @@ public:
/**
* Quetzal save file writer
*/
-class QuetzalWriter {
+class QuetzalWriter : public QuetzalBase {
/**
* Chunk entry
*/
Commit: 0a4d64f55e03656a403b66656dbcd5dd00ee5912
https://github.com/scummvm/scummvm/commit/0a4d64f55e03656a403b66656dbcd5dd00ee5912
Author: Paul Gilbert (dreammaster at scummvm.org)
Date: 2019-10-26T11:13:29-07:00
Commit Message:
GLK: LEVEL9: Hooking up engine class to subengine code
Changed paths:
engines/glk/level9/level9.cpp
engines/glk/level9/level9_main.cpp
engines/glk/level9/os_glk.cpp
diff --git a/engines/glk/level9/level9.cpp b/engines/glk/level9/level9.cpp
index 5cae3dc..6d8baa1 100644
--- a/engines/glk/level9/level9.cpp
+++ b/engines/glk/level9/level9.cpp
@@ -27,12 +27,27 @@ namespace Level9 {
Level9 *g_vm = nullptr;
+extern void gln_main(const char *filename);
+extern int gln_startup_code(int argc, char *argv[]);
+
Level9::Level9(OSystem *syst, const GlkGameDescription &gameDesc) : GlkAPI(syst, gameDesc) {
g_vm = this;
}
void Level9::runGame() {
- // TODO
+ initialize();
+
+ _gameFile.close();
+ gln_main(getFilename().c_str());
+
+ deinitialize();
+}
+
+bool Level9::initialize() {
+ return gln_startup_code(0, nullptr);
+}
+
+void Level9::deinitialize() {
}
Common::Error Level9::readSaveData(Common::SeekableReadStream *rs) {
diff --git a/engines/glk/level9/level9_main.cpp b/engines/glk/level9/level9_main.cpp
index 7aa95a7..e8963c9 100644
--- a/engines/glk/level9/level9_main.cpp
+++ b/engines/glk/level9/level9_main.cpp
@@ -163,7 +163,7 @@ int L9V1Game = -1;
/* Prototypes */
-L9BOOL LoadGame2(char *filename, char *picname);
+L9BOOL LoadGame2(const char *filename, char *picname);
int getlongcode();
L9BOOL GetWordV2(char *buff, int Word);
L9BOOL GetWordV3(char *buff, int Word);
@@ -174,7 +174,7 @@ void show_picture(int pic);
#define CODEFOLLOWFILE "c:\\temp\\level9.txt"
FILE *f;
L9UINT16 *cfvar, *cfvar2;
-const char *codes[] = {
+const char *const codes[] = {
"Goto",
"intgosub",
"intreturn",
@@ -208,7 +208,7 @@ const char *codes[] = {
"ilins",
"ilins",
};
-const char *functions[] = {
+const char *const functions[] = {
"calldriver",
"L9Random",
"save",
@@ -216,7 +216,7 @@ const char *functions[] = {
"clearworkspace",
"clearstack"
};
-const char *drivercalls[] = {
+const char *const drivercalls[] = {
"init",
"drivercalcchecksum",
"driveroswrch",
diff --git a/engines/glk/level9/os_glk.cpp b/engines/glk/level9/os_glk.cpp
index be63ae5..eac1616 100644
--- a/engines/glk/level9/os_glk.cpp
+++ b/engines/glk/level9/os_glk.cpp
@@ -1681,6 +1681,39 @@ static void gln_graphics_position_picture(winid_t glk_window, int pixel_size,
*y_offset = ((int) window_height - height * pixel_size) / 2;
}
+/*
+ * gms_graphics_compare_layering_inverted()
+ * gln_graphics_assign_layers()
+ *
+ * Given two sets of image bitmaps, and a palette, this function will
+ * assign layers palette colors.
+ *
+ * Layers are assigned by first counting the number of vertices in the
+ * color plane, to get a measure of the complexity of shapes displayed in
+ * this color, and also the raw number of times each palette color is
+ * used. This is then sorted, so that layers are assigned to colors, with
+ * the lowest layer being the color with the most complex shapes, and
+ * within this (or where the count of vertices is zero) the most used color.
+ *
+ * The function compares pixels in the two image bitmaps given, these
+ * being the off-screen and on-screen buffers, and generates counts only
+ * where these bitmaps differ. This ensures that only pixels not yet
+ * painted are included in layering.
+ *
+ * As well as assigning layers, this function returns a set of layer usage
+ * flags, to help the rendering loop to terminate as early as possible.
+ *
+ * By painting lower layers first, the paint can take in larger areas if
+ * it's permitted to include not-yet-validated higher levels. This helps
+ * minimize the amount of Glk areas fills needed to render a picture.
+ */
+struct gln_layering_t {
+ long complexity; /* Count of vertices for this color. */
+ long usage; /* Color usage count. */
+ int color; /* Color index into palette. */
+};
+
+#ifndef GARGLK
/*
* gln_graphics_is_vertex()
@@ -1696,7 +1729,7 @@ static void gln_graphics_position_picture(winid_t glk_window, int pixel_size,
* regions needed to render the complete picture.
*/
static int gln_graphics_is_vertex(gln_byte off_screen[], gln_uint16 width, gln_uint16 height,
- int x, int y) {
+ int x, int y) {
gln_byte pixel;
int above, below, left, right;
long index_row;
@@ -1727,39 +1760,6 @@ static int gln_graphics_is_vertex(gln_byte off_screen[], gln_uint16 width, gln_u
return ((above || below) && (left || right));
}
-
-/*
- * gms_graphics_compare_layering_inverted()
- * gln_graphics_assign_layers()
- *
- * Given two sets of image bitmaps, and a palette, this function will
- * assign layers palette colors.
- *
- * Layers are assigned by first counting the number of vertices in the
- * color plane, to get a measure of the complexity of shapes displayed in
- * this color, and also the raw number of times each palette color is
- * used. This is then sorted, so that layers are assigned to colors, with
- * the lowest layer being the color with the most complex shapes, and
- * within this (or where the count of vertices is zero) the most used color.
- *
- * The function compares pixels in the two image bitmaps given, these
- * being the off-screen and on-screen buffers, and generates counts only
- * where these bitmaps differ. This ensures that only pixels not yet
- * painted are included in layering.
- *
- * As well as assigning layers, this function returns a set of layer usage
- * flags, to help the rendering loop to terminate as early as possible.
- *
- * By painting lower layers first, the paint can take in larger areas if
- * it's permitted to include not-yet-validated higher levels. This helps
- * minimize the amount of Glk areas fills needed to render a picture.
- */
-struct gln_layering_t {
- long complexity; /* Count of vertices for this color. */
- long usage; /* Color usage count. */
- int color; /* Color index into palette. */
-};
-
static int gln_graphics_compare_layering_inverted(const void *void_first,
const void *void_second) {
const gln_layering_t *first = (const gln_layering_t *)void_first;
@@ -1833,7 +1833,6 @@ static void gln_graphics_assign_layers(gln_byte off_screen[], gln_byte on_screen
}
}
-
/*
* gln_graphics_paint_region()
*
@@ -1966,6 +1965,7 @@ break_y_max:
index_row += width;
}
}
+#endif
static void gln_graphics_paint_everything(winid_t glk_window, glui32 palette[],
gln_byte off_screen[], int x_offset, int y_offset, gln_uint16 width, gln_uint16 height) {
@@ -3640,10 +3640,14 @@ static void gln_standout_string(const char *message) {
gln_styled_string(style_Emphasized, message);
}
+#ifndef GARGLK
+
static void gln_standout_char(char c) {
gln_styled_char(style_Emphasized, c);
}
+#endif
+
static void gln_normal_string(const char *message) {
gln_styled_string(style_Normal, message);
}
@@ -5541,8 +5545,7 @@ static const int GLN_WATCHDOG_TIMEOUT = 5,
* The following values need to be passed between the startup_code and main
* functions.
*/
-static const char *gln_gamefile = NULL, /* Name of game file. */
- *gln_game_message = NULL; /* Error message. */
+static const char *gln_game_message = NULL; /* Error message. */
/*
@@ -5677,7 +5680,7 @@ static void gln_establish_picture_filename(const char *name, char **graphics) {
* handle options. The second is called from g_vm->glk_main(), and does the real
* work of running the game.
*/
-static int gln_startup_code(int argc, char *argv[]) {
+int gln_startup_code(int argc, char *argv[]) {
int argv_index;
/* Handle command line arguments. */
@@ -5710,41 +5713,11 @@ static int gln_startup_code(int argc, char *argv[]) {
return FALSE;
}
- /*
- * Get the name of the game file. Since we need this in our call from
- * g_vm->glk_main, we need to keep it in a module static variable. If the game
- * file name is omitted, then here we'll set the pointerto NULL, and
- * complain about it later in main. Passing the message string around
- * like this is a nuisance...
- */
- if (argv_index == argc - 1) {
- gln_gamefile = argv[argv_index];
- gln_game_message = NULL;
-#ifdef GARGLK
- {
- const char *s;
- s = strrchr(gln_gamefile, '\\');
- if (s)
- g_vm->garglk_set_story_name(s + 1);
- s = strrchr(gln_gamefile, '/');
- if (s)
- g_vm->garglk_set_story_name(s + 1);
- }
-#endif
- } else {
- gln_gamefile = NULL;
- if (argv_index < argc - 1)
- gln_game_message = "More than one game file was given"
- " on the command line.";
- else
- gln_game_message = "No game file was given on the command line.";
- }
-
/* All startup options were handled successfully. */
return TRUE;
}
-static void gln_main() {
+void gln_main(const char *filename) {
char *graphics_file = NULL;
int is_running;
@@ -5765,20 +5738,11 @@ static void gln_main() {
g_vm->glk_set_window(gln_main_window);
g_vm->glk_set_style(style_Normal);
- /* If there's a problem with the game file, complain now. */
- if (!gln_gamefile) {
- assert(gln_game_message);
- gln_header_string("Glk Level 9 Error\n\n");
- gln_normal_string(gln_game_message);
- gln_normal_char('\n');
- g_vm->glk_exit();
- }
-
/*
* Given the basic game name, try to come up with a usable graphics
* filenames. The graphics file may be null.
*/
- gln_establish_picture_filename(gln_gamefile, &graphics_file);
+ gln_establish_picture_filename(g_vm->getFilename().c_str(), &graphics_file);
/*
* Check Glk library capabilities, and note pictures are impossible if the
@@ -5798,7 +5762,7 @@ static void gln_main() {
/* If pictures are possible, search for bitmap graphics. */
if (gln_graphics_possible)
- gln_graphics_locate_bitmaps(gln_gamefile);
+ gln_graphics_locate_bitmaps(g_vm->getFilename().c_str());
/* Try to create a one-line status window. We can live without it. */
/*
@@ -5820,12 +5784,12 @@ static void gln_main() {
/* Load the game, sending in any established graphics file. */
int errNum = 0;
- if (!LoadGame(gln_gamefile, graphics_file)) {
+ if (!LoadGame(filename, graphics_file)) {
if (gln_status_window)
g_vm->glk_window_close(gln_status_window, NULL);
gln_header_string("Glk Level 9 Error\n\n");
gln_normal_string("Can't find, open, or load game file '");
- gln_normal_string(gln_gamefile);
+ gln_normal_string(g_vm->getFilename().c_str());
gln_normal_char('\'');
if (errNum != 0) {
gln_normal_string(": ERROR");
@@ -5927,97 +5891,5 @@ static void gln_main() {
free(graphics_file);
}
-
-/*---------------------------------------------------------------------*/
-/* Linkage between Glk entry/exit calls and the real interpreter */
-/*---------------------------------------------------------------------*/
-
-/*
- * Safety flags, to ensure we always get startup before main, and that
- * we only get a call to main once.
- */
-static int gln_startup_called = FALSE,
- gln_main_called = FALSE;
-
-/*
- * g_vm->glk_main()
- *
- * Main entry point for Glk. Here, all startup is done, and we call our
- * function to run the game.
- */
-void glk_main() {
- assert(gln_startup_called && !gln_main_called);
- gln_main_called = TRUE;
-
- /* Call the interpreter main function. */
- gln_main();
-}
-
-
-/*---------------------------------------------------------------------*/
-/* Glk linkage relevant only to the UNIX platform */
-/*---------------------------------------------------------------------*/
-#ifdef UNUSED
-
-/*
- * Glk arguments for UNIX versions of the Glk interpreter.
- */
-glkunix_argumentlist_t glkunix_arguments[] = {
- {
- (char *) "-nc", glkunix_arg_NoValue,
- (char *) "-nc No local handling for Glk special commands"
- },
- {
- (char *) "-na", glkunix_arg_NoValue,
- (char *) "-na Turn off abbreviation expansions"
- },
- {
- (char *) "-ni", glkunix_arg_NoValue,
- (char *) "-ni No local handling for 'quit', 'restart',"
- " 'save', and 'restore'"
- },
- {
- (char *) "-np", glkunix_arg_NoValue,
- (char *) "-np Turn off pictures"
- },
- {
- (char *) "-ne", glkunix_arg_NoValue,
- (char *) "-ne Turn off additional interpreter prompt"
- },
- {
- (char *) "-nl", glkunix_arg_NoValue,
- (char *) "-nl Turn off infinite loop detection"
- },
- {
- (char *) "", glkunix_arg_ValueCanFollow,
- (char *) "filename game to run"
- },
- {NULL, glkunix_arg_End, NULL}
-};
-
-
-/*
- * glkunix_startup_code()
- *
- * Startup entry point for UNIX versions of Glk interpreter. Glk will
- * call glkunix_startup_code() to pass in arguments. On startup, we call
- * our function to parse arguments and generally set stuff up.
- */
-int glkunix_startup_code(glkunix_startup_t *data) {
- assert(!gln_startup_called);
- gln_startup_called = TRUE;
-
-#ifdef GARGLK
- garg_vm->glk_set_program_name("Level 9 5.1");
- garg_vm->glk_set_program_info(
- "Level 9 5.1 by Glen Summers, David Kinder\n"
- "Alan Staniforth, Simon Baldwin and Dieter Baron\n"
- "Glk Graphics support by Tor Andersson\n");
-#endif
-
- return gln_startup_code(data->argc, data->argv);
-}
-#endif
-
} // End of namespace Level9
} // End of namespace Glk
Commit: 25c682793d0034fea7e5c48425f9acb4b43bba9f
https://github.com/scummvm/scummvm/commit/25c682793d0034fea7e5c48425f9acb4b43bba9f
Author: Paul Gilbert (dreammaster at scummvm.org)
Date: 2019-10-26T11:13:29-07:00
Commit Message:
GLK: LEVEL9: Data loading fixes and cleanup
Changed paths:
engines/glk/level9/level9_main.cpp
engines/glk/level9/os_glk.cpp
diff --git a/engines/glk/level9/level9_main.cpp b/engines/glk/level9/level9_main.cpp
index e8963c9..9a08984 100644
--- a/engines/glk/level9/level9_main.cpp
+++ b/engines/glk/level9/level9_main.cpp
@@ -891,34 +891,6 @@ L9BYTE calcchecksum(L9BYTE *ptr, L9UINT32 num) {
return d1;
}
-/*
-L9BOOL Check(L9BYTE* StartFile,L9UINT32 FileSize,L9UINT32 Offset)
-{
- L9UINT16 d0,num;
- int i;
- L9BYTE* Image;
- L9UINT32 Size=0,Min,Max;
- L9BOOL ret,JumpKill;
-
- for (i=0;i<12;i++)
- {
- d0=L9WORD (StartFile+Offset+0x12 + i*2);
- if (d0>=0x8000+LISTAREASIZE) return FALSE;
- }
-
- num=L9WORD(StartFile+Offset)+1;
- if (Offset+num>FileSize) return FALSE;
- if (calcchecksum(StartFile+Offset,num)) return FALSE;
-
- Image=calloc(FileSize,1);
-
- Min=Max=Offset+d0;
- ret=ValidateSequence(StartFile,Image,Offset+d0,Offset+d0,&Size,FileSize,&Min,&Max,FALSE,&JumpKill,NULL);
- free(Image);
- return ret;
-}
-*/
-
long Scan(L9BYTE *StartFile, L9UINT32 size) {
L9BYTE *Chk = (L9BYTE *)malloc(size + 1);
L9BYTE *Image = (L9BYTE *)calloc(size, 1);
diff --git a/engines/glk/level9/os_glk.cpp b/engines/glk/level9/os_glk.cpp
index eac1616..9005b6c 100644
--- a/engines/glk/level9/os_glk.cpp
+++ b/engines/glk/level9/os_glk.cpp
@@ -31,12 +31,7 @@ namespace Level9 {
#define BITS_PER_CHAR 8
/* File path delimiter, used to be #defined in v2 interpreter. */
-#if defined(_Windows) || defined(__MSDOS__) \
- || defined (_WIN32) || defined (__WIN32__)
-static const char GLN_FILE_DELIM = '\\';
-#else
static const char GLN_FILE_DELIM = '/';
-#endif
/*---------------------------------------------------------------------*/
/* Module variables, miscellaneous other stuff */
@@ -3660,10 +3655,14 @@ static void gln_header_string(const char *message) {
gln_styled_string(style_Header, message);
}
+#ifndef GARGLK
+
static void gln_banner_string(const char *message) {
gln_styled_string(style_Subheader, message);
}
+#endif
+
/*
* os_flush()
@@ -5227,6 +5226,8 @@ static void gln_event_wait_2(glui32 wait_type_1, glui32 wait_type_2, event_t *ev
do {
g_vm->glk_select(event);
+ if (g_vm->shouldQuit())
+ return;
switch (event->type) {
case evtype_Arrange:
@@ -5581,46 +5582,46 @@ static void gln_establish_picture_filename(const char *name, char **graphics) {
graphics_file = (char *)gln_malloc(strlen(base) + strlen(".___") + 1);
/* Form a candidate graphics file, using a .PIC extension. */
- if (f.isOpen()) {
+ if (!f.isOpen()) {
strcpy(graphics_file, base);
strcat(graphics_file, ".PIC");
f.open(graphics_file);
}
- if (f.isOpen()) {
+ if (!f.isOpen()) {
strcpy(graphics_file, base);
strcat(graphics_file, ".pic");
f.open(graphics_file);
}
/* Form a candidate graphics file, using a .CGA extension. */
- if (f.isOpen()) {
+ if (!f.isOpen()) {
strcpy(graphics_file, base);
strcat(graphics_file, ".CGA");
f.open(graphics_file);
}
- if (f.isOpen()) {
+ if (!f.isOpen()) {
strcpy(graphics_file, base);
strcat(graphics_file, ".cga");
f.open(graphics_file);
}
/* Form a candidate graphics file, using a .HRC extension. */
- if (f.isOpen()) {
+ if (!f.isOpen()) {
strcpy(graphics_file, base);
strcat(graphics_file, ".HRC");
f.open(graphics_file);
}
- if (f.isOpen()) {
+ if (!f.isOpen()) {
strcpy(graphics_file, base);
strcat(graphics_file, ".hrc");
f.open(graphics_file);
}
/* No access to graphics file. */
- if (f.isOpen()) {
+ if (!f.isOpen()) {
free(graphics_file);
graphics_file = NULL;
}
@@ -5721,13 +5722,6 @@ void gln_main(const char *filename) {
char *graphics_file = NULL;
int is_running;
- /* Ensure Level 9 internal types have the right sizes. */
- if (!(sizeof(gln_byte) == 1
- && sizeof(gln_uint16) == 2 && sizeof(gln_uint32) == 4)) {
- gln_fatal("GLK: Types sized incorrectly, recompilation is needed");
- g_vm->glk_exit();
- }
-
/* Create the main Glk window, and set its stream as current. */
gln_main_window = g_vm->glk_window_open(0, 0, 0, wintype_TextBuffer, 0);
if (!gln_main_window) {
@@ -5742,7 +5736,7 @@ void gln_main(const char *filename) {
* Given the basic game name, try to come up with a usable graphics
* filenames. The graphics file may be null.
*/
- gln_establish_picture_filename(g_vm->getFilename().c_str(), &graphics_file);
+ gln_establish_picture_filename(filename, &graphics_file);
/*
* Check Glk library capabilities, and note pictures are impossible if the
@@ -5762,7 +5756,7 @@ void gln_main(const char *filename) {
/* If pictures are possible, search for bitmap graphics. */
if (gln_graphics_possible)
- gln_graphics_locate_bitmaps(g_vm->getFilename().c_str());
+ gln_graphics_locate_bitmaps(filename);
/* Try to create a one-line status window. We can live without it. */
/*
@@ -5789,7 +5783,7 @@ void gln_main(const char *filename) {
g_vm->glk_window_close(gln_status_window, NULL);
gln_header_string("Glk Level 9 Error\n\n");
gln_normal_string("Can't find, open, or load game file '");
- gln_normal_string(g_vm->getFilename().c_str());
+ gln_normal_string(filename);
gln_normal_char('\'');
if (errNum != 0) {
gln_normal_string(": ERROR");
@@ -5806,9 +5800,7 @@ void gln_main(const char *filename) {
}
/* Print out a short banner. */
- gln_header_string("\nLevel 9 Interpreter, version 5.1\n");
- gln_banner_string("Written by Glen Summers and David Kinder\n"
- "Glk interface by Simon Baldwin\n\n");
+ gln_header_string("\nLevel 9 Interpreter, ScummVM version\n");
/*
* Set the stop reason indicator to none. A game will then exit with a
Commit: 0ecae64fdc00a7c874cc02a5cb4a1c37249687ef
https://github.com/scummvm/scummvm/commit/0ecae64fdc00a7c874cc02a5cb4a1c37249687ef
Author: Paul Gilbert (dreammaster at scummvm.org)
Date: 2019-10-26T11:13:29-07:00
Commit Message:
GLK: LEVEL9: Cleaning up initialization
Changed paths:
A engines/glk/level9/os_glk.h
engines/glk/level9/level9.cpp
engines/glk/level9/level9_main.cpp
engines/glk/level9/level9_main.h
engines/glk/level9/os_glk.cpp
diff --git a/engines/glk/level9/level9.cpp b/engines/glk/level9/level9.cpp
index 6d8baa1..be4ac22 100644
--- a/engines/glk/level9/level9.cpp
+++ b/engines/glk/level9/level9.cpp
@@ -21,15 +21,14 @@
*/
#include "glk/level9/level9.h"
+#include "glk/level9/level9_main.h"
+#include "glk/level9/os_glk.h"
namespace Glk {
namespace Level9 {
Level9 *g_vm = nullptr;
-extern void gln_main(const char *filename);
-extern int gln_startup_code(int argc, char *argv[]);
-
Level9::Level9(OSystem *syst, const GlkGameDescription &gameDesc) : GlkAPI(syst, gameDesc) {
g_vm = this;
}
@@ -37,6 +36,7 @@ Level9::Level9(OSystem *syst, const GlkGameDescription &gameDesc) : GlkAPI(syst,
void Level9::runGame() {
initialize();
+
_gameFile.close();
gln_main(getFilename().c_str());
diff --git a/engines/glk/level9/level9_main.cpp b/engines/glk/level9/level9_main.cpp
index 9a08984..043266a 100644
--- a/engines/glk/level9/level9_main.cpp
+++ b/engines/glk/level9/level9_main.cpp
@@ -57,10 +57,10 @@ namespace Level9 {
#define FIRSTLINESIZE 96
/* Typedefs */
-typedef struct {
+struct SaveStruct {
L9UINT16 vartable[256];
L9BYTE listarea[LISTAREASIZE];
-} SaveStruct;
+};
/* Enumerations */
enum L9GameTypes { L9_V1, L9_V2, L9_V3, L9_V4 };
@@ -76,7 +76,7 @@ enum L9MsgTypes { MSGT_V1, MSGT_V2 };
enum L9GfxTypes { GFX_V2, GFX_V3A, GFX_V3B, GFX_V3C };
/* Global Variables */
-L9BYTE *startfile = NULL, *pictureaddress = NULL, *picturedata = NULL;
+L9BYTE *startfile, *pictureaddress, *picturedata;
L9BYTE *startdata;
L9UINT32 FileSize, picturesize;
@@ -95,43 +95,39 @@ int L9GameType;
int L9MsgType;
char LastGame[MAX_PATH];
char FirstLine[FIRSTLINESIZE];
-int FirstLinePos = 0;
-int FirstPicture = -1;
+int FirstLinePos;
+int FirstPicture;
-#if defined(AMIGA) && defined(_DCC)
-__far SaveStruct ramsavearea[RAMSAVESLOTS];
-#else
SaveStruct ramsavearea[RAMSAVESLOTS];
-#endif
GameState workspace;
L9UINT16 randomseed;
-L9UINT16 constseed = 0;
+L9UINT16 constseed;
L9BOOL Running;
char ibuff[IBUFFSIZE];
L9BYTE *ibuffptr;
char obuff[34];
-Common::SeekableReadStream *scriptfile = NULL;
+Common::SeekableReadStream *scriptfile;
-L9BOOL Cheating = FALSE;
+L9BOOL Cheating;
int CheatWord;
GameState CheatWorkspace;
int reflectflag, scale, gintcolour, option;
-int l9textmode = 0, drawx = 0, drawy = 0, screencalled = 0, showtitle = 1;
-L9BYTE *gfxa5 = NULL;
-Bitmap *bitmap = NULL;
-int gfx_mode = GFX_V2;
+int l9textmode, drawx, drawy, screencalled, showtitle;
+L9BYTE *gfxa5;
+Bitmap *bitmap;
+int gfx_mode;
L9BYTE *GfxA5Stack[GFXSTACKSIZE];
-int GfxA5StackPos = 0;
+int GfxA5StackPos;
int GfxScaleStack[GFXSTACKSIZE];
-int GfxScaleStackPos = 0;
+int GfxScaleStackPos;
-char lastchar = '.';
-char lastactualchar = 0;
+char lastchar;
+char lastactualchar;
int d5;
L9BYTE *codeptr; /* instruction codes */
@@ -141,7 +137,7 @@ L9BYTE *list9ptr;
int unpackd3;
-L9BYTE exitreversaltable[16] = {0x00, 0x04, 0x06, 0x07, 0x01, 0x08, 0x02, 0x03, 0x05, 0x0a, 0x09, 0x0c, 0x0b, 0xff, 0xff, 0x0f};
+const L9BYTE exitreversaltable[16] = {0x00, 0x04, 0x06, 0x07, 0x01, 0x08, 0x02, 0x03, 0x05, 0x0a, 0x09, 0x0c, 0x0b, 0xff, 0xff, 0x0f};
L9UINT16 gnostack[128];
L9BYTE gnoscratch[32];
@@ -159,8 +155,7 @@ const L9V1GameInfo L9V1Games[] = {
0x15, 0x5d, 252, -0x3e70, 0x0000, -0x3d30, -0x3ca0, 0x0100, 0x4120, -0x3b9d, 0x3988, /* Lords of Time */
0x15, 0x6c, 284, -0x00f0, 0x0000, -0x0050, -0x0050, -0x0050, 0x0300, 0x1930, 0x3c17, /* Snowball */
};
-int L9V1Game = -1;
-
+int L9V1Game;
/* Prototypes */
L9BOOL LoadGame2(const char *filename, char *picname);
@@ -255,6 +250,30 @@ const char *const drivercalls[] = {
};
#endif
+void level9_initialize() {
+ FirstLinePos = 0;
+ FirstPicture = -1;
+ constseed = 0;
+ scriptfile = nullptr;
+ Cheating = FALSE;
+ l9textmode = 0;
+ drawx = 0;
+ drawy = 0;
+ screencalled = 0;
+ showtitle = 1;
+ gfxa5 = nullptr;
+ gfx_mode = GFX_V2;
+
+ GfxA5StackPos = 0;
+ GfxScaleStackPos = 0;
+
+ lastchar = '.';
+ lastactualchar = 0;
+ L9V1Game = -1;
+
+ startfile = pictureaddress = picturedata = nullptr;
+}
+
void initdict(L9BYTE *ptr) {
dictptr = ptr;
unpackcount = 8;
@@ -621,7 +640,7 @@ void printmessageV2(int Msg) {
void L9Allocate(L9BYTE **ptr, L9UINT32 Size) {
if (*ptr) free(*ptr);
*ptr = (L9BYTE *)malloc(Size);
- if (*ptr == NULL) {
+ if (*ptr == nullptr) {
error("Unable to allocate memory for the game! Exiting...");
}
}
@@ -629,11 +648,11 @@ void L9Allocate(L9BYTE **ptr, L9UINT32 Size) {
void FreeMemory() {
if (startfile) {
free(startfile);
- startfile = NULL;
+ startfile = nullptr;
}
if (pictureaddress) {
free(pictureaddress);
- pictureaddress = NULL;
+ pictureaddress = nullptr;
}
if (bitmap) {
free(bitmap);
@@ -641,11 +660,11 @@ void FreeMemory() {
}
if (scriptfile) {
delete scriptfile;
- scriptfile = NULL;
+ scriptfile = nullptr;
}
- picturedata = NULL;
+ picturedata = nullptr;
picturesize = 0;
- gfxa5 = NULL;
+ gfxa5 = nullptr;
}
L9BOOL load(const char *filename) {
@@ -901,7 +920,7 @@ long Scan(L9BYTE *StartFile, L9UINT32 size) {
long Offset = -1;
L9BOOL JumpKill, DriverV4;
- if ((Chk == NULL) || (Image == NULL)) {
+ if ((Chk == nullptr) || (Image == nullptr)) {
error("Unable to allocate memory for game scan! Exiting...");
}
@@ -967,7 +986,7 @@ long ScanV2(L9BYTE *StartFile, L9UINT32 size) {
long Offset = -1;
L9BOOL JumpKill;
- if ((Chk == NULL) || (Image == NULL)) {
+ if ((Chk == nullptr) || (Image == nullptr)) {
error("Unable to allocate memory for game scan! Exiting...");
}
@@ -992,7 +1011,7 @@ long ScanV2(L9BYTE *StartFile, L9UINT32 size) {
Size = 0;
Min = Max = i + d0;
- if (ValidateSequence(StartFile, Image, i + d0, i + d0, &Size, size, &Min, &Max, FALSE, &JumpKill, NULL)) {
+ if (ValidateSequence(StartFile, Image, i + d0, i + d0, &Size, size, &Min, &Max, FALSE, &JumpKill, nullptr)) {
#ifdef L9DEBUG
printf("Found valid V2 header at %ld, code size %ld", i, Size);
#endif
@@ -1021,7 +1040,7 @@ long ScanV1(L9BYTE *StartFile, L9UINT32 size) {
int dictOff1 = 0, dictOff2 = 0;
L9BYTE dictVal1 = 0xff, dictVal2 = 0xff;
- if (Image == NULL) {
+ if (Image == nullptr) {
error("Unable to allocate memory for game scan! Exiting...");
}
@@ -1030,7 +1049,7 @@ long ScanV1(L9BYTE *StartFile, L9UINT32 size) {
Size = 0;
Min = Max = i;
Replace = 0;
- if (ValidateSequence(StartFile, Image, i, i, &Size, size, &Min, &Max, FALSE, &JumpKill, NULL)) {
+ if (ValidateSequence(StartFile, Image, i, i, &Size, size, &Min, &Max, FALSE, &JumpKill, nullptr)) {
if (Size > MaxCount && Size > 100 && Size < 10000) {
MaxCount = Size;
//MaxMin = Min;
@@ -1109,7 +1128,7 @@ void FullScan(L9BYTE *StartFile, L9UINT32 size) {
Size = 0;
Min = Max = i;
Replace = 0;
- if (ValidateSequence(StartFile, Image, i, i, &Size, size, &Min, &Max, FALSE, &JumpKill, NULL)) {
+ if (ValidateSequence(StartFile, Image, i, i, &Size, size, &Min, &Max, FALSE, &JumpKill, nullptr)) {
if (Size > MaxCount) {
MaxCount = Size;
MaxMin = Min;
@@ -1223,11 +1242,11 @@ L9BOOL intinitialise(const char *filename, char *picname) {
if (pictureaddress) {
free(pictureaddress);
- pictureaddress = NULL;
+ pictureaddress = nullptr;
}
- picturedata = NULL;
+ picturedata = nullptr;
picturesize = 0;
- gfxa5 = NULL;
+ gfxa5 = nullptr;
if (!load(filename)) {
error("\rUnable to load: %s\r", filename);
@@ -1241,7 +1260,7 @@ L9BOOL intinitialise(const char *filename, char *picname) {
L9Allocate(&pictureaddress, picturesize);
if (f.read(pictureaddress, picturesize) != picturesize) {
free(pictureaddress);
- pictureaddress = NULL;
+ pictureaddress = nullptr;
picturesize = 0;
}
f.close();
@@ -1356,13 +1375,13 @@ L9BOOL intinitialise(const char *filename, char *picname) {
/* If there was no graphics file, look in the game data */
if (pictureaddress) {
if (!findsubs(pictureaddress, picturesize, &picturedata, &picturesize)) {
- picturedata = NULL;
+ picturedata = nullptr;
picturesize = 0;
}
} else {
if (!findsubs(startdata, FileSize, &picturedata, &picturesize)
&& !findsubs(startfile, startdata - startfile, &picturedata, &picturesize)) {
- picturedata = NULL;
+ picturedata = nullptr;
picturesize = 0;
}
}
@@ -1712,7 +1731,7 @@ void calldriver() {
} else {
os_set_filenumber(NewName, MAX_PATH, *a6);
}
- LoadGame2(NewName, NULL);
+ LoadGame2(NewName, nullptr);
} else driver(d0, a6);
}
@@ -1859,10 +1878,10 @@ void l9_fgets(char *s, int n, Common::SeekableReadStream *f) {
}
L9BOOL scriptinput(char *buffer, int size) {
- while (scriptfile != NULL) {
+ while (scriptfile != nullptr) {
if (scriptfile->eos()) {
delete scriptfile;
- scriptfile = NULL;
+ scriptfile = nullptr;
} else {
char *p = buffer;
*p = '\0';
@@ -2150,7 +2169,7 @@ L9BOOL corruptinginput() {
list9ptr = list9startptr;
- if (ibuffptr == NULL) {
+ if (ibuffptr == nullptr) {
if (Cheating) NextCheat();
else {
/* flush */
@@ -2183,7 +2202,7 @@ L9BOOL corruptinginput() {
while (TRUE) {
d0 = *a6++;
if (d0 == 0) {
- ibuffptr = NULL;
+ ibuffptr = nullptr;
L9SETWORD(list9ptr, 0);
return TRUE;
}
@@ -3107,7 +3126,7 @@ void show_picture(int pic) {
GfxScaleStackPos = 0;
absrunsub(0);
if (!findsub(pic, &gfxa5))
- gfxa5 = NULL;
+ gfxa5 = nullptr;
}
}
@@ -3117,14 +3136,14 @@ void picture() {
void GetPictureSize(int *width, int *height) {
if (L9GameType == L9_V4) {
- if (width != NULL)
+ if (width != nullptr)
*width = 0;
- if (height != NULL)
+ if (height != nullptr)
*height = 0;
} else {
- if (width != NULL)
+ if (width != nullptr)
*width = (gfx_mode != GFX_V3C) ? 160 : 320;
- if (height != NULL)
+ if (height != nullptr)
*height = (gfx_mode == GFX_V2) ? 128 : 96;
}
}
@@ -3132,7 +3151,7 @@ void GetPictureSize(int *width, int *height) {
L9BOOL RunGraphics() {
if (gfxa5) {
if (!getinstruction(&gfxa5))
- gfxa5 = NULL;
+ gfxa5 = nullptr;
return TRUE;
}
return FALSE;
@@ -3506,7 +3525,7 @@ L9BOOL LoadGame2(const char *filename, char *picname) {
/* may be already running a game, maybe in input routine */
Running = FALSE;
- ibuffptr = NULL;
+ ibuffptr = nullptr;
/* intstart */
if (!intinitialise(filename, picname)) return FALSE;
diff --git a/engines/glk/level9/level9_main.h b/engines/glk/level9/level9_main.h
index ab30242..ec04db3 100644
--- a/engines/glk/level9/level9_main.h
+++ b/engines/glk/level9/level9_main.h
@@ -86,32 +86,34 @@ struct Bitmap {
#define L9SETWORD(x,val) WRITE_LE_UINT16(x, val)
#define L9SETDWORD(x,val) WRITE_LE_UINT32(x, val)
+extern void level9_initialize();
+
/* routines provided by os dependent code */
-void os_printchar(char c);
-L9BOOL os_input(char *ibuff, int size);
-char os_readchar(int millis);
-L9BOOL os_stoplist(void);
-void os_flush(void);
-L9BOOL os_save_file(L9BYTE *Ptr, int Bytes);
-L9BOOL os_load_file(L9BYTE *Ptr, int *Bytes, int Max);
-L9BOOL os_get_game_file(char *NewName, int Size);
-void os_set_filenumber(char *NewName, int Size, int n);
-void os_graphics(int mode);
-void os_cleargraphics(void);
-void os_setcolour(int colour, int index);
-void os_drawline(int x1, int y1, int x2, int y2, int colour1, int colour2);
-void os_fill(int x, int y, int colour1, int colour2);
-void os_show_bitmap(int pic, int x, int y);
-Common::SeekableReadStream *os_open_script_file(void);
+extern void os_printchar(char c);
+extern L9BOOL os_input(char *ibuff, int size);
+extern char os_readchar(int millis);
+extern L9BOOL os_stoplist(void);
+extern void os_flush(void);
+extern L9BOOL os_save_file(L9BYTE *Ptr, int Bytes);
+extern L9BOOL os_load_file(L9BYTE *Ptr, int *Bytes, int Max);
+extern L9BOOL os_get_game_file(char *NewName, int Size);
+extern void os_set_filenumber(char *NewName, int Size, int n);
+extern void os_graphics(int mode);
+extern void os_cleargraphics(void);
+extern void os_setcolour(int colour, int index);
+extern void os_drawline(int x1, int y1, int x2, int y2, int colour1, int colour2);
+extern void os_fill(int x, int y, int colour1, int colour2);
+extern void os_show_bitmap(int pic, int x, int y);
+extern Common::SeekableReadStream *os_open_script_file(void);
/* routines provided by level9 interpreter */
-L9BOOL LoadGame(const char *filename, char *picname);
-L9BOOL RunGame(void);
-void StopGame(void);
-void RestoreGame(char *filename);
-void FreeMemory(void);
-void GetPictureSize(int *width, int *height);
-L9BOOL RunGraphics(void);
+extern L9BOOL LoadGame(const char *filename, char *picname);
+extern L9BOOL RunGame(void);
+extern void StopGame(void);
+extern void RestoreGame(char *filename);
+extern void FreeMemory(void);
+extern void GetPictureSize(int *width, int *height);
+extern L9BOOL RunGraphics(void);
/* bitmap routines provided by level9 interpreter */
BitmapType DetectBitmaps(char *dir);
diff --git a/engines/glk/level9/os_glk.cpp b/engines/glk/level9/os_glk.cpp
index 9005b6c..1b65ef1 100644
--- a/engines/glk/level9/os_glk.cpp
+++ b/engines/glk/level9/os_glk.cpp
@@ -30,9 +30,6 @@ namespace Level9 {
#define BYTE_MAX 0xff
#define BITS_PER_CHAR 8
-/* File path delimiter, used to be #defined in v2 interpreter. */
-static const char GLN_FILE_DELIM = '/';
-
/*---------------------------------------------------------------------*/
/* Module variables, miscellaneous other stuff */
/*---------------------------------------------------------------------*/
@@ -43,39 +40,32 @@ static const glui32 GLN_PORT_VERSION = 0x00020201;
/*
* We use a maximum of three Glk windows, one for status, one for pictures,
* and one for everything else. The status and pictures windows may be
- * NULL, depending on user selections and the capabilities of the Glk
+ * nullptr, depending on user selections and the capabilities of the Glk
* library.
*/
-static winid_t gln_main_window = NULL,
- gln_status_window = NULL,
- gln_graphics_window = NULL;
+static winid_t gln_main_window, gln_status_window, gln_graphics_window;
/*
- * Transcript stream and input log. These are NULL if there is no current
+ * Transcript stream and input log. These are nullptr if there is no current
* collection of these strings.
*/
-static strid_t gln_transcript_stream = NULL,
- gln_inputlog_stream = NULL;
+static strid_t gln_transcript_stream, gln_inputlog_stream;
/* Input read log stream, for reading back an input log. */
-static strid_t gln_readlog_stream = NULL;
+static strid_t gln_readlog_stream;
/* Note about whether graphics is possible, or not. */
-static int gln_graphics_possible = TRUE;
+bool gln_graphics_possible;
/* Options that may be turned off by command line flags. */
-static int gln_graphics_enabled = TRUE,
- gln_intercept_enabled = TRUE,
- gln_prompt_enabled = TRUE,
- gln_loopcheck_enabled = TRUE,
- gln_abbreviations_enabled = TRUE,
- gln_commands_enabled = TRUE;
+bool gln_graphics_enabled, gln_intercept_enabled, gln_prompt_enabled;
+bool gln_loopcheck_enabled, gln_abbreviations_enabled, gln_commands_enabled;
/* Reason for stopping the game, used to detect restarts and ^C exits. */
enum StopReason {
STOP_NONE, STOP_FORCE, STOP_RESTART, STOP_EXIT
};
-static StopReason gln_stop_reason = STOP_NONE;
+static StopReason gln_stop_reason;
/* Level 9 standard input prompt string. */
static const char *const GLN_INPUT_PROMPT = "> ";
@@ -111,6 +101,23 @@ static int gln_confirm(const char *prompt);
/* Glk port utility functions */
/*---------------------------------------------------------------------*/
+void gln_initialize() {
+ gln_main_window = nullptr;
+ gln_status_window = nullptr;
+ gln_graphics_window = nullptr;
+ gln_transcript_stream = nullptr;
+ gln_inputlog_stream = nullptr;
+ gln_readlog_stream = nullptr;
+ gln_graphics_possible = TRUE;
+ gln_graphics_enabled = TRUE;
+ gln_intercept_enabled = TRUE;
+ gln_prompt_enabled = TRUE;
+ gln_loopcheck_enabled = TRUE;
+ gln_abbreviations_enabled = TRUE;
+ gln_commands_enabled = TRUE;
+ gln_stop_reason = STOP_NONE;
+}
+
/*
* gln_fatal()
*
@@ -128,7 +135,7 @@ static void gln_fatal(const char *string) {
}
/* Cancel all possible pending window input events. */
- g_vm->glk_cancel_line_event(gln_main_window, NULL);
+ g_vm->glk_cancel_line_event(gln_main_window, nullptr);
g_vm->glk_cancel_char_event(gln_main_window);
/* Print a message indicating the error. */
@@ -279,7 +286,7 @@ static gln_uint16 gln_get_buffer_crc(const void *void_buffer, size_t length, siz
* and may be re-requested when, say, the game changes, perhaps by moving to
* the next part of a multipart game.
*/
-static const char *gln_gameid_game_name = NULL;
+static const char *gln_gameid_game_name = nullptr;
/*
@@ -701,7 +708,7 @@ static const gln_game_table_t GLN_GAME_TABLE[] = {
{0x110f, 0x00, 0x4b57, "Champion of the Raj (French) 2/2 GD (ST)"},
- {0x0000, 0x00, 0x0000, NULL}
+ {0x0000, 0x00, 0x0000, nullptr}
};
@@ -1080,7 +1087,7 @@ static const gln_patch_table_t GLN_PATCH_TABLE[] = {
* gln_gameid_lookup_patch()
*
* Look up and return game table and patch table entries given a game's
- * length, checksum, and CRC. Returns the entry, or NULL if not found.
+ * length, checksum, and CRC. Returns the entry, or nullptr if not found.
*/
static gln_game_tableref_t gln_gameid_lookup_game(gln_uint16 length, gln_byte checksum,
gln_uint16 crc, int ignore_crc) {
@@ -1092,7 +1099,7 @@ static gln_game_tableref_t gln_gameid_lookup_game(gln_uint16 length, gln_byte ch
break;
}
- return game->length ? game : NULL;
+ return game->length ? game : nullptr;
}
static gln_patch_tableref_t gln_gameid_lookup_patch(gln_uint16 length, gln_byte checksum,
@@ -1105,7 +1112,7 @@ static gln_patch_tableref_t gln_gameid_lookup_patch(gln_uint16 length, gln_byte
break;
}
- return patch->length ? patch : NULL;
+ return patch->length ? patch : nullptr;
}
@@ -1113,7 +1120,7 @@ static gln_patch_tableref_t gln_gameid_lookup_patch(gln_uint16 length, gln_byte
* gln_gameid_identify_game()
*
* Identify a game from its data length, checksum, and CRC. Returns the
- * entry of the game in the game table, or NULL if not found.
+ * entry of the game in the game table, or nullptr if not found.
*
* This function uses startdata and FileSize from the core interpreter.
* These aren't advertised symbols, so be warned.
@@ -1127,7 +1134,7 @@ static gln_game_tableref_t gln_gameid_identify_game() {
/* If the data file appears too short for a header, give up now. */
if (FileSize < 30)
- return NULL;
+ return nullptr;
/*
* Find the version of the game, and the length of game data. This logic
@@ -1144,7 +1151,7 @@ static gln_game_tableref_t gln_gameid_identify_game() {
? startdata[28] | startdata[29] << BITS_PER_CHAR
: startdata[0] | startdata[1] << BITS_PER_CHAR;
if (length >= FileSize)
- return NULL;
+ return nullptr;
/* Calculate or retrieve the checksum, in a version specific way. */
if (is_version2) {
@@ -1184,7 +1191,7 @@ static gln_game_tableref_t gln_gameid_identify_game() {
/*
* gln_gameid_get_game_name()
*
- * Return the name of the game, or NULL if not identifiable.
+ * Return the name of the game, or nullptr if not identifiable.
*
* This function uses startdata from the core interpreter. This isn't an
* advertised symbol, so be warned.
@@ -1200,20 +1207,20 @@ static const char *gln_gameid_get_game_name() {
gln_game_tableref_t game;
/*
- * If the interpreter hasn't yet loaded a game, startdata is NULL
- * (uninitialized, global). In this case, we return NULL, allowing
+ * If the interpreter hasn't yet loaded a game, startdata is nullptr
+ * (uninitialized, global). In this case, we return nullptr, allowing
* for retries until a game is loaded.
*/
if (!startdata)
- return NULL;
+ return nullptr;
game = gln_gameid_identify_game();
gln_gameid_game_name = game ? game->name : "";
}
- /* Return the game's name, or NULL if it was unidentifiable. */
+ /* Return the game's name, or nullptr if it was unidentifiable. */
assert(gln_gameid_game_name);
- return strlen(gln_gameid_game_name) > 0 ? gln_gameid_game_name : NULL;
+ return strlen(gln_gameid_game_name) > 0 ? gln_gameid_game_name : nullptr;
}
@@ -1225,7 +1232,7 @@ static const char *gln_gameid_get_game_name() {
* change game file, for example os_set_filenumber().
*/
static void gln_gameid_game_name_reset() {
- gln_gameid_game_name = NULL;
+ gln_gameid_game_name = nullptr;
}
@@ -1307,12 +1314,12 @@ static const int GLN_GRAPHICS_BORDER = 1,
static const int GLN_GRAPHICS_UNUSED_PIXEL = 0xff;
/* Graphics file directory, and type of graphics found in it. */
-static char *gln_graphics_bitmap_directory = NULL;
+static char *gln_graphics_bitmap_directory = nullptr;
static BitmapType gln_graphics_bitmap_type = NO_BITMAPS;
/* The current picture id being displayed. */
enum { GLN_PALETTE_SIZE = 32 };
-static gln_byte *gln_graphics_bitmap = NULL;
+static gln_byte *gln_graphics_bitmap = nullptr;
static gln_uint16 gln_graphics_width = 0,
gln_graphics_height = 0;
static Colour gln_graphics_palette[GLN_PALETTE_SIZE]; /* = { 0, ... }; */
@@ -1343,8 +1350,8 @@ static GraphicsState gln_graphics_interpreter_state = GLN_GRAPHICS_OFF;
* of pixels, and the other tracking on-screen data. These are temporary
* graphics malloc'ed memory, and should be free'd on exit.
*/
-static gln_byte *gln_graphics_off_screen = NULL,
- *gln_graphics_on_screen = NULL;
+static gln_byte *gln_graphics_off_screen = nullptr,
+ *gln_graphics_on_screen = nullptr;
/*
* The number of colors used in the palette by the current picture. Because
@@ -1371,19 +1378,19 @@ static int gln_graphics_open() {
wintype_Graphics, 0);
}
- return gln_graphics_window != NULL;
+ return gln_graphics_window != nullptr;
}
/*
* gln_graphics_close()
*
- * If open, close the graphics window and set back to NULL.
+ * If open, close the graphics window and set back to nullptr.
*/
static void gln_graphics_close() {
if (gln_graphics_window) {
- g_vm->glk_window_close(gln_graphics_window, NULL);
- gln_graphics_window = NULL;
+ g_vm->glk_window_close(gln_graphics_window, nullptr);
+ gln_graphics_window = nullptr;
}
}
@@ -1424,7 +1431,7 @@ static void gln_graphics_stop() {
* Return TRUE if graphics are currently being displayed, FALSE otherwise.
*/
static int gln_graphics_are_displayed() {
- return gln_graphics_window != NULL;
+ return gln_graphics_window != nullptr;
}
@@ -2263,8 +2270,7 @@ static void gln_graphics_locate_bitmaps(const char *gamefile) {
BitmapType bitmap_type;
/* Find the start of the last element of the filename passed in. */
- basename = strrchr(gamefile, GLN_FILE_DELIM);
- basename = basename ? basename + 1 : gamefile;
+ basename = gamefile;
/* Take a copy of the directory part of the filename. */
dirname = (char *)gln_malloc(basename - gamefile + 1);
@@ -2438,7 +2444,7 @@ void os_show_bitmap(int picture, int x, int y) {
* FALSE if there is no picture available to display.
*/
static int gln_graphics_picture_is_available() {
- return gln_graphics_bitmap != NULL;
+ return gln_graphics_bitmap != nullptr;
}
@@ -2482,7 +2488,7 @@ static int gln_graphics_get_rendering_details(const char **bitmap_type,
if (gln_graphics_enabled && gln_graphics_are_displayed()) {
/*
* Convert the detected bitmap type into a string and return it.
- * A NULL bitmap string implies no bitmaps.
+ * A nullptr bitmap string implies no bitmaps.
*/
if (bitmap_type) {
const char *return_type;
@@ -2517,7 +2523,7 @@ static int gln_graphics_get_rendering_details(const char **bitmap_type,
break;
case NO_BITMAPS:
default:
- return_type = NULL;
+ return_type = nullptr;
break;
}
@@ -2562,13 +2568,13 @@ static int gln_graphics_interpreter_enabled() {
*/
static void gln_graphics_cleanup() {
free(gln_graphics_bitmap);
- gln_graphics_bitmap = NULL;
+ gln_graphics_bitmap = nullptr;
free(gln_graphics_off_screen);
- gln_graphics_off_screen = NULL;
+ gln_graphics_off_screen = nullptr;
free(gln_graphics_on_screen);
- gln_graphics_on_screen = NULL;
+ gln_graphics_on_screen = nullptr;
free(gln_graphics_bitmap_directory);
- gln_graphics_bitmap_directory = NULL;
+ gln_graphics_bitmap_directory = nullptr;
gln_graphics_bitmap_type = NO_BITMAPS;
gln_graphics_picture = -1;
@@ -2607,7 +2613,7 @@ struct gln_linegraphics_segment_t {
int dy; /* Segment y delta */
};
-static gln_linegraphics_segment_t *gln_linegraphics_fill_segments = NULL;
+static gln_linegraphics_segment_t *gln_linegraphics_fill_segments = nullptr;
static int gln_linegraphics_fill_segments_allocation = 0,
gln_linegraphics_fill_segments_length = 0;
@@ -3001,7 +3007,7 @@ static void gln_linegraphics_process() {
*/
static void gln_linegraphics_cleanup() {
free(gln_linegraphics_fill_segments);
- gln_linegraphics_fill_segments = NULL;
+ gln_linegraphics_fill_segments = nullptr;
gln_linegraphics_fill_segments_allocation = 0;
gln_linegraphics_fill_segments_length = 0;
@@ -3383,7 +3389,7 @@ static void gln_status_redraw() {
*/
parent = g_vm->glk_window_get_parent(gln_status_window);
g_vm->glk_window_set_arrangement(parent,
- winmethod_Above | winmethod_Fixed, 1, NULL);
+ winmethod_Above | winmethod_Fixed, 1, nullptr);
gln_status_update();
}
@@ -3406,7 +3412,7 @@ static int gln_help_requested = FALSE,
* more efficient for everyone if we buffer them, and output a complete
* string on a flush call.
*/
-static char *gln_output_buffer = NULL;
+static char *gln_output_buffer = nullptr;
static int gln_output_allocation = 0,
gln_output_length = 0;
@@ -3532,7 +3538,7 @@ static void gln_detect_game_prompt() {
*/
static void gln_output_delete() {
free(gln_output_buffer);
- gln_output_buffer = NULL;
+ gln_output_buffer = nullptr;
gln_output_allocation = gln_output_length = 0;
}
@@ -3727,10 +3733,10 @@ static void gln_command_script(const char *argument) {
return;
}
- g_vm->glk_stream_close(gln_transcript_stream, NULL);
- gln_transcript_stream = NULL;
+ g_vm->glk_stream_close(gln_transcript_stream, nullptr);
+ gln_transcript_stream = nullptr;
- g_vm->glk_window_set_echo_stream(gln_main_window, NULL);
+ g_vm->glk_window_set_echo_stream(gln_main_window, nullptr);
gln_normal_string("Glk transcript is now off.\n");
}
@@ -3792,8 +3798,8 @@ static void gln_command_inputlog(const char *argument) {
return;
}
- g_vm->glk_stream_close(gln_inputlog_stream, NULL);
- gln_inputlog_stream = NULL;
+ g_vm->glk_stream_close(gln_inputlog_stream, nullptr);
+ gln_inputlog_stream = nullptr;
gln_normal_string("Glk input log is now off.\n");
}
@@ -3860,8 +3866,8 @@ static void gln_command_readlog(const char *argument) {
return;
}
- g_vm->glk_stream_close(gln_readlog_stream, NULL);
- gln_readlog_stream = NULL;
+ g_vm->glk_stream_close(gln_readlog_stream, nullptr);
+ gln_readlog_stream = nullptr;
gln_normal_string("Glk read log is now off.\n");
}
@@ -4268,7 +4274,7 @@ static const gln_command_t GLN_COMMAND_TABLE[] = {
{"version", gln_command_version, FALSE},
{"commands", gln_command_commands, TRUE},
{"help", gln_command_help, TRUE},
- {NULL, NULL, FALSE}
+ {nullptr, nullptr, FALSE}
};
@@ -4323,7 +4329,7 @@ static void gln_command_help(const char *command) {
return;
}
- matched = NULL;
+ matched = nullptr;
for (entry = GLN_COMMAND_TABLE; entry->command; entry++) {
if (gln_strncasecmp(command, entry->command, strlen(command)) == 0) {
if (matched) {
@@ -4531,7 +4537,7 @@ static int gln_command_escape(const char *string) {
* the command passed in.
*/
matches = 0;
- matched = NULL;
+ matched = nullptr;
for (entry = GLN_COMMAND_TABLE; entry->command; entry++) {
if (gln_strncasecmp(command, entry->command, strlen(command)) == 0) {
matches++;
@@ -4696,7 +4702,7 @@ static const gln_abbreviation_t GLN_ABBREVIATIONS[] = {
{'k', "attack"}, {'l', "look"}, {'p', "open"},
{'q', "quit"}, {'r', "drop"}, {'t', "take"},
{'x', "examine"}, {'y', "yes"}, {'z', "wait"},
- {'\0', NULL}
+ {'\0', nullptr}
};
@@ -4720,7 +4726,7 @@ static void gln_expand_abbreviations(char *buffer, int size) {
/* Scan the abbreviations table for a match. */
abbreviation = g_vm->glk_char_to_lower((unsigned char) command[0]);
- expansion = NULL;
+ expansion = nullptr;
for (entry = GLN_ABBREVIATIONS; entry->expansion; entry++) {
if (entry->abbreviation == abbreviation) {
expansion = entry->expansion;
@@ -4828,8 +4834,8 @@ gln_bool os_input(char *buffer, int size) {
* We're at the end of the log stream. Close it, and then continue
* on to request a line from Glk.
*/
- g_vm->glk_stream_close(gln_readlog_stream, NULL);
- gln_readlog_stream = NULL;
+ g_vm->glk_stream_close(gln_readlog_stream, nullptr);
+ gln_readlog_stream = nullptr;
}
/*
@@ -5289,7 +5295,7 @@ gln_bool os_save_file(gln_byte *ptr, int bytes) {
/* Write game state. */
g_vm->glk_put_buffer_stream(stream, (const char *)ptr, bytes);
- g_vm->glk_stream_close(stream, NULL);
+ g_vm->glk_stream_close(stream, nullptr);
g_vm->glk_fileref_destroy(fileref);
gln_watchdog_tick();
@@ -5331,7 +5337,7 @@ gln_bool os_load_file(gln_byte *ptr, int *bytes, int max) {
/* Restore saved game data. */
*bytes = g_vm->glk_get_buffer_stream(stream, (char *)ptr, max);
- g_vm->glk_stream_close(stream, NULL);
+ g_vm->glk_stream_close(stream, nullptr);
g_vm->glk_fileref_destroy(fileref);
gln_watchdog_tick();
@@ -5372,9 +5378,7 @@ gln_bool os_get_game_file(char *newname, int size) {
Common::File f;
assert(newname);
- /* Find the last element of the filename passed in. */
- basename = strrchr(newname, GLN_FILE_DELIM);
- basename = basename ? basename + 1 : newname;
+ basename = newname;
/* Search for the last numeric character in the basename. */
digit = -1;
@@ -5453,9 +5457,7 @@ void os_set_filenumber(char *newname, int size, int file_number) {
return;
}
- /* Find the last element of the new filename. */
- basename = strrchr(newname, GLN_FILE_DELIM);
- basename = basename ? basename + 1 : newname;
+ basename = newname;
/* Search for the last numeric character in the basename. */
digit = -1;
@@ -5493,7 +5495,7 @@ void os_set_filenumber(char *newname, int size, int file_number) {
* own way of handling scripts, this function is a stub.
*/
Common::SeekableReadStream *os_open_script_file() {
- return NULL;
+ return nullptr;
}
@@ -5546,8 +5548,7 @@ static const int GLN_WATCHDOG_TIMEOUT = 5,
* The following values need to be passed between the startup_code and main
* functions.
*/
-static const char *gln_game_message = NULL; /* Error message. */
-
+static const char *gln_game_message = nullptr; /* Error message. */
/*
* gln_establish_picture_filename()
@@ -5557,7 +5558,7 @@ static const char *gln_game_message = NULL; /* Error message. */
* PICTURE.DAT or picture.dat in the same directory as X. If the input file
* already ends with a three-letter extension, it's stripped first.
*
- * The function returns NULL if a graphics file is not available. It's not
+ * The function returns nullptr if a graphics file is not available. It's not
* fatal for this to be the case. Filenames are malloc'ed, and need to be
* freed by the caller.
*
@@ -5565,7 +5566,7 @@ static const char *gln_game_message = NULL; /* Error message. */
* standard function, and access() isn't.
*/
static void gln_establish_picture_filename(const char *name, char **graphics) {
- char *base, *directory_end, *graphics_file;
+ char *base, *graphics_file;
Common::File f;
assert(name && graphics);
@@ -5623,7 +5624,7 @@ static void gln_establish_picture_filename(const char *name, char **graphics) {
/* No access to graphics file. */
if (!f.isOpen()) {
free(graphics_file);
- graphics_file = NULL;
+ graphics_file = nullptr;
}
f.close();
@@ -5635,11 +5636,6 @@ static void gln_establish_picture_filename(const char *name, char **graphics) {
return;
}
- /* Retry with base set to the game file directory part only. */
- directory_end = strrchr(base, GLN_FILE_DELIM);
- directory_end = directory_end ? directory_end + 1 : base;
- base[directory_end - base] = '\0';
-
/* Again, allocate space for the return graphics file. */
graphics_file = (char *)gln_malloc(strlen(base) + strlen("PICTURE.DAT") + 1);
@@ -5654,24 +5650,23 @@ static void gln_establish_picture_filename(const char *name, char **graphics) {
if (!f.open(graphics_file)) {
/*
* No access to this graphics file. In this case, free memory
- * and reset graphics file to NULL.
+ * and reset graphics file to nullptr.
*/
free(graphics_file);
- graphics_file = NULL;
+ graphics_file = nullptr;
}
}
f.close();
/*
- * Return whatever we found for the graphics file (NULL if none found),
+ * Return whatever we found for the graphics file (nullptr if none found),
* and free base.
*/
*graphics = graphics_file;
free(base);
}
-
/*
* gln_startup_code()
* gln_main()
@@ -5719,7 +5714,7 @@ int gln_startup_code(int argc, char *argv[]) {
}
void gln_main(const char *filename) {
- char *graphics_file = NULL;
+ char *graphics_file = nullptr;
int is_running;
/* Create the main Glk window, and set its stream as current. */
@@ -5780,7 +5775,7 @@ void gln_main(const char *filename) {
int errNum = 0;
if (!LoadGame(filename, graphics_file)) {
if (gln_status_window)
- g_vm->glk_window_close(gln_status_window, NULL);
+ g_vm->glk_window_close(gln_status_window, nullptr);
gln_header_string("Glk Level 9 Error\n\n");
gln_normal_string("Can't find, open, or load game file '");
gln_normal_string(filename);
@@ -5867,16 +5862,16 @@ void gln_main(const char *filename) {
/* Close any open transcript, input log, and/or read log. */
if (gln_transcript_stream) {
- g_vm->glk_stream_close(gln_transcript_stream, NULL);
- gln_transcript_stream = NULL;
+ g_vm->glk_stream_close(gln_transcript_stream, nullptr);
+ gln_transcript_stream = nullptr;
}
if (gln_inputlog_stream) {
- g_vm->glk_stream_close(gln_inputlog_stream, NULL);
- gln_inputlog_stream = NULL;
+ g_vm->glk_stream_close(gln_inputlog_stream, nullptr);
+ gln_inputlog_stream = nullptr;
}
if (gln_readlog_stream) {
- g_vm->glk_stream_close(gln_readlog_stream, NULL);
- gln_readlog_stream = NULL;
+ g_vm->glk_stream_close(gln_readlog_stream, nullptr);
+ gln_readlog_stream = nullptr;
}
/* Free any graphics file path. */
diff --git a/engines/glk/level9/os_glk.h b/engines/glk/level9/os_glk.h
new file mode 100644
index 0000000..a10a640
--- /dev/null
+++ b/engines/glk/level9/os_glk.h
@@ -0,0 +1,39 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef GLK_LEVEL9_OS_GLK
+#define GLK_LEVEL9_OS_GLK
+
+namespace Glk {
+namespace Level9 {
+
+extern bool gln_graphics_enabled;
+extern bool gln_graphics_possible;
+
+extern void gln_initialize();
+extern void gln_main(const char *filename);
+extern int gln_startup_code(int argc, char *argv[]);
+
+} // End of namespace Alan2
+} // End of namespace Glk
+
+#endif
Commit: d923ed3c5fbb1aad03c82f29cf93412215ae662e
https://github.com/scummvm/scummvm/commit/d923ed3c5fbb1aad03c82f29cf93412215ae662e
Author: Paul Gilbert (dreammaster at scummvm.org)
Date: 2019-10-26T11:13:29-07:00
Commit Message:
GLK: LEVEL9: Fixes for exiting game by closing window
Changed paths:
engines/glk/level9/level9_main.cpp
engines/glk/level9/os_glk.cpp
diff --git a/engines/glk/level9/level9_main.cpp b/engines/glk/level9/level9_main.cpp
index 043266a..2ce60a5 100644
--- a/engines/glk/level9/level9_main.cpp
+++ b/engines/glk/level9/level9_main.cpp
@@ -3559,6 +3559,10 @@ L9BOOL RunGame() {
code = *codeptr++;
/* printf("%d",code); */
executeinstruction();
+
+ if (g_vm->shouldQuit())
+ Running = false;
+
return Running;
}
diff --git a/engines/glk/level9/os_glk.cpp b/engines/glk/level9/os_glk.cpp
index 1b65ef1..7722507 100644
--- a/engines/glk/level9/os_glk.cpp
+++ b/engines/glk/level9/os_glk.cpp
@@ -4844,6 +4844,11 @@ gln_bool os_input(char *buffer, int size) {
*/
g_vm->glk_request_line_event(gln_main_window, buffer, size - 1, 0);
gln_event_wait(evtype_LineInput, &event);
+ if (g_vm->shouldQuit()) {
+ g_vm->glk_cancel_line_event(gln_main_window, &event);
+ gln_stop_reason = STOP_EXIT;
+ return FALSE;
+ }
/* Terminate the input line with a NUL. */
assert((int)event.val1 <= size - 1);
Commit: fa323c6187ac2a698805c07f6dd2b906e9c117ee
https://github.com/scummvm/scummvm/commit/fa323c6187ac2a698805c07f6dd2b906e9c117ee
Author: Paul Gilbert (dreammaster at scummvm.org)
Date: 2019-10-26T11:13:29-07:00
Commit Message:
GLK: LEVEL9: Further variable initialization
Changed paths:
engines/glk/level9/os_glk.cpp
diff --git a/engines/glk/level9/os_glk.cpp b/engines/glk/level9/os_glk.cpp
index 7722507..8c97c5c 100644
--- a/engines/glk/level9/os_glk.cpp
+++ b/engines/glk/level9/os_glk.cpp
@@ -96,6 +96,56 @@ static void gln_standout_string(const char *message);
static int gln_confirm(const char *prompt);
+/* Picture variables */
+/* Graphics file directory, and type of graphics found in it. */
+static char *gln_graphics_bitmap_directory = nullptr;
+static BitmapType gln_graphics_bitmap_type = NO_BITMAPS;
+
+/* The current picture id being displayed. */
+enum { GLN_PALETTE_SIZE = 32 };
+static gln_byte *gln_graphics_bitmap = nullptr;
+static gln_uint16 gln_graphics_width = 0,
+gln_graphics_height = 0;
+static Colour gln_graphics_palette[GLN_PALETTE_SIZE]; /* = { 0, ... }; */
+static int gln_graphics_picture = -1;
+
+/*
+ * Flags set on new picture, and on resize or arrange events, and a flag
+ * to indicate whether background repaint is stopped or active.
+ */
+static int gln_graphics_new_picture = FALSE,
+gln_graphics_repaint = FALSE,
+gln_graphics_active = FALSE;
+
+/*
+ * State to monitor the state of interpreter graphics. The values of the
+ * enumerations match the modes supplied by os_graphics().
+ */
+enum GraphicsState {
+ GLN_GRAPHICS_OFF = 0,
+ GLN_GRAPHICS_LINE_MODE = 1,
+ GLN_GRAPHICS_BITMAP_MODE = 2
+};
+static GraphicsState gln_graphics_interpreter_state = GLN_GRAPHICS_OFF;
+
+
+/*
+ * Pointer to the two graphics buffers, one the off-screen representation
+ * of pixels, and the other tracking on-screen data. These are temporary
+ * graphics malloc'ed memory, and should be free'd on exit.
+ */
+static gln_byte *gln_graphics_off_screen = nullptr,
+*gln_graphics_on_screen = nullptr;
+
+/*
+ * The number of colors used in the palette by the current picture. Because
+ * of the way it's queried, we risk a race, with admittedly a very low
+ * probability, with the updater. So, it's initialized instead to the
+ * largest possible value. The real value in use is inserted on the first
+ * picture update timeout call for a new picture.
+ */
+static int gln_graphics_color_count = GLN_PALETTE_SIZE;
+
/*---------------------------------------------------------------------*/
/* Glk port utility functions */
@@ -116,6 +166,20 @@ void gln_initialize() {
gln_abbreviations_enabled = TRUE;
gln_commands_enabled = TRUE;
gln_stop_reason = STOP_NONE;
+
+ gln_graphics_bitmap_directory = nullptr;
+ gln_graphics_bitmap_type = NO_BITMAPS;
+ gln_graphics_bitmap = nullptr;
+ gln_graphics_width = 0;
+ gln_graphics_height = 0;
+ gln_graphics_picture = -1;
+ gln_graphics_new_picture = FALSE;
+ gln_graphics_repaint = FALSE;
+ gln_graphics_active = FALSE;
+ gln_graphics_interpreter_state = GLN_GRAPHICS_OFF;
+ gln_graphics_off_screen = nullptr;
+ gln_graphics_on_screen = nullptr;
+ gln_graphics_color_count = GLN_PALETTE_SIZE;
}
/*
@@ -1313,55 +1377,6 @@ static const int GLN_GRAPHICS_BORDER = 1,
*/
static const int GLN_GRAPHICS_UNUSED_PIXEL = 0xff;
-/* Graphics file directory, and type of graphics found in it. */
-static char *gln_graphics_bitmap_directory = nullptr;
-static BitmapType gln_graphics_bitmap_type = NO_BITMAPS;
-
-/* The current picture id being displayed. */
-enum { GLN_PALETTE_SIZE = 32 };
-static gln_byte *gln_graphics_bitmap = nullptr;
-static gln_uint16 gln_graphics_width = 0,
- gln_graphics_height = 0;
-static Colour gln_graphics_palette[GLN_PALETTE_SIZE]; /* = { 0, ... }; */
-static int gln_graphics_picture = -1;
-
-/*
- * Flags set on new picture, and on resize or arrange events, and a flag
- * to indicate whether background repaint is stopped or active.
- */
-static int gln_graphics_new_picture = FALSE,
- gln_graphics_repaint = FALSE,
- gln_graphics_active = FALSE;
-
-/*
- * State to monitor the state of interpreter graphics. The values of the
- * enumerations match the modes supplied by os_graphics().
- */
-enum GraphicsState {
- GLN_GRAPHICS_OFF = 0,
- GLN_GRAPHICS_LINE_MODE = 1,
- GLN_GRAPHICS_BITMAP_MODE = 2
-};
-static GraphicsState gln_graphics_interpreter_state = GLN_GRAPHICS_OFF;
-
-
-/*
- * Pointer to the two graphics buffers, one the off-screen representation
- * of pixels, and the other tracking on-screen data. These are temporary
- * graphics malloc'ed memory, and should be free'd on exit.
- */
-static gln_byte *gln_graphics_off_screen = nullptr,
- *gln_graphics_on_screen = nullptr;
-
-/*
- * The number of colors used in the palette by the current picture. Because
- * of the way it's queried, we risk a race, with admittedly a very low
- * probability, with the updater. So, it's initialized instead to the
- * largest possible value. The real value in use is inserted on the first
- * picture update timeout call for a new picture.
- */
-static int gln_graphics_color_count = GLN_PALETTE_SIZE;
-
/*
* gln_graphics_open()
Commit: 07c8437e268b12c0633495579f56c4e08e8ef7d2
https://github.com/scummvm/scummvm/commit/07c8437e268b12c0633495579f56c4e08e8ef7d2
Author: Paul Gilbert (dreammaster at scummvm.org)
Date: 2019-10-26T11:13:29-07:00
Commit Message:
GLK: LEVEL9: Title screen graphics now showing
Changed paths:
engines/glk/glk_api.cpp
engines/glk/glk_api.h
engines/glk/level9/level9.cpp
engines/glk/level9/os_glk.cpp
engines/glk/window_graphics.cpp
engines/glk/window_graphics.h
diff --git a/engines/glk/glk_api.cpp b/engines/glk/glk_api.cpp
index d86bd26..6d34e3a 100644
--- a/engines/glk/glk_api.cpp
+++ b/engines/glk/glk_api.cpp
@@ -911,6 +911,39 @@ bool GlkAPI::glk_image_draw_scaled(winid_t win, uint image, int val1, int val2,
return false;
}
+bool GlkAPI::glk_image_draw(winid_t win, const Graphics::Surface &image, uint transColor,
+ int xp, int yp) {
+ if (!win) {
+ warning("image_draw: invalid ref");
+ } else if (g_conf->_graphics) {
+ GraphicsWindow *gfxWin = dynamic_cast<GraphicsWindow *>(win);
+
+ if (gfxWin)
+ gfxWin->drawPicture(image, 0xff, xp, yp, 0, 0);
+ }
+
+ return true;
+}
+
+bool GlkAPI::glk_image_draw_scaled(winid_t win, const Graphics::Surface &image, uint transColor,
+ int xp, int yp, uint width, uint height) {
+ if (!win) {
+ warning("image_draw_scaled: invalid ref");
+ } else if (g_conf->_graphics) {
+ GraphicsWindow *gfxWin = dynamic_cast<GraphicsWindow *>(win);
+
+ Graphics::ManagedSurface s(width, height);
+ s.clear(transColor);
+ s.transBlitFrom(image, Common::Rect(0, 0, image.w, image.h),
+ Common::Rect(0, 0, width, height), transColor);
+
+ if (gfxWin)
+ gfxWin->drawPicture(s, transColor, xp, yp, s.w, s.h);
+ }
+
+ return true;
+}
+
bool GlkAPI::glk_image_get_info(uint image, uint *width, uint *height) {
if (!g_conf->_graphics)
return false;
diff --git a/engines/glk/glk_api.h b/engines/glk/glk_api.h
index 6ddeb47..a0efb30 100644
--- a/engines/glk/glk_api.h
+++ b/engines/glk/glk_api.h
@@ -198,7 +198,12 @@ public:
bool glk_image_draw(winid_t win, uint image, int val1, int val2);
bool glk_image_draw_scaled(winid_t win, uint image,
- int val1, int val2, uint width, uint height);
+ int val1, int val2, uint width, uint height);
+ bool glk_image_draw(winid_t win, const Graphics::Surface &image, uint transColor = (uint)-1,
+ int xp = 0, int yp = 0);
+ bool glk_image_draw_scaled(winid_t win, const Graphics::Surface &image, uint transColor,
+ int xp, int yp, uint width, uint height);
+
bool glk_image_get_info(uint image, uint *width, uint *height);
void glk_window_flow_break(winid_t win);
diff --git a/engines/glk/level9/level9.cpp b/engines/glk/level9/level9.cpp
index be4ac22..7fe85ee 100644
--- a/engines/glk/level9/level9.cpp
+++ b/engines/glk/level9/level9.cpp
@@ -36,7 +36,6 @@ Level9::Level9(OSystem *syst, const GlkGameDescription &gameDesc) : GlkAPI(syst,
void Level9::runGame() {
initialize();
-
_gameFile.close();
gln_main(getFilename().c_str());
@@ -44,6 +43,7 @@ void Level9::runGame() {
}
bool Level9::initialize() {
+ gln_initialize();
return gln_startup_code(0, nullptr);
}
diff --git a/engines/glk/level9/os_glk.cpp b/engines/glk/level9/os_glk.cpp
index 8c97c5c..b2ac4ac 100644
--- a/engines/glk/level9/os_glk.cpp
+++ b/engines/glk/level9/os_glk.cpp
@@ -1984,21 +1984,23 @@ break_y_max:
}
#endif
-static void gln_graphics_paint_everything(winid_t glk_window, glui32 palette[],
+static void gln_graphics_paint_everything(winid_t glk_window, Colour palette[],
gln_byte off_screen[], int x_offset, int y_offset, gln_uint16 width, gln_uint16 height) {
- gln_byte pixel; /* Reference pixel color */
- int x, y;
-
- for (y = 0; y < height; y++) {
- for (x = 0; x < width; x ++) {
- pixel = off_screen[ y * width + x ];
- g_vm->glk_window_fill_rect(glk_window,
- palette[ pixel ],
- x * GLN_GRAPHICS_PIXEL + x_offset,
- y * GLN_GRAPHICS_PIXEL + y_offset,
- GLN_GRAPHICS_PIXEL, GLN_GRAPHICS_PIXEL);
+ Graphics::PixelFormat format(4, 8, 8, 8, 8, 24, 16, 8, 0);
+ Graphics::ManagedSurface s(width, height, format);
+
+ for (int y = 0; y < height; ++y) {
+ uint32 *lineP = (uint32 *)s.getBasePtr(0, y);
+ for (int x = 0; x < width; ++x, ++lineP) {
+ byte pixel = off_screen[y * width + x];
+ assert(pixel < GLN_PALETTE_SIZE);
+ const Colour &col = palette[pixel];
+
+ *lineP = format.RGBToColor(col.red, col.green, col.blue);
}
}
+
+ g_vm->glk_image_draw(glk_window, s, (uint)-1, x_offset, y_offset);
}
/*
@@ -2260,12 +2262,8 @@ static void gln_graphics_timeout() {
total_regions += regions;
#else
- gln_graphics_paint_everything
- (gln_graphics_window,
- palette, off_screen,
- x_offset, y_offset,
- gln_graphics_width,
- gln_graphics_height);
+ gln_graphics_paint_everything(gln_graphics_window, gln_graphics_palette, off_screen,
+ x_offset, y_offset, gln_graphics_width, gln_graphics_height);
#endif
/* Stop graphics; there's no more to be done until something restarts us. */
diff --git a/engines/glk/window_graphics.cpp b/engines/glk/window_graphics.cpp
index 572d8d9..f9fd43a 100644
--- a/engines/glk/window_graphics.cpp
+++ b/engines/glk/window_graphics.cpp
@@ -96,7 +96,7 @@ void GraphicsWindow::redraw() {
}
}
-uint GraphicsWindow::drawPicture(uint image, int xpos, int ypos, int scale,
+bool GraphicsWindow::drawPicture(uint image, int xpos, int ypos, bool scale,
uint imagewidth, uint imageheight) {
Picture *pic = g_vm->_pictures->load(image);
uint hyperlink = _attr.hyper;
@@ -177,26 +177,31 @@ void GraphicsWindow::fillRect(uint color, const Rect &box) {
touch();
}
-void GraphicsWindow::drawPicture(Picture *src, int x0, int y0, int width, int height, uint linkval) {
- int dx1, dy1, x1, y1, sx0, sy0, sx1, sy1;
- int hx0, hx1, hy0, hy1;
- int w, h;
-
+void GraphicsWindow::drawPicture(Picture *src, int x0, int y0, int width, int height, uint linkval) {
if (width != src->w || height != src->h) {
src = g_vm->_pictures->scale(src, width, height);
if (!src)
return;
}
+ drawPicture(*src, src->getTransparentColor(), x0, y0, width, height, linkval);
+}
+
+void GraphicsWindow::drawPicture(const Graphics::Surface &image, uint transColor, int x0, int y0,
+ int width, int height, uint linkval) {
+ int dx1, dy1, x1, y1, sx0, sy0, sx1, sy1;
+ int hx0, hx1, hy0, hy1;
+ int w, h;
+
sx0 = 0;
sy0 = 0;
- sx1 = src->w;
- sy1 = src->h;
+ sx1 = image.w;
+ sy1 = image.h;
dx1 = _w;
dy1 = _h;
- x1 = x0 + src->w;
- y1 = y0 + src->h;
+ x1 = x0 + image.w;
+ y1 = y0 + image.h;
if (x1 <= 0 || x0 >= dx1) return;
if (y1 <= 0 || y0 >= dy1) return;
@@ -228,7 +233,7 @@ void GraphicsWindow::drawPicture(Picture *src, int x0, int y0, int width, int h
w = sx1 - sx0;
h = sy1 - sy0;
- _surface->transBlitFrom(*src, Rect(sx0, sy0, sx0 + w, sy0 + h), Point(x0, y0), src->getTransparentColor());
+ _surface->transBlitFrom(image, Rect(sx0, sy0, sx0 + w, sy0 + h), Point(x0, y0), transColor);
}
void GraphicsWindow::getSize(uint *width, uint *height) const {
diff --git a/engines/glk/window_graphics.h b/engines/glk/window_graphics.h
index a3fe6db..59b1ce4 100644
--- a/engines/glk/window_graphics.h
+++ b/engines/glk/window_graphics.h
@@ -52,8 +52,10 @@ public:
*/
virtual ~GraphicsWindow();
- uint drawPicture(uint image, int xpos, int ypos, int scale,
+ bool drawPicture(uint image, int xpos, int ypos, bool scale,
uint imagewidth, uint imageheight);
+ void drawPicture(const Graphics::Surface &image, uint transColor, int x0, int y0,
+ int width, int height, uint linkval = 0);
/**
* Rearranges the window
Commit: 4332df2bda8d8f3942d81db3051f780d2f1bf02a
https://github.com/scummvm/scummvm/commit/4332df2bda8d8f3942d81db3051f780d2f1bf02a
Author: Paul Gilbert (dreammaster at scummvm.org)
Date: 2019-10-26T11:13:29-07:00
Commit Message:
GLK: LEVEL9: Allow for future graphic scaling
Changed paths:
engines/glk/glk_api.cpp
engines/glk/level9/os_glk.cpp
diff --git a/engines/glk/glk_api.cpp b/engines/glk/glk_api.cpp
index 6d34e3a..ba05988 100644
--- a/engines/glk/glk_api.cpp
+++ b/engines/glk/glk_api.cpp
@@ -930,15 +930,19 @@ bool GlkAPI::glk_image_draw_scaled(winid_t win, const Graphics::Surface &image,
if (!win) {
warning("image_draw_scaled: invalid ref");
} else if (g_conf->_graphics) {
- GraphicsWindow *gfxWin = dynamic_cast<GraphicsWindow *>(win);
+ if (image.w == width && image.h == height) {
+ return glk_image_draw(win, image, transColor, xp, yp);
- Graphics::ManagedSurface s(width, height);
- s.clear(transColor);
- s.transBlitFrom(image, Common::Rect(0, 0, image.w, image.h),
- Common::Rect(0, 0, width, height), transColor);
+ } else {
+ GraphicsWindow *gfxWin = dynamic_cast<GraphicsWindow *>(win);
- if (gfxWin)
- gfxWin->drawPicture(s, transColor, xp, yp, s.w, s.h);
+ Graphics::ManagedSurface s(width, height, image.format);
+ s.transBlitFrom(image, Common::Rect(0, 0, image.w, image.h),
+ Common::Rect(0, 0, width, height));
+
+ if (gfxWin)
+ gfxWin->drawPicture(s, transColor, xp, yp, s.w, s.h);
+ }
}
return true;
diff --git a/engines/glk/level9/os_glk.cpp b/engines/glk/level9/os_glk.cpp
index b2ac4ac..586b808 100644
--- a/engines/glk/level9/os_glk.cpp
+++ b/engines/glk/level9/os_glk.cpp
@@ -2000,7 +2000,8 @@ static void gln_graphics_paint_everything(winid_t glk_window, Colour palette[],
}
}
- g_vm->glk_image_draw(glk_window, s, (uint)-1, x_offset, y_offset);
+ g_vm->glk_image_draw_scaled(glk_window, s, (uint)-1, x_offset, y_offset,
+ width * GLN_GRAPHICS_PIXEL, height * GLN_GRAPHICS_PIXEL);
}
/*
Commit: 0facfc2b2497cc61e86582f72dbab1dad1b1b83b
https://github.com/scummvm/scummvm/commit/0facfc2b2497cc61e86582f72dbab1dad1b1b83b
Author: Paul Gilbert (dreammaster at scummvm.org)
Date: 2019-10-26T11:13:29-07:00
Commit Message:
GLK: LEVEL9: Moved pre-existing detection code into separate class
Changed paths:
engines/glk/level9/detection.cpp
engines/glk/level9/detection.h
engines/glk/level9/detection_tables.h
engines/glk/level9/level9.cpp
engines/glk/level9/level9.h
engines/glk/level9/level9_main.cpp
engines/glk/level9/level9_main.h
engines/glk/level9/os_glk.cpp
engines/glk/level9/os_glk.h
diff --git a/engines/glk/level9/detection.cpp b/engines/glk/level9/detection.cpp
index 9904091..2648db8 100644
--- a/engines/glk/level9/detection.cpp
+++ b/engines/glk/level9/detection.cpp
@@ -22,6 +22,7 @@
#include "glk/level9/detection.h"
#include "glk/level9/detection_tables.h"
+#include "glk/level9/os_glk.h"
#include "glk/blorb.h"
#include "common/debug.h"
#include "common/file.h"
@@ -31,6 +32,176 @@
namespace Glk {
namespace Level9 {
+GameDetection::GameDetection(byte *&startData, size_t &fileSize) :
+ _startData(startData), _fileSize(fileSize), _crcInitialized(false), _gameName(nullptr) {
+ Common::fill(&_crcTable[0], &_crcTable[256], 0);
+}
+
+gln_game_tableref_t GameDetection::gln_gameid_identify_game() {
+ uint16 length, crc;
+ byte checksum;
+ int is_version2;
+ gln_game_tableref_t game;
+ gln_patch_tableref_t patch;
+
+ /* If the data file appears too short for a header, give up now. */
+ if (_fileSize < 30)
+ return nullptr;
+
+ /*
+ * Find the version of the game, and the length of game data. This logic
+ * is taken from L9cut, with calcword() replaced by simple byte comparisons.
+ * If the length exceeds the available data, fail.
+ */
+ assert(_startData);
+ is_version2 = _startData[4] == 0x20 && _startData[5] == 0x00
+ && _startData[10] == 0x00 && _startData[11] == 0x80
+ && _startData[20] == _startData[22]
+ && _startData[21] == _startData[23];
+
+ length = is_version2
+ ? _startData[28] | _startData[29] << BITS_PER_CHAR
+ : _startData[0] | _startData[1] << BITS_PER_CHAR;
+ if (length >= _fileSize)
+ return nullptr;
+
+ /* Calculate or retrieve the checksum, in a version specific way. */
+ if (is_version2) {
+ int index;
+
+ checksum = 0;
+ for (index = 0; index < length + 1; index++)
+ checksum += _startData[index];
+ }
+ else
+ checksum = _startData[length];
+
+ /*
+ * Generate a CRC for this data. When L9cut calculates a CRC, it's using a
+ * copy taken up to length + 1 and then padded with two NUL bytes, so we
+ * mimic that here.
+ */
+ crc = gln_get_buffer_crc(_startData, length + 1, 2);
+
+ /*
+ * See if this is a patched file. If it is, look up the game based on the
+ * original CRC and checksum. If not, use the current CRC and checksum.
+ */
+ patch = gln_gameid_lookup_patch(length, checksum, crc);
+ game = gln_gameid_lookup_game(length,
+ patch ? patch->orig_checksum : checksum,
+ patch ? patch->orig_crc : crc,
+ false);
+
+ /* If no game identified, retry without the CRC. This is guesswork. */
+ if (!game)
+ game = gln_gameid_lookup_game(length, checksum, crc, true);
+
+ return game;
+}
+
+// CRC table initialization polynomial
+static const uint16 GLN_CRC_POLYNOMIAL = 0xa001;
+
+uint16 GameDetection::gln_get_buffer_crc(const void *void_buffer, size_t length, size_t padding) {
+
+ const char *buffer = (const char *)void_buffer;
+ uint16 crc;
+ size_t index;
+
+ /* Build the static CRC lookup table on first call. */
+ if (!_crcInitialized) {
+ for (index = 0; index < BYTE_MAX + 1; index++) {
+ int bit;
+
+ crc = (uint16)index;
+ for (bit = 0; bit < BITS_PER_CHAR; bit++)
+ crc = crc & 1 ? GLN_CRC_POLYNOMIAL ^ (crc >> 1) : crc >> 1;
+
+ _crcTable[index] = crc;
+ }
+
+ _crcInitialized = true;
+
+ /* CRC lookup table self-test, after is_initialized set -- recursion. */
+ assert(gln_get_buffer_crc("123456789", 9, 0) == 0xbb3d);
+ }
+
+ /* Start with zero in the crc, then update using table entries. */
+ crc = 0;
+ for (index = 0; index < length; index++)
+ crc = _crcTable[(crc ^ buffer[index]) & BYTE_MAX] ^ (crc >> BITS_PER_CHAR);
+
+ /* Add in any requested NUL padding bytes. */
+ for (index = 0; index < padding; index++)
+ crc = _crcTable[crc & BYTE_MAX] ^ (crc >> BITS_PER_CHAR);
+
+ return crc;
+}
+
+gln_game_tableref_t GameDetection::gln_gameid_lookup_game(uint16 length, byte checksum, uint16 crc, int ignore_crc) const {
+ gln_game_tableref_t game;
+
+ for (game = GLN_GAME_TABLE; game->length; game++) {
+ if (game->length == length && game->checksum == checksum
+ && (ignore_crc || game->crc == crc))
+ break;
+ }
+
+ return game->length ? game : nullptr;
+}
+
+gln_patch_tableref_t GameDetection::gln_gameid_lookup_patch(uint16 length, byte checksum, uint16 crc) const {
+ gln_patch_tableref_t patch;
+
+ for (patch = GLN_PATCH_TABLE; patch->length; patch++) {
+ if (patch->length == length && patch->patch_checksum == checksum
+ && patch->patch_crc == crc)
+ break;
+ }
+
+ return patch->length ? patch : nullptr;
+}
+
+const char *GameDetection::gln_gameid_get_game_name() {
+ /*
+ * If no game name yet known, attempt to identify the game. If it can't
+ * be identified, set the cached game name to "" -- this special value
+ * indicates that the game is an unknown one, but suppresses repeated
+ * attempts to identify it on successive calls.
+ */
+ if (!_gameName) {
+ gln_game_tableref_t game;
+
+ /*
+ * If the interpreter hasn't yet loaded a game, startdata is nullptr
+ * (uninitialized, global). In this case, we return nullptr, allowing
+ * for retries until a game is loaded.
+ */
+ if (!_startData)
+ return nullptr;
+
+ game = gln_gameid_identify_game();
+ _gameName = game ? game->name : "";
+ }
+
+ /* Return the game's name, or nullptr if it was unidentifiable. */
+ assert(_gameName);
+ return strlen(_gameName) > 0 ? _gameName : nullptr;
+}
+
+/**
+ * Clear the saved game name, forcing a new lookup when next queried. This
+ * function should be called by actions that may cause the interpreter to
+ * change game file, for example os_set_filenumber().
+ */
+void GameDetection::gln_gameid_game_name_reset() {
+ _gameName = nullptr;
+}
+
+
+/*----------------------------------------------------------------------*/
+
void Level9MetaEngine::getSupportedGames(PlainGameList &games) {
for (const PlainGameDescriptor *pd = LEVEL9_GAME_LIST; pd->gameId; ++pd) {
games.push_back(*pd);
@@ -62,7 +233,7 @@ bool Level9MetaEngine::detectGames(const Common::FSList &fslist, DetectedGames &
continue;
Common::String md5 = Common::computeStreamMD5AsString(gameFile, 5000);
- size_t filesize = gameFile.size();
+ size_t _fileSize = gameFile.size();
gameFile.seek(0);
bool isBlorb = Blorb::isBlorb(gameFile, ID_ADRI);
gameFile.close();
@@ -72,12 +243,12 @@ bool Level9MetaEngine::detectGames(const Common::FSList &fslist, DetectedGames &
// Check for known games
const GlkDetectionEntry *p = LEVEL9_GAMES;
- while (p->_gameId && (md5 != p->_md5 || filesize != p->_filesize))
+ while (p->_gameId && (md5 != p->_md5 || _fileSize != p->_filesize))
++p;
if (!p->_gameId) {
const PlainGameDescriptor &desc = LEVEL9_GAME_LIST[0];
- gameList.push_back(GlkDetectedGame(desc.gameId, desc.description, filename, md5, filesize));
+ gameList.push_back(GlkDetectedGame(desc.gameId, desc.description, filename, md5, _fileSize));
} else {
PlainGameDescriptor gameDesc = findGame(p->_gameId);
gameList.push_back(GlkDetectedGame(p->_gameId, gameDesc.description, p->_extra, filename, p->_language));
@@ -88,11 +259,7 @@ bool Level9MetaEngine::detectGames(const Common::FSList &fslist, DetectedGames &
}
void Level9MetaEngine::detectClashes(Common::StringMap &map) {
- for (const PlainGameDescriptor *pd = LEVEL9_GAME_LIST; pd->gameId; ++pd) {
- if (map.contains(pd->gameId))
- error("Duplicate game Id found - %s", pd->gameId);
- map[pd->gameId] = "";
- }
+ // No implementation
}
} // End of namespace Level9
diff --git a/engines/glk/level9/detection.h b/engines/glk/level9/detection.h
index 386e475..9e8401b 100644
--- a/engines/glk/level9/detection.h
+++ b/engines/glk/level9/detection.h
@@ -31,6 +31,86 @@
namespace Glk {
namespace Level9 {
+struct gln_game_table_t {
+ const size_t length; ///< Datafile length in bytes
+ const byte checksum; ///< 8-bit checksum, last datafile byte
+ const uint16 crc; ///< 16-bit CRC, L9cut-internal
+ const char *const name; ///< Game title and platform
+};
+typedef const gln_game_table_t *gln_game_tableref_t;
+
+struct gln_patch_table_t {
+ const size_t length; ///< Datafile length in bytes
+ const byte orig_checksum; ///< 8-bit checksum, last datafile byte
+ const uint16 orig_crc; ///< 16-bit CRC, L9cut-internal
+ const byte patch_checksum; ///< 8-bit checksum, last datafile byte
+ const uint16 patch_crc; ///< 16-bit CRC, L9cut-internal
+};
+typedef const gln_patch_table_t *gln_patch_tableref_t;
+
+/**
+ * Detection manager for specific games
+ */
+class GameDetection {
+private:
+ byte *&_startData;
+ size_t &_fileSize;
+ bool _crcInitialized;
+ uint16 _crcTable[256];
+public:
+ const char *_gameName;
+public:
+ /**
+ * Constructor
+ */
+ GameDetection(byte *&startData, size_t &fileSize);
+
+ /**
+ * Identify a game from its data length, checksum, and CRC. Returns the
+ * entry of the game in the game table, or nullptr if not found.
+ *
+ * This function uses startdata and FileSize from the core interpreter.
+ * These aren't advertised symbols, so be warned.
+ */
+ gln_game_tableref_t gln_gameid_identify_game();
+
+ /**
+ * Return the CRC of the bytes buffer[0..length-1].
+ *
+ * This algorithm is selected to match the CRCs used in L9cut. Because of
+ * the odd way CRCs are padded when L9cut calculates the CRC, this function
+ * allows a count of NUL padding bytes to be included within the return CRC.
+ */
+ uint16 gln_get_buffer_crc(const void *void_buffer, size_t length, size_t padding = 0);
+
+ /*
+ * Look up and return game table and patch table entries given a game's
+ * length, checksum, and CRC. Returns the entry, or nullptr if not found.
+ */
+ gln_game_tableref_t gln_gameid_lookup_game(uint16 length, byte checksum, uint16 crc, int ignore_crc) const;
+
+ /**
+ * Look up and return patch table entries given a game's length, checksum, and CRC.
+ * Returns the entry, or nullptr if not found
+ */
+ gln_patch_tableref_t gln_gameid_lookup_patch(uint16 length, byte checksum, uint16 crc) const;
+
+ /**
+ * Return the name of the game, or nullptr if not identifiable.
+ *
+ * This function uses startdata from the core interpreter. This isn't an
+ * advertised symbol, so be warned.
+ */
+ const char *gln_gameid_get_game_name();
+
+ /**
+ * Clear the saved game name, forcing a new lookup when next queried. This
+ * function should be called by actions that may cause the interpreter to
+ * change game file, for example os_set_filenumber().
+ */
+ void gln_gameid_game_name_reset();
+};
+
/**
* Meta engine for Level 9 interpreter
*/
diff --git a/engines/glk/level9/detection_tables.h b/engines/glk/level9/detection_tables.h
index 12b3b0e..dda4ce4 100644
--- a/engines/glk/level9/detection_tables.h
+++ b/engines/glk/level9/detection_tables.h
@@ -21,12 +21,781 @@
*/
#include "engines/game.h"
-#include "common/gui_options.h"
-#include "common/language.h"
+
+/**
+ * Unlike other ScummVM GLK subengines, Level9 has a detection table that was already
+ * included in the Level9 interpreter. So it's used instead of a standard MD5-based list
+ */
namespace Glk {
namespace Level9 {
+/**
+ * The following game database is obtained from L9cut's l9data_d.h, and
+ * lets us find a game's name from its data CRC. Entries marked "WANTED" in
+ * l9data_d.h, and file commentary, have been removed for brevity, and the
+ * file has been reformatted (patchlevel data removed).
+ *
+ * The version of l9data_d.h used is 050 (22 Oct 2002).
+ */
+static const gln_game_table_t GLN_GAME_TABLE[] = {
+ {0x5323, 0xb7, 0x8af7, "Adventure Quest (Amstrad CPC/Spectrum)"},
+
+ {0x630e, 0x8d, 0x7d7d, "Dungeon Adventure (Amstrad CPC)"},
+ {0x630e, 0xbe, 0x3374, "Dungeon Adventure (MSX)"},
+
+ {0x5eb9, 0x30, 0xe99a, "Lords of Time (Amstrad CPC)"},
+ {0x5eb9, 0x5d, 0xc098, "Lords of Time (MSX)"},
+ {0x5eb9, 0x6e, 0xc689, "Lords of Time (Spectrum)"},
+
+ {0x5fab, 0x5c, 0xa309, "Snowball (Amstrad CPC)"},
+ {0x5fab, 0x2f, 0x8aa2, "Snowball (MSX)"},
+
+ {0x60c4, 0x28, 0x0154, "Return to Eden (Amstrad CPC/Commodore 64[v1])"},
+ {0x6064, 0x01, 0x5b3c, "Return to Eden (BBC[v1])"},
+ {0x6064, 0x95, 0x510c, "Return to Eden (Commodore 64[v2])"},
+ {0x6064, 0xda, 0xe610, "Return to Eden (Commodore 64[v2] *corrupt*)"},
+ {0x6064, 0xbd, 0x73ec, "Return to Eden (Atari *corrupt*)"},
+ {0x6047, 0x6c, 0x17ab, "Return to Eden (BBC[v2])"},
+ {0x5ca1, 0x33, 0x1c43, "Return to Eden (Spectrum[v1])"},
+ {0x5cb7, 0x64, 0x0790, "Return to Eden (Spectrum[v2])"},
+ {0x5cb7, 0xfe, 0x3533, "Return to Eden (MSX)"},
+
+ {0x34b3, 0x20, 0xccda, "Erik the Viking (BBC/Commodore 64)"},
+ {0x34b3, 0x53, 0x8f00, "Erik the Viking (Spectrum)"},
+ {0x34b3, 0xc7, 0x9058, "Erik the Viking (Amstrad CPC)"},
+
+ {0x63be, 0xd6, 0xcf5d, "Emerald Isle (Atari/Commodore 64/Amstrad CPC/Spectrum)"},
+ {0x63be, 0x0a, 0x21ed, "Emerald Isle (MSX *corrupt*)"},
+ {0x378c, 0x8d, 0x3a21, "Emerald Isle (BBC)"},
+
+ {0x506c, 0xf0, 0xba72, "Red Moon (BBC/Commodore 64/Amstrad CPC/MSX)"},
+ {0x505d, 0x32, 0x2dcf, "Red Moon (Spectrum)"},
+
+ {0x772b, 0xcd, 0xa503, "Worm in Paradise (Spectrum 128)"},
+ {0x546c, 0xb7, 0x9420, "Worm in Paradise (Spectrum 48)"},
+ {0x6d84, 0xf9, 0x49ae, "Worm in Paradise (Commodore 64 *corrupt*)"},
+ {0x6d84, 0xc8, 0x943f, "Worm in Paradise (Commodore 64 *fixed*)"},
+ {0x6030, 0x47, 0x46ad, "Worm in Paradise (Amstrad CPC)"},
+ {0x5828, 0xbd, 0xe7cb, "Worm in Paradise (BBC)"},
+
+ {0x7410, 0x5e, 0x60be, "Price of Magik (Spectrum 128)"},
+ {0x5aa4, 0xc1, 0x10a0, "Price of Magik (Spectrum 48[v1])"},
+ {0x5aa4, 0xc1, 0xeda4, "Price of Magik (Spectrum 48[v2])"},
+ {0x6fc6, 0x14, 0xf9b6, "Price of Magik (Commodore 64)"},
+ {0x5aa4, 0xc1, 0xbbf4, "Price of Magik (Amstrad CPC)"},
+ {0x5671, 0xbc, 0xff35, "Price of Magik (BBC)"},
+
+ {0x76f4, 0x5e, 0x1fe5, "Colossal Adventure /JoD (Amiga/PC)"},
+ {0x76f4, 0x5a, 0xcf4b, "Colossal Adventure /JoD (ST)"},
+ {0x6e60, 0x83, 0x18e0, "Adventure Quest /JoD (Amiga/PC)"},
+ {0x6e5c, 0xf6, 0xd356, "Adventure Quest /JoD (ST)"},
+ {0x6f0c, 0x95, 0x1f64, "Dungeon Adventure /JoD (Amiga/PC/ST)"},
+
+ {0x6f70, 0x40, 0xbd91, "Colossal Adventure /JoD (MSX)"},
+
+ {0x6f6e, 0x78, 0x28cd, "Colossal Adventure /JoD (Spectrum 128)"},
+ {0x6970, 0xd6, 0xa820, "Adventure Quest /JoD (Spectrum 128)"},
+ {0x6de8, 0x4c, 0xd795, "Dungeon Adventure /JoD (Spectrum 128)"},
+
+ {0x6f4d, 0xcb, 0xe8f2, "Colossal Adventure /JoD (Amstrad CPC128[v1]/Spectrum +3)"},
+ {0x6f6a, 0xa5, 0x8dd2, "Colossal Adventure /JoD (Amstrad CPC128[v2])"},
+ {0x6968, 0x32, 0x0c01, "Adventure Quest /JoD (Amstrad CPC128/Spectrum +3)"},
+ {0x6dc0, 0x63, 0x5d95, "Dungeon Adventure /JoD (Amstrad CPC128/Spectrum +3)"},
+
+ {0x5e31, 0x7c, 0xaa54, "Colossal Adventure /JoD (Amstrad CPC64)"},
+ {0x5b50, 0x66, 0x1800, "Adventure Quest /JoD (Amstrad CPC64)"},
+ {0x58a6, 0x24, 0xb50f, "Dungeon Adventure /JoD (Amstrad CPC64)"},
+
+ {0x6c8e, 0xb6, 0x9be3, "Colossal Adventure /JoD (Commodore 64)"},
+ {0x63b6, 0x2e, 0xef38, "Adventure Quest /JoD (Commodore 64)"},
+ {0x6bd2, 0x65, 0xa41f, "Dungeon Adventure /JoD (Commodore 64)"},
+
+ {0x5b16, 0x3b, 0xe2aa, "Colossal Adventure /JoD (Atari)"},
+ {0x5b58, 0x50, 0x332e, "Adventure Quest /JoD (Atari)"},
+ {0x593a, 0x80, 0x7a34, "Dungeon Adventure /JoD (Atari)"},
+
+ {0x5a8e, 0xf2, 0x7cca, "Colossal Adventure /JoD (Spectrum 48)"},
+ {0x5ace, 0x11, 0xdc12, "Adventure Quest /JoD (Spectrum 48)"},
+ {0x58a3, 0x38, 0x8ce4, "Dungeon Adventure /JoD (Spectrum 48)"},
+
+ {0x7b31, 0x6e, 0x2e2b, "Snowball /SD (Amiga/ST)"},
+ {0x7d16, 0xe6, 0x5438, "Return to Eden /SD (Amiga/ST)"},
+ {0x7cd9, 0x0c, 0x4df1, "Worm in Paradise /SD (Amiga/ST)"},
+
+ {0x7b2f, 0x70, 0x6955, "Snowball /SD (Mac/PC/Spectrum 128)"},
+ {0x7b2f, 0x70, 0x6f6c, "Snowball /SD (Amstrad CPC/Spectrum +3)"},
+ {0x7d14, 0xe8, 0xfbab, "Return to Eden /SD (PC)"},
+ {0x7cff, 0xf8, 0x6044, "Return to Eden /SD (Amstrad CPC/Spectrum +3)"},
+ {0x7cf8, 0x24, 0x9c1c, "Return to Eden /SD (Mac)"},
+ {0x7c55, 0x18, 0xdaee, "Return to Eden /SD (Spectrum 128)"},
+ {0x7cd7, 0x0e, 0x4feb, "Worm in Paradise /SD (Amstrad CPC/Mac/PC/Spectrum 128/Spectrum +3)"},
+
+ {0x7363, 0x65, 0xa0ab, "Snowball /SD (Commodore 64)"},
+ {0x772f, 0xca, 0x8602, "Return to Eden /SD (Commodore 64)"},
+ {0x788d, 0x72, 0x888a, "Worm in Paradise /SD (Commodore 64)"},
+
+ {0x6bf8, 0x3f, 0xc9f7, "Snowball /SD (Atari)"},
+ {0x60f7, 0x68, 0xc2bc, "Return to Eden /SD (Atari)"},
+ {0x6161, 0xf3, 0xe6d7, "Worm in Paradise /SD (Atari)"},
+
+ {0x67a3, 0x9d, 0x1d05, "Snowball /SD (Apple ][)"},
+ {0x639c, 0x8b, 0x06e2, "Return to Eden /SD (Apple ][)"},
+ {0x60dd, 0xf2, 0x5bb8, "Worm in Paradise /SD (Apple ][)"},
+
+ {0x6541, 0x02, 0x2e6c, "Snowball /SD (Spectrum 48)"},
+ {0x5f43, 0xca, 0x828c, "Return to Eden /SD (Spectrum 48)"},
+ {0x5ebb, 0xf1, 0x4dec, "Worm in Paradise /SD (Spectrum 48)"},
+
+ {0x8333, 0xb7, 0xe2ac, "Adrian Mole I, pt. 1 (Commodore 64)"},
+ {0x844d, 0x50, 0x5353, "Adrian Mole I, pt. 2 (Commodore 64)"},
+ {0x8251, 0x5f, 0x862a, "Adrian Mole I, pt. 3 (Commodore 64)"},
+ {0x7a78, 0x5e, 0x6ea3, "Adrian Mole I, pt. 4 (Commodore 64)"},
+
+ {0x7c6f, 0x0f, 0xba24, "Adrian Mole I, pt. 1 (Amstrad CPC)"},
+
+ {0x72fa, 0x8b, 0x6f12, "Adrian Mole I, pt. 1 (Spectrum)"},
+ {0x738e, 0x5b, 0x7e3d, "Adrian Mole I, pt. 2 (Spectrum)"},
+ {0x7375, 0xe5, 0x3f3e, "Adrian Mole I, pt. 3 (Spectrum)"},
+ {0x78d5, 0xe3, 0xcd7d, "Adrian Mole I, pt. 4 (Spectrum)"},
+
+ {0x3a31, 0xe5, 0x0bdb, "Adrian Mole I, pt. 1 (BBC)"},
+ {0x37f1, 0x77, 0xd231, "Adrian Mole I, pt. 2 (BBC)"},
+ {0x3900, 0x1c, 0x5d9a, "Adrian Mole I, pt. 3 (BBC)"},
+ {0x3910, 0xac, 0x07f9, "Adrian Mole I, pt. 4 (BBC)"},
+ {0x3ad6, 0xa7, 0x95d2, "Adrian Mole I, pt. 5 (BBC)"},
+ {0x38a5, 0x0f, 0xdefc, "Adrian Mole I, pt. 6 (BBC)"},
+ {0x361e, 0x7e, 0xfd9f, "Adrian Mole I, pt. 7 (BBC)"},
+ {0x3934, 0x75, 0xe141, "Adrian Mole I, pt. 8 (BBC)"},
+ {0x3511, 0xcc, 0xd829, "Adrian Mole I, pt. 9 (BBC)"},
+ {0x38dd, 0x31, 0x2534, "Adrian Mole I, pt. 10 (BBC)"},
+ {0x39c0, 0x44, 0x89df, "Adrian Mole I, pt. 11 (BBC)"},
+ {0x3a12, 0x8f, 0xc2bd, "Adrian Mole I, pt. 12 (BBC)"},
+
+ {0x7931, 0xb9, 0xc51b, "Adrian Mole II, pt. 1 (Commodore 64/Amstrad CPC)"},
+ {0x7cdf, 0xa5, 0x43e3, "Adrian Mole II, pt. 2 (Commodore 64/Amstrad CPC)"},
+ {0x7a0c, 0x97, 0x4bea, "Adrian Mole II, pt. 3 (Commodore 64/Amstrad CPC)"},
+ {0x7883, 0xe2, 0xee0e, "Adrian Mole II, pt. 4 (Commodore 64/Amstrad CPC)"},
+
+ {0x6841, 0x4a, 0x94e7, "Adrian Mole II, pt. 1 (Spectrum)"},
+ {0x6bc0, 0x62, 0xab3d, "Adrian Mole II, pt. 2 (Spectrum)"},
+ {0x692c, 0x21, 0x2015, "Adrian Mole II, pt. 3 (Spectrum)"},
+ {0x670a, 0x94, 0xa2a6, "Adrian Mole II, pt. 4 (Spectrum)"},
+
+ {0x593a, 0xaf, 0x30e9, "Adrian Mole II, pt. 1 (BBC)"},
+ {0x57e6, 0x8a, 0xc41a, "Adrian Mole II, pt. 2 (BBC)"},
+ {0x5819, 0xcd, 0x1ba0, "Adrian Mole II, pt. 3 (BBC)"},
+ {0x579b, 0xad, 0xa723, "Adrian Mole II, pt. 4 (BBC)"},
+
+ {0x765d, 0xcd, 0xfc02, "The Archers, pt. 1 (Commodore 64)"},
+ {0x6e58, 0x07, 0xbffc, "The Archers, pt. 2 (Commodore 64)"},
+ {0x7e98, 0x6a, 0x95e5, "The Archers, pt. 3 (Commodore 64)"},
+ {0x81e2, 0xd5, 0xb278, "The Archers, pt. 4 (Commodore 64)"},
+
+ {0x6ce5, 0x58, 0x46de, "The Archers, pt. 1 (Spectrum)"},
+ {0x68da, 0xc1, 0x3b8e, "The Archers, pt. 2 (Spectrum)"},
+ {0x6c67, 0x9a, 0x9a6a, "The Archers, pt. 3 (Spectrum)"},
+ {0x6d91, 0xb9, 0x12a7, "The Archers, pt. 4 (Spectrum)"},
+
+ {0x5834, 0x42, 0xcc9d, "The Archers, pt. 1 (BBC)"},
+ {0x56dd, 0x51, 0xe582, "The Archers, pt. 2 (BBC)"},
+ {0x5801, 0x53, 0xf2ef, "The Archers, pt. 3 (BBC)"},
+ {0x54a4, 0x01, 0xc0ab, "The Archers, pt. 4 (BBC)"},
+
+ {0x579e, 0x97, 0x9faa, "Lords of Time /T&M GD (BBC)"},
+ {0x5500, 0x50, 0xca75, "Red Moon /T&M GD (BBC)"},
+ {0x579a, 0x2a, 0x9373, "Price of Magik /T&M GD (BBC)"},
+
+ {0x4fd2, 0x9d, 0x799a, "Lancelot, pt. 1 GD (BBC)"},
+ {0x4dac, 0xa8, 0x86ed, "Lancelot, pt. 2 GD (BBC)"},
+ {0x4f96, 0x22, 0x30f8, "Lancelot, pt. 3 GD (BBC)"},
+
+ {0x55ce, 0xa1, 0xba12, "Scapeghost, pt. 1 GD (BBC)"},
+ {0x54a6, 0xa9, 0xc9f3, "Scapeghost, pt. 2 GD (BBC)"},
+ {0x51bc, 0xe3, 0x89c3, "Scapeghost, pt. 3 GD (BBC)"},
+
+ {0x46ec, 0x64, 0x2300, "Knight Orc, pt. 1 GD (Amstrad CPC/Spectrum +3)"},
+ {0x6140, 0x18, 0x4f66, "Knight Orc, pt. 2 GD (Amstrad CPC/Spectrum +3)"},
+ {0x640e, 0xc1, 0xfc69, "Knight Orc, pt. 3 GD (Amstrad CPC/Spectrum +3)"},
+
+ {0x5ff0, 0xf8, 0x3a13, "Gnome Ranger, pt. 1 GD (Amstrad CPC/Spectrum +3)"},
+ {0x6024, 0x01, 0xaaa9, "Gnome Ranger, pt. 2 GD (Amstrad CPC/Spectrum +3)"},
+ {0x6036, 0x3d, 0x6c6c, "Gnome Ranger, pt. 3 GD (Amstrad CPC/Spectrum +3)"},
+
+ {0x69fe, 0x56, 0xecfb, "Lords of Time /T&M GD (Amstrad CPC/Spectrum +3)"},
+ {0x6888, 0x8d, 0x7f6a, "Red Moon /T&M GD (Amstrad CPC/Spectrum +3)"},
+ {0x5a50, 0xa9, 0xa5fa, "Price of Magik /T&M GD (Amstrad CPC/Spectrum +3)"},
+
+ {0x5c7a, 0x44, 0x460e, "Lancelot, pt. 1 GD (Amstrad CPC/Spectrum +3)"},
+ {0x53a2, 0x1e, 0x2fae, "Lancelot, pt. 2 GD (Amstrad CPC/Spectrum +3)"},
+ {0x5914, 0x22, 0x4a31, "Lancelot, pt. 3 GD (Amstrad CPC/Spectrum +3)"},
+
+ {0x5a38, 0xf7, 0x876e, "Ingrid's Back, pt. 1 GD (Amstrad CPC/Spectrum +3)"},
+ {0x531a, 0xed, 0xcf3f, "Ingrid's Back, pt. 2 GD (Amstrad CPC/Spectrum +3)"},
+ {0x57e4, 0x19, 0xb354, "Ingrid's Back, pt. 3 GD (Amstrad CPC/Spectrum +3)"},
+
+ {0x5cbc, 0xa5, 0x0dbe, "Scapeghost, pt. 1 GD (Amstrad CPC/Spectrum +3)"},
+ {0x5932, 0x4e, 0xb2f5, "Scapeghost, pt. 2 GD (Amstrad CPC/Spectrum +3)"},
+ {0x5860, 0x95, 0x3227, "Scapeghost, pt. 3 GD (Amstrad CPC/Spectrum +3)"},
+
+ {0x74e0, 0x92, 0x885e, "Knight Orc, pt. 1 GD (Spectrum 128)"},
+ {0x6dbc, 0x97, 0x6f55, "Knight Orc, pt. 2 GD (Spectrum 128)"},
+ {0x7402, 0x07, 0x385f, "Knight Orc, pt. 3 GD (Spectrum 128)"},
+
+ {0x52aa, 0xdf, 0x7b5b, "Gnome Ranger, pt. 1 GD (Spectrum 128)"},
+ {0x6ffa, 0xdb, 0xdde2, "Gnome Ranger, pt. 2 GD (Spectrum 128)"},
+ {0x723a, 0x69, 0x039b, "Gnome Ranger, pt. 3 GD (Spectrum 128)"},
+
+ {0x6f1e, 0xda, 0x2ce0, "Lords of Time /T&M GD (Spectrum 128)"},
+ {0x6da0, 0xb8, 0x3802, "Red Moon /T&M GD (Spectrum 128)"},
+ {0x6108, 0xdd, 0xefe7, "Price of Magik /T&M GD (Spectrum 128)"},
+
+ {0x768c, 0xe8, 0x8fc6, "Lancelot, pt. 1 GD (Spectrum 128)"},
+ {0x76b0, 0x1d, 0x0fcd, "Lancelot, pt. 2 GD (Spectrum 128)"},
+ {0x765e, 0x4f, 0x3b73, "Lancelot, pt. 3 GD (Spectrum 128)"},
+
+ {0x76a0, 0x3a, 0xb803, "Ingrid's Back, pt. 1 GD (Spectrum 128)"},
+ {0x7674, 0x0b, 0xe92f, "Ingrid's Back, pt. 2 GD (Spectrum 128)"},
+ {0x765e, 0xba, 0x086d, "Ingrid's Back, pt. 3 GD (Spectrum 128)"},
+
+ {0x762e, 0x82, 0x8848, "Scapeghost, pt. 1 GD (Spectrum 128)"},
+ {0x5bd6, 0x35, 0x79ef, "Scapeghost, pt. 2 GD (Spectrum 128)"},
+ {0x6fa8, 0xa4, 0x62c2, "Scapeghost, pt. 3 GD (Spectrum 128)"},
+
+ {0xbb93, 0x36, 0x6a05, "Knight Orc, pt. 1 (Amiga)"},
+ {0xbb6e, 0xad, 0x4d40, "Knight Orc, pt. 1 (ST)"},
+ {0xc58e, 0x4a, 0x4e9d, "Knight Orc, pt. 2 (Amiga/ST)"},
+ {0xcb9a, 0x0f, 0x0804, "Knight Orc, pt. 3 (Amiga/ST)"},
+
+ {0xbb6e, 0xa6, 0x9753, "Knight Orc, pt. 1 (PC)"},
+ {0xc58e, 0x43, 0xe9ce, "Knight Orc, pt. 2 (PC)"},
+ {0xcb9a, 0x08, 0x6c36, "Knight Orc, pt. 3 (PC)"},
+
+ {0x898a, 0x43, 0xfc8b, "Knight Orc, pt. 1 (Apple ][)"},
+ {0x8b9f, 0x61, 0x7288, "Knight Orc, pt. 2 (Apple ][)"},
+ {0x8af9, 0x61, 0x7542, "Knight Orc, pt. 3 (Apple ][)"},
+
+ {0x8970, 0x6b, 0x3c7b, "Knight Orc, pt. 1 (Commodore 64 Gfx)"},
+ {0x8b90, 0x4e, 0x098c, "Knight Orc, pt. 2 (Commodore 64 Gfx)"},
+ {0x8aea, 0x4e, 0xca54, "Knight Orc, pt. 3 (Commodore 64 Gfx)"},
+
+ {0x86d0, 0xb7, 0xadbd, "Knight Orc, pt. 1 (Spectrum 48)"},
+ {0x8885, 0x22, 0xe293, "Knight Orc, pt. 2 (Spectrum 48)"},
+ {0x87e5, 0x0e, 0xdc33, "Knight Orc, pt. 3 (Spectrum 48)"},
+
+ {0xb1a9, 0x80, 0x5fb7, "Gnome Ranger, pt. 1 (Amiga/ST)"},
+ {0xab9d, 0x31, 0xbe6d, "Gnome Ranger, pt. 2 (Amiga/ST)"},
+ {0xae28, 0x87, 0xb6b6, "Gnome Ranger, pt. 3 (Amiga/ST)"},
+
+ {0xb0ec, 0xc2, 0x0053, "Gnome Ranger, pt. 1 (ST[v1])"},
+ {0xaf82, 0x83, 0x19f7, "Gnome Ranger, pt. 2 (ST[v1])"},
+
+ {0xb1aa, 0xad, 0xaf47, "Gnome Ranger, pt. 1 (PC)"},
+ {0xb19e, 0x92, 0x8f96, "Gnome Ranger, pt. 1 (ST[v2])"},
+ {0xab8b, 0xbf, 0x31e6, "Gnome Ranger, pt. 2 (PC/ST[v2])"},
+ {0xae16, 0x81, 0x8741, "Gnome Ranger, pt. 3 (PC/ST[v2])"},
+
+ {0xad41, 0xa8, 0x42c5, "Gnome Ranger, pt. 1 (Commodore 64 TO)"},
+ {0xa735, 0xf7, 0x2e08, "Gnome Ranger, pt. 2 (Commodore 64 TO)"},
+ {0xa9c0, 0x9e, 0x0d70, "Gnome Ranger, pt. 3 (Commodore 64 TO)"},
+
+ {0x908e, 0x0d, 0x58a7, "Gnome Ranger, pt. 1 (Commodore 64 Gfx)"},
+ {0x8f6f, 0x0a, 0x411a, "Gnome Ranger, pt. 2 (Commodore 64 Gfx)"},
+ {0x9060, 0xbb, 0xe75d, "Gnome Ranger, pt. 3 (Commodore 64 Gfx)"},
+
+ {0x8aab, 0xc0, 0xde5f, "Gnome Ranger, pt. 1 (Spectrum 48)"},
+ {0x8ac8, 0x9a, 0xc89b, "Gnome Ranger, pt. 2 (Spectrum 48)"},
+ {0x8a93, 0x4f, 0x10cc, "Gnome Ranger, pt. 3 (Spectrum 48)"},
+
+ {0xb57c, 0x44, 0x7779, "Lords of Time /T&M (PC)"},
+ {0xa69e, 0x6c, 0xb268, "Red Moon /T&M (PC)"},
+ {0xbac7, 0x7f, 0xddb2, "Price of Magik /T&M (PC)"},
+
+ {0xb579, 0x89, 0x3e89, "Lords of Time /T&M (ST)"},
+ {0xa698, 0x41, 0xcaca, "Red Moon /T&M (ST)"},
+ {0xbac4, 0x80, 0xa750, "Price of Magik /T&M (ST)"},
+
+ {0xb576, 0x2a, 0x7239, "Lords of Time /T&M (Amiga)"},
+ {0xa692, 0xd1, 0x6a99, "Red Moon /T&M (Amiga)"},
+ {0xbaca, 0x3a, 0x221b, "Price of Magik /T&M (Amiga)"},
+
+ {0xb563, 0x6a, 0x0c5c, "Lords of Time /T&M (Mac)"},
+ {0xa67c, 0xb8, 0xff41, "Red Moon /T&M (Mac)"},
+ {0xbab2, 0x87, 0x09f5, "Price of Magik /T&M (Mac)"},
+
+ {0xb38c, 0x37, 0x9f8e, "Lords of Time /T&M (Commodore 64 TO)"},
+ {0xa4e2, 0xa6, 0x016d, "Red Moon /T&M (Commodore 64 TO)"},
+ {0xb451, 0xa8, 0x2682, "Price of Magik /T&M (Commodore 64 TO)"},
+
+ {0x9070, 0x43, 0x45d4, "Lords of Time /T&M (Commodore 64 Gfx)"},
+ {0x903f, 0x6b, 0x603e, "Red Moon /T&M (Commodore 64 Gfx)"},
+ {0x8f51, 0xb2, 0x6c9a, "Price of Magik /T&M (Commodore 64 Gfx)"},
+
+ {0x8950, 0xa1, 0xbb16, "Lords of Time /T&M (Spectrum 48)"},
+ {0x8813, 0x11, 0x22de, "Red Moon /T&M (Spectrum 48)"},
+ {0x8a60, 0x2a, 0x29ed, "Price of Magik /T&M (Spectrum 48)"},
+
+ {0xb260, 0xe5, 0xc5b2, "Lords of Time /T&M (PC/ST *USA*)"},
+ {0xa3a4, 0xdf, 0x6732, "Red Moon /T&M (PC/ST *USA*)"},
+ {0xb7a0, 0x7e, 0x2226, "Price of Magik /T&M (PC/ST *USA*)"},
+
+ {0xb257, 0xf8, 0xfbd5, "Lords of Time /T&M (Amiga *USA*)"},
+ {0xa398, 0x82, 0xd031, "Red Moon /T&M (Amiga *USA*)"},
+ {0xb797, 0x1f, 0x84a9, "Price of Magik /T&M (Amiga *USA*)"},
+
+ {0x8d78, 0x3a, 0xba6e, "Lords of Time /T&M (Commodore 64 Gfx *USA*)"},
+ {0x8d56, 0xd3, 0x146a, "Red Moon /T&M (Commodore 64 Gfx *USA*)"},
+ {0x8c46, 0xf0, 0xcaf6, "Price of Magik /T&M (Commodore 64 Gfx *USA*)"},
+
+ {0xc0cf, 0x4e, 0xb7fa, "Lancelot, pt. 1 (Amiga/PC/ST)"},
+ {0xd5e9, 0x6a, 0x4192, "Lancelot, pt. 2 (Amiga/PC/ST)"},
+ {0xbb8f, 0x1a, 0x7487, "Lancelot, pt. 3 (Amiga/PC/ST)"},
+
+ {0xc0bd, 0x57, 0x6ef1, "Lancelot, pt. 1 (Mac)"},
+ {0xd5d7, 0x99, 0x770b, "Lancelot, pt. 2 (Mac)"},
+ {0xbb7d, 0x17, 0xbc42, "Lancelot, pt. 3 (Mac)"},
+
+ {0xb4c9, 0x94, 0xd784, "Lancelot, pt. 1 (Commodore 64 TO)"},
+ {0xb729, 0x51, 0x8ee5, "Lancelot, pt. 2 (Commodore 64 TO)"},
+ {0xb702, 0xe4, 0x1809, "Lancelot, pt. 3 (Commodore 64 TO)"},
+
+ {0x8feb, 0xba, 0xa800, "Lancelot, pt. 1 (Commodore 64 Gfx)"},
+ {0x8f6b, 0xfa, 0x0f7e, "Lancelot, pt. 2 (Commodore 64 Gfx)"},
+ {0x8f71, 0x2f, 0x0ddc, "Lancelot, pt. 3 (Commodore 64 Gfx)"},
+
+ {0x8ade, 0xf2, 0xfffb, "Lancelot, pt. 1 (Spectrum 48)"},
+ {0x8b0e, 0xfb, 0x0bab, "Lancelot, pt. 2 (Spectrum 48)"},
+ {0x8ab3, 0xc1, 0xcb62, "Lancelot, pt. 3 (Spectrum 48)"},
+
+ {0xbba4, 0x94, 0x0871, "Lancelot, pt. 1 (Amiga/PC *USA*)"},
+ {0xd0c0, 0x56, 0x8c48, "Lancelot, pt. 2 (Amiga/PC *USA*)"},
+ {0xb6ac, 0xc6, 0xaea0, "Lancelot, pt. 3 (Amiga/PC *USA*)"},
+
+ {0x8afc, 0x07, 0x8321, "Lancelot, pt. 1 (Commodore 64 Gfx *USA*)"},
+ {0x8aec, 0x13, 0x6791, "Lancelot, pt. 2 (Commodore 64 Gfx *USA*)"},
+ {0x8aba, 0x0d, 0x5602, "Lancelot, pt. 3 (Commodore 64 Gfx *USA*)"},
+
+ {0xd19b, 0xad, 0x306d, "Ingrid's Back, pt. 1 (PC)"},
+ {0xc5a5, 0xfe, 0x3c98, "Ingrid's Back, pt. 2 (PC)"},
+ {0xd7ae, 0x9e, 0x1878, "Ingrid's Back, pt. 3 (PC)"},
+
+ {0xd188, 0x13, 0xdc60, "Ingrid's Back, pt. 1 (Amiga)"},
+ {0xc594, 0x03, 0xea95, "Ingrid's Back, pt. 2 (Amiga)"},
+ {0xd79f, 0xb5, 0x1661, "Ingrid's Back, pt. 3 (Amiga)"},
+
+ {0xd183, 0x83, 0xef72, "Ingrid's Back, pt. 1 (ST)"},
+ {0xc58f, 0x65, 0xf337, "Ingrid's Back, pt. 2 (ST)"},
+ {0xd79a, 0x57, 0x49c5, "Ingrid's Back, pt. 3 (ST)"},
+
+ {0xb770, 0x03, 0x9a03, "Ingrid's Back, pt. 1 (Commodore 64 TO)"},
+ {0xb741, 0xb6, 0x2aa5, "Ingrid's Back, pt. 2 (Commodore 64 TO)"},
+ {0xb791, 0xa1, 0xd065, "Ingrid's Back, pt. 3 (Commodore 64 TO)"},
+
+ {0x9089, 0xce, 0xc5e2, "Ingrid's Back, pt. 1 (Commodore 64 Gfx)"},
+ {0x908d, 0x80, 0x30c7, "Ingrid's Back, pt. 2 (Commodore 64 Gfx)"},
+ {0x909e, 0x9f, 0xdecc, "Ingrid's Back, pt. 3 (Commodore 64 Gfx)"},
+
+ {0x8ab7, 0x68, 0xee57, "Ingrid's Back, pt. 1 (Spectrum 48)"},
+ {0x8b1e, 0x84, 0x2538, "Ingrid's Back, pt. 2 (Spectrum 48)"},
+ {0x8b1c, 0xa8, 0x9262, "Ingrid's Back, pt. 3 (Spectrum 48)"},
+
+ {0xbeab, 0x2d, 0x94d9, "Scapeghost, pt. 1 (Amiga)"},
+ {0xc132, 0x14, 0x7adc, "Scapeghost, pt. 1 (Amiga *bak*)"},
+ {0xbe94, 0xcc, 0x04b8, "Scapeghost, pt. 1 (PC/ST)"},
+ {0x99bd, 0x65, 0x032e, "Scapeghost, pt. 2 (Amiga/PC/ST)"},
+ {0xbcb6, 0x7a, 0x7d4f, "Scapeghost, pt. 3 (Amiga/PC/ST)"},
+
+ {0x9058, 0xcf, 0x9748, "Scapeghost, pt. 1 (Commodore 64 Gfx)"},
+ {0x8f43, 0xc9, 0xeefd, "Scapeghost, pt. 2 (Commodore 64 Gfx)"},
+ {0x90ac, 0x68, 0xb4a8, "Scapeghost, pt. 3 (Commodore 64 Gfx)"},
+
+ {0x8a21, 0xf4, 0xd9e4, "Scapeghost, pt. 1 (Spectrum 48)"},
+ {0x8a12, 0xe3, 0xc2ff, "Scapeghost, pt. 2 (Spectrum 48)"},
+ {0x8a16, 0xcc, 0x4f3b, "Scapeghost, pt. 3 (Spectrum 48)"},
+
+ {0x3ebb, 0x00, 0xf6dc, "Champion of the Raj (English) 1/2 GD (Amiga)"},
+ {0x0fd8, 0x00, 0xf250, "Champion of the Raj (English) 2/2 GD (Amiga)"},
+
+ {0x3e8f, 0x00, 0x5599, "Champion of the Raj (English) 1/2 GD (ST)"},
+
+ {0x3e4f, 0x00, 0xb202, "Champion of the Raj (English) 1/2 GD (PC)"},
+ {0x14a3, 0x00, 0xa288, "Champion of the Raj (English) 2/2 GD (PC)"},
+
+ {0x1929, 0x00, 0xd4b2, "Champion of the Raj (demo), 1/2 GD (ST)"},
+ {0x40e0, 0x02, 0x080d, "Champion of the Raj (demo), 2/2 GD (ST)"},
+
+ {0x4872, 0x00, 0x9515, "Champion of the Raj (German) 1/2 GD (Amiga)"},
+ {0x11f5, 0x00, 0xbf39, "Champion of the Raj (German) 2/2 GD (Amiga)"},
+
+ {0x4846, 0x00, 0xd9c1, "Champion of the Raj (German) 1/2 GD (ST)"},
+ {0x11f5, 0x00, 0x7aa4, "Champion of the Raj (German) 2/2 GD (ST)"},
+
+ {0x110f, 0x00, 0x4b57, "Champion of the Raj (French) 2/2 GD (ST)"},
+
+ {0x0000, 0x00, 0x0000, nullptr}
+};
+
+/**
+ * The following patch database is obtained from L9cut's l9data_p.h, and
+ * allows CRCs from patched games to be translated into original CRCs for
+ * lookup in the game database above. Some file commentary has been removed
+ * for brevity, and unused patch fields deleted.
+ *
+ * The version of l9data_p.h used is 012 (22 May 2001).
+ */
+static const gln_patch_table_t GLN_PATCH_TABLE[] = {
+ /* Price of Magik (Spectrum128) */
+ {0x7410, 0x5e, 0x60be, 0x70, 0x6cef},
+
+ /* Price of Magik (Commodore 64) */
+ {0x6fc6, 0x14, 0xf9b6, 0x26, 0x3326},
+
+ /* Price of Magik (Spectrum48) */
+ {0x5aa4, 0xc1, 0xeda4, 0xd3, 0xed35},
+ {0x5aa4, 0xc1, 0xeda4, 0xc1, 0x8a65},
+
+ /* Colossal Adventure /JoD (Amiga/PC) */
+ {0x76f4, 0x5e, 0x1fe5, 0xea, 0x1305},
+ {0x76f4, 0x5e, 0x1fe5, 0xb5, 0x901f},
+ {0x76f4, 0x5e, 0x1fe5, 0x5e, 0x6ea1},
+
+ /* Colossal Adventure /JoD (ST) */
+ {0x76f4, 0x5a, 0xcf4b, 0xe6, 0x012a},
+ {0x76f4, 0x5a, 0xcf4b, 0xb1, 0x40b1},
+
+ /* Adventure Quest /JoD (Amiga/PC) */
+ {0x6e60, 0x83, 0x18e0, 0x4c, 0xcfb0},
+ {0x6e60, 0x83, 0x18e0, 0xfa, 0x9b3b},
+ {0x6e60, 0x83, 0x18e0, 0x83, 0x303d},
+
+ /* Adventure Quest /JoD (ST) */
+ {0x6e5c, 0xf6, 0xd356, 0xbf, 0xede7},
+ {0x6e5c, 0xf6, 0xd356, 0x6d, 0x662d},
+
+ /* Dungeon Adventure /JoD (Amiga/PC/ST) */
+ {0x6f0c, 0x95, 0x1f64, 0x6d, 0x2443},
+ {0x6f0c, 0x95, 0x1f64, 0x0c, 0x6066},
+ {0x6f0c, 0x95, 0x1f64, 0x96, 0xdaca},
+ {0x6f0c, 0x95, 0x1f64, 0x95, 0x848d},
+
+ /* Colossal Adventure /JoD (Spectrum128) */
+ {0x6f6e, 0x78, 0x28cd, 0xf8, 0xda5f},
+ {0x6f6e, 0x78, 0x28cd, 0x77, 0x5b4e},
+
+ /* Adventure Quest /JoD (Spectrum128) */
+ {0x6970, 0xd6, 0xa820, 0x3b, 0x1870},
+ {0x6970, 0xd6, 0xa820, 0xd5, 0x13c4},
+
+ /* Dungeon Adventure /JoD (Spectrum128) */
+ {0x6de8, 0x4c, 0xd795, 0xa2, 0x3eea},
+ {0x6de8, 0x4c, 0xd795, 0x4b, 0xad30},
+
+ /* Colossal Adventure /JoD (Amstrad CPC) */
+ {0x6f4d, 0xcb, 0xe8f2, 0x4b, 0xb384},
+ {0x6f4d, 0xcb, 0xe8f2, 0xca, 0x96e7},
+
+ /* Adventure Quest /JoD (Amstrad CPC) */
+ {0x6968, 0x32, 0x0c01, 0x97, 0xdded},
+ {0x6968, 0x32, 0x0c01, 0x31, 0xe8c2},
+
+ /* Dungeon Adventure /JoD (Amstrad CPC) */
+ {0x6dc0, 0x63, 0x5d95, 0xb9, 0xc963},
+ {0x6dc0, 0x63, 0x5d95, 0x62, 0x79f7},
+
+ /* Colossal Adventure /JoD (Commodore 64) */
+ {0x6c8e, 0xb6, 0x9be3, 0x36, 0x6971},
+ {0x6c8e, 0xb6, 0x9be3, 0xb5, 0xeba0},
+
+ /* Adventure Quest /JoD (Commodore 64) */
+ {0x63b6, 0x2e, 0xef38, 0x93, 0x4e68},
+ {0x63b6, 0x2e, 0xef38, 0x2d, 0x54dc},
+
+ /* Dungeon Adventure /JoD (Commodore 64) */
+ {0x6bd2, 0x65, 0xa41f, 0xbb, 0x4260},
+ {0x6bd2, 0x65, 0xa41f, 0x64, 0xdf5a},
+
+ /* Colossal Adventure /JoD (Spectrum48) */
+ {0x5a8e, 0xf2, 0x7cca, 0x72, 0x8e58},
+ {0x5a8e, 0xf2, 0x7cca, 0xf1, 0x0c89},
+ {0x5a8e, 0xf2, 0x7cca, 0xf2, 0x2c96},
+
+ /* Adventure Quest /JoD (Spectrum48) */
+ {0x5ace, 0x11, 0xdc12, 0x76, 0x8663},
+ {0x5ace, 0x11, 0xdc12, 0x10, 0xa757},
+ {0x5ace, 0x11, 0xdc12, 0x11, 0xf118},
+
+ /* Dungeon Adventure /JoD (Spectrum48) */
+ {0x58a3, 0x38, 0x8ce4, 0x8e, 0xb61a},
+ {0x58a3, 0x38, 0x8ce4, 0x37, 0x34c0},
+ {0x58a3, 0x38, 0x8ce4, 0x38, 0xa1ee},
+
+ /* Snowball /SD (Amiga/ST) */
+ {0x7b31, 0x6e, 0x2e2b, 0xe5, 0x6017},
+
+ /* Return to Eden /SD (Amiga/ST) */
+ {0x7d16, 0xe6, 0x5438, 0x5d, 0xc770},
+
+ /* Worm in Paradise /SD (Amiga/ST) */
+ {0x7cd9, 0x0c, 0x4df1, 0x83, 0xe997},
+
+ /* Snowball /SD (PC/Spectrum128) */
+ {0x7b2f, 0x70, 0x6955, 0xe7, 0x0af4},
+ {0x7b2f, 0x70, 0x6955, 0x70, 0x1179},
+
+ /* Return to Eden /SD (PC) */
+ {0x7d14, 0xe8, 0xfbab, 0x5f, 0xeab9},
+ {0x7d14, 0xe8, 0xfbab, 0xe8, 0xe216},
+
+ /* Return to Eden /SD (Amstrad CPC) */
+ {0x7cff, 0xf8, 0x6044, 0x6f, 0xbb57},
+
+ /* Return to Eden /SD (Spectrum128) */
+ {0x7c55, 0x18, 0xdaee, 0x8f, 0x01fd},
+
+ /* Worm in Paradise /SD (Amstrad CPC/PC/Spectrum128) */
+ {0x7cd7, 0x0e, 0x4feb, 0x85, 0x4eae},
+ {0x7cd7, 0x0e, 0x4feb, 0x0e, 0xb02c},
+
+ /* Snowball /SD (Commodore 64) */
+ {0x7363, 0x65, 0xa0ab, 0xdc, 0xca6a},
+
+ /* Return to Eden /SD (Commodore 64) */
+ {0x772f, 0xca, 0x8602, 0x41, 0x9bd0},
+
+ /* Worm in Paradise /SD (Commodore 64) */
+ {0x788d, 0x72, 0x888a, 0xe9, 0x4cce},
+
+ /* Snowball /SD (Atari) */
+ {0x6bf8, 0x3f, 0xc9f7, 0x96, 0x1908},
+
+ /* Return to Eden /SD (Atari) */
+ {0x60f7, 0x68, 0xc2bc, 0xdf, 0xd3ae},
+
+ /* Worm in Paradise /SD (Atari) */
+ {0x6161, 0xf3, 0xe6d7, 0x6a, 0xe232},
+
+ /* Snowball /SD (Spectrum48) */
+ {0x6541, 0x02, 0x2e6c, 0x79, 0xb80c},
+ {0x6541, 0x02, 0x2e6c, 0x02, 0x028a},
+
+ /* Return to Eden /SD (Spectrum48) */
+ {0x5f43, 0xca, 0x828c, 0x41, 0x9f5e},
+ {0x5f43, 0xca, 0x828c, 0xca, 0x6e1b},
+
+ /* Worm in Paradise /SD (Spectrum48) */
+ {0x5ebb, 0xf1, 0x4dec, 0x68, 0x4909},
+ {0x5ebb, 0xf1, 0x4dec, 0xf1, 0xcc1a},
+
+ /* Knight Orc, pt. 1 (Amiga) */
+ {0xbb93, 0x36, 0x6a05, 0xad, 0xe52d},
+
+ /* Knight Orc, pt. 1 (ST) */
+ {0xbb6e, 0xad, 0x4d40, 0x24, 0x3bcd},
+
+ /* Knight Orc, pt. 2 (Amiga/ST) */
+ {0xc58e, 0x4a, 0x4e9d, 0xc1, 0xe2bf},
+
+ /* Knight Orc, pt. 3 (Amiga/ST) */
+ {0xcb9a, 0x0f, 0x0804, 0x86, 0x6487},
+
+ /* Knight Orc, pt. 1 (PC) */
+ {0xbb6e, 0xa6, 0x9753, 0x1d, 0x2e7f},
+ {0xbb6e, 0xa6, 0x9753, 0xa6, 0x001d},
+
+ /* Knight Orc, pt. 2 (PC) */
+ {0xc58e, 0x43, 0xe9ce, 0xba, 0x5e4c},
+ {0xc58e, 0x43, 0xe9ce, 0x43, 0xa8f0},
+
+ /* Knight Orc, pt. 3 (PC) */
+ {0xcb9a, 0x08, 0x6c36, 0x7f, 0xf0d4},
+ {0xcb9a, 0x08, 0x6c36, 0x08, 0x2d08},
+
+ /* Knight Orc, pt. 1 (Commodore 64 Gfx) */
+ {0x8970, 0x6b, 0x3c7b, 0xe2, 0xb6f3},
+
+ /* Knight Orc, pt. 1 (Spectrum48) */
+ {0x86d0, 0xb7, 0xadbd, 0x2e, 0x43e1},
+
+ /* Gnome Ranger, pt. 1 (Amiga/ST) */
+ {0xb1a9, 0x80, 0x5fb7, 0xf7, 0x5c6c},
+
+ /* Gnome Ranger, pt. 2 (Amiga/ST) */
+ {0xab9d, 0x31, 0xbe6d, 0xa8, 0xcb96},
+
+ /* Gnome Ranger, pt. 3 (Amiga/ST) */
+ {0xae28, 0x87, 0xb6b6, 0xfe, 0x760c},
+
+ /* Gnome Ranger, pt. 1 (PC) */
+ {0xb1aa, 0xad, 0xaf47, 0x24, 0x5cfd},
+ {0xb1aa, 0xad, 0xaf47, 0xad, 0xe0ed},
+
+ /* Gnome Ranger, pt. 1 (ST-var) */
+ {0xb19e, 0x92, 0x8f96, 0x09, 0x798c},
+
+ /* Gnome Ranger, pt. 2 (PC/ST-var) */
+ {0xab8b, 0xbf, 0x31e6, 0x36, 0x811c},
+ {0xab8b, 0xbf, 0x31e6, 0xbf, 0x8ff3},
+
+ /* Gnome Ranger, pt. 3 (PC/ST-var) */
+ {0xae16, 0x81, 0x8741, 0xf8, 0x47fb},
+ {0xae16, 0x81, 0x8741, 0x81, 0xc8eb},
+
+ /* Gnome Ranger, pt. 1 (Commodore 64 TO) */
+ {0xad41, 0xa8, 0x42c5, 0x1f, 0x7d1e},
+
+ /* Gnome Ranger, pt. 2 (Commodore 64 TO) */
+ {0xa735, 0xf7, 0x2e08, 0x6e, 0x780e},
+
+ /* Gnome Ranger, pt. 3 (Commodore 64 TO) */
+ {0xa9c0, 0x9e, 0x0d70, 0x15, 0x3e6b},
+
+ /* Gnome Ranger, pt. 1 (Commodore 64 Gfx) */
+ {0x908e, 0x0d, 0x58a7, 0x84, 0xab1d},
+
+ /* Gnome Ranger, pt. 2 (Commodore 64 Gfx) */
+ {0x8f6f, 0x0a, 0x411a, 0x81, 0x12bc},
+
+ /* Gnome Ranger, pt. 3 (Commodore 64 Gfx) */
+ {0x9060, 0xbb, 0xe75d, 0x32, 0x14e7},
+
+ /* Lords of Time /T&M (PC) */
+ {0xb57c, 0x44, 0x7779, 0xbb, 0x31a6},
+ {0xb57c, 0x44, 0x7779, 0x44, 0xea72},
+
+ /* Red Moon /T&M (PC) */
+ {0xa69e, 0x6c, 0xb268, 0xe3, 0x4cef},
+ {0xa69e, 0x6c, 0xb268, 0x6c, 0x3799},
+
+ /* Price of Magik /T&M (PC) */
+ {0xbac7, 0x7f, 0xddb2, 0xf6, 0x6ab3},
+ {0xbac7, 0x7f, 0xddb2, 0x7f, 0x905c},
+
+ /* Lords of Time /T&M (ST) */
+ {0xb579, 0x89, 0x3e89, 0x00, 0xa2b7},
+
+ /* Red Moon /T&M (ST) */
+ {0xa698, 0x41, 0xcaca, 0xb8, 0xeeac},
+
+ /* Price of Magik /T&M (ST) */
+ {0xbac4, 0x80, 0xa750, 0xf7, 0xe030},
+
+ /* Lords of Time /T&M (Amiga) */
+ {0xb576, 0x2a, 0x7239, 0xa1, 0x2ea6},
+
+ /* Red Moon /T&M (Amiga) */
+ {0xa692, 0xd1, 0x6a99, 0x48, 0x50ff},
+
+ /* Price of Magik /T&M (Amiga) */
+ {0xbaca, 0x3a, 0x221b, 0xb1, 0x55bb},
+
+ /* Lords of Time /T&M (Commodore 64 TO) */
+ {0xb38c, 0x37, 0x9f8e, 0xae, 0xc6b1},
+
+ /* Red Moon /T&M (Commodore 64 TO) */
+ {0xa4e2, 0xa6, 0x016d, 0x1d, 0x31ab},
+
+ /* Price of Magik /T&M (Commodore 64 TO) */
+ {0xb451, 0xa8, 0x2682, 0x1f, 0x5de2},
+
+ /* Lords of Time /T&M (Commodore 64 Gfx) */
+ {0x9070, 0x43, 0x45d4, 0xba, 0x02eb},
+
+ /* Red Moon /T&M (Commodore 64 Gfx) */
+ {0x903f, 0x6b, 0x603e, 0xe2, 0x9f59},
+
+ /* Price of Magik /T&M (Commodore 64 Gfx) */
+ {0x8f51, 0xb2, 0x6c9a, 0x29, 0xde3b},
+
+ /* Lords of Time /T&M (Spectrum48) */
+ {0x8950, 0xa1, 0xbb16, 0x18, 0x2828},
+ {0x8950, 0xa1, 0xbb16, 0xa1, 0x1ea2},
+
+ /* Red Moon /T&M (Spectrum48) */
+ {0x8813, 0x11, 0x22de, 0x88, 0x18b8},
+ {0x8813, 0x11, 0x22de, 0x11, 0xd0cd},
+
+ /* Price of Magik /T&M (Spectrum48) */
+ {0x8a60, 0x2a, 0x29ed, 0xa1, 0x5e4d},
+
+ /* Lancelot, pt. 1 (Amiga/PC/ST) */
+ {0xc0cf, 0x4e, 0xb7fa, 0xc5, 0x4400},
+
+ /* Lancelot, pt. 2 (Amiga/PC/ST) */
+ {0xd5e9, 0x6a, 0x4192, 0xe1, 0x3b1e},
+
+ /* Lancelot, pt. 3 (Amiga/PC/ST) */
+ {0xbb8f, 0x1a, 0x7487, 0x91, 0x877d},
+
+ /* Lancelot, pt. 1 (Commodore 64 TO) */
+ {0xb4c9, 0x94, 0xd784, 0x0b, 0x203e},
+
+ /* Lancelot, pt. 2 (Commodore 64 TO) */
+ {0xb729, 0x51, 0x8ee5, 0xc8, 0xf1c9},
+
+ /* Lancelot, pt. 3 (Commodore 64 TO) */
+ {0xb702, 0xe4, 0x1809, 0x5b, 0x25b2},
+
+ /* Lancelot, pt. 1 (Commodore 64 Gfx) */
+ {0x8feb, 0xba, 0xa800, 0x31, 0x5bfa},
+
+ /* Lancelot, pt. 2 (Commodore 64 Gfx) */
+ {0x8f6b, 0xfa, 0x0f7e, 0x71, 0x75f2},
+
+ /* Lancelot, pt. 3 (Commodore 64 Gfx) */
+ {0x8f71, 0x2f, 0x0ddc, 0xa6, 0x3e87},
+
+ /* Ingrid's Back, pt. 1 (PC) */
+ {0xd19b, 0xad, 0x306d, 0x24, 0x4504},
+ {0xd19b, 0xad, 0x306d, 0xad, 0x878e},
+
+ /* Ingrid's Back, pt. 2 (PC) */
+ {0xc5a5, 0xfe, 0x3c98, 0x75, 0x8950},
+ {0xc5a5, 0xfe, 0x3c98, 0xfe, 0x8b7b},
+
+ /* Ingrid's Back, pt. 3 (PC) */
+ {0xd7ae, 0x9e, 0x1878, 0x15, 0xadb0},
+ {0xd7ae, 0x9e, 0x1878, 0x9e, 0xaf9b},
+
+ /* Ingrid's Back, pt. 1 (Amiga) */
+ {0xd188, 0x13, 0xdc60, 0x8a, 0x755c},
+
+ /* Ingrid's Back, pt. 2 (Amiga) */
+ {0xc594, 0x03, 0xea95, 0x7a, 0xb5a8},
+
+ /* Ingrid's Back, pt. 3 (Amiga) */
+ {0xd79f, 0xb5, 0x1661, 0x2c, 0xbf5d},
+
+ /* Ingrid's Back, pt. 1 (ST) */
+ {0xd183, 0x83, 0xef72, 0xfa, 0xb04f},
+
+ /* Ingrid's Back, pt. 2 (ST) */
+ {0xc58f, 0x65, 0xf337, 0xdc, 0x900a},
+
+ /* Ingrid's Back, pt. 3 (ST) */
+ {0xd79a, 0x57, 0x49c5, 0xce, 0xe0f9},
+
+ /* Ingrid's Back, pt. 1 (Commodore 64 TO) */
+ {0xb770, 0x03, 0x9a03, 0x7a, 0xdc6a},
+
+ /* Ingrid's Back, pt. 2 (Commodore 64 TO) */
+ {0xb741, 0xb6, 0x2aa5, 0x2d, 0x5a6c},
+
+ /* Ingrid's Back, pt. 3 (Commodore 64 TO) */
+ {0xb791, 0xa1, 0xd065, 0x18, 0xaa0c},
+
+ /* Ingrid's Back, pt. 1 (Commodore 64 Gfx) */
+ {0x9089, 0xce, 0xc5e2, 0x44, 0xeff4},
+
+ /* Ingrid's Back, pt. 2 (Commodore 64 Gfx) */
+ {0x908d, 0x80, 0x30c7, 0xf6, 0x2a11},
+
+ /* Ingrid's Back, pt. 3 (Commodore 64 Gfx) */
+ {0x909e, 0x9f, 0xdecc, 0x15, 0xf4da},
+
+ {0x0000, 0x00, 0x0000, 0x00, 0x0000},
+};
+
+
// TODO: The list of Level 9 games and detection entries needs to be filled out
const PlainGameDescriptor LEVEL9_GAME_LIST[] = {
{ "level9", "Level 9 IF Game" },
diff --git a/engines/glk/level9/level9.cpp b/engines/glk/level9/level9.cpp
index 7fe85ee..31caeed 100644
--- a/engines/glk/level9/level9.cpp
+++ b/engines/glk/level9/level9.cpp
@@ -29,7 +29,8 @@ namespace Level9 {
Level9 *g_vm = nullptr;
-Level9::Level9(OSystem *syst, const GlkGameDescription &gameDesc) : GlkAPI(syst, gameDesc) {
+Level9::Level9(OSystem *syst, const GlkGameDescription &gameDesc) : GlkAPI(syst, gameDesc),
+ _detection(startdata, FileSize) {
g_vm = this;
}
diff --git a/engines/glk/level9/level9.h b/engines/glk/level9/level9.h
index 90c441e..ee7dbce 100644
--- a/engines/glk/level9/level9.h
+++ b/engines/glk/level9/level9.h
@@ -27,6 +27,7 @@
#include "common/serializer.h"
#include "common/stack.h"
#include "glk/glk_api.h"
+#include "glk/level9/detection.h"
namespace Glk {
namespace Level9 {
@@ -46,6 +47,8 @@ private:
*/
void deinitialize();
public:
+ GameDetection _detection;
+public:
/**
* Constructor
*/
diff --git a/engines/glk/level9/level9_main.cpp b/engines/glk/level9/level9_main.cpp
index 2ce60a5..b4ab1a8 100644
--- a/engines/glk/level9/level9_main.cpp
+++ b/engines/glk/level9/level9_main.cpp
@@ -77,8 +77,8 @@ enum L9GfxTypes { GFX_V2, GFX_V3A, GFX_V3B, GFX_V3C };
/* Global Variables */
L9BYTE *startfile, *pictureaddress, *picturedata;
-L9BYTE *startdata;
-L9UINT32 FileSize, picturesize;
+byte *startdata;
+size_t FileSize, picturesize;
L9BYTE *L9Pointers[12];
L9BYTE *absdatablock, *list2ptr, *list3ptr, *list9startptr, *acodeptr;
diff --git a/engines/glk/level9/level9_main.h b/engines/glk/level9/level9_main.h
index ec04db3..95d9f18 100644
--- a/engines/glk/level9/level9_main.h
+++ b/engines/glk/level9/level9_main.h
@@ -86,6 +86,9 @@ struct Bitmap {
#define L9SETWORD(x,val) WRITE_LE_UINT16(x, val)
#define L9SETDWORD(x,val) WRITE_LE_UINT32(x, val)
+extern byte *startdata;
+extern size_t FileSize;
+
extern void level9_initialize();
/* routines provided by os dependent code */
diff --git a/engines/glk/level9/os_glk.cpp b/engines/glk/level9/os_glk.cpp
index 586b808..ab171c3 100644
--- a/engines/glk/level9/os_glk.cpp
+++ b/engines/glk/level9/os_glk.cpp
@@ -20,6 +20,7 @@
*
*/
+#include "glk/level9/os_glk.h"
#include "glk/level9/level9_main.h"
#include "glk/level9/level9.h"
#include "common/textconsole.h"
@@ -27,9 +28,6 @@
namespace Glk {
namespace Level9 {
-#define BYTE_MAX 0xff
-#define BITS_PER_CHAR 8
-
/*---------------------------------------------------------------------*/
/* Module variables, miscellaneous other stuff */
/*---------------------------------------------------------------------*/
@@ -83,7 +81,6 @@ typedef L9UINT32 gln_uint32;
extern void save();
extern void restore();
extern gln_bool Cheating;
-extern gln_byte *startdata;
extern gln_uint32 FileSize;
/* Forward declarations of event wait and other miscellaneous functions. */
@@ -285,1022 +282,6 @@ int gln_strcasecmp(const char *s1, const char *s2) {
}
/*---------------------------------------------------------------------*/
-/* Glk port CRC functions */
-/*---------------------------------------------------------------------*/
-
-/* CRC table initialization polynomial. */
-static const gln_uint16 GLN_CRC_POLYNOMIAL = 0xa001;
-
-
-/*
- * gln_get_buffer_crc()
- *
- * Return the CRC of the bytes buffer[0..length-1].
- *
- * This algorithm is selected to match the CRCs used in L9cut. Because of
- * the odd way CRCs are padded when L9cut calculates the CRC, this function
- * allows a count of NUL padding bytes to be included within the return CRC.
- */
-static gln_uint16 gln_get_buffer_crc(const void *void_buffer, size_t length, size_t padding) {
- static int is_initialized = FALSE;
- static gln_uint16 crc_table[BYTE_MAX + 1];
-
- const char *buffer = (const char *) void_buffer;
- gln_uint16 crc;
- size_t index;
-
- /* Build the static CRC lookup table on first call. */
- if (!is_initialized) {
- for (index = 0; index < BYTE_MAX + 1; index++) {
- int bit;
-
- crc = (gln_uint16) index;
- for (bit = 0; bit < BITS_PER_CHAR; bit++)
- crc = crc & 1 ? GLN_CRC_POLYNOMIAL ^ (crc >> 1) : crc >> 1;
-
- crc_table[index] = crc;
- }
-
- is_initialized = TRUE;
-
- /* CRC lookup table self-test, after is_initialized set -- recursion. */
- assert(gln_get_buffer_crc("123456789", 9, 0) == 0xbb3d);
- }
-
- /* Start with zero in the crc, then update using table entries. */
- crc = 0;
- for (index = 0; index < length; index++)
- crc = crc_table[(crc ^ buffer[index]) & BYTE_MAX] ^ (crc >> BITS_PER_CHAR);
-
- /* Add in any requested NUL padding bytes. */
- for (index = 0; index < padding; index++)
- crc = crc_table[crc & BYTE_MAX] ^ (crc >> BITS_PER_CHAR);
-
- return crc;
-}
-
-
-/*---------------------------------------------------------------------*/
-/* Glk port game identification data and identification functions */
-/*---------------------------------------------------------------------*/
-
-/*
- * The game's name, suitable for printing out on a status line, or other
- * location where game information is relevant. It's generated on demand,
- * and may be re-requested when, say, the game changes, perhaps by moving to
- * the next part of a multipart game.
- */
-static const char *gln_gameid_game_name = nullptr;
-
-
-/*
- * The following game database is obtained from L9cut's l9data_d.h, and
- * lets us find a game's name from its data CRC. Entries marked "WANTED" in
- * l9data_d.h, and file commentary, have been removed for brevity, and the
- * file has been reformatted (patchlevel data removed).
- *
- * The version of l9data_d.h used is 050 (22 Oct 2002).
- */
-struct gln_game_table_t {
- const gln_uint16 length; /* Datafile length in bytes */
- const gln_byte checksum; /* 8-bit checksum, last datafile byte */
- const gln_uint16 crc; /* 16-bit CRC, L9cut-internal */
- const char *const name; /* Game title and platform */
-};
-typedef const gln_game_table_t *gln_game_tableref_t;
-
-static const gln_game_table_t GLN_GAME_TABLE[] = {
- {0x5323, 0xb7, 0x8af7, "Adventure Quest (Amstrad CPC/Spectrum)"},
-
- {0x630e, 0x8d, 0x7d7d, "Dungeon Adventure (Amstrad CPC)"},
- {0x630e, 0xbe, 0x3374, "Dungeon Adventure (MSX)"},
-
- {0x5eb9, 0x30, 0xe99a, "Lords of Time (Amstrad CPC)"},
- {0x5eb9, 0x5d, 0xc098, "Lords of Time (MSX)"},
- {0x5eb9, 0x6e, 0xc689, "Lords of Time (Spectrum)"},
-
- {0x5fab, 0x5c, 0xa309, "Snowball (Amstrad CPC)"},
- {0x5fab, 0x2f, 0x8aa2, "Snowball (MSX)"},
-
- {0x60c4, 0x28, 0x0154, "Return to Eden (Amstrad CPC/Commodore 64[v1])"},
- {0x6064, 0x01, 0x5b3c, "Return to Eden (BBC[v1])"},
- {0x6064, 0x95, 0x510c, "Return to Eden (Commodore 64[v2])"},
- {0x6064, 0xda, 0xe610, "Return to Eden (Commodore 64[v2] *corrupt*)"},
- {0x6064, 0xbd, 0x73ec, "Return to Eden (Atari *corrupt*)"},
- {0x6047, 0x6c, 0x17ab, "Return to Eden (BBC[v2])"},
- {0x5ca1, 0x33, 0x1c43, "Return to Eden (Spectrum[v1])"},
- {0x5cb7, 0x64, 0x0790, "Return to Eden (Spectrum[v2])"},
- {0x5cb7, 0xfe, 0x3533, "Return to Eden (MSX)"},
-
- {0x34b3, 0x20, 0xccda, "Erik the Viking (BBC/Commodore 64)"},
- {0x34b3, 0x53, 0x8f00, "Erik the Viking (Spectrum)"},
- {0x34b3, 0xc7, 0x9058, "Erik the Viking (Amstrad CPC)"},
-
- {
- 0x63be, 0xd6, 0xcf5d,
- "Emerald Isle (Atari/Commodore 64/Amstrad CPC/Spectrum)"
- },
- {0x63be, 0x0a, 0x21ed, "Emerald Isle (MSX *corrupt*)"},
- {0x378c, 0x8d, 0x3a21, "Emerald Isle (BBC)"},
-
- {0x506c, 0xf0, 0xba72, "Red Moon (BBC/Commodore 64/Amstrad CPC/MSX)"},
- {0x505d, 0x32, 0x2dcf, "Red Moon (Spectrum)"},
-
- {0x772b, 0xcd, 0xa503, "Worm in Paradise (Spectrum 128)"},
- {0x546c, 0xb7, 0x9420, "Worm in Paradise (Spectrum 48)"},
- {0x6d84, 0xf9, 0x49ae, "Worm in Paradise (Commodore 64 *corrupt*)"},
- {0x6d84, 0xc8, 0x943f, "Worm in Paradise (Commodore 64 *fixed*)"},
- {0x6030, 0x47, 0x46ad, "Worm in Paradise (Amstrad CPC)"},
- {0x5828, 0xbd, 0xe7cb, "Worm in Paradise (BBC)"},
-
- {0x7410, 0x5e, 0x60be, "Price of Magik (Spectrum 128)"},
- {0x5aa4, 0xc1, 0x10a0, "Price of Magik (Spectrum 48[v1])"},
- {0x5aa4, 0xc1, 0xeda4, "Price of Magik (Spectrum 48[v2])"},
- {0x6fc6, 0x14, 0xf9b6, "Price of Magik (Commodore 64)"},
- {0x5aa4, 0xc1, 0xbbf4, "Price of Magik (Amstrad CPC)"},
- {0x5671, 0xbc, 0xff35, "Price of Magik (BBC)"},
-
- {0x76f4, 0x5e, 0x1fe5, "Colossal Adventure /JoD (Amiga/PC)"},
- {0x76f4, 0x5a, 0xcf4b, "Colossal Adventure /JoD (ST)"},
- {0x6e60, 0x83, 0x18e0, "Adventure Quest /JoD (Amiga/PC)"},
- {0x6e5c, 0xf6, 0xd356, "Adventure Quest /JoD (ST)"},
- {0x6f0c, 0x95, 0x1f64, "Dungeon Adventure /JoD (Amiga/PC/ST)"},
-
- {0x6f70, 0x40, 0xbd91, "Colossal Adventure /JoD (MSX)"},
-
- {0x6f6e, 0x78, 0x28cd, "Colossal Adventure /JoD (Spectrum 128)"},
- {0x6970, 0xd6, 0xa820, "Adventure Quest /JoD (Spectrum 128)"},
- {0x6de8, 0x4c, 0xd795, "Dungeon Adventure /JoD (Spectrum 128)"},
-
- {
- 0x6f4d, 0xcb, 0xe8f2,
- "Colossal Adventure /JoD (Amstrad CPC128[v1]/Spectrum +3)"
- },
- {0x6f6a, 0xa5, 0x8dd2, "Colossal Adventure /JoD (Amstrad CPC128[v2])"},
- {0x6968, 0x32, 0x0c01, "Adventure Quest /JoD (Amstrad CPC128/Spectrum +3)"},
- {0x6dc0, 0x63, 0x5d95, "Dungeon Adventure /JoD (Amstrad CPC128/Spectrum +3)"},
-
- {0x5e31, 0x7c, 0xaa54, "Colossal Adventure /JoD (Amstrad CPC64)"},
- {0x5b50, 0x66, 0x1800, "Adventure Quest /JoD (Amstrad CPC64)"},
- {0x58a6, 0x24, 0xb50f, "Dungeon Adventure /JoD (Amstrad CPC64)"},
-
- {0x6c8e, 0xb6, 0x9be3, "Colossal Adventure /JoD (Commodore 64)"},
- {0x63b6, 0x2e, 0xef38, "Adventure Quest /JoD (Commodore 64)"},
- {0x6bd2, 0x65, 0xa41f, "Dungeon Adventure /JoD (Commodore 64)"},
-
- {0x5b16, 0x3b, 0xe2aa, "Colossal Adventure /JoD (Atari)"},
- {0x5b58, 0x50, 0x332e, "Adventure Quest /JoD (Atari)"},
- {0x593a, 0x80, 0x7a34, "Dungeon Adventure /JoD (Atari)"},
-
- {0x5a8e, 0xf2, 0x7cca, "Colossal Adventure /JoD (Spectrum 48)"},
- {0x5ace, 0x11, 0xdc12, "Adventure Quest /JoD (Spectrum 48)"},
- {0x58a3, 0x38, 0x8ce4, "Dungeon Adventure /JoD (Spectrum 48)"},
-
- {0x7b31, 0x6e, 0x2e2b, "Snowball /SD (Amiga/ST)"},
- {0x7d16, 0xe6, 0x5438, "Return to Eden /SD (Amiga/ST)"},
- {0x7cd9, 0x0c, 0x4df1, "Worm in Paradise /SD (Amiga/ST)"},
-
- {0x7b2f, 0x70, 0x6955, "Snowball /SD (Mac/PC/Spectrum 128)"},
- {0x7b2f, 0x70, 0x6f6c, "Snowball /SD (Amstrad CPC/Spectrum +3)"},
- {0x7d14, 0xe8, 0xfbab, "Return to Eden /SD (PC)"},
- {0x7cff, 0xf8, 0x6044, "Return to Eden /SD (Amstrad CPC/Spectrum +3)"},
- {0x7cf8, 0x24, 0x9c1c, "Return to Eden /SD (Mac)"},
- {0x7c55, 0x18, 0xdaee, "Return to Eden /SD (Spectrum 128)"},
- {
- 0x7cd7, 0x0e, 0x4feb,
- "Worm in Paradise /SD (Amstrad CPC/Mac/PC/Spectrum 128/Spectrum +3)"
- },
-
- {0x7363, 0x65, 0xa0ab, "Snowball /SD (Commodore 64)"},
- {0x772f, 0xca, 0x8602, "Return to Eden /SD (Commodore 64)"},
- {0x788d, 0x72, 0x888a, "Worm in Paradise /SD (Commodore 64)"},
-
- {0x6bf8, 0x3f, 0xc9f7, "Snowball /SD (Atari)"},
- {0x60f7, 0x68, 0xc2bc, "Return to Eden /SD (Atari)"},
- {0x6161, 0xf3, 0xe6d7, "Worm in Paradise /SD (Atari)"},
-
- {0x67a3, 0x9d, 0x1d05, "Snowball /SD (Apple ][)"},
- {0x639c, 0x8b, 0x06e2, "Return to Eden /SD (Apple ][)"},
- {0x60dd, 0xf2, 0x5bb8, "Worm in Paradise /SD (Apple ][)"},
-
- {0x6541, 0x02, 0x2e6c, "Snowball /SD (Spectrum 48)"},
- {0x5f43, 0xca, 0x828c, "Return to Eden /SD (Spectrum 48)"},
- {0x5ebb, 0xf1, 0x4dec, "Worm in Paradise /SD (Spectrum 48)"},
-
- {0x8333, 0xb7, 0xe2ac, "Adrian Mole I, pt. 1 (Commodore 64)"},
- {0x844d, 0x50, 0x5353, "Adrian Mole I, pt. 2 (Commodore 64)"},
- {0x8251, 0x5f, 0x862a, "Adrian Mole I, pt. 3 (Commodore 64)"},
- {0x7a78, 0x5e, 0x6ea3, "Adrian Mole I, pt. 4 (Commodore 64)"},
-
- {0x7c6f, 0x0f, 0xba24, "Adrian Mole I, pt. 1 (Amstrad CPC)"},
-
- {0x72fa, 0x8b, 0x6f12, "Adrian Mole I, pt. 1 (Spectrum)"},
- {0x738e, 0x5b, 0x7e3d, "Adrian Mole I, pt. 2 (Spectrum)"},
- {0x7375, 0xe5, 0x3f3e, "Adrian Mole I, pt. 3 (Spectrum)"},
- {0x78d5, 0xe3, 0xcd7d, "Adrian Mole I, pt. 4 (Spectrum)"},
-
- {0x3a31, 0xe5, 0x0bdb, "Adrian Mole I, pt. 1 (BBC)"},
- {0x37f1, 0x77, 0xd231, "Adrian Mole I, pt. 2 (BBC)"},
- {0x3900, 0x1c, 0x5d9a, "Adrian Mole I, pt. 3 (BBC)"},
- {0x3910, 0xac, 0x07f9, "Adrian Mole I, pt. 4 (BBC)"},
- {0x3ad6, 0xa7, 0x95d2, "Adrian Mole I, pt. 5 (BBC)"},
- {0x38a5, 0x0f, 0xdefc, "Adrian Mole I, pt. 6 (BBC)"},
- {0x361e, 0x7e, 0xfd9f, "Adrian Mole I, pt. 7 (BBC)"},
- {0x3934, 0x75, 0xe141, "Adrian Mole I, pt. 8 (BBC)"},
- {0x3511, 0xcc, 0xd829, "Adrian Mole I, pt. 9 (BBC)"},
- {0x38dd, 0x31, 0x2534, "Adrian Mole I, pt. 10 (BBC)"},
- {0x39c0, 0x44, 0x89df, "Adrian Mole I, pt. 11 (BBC)"},
- {0x3a12, 0x8f, 0xc2bd, "Adrian Mole I, pt. 12 (BBC)"},
-
- {0x7931, 0xb9, 0xc51b, "Adrian Mole II, pt. 1 (Commodore 64/Amstrad CPC)"},
- {0x7cdf, 0xa5, 0x43e3, "Adrian Mole II, pt. 2 (Commodore 64/Amstrad CPC)"},
- {0x7a0c, 0x97, 0x4bea, "Adrian Mole II, pt. 3 (Commodore 64/Amstrad CPC)"},
- {0x7883, 0xe2, 0xee0e, "Adrian Mole II, pt. 4 (Commodore 64/Amstrad CPC)"},
-
- {0x6841, 0x4a, 0x94e7, "Adrian Mole II, pt. 1 (Spectrum)"},
- {0x6bc0, 0x62, 0xab3d, "Adrian Mole II, pt. 2 (Spectrum)"},
- {0x692c, 0x21, 0x2015, "Adrian Mole II, pt. 3 (Spectrum)"},
- {0x670a, 0x94, 0xa2a6, "Adrian Mole II, pt. 4 (Spectrum)"},
-
- {0x593a, 0xaf, 0x30e9, "Adrian Mole II, pt. 1 (BBC)"},
- {0x57e6, 0x8a, 0xc41a, "Adrian Mole II, pt. 2 (BBC)"},
- {0x5819, 0xcd, 0x1ba0, "Adrian Mole II, pt. 3 (BBC)"},
- {0x579b, 0xad, 0xa723, "Adrian Mole II, pt. 4 (BBC)"},
-
- {0x765d, 0xcd, 0xfc02, "The Archers, pt. 1 (Commodore 64)"},
- {0x6e58, 0x07, 0xbffc, "The Archers, pt. 2 (Commodore 64)"},
- {0x7e98, 0x6a, 0x95e5, "The Archers, pt. 3 (Commodore 64)"},
- {0x81e2, 0xd5, 0xb278, "The Archers, pt. 4 (Commodore 64)"},
-
- {0x6ce5, 0x58, 0x46de, "The Archers, pt. 1 (Spectrum)"},
- {0x68da, 0xc1, 0x3b8e, "The Archers, pt. 2 (Spectrum)"},
- {0x6c67, 0x9a, 0x9a6a, "The Archers, pt. 3 (Spectrum)"},
- {0x6d91, 0xb9, 0x12a7, "The Archers, pt. 4 (Spectrum)"},
-
- {0x5834, 0x42, 0xcc9d, "The Archers, pt. 1 (BBC)"},
- {0x56dd, 0x51, 0xe582, "The Archers, pt. 2 (BBC)"},
- {0x5801, 0x53, 0xf2ef, "The Archers, pt. 3 (BBC)"},
- {0x54a4, 0x01, 0xc0ab, "The Archers, pt. 4 (BBC)"},
-
- {0x579e, 0x97, 0x9faa, "Lords of Time /T&M GD (BBC)"},
- {0x5500, 0x50, 0xca75, "Red Moon /T&M GD (BBC)"},
- {0x579a, 0x2a, 0x9373, "Price of Magik /T&M GD (BBC)"},
-
- {0x4fd2, 0x9d, 0x799a, "Lancelot, pt. 1 GD (BBC)"},
- {0x4dac, 0xa8, 0x86ed, "Lancelot, pt. 2 GD (BBC)"},
- {0x4f96, 0x22, 0x30f8, "Lancelot, pt. 3 GD (BBC)"},
-
- {0x55ce, 0xa1, 0xba12, "Scapeghost, pt. 1 GD (BBC)"},
- {0x54a6, 0xa9, 0xc9f3, "Scapeghost, pt. 2 GD (BBC)"},
- {0x51bc, 0xe3, 0x89c3, "Scapeghost, pt. 3 GD (BBC)"},
-
- {0x46ec, 0x64, 0x2300, "Knight Orc, pt. 1 GD (Amstrad CPC/Spectrum +3)"},
- {0x6140, 0x18, 0x4f66, "Knight Orc, pt. 2 GD (Amstrad CPC/Spectrum +3)"},
- {0x640e, 0xc1, 0xfc69, "Knight Orc, pt. 3 GD (Amstrad CPC/Spectrum +3)"},
-
- {0x5ff0, 0xf8, 0x3a13, "Gnome Ranger, pt. 1 GD (Amstrad CPC/Spectrum +3)"},
- {0x6024, 0x01, 0xaaa9, "Gnome Ranger, pt. 2 GD (Amstrad CPC/Spectrum +3)"},
- {0x6036, 0x3d, 0x6c6c, "Gnome Ranger, pt. 3 GD (Amstrad CPC/Spectrum +3)"},
-
- {0x69fe, 0x56, 0xecfb, "Lords of Time /T&M GD (Amstrad CPC/Spectrum +3)"},
- {0x6888, 0x8d, 0x7f6a, "Red Moon /T&M GD (Amstrad CPC/Spectrum +3)"},
- {0x5a50, 0xa9, 0xa5fa, "Price of Magik /T&M GD (Amstrad CPC/Spectrum +3)"},
-
- {0x5c7a, 0x44, 0x460e, "Lancelot, pt. 1 GD (Amstrad CPC/Spectrum +3)"},
- {0x53a2, 0x1e, 0x2fae, "Lancelot, pt. 2 GD (Amstrad CPC/Spectrum +3)"},
- {0x5914, 0x22, 0x4a31, "Lancelot, pt. 3 GD (Amstrad CPC/Spectrum +3)"},
-
- {0x5a38, 0xf7, 0x876e, "Ingrid's Back, pt. 1 GD (Amstrad CPC/Spectrum +3)"},
- {0x531a, 0xed, 0xcf3f, "Ingrid's Back, pt. 2 GD (Amstrad CPC/Spectrum +3)"},
- {0x57e4, 0x19, 0xb354, "Ingrid's Back, pt. 3 GD (Amstrad CPC/Spectrum +3)"},
-
- {0x5cbc, 0xa5, 0x0dbe, "Scapeghost, pt. 1 GD (Amstrad CPC/Spectrum +3)"},
- {0x5932, 0x4e, 0xb2f5, "Scapeghost, pt. 2 GD (Amstrad CPC/Spectrum +3)"},
- {0x5860, 0x95, 0x3227, "Scapeghost, pt. 3 GD (Amstrad CPC/Spectrum +3)"},
-
- {0x74e0, 0x92, 0x885e, "Knight Orc, pt. 1 GD (Spectrum 128)"},
- {0x6dbc, 0x97, 0x6f55, "Knight Orc, pt. 2 GD (Spectrum 128)"},
- {0x7402, 0x07, 0x385f, "Knight Orc, pt. 3 GD (Spectrum 128)"},
-
- {0x52aa, 0xdf, 0x7b5b, "Gnome Ranger, pt. 1 GD (Spectrum 128)"},
- {0x6ffa, 0xdb, 0xdde2, "Gnome Ranger, pt. 2 GD (Spectrum 128)"},
- {0x723a, 0x69, 0x039b, "Gnome Ranger, pt. 3 GD (Spectrum 128)"},
-
- {0x6f1e, 0xda, 0x2ce0, "Lords of Time /T&M GD (Spectrum 128)"},
- {0x6da0, 0xb8, 0x3802, "Red Moon /T&M GD (Spectrum 128)"},
- {0x6108, 0xdd, 0xefe7, "Price of Magik /T&M GD (Spectrum 128)"},
-
- {0x768c, 0xe8, 0x8fc6, "Lancelot, pt. 1 GD (Spectrum 128)"},
- {0x76b0, 0x1d, 0x0fcd, "Lancelot, pt. 2 GD (Spectrum 128)"},
- {0x765e, 0x4f, 0x3b73, "Lancelot, pt. 3 GD (Spectrum 128)"},
-
- {0x76a0, 0x3a, 0xb803, "Ingrid's Back, pt. 1 GD (Spectrum 128)"},
- {0x7674, 0x0b, 0xe92f, "Ingrid's Back, pt. 2 GD (Spectrum 128)"},
- {0x765e, 0xba, 0x086d, "Ingrid's Back, pt. 3 GD (Spectrum 128)"},
-
- {0x762e, 0x82, 0x8848, "Scapeghost, pt. 1 GD (Spectrum 128)"},
- {0x5bd6, 0x35, 0x79ef, "Scapeghost, pt. 2 GD (Spectrum 128)"},
- {0x6fa8, 0xa4, 0x62c2, "Scapeghost, pt. 3 GD (Spectrum 128)"},
-
- {0xbb93, 0x36, 0x6a05, "Knight Orc, pt. 1 (Amiga)"},
- {0xbb6e, 0xad, 0x4d40, "Knight Orc, pt. 1 (ST)"},
- {0xc58e, 0x4a, 0x4e9d, "Knight Orc, pt. 2 (Amiga/ST)"},
- {0xcb9a, 0x0f, 0x0804, "Knight Orc, pt. 3 (Amiga/ST)"},
-
- {0xbb6e, 0xa6, 0x9753, "Knight Orc, pt. 1 (PC)"},
- {0xc58e, 0x43, 0xe9ce, "Knight Orc, pt. 2 (PC)"},
- {0xcb9a, 0x08, 0x6c36, "Knight Orc, pt. 3 (PC)"},
-
- {0x898a, 0x43, 0xfc8b, "Knight Orc, pt. 1 (Apple ][)"},
- {0x8b9f, 0x61, 0x7288, "Knight Orc, pt. 2 (Apple ][)"},
- {0x8af9, 0x61, 0x7542, "Knight Orc, pt. 3 (Apple ][)"},
-
- {0x8970, 0x6b, 0x3c7b, "Knight Orc, pt. 1 (Commodore 64 Gfx)"},
- {0x8b90, 0x4e, 0x098c, "Knight Orc, pt. 2 (Commodore 64 Gfx)"},
- {0x8aea, 0x4e, 0xca54, "Knight Orc, pt. 3 (Commodore 64 Gfx)"},
-
- {0x86d0, 0xb7, 0xadbd, "Knight Orc, pt. 1 (Spectrum 48)"},
- {0x8885, 0x22, 0xe293, "Knight Orc, pt. 2 (Spectrum 48)"},
- {0x87e5, 0x0e, 0xdc33, "Knight Orc, pt. 3 (Spectrum 48)"},
-
- {0xb1a9, 0x80, 0x5fb7, "Gnome Ranger, pt. 1 (Amiga/ST)"},
- {0xab9d, 0x31, 0xbe6d, "Gnome Ranger, pt. 2 (Amiga/ST)"},
- {0xae28, 0x87, 0xb6b6, "Gnome Ranger, pt. 3 (Amiga/ST)"},
-
- {0xb0ec, 0xc2, 0x0053, "Gnome Ranger, pt. 1 (ST[v1])"},
- {0xaf82, 0x83, 0x19f7, "Gnome Ranger, pt. 2 (ST[v1])"},
-
- {0xb1aa, 0xad, 0xaf47, "Gnome Ranger, pt. 1 (PC)"},
- {0xb19e, 0x92, 0x8f96, "Gnome Ranger, pt. 1 (ST[v2])"},
- {0xab8b, 0xbf, 0x31e6, "Gnome Ranger, pt. 2 (PC/ST[v2])"},
- {0xae16, 0x81, 0x8741, "Gnome Ranger, pt. 3 (PC/ST[v2])"},
-
- {0xad41, 0xa8, 0x42c5, "Gnome Ranger, pt. 1 (Commodore 64 TO)"},
- {0xa735, 0xf7, 0x2e08, "Gnome Ranger, pt. 2 (Commodore 64 TO)"},
- {0xa9c0, 0x9e, 0x0d70, "Gnome Ranger, pt. 3 (Commodore 64 TO)"},
-
- {0x908e, 0x0d, 0x58a7, "Gnome Ranger, pt. 1 (Commodore 64 Gfx)"},
- {0x8f6f, 0x0a, 0x411a, "Gnome Ranger, pt. 2 (Commodore 64 Gfx)"},
- {0x9060, 0xbb, 0xe75d, "Gnome Ranger, pt. 3 (Commodore 64 Gfx)"},
-
- {0x8aab, 0xc0, 0xde5f, "Gnome Ranger, pt. 1 (Spectrum 48)"},
- {0x8ac8, 0x9a, 0xc89b, "Gnome Ranger, pt. 2 (Spectrum 48)"},
- {0x8a93, 0x4f, 0x10cc, "Gnome Ranger, pt. 3 (Spectrum 48)"},
-
- {0xb57c, 0x44, 0x7779, "Lords of Time /T&M (PC)"},
- {0xa69e, 0x6c, 0xb268, "Red Moon /T&M (PC)"},
- {0xbac7, 0x7f, 0xddb2, "Price of Magik /T&M (PC)"},
-
- {0xb579, 0x89, 0x3e89, "Lords of Time /T&M (ST)"},
- {0xa698, 0x41, 0xcaca, "Red Moon /T&M (ST)"},
- {0xbac4, 0x80, 0xa750, "Price of Magik /T&M (ST)"},
-
- {0xb576, 0x2a, 0x7239, "Lords of Time /T&M (Amiga)"},
- {0xa692, 0xd1, 0x6a99, "Red Moon /T&M (Amiga)"},
- {0xbaca, 0x3a, 0x221b, "Price of Magik /T&M (Amiga)"},
-
- {0xb563, 0x6a, 0x0c5c, "Lords of Time /T&M (Mac)"},
- {0xa67c, 0xb8, 0xff41, "Red Moon /T&M (Mac)"},
- {0xbab2, 0x87, 0x09f5, "Price of Magik /T&M (Mac)"},
-
- {0xb38c, 0x37, 0x9f8e, "Lords of Time /T&M (Commodore 64 TO)"},
- {0xa4e2, 0xa6, 0x016d, "Red Moon /T&M (Commodore 64 TO)"},
- {0xb451, 0xa8, 0x2682, "Price of Magik /T&M (Commodore 64 TO)"},
-
- {0x9070, 0x43, 0x45d4, "Lords of Time /T&M (Commodore 64 Gfx)"},
- {0x903f, 0x6b, 0x603e, "Red Moon /T&M (Commodore 64 Gfx)"},
- {0x8f51, 0xb2, 0x6c9a, "Price of Magik /T&M (Commodore 64 Gfx)"},
-
- {0x8950, 0xa1, 0xbb16, "Lords of Time /T&M (Spectrum 48)"},
- {0x8813, 0x11, 0x22de, "Red Moon /T&M (Spectrum 48)"},
- {0x8a60, 0x2a, 0x29ed, "Price of Magik /T&M (Spectrum 48)"},
-
- {0xb260, 0xe5, 0xc5b2, "Lords of Time /T&M (PC/ST *USA*)"},
- {0xa3a4, 0xdf, 0x6732, "Red Moon /T&M (PC/ST *USA*)"},
- {0xb7a0, 0x7e, 0x2226, "Price of Magik /T&M (PC/ST *USA*)"},
-
- {0xb257, 0xf8, 0xfbd5, "Lords of Time /T&M (Amiga *USA*)"},
- {0xa398, 0x82, 0xd031, "Red Moon /T&M (Amiga *USA*)"},
- {0xb797, 0x1f, 0x84a9, "Price of Magik /T&M (Amiga *USA*)"},
-
- {0x8d78, 0x3a, 0xba6e, "Lords of Time /T&M (Commodore 64 Gfx *USA*)"},
- {0x8d56, 0xd3, 0x146a, "Red Moon /T&M (Commodore 64 Gfx *USA*)"},
- {0x8c46, 0xf0, 0xcaf6, "Price of Magik /T&M (Commodore 64 Gfx *USA*)"},
-
- {0xc0cf, 0x4e, 0xb7fa, "Lancelot, pt. 1 (Amiga/PC/ST)"},
- {0xd5e9, 0x6a, 0x4192, "Lancelot, pt. 2 (Amiga/PC/ST)"},
- {0xbb8f, 0x1a, 0x7487, "Lancelot, pt. 3 (Amiga/PC/ST)"},
-
- {0xc0bd, 0x57, 0x6ef1, "Lancelot, pt. 1 (Mac)"},
- {0xd5d7, 0x99, 0x770b, "Lancelot, pt. 2 (Mac)"},
- {0xbb7d, 0x17, 0xbc42, "Lancelot, pt. 3 (Mac)"},
-
- {0xb4c9, 0x94, 0xd784, "Lancelot, pt. 1 (Commodore 64 TO)"},
- {0xb729, 0x51, 0x8ee5, "Lancelot, pt. 2 (Commodore 64 TO)"},
- {0xb702, 0xe4, 0x1809, "Lancelot, pt. 3 (Commodore 64 TO)"},
-
- {0x8feb, 0xba, 0xa800, "Lancelot, pt. 1 (Commodore 64 Gfx)"},
- {0x8f6b, 0xfa, 0x0f7e, "Lancelot, pt. 2 (Commodore 64 Gfx)"},
- {0x8f71, 0x2f, 0x0ddc, "Lancelot, pt. 3 (Commodore 64 Gfx)"},
-
- {0x8ade, 0xf2, 0xfffb, "Lancelot, pt. 1 (Spectrum 48)"},
- {0x8b0e, 0xfb, 0x0bab, "Lancelot, pt. 2 (Spectrum 48)"},
- {0x8ab3, 0xc1, 0xcb62, "Lancelot, pt. 3 (Spectrum 48)"},
-
- {0xbba4, 0x94, 0x0871, "Lancelot, pt. 1 (Amiga/PC *USA*)"},
- {0xd0c0, 0x56, 0x8c48, "Lancelot, pt. 2 (Amiga/PC *USA*)"},
- {0xb6ac, 0xc6, 0xaea0, "Lancelot, pt. 3 (Amiga/PC *USA*)"},
-
- {0x8afc, 0x07, 0x8321, "Lancelot, pt. 1 (Commodore 64 Gfx *USA*)"},
- {0x8aec, 0x13, 0x6791, "Lancelot, pt. 2 (Commodore 64 Gfx *USA*)"},
- {0x8aba, 0x0d, 0x5602, "Lancelot, pt. 3 (Commodore 64 Gfx *USA*)"},
-
- {0xd19b, 0xad, 0x306d, "Ingrid's Back, pt. 1 (PC)"},
- {0xc5a5, 0xfe, 0x3c98, "Ingrid's Back, pt. 2 (PC)"},
- {0xd7ae, 0x9e, 0x1878, "Ingrid's Back, pt. 3 (PC)"},
-
- {0xd188, 0x13, 0xdc60, "Ingrid's Back, pt. 1 (Amiga)"},
- {0xc594, 0x03, 0xea95, "Ingrid's Back, pt. 2 (Amiga)"},
- {0xd79f, 0xb5, 0x1661, "Ingrid's Back, pt. 3 (Amiga)"},
-
- {0xd183, 0x83, 0xef72, "Ingrid's Back, pt. 1 (ST)"},
- {0xc58f, 0x65, 0xf337, "Ingrid's Back, pt. 2 (ST)"},
- {0xd79a, 0x57, 0x49c5, "Ingrid's Back, pt. 3 (ST)"},
-
- {0xb770, 0x03, 0x9a03, "Ingrid's Back, pt. 1 (Commodore 64 TO)"},
- {0xb741, 0xb6, 0x2aa5, "Ingrid's Back, pt. 2 (Commodore 64 TO)"},
- {0xb791, 0xa1, 0xd065, "Ingrid's Back, pt. 3 (Commodore 64 TO)"},
-
- {0x9089, 0xce, 0xc5e2, "Ingrid's Back, pt. 1 (Commodore 64 Gfx)"},
- {0x908d, 0x80, 0x30c7, "Ingrid's Back, pt. 2 (Commodore 64 Gfx)"},
- {0x909e, 0x9f, 0xdecc, "Ingrid's Back, pt. 3 (Commodore 64 Gfx)"},
-
- {0x8ab7, 0x68, 0xee57, "Ingrid's Back, pt. 1 (Spectrum 48)"},
- {0x8b1e, 0x84, 0x2538, "Ingrid's Back, pt. 2 (Spectrum 48)"},
- {0x8b1c, 0xa8, 0x9262, "Ingrid's Back, pt. 3 (Spectrum 48)"},
-
- {0xbeab, 0x2d, 0x94d9, "Scapeghost, pt. 1 (Amiga)"},
- {0xc132, 0x14, 0x7adc, "Scapeghost, pt. 1 (Amiga *bak*)"},
- {0xbe94, 0xcc, 0x04b8, "Scapeghost, pt. 1 (PC/ST)"},
- {0x99bd, 0x65, 0x032e, "Scapeghost, pt. 2 (Amiga/PC/ST)"},
- {0xbcb6, 0x7a, 0x7d4f, "Scapeghost, pt. 3 (Amiga/PC/ST)"},
-
- {0x9058, 0xcf, 0x9748, "Scapeghost, pt. 1 (Commodore 64 Gfx)"},
- {0x8f43, 0xc9, 0xeefd, "Scapeghost, pt. 2 (Commodore 64 Gfx)"},
- {0x90ac, 0x68, 0xb4a8, "Scapeghost, pt. 3 (Commodore 64 Gfx)"},
-
- {0x8a21, 0xf4, 0xd9e4, "Scapeghost, pt. 1 (Spectrum 48)"},
- {0x8a12, 0xe3, 0xc2ff, "Scapeghost, pt. 2 (Spectrum 48)"},
- {0x8a16, 0xcc, 0x4f3b, "Scapeghost, pt. 3 (Spectrum 48)"},
-
- {0x3ebb, 0x00, 0xf6dc, "Champion of the Raj (English) 1/2 GD (Amiga)"},
- {0x0fd8, 0x00, 0xf250, "Champion of the Raj (English) 2/2 GD (Amiga)"},
-
- {0x3e8f, 0x00, 0x5599, "Champion of the Raj (English) 1/2 GD (ST)"},
-
- {0x3e4f, 0x00, 0xb202, "Champion of the Raj (English) 1/2 GD (PC)"},
- {0x14a3, 0x00, 0xa288, "Champion of the Raj (English) 2/2 GD (PC)"},
-
- {0x1929, 0x00, 0xd4b2, "Champion of the Raj (demo), 1/2 GD (ST)"},
- {0x40e0, 0x02, 0x080d, "Champion of the Raj (demo), 2/2 GD (ST)"},
-
- {0x4872, 0x00, 0x9515, "Champion of the Raj (German) 1/2 GD (Amiga)"},
- {0x11f5, 0x00, 0xbf39, "Champion of the Raj (German) 2/2 GD (Amiga)"},
-
- {0x4846, 0x00, 0xd9c1, "Champion of the Raj (German) 1/2 GD (ST)"},
- {0x11f5, 0x00, 0x7aa4, "Champion of the Raj (German) 2/2 GD (ST)"},
-
- {0x110f, 0x00, 0x4b57, "Champion of the Raj (French) 2/2 GD (ST)"},
-
- {0x0000, 0x00, 0x0000, nullptr}
-};
-
-
-/*
- * The following patch database is obtained from L9cut's l9data_p.h, and
- * allows CRCs from patched games to be translated into original CRCs for
- * lookup in the game database above. Some file commentary has been removed
- * for brevity, and unused patch fields deleted.
- *
- * The version of l9data_p.h used is 012 (22 May 2001).
- */
-struct gln_patch_table_t {
- const gln_uint16 length; /* Datafile length in bytes */
- const gln_byte orig_checksum; /* 8-bit checksum, last datafile byte */
- const gln_uint16 orig_crc; /* 16-bit CRC, L9cut-internal */
- const gln_byte patch_checksum; /* 8-bit checksum, last datafile byte */
- const gln_uint16 patch_crc; /* 16-bit CRC, L9cut-internal */
-};
-typedef const gln_patch_table_t *gln_patch_tableref_t;
-
-static const gln_patch_table_t GLN_PATCH_TABLE[] = {
- /* Price of Magik (Spectrum128) */
- {0x7410, 0x5e, 0x60be, 0x70, 0x6cef},
-
- /* Price of Magik (Commodore 64) */
- {0x6fc6, 0x14, 0xf9b6, 0x26, 0x3326},
-
- /* Price of Magik (Spectrum48) */
- {0x5aa4, 0xc1, 0xeda4, 0xd3, 0xed35},
- {0x5aa4, 0xc1, 0xeda4, 0xc1, 0x8a65},
-
- /* Colossal Adventure /JoD (Amiga/PC) */
- {0x76f4, 0x5e, 0x1fe5, 0xea, 0x1305},
- {0x76f4, 0x5e, 0x1fe5, 0xb5, 0x901f},
- {0x76f4, 0x5e, 0x1fe5, 0x5e, 0x6ea1},
-
- /* Colossal Adventure /JoD (ST) */
- {0x76f4, 0x5a, 0xcf4b, 0xe6, 0x012a},
- {0x76f4, 0x5a, 0xcf4b, 0xb1, 0x40b1},
-
- /* Adventure Quest /JoD (Amiga/PC) */
- {0x6e60, 0x83, 0x18e0, 0x4c, 0xcfb0},
- {0x6e60, 0x83, 0x18e0, 0xfa, 0x9b3b},
- {0x6e60, 0x83, 0x18e0, 0x83, 0x303d},
-
- /* Adventure Quest /JoD (ST) */
- {0x6e5c, 0xf6, 0xd356, 0xbf, 0xede7},
- {0x6e5c, 0xf6, 0xd356, 0x6d, 0x662d},
-
- /* Dungeon Adventure /JoD (Amiga/PC/ST) */
- {0x6f0c, 0x95, 0x1f64, 0x6d, 0x2443},
- {0x6f0c, 0x95, 0x1f64, 0x0c, 0x6066},
- {0x6f0c, 0x95, 0x1f64, 0x96, 0xdaca},
- {0x6f0c, 0x95, 0x1f64, 0x95, 0x848d},
-
- /* Colossal Adventure /JoD (Spectrum128) */
- {0x6f6e, 0x78, 0x28cd, 0xf8, 0xda5f},
- {0x6f6e, 0x78, 0x28cd, 0x77, 0x5b4e},
-
- /* Adventure Quest /JoD (Spectrum128) */
- {0x6970, 0xd6, 0xa820, 0x3b, 0x1870},
- {0x6970, 0xd6, 0xa820, 0xd5, 0x13c4},
-
- /* Dungeon Adventure /JoD (Spectrum128) */
- {0x6de8, 0x4c, 0xd795, 0xa2, 0x3eea},
- {0x6de8, 0x4c, 0xd795, 0x4b, 0xad30},
-
- /* Colossal Adventure /JoD (Amstrad CPC) */
- {0x6f4d, 0xcb, 0xe8f2, 0x4b, 0xb384},
- {0x6f4d, 0xcb, 0xe8f2, 0xca, 0x96e7},
-
- /* Adventure Quest /JoD (Amstrad CPC) */
- {0x6968, 0x32, 0x0c01, 0x97, 0xdded},
- {0x6968, 0x32, 0x0c01, 0x31, 0xe8c2},
-
- /* Dungeon Adventure /JoD (Amstrad CPC) */
- {0x6dc0, 0x63, 0x5d95, 0xb9, 0xc963},
- {0x6dc0, 0x63, 0x5d95, 0x62, 0x79f7},
-
- /* Colossal Adventure /JoD (Commodore 64) */
- {0x6c8e, 0xb6, 0x9be3, 0x36, 0x6971},
- {0x6c8e, 0xb6, 0x9be3, 0xb5, 0xeba0},
-
- /* Adventure Quest /JoD (Commodore 64) */
- {0x63b6, 0x2e, 0xef38, 0x93, 0x4e68},
- {0x63b6, 0x2e, 0xef38, 0x2d, 0x54dc},
-
- /* Dungeon Adventure /JoD (Commodore 64) */
- {0x6bd2, 0x65, 0xa41f, 0xbb, 0x4260},
- {0x6bd2, 0x65, 0xa41f, 0x64, 0xdf5a},
-
- /* Colossal Adventure /JoD (Spectrum48) */
- {0x5a8e, 0xf2, 0x7cca, 0x72, 0x8e58},
- {0x5a8e, 0xf2, 0x7cca, 0xf1, 0x0c89},
- {0x5a8e, 0xf2, 0x7cca, 0xf2, 0x2c96},
-
- /* Adventure Quest /JoD (Spectrum48) */
- {0x5ace, 0x11, 0xdc12, 0x76, 0x8663},
- {0x5ace, 0x11, 0xdc12, 0x10, 0xa757},
- {0x5ace, 0x11, 0xdc12, 0x11, 0xf118},
-
- /* Dungeon Adventure /JoD (Spectrum48) */
- {0x58a3, 0x38, 0x8ce4, 0x8e, 0xb61a},
- {0x58a3, 0x38, 0x8ce4, 0x37, 0x34c0},
- {0x58a3, 0x38, 0x8ce4, 0x38, 0xa1ee},
-
- /* Snowball /SD (Amiga/ST) */
- {0x7b31, 0x6e, 0x2e2b, 0xe5, 0x6017},
-
- /* Return to Eden /SD (Amiga/ST) */
- {0x7d16, 0xe6, 0x5438, 0x5d, 0xc770},
-
- /* Worm in Paradise /SD (Amiga/ST) */
- {0x7cd9, 0x0c, 0x4df1, 0x83, 0xe997},
-
- /* Snowball /SD (PC/Spectrum128) */
- {0x7b2f, 0x70, 0x6955, 0xe7, 0x0af4},
- {0x7b2f, 0x70, 0x6955, 0x70, 0x1179},
-
- /* Return to Eden /SD (PC) */
- {0x7d14, 0xe8, 0xfbab, 0x5f, 0xeab9},
- {0x7d14, 0xe8, 0xfbab, 0xe8, 0xe216},
-
- /* Return to Eden /SD (Amstrad CPC) */
- {0x7cff, 0xf8, 0x6044, 0x6f, 0xbb57},
-
- /* Return to Eden /SD (Spectrum128) */
- {0x7c55, 0x18, 0xdaee, 0x8f, 0x01fd},
-
- /* Worm in Paradise /SD (Amstrad CPC/PC/Spectrum128) */
- {0x7cd7, 0x0e, 0x4feb, 0x85, 0x4eae},
- {0x7cd7, 0x0e, 0x4feb, 0x0e, 0xb02c},
-
- /* Snowball /SD (Commodore 64) */
- {0x7363, 0x65, 0xa0ab, 0xdc, 0xca6a},
-
- /* Return to Eden /SD (Commodore 64) */
- {0x772f, 0xca, 0x8602, 0x41, 0x9bd0},
-
- /* Worm in Paradise /SD (Commodore 64) */
- {0x788d, 0x72, 0x888a, 0xe9, 0x4cce},
-
- /* Snowball /SD (Atari) */
- {0x6bf8, 0x3f, 0xc9f7, 0x96, 0x1908},
-
- /* Return to Eden /SD (Atari) */
- {0x60f7, 0x68, 0xc2bc, 0xdf, 0xd3ae},
-
- /* Worm in Paradise /SD (Atari) */
- {0x6161, 0xf3, 0xe6d7, 0x6a, 0xe232},
-
- /* Snowball /SD (Spectrum48) */
- {0x6541, 0x02, 0x2e6c, 0x79, 0xb80c},
- {0x6541, 0x02, 0x2e6c, 0x02, 0x028a},
-
- /* Return to Eden /SD (Spectrum48) */
- {0x5f43, 0xca, 0x828c, 0x41, 0x9f5e},
- {0x5f43, 0xca, 0x828c, 0xca, 0x6e1b},
-
- /* Worm in Paradise /SD (Spectrum48) */
- {0x5ebb, 0xf1, 0x4dec, 0x68, 0x4909},
- {0x5ebb, 0xf1, 0x4dec, 0xf1, 0xcc1a},
-
- /* Knight Orc, pt. 1 (Amiga) */
- {0xbb93, 0x36, 0x6a05, 0xad, 0xe52d},
-
- /* Knight Orc, pt. 1 (ST) */
- {0xbb6e, 0xad, 0x4d40, 0x24, 0x3bcd},
-
- /* Knight Orc, pt. 2 (Amiga/ST) */
- {0xc58e, 0x4a, 0x4e9d, 0xc1, 0xe2bf},
-
- /* Knight Orc, pt. 3 (Amiga/ST) */
- {0xcb9a, 0x0f, 0x0804, 0x86, 0x6487},
-
- /* Knight Orc, pt. 1 (PC) */
- {0xbb6e, 0xa6, 0x9753, 0x1d, 0x2e7f},
- {0xbb6e, 0xa6, 0x9753, 0xa6, 0x001d},
-
- /* Knight Orc, pt. 2 (PC) */
- {0xc58e, 0x43, 0xe9ce, 0xba, 0x5e4c},
- {0xc58e, 0x43, 0xe9ce, 0x43, 0xa8f0},
-
- /* Knight Orc, pt. 3 (PC) */
- {0xcb9a, 0x08, 0x6c36, 0x7f, 0xf0d4},
- {0xcb9a, 0x08, 0x6c36, 0x08, 0x2d08},
-
- /* Knight Orc, pt. 1 (Commodore 64 Gfx) */
- {0x8970, 0x6b, 0x3c7b, 0xe2, 0xb6f3},
-
- /* Knight Orc, pt. 1 (Spectrum48) */
- {0x86d0, 0xb7, 0xadbd, 0x2e, 0x43e1},
-
- /* Gnome Ranger, pt. 1 (Amiga/ST) */
- {0xb1a9, 0x80, 0x5fb7, 0xf7, 0x5c6c},
-
- /* Gnome Ranger, pt. 2 (Amiga/ST) */
- {0xab9d, 0x31, 0xbe6d, 0xa8, 0xcb96},
-
- /* Gnome Ranger, pt. 3 (Amiga/ST) */
- {0xae28, 0x87, 0xb6b6, 0xfe, 0x760c},
-
- /* Gnome Ranger, pt. 1 (PC) */
- {0xb1aa, 0xad, 0xaf47, 0x24, 0x5cfd},
- {0xb1aa, 0xad, 0xaf47, 0xad, 0xe0ed},
-
- /* Gnome Ranger, pt. 1 (ST-var) */
- {0xb19e, 0x92, 0x8f96, 0x09, 0x798c},
-
- /* Gnome Ranger, pt. 2 (PC/ST-var) */
- {0xab8b, 0xbf, 0x31e6, 0x36, 0x811c},
- {0xab8b, 0xbf, 0x31e6, 0xbf, 0x8ff3},
-
- /* Gnome Ranger, pt. 3 (PC/ST-var) */
- {0xae16, 0x81, 0x8741, 0xf8, 0x47fb},
- {0xae16, 0x81, 0x8741, 0x81, 0xc8eb},
-
- /* Gnome Ranger, pt. 1 (Commodore 64 TO) */
- {0xad41, 0xa8, 0x42c5, 0x1f, 0x7d1e},
-
- /* Gnome Ranger, pt. 2 (Commodore 64 TO) */
- {0xa735, 0xf7, 0x2e08, 0x6e, 0x780e},
-
- /* Gnome Ranger, pt. 3 (Commodore 64 TO) */
- {0xa9c0, 0x9e, 0x0d70, 0x15, 0x3e6b},
-
- /* Gnome Ranger, pt. 1 (Commodore 64 Gfx) */
- {0x908e, 0x0d, 0x58a7, 0x84, 0xab1d},
-
- /* Gnome Ranger, pt. 2 (Commodore 64 Gfx) */
- {0x8f6f, 0x0a, 0x411a, 0x81, 0x12bc},
-
- /* Gnome Ranger, pt. 3 (Commodore 64 Gfx) */
- {0x9060, 0xbb, 0xe75d, 0x32, 0x14e7},
-
- /* Lords of Time /T&M (PC) */
- {0xb57c, 0x44, 0x7779, 0xbb, 0x31a6},
- {0xb57c, 0x44, 0x7779, 0x44, 0xea72},
-
- /* Red Moon /T&M (PC) */
- {0xa69e, 0x6c, 0xb268, 0xe3, 0x4cef},
- {0xa69e, 0x6c, 0xb268, 0x6c, 0x3799},
-
- /* Price of Magik /T&M (PC) */
- {0xbac7, 0x7f, 0xddb2, 0xf6, 0x6ab3},
- {0xbac7, 0x7f, 0xddb2, 0x7f, 0x905c},
-
- /* Lords of Time /T&M (ST) */
- {0xb579, 0x89, 0x3e89, 0x00, 0xa2b7},
-
- /* Red Moon /T&M (ST) */
- {0xa698, 0x41, 0xcaca, 0xb8, 0xeeac},
-
- /* Price of Magik /T&M (ST) */
- {0xbac4, 0x80, 0xa750, 0xf7, 0xe030},
-
- /* Lords of Time /T&M (Amiga) */
- {0xb576, 0x2a, 0x7239, 0xa1, 0x2ea6},
-
- /* Red Moon /T&M (Amiga) */
- {0xa692, 0xd1, 0x6a99, 0x48, 0x50ff},
-
- /* Price of Magik /T&M (Amiga) */
- {0xbaca, 0x3a, 0x221b, 0xb1, 0x55bb},
-
- /* Lords of Time /T&M (Commodore 64 TO) */
- {0xb38c, 0x37, 0x9f8e, 0xae, 0xc6b1},
-
- /* Red Moon /T&M (Commodore 64 TO) */
- {0xa4e2, 0xa6, 0x016d, 0x1d, 0x31ab},
-
- /* Price of Magik /T&M (Commodore 64 TO) */
- {0xb451, 0xa8, 0x2682, 0x1f, 0x5de2},
-
- /* Lords of Time /T&M (Commodore 64 Gfx) */
- {0x9070, 0x43, 0x45d4, 0xba, 0x02eb},
-
- /* Red Moon /T&M (Commodore 64 Gfx) */
- {0x903f, 0x6b, 0x603e, 0xe2, 0x9f59},
-
- /* Price of Magik /T&M (Commodore 64 Gfx) */
- {0x8f51, 0xb2, 0x6c9a, 0x29, 0xde3b},
-
- /* Lords of Time /T&M (Spectrum48) */
- {0x8950, 0xa1, 0xbb16, 0x18, 0x2828},
- {0x8950, 0xa1, 0xbb16, 0xa1, 0x1ea2},
-
- /* Red Moon /T&M (Spectrum48) */
- {0x8813, 0x11, 0x22de, 0x88, 0x18b8},
- {0x8813, 0x11, 0x22de, 0x11, 0xd0cd},
-
- /* Price of Magik /T&M (Spectrum48) */
- {0x8a60, 0x2a, 0x29ed, 0xa1, 0x5e4d},
-
- /* Lancelot, pt. 1 (Amiga/PC/ST) */
- {0xc0cf, 0x4e, 0xb7fa, 0xc5, 0x4400},
-
- /* Lancelot, pt. 2 (Amiga/PC/ST) */
- {0xd5e9, 0x6a, 0x4192, 0xe1, 0x3b1e},
-
- /* Lancelot, pt. 3 (Amiga/PC/ST) */
- {0xbb8f, 0x1a, 0x7487, 0x91, 0x877d},
-
- /* Lancelot, pt. 1 (Commodore 64 TO) */
- {0xb4c9, 0x94, 0xd784, 0x0b, 0x203e},
-
- /* Lancelot, pt. 2 (Commodore 64 TO) */
- {0xb729, 0x51, 0x8ee5, 0xc8, 0xf1c9},
-
- /* Lancelot, pt. 3 (Commodore 64 TO) */
- {0xb702, 0xe4, 0x1809, 0x5b, 0x25b2},
-
- /* Lancelot, pt. 1 (Commodore 64 Gfx) */
- {0x8feb, 0xba, 0xa800, 0x31, 0x5bfa},
-
- /* Lancelot, pt. 2 (Commodore 64 Gfx) */
- {0x8f6b, 0xfa, 0x0f7e, 0x71, 0x75f2},
-
- /* Lancelot, pt. 3 (Commodore 64 Gfx) */
- {0x8f71, 0x2f, 0x0ddc, 0xa6, 0x3e87},
-
- /* Ingrid's Back, pt. 1 (PC) */
- {0xd19b, 0xad, 0x306d, 0x24, 0x4504},
- {0xd19b, 0xad, 0x306d, 0xad, 0x878e},
-
- /* Ingrid's Back, pt. 2 (PC) */
- {0xc5a5, 0xfe, 0x3c98, 0x75, 0x8950},
- {0xc5a5, 0xfe, 0x3c98, 0xfe, 0x8b7b},
-
- /* Ingrid's Back, pt. 3 (PC) */
- {0xd7ae, 0x9e, 0x1878, 0x15, 0xadb0},
- {0xd7ae, 0x9e, 0x1878, 0x9e, 0xaf9b},
-
- /* Ingrid's Back, pt. 1 (Amiga) */
- {0xd188, 0x13, 0xdc60, 0x8a, 0x755c},
-
- /* Ingrid's Back, pt. 2 (Amiga) */
- {0xc594, 0x03, 0xea95, 0x7a, 0xb5a8},
-
- /* Ingrid's Back, pt. 3 (Amiga) */
- {0xd79f, 0xb5, 0x1661, 0x2c, 0xbf5d},
-
- /* Ingrid's Back, pt. 1 (ST) */
- {0xd183, 0x83, 0xef72, 0xfa, 0xb04f},
-
- /* Ingrid's Back, pt. 2 (ST) */
- {0xc58f, 0x65, 0xf337, 0xdc, 0x900a},
-
- /* Ingrid's Back, pt. 3 (ST) */
- {0xd79a, 0x57, 0x49c5, 0xce, 0xe0f9},
-
- /* Ingrid's Back, pt. 1 (Commodore 64 TO) */
- {0xb770, 0x03, 0x9a03, 0x7a, 0xdc6a},
-
- /* Ingrid's Back, pt. 2 (Commodore 64 TO) */
- {0xb741, 0xb6, 0x2aa5, 0x2d, 0x5a6c},
-
- /* Ingrid's Back, pt. 3 (Commodore 64 TO) */
- {0xb791, 0xa1, 0xd065, 0x18, 0xaa0c},
-
- /* Ingrid's Back, pt. 1 (Commodore 64 Gfx) */
- {0x9089, 0xce, 0xc5e2, 0x44, 0xeff4},
-
- /* Ingrid's Back, pt. 2 (Commodore 64 Gfx) */
- {0x908d, 0x80, 0x30c7, 0xf6, 0x2a11},
-
- /* Ingrid's Back, pt. 3 (Commodore 64 Gfx) */
- {0x909e, 0x9f, 0xdecc, 0x15, 0xf4da},
-
- {0x0000, 0x00, 0x0000, 0x00, 0x0000},
-};
-
-
-/*
- * gln_gameid_lookup_game()
- * gln_gameid_lookup_patch()
- *
- * Look up and return game table and patch table entries given a game's
- * length, checksum, and CRC. Returns the entry, or nullptr if not found.
- */
-static gln_game_tableref_t gln_gameid_lookup_game(gln_uint16 length, gln_byte checksum,
- gln_uint16 crc, int ignore_crc) {
- gln_game_tableref_t game;
-
- for (game = GLN_GAME_TABLE; game->length; game++) {
- if (game->length == length && game->checksum == checksum
- && (ignore_crc || game->crc == crc))
- break;
- }
-
- return game->length ? game : nullptr;
-}
-
-static gln_patch_tableref_t gln_gameid_lookup_patch(gln_uint16 length, gln_byte checksum,
- gln_uint16 crc) {
- gln_patch_tableref_t patch;
-
- for (patch = GLN_PATCH_TABLE; patch->length; patch++) {
- if (patch->length == length && patch->patch_checksum == checksum
- && patch->patch_crc == crc)
- break;
- }
-
- return patch->length ? patch : nullptr;
-}
-
-
-/*
- * gln_gameid_identify_game()
- *
- * Identify a game from its data length, checksum, and CRC. Returns the
- * entry of the game in the game table, or nullptr if not found.
- *
- * This function uses startdata and FileSize from the core interpreter.
- * These aren't advertised symbols, so be warned.
- */
-static gln_game_tableref_t gln_gameid_identify_game() {
- gln_uint16 length, crc;
- gln_byte checksum;
- int is_version2;
- gln_game_tableref_t game;
- gln_patch_tableref_t patch;
-
- /* If the data file appears too short for a header, give up now. */
- if (FileSize < 30)
- return nullptr;
-
- /*
- * Find the version of the game, and the length of game data. This logic
- * is taken from L9cut, with calcword() replaced by simple byte comparisons.
- * If the length exceeds the available data, fail.
- */
- assert(startdata);
- is_version2 = startdata[4] == 0x20 && startdata[5] == 0x00
- && startdata[10] == 0x00 && startdata[11] == 0x80
- && startdata[20] == startdata[22]
- && startdata[21] == startdata[23];
-
- length = is_version2
- ? startdata[28] | startdata[29] << BITS_PER_CHAR
- : startdata[0] | startdata[1] << BITS_PER_CHAR;
- if (length >= FileSize)
- return nullptr;
-
- /* Calculate or retrieve the checksum, in a version specific way. */
- if (is_version2) {
- int index;
-
- checksum = 0;
- for (index = 0; index < length + 1; index++)
- checksum += startdata[index];
- } else
- checksum = startdata[length];
-
- /*
- * Generate a CRC for this data. When L9cut calculates a CRC, it's using a
- * copy taken up to length + 1 and then padded with two NUL bytes, so we
- * mimic that here.
- */
- crc = gln_get_buffer_crc(startdata, length + 1, 2);
-
- /*
- * See if this is a patched file. If it is, look up the game based on the
- * original CRC and checksum. If not, use the current CRC and checksum.
- */
- patch = gln_gameid_lookup_patch(length, checksum, crc);
- game = gln_gameid_lookup_game(length,
- patch ? patch->orig_checksum : checksum,
- patch ? patch->orig_crc : crc,
- FALSE);
-
- /* If no game identified, retry without the CRC. This is guesswork. */
- if (!game)
- game = gln_gameid_lookup_game(length, checksum, crc, TRUE);
-
- return game;
-}
-
-
-/*
- * gln_gameid_get_game_name()
- *
- * Return the name of the game, or nullptr if not identifiable.
- *
- * This function uses startdata from the core interpreter. This isn't an
- * advertised symbol, so be warned.
- */
-static const char *gln_gameid_get_game_name() {
- /*
- * If no game name yet known, attempt to identify the game. If it can't
- * be identified, set the cached game name to "" -- this special value
- * indicates that the game is an unknown one, but suppresses repeated
- * attempts to identify it on successive calls.
- */
- if (!gln_gameid_game_name) {
- gln_game_tableref_t game;
-
- /*
- * If the interpreter hasn't yet loaded a game, startdata is nullptr
- * (uninitialized, global). In this case, we return nullptr, allowing
- * for retries until a game is loaded.
- */
- if (!startdata)
- return nullptr;
-
- game = gln_gameid_identify_game();
- gln_gameid_game_name = game ? game->name : "";
- }
-
- /* Return the game's name, or nullptr if it was unidentifiable. */
- assert(gln_gameid_game_name);
- return strlen(gln_gameid_game_name) > 0 ? gln_gameid_game_name : nullptr;
-}
-
-
-/*
- * gln_gameid_game_name_reset()
- *
- * Clear the saved game name, forcing a new lookup when next queried. This
- * function should be called by actions that may cause the interpreter to
- * change game file, for example os_set_filenumber().
- */
-static void gln_gameid_game_name_reset() {
- gln_gameid_game_name = nullptr;
-}
-
-
-/*---------------------------------------------------------------------*/
/* Glk port bitmap picture functions */
/*---------------------------------------------------------------------*/
@@ -3307,7 +2288,7 @@ static void gln_status_update() {
* Try to establish a game identity to display; if none, use a standard
* message instead.
*/
- game_name = gln_gameid_get_game_name();
+ game_name = g_vm->_detection._gameName;
g_vm->glk_put_string(game_name ? game_name : "ScummVM GLK Level 9 Game");
g_vm->glk_set_window(gln_main_window);
@@ -3333,7 +2314,7 @@ static void gln_status_print() {
const char *game_name;
/* Get the current game name, and do nothing if none available. */
- game_name = gln_gameid_get_game_name();
+ game_name = g_vm->_detection._gameName;
if (game_name) {
gln_uint16 new_crc;
@@ -3341,7 +2322,7 @@ static void gln_status_print() {
* If not the first call and the game identity string has not changed,
* again, do nothing.
*/
- new_crc = gln_get_buffer_crc(game_name, strlen(game_name), 0);
+ new_crc = g_vm->_detection.gln_get_buffer_crc(game_name, strlen(game_name));
if (!is_initialized || new_crc != crc) {
int index;
@@ -5445,7 +4426,7 @@ gln_bool os_get_game_file(char *newname, int size) {
}
/* Encourage game name re-lookup, and return success. */
- gln_gameid_game_name_reset();
+ g_vm->_detection.gln_gameid_game_name_reset();
gln_watchdog_tick();
return TRUE;
}
@@ -5502,7 +4483,7 @@ void os_set_filenumber(char *newname, int size, int file_number) {
gln_standout_string("\n\n");
/* Encourage game name re-lookup, and return. */
- gln_gameid_game_name_reset();
+ g_vm->_detection.gln_gameid_game_name_reset();
gln_watchdog_tick();
}
@@ -5788,7 +4769,7 @@ void gln_main(const char *filename) {
* of the game. So we have to encourage a re-lookup of the game name
* at this point.
*/
- gln_gameid_game_name_reset();
+ g_vm->_detection.gln_gameid_game_name_reset();
/* Load the game, sending in any established graphics file. */
int errNum = 0;
diff --git a/engines/glk/level9/os_glk.h b/engines/glk/level9/os_glk.h
index a10a640..d617fb3 100644
--- a/engines/glk/level9/os_glk.h
+++ b/engines/glk/level9/os_glk.h
@@ -26,6 +26,9 @@
namespace Glk {
namespace Level9 {
+#define BYTE_MAX 0xff
+#define BITS_PER_CHAR 8
+
extern bool gln_graphics_enabled;
extern bool gln_graphics_possible;
Commit: 51471940a9043cfd9a0b1c0846490f556de283c0
https://github.com/scummvm/scummvm/commit/51471940a9043cfd9a0b1c0846490f556de283c0
Author: Paul Gilbert (dreammaster at scummvm.org)
Date: 2019-10-26T11:13:30-07:00
Commit Message:
GLK: LEVEL9: Move game scanner code to new Scanner class
Changed paths:
engines/glk/level9/detection.cpp
engines/glk/level9/detection.h
engines/glk/level9/detection_tables.h
engines/glk/level9/level9_main.cpp
diff --git a/engines/glk/level9/detection.cpp b/engines/glk/level9/detection.cpp
index 2648db8..3b3228e 100644
--- a/engines/glk/level9/detection.cpp
+++ b/engines/glk/level9/detection.cpp
@@ -22,6 +22,7 @@
#include "glk/level9/detection.h"
#include "glk/level9/detection_tables.h"
+#include "glk/level9/level9_main.h"
#include "glk/level9/os_glk.h"
#include "glk/blorb.h"
#include "common/debug.h"
@@ -32,6 +33,486 @@
namespace Glk {
namespace Level9 {
+long Scanner::scanner(byte *startFile, uint32 size, byte **dictData, byte **aCodePtr) {
+ _dictData = dictData;
+ _aCodePtr = aCodePtr;
+
+#ifdef FULLSCAN
+ FullScan(startfile, FileSize);
+#endif
+
+ int offset = scan(startFile, size);
+ if (offset < 0) {
+ offset = ScanV2(startFile, size);
+ _gameType = L9_V2;
+ if (offset < 0) {
+ offset = ScanV1(startFile, size);
+ _gameType = L9_V1;
+ if (offset < 0) {
+ return -1;
+ }
+ }
+ }
+
+ return offset;
+}
+
+const L9V1GameInfo &Scanner::v1Game() const {
+ assert(_gameType == L9_V1);
+ return L9_V1_GAMES[_l9V1Game];
+}
+
+long Scanner::scan(byte *StartFile, uint32 size) {
+ byte *Chk = (byte *)malloc(size + 1);
+ byte *Image = (byte *)calloc(size, 1);
+ uint32 i, num, Size, MaxSize = 0;
+ int j;
+ uint16 d0 = 0, l9, md, ml, dd, dl;
+ uint32 Min, Max;
+ long Offset = -1;
+ bool JumpKill, DriverV4;
+
+ if ((Chk == nullptr) || (Image == nullptr)) {
+ error("Unable to allocate memory for game scan! Exiting...");
+ }
+
+ Chk[0] = 0;
+ for (i = 1; i <= size; i++)
+ Chk[i] = Chk[i - 1] + StartFile[i - 1];
+
+ for (i = 0; i < size - 33; i++) {
+ num = L9WORD(StartFile + i) + 1;
+ /*
+ Chk[i] = 0 +...+ i-1
+ Chk[i+n] = 0 +...+ i+n-1
+ Chk[i+n] - Chk[i] = i + ... + i+n
+ */
+ if (num > 0x2000 && i + num <= size && Chk[i + num] == Chk[i]) {
+ md = L9WORD(StartFile + i + 0x2);
+ ml = L9WORD(StartFile + i + 0x4);
+ dd = L9WORD(StartFile + i + 0xa);
+ dl = L9WORD(StartFile + i + 0xc);
+
+ if (ml > 0 && md > 0 && i + md + ml <= size && dd > 0 && dl > 0 && i + dd + dl * 4 <= size) {
+ /* v4 files may have acodeptr in 8000-9000, need to fix */
+ for (j = 0; j < 12; j++) {
+ d0 = L9WORD(StartFile + i + 0x12 + j * 2);
+ if (j != 11 && d0 >= 0x8000 && d0 < 0x9000) {
+ if (d0 >= 0x8000 + LISTAREASIZE) break;
+ } else if (i + d0 > size) break;
+ }
+ /* list9 ptr must be in listarea, acode ptr in data */
+ if (j < 12 /*|| (d0>=0x8000 && d0<0x9000)*/) continue;
+
+ l9 = L9WORD(StartFile + i + 0x12 + 10 * 2);
+ if (l9 < 0x8000 || l9 >= 0x8000 + LISTAREASIZE) continue;
+
+ Size = 0;
+ Min = Max = i + d0;
+ DriverV4 = 0;
+ if (ValidateSequence(StartFile, Image, i + d0, i + d0, &Size, size, &Min, &Max, false, &JumpKill, &DriverV4)) {
+ if (Size > MaxSize && Size > 100) {
+ Offset = i;
+ MaxSize = Size;
+ _gameType = DriverV4 ? L9_V4 : L9_V3;
+ }
+ }
+ }
+ }
+ }
+
+ free(Chk);
+ free(Image);
+ return Offset;
+}
+
+long Scanner::ScanV2(byte *StartFile, uint32 size) {
+ byte *Chk = (byte *)malloc(size + 1);
+ byte *Image = (byte *)calloc(size, 1);
+ uint32 i, Size, MaxSize = 0, num;
+ int j;
+ uint16 d0 = 0, l9;
+ uint32 Min, Max;
+ long Offset = -1;
+ bool JumpKill;
+
+ if ((Chk == nullptr) || (Image == nullptr)) {
+ error("Unable to allocate memory for game scan! Exiting...");
+ }
+
+ Chk[0] = 0;
+ for (i = 1; i <= size; i++)
+ Chk[i] = Chk[i - 1] + StartFile[i - 1];
+
+ for (i = 0; i < size - 28; i++) {
+ num = L9WORD(StartFile + i + 28) + 1;
+ if (i + num <= size && ((Chk[i + num] - Chk[i + 32]) & 0xff) == StartFile[i + 0x1e]) {
+ for (j = 0; j < 14; j++) {
+ d0 = L9WORD(StartFile + i + j * 2);
+ if (j != 13 && d0 >= 0x8000 && d0 < 0x9000) {
+ if (d0 >= 0x8000 + LISTAREASIZE) break;
+ } else if (i + d0 > size) break;
+ }
+ /* list9 ptr must be in listarea, acode ptr in data */
+ if (j < 14 /*|| (d0>=0x8000 && d0<0x9000)*/) continue;
+
+ l9 = L9WORD(StartFile + i + 6 + 9 * 2);
+ if (l9 < 0x8000 || l9 >= 0x8000 + LISTAREASIZE) continue;
+
+ Size = 0;
+ Min = Max = i + d0;
+ if (ValidateSequence(StartFile, Image, i + d0, i + d0, &Size, size, &Min, &Max, false, &JumpKill, nullptr)) {
+#ifdef L9DEBUG
+ printf("Found valid V2 header at %ld, code size %ld", i, Size);
+#endif
+ if (Size > MaxSize && Size > 100) {
+ Offset = i;
+ MaxSize = Size;
+ }
+ }
+ }
+ }
+ free(Chk);
+ free(Image);
+ return Offset;
+}
+
+long Scanner::ScanV1(byte *StartFile, uint32 size) {
+ byte *Image = (byte *)calloc(size, 1);
+ uint32 i, Size;
+ int Replace;
+ byte *ImagePtr;
+ long MaxPos = -1;
+ uint32 MaxCount = 0;
+ uint32 Min, Max; //, MaxMax, MaxMin;
+ bool JumpKill; // , MaxJK;
+
+ int dictOff1 = 0, dictOff2 = 0;
+ byte dictVal1 = 0xff, dictVal2 = 0xff;
+
+ if (Image == nullptr) {
+ error("Unable to allocate memory for game scan! Exiting...");
+ }
+
+ for (i = 0; i < size; i++) {
+ if ((StartFile[i] == 0 && StartFile[i + 1] == 6) || (StartFile[i] == 32 && StartFile[i + 1] == 4)) {
+ Size = 0;
+ Min = Max = i;
+ Replace = 0;
+ if (ValidateSequence(StartFile, Image, i, i, &Size, size, &Min, &Max, false, &JumpKill, nullptr)) {
+ if (Size > MaxCount && Size > 100 && Size < 10000) {
+ MaxCount = Size;
+ //MaxMin = Min;
+ //MaxMax = Max;
+
+ MaxPos = i;
+ //MaxJK = JumpKill;
+ }
+ Replace = 0;
+ }
+ for (ImagePtr = Image + Min; ImagePtr <= Image + Max; ImagePtr++) {
+ if (*ImagePtr == 2)
+ *ImagePtr = Replace;
+ }
+ }
+ }
+
+ /* V1 dictionary detection from L9Cut by Paul David Doherty */
+ for (i = 0; i < size - 20; i++) {
+ if (StartFile[i] == 'A') {
+ if (StartFile[i + 1] == 'T' && StartFile[i + 2] == 'T' && StartFile[i + 3] == 'A' && StartFile[i + 4] == 'C' && StartFile[i + 5] == 0xcb) {
+ dictOff1 = i;
+ dictVal1 = StartFile[dictOff1 + 6];
+ break;
+ }
+ }
+ }
+ for (i = dictOff1; i < size - 20; i++) {
+ if (StartFile[i] == 'B') {
+ if (StartFile[i + 1] == 'U' && StartFile[i + 2] == 'N' && StartFile[i + 3] == 'C' && StartFile[i + 4] == 0xc8) {
+ dictOff2 = i;
+ dictVal2 = StartFile[dictOff2 + 5];
+ break;
+ }
+ }
+ }
+ _l9V1Game = -1;
+ if (_dictData && (dictVal1 != 0xff || dictVal2 != 0xff)) {
+ for (i = 0; i < sizeof L9_V1_GAMES / sizeof L9_V1_GAMES[0]; i++) {
+ if ((L9_V1_GAMES[i].dictVal1 == dictVal1) && (L9_V1_GAMES[i].dictVal2 == dictVal2)) {
+ _l9V1Game = i;
+ (*_dictData) = StartFile + dictOff1 - L9_V1_GAMES[i].dictStart;
+ }
+ }
+ }
+
+ free(Image);
+
+ if (MaxPos > 0 && _aCodePtr) {
+ (*_aCodePtr) = StartFile + MaxPos;
+ return 0;
+ }
+
+ return -1;
+}
+
+bool Scanner::ValidateSequence(byte *Base, byte *Image, uint32 iPos, uint32 acode, uint32 *Size, uint32 size, uint32 *Min, uint32 *Max, bool Rts, bool *JumpKill, bool *DriverV4) {
+ uint32 Pos;
+ bool Finished = false, Valid;
+ uint32 Strange = 0;
+ int ScanCodeMask;
+ int Code;
+ *JumpKill = false;
+
+ if (iPos >= size)
+ return false;
+ Pos = iPos;
+ if (Pos < *Min) *Min = Pos;
+
+ if (Image[Pos]) return true; /* hit valid code */
+
+ do {
+ Code = Base[Pos];
+ Valid = true;
+ if (Image[Pos]) break; /* converged to found code */
+ Image[Pos++] = 2;
+ if (Pos > *Max) *Max = Pos;
+
+ ScanCodeMask = 0x9f;
+ if (Code & 0x80) {
+ ScanCodeMask = 0xff;
+ if ((Code & 0x1f) > 0xa)
+ Valid = false;
+ Pos += 2;
+ }
+ else switch (Code & 0x1f) {
+ case 0: { /* goto */
+ uint32 Val = scangetaddr(Code, Base, &Pos, acode, &ScanCodeMask);
+ Valid = ValidateSequence(Base, Image, Val, acode, Size, size, Min, Max, true/*Rts*/, JumpKill, DriverV4);
+ Finished = true;
+ break;
+ }
+ case 1: { /* intgosub */
+ uint32 Val = scangetaddr(Code, Base, &Pos, acode, &ScanCodeMask);
+ Valid = ValidateSequence(Base, Image, Val, acode, Size, size, Min, Max, true, JumpKill, DriverV4);
+ break;
+ }
+ case 2: /* intreturn */
+ Valid = Rts;
+ Finished = true;
+ break;
+ case 3: /* printnumber */
+ Pos++;
+ break;
+ case 4: /* messagev */
+ Pos++;
+ break;
+ case 5: /* messagec */
+ scangetcon(Code, &Pos, &ScanCodeMask);
+ break;
+ case 6: /* function */
+ switch (Base[Pos++]) {
+ case 2:/* random */
+ Pos++;
+ break;
+ case 1:/* calldriver */
+ if (DriverV4) {
+ if (CheckCallDriverV4(Base, Pos - 2))
+ *DriverV4 = true;
+ }
+ break;
+ case 3:/* save */
+ case 4:/* restore */
+ case 5:/* clearworkspace */
+ case 6:/* clear stack */
+ break;
+ case 250: /* printstr */
+ while (Base[Pos++]);
+ break;
+
+ default:
+ Valid = false;
+ break;
+ }
+ break;
+ case 7: /* input */
+ Pos += 4;
+ break;
+ case 8: /* varcon */
+ scangetcon(Code, &Pos, &ScanCodeMask);
+ Pos++;
+ break;
+ case 9: /* varvar */
+ Pos += 2;
+ break;
+ case 10: /* _add */
+ Pos += 2;
+ break;
+ case 11: /* _sub */
+ Pos += 2;
+ break;
+ case 14: /* jump */
+ *JumpKill = true;
+ Finished = true;
+ break;
+ case 15: /* exit */
+ Pos += 4;
+ break;
+ case 16: /* ifeqvt */
+ case 17: /* ifnevt */
+ case 18: /* ifltvt */
+ case 19: { /* ifgtvt */
+ uint32 Val;
+ Pos += 2;
+ Val = scangetaddr(Code, Base, &Pos, acode, &ScanCodeMask);
+ Valid = ValidateSequence(Base, Image, Val, acode, Size, size, Min, Max, Rts, JumpKill, DriverV4);
+ break;
+ }
+ case 20: /* screen */
+ if (Base[Pos++]) Pos++;
+ break;
+ case 21: /* cleartg */
+ Pos++;
+ break;
+ case 22: /* picture */
+ Pos++;
+ break;
+ case 23: /* getnextobject */
+ Pos += 6;
+ break;
+ case 24: /* ifeqct */
+ case 25: /* ifnect */
+ case 26: /* ifltct */
+ case 27: { /* ifgtct */
+ uint32 Val;
+ Pos++;
+ scangetcon(Code, &Pos, &ScanCodeMask);
+ Val = scangetaddr(Code, Base, &Pos, acode, &ScanCodeMask);
+ Valid = ValidateSequence(Base, Image, Val, acode, Size, size, Min, Max, Rts, JumpKill, DriverV4);
+ break;
+ }
+ case 28: /* printinput */
+ break;
+ case 12: /* ilins */
+ case 13: /* ilins */
+ case 29: /* ilins */
+ case 30: /* ilins */
+ case 31: /* ilins */
+ Valid = false;
+ break;
+ }
+ if (Valid && (Code & ~ScanCodeMask))
+ Strange++;
+ } while (Valid && !Finished && Pos < size); /* && Strange==0); */
+ (*Size) += Pos - iPos;
+ return Valid; /* && Strange==0; */
+}
+
+uint16 Scanner::scanmovewa5d0(byte *Base, uint32 *Pos) {
+ uint16 ret = L9WORD(Base + *Pos);
+ (*Pos) += 2;
+ return ret;
+}
+
+uint32 Scanner::scangetaddr(int Code, byte *Base, uint32 *Pos, uint32 acode, int *Mask) {
+ (*Mask) |= 0x20;
+ if (Code & 0x20) {
+ /* getaddrshort */
+ signed char diff = Base[*Pos];
+ (*Pos)++;
+ return (*Pos) + diff - 1;
+ } else {
+ return acode + scanmovewa5d0(Base, Pos);
+ }
+}
+
+void Scanner::scangetcon(int Code, uint32 *Pos, int *Mask) {
+ (*Pos)++;
+ if (!(Code & 64))(*Pos)++;
+ (*Mask) |= 0x40;
+}
+
+bool Scanner::CheckCallDriverV4(byte *Base, uint32 Pos) {
+ int i, j;
+
+ // Look back for an assignment from a variable to list9[0], which is used
+ // to specify the driver call.
+ for (i = 0; i < 2; i++) {
+ int x = Pos - ((i + 1) * 3);
+ if ((Base[x] == 0x89) && (Base[x + 1] == 0x00)) {
+ // Get the variable being copied to list9[0]
+ int var = Base[x + 2];
+
+ // Look back for an assignment to the variable
+ for (j = 0; j < 2; j++) {
+ int y = x - ((j + 1) * 3);
+ if ((Base[y] == 0x48) && (Base[y + 2] == var)) {
+ // If this a V4 driver call?
+ switch (Base[y + 1]) {
+ case 0x0E:
+ case 0x20:
+ case 0x22:
+ return TRUE;
+ }
+ return FALSE;
+ }
+ }
+ }
+ }
+ return FALSE;
+}
+
+#ifdef FULLSCAN
+void Scanner::fullScan(byte *StartFile, uint32 size) {
+ byte *Image = (byte *)calloc(size, 1);
+ uint32 i, Size;
+ int Replace;
+ byte *ImagePtr;
+ uint32 MaxPos = 0;
+ uint32 MaxCount = 0;
+ uint32 Min, Max, MaxMin, MaxMax;
+ int Offset;
+ bool JumpKill, MaxJK;
+ for (i = 0; i < size; i++) {
+ Size = 0;
+ Min = Max = i;
+ Replace = 0;
+ if (ValidateSequence(StartFile, Image, i, i, &Size, size, &Min, &Max, FALSE, &JumpKill, nullptr)) {
+ if (Size > MaxCount) {
+ MaxCount = Size;
+ MaxMin = Min;
+ MaxMax = Max;
+
+ MaxPos = i;
+ MaxJK = JumpKill;
+ }
+ Replace = 0;
+ }
+ for (ImagePtr = Image + Min; ImagePtr <= Image + Max; ImagePtr++) {
+ if (*ImagePtr == 2)
+ *ImagePtr = Replace;
+ }
+ }
+ printf("%ld %ld %ld %ld %s", MaxPos, MaxCount, MaxMin, MaxMax, MaxJK ? "jmp killed" : "");
+ /* search for reference to MaxPos */
+ Offset = 0x12 + 11 * 2;
+ for (i = 0; i < size - Offset - 1; i++) {
+ if ((L9WORD(StartFile + i + Offset)) + i == MaxPos) {
+ printf("possible v3,4 Code reference at : %ld", i);
+ /* startdata=StartFile+i; */
+ }
+ }
+ Offset = 13 * 2;
+ for (i = 0; i < size - Offset - 1; i++) {
+ if ((L9WORD(StartFile + i + Offset)) + i == MaxPos)
+ printf("possible v2 Code reference at : %ld", i);
+ }
+ free(Image);
+}
+#endif
+
+/*----------------------------------------------------------------------*/
+
GameDetection::GameDetection(byte *&startData, size_t &fileSize) :
_startData(startData), _fileSize(fileSize), _crcInitialized(false), _gameName(nullptr) {
Common::fill(&_crcTable[0], &_crcTable[256], 0);
@@ -199,7 +680,6 @@ void GameDetection::gln_gameid_game_name_reset() {
_gameName = nullptr;
}
-
/*----------------------------------------------------------------------*/
void Level9MetaEngine::getSupportedGames(PlainGameList &games) {
diff --git a/engines/glk/level9/detection.h b/engines/glk/level9/detection.h
index 9e8401b..89bd854 100644
--- a/engines/glk/level9/detection.h
+++ b/engines/glk/level9/detection.h
@@ -31,6 +31,8 @@
namespace Glk {
namespace Level9 {
+enum L9GameTypes { L9_V1, L9_V2, L9_V3, L9_V4 };
+
struct gln_game_table_t {
const size_t length; ///< Datafile length in bytes
const byte checksum; ///< 8-bit checksum, last datafile byte
@@ -48,10 +50,53 @@ struct gln_patch_table_t {
};
typedef const gln_patch_table_t *gln_patch_tableref_t;
+struct L9V1GameInfo {
+ byte dictVal1, dictVal2;
+ int dictStart, L9Ptrs[5], absData, msgStart, msgLen;
+};
+
+/**
+ * Scanner for game data
+ */
+class Scanner {
+private:
+ long scan(byte *StartFile, uint32 size);
+ long ScanV2(byte *StartFile, uint32 size);
+ long ScanV1(byte *StartFile, uint32 size);
+
+ bool ValidateSequence(byte *Base, byte *Image, uint32 iPos, uint32 acode, uint32 *Size, uint32 size, uint32 *Min, uint32 *Max, bool Rts, bool *JumpKill, bool *DriverV4);
+ uint16 scanmovewa5d0(byte *Base, uint32 *Pos);
+ uint32 scangetaddr(int Code, byte *Base, uint32 *Pos, uint32 acode, int *Mask);
+ void scangetcon(int Code, uint32 *Pos, int *Mask);
+ bool CheckCallDriverV4(byte *Base, uint32 Pos);
+#ifdef FULLSCAN
+ void Scanner::fullScan(byte *StartFile, uint32 size);
+#endif
+private:
+ byte **_dictData;
+ byte **_aCodePtr;
+public:
+ L9GameTypes _gameType;
+ int _l9V1Game;
+public:
+ Scanner() : _dictData(nullptr), _aCodePtr(nullptr), _gameType(L9_V1), _l9V1Game(-1) {}
+
+ /**
+ * Scan passed file for a valid game, and if found return it's offset
+ */
+ long scanner(byte *StartFile, uint32 size, byte **dictData = nullptr,
+ byte **aCodePtr = nullptr);
+
+ /**
+ * Returns the info for a V1 game
+ */
+ const L9V1GameInfo &v1Game() const;
+};
+
/**
* Detection manager for specific games
*/
-class GameDetection {
+class GameDetection : public Scanner {
private:
byte *&_startData;
size_t &_fileSize;
diff --git a/engines/glk/level9/detection_tables.h b/engines/glk/level9/detection_tables.h
index dda4ce4..eb8ae7d 100644
--- a/engines/glk/level9/detection_tables.h
+++ b/engines/glk/level9/detection_tables.h
@@ -795,6 +795,13 @@ static const gln_patch_table_t GLN_PATCH_TABLE[] = {
{0x0000, 0x00, 0x0000, 0x00, 0x0000},
};
+const L9V1GameInfo L9_V1_GAMES[] = {
+ 0x1a, 0x24, 301, 0x0000, -0x004b, 0x0080, -0x002b, 0x00d0, 0x03b0, 0x0f80, 0x4857, /* Colossal Adventure */
+ 0x20, 0x3b, 283, -0x0583, 0x0000, -0x0508, -0x04e0, 0x0000, 0x0800, 0x1000, 0x39d1, /* Adventure Quest */
+ 0x14, 0xff, 153, -0x00d6, 0x0000, 0x0000, 0x0000, 0x0000, 0x0a20, 0x16bf, 0x420d, /* Dungeon Adventure */
+ 0x15, 0x5d, 252, -0x3e70, 0x0000, -0x3d30, -0x3ca0, 0x0100, 0x4120, -0x3b9d, 0x3988, /* Lords of Time */
+ 0x15, 0x6c, 284, -0x00f0, 0x0000, -0x0050, -0x0050, -0x0050, 0x0300, 0x1930, 0x3c17, /* Snowball */
+};
// TODO: The list of Level 9 games and detection entries needs to be filled out
const PlainGameDescriptor LEVEL9_GAME_LIST[] = {
diff --git a/engines/glk/level9/level9_main.cpp b/engines/glk/level9/level9_main.cpp
index b4ab1a8..bbaad57 100644
--- a/engines/glk/level9/level9_main.cpp
+++ b/engines/glk/level9/level9_main.cpp
@@ -63,8 +63,8 @@ struct SaveStruct {
};
/* Enumerations */
-enum L9GameTypes { L9_V1, L9_V2, L9_V3, L9_V4 };
enum L9MsgTypes { MSGT_V1, MSGT_V2 };
+
/*
Graphics type Resolution Scale stack reset
-------------------------------------------------
@@ -80,18 +80,17 @@ L9BYTE *startfile, *pictureaddress, *picturedata;
byte *startdata;
size_t FileSize, picturesize;
-L9BYTE *L9Pointers[12];
-L9BYTE *absdatablock, *list2ptr, *list3ptr, *list9startptr, *acodeptr;
-L9BYTE *startmd, *endmd, *endwdp5, *wordtable, *dictdata, *defdict;
+byte *L9Pointers[12];
+byte *absdatablock, *list2ptr, *list3ptr, *list9startptr, *acodeptr;
+byte *startmd, *endmd, *endwdp5, *wordtable, *dictdata, *defdict;
L9UINT16 dictdatalen;
-L9BYTE *startmdV2;
+byte *startmdV2;
int wordcase;
int unpackcount;
char unpackbuf[8];
L9BYTE *dictptr;
char threechars[34];
-int L9GameType;
int L9MsgType;
char LastGame[MAX_PATH];
char FirstLine[FIRSTLINESIZE];
@@ -143,20 +142,6 @@ L9UINT16 gnostack[128];
L9BYTE gnoscratch[32];
int object, gnosp, numobjectfound, searchdepth, inithisearchpos;
-
-struct L9V1GameInfo {
- L9BYTE dictVal1, dictVal2;
- int dictStart, L9Ptrs[5], absData, msgStart, msgLen;
-};
-const L9V1GameInfo L9V1Games[] = {
- 0x1a, 0x24, 301, 0x0000, -0x004b, 0x0080, -0x002b, 0x00d0, 0x03b0, 0x0f80, 0x4857, /* Colossal Adventure */
- 0x20, 0x3b, 283, -0x0583, 0x0000, -0x0508, -0x04e0, 0x0000, 0x0800, 0x1000, 0x39d1, /* Adventure Quest */
- 0x14, 0xff, 153, -0x00d6, 0x0000, 0x0000, 0x0000, 0x0000, 0x0a20, 0x16bf, 0x420d, /* Dungeon Adventure */
- 0x15, 0x5d, 252, -0x3e70, 0x0000, -0x3d30, -0x3ca0, 0x0100, 0x4120, -0x3b9d, 0x3988, /* Lords of Time */
- 0x15, 0x6c, 284, -0x00f0, 0x0000, -0x0050, -0x0050, -0x0050, 0x0300, 0x1930, 0x3c17, /* Snowball */
-};
-int L9V1Game;
-
/* Prototypes */
L9BOOL LoadGame2(const char *filename, char *picname);
int getlongcode();
@@ -269,7 +254,7 @@ void level9_initialize() {
lastchar = '.';
lastactualchar = 0;
- L9V1Game = -1;
+ g_vm->_detection._l9V1Game = -1;
startfile = pictureaddress = picturedata = nullptr;
}
@@ -688,480 +673,6 @@ L9BOOL load(const char *filename) {
return TRUE;
}
-L9UINT16 scanmovewa5d0(L9BYTE *Base, L9UINT32 *Pos) {
- L9UINT16 ret = L9WORD(Base + *Pos);
- (*Pos) += 2;
- return ret;
-}
-
-L9UINT32 scangetaddr(int Code, L9BYTE *Base, L9UINT32 *Pos, L9UINT32 acode, int *Mask) {
- (*Mask) |= 0x20;
- if (Code & 0x20) {
- /* getaddrshort */
- signed char diff = Base[*Pos];
- (*Pos)++;
- return (*Pos) + diff - 1;
- } else {
- return acode + scanmovewa5d0(Base, Pos);
- }
-}
-
-void scangetcon(int Code, L9UINT32 *Pos, int *Mask) {
- (*Pos)++;
- if (!(Code & 64))(*Pos)++;
- (*Mask) |= 0x40;
-}
-
-L9BOOL CheckCallDriverV4(L9BYTE *Base, L9UINT32 Pos) {
- int i, j;
-
- /* Look back for an assignment from a variable
- * to list9[0], which is used to specify the
- * driver call.
- */
- for (i = 0; i < 2; i++) {
- int x = Pos - ((i + 1) * 3);
- if ((Base[x] == 0x89) && (Base[x + 1] == 0x00)) {
- /* Get the variable being copied to list9[0] */
- int var = Base[x + 2];
-
- /* Look back for an assignment to the variable. */
- for (j = 0; j < 2; j++) {
- int y = x - ((j + 1) * 3);
- if ((Base[y] == 0x48) && (Base[y + 2] == var)) {
- /* If this a V4 driver call? */
- switch (Base[y + 1]) {
- case 0x0E:
- case 0x20:
- case 0x22:
- return TRUE;
- }
- return FALSE;
- }
- }
- }
- }
- return FALSE;
-}
-
-L9BOOL ValidateSequence(L9BYTE *Base, L9BYTE *Image, L9UINT32 iPos, L9UINT32 acode, L9UINT32 *Size, L9UINT32 size, L9UINT32 *Min, L9UINT32 *Max, L9BOOL Rts, L9BOOL *JumpKill, L9BOOL *DriverV4) {
- L9UINT32 Pos;
- L9BOOL Finished = FALSE, Valid;
- L9UINT32 Strange = 0;
- int ScanCodeMask;
- int Code;
- *JumpKill = FALSE;
-
- if (iPos >= size)
- return FALSE;
- Pos = iPos;
- if (Pos < *Min) *Min = Pos;
-
- if (Image[Pos]) return TRUE; /* hit valid code */
-
- do {
- Code = Base[Pos];
- Valid = TRUE;
- if (Image[Pos]) break; /* converged to found code */
- Image[Pos++] = 2;
- if (Pos > *Max) *Max = Pos;
-
- ScanCodeMask = 0x9f;
- if (Code & 0x80) {
- ScanCodeMask = 0xff;
- if ((Code & 0x1f) > 0xa)
- Valid = FALSE;
- Pos += 2;
- } else switch (Code & 0x1f) {
- case 0: { /* goto */
- L9UINT32 Val = scangetaddr(Code, Base, &Pos, acode, &ScanCodeMask);
- Valid = ValidateSequence(Base, Image, Val, acode, Size, size, Min, Max, TRUE/*Rts*/, JumpKill, DriverV4);
- Finished = TRUE;
- break;
- }
- case 1: { /* intgosub */
- L9UINT32 Val = scangetaddr(Code, Base, &Pos, acode, &ScanCodeMask);
- Valid = ValidateSequence(Base, Image, Val, acode, Size, size, Min, Max, TRUE, JumpKill, DriverV4);
- break;
- }
- case 2: /* intreturn */
- Valid = Rts;
- Finished = TRUE;
- break;
- case 3: /* printnumber */
- Pos++;
- break;
- case 4: /* messagev */
- Pos++;
- break;
- case 5: /* messagec */
- scangetcon(Code, &Pos, &ScanCodeMask);
- break;
- case 6: /* function */
- switch (Base[Pos++]) {
- case 2:/* random */
- Pos++;
- break;
- case 1:/* calldriver */
- if (DriverV4) {
- if (CheckCallDriverV4(Base, Pos - 2))
- *DriverV4 = TRUE;
- }
- break;
- case 3:/* save */
- case 4:/* restore */
- case 5:/* clearworkspace */
- case 6:/* clear stack */
- break;
- case 250: /* printstr */
- while (Base[Pos++]);
- break;
-
- default:
-#ifdef L9DEBUG
- /* printf("scan: illegal function call: %d",Base[Pos-1]); */
-#endif
- Valid = FALSE;
- break;
- }
- break;
- case 7: /* input */
- Pos += 4;
- break;
- case 8: /* varcon */
- scangetcon(Code, &Pos, &ScanCodeMask);
- Pos++;
- break;
- case 9: /* varvar */
- Pos += 2;
- break;
- case 10: /* _add */
- Pos += 2;
- break;
- case 11: /* _sub */
- Pos += 2;
- break;
- case 14: /* jump */
-#ifdef L9DEBUG
- /* printf("jmp at codestart: %ld",acode); */
-#endif
- *JumpKill = TRUE;
- Finished = TRUE;
- break;
- case 15: /* exit */
- Pos += 4;
- break;
- case 16: /* ifeqvt */
- case 17: /* ifnevt */
- case 18: /* ifltvt */
- case 19: { /* ifgtvt */
- L9UINT32 Val;
- Pos += 2;
- Val = scangetaddr(Code, Base, &Pos, acode, &ScanCodeMask);
- Valid = ValidateSequence(Base, Image, Val, acode, Size, size, Min, Max, Rts, JumpKill, DriverV4);
- break;
- }
- case 20: /* screen */
- if (Base[Pos++]) Pos++;
- break;
- case 21: /* cleartg */
- Pos++;
- break;
- case 22: /* picture */
- Pos++;
- break;
- case 23: /* getnextobject */
- Pos += 6;
- break;
- case 24: /* ifeqct */
- case 25: /* ifnect */
- case 26: /* ifltct */
- case 27: { /* ifgtct */
- L9UINT32 Val;
- Pos++;
- scangetcon(Code, &Pos, &ScanCodeMask);
- Val = scangetaddr(Code, Base, &Pos, acode, &ScanCodeMask);
- Valid = ValidateSequence(Base, Image, Val, acode, Size, size, Min, Max, Rts, JumpKill, DriverV4);
- break;
- }
- case 28: /* printinput */
- break;
- case 12: /* ilins */
- case 13: /* ilins */
- case 29: /* ilins */
- case 30: /* ilins */
- case 31: /* ilins */
-#ifdef L9DEBUG
- /* printf("scan: illegal instruction"); */
-#endif
- Valid = FALSE;
- break;
- }
- if (Valid && (Code & ~ScanCodeMask))
- Strange++;
- } while (Valid && !Finished && Pos < size); /* && Strange==0); */
- (*Size) += Pos - iPos;
- return Valid; /* && Strange==0; */
-}
-
-L9BYTE calcchecksum(L9BYTE *ptr, L9UINT32 num) {
- L9BYTE d1 = 0;
- while (num-- != 0) d1 += *ptr++;
- return d1;
-}
-
-long Scan(L9BYTE *StartFile, L9UINT32 size) {
- L9BYTE *Chk = (L9BYTE *)malloc(size + 1);
- L9BYTE *Image = (L9BYTE *)calloc(size, 1);
- L9UINT32 i, num, Size, MaxSize = 0;
- int j;
- L9UINT16 d0 = 0, l9, md, ml, dd, dl;
- L9UINT32 Min, Max;
- long Offset = -1;
- L9BOOL JumpKill, DriverV4;
-
- if ((Chk == nullptr) || (Image == nullptr)) {
- error("Unable to allocate memory for game scan! Exiting...");
- }
-
- Chk[0] = 0;
- for (i = 1; i <= size; i++)
- Chk[i] = Chk[i - 1] + StartFile[i - 1];
-
- for (i = 0; i < size - 33; i++) {
- num = L9WORD(StartFile + i) + 1;
- /*
- Chk[i] = 0 +...+ i-1
- Chk[i+n] = 0 +...+ i+n-1
- Chk[i+n] - Chk[i] = i + ... + i+n
- */
- if (num > 0x2000 && i + num <= size && Chk[i + num] == Chk[i]) {
- md = L9WORD(StartFile + i + 0x2);
- ml = L9WORD(StartFile + i + 0x4);
- dd = L9WORD(StartFile + i + 0xa);
- dl = L9WORD(StartFile + i + 0xc);
-
- if (ml > 0 && md > 0 && i + md + ml <= size && dd > 0 && dl > 0 && i + dd + dl * 4 <= size) {
- /* v4 files may have acodeptr in 8000-9000, need to fix */
- for (j = 0; j < 12; j++) {
- d0 = L9WORD(StartFile + i + 0x12 + j * 2);
- if (j != 11 && d0 >= 0x8000 && d0 < 0x9000) {
- if (d0 >= 0x8000 + LISTAREASIZE) break;
- } else if (i + d0 > size) break;
- }
- /* list9 ptr must be in listarea, acode ptr in data */
- if (j < 12 /*|| (d0>=0x8000 && d0<0x9000)*/) continue;
-
- l9 = L9WORD(StartFile + i + 0x12 + 10 * 2);
- if (l9 < 0x8000 || l9 >= 0x8000 + LISTAREASIZE) continue;
-
- Size = 0;
- Min = Max = i + d0;
- DriverV4 = 0;
- if (ValidateSequence(StartFile, Image, i + d0, i + d0, &Size, size, &Min, &Max, FALSE, &JumpKill, &DriverV4)) {
-#ifdef L9DEBUG
- printf("Found valid header at %ld, code size %ld", i, Size);
-#endif
- if (Size > MaxSize && Size > 100) {
- Offset = i;
- MaxSize = Size;
- L9GameType = DriverV4 ? L9_V4 : L9_V3;
- }
- }
- }
- }
- }
- free(Chk);
- free(Image);
- return Offset;
-}
-
-long ScanV2(L9BYTE *StartFile, L9UINT32 size) {
- L9BYTE *Chk = (L9BYTE *)malloc(size + 1);
- L9BYTE *Image = (L9BYTE *)calloc(size, 1);
- L9UINT32 i, Size, MaxSize = 0, num;
- int j;
- L9UINT16 d0 = 0, l9;
- L9UINT32 Min, Max;
- long Offset = -1;
- L9BOOL JumpKill;
-
- if ((Chk == nullptr) || (Image == nullptr)) {
- error("Unable to allocate memory for game scan! Exiting...");
- }
-
- Chk[0] = 0;
- for (i = 1; i <= size; i++)
- Chk[i] = Chk[i - 1] + StartFile[i - 1];
-
- for (i = 0; i < size - 28; i++) {
- num = L9WORD(StartFile + i + 28) + 1;
- if (i + num <= size && ((Chk[i + num] - Chk[i + 32]) & 0xff) == StartFile[i + 0x1e]) {
- for (j = 0; j < 14; j++) {
- d0 = L9WORD(StartFile + i + j * 2);
- if (j != 13 && d0 >= 0x8000 && d0 < 0x9000) {
- if (d0 >= 0x8000 + LISTAREASIZE) break;
- } else if (i + d0 > size) break;
- }
- /* list9 ptr must be in listarea, acode ptr in data */
- if (j < 14 /*|| (d0>=0x8000 && d0<0x9000)*/) continue;
-
- l9 = L9WORD(StartFile + i + 6 + 9 * 2);
- if (l9 < 0x8000 || l9 >= 0x8000 + LISTAREASIZE) continue;
-
- Size = 0;
- Min = Max = i + d0;
- if (ValidateSequence(StartFile, Image, i + d0, i + d0, &Size, size, &Min, &Max, FALSE, &JumpKill, nullptr)) {
-#ifdef L9DEBUG
- printf("Found valid V2 header at %ld, code size %ld", i, Size);
-#endif
- if (Size > MaxSize && Size > 100) {
- Offset = i;
- MaxSize = Size;
- }
- }
- }
- }
- free(Chk);
- free(Image);
- return Offset;
-}
-
-long ScanV1(L9BYTE *StartFile, L9UINT32 size) {
- L9BYTE *Image = (L9BYTE *)calloc(size, 1);
- L9UINT32 i, Size;
- int Replace;
- L9BYTE *ImagePtr;
- long MaxPos = -1;
- L9UINT32 MaxCount = 0;
- L9UINT32 Min, Max; //, MaxMax, MaxMin;
- L9BOOL JumpKill; // , MaxJK;
-
- int dictOff1 = 0, dictOff2 = 0;
- L9BYTE dictVal1 = 0xff, dictVal2 = 0xff;
-
- if (Image == nullptr) {
- error("Unable to allocate memory for game scan! Exiting...");
- }
-
- for (i = 0; i < size; i++) {
- if ((StartFile[i] == 0 && StartFile[i + 1] == 6) || (StartFile[i] == 32 && StartFile[i + 1] == 4)) {
- Size = 0;
- Min = Max = i;
- Replace = 0;
- if (ValidateSequence(StartFile, Image, i, i, &Size, size, &Min, &Max, FALSE, &JumpKill, nullptr)) {
- if (Size > MaxCount && Size > 100 && Size < 10000) {
- MaxCount = Size;
- //MaxMin = Min;
- //MaxMax = Max;
-
- MaxPos = i;
- //MaxJK = JumpKill;
- }
- Replace = 0;
- }
- for (ImagePtr = Image + Min; ImagePtr <= Image + Max; ImagePtr++) {
- if (*ImagePtr == 2)
- *ImagePtr = Replace;
- }
- }
- }
-#ifdef L9DEBUG
- printf("V1scan found code at %ld size %ld", MaxPos, MaxCount);
-#endif
-
- /* V1 dictionary detection from L9Cut by Paul David Doherty */
- for (i = 0; i < size - 20; i++) {
- if (StartFile[i] == 'A') {
- if (StartFile[i + 1] == 'T' && StartFile[i + 2] == 'T' && StartFile[i + 3] == 'A' && StartFile[i + 4] == 'C' && StartFile[i + 5] == 0xcb) {
- dictOff1 = i;
- dictVal1 = StartFile[dictOff1 + 6];
- break;
- }
- }
- }
- for (i = dictOff1; i < size - 20; i++) {
- if (StartFile[i] == 'B') {
- if (StartFile[i + 1] == 'U' && StartFile[i + 2] == 'N' && StartFile[i + 3] == 'C' && StartFile[i + 4] == 0xc8) {
- dictOff2 = i;
- dictVal2 = StartFile[dictOff2 + 5];
- break;
- }
- }
- }
- L9V1Game = -1;
- if (dictVal1 != 0xff || dictVal2 != 0xff) {
- for (i = 0; i < sizeof L9V1Games / sizeof L9V1Games[0]; i++) {
- if ((L9V1Games[i].dictVal1 == dictVal1) && (L9V1Games[i].dictVal2 == dictVal2)) {
- L9V1Game = i;
- dictdata = StartFile + dictOff1 - L9V1Games[i].dictStart;
- }
- }
- }
-
-#ifdef L9DEBUG
- if (L9V1Game >= 0)
- printf("V1scan found known dictionary: %d", L9V1Game);
-#endif
-
- free(Image);
-
- if (MaxPos > 0) {
- acodeptr = StartFile + MaxPos;
- return 0;
- }
- return -1;
-}
-
-#ifdef FULLSCAN
-void FullScan(L9BYTE *StartFile, L9UINT32 size) {
- L9BYTE *Image = (L9BYTE *)calloc(size, 1);
- L9UINT32 i, Size;
- int Replace;
- L9BYTE *ImagePtr;
- L9UINT32 MaxPos = 0;
- L9UINT32 MaxCount = 0;
- L9UINT32 Min, Max, MaxMin, MaxMax;
- int Offset;
- L9BOOL JumpKill, MaxJK;
- for (i = 0; i < size; i++) {
- Size = 0;
- Min = Max = i;
- Replace = 0;
- if (ValidateSequence(StartFile, Image, i, i, &Size, size, &Min, &Max, FALSE, &JumpKill, nullptr)) {
- if (Size > MaxCount) {
- MaxCount = Size;
- MaxMin = Min;
- MaxMax = Max;
-
- MaxPos = i;
- MaxJK = JumpKill;
- }
- Replace = 0;
- }
- for (ImagePtr = Image + Min; ImagePtr <= Image + Max; ImagePtr++) {
- if (*ImagePtr == 2)
- *ImagePtr = Replace;
- }
- }
- printf("%ld %ld %ld %ld %s", MaxPos, MaxCount, MaxMin, MaxMax, MaxJK ? "jmp killed" : "");
- /* search for reference to MaxPos */
- Offset = 0x12 + 11 * 2;
- for (i = 0; i < size - Offset - 1; i++) {
- if ((L9WORD(StartFile + i + Offset)) + i == MaxPos) {
- printf("possible v3,4 Code reference at : %ld", i);
- /* startdata=StartFile+i; */
- }
- }
- Offset = 13 * 2;
- for (i = 0; i < size - Offset - 1; i++) {
- if ((L9WORD(StartFile + i + Offset)) + i == MaxPos)
- printf("possible v2 Code reference at : %ld", i);
- }
- free(Image);
-}
-#endif
-
L9BOOL findsubs(L9BYTE *testptr, L9UINT32 testsize, L9BYTE **picdata, L9UINT32 *picsize) {
int i, j, length, count;
L9BYTE *picptr, *startptr, *tmpptr;
@@ -1269,44 +780,32 @@ L9BOOL intinitialise(const char *filename, char *picname) {
screencalled = 0;
l9textmode = 0;
-#ifdef FULLSCAN
- FullScan(startfile, FileSize);
-#endif
-
- Offset = Scan(startfile, FileSize);
+ Offset = g_vm->_detection.scanner(startfile, FileSize, &dictdata, &acodeptr);
if (Offset < 0) {
- Offset = ScanV2(startfile, FileSize);
- L9GameType = L9_V2;
- if (Offset < 0) {
- Offset = ScanV1(startfile, FileSize);
- L9GameType = L9_V1;
- if (Offset < 0) {
- error("\rUnable to locate valid Level 9 game in file: %s\r", filename);
- return FALSE;
- }
- }
+ error("Unable to locate valid Level 9 game in file: %s", filename);
+ return FALSE;
}
startdata = startfile + Offset;
FileSize -= Offset;
/* setup pointers */
- if (L9GameType == L9_V1) {
- if (L9V1Game < 0) {
+ if (g_vm->_detection._gameType == L9_V1) {
+ if (g_vm->_detection._l9V1Game < 0) {
error("\rWhat appears to be V1 game data was found, but the game was not recognised.\rEither this is an unknown V1 game file or, more likely, it is corrupted.\r");
return FALSE;
}
for (i = 0; i < 6; i++) {
- int off = L9V1Games[L9V1Game].L9Ptrs[i];
+ int off = g_vm->_detection.v1Game().L9Ptrs[i];
if (off < 0)
L9Pointers[i + 2] = acodeptr + off;
else
L9Pointers[i + 2] = workspace.listarea + off;
}
- absdatablock = acodeptr - L9V1Games[L9V1Game].absData;
+ absdatablock = acodeptr - g_vm->_detection.v1Game().absData;
} else {
/* V2,V3,V4 */
- hdoffset = L9GameType == L9_V2 ? 4 : 0x12;
+ hdoffset = g_vm->_detection._gameType == L9_V2 ? 4 : 0x12;
for (i = 0; i < 12; i++) {
L9UINT16 d0 = L9WORD(startdata + hdoffset + i * 2);
L9Pointers[i] = (i != 11 && d0 >= 0x8000 && d0 <= 0x9000) ? workspace.listarea + d0 - 0x8000 : startdata + d0;
@@ -1320,11 +819,11 @@ L9BOOL intinitialise(const char *filename, char *picname) {
acodeptr = L9Pointers[11];
}
- switch (L9GameType) {
+ switch (g_vm->_detection._gameType) {
case L9_V1: {
double a1;
- startmd = acodeptr + L9V1Games[L9V1Game].msgStart;
- startmdV2 = startmd + L9V1Games[L9V1Game].msgLen;
+ startmd = acodeptr + g_vm->_detection.v1Game().msgStart;
+ startmdV2 = startmd + g_vm->_detection.v1Game().msgLen;
if (analyseV1(&a1) && a1 > 2 && a1 < 10) {
L9MsgType = MSGT_V1;
@@ -1393,8 +892,14 @@ L9BOOL intinitialise(const char *filename, char *picname) {
return TRUE;
}
+static byte calcChecksum(byte *ptr, uint32 num) {
+ byte d1 = 0;
+ while (num-- != 0) d1 += *ptr++;
+ return d1;
+}
+
L9BOOL checksumgamedata() {
- return calcchecksum(startdata, L9WORD(startdata) + 1) == 0;
+ return calcChecksum(startdata, L9WORD(startdata) + 1) == 0;
}
L9UINT16 movewa5d0() {
@@ -1462,14 +967,14 @@ void printnumber() {
}
void messagec() {
- if (L9GameType <= L9_V2)
+ if (g_vm->_detection._gameType <= L9_V2)
printmessageV2(getcon());
else
printmessage(getcon());
}
void messagev() {
- if (L9GameType <= L9_V2)
+ if (g_vm->_detection._gameType <= L9_V2)
printmessageV2(*getvar());
else
printmessage(*getvar());
@@ -1932,7 +1437,7 @@ void function() {
switch (d0) {
case 1:
- if (L9GameType == L9_V1)
+ if (g_vm->_detection._gameType == L9_V1)
StopGame();
else
calldriver();
@@ -2048,7 +1553,7 @@ L9UINT32 readdecimal(char *buff) {
void checknumber() {
if (*obuff >= 0x30 && *obuff < 0x3a) {
- if (L9GameType == L9_V4) {
+ if (g_vm->_detection._gameType == L9_V4) {
*list9ptr = 1;
L9SETWORD(list9ptr + 1, readdecimal(obuff));
L9SETWORD(list9ptr + 3, 0);
@@ -2067,7 +1572,7 @@ void NextCheat() {
memmove(&workspace, &CheatWorkspace, sizeof(GameState));
codeptr = acodeptr + workspace.codeptr;
- if (!((L9GameType <= L9_V2) ? GetWordV2(ibuff, CheatWord++) : GetWordV3(ibuff, CheatWord++))) {
+ if (!((g_vm->_detection._gameType <= L9_V2) ? GetWordV2(ibuff, CheatWord++) : GetWordV3(ibuff, CheatWord++))) {
Cheating = FALSE;
printstring("\rCheat failed.\r");
*ibuff = 0;
@@ -2122,7 +1627,7 @@ L9BOOL CheckHash() {
} else if (scumm_stricmp(ibuff, "#dictionary") == 0) {
CheatWord = 0;
printstring("\r");
- while ((L9GameType <= L9_V2) ? GetWordV2(ibuff, CheatWord++) : GetWordV3(ibuff, CheatWord++)) {
+ while ((g_vm->_detection._gameType <= L9_V2) ? GetWordV2(ibuff, CheatWord++) : GetWordV3(ibuff, CheatWord++)) {
error("%s ", ibuff);
if (os_stoplist() || !Running) break;
}
@@ -2131,7 +1636,7 @@ L9BOOL CheckHash() {
} else if (scumm_strnicmp(ibuff, "#picture ", 9) == 0) {
int pic = 0;
if (sscanf(ibuff + 9, "%d", &pic) == 1) {
- if (L9GameType == L9_V4)
+ if (g_vm->_detection._gameType == L9_V4)
os_show_bitmap(pic, 0, 0);
else
show_picture(pic);
@@ -2157,7 +1662,7 @@ L9BOOL CheckHash() {
L9BOOL IsInputChar(char c) {
if (c == '-' || c == '\'')
return TRUE;
- if ((L9GameType >= L9_V3) && (c == '.' || c == ','))
+ if ((g_vm->_detection._gameType >= L9_V3) && (c == '.' || c == ','))
return TRUE;
return Common::isAlnum(c);
}
@@ -2444,7 +1949,7 @@ L9BOOL inputV2(int *wordcount) {
}
void input() {
- if (L9GameType == L9_V3 && FirstPicture >= 0) {
+ if (g_vm->_detection._gameType == L9_V3 && FirstPicture >= 0) {
show_picture(FirstPicture);
FirstPicture = -1;
}
@@ -2454,7 +1959,7 @@ void input() {
are called out of line */
codeptr--;
- if (L9GameType <= L9_V2) {
+ if (g_vm->_detection._gameType <= L9_V2) {
int wordcount;
if (inputV2(&wordcount)) {
L9BYTE *obuffptr = (L9BYTE *) obuff;
@@ -2519,7 +2024,7 @@ void exit1(L9BYTE *d4, L9BYTE *d5p, L9BYTE d6, L9BYTE d7) {
if (--d1) {
do {
d0 = *a0;
- if (L9GameType == L9_V4) {
+ if (g_vm->_detection._gameType == L9_V4) {
if ((d0 == 0) && (*(a0 + 1) == 0))
goto notfn4;
}
@@ -2622,7 +2127,7 @@ int scaley(int y) {
}
void detect_gfx_mode() {
- if (L9GameType == L9_V3) {
+ if (g_vm->_detection._gameType == L9_V3) {
/* These V3 games use graphics logic similar to the V2 games */
if (strstr(FirstLine, "price of magik") != 0)
gfx_mode = GFX_V3A;
@@ -2653,7 +2158,7 @@ void detect_gfx_mode() {
void _screen() {
int mode = 0;
- if (L9GameType == L9_V3 && strlen(FirstLine) == 0) {
+ if (g_vm->_detection._gameType == L9_V3 && strlen(FirstLine) == 0) {
if (*codeptr++)
codeptr++;
return;
@@ -2662,7 +2167,7 @@ void _screen() {
detect_gfx_mode();
l9textmode = *codeptr++;
if (l9textmode) {
- if (L9GameType == L9_V4)
+ if (g_vm->_detection._gameType == L9_V4)
mode = 2;
else if (picturedata)
mode = 1;
@@ -3094,7 +2599,7 @@ void absrunsub(int d0) {
}
void show_picture(int pic) {
- if (L9GameType == L9_V3 && strlen(FirstLine) == 0) {
+ if (g_vm->_detection._gameType == L9_V3 && strlen(FirstLine) == 0) {
FirstPicture = pic;
return;
}
@@ -3135,7 +2640,7 @@ void picture() {
}
void GetPictureSize(int *width, int *height) {
- if (L9GameType == L9_V4) {
+ if (g_vm->_detection._gameType == L9_V4) {
if (width != nullptr)
*width = 0;
if (height != nullptr)
Commit: d9c8237042c626a205ff5f59bcdbfe9ea31599d6
https://github.com/scummvm/scummvm/commit/d9c8237042c626a205ff5f59bcdbfe9ea31599d6
Author: Paul Gilbert (dreammaster at scummvm.org)
Date: 2019-10-26T11:13:30-07:00
Commit Message:
GLK: LEVEL9: Hook up new detection
Changed paths:
engines/glk/level9/detection.cpp
engines/glk/level9/detection.h
engines/glk/level9/detection_tables.h
diff --git a/engines/glk/level9/detection.cpp b/engines/glk/level9/detection.cpp
index 3b3228e..93e8143 100644
--- a/engines/glk/level9/detection.cpp
+++ b/engines/glk/level9/detection.cpp
@@ -25,6 +25,7 @@
#include "glk/level9/level9_main.h"
#include "glk/level9/os_glk.h"
#include "glk/blorb.h"
+#include "glk/detection.h"
#include "common/debug.h"
#include "common/file.h"
#include "common/md5.h"
@@ -62,14 +63,14 @@ const L9V1GameInfo &Scanner::v1Game() const {
return L9_V1_GAMES[_l9V1Game];
}
-long Scanner::scan(byte *StartFile, uint32 size) {
+long Scanner::scan(byte *startFile, uint32 size) {
byte *Chk = (byte *)malloc(size + 1);
byte *Image = (byte *)calloc(size, 1);
uint32 i, num, Size, MaxSize = 0;
int j;
uint16 d0 = 0, l9, md, ml, dd, dl;
uint32 Min, Max;
- long Offset = -1;
+ long offset = -1;
bool JumpKill, DriverV4;
if ((Chk == nullptr) || (Image == nullptr)) {
@@ -78,25 +79,25 @@ long Scanner::scan(byte *StartFile, uint32 size) {
Chk[0] = 0;
for (i = 1; i <= size; i++)
- Chk[i] = Chk[i - 1] + StartFile[i - 1];
+ Chk[i] = Chk[i - 1] + startFile[i - 1];
for (i = 0; i < size - 33; i++) {
- num = L9WORD(StartFile + i) + 1;
+ num = L9WORD(startFile + i) + 1;
/*
Chk[i] = 0 +...+ i-1
Chk[i+n] = 0 +...+ i+n-1
Chk[i+n] - Chk[i] = i + ... + i+n
*/
if (num > 0x2000 && i + num <= size && Chk[i + num] == Chk[i]) {
- md = L9WORD(StartFile + i + 0x2);
- ml = L9WORD(StartFile + i + 0x4);
- dd = L9WORD(StartFile + i + 0xa);
- dl = L9WORD(StartFile + i + 0xc);
+ md = L9WORD(startFile + i + 0x2);
+ ml = L9WORD(startFile + i + 0x4);
+ dd = L9WORD(startFile + i + 0xa);
+ dl = L9WORD(startFile + i + 0xc);
if (ml > 0 && md > 0 && i + md + ml <= size && dd > 0 && dl > 0 && i + dd + dl * 4 <= size) {
/* v4 files may have acodeptr in 8000-9000, need to fix */
for (j = 0; j < 12; j++) {
- d0 = L9WORD(StartFile + i + 0x12 + j * 2);
+ d0 = L9WORD(startFile + i + 0x12 + j * 2);
if (j != 11 && d0 >= 0x8000 && d0 < 0x9000) {
if (d0 >= 0x8000 + LISTAREASIZE) break;
} else if (i + d0 > size) break;
@@ -104,15 +105,15 @@ long Scanner::scan(byte *StartFile, uint32 size) {
/* list9 ptr must be in listarea, acode ptr in data */
if (j < 12 /*|| (d0>=0x8000 && d0<0x9000)*/) continue;
- l9 = L9WORD(StartFile + i + 0x12 + 10 * 2);
+ l9 = L9WORD(startFile + i + 0x12 + 10 * 2);
if (l9 < 0x8000 || l9 >= 0x8000 + LISTAREASIZE) continue;
Size = 0;
Min = Max = i + d0;
DriverV4 = 0;
- if (ValidateSequence(StartFile, Image, i + d0, i + d0, &Size, size, &Min, &Max, false, &JumpKill, &DriverV4)) {
+ if (ValidateSequence(startFile, Image, i + d0, i + d0, &Size, size, &Min, &Max, false, &JumpKill, &DriverV4)) {
if (Size > MaxSize && Size > 100) {
- Offset = i;
+ offset = i;
MaxSize = Size;
_gameType = DriverV4 ? L9_V4 : L9_V3;
}
@@ -123,17 +124,17 @@ long Scanner::scan(byte *StartFile, uint32 size) {
free(Chk);
free(Image);
- return Offset;
+ return offset;
}
-long Scanner::ScanV2(byte *StartFile, uint32 size) {
+long Scanner::ScanV2(byte *startFile, uint32 size) {
byte *Chk = (byte *)malloc(size + 1);
byte *Image = (byte *)calloc(size, 1);
uint32 i, Size, MaxSize = 0, num;
int j;
uint16 d0 = 0, l9;
uint32 Min, Max;
- long Offset = -1;
+ long offset = -1;
bool JumpKill;
if ((Chk == nullptr) || (Image == nullptr)) {
@@ -142,13 +143,13 @@ long Scanner::ScanV2(byte *StartFile, uint32 size) {
Chk[0] = 0;
for (i = 1; i <= size; i++)
- Chk[i] = Chk[i - 1] + StartFile[i - 1];
+ Chk[i] = Chk[i - 1] + startFile[i - 1];
for (i = 0; i < size - 28; i++) {
- num = L9WORD(StartFile + i + 28) + 1;
- if (i + num <= size && ((Chk[i + num] - Chk[i + 32]) & 0xff) == StartFile[i + 0x1e]) {
+ num = L9WORD(startFile + i + 28) + 1;
+ if (i + num <= size && ((Chk[i + num] - Chk[i + 32]) & 0xff) == startFile[i + 0x1e]) {
for (j = 0; j < 14; j++) {
- d0 = L9WORD(StartFile + i + j * 2);
+ d0 = L9WORD(startFile + i + j * 2);
if (j != 13 && d0 >= 0x8000 && d0 < 0x9000) {
if (d0 >= 0x8000 + LISTAREASIZE) break;
} else if (i + d0 > size) break;
@@ -156,17 +157,17 @@ long Scanner::ScanV2(byte *StartFile, uint32 size) {
/* list9 ptr must be in listarea, acode ptr in data */
if (j < 14 /*|| (d0>=0x8000 && d0<0x9000)*/) continue;
- l9 = L9WORD(StartFile + i + 6 + 9 * 2);
+ l9 = L9WORD(startFile + i + 6 + 9 * 2);
if (l9 < 0x8000 || l9 >= 0x8000 + LISTAREASIZE) continue;
Size = 0;
Min = Max = i + d0;
- if (ValidateSequence(StartFile, Image, i + d0, i + d0, &Size, size, &Min, &Max, false, &JumpKill, nullptr)) {
+ if (ValidateSequence(startFile, Image, i + d0, i + d0, &Size, size, &Min, &Max, false, &JumpKill, nullptr)) {
#ifdef L9DEBUG
printf("Found valid V2 header at %ld, code size %ld", i, Size);
#endif
if (Size > MaxSize && Size > 100) {
- Offset = i;
+ offset = i;
MaxSize = Size;
}
}
@@ -174,10 +175,10 @@ long Scanner::ScanV2(byte *StartFile, uint32 size) {
}
free(Chk);
free(Image);
- return Offset;
+ return offset;
}
-long Scanner::ScanV1(byte *StartFile, uint32 size) {
+long Scanner::ScanV1(byte *startFile, uint32 size) {
byte *Image = (byte *)calloc(size, 1);
uint32 i, Size;
int Replace;
@@ -195,11 +196,11 @@ long Scanner::ScanV1(byte *StartFile, uint32 size) {
}
for (i = 0; i < size; i++) {
- if ((StartFile[i] == 0 && StartFile[i + 1] == 6) || (StartFile[i] == 32 && StartFile[i + 1] == 4)) {
+ if ((startFile[i] == 0 && startFile[i + 1] == 6) || (startFile[i] == 32 && startFile[i + 1] == 4)) {
Size = 0;
Min = Max = i;
Replace = 0;
- if (ValidateSequence(StartFile, Image, i, i, &Size, size, &Min, &Max, false, &JumpKill, nullptr)) {
+ if (ValidateSequence(startFile, Image, i, i, &Size, size, &Min, &Max, false, &JumpKill, nullptr)) {
if (Size > MaxCount && Size > 100 && Size < 10000) {
MaxCount = Size;
//MaxMin = Min;
@@ -219,19 +220,19 @@ long Scanner::ScanV1(byte *StartFile, uint32 size) {
/* V1 dictionary detection from L9Cut by Paul David Doherty */
for (i = 0; i < size - 20; i++) {
- if (StartFile[i] == 'A') {
- if (StartFile[i + 1] == 'T' && StartFile[i + 2] == 'T' && StartFile[i + 3] == 'A' && StartFile[i + 4] == 'C' && StartFile[i + 5] == 0xcb) {
+ if (startFile[i] == 'A') {
+ if (startFile[i + 1] == 'T' && startFile[i + 2] == 'T' && startFile[i + 3] == 'A' && startFile[i + 4] == 'C' && startFile[i + 5] == 0xcb) {
dictOff1 = i;
- dictVal1 = StartFile[dictOff1 + 6];
+ dictVal1 = startFile[dictOff1 + 6];
break;
}
}
}
for (i = dictOff1; i < size - 20; i++) {
- if (StartFile[i] == 'B') {
- if (StartFile[i + 1] == 'U' && StartFile[i + 2] == 'N' && StartFile[i + 3] == 'C' && StartFile[i + 4] == 0xc8) {
+ if (startFile[i] == 'B') {
+ if (startFile[i + 1] == 'U' && startFile[i + 2] == 'N' && startFile[i + 3] == 'C' && startFile[i + 4] == 0xc8) {
dictOff2 = i;
- dictVal2 = StartFile[dictOff2 + 5];
+ dictVal2 = startFile[dictOff2 + 5];
break;
}
}
@@ -241,7 +242,7 @@ long Scanner::ScanV1(byte *StartFile, uint32 size) {
for (i = 0; i < sizeof L9_V1_GAMES / sizeof L9_V1_GAMES[0]; i++) {
if ((L9_V1_GAMES[i].dictVal1 == dictVal1) && (L9_V1_GAMES[i].dictVal2 == dictVal2)) {
_l9V1Game = i;
- (*_dictData) = StartFile + dictOff1 - L9_V1_GAMES[i].dictStart;
+ (*_dictData) = startFile + dictOff1 - L9_V1_GAMES[i].dictStart;
}
}
}
@@ -249,7 +250,7 @@ long Scanner::ScanV1(byte *StartFile, uint32 size) {
free(Image);
if (MaxPos > 0 && _aCodePtr) {
- (*_aCodePtr) = StartFile + MaxPos;
+ (*_aCodePtr) = startFile + MaxPos;
return 0;
}
@@ -463,7 +464,7 @@ bool Scanner::CheckCallDriverV4(byte *Base, uint32 Pos) {
}
#ifdef FULLSCAN
-void Scanner::fullScan(byte *StartFile, uint32 size) {
+void Scanner::fullScan(byte *startFile, uint32 size) {
byte *Image = (byte *)calloc(size, 1);
uint32 i, Size;
int Replace;
@@ -471,13 +472,13 @@ void Scanner::fullScan(byte *StartFile, uint32 size) {
uint32 MaxPos = 0;
uint32 MaxCount = 0;
uint32 Min, Max, MaxMin, MaxMax;
- int Offset;
+ int offset;
bool JumpKill, MaxJK;
for (i = 0; i < size; i++) {
Size = 0;
Min = Max = i;
Replace = 0;
- if (ValidateSequence(StartFile, Image, i, i, &Size, size, &Min, &Max, FALSE, &JumpKill, nullptr)) {
+ if (ValidateSequence(startFile, Image, i, i, &Size, size, &Min, &Max, FALSE, &JumpKill, nullptr)) {
if (Size > MaxCount) {
MaxCount = Size;
MaxMin = Min;
@@ -495,16 +496,16 @@ void Scanner::fullScan(byte *StartFile, uint32 size) {
}
printf("%ld %ld %ld %ld %s", MaxPos, MaxCount, MaxMin, MaxMax, MaxJK ? "jmp killed" : "");
/* search for reference to MaxPos */
- Offset = 0x12 + 11 * 2;
- for (i = 0; i < size - Offset - 1; i++) {
- if ((L9WORD(StartFile + i + Offset)) + i == MaxPos) {
+ offset = 0x12 + 11 * 2;
+ for (i = 0; i < size - offset - 1; i++) {
+ if ((L9WORD(startFile + i + offset)) + i == MaxPos) {
printf("possible v3,4 Code reference at : %ld", i);
- /* startdata=StartFile+i; */
+ /* startdata=startFile+i; */
}
}
- Offset = 13 * 2;
- for (i = 0; i < size - Offset - 1; i++) {
- if ((L9WORD(StartFile + i + Offset)) + i == MaxPos)
+ offset = 13 * 2;
+ for (i = 0; i < size - offset - 1; i++) {
+ if ((L9WORD(startFile + i + offset)) + i == MaxPos)
printf("possible v2 Code reference at : %ld", i);
}
free(Image);
@@ -585,7 +586,6 @@ gln_game_tableref_t GameDetection::gln_gameid_identify_game() {
static const uint16 GLN_CRC_POLYNOMIAL = 0xa001;
uint16 GameDetection::gln_get_buffer_crc(const void *void_buffer, size_t length, size_t padding) {
-
const char *buffer = (const char *)void_buffer;
uint16 crc;
size_t index;
@@ -683,15 +683,26 @@ void GameDetection::gln_gameid_game_name_reset() {
/*----------------------------------------------------------------------*/
void Level9MetaEngine::getSupportedGames(PlainGameList &games) {
- for (const PlainGameDescriptor *pd = LEVEL9_GAME_LIST; pd->gameId; ++pd) {
- games.push_back(*pd);
+ const char *prior_id = nullptr;
+
+ for (const gln_game_table_t *pd = GLN_GAME_TABLE; pd->name; ++pd) {
+ if (prior_id == nullptr || strcmp(pd->gameId, prior_id)) {
+ PlainGameDescriptor gd;
+ gd.gameId = pd->gameId;
+ gd.description = pd->name;
+ games.push_back(gd);
+
+ prior_id = pd->gameId;
+ }
}
}
GameDescriptor Level9MetaEngine::findGame(const char *gameId) {
- for (const PlainGameDescriptor *pd = LEVEL9_GAME_LIST; pd->gameId; ++pd) {
- if (!strcmp(gameId, pd->gameId))
- return *pd;
+ for (const gln_game_table_t *pd = GLN_GAME_TABLE; pd->gameId; ++pd) {
+ if (!strcmp(gameId, pd->gameId)) {
+ GameDescriptor gd(pd->gameId, pd->name, 0);
+ return gd;
+ }
}
return PlainGameDescriptor();
@@ -704,42 +715,64 @@ bool Level9MetaEngine::detectGames(const Common::FSList &fslist, DetectedGames &
if (file->isDirectory())
continue;
Common::String filename = file->getName();
- if (!filename.hasSuffixIgnoreCase(".l9"))
+ if (!filename.hasSuffixIgnoreCase(".l9") && !filename.hasSuffixIgnoreCase(".dat"))
continue;
- // Open up the file and calculate the md5
+ // Open up the file so we can get it's size
Common::File gameFile;
if (!gameFile.open(*file))
continue;
- Common::String md5 = Common::computeStreamMD5AsString(gameFile, 5000);
- size_t _fileSize = gameFile.size();
- gameFile.seek(0);
- bool isBlorb = Blorb::isBlorb(gameFile, ID_ADRI);
+ size_t fileSize = gameFile.size();
+ if (fileSize > 0xffff) {
+ // Too big to possibly be a Level 9 game
+ gameFile.close();
+ continue;
+ }
+
+ // Read in the game data
+ Common::Array<byte> data;
+ data.resize(fileSize);
+ gameFile.read(&data[0], fileSize);
gameFile.close();
- if (!isBlorb && Blorb::hasBlorbExt(filename))
+ // Check if it's a valid Level 9 game
+ byte *startFile = &data[0];
+ Scanner scanner;
+ int offset = scanner.scanner(&data[0], fileSize) < 0;
+ if (offset < 0)
continue;
- // Check for known games
- const GlkDetectionEntry *p = LEVEL9_GAMES;
- while (p->_gameId && (md5 != p->_md5 || _fileSize != p->_filesize))
- ++p;
-
- if (!p->_gameId) {
- const PlainGameDescriptor &desc = LEVEL9_GAME_LIST[0];
- gameList.push_back(GlkDetectedGame(desc.gameId, desc.description, filename, md5, _fileSize));
- } else {
- PlainGameDescriptor gameDesc = findGame(p->_gameId);
- gameList.push_back(GlkDetectedGame(p->_gameId, gameDesc.description, p->_extra, filename, p->_language));
- }
+ // Check for the specific game
+ byte *startData = startFile + offset;
+ GameDetection detection(startData, fileSize);
+
+ const gln_game_tableref_t game = detection.gln_gameid_identify_game();
+ if (!game)
+ continue;
+
+ // Found the game, add a detection entry
+ DetectedGame gd = DetectedGame(game->gameId, game->name, Common::UNK_LANG,
+ Common::kPlatformUnknown, game->extra);
+ gd.addExtraEntry("filename", filename);
+ gameList.push_back(gd);
}
return !gameList.empty();
}
void Level9MetaEngine::detectClashes(Common::StringMap &map) {
- // No implementation
+ const char *prior_id = nullptr;
+
+ for (const gln_game_table_t *pd = GLN_GAME_TABLE; pd->name; ++pd) {
+ if (prior_id == nullptr || strcmp(pd->gameId, prior_id)) {
+ prior_id = pd->gameId;
+
+ if (map.contains(pd->gameId))
+ error("Duplicate game Id found - %s", pd->gameId);
+ map[pd->gameId] = "";
+ }
+ }
}
} // End of namespace Level9
diff --git a/engines/glk/level9/detection.h b/engines/glk/level9/detection.h
index 89bd854..ae876b9 100644
--- a/engines/glk/level9/detection.h
+++ b/engines/glk/level9/detection.h
@@ -37,7 +37,9 @@ struct gln_game_table_t {
const size_t length; ///< Datafile length in bytes
const byte checksum; ///< 8-bit checksum, last datafile byte
const uint16 crc; ///< 16-bit CRC, L9cut-internal
- const char *const name; ///< Game title and platform
+ const char *const gameId; ///< Game Id
+ const char *const name; ///< Game title
+ const char *const extra; ///< Disk number, platform, etc.
};
typedef const gln_game_table_t *gln_game_tableref_t;
diff --git a/engines/glk/level9/detection_tables.h b/engines/glk/level9/detection_tables.h
index eb8ae7d..b43deff 100644
--- a/engines/glk/level9/detection_tables.h
+++ b/engines/glk/level9/detection_tables.h
@@ -31,408 +31,382 @@ namespace Glk {
namespace Level9 {
/**
- * The following game database is obtained from L9cut's l9data_d.h, and
- * lets us find a game's name from its data CRC. Entries marked "WANTED" in
- * l9data_d.h, and file commentary, have been removed for brevity, and the
- * file has been reformatted (patchlevel data removed).
+ * The following game database is modified for ScummVM based on the data obtained
+ * from L9cut's l9data_d.h, and lets us find a game's name from its data CRC.
+ * Entries marked "WANTED" in l9data_d.h, and file commentary, have been removed for brevity,
+ * and the file has been reformatted (patchlevel data removed).
*
* The version of l9data_d.h used is 050 (22 Oct 2002).
*/
static const gln_game_table_t GLN_GAME_TABLE[] = {
- {0x5323, 0xb7, 0x8af7, "Adventure Quest (Amstrad CPC/Spectrum)"},
-
- {0x630e, 0x8d, 0x7d7d, "Dungeon Adventure (Amstrad CPC)"},
- {0x630e, 0xbe, 0x3374, "Dungeon Adventure (MSX)"},
-
- {0x5eb9, 0x30, 0xe99a, "Lords of Time (Amstrad CPC)"},
- {0x5eb9, 0x5d, 0xc098, "Lords of Time (MSX)"},
- {0x5eb9, 0x6e, 0xc689, "Lords of Time (Spectrum)"},
-
- {0x5fab, 0x5c, 0xa309, "Snowball (Amstrad CPC)"},
- {0x5fab, 0x2f, 0x8aa2, "Snowball (MSX)"},
-
- {0x60c4, 0x28, 0x0154, "Return to Eden (Amstrad CPC/Commodore 64[v1])"},
- {0x6064, 0x01, 0x5b3c, "Return to Eden (BBC[v1])"},
- {0x6064, 0x95, 0x510c, "Return to Eden (Commodore 64[v2])"},
- {0x6064, 0xda, 0xe610, "Return to Eden (Commodore 64[v2] *corrupt*)"},
- {0x6064, 0xbd, 0x73ec, "Return to Eden (Atari *corrupt*)"},
- {0x6047, 0x6c, 0x17ab, "Return to Eden (BBC[v2])"},
- {0x5ca1, 0x33, 0x1c43, "Return to Eden (Spectrum[v1])"},
- {0x5cb7, 0x64, 0x0790, "Return to Eden (Spectrum[v2])"},
- {0x5cb7, 0xfe, 0x3533, "Return to Eden (MSX)"},
-
- {0x34b3, 0x20, 0xccda, "Erik the Viking (BBC/Commodore 64)"},
- {0x34b3, 0x53, 0x8f00, "Erik the Viking (Spectrum)"},
- {0x34b3, 0xc7, 0x9058, "Erik the Viking (Amstrad CPC)"},
-
- {0x63be, 0xd6, 0xcf5d, "Emerald Isle (Atari/Commodore 64/Amstrad CPC/Spectrum)"},
- {0x63be, 0x0a, 0x21ed, "Emerald Isle (MSX *corrupt*)"},
- {0x378c, 0x8d, 0x3a21, "Emerald Isle (BBC)"},
-
- {0x506c, 0xf0, 0xba72, "Red Moon (BBC/Commodore 64/Amstrad CPC/MSX)"},
- {0x505d, 0x32, 0x2dcf, "Red Moon (Spectrum)"},
-
- {0x772b, 0xcd, 0xa503, "Worm in Paradise (Spectrum 128)"},
- {0x546c, 0xb7, 0x9420, "Worm in Paradise (Spectrum 48)"},
- {0x6d84, 0xf9, 0x49ae, "Worm in Paradise (Commodore 64 *corrupt*)"},
- {0x6d84, 0xc8, 0x943f, "Worm in Paradise (Commodore 64 *fixed*)"},
- {0x6030, 0x47, 0x46ad, "Worm in Paradise (Amstrad CPC)"},
- {0x5828, 0xbd, 0xe7cb, "Worm in Paradise (BBC)"},
-
- {0x7410, 0x5e, 0x60be, "Price of Magik (Spectrum 128)"},
- {0x5aa4, 0xc1, 0x10a0, "Price of Magik (Spectrum 48[v1])"},
- {0x5aa4, 0xc1, 0xeda4, "Price of Magik (Spectrum 48[v2])"},
- {0x6fc6, 0x14, 0xf9b6, "Price of Magik (Commodore 64)"},
- {0x5aa4, 0xc1, 0xbbf4, "Price of Magik (Amstrad CPC)"},
- {0x5671, 0xbc, 0xff35, "Price of Magik (BBC)"},
-
- {0x76f4, 0x5e, 0x1fe5, "Colossal Adventure /JoD (Amiga/PC)"},
- {0x76f4, 0x5a, 0xcf4b, "Colossal Adventure /JoD (ST)"},
- {0x6e60, 0x83, 0x18e0, "Adventure Quest /JoD (Amiga/PC)"},
- {0x6e5c, 0xf6, 0xd356, "Adventure Quest /JoD (ST)"},
- {0x6f0c, 0x95, 0x1f64, "Dungeon Adventure /JoD (Amiga/PC/ST)"},
-
- {0x6f70, 0x40, 0xbd91, "Colossal Adventure /JoD (MSX)"},
-
- {0x6f6e, 0x78, 0x28cd, "Colossal Adventure /JoD (Spectrum 128)"},
- {0x6970, 0xd6, 0xa820, "Adventure Quest /JoD (Spectrum 128)"},
- {0x6de8, 0x4c, 0xd795, "Dungeon Adventure /JoD (Spectrum 128)"},
-
- {0x6f4d, 0xcb, 0xe8f2, "Colossal Adventure /JoD (Amstrad CPC128[v1]/Spectrum +3)"},
- {0x6f6a, 0xa5, 0x8dd2, "Colossal Adventure /JoD (Amstrad CPC128[v2])"},
- {0x6968, 0x32, 0x0c01, "Adventure Quest /JoD (Amstrad CPC128/Spectrum +3)"},
- {0x6dc0, 0x63, 0x5d95, "Dungeon Adventure /JoD (Amstrad CPC128/Spectrum +3)"},
-
- {0x5e31, 0x7c, 0xaa54, "Colossal Adventure /JoD (Amstrad CPC64)"},
- {0x5b50, 0x66, 0x1800, "Adventure Quest /JoD (Amstrad CPC64)"},
- {0x58a6, 0x24, 0xb50f, "Dungeon Adventure /JoD (Amstrad CPC64)"},
-
- {0x6c8e, 0xb6, 0x9be3, "Colossal Adventure /JoD (Commodore 64)"},
- {0x63b6, 0x2e, 0xef38, "Adventure Quest /JoD (Commodore 64)"},
- {0x6bd2, 0x65, 0xa41f, "Dungeon Adventure /JoD (Commodore 64)"},
-
- {0x5b16, 0x3b, 0xe2aa, "Colossal Adventure /JoD (Atari)"},
- {0x5b58, 0x50, 0x332e, "Adventure Quest /JoD (Atari)"},
- {0x593a, 0x80, 0x7a34, "Dungeon Adventure /JoD (Atari)"},
-
- {0x5a8e, 0xf2, 0x7cca, "Colossal Adventure /JoD (Spectrum 48)"},
- {0x5ace, 0x11, 0xdc12, "Adventure Quest /JoD (Spectrum 48)"},
- {0x58a3, 0x38, 0x8ce4, "Dungeon Adventure /JoD (Spectrum 48)"},
-
- {0x7b31, 0x6e, 0x2e2b, "Snowball /SD (Amiga/ST)"},
- {0x7d16, 0xe6, 0x5438, "Return to Eden /SD (Amiga/ST)"},
- {0x7cd9, 0x0c, 0x4df1, "Worm in Paradise /SD (Amiga/ST)"},
-
- {0x7b2f, 0x70, 0x6955, "Snowball /SD (Mac/PC/Spectrum 128)"},
- {0x7b2f, 0x70, 0x6f6c, "Snowball /SD (Amstrad CPC/Spectrum +3)"},
- {0x7d14, 0xe8, 0xfbab, "Return to Eden /SD (PC)"},
- {0x7cff, 0xf8, 0x6044, "Return to Eden /SD (Amstrad CPC/Spectrum +3)"},
- {0x7cf8, 0x24, 0x9c1c, "Return to Eden /SD (Mac)"},
- {0x7c55, 0x18, 0xdaee, "Return to Eden /SD (Spectrum 128)"},
- {0x7cd7, 0x0e, 0x4feb, "Worm in Paradise /SD (Amstrad CPC/Mac/PC/Spectrum 128/Spectrum +3)"},
-
- {0x7363, 0x65, 0xa0ab, "Snowball /SD (Commodore 64)"},
- {0x772f, 0xca, 0x8602, "Return to Eden /SD (Commodore 64)"},
- {0x788d, 0x72, 0x888a, "Worm in Paradise /SD (Commodore 64)"},
-
- {0x6bf8, 0x3f, 0xc9f7, "Snowball /SD (Atari)"},
- {0x60f7, 0x68, 0xc2bc, "Return to Eden /SD (Atari)"},
- {0x6161, 0xf3, 0xe6d7, "Worm in Paradise /SD (Atari)"},
-
- {0x67a3, 0x9d, 0x1d05, "Snowball /SD (Apple ][)"},
- {0x639c, 0x8b, 0x06e2, "Return to Eden /SD (Apple ][)"},
- {0x60dd, 0xf2, 0x5bb8, "Worm in Paradise /SD (Apple ][)"},
-
- {0x6541, 0x02, 0x2e6c, "Snowball /SD (Spectrum 48)"},
- {0x5f43, 0xca, 0x828c, "Return to Eden /SD (Spectrum 48)"},
- {0x5ebb, 0xf1, 0x4dec, "Worm in Paradise /SD (Spectrum 48)"},
-
- {0x8333, 0xb7, 0xe2ac, "Adrian Mole I, pt. 1 (Commodore 64)"},
- {0x844d, 0x50, 0x5353, "Adrian Mole I, pt. 2 (Commodore 64)"},
- {0x8251, 0x5f, 0x862a, "Adrian Mole I, pt. 3 (Commodore 64)"},
- {0x7a78, 0x5e, 0x6ea3, "Adrian Mole I, pt. 4 (Commodore 64)"},
-
- {0x7c6f, 0x0f, 0xba24, "Adrian Mole I, pt. 1 (Amstrad CPC)"},
-
- {0x72fa, 0x8b, 0x6f12, "Adrian Mole I, pt. 1 (Spectrum)"},
- {0x738e, 0x5b, 0x7e3d, "Adrian Mole I, pt. 2 (Spectrum)"},
- {0x7375, 0xe5, 0x3f3e, "Adrian Mole I, pt. 3 (Spectrum)"},
- {0x78d5, 0xe3, 0xcd7d, "Adrian Mole I, pt. 4 (Spectrum)"},
-
- {0x3a31, 0xe5, 0x0bdb, "Adrian Mole I, pt. 1 (BBC)"},
- {0x37f1, 0x77, 0xd231, "Adrian Mole I, pt. 2 (BBC)"},
- {0x3900, 0x1c, 0x5d9a, "Adrian Mole I, pt. 3 (BBC)"},
- {0x3910, 0xac, 0x07f9, "Adrian Mole I, pt. 4 (BBC)"},
- {0x3ad6, 0xa7, 0x95d2, "Adrian Mole I, pt. 5 (BBC)"},
- {0x38a5, 0x0f, 0xdefc, "Adrian Mole I, pt. 6 (BBC)"},
- {0x361e, 0x7e, 0xfd9f, "Adrian Mole I, pt. 7 (BBC)"},
- {0x3934, 0x75, 0xe141, "Adrian Mole I, pt. 8 (BBC)"},
- {0x3511, 0xcc, 0xd829, "Adrian Mole I, pt. 9 (BBC)"},
- {0x38dd, 0x31, 0x2534, "Adrian Mole I, pt. 10 (BBC)"},
- {0x39c0, 0x44, 0x89df, "Adrian Mole I, pt. 11 (BBC)"},
- {0x3a12, 0x8f, 0xc2bd, "Adrian Mole I, pt. 12 (BBC)"},
-
- {0x7931, 0xb9, 0xc51b, "Adrian Mole II, pt. 1 (Commodore 64/Amstrad CPC)"},
- {0x7cdf, 0xa5, 0x43e3, "Adrian Mole II, pt. 2 (Commodore 64/Amstrad CPC)"},
- {0x7a0c, 0x97, 0x4bea, "Adrian Mole II, pt. 3 (Commodore 64/Amstrad CPC)"},
- {0x7883, 0xe2, 0xee0e, "Adrian Mole II, pt. 4 (Commodore 64/Amstrad CPC)"},
-
- {0x6841, 0x4a, 0x94e7, "Adrian Mole II, pt. 1 (Spectrum)"},
- {0x6bc0, 0x62, 0xab3d, "Adrian Mole II, pt. 2 (Spectrum)"},
- {0x692c, 0x21, 0x2015, "Adrian Mole II, pt. 3 (Spectrum)"},
- {0x670a, 0x94, 0xa2a6, "Adrian Mole II, pt. 4 (Spectrum)"},
-
- {0x593a, 0xaf, 0x30e9, "Adrian Mole II, pt. 1 (BBC)"},
- {0x57e6, 0x8a, 0xc41a, "Adrian Mole II, pt. 2 (BBC)"},
- {0x5819, 0xcd, 0x1ba0, "Adrian Mole II, pt. 3 (BBC)"},
- {0x579b, 0xad, 0xa723, "Adrian Mole II, pt. 4 (BBC)"},
-
- {0x765d, 0xcd, 0xfc02, "The Archers, pt. 1 (Commodore 64)"},
- {0x6e58, 0x07, 0xbffc, "The Archers, pt. 2 (Commodore 64)"},
- {0x7e98, 0x6a, 0x95e5, "The Archers, pt. 3 (Commodore 64)"},
- {0x81e2, 0xd5, 0xb278, "The Archers, pt. 4 (Commodore 64)"},
-
- {0x6ce5, 0x58, 0x46de, "The Archers, pt. 1 (Spectrum)"},
- {0x68da, 0xc1, 0x3b8e, "The Archers, pt. 2 (Spectrum)"},
- {0x6c67, 0x9a, 0x9a6a, "The Archers, pt. 3 (Spectrum)"},
- {0x6d91, 0xb9, 0x12a7, "The Archers, pt. 4 (Spectrum)"},
-
- {0x5834, 0x42, 0xcc9d, "The Archers, pt. 1 (BBC)"},
- {0x56dd, 0x51, 0xe582, "The Archers, pt. 2 (BBC)"},
- {0x5801, 0x53, 0xf2ef, "The Archers, pt. 3 (BBC)"},
- {0x54a4, 0x01, 0xc0ab, "The Archers, pt. 4 (BBC)"},
-
- {0x579e, 0x97, 0x9faa, "Lords of Time /T&M GD (BBC)"},
- {0x5500, 0x50, 0xca75, "Red Moon /T&M GD (BBC)"},
- {0x579a, 0x2a, 0x9373, "Price of Magik /T&M GD (BBC)"},
-
- {0x4fd2, 0x9d, 0x799a, "Lancelot, pt. 1 GD (BBC)"},
- {0x4dac, 0xa8, 0x86ed, "Lancelot, pt. 2 GD (BBC)"},
- {0x4f96, 0x22, 0x30f8, "Lancelot, pt. 3 GD (BBC)"},
-
- {0x55ce, 0xa1, 0xba12, "Scapeghost, pt. 1 GD (BBC)"},
- {0x54a6, 0xa9, 0xc9f3, "Scapeghost, pt. 2 GD (BBC)"},
- {0x51bc, 0xe3, 0x89c3, "Scapeghost, pt. 3 GD (BBC)"},
-
- {0x46ec, 0x64, 0x2300, "Knight Orc, pt. 1 GD (Amstrad CPC/Spectrum +3)"},
- {0x6140, 0x18, 0x4f66, "Knight Orc, pt. 2 GD (Amstrad CPC/Spectrum +3)"},
- {0x640e, 0xc1, 0xfc69, "Knight Orc, pt. 3 GD (Amstrad CPC/Spectrum +3)"},
-
- {0x5ff0, 0xf8, 0x3a13, "Gnome Ranger, pt. 1 GD (Amstrad CPC/Spectrum +3)"},
- {0x6024, 0x01, 0xaaa9, "Gnome Ranger, pt. 2 GD (Amstrad CPC/Spectrum +3)"},
- {0x6036, 0x3d, 0x6c6c, "Gnome Ranger, pt. 3 GD (Amstrad CPC/Spectrum +3)"},
-
- {0x69fe, 0x56, 0xecfb, "Lords of Time /T&M GD (Amstrad CPC/Spectrum +3)"},
- {0x6888, 0x8d, 0x7f6a, "Red Moon /T&M GD (Amstrad CPC/Spectrum +3)"},
- {0x5a50, 0xa9, 0xa5fa, "Price of Magik /T&M GD (Amstrad CPC/Spectrum +3)"},
-
- {0x5c7a, 0x44, 0x460e, "Lancelot, pt. 1 GD (Amstrad CPC/Spectrum +3)"},
- {0x53a2, 0x1e, 0x2fae, "Lancelot, pt. 2 GD (Amstrad CPC/Spectrum +3)"},
- {0x5914, 0x22, 0x4a31, "Lancelot, pt. 3 GD (Amstrad CPC/Spectrum +3)"},
-
- {0x5a38, 0xf7, 0x876e, "Ingrid's Back, pt. 1 GD (Amstrad CPC/Spectrum +3)"},
- {0x531a, 0xed, 0xcf3f, "Ingrid's Back, pt. 2 GD (Amstrad CPC/Spectrum +3)"},
- {0x57e4, 0x19, 0xb354, "Ingrid's Back, pt. 3 GD (Amstrad CPC/Spectrum +3)"},
-
- {0x5cbc, 0xa5, 0x0dbe, "Scapeghost, pt. 1 GD (Amstrad CPC/Spectrum +3)"},
- {0x5932, 0x4e, 0xb2f5, "Scapeghost, pt. 2 GD (Amstrad CPC/Spectrum +3)"},
- {0x5860, 0x95, 0x3227, "Scapeghost, pt. 3 GD (Amstrad CPC/Spectrum +3)"},
-
- {0x74e0, 0x92, 0x885e, "Knight Orc, pt. 1 GD (Spectrum 128)"},
- {0x6dbc, 0x97, 0x6f55, "Knight Orc, pt. 2 GD (Spectrum 128)"},
- {0x7402, 0x07, 0x385f, "Knight Orc, pt. 3 GD (Spectrum 128)"},
-
- {0x52aa, 0xdf, 0x7b5b, "Gnome Ranger, pt. 1 GD (Spectrum 128)"},
- {0x6ffa, 0xdb, 0xdde2, "Gnome Ranger, pt. 2 GD (Spectrum 128)"},
- {0x723a, 0x69, 0x039b, "Gnome Ranger, pt. 3 GD (Spectrum 128)"},
-
- {0x6f1e, 0xda, 0x2ce0, "Lords of Time /T&M GD (Spectrum 128)"},
- {0x6da0, 0xb8, 0x3802, "Red Moon /T&M GD (Spectrum 128)"},
- {0x6108, 0xdd, 0xefe7, "Price of Magik /T&M GD (Spectrum 128)"},
-
- {0x768c, 0xe8, 0x8fc6, "Lancelot, pt. 1 GD (Spectrum 128)"},
- {0x76b0, 0x1d, 0x0fcd, "Lancelot, pt. 2 GD (Spectrum 128)"},
- {0x765e, 0x4f, 0x3b73, "Lancelot, pt. 3 GD (Spectrum 128)"},
-
- {0x76a0, 0x3a, 0xb803, "Ingrid's Back, pt. 1 GD (Spectrum 128)"},
- {0x7674, 0x0b, 0xe92f, "Ingrid's Back, pt. 2 GD (Spectrum 128)"},
- {0x765e, 0xba, 0x086d, "Ingrid's Back, pt. 3 GD (Spectrum 128)"},
-
- {0x762e, 0x82, 0x8848, "Scapeghost, pt. 1 GD (Spectrum 128)"},
- {0x5bd6, 0x35, 0x79ef, "Scapeghost, pt. 2 GD (Spectrum 128)"},
- {0x6fa8, 0xa4, 0x62c2, "Scapeghost, pt. 3 GD (Spectrum 128)"},
-
- {0xbb93, 0x36, 0x6a05, "Knight Orc, pt. 1 (Amiga)"},
- {0xbb6e, 0xad, 0x4d40, "Knight Orc, pt. 1 (ST)"},
- {0xc58e, 0x4a, 0x4e9d, "Knight Orc, pt. 2 (Amiga/ST)"},
- {0xcb9a, 0x0f, 0x0804, "Knight Orc, pt. 3 (Amiga/ST)"},
-
- {0xbb6e, 0xa6, 0x9753, "Knight Orc, pt. 1 (PC)"},
- {0xc58e, 0x43, 0xe9ce, "Knight Orc, pt. 2 (PC)"},
- {0xcb9a, 0x08, 0x6c36, "Knight Orc, pt. 3 (PC)"},
-
- {0x898a, 0x43, 0xfc8b, "Knight Orc, pt. 1 (Apple ][)"},
- {0x8b9f, 0x61, 0x7288, "Knight Orc, pt. 2 (Apple ][)"},
- {0x8af9, 0x61, 0x7542, "Knight Orc, pt. 3 (Apple ][)"},
-
- {0x8970, 0x6b, 0x3c7b, "Knight Orc, pt. 1 (Commodore 64 Gfx)"},
- {0x8b90, 0x4e, 0x098c, "Knight Orc, pt. 2 (Commodore 64 Gfx)"},
- {0x8aea, 0x4e, 0xca54, "Knight Orc, pt. 3 (Commodore 64 Gfx)"},
-
- {0x86d0, 0xb7, 0xadbd, "Knight Orc, pt. 1 (Spectrum 48)"},
- {0x8885, 0x22, 0xe293, "Knight Orc, pt. 2 (Spectrum 48)"},
- {0x87e5, 0x0e, 0xdc33, "Knight Orc, pt. 3 (Spectrum 48)"},
-
- {0xb1a9, 0x80, 0x5fb7, "Gnome Ranger, pt. 1 (Amiga/ST)"},
- {0xab9d, 0x31, 0xbe6d, "Gnome Ranger, pt. 2 (Amiga/ST)"},
- {0xae28, 0x87, 0xb6b6, "Gnome Ranger, pt. 3 (Amiga/ST)"},
-
- {0xb0ec, 0xc2, 0x0053, "Gnome Ranger, pt. 1 (ST[v1])"},
- {0xaf82, 0x83, 0x19f7, "Gnome Ranger, pt. 2 (ST[v1])"},
-
- {0xb1aa, 0xad, 0xaf47, "Gnome Ranger, pt. 1 (PC)"},
- {0xb19e, 0x92, 0x8f96, "Gnome Ranger, pt. 1 (ST[v2])"},
- {0xab8b, 0xbf, 0x31e6, "Gnome Ranger, pt. 2 (PC/ST[v2])"},
- {0xae16, 0x81, 0x8741, "Gnome Ranger, pt. 3 (PC/ST[v2])"},
-
- {0xad41, 0xa8, 0x42c5, "Gnome Ranger, pt. 1 (Commodore 64 TO)"},
- {0xa735, 0xf7, 0x2e08, "Gnome Ranger, pt. 2 (Commodore 64 TO)"},
- {0xa9c0, 0x9e, 0x0d70, "Gnome Ranger, pt. 3 (Commodore 64 TO)"},
-
- {0x908e, 0x0d, 0x58a7, "Gnome Ranger, pt. 1 (Commodore 64 Gfx)"},
- {0x8f6f, 0x0a, 0x411a, "Gnome Ranger, pt. 2 (Commodore 64 Gfx)"},
- {0x9060, 0xbb, 0xe75d, "Gnome Ranger, pt. 3 (Commodore 64 Gfx)"},
-
- {0x8aab, 0xc0, 0xde5f, "Gnome Ranger, pt. 1 (Spectrum 48)"},
- {0x8ac8, 0x9a, 0xc89b, "Gnome Ranger, pt. 2 (Spectrum 48)"},
- {0x8a93, 0x4f, 0x10cc, "Gnome Ranger, pt. 3 (Spectrum 48)"},
-
- {0xb57c, 0x44, 0x7779, "Lords of Time /T&M (PC)"},
- {0xa69e, 0x6c, 0xb268, "Red Moon /T&M (PC)"},
- {0xbac7, 0x7f, 0xddb2, "Price of Magik /T&M (PC)"},
-
- {0xb579, 0x89, 0x3e89, "Lords of Time /T&M (ST)"},
- {0xa698, 0x41, 0xcaca, "Red Moon /T&M (ST)"},
- {0xbac4, 0x80, 0xa750, "Price of Magik /T&M (ST)"},
-
- {0xb576, 0x2a, 0x7239, "Lords of Time /T&M (Amiga)"},
- {0xa692, 0xd1, 0x6a99, "Red Moon /T&M (Amiga)"},
- {0xbaca, 0x3a, 0x221b, "Price of Magik /T&M (Amiga)"},
-
- {0xb563, 0x6a, 0x0c5c, "Lords of Time /T&M (Mac)"},
- {0xa67c, 0xb8, 0xff41, "Red Moon /T&M (Mac)"},
- {0xbab2, 0x87, 0x09f5, "Price of Magik /T&M (Mac)"},
-
- {0xb38c, 0x37, 0x9f8e, "Lords of Time /T&M (Commodore 64 TO)"},
- {0xa4e2, 0xa6, 0x016d, "Red Moon /T&M (Commodore 64 TO)"},
- {0xb451, 0xa8, 0x2682, "Price of Magik /T&M (Commodore 64 TO)"},
-
- {0x9070, 0x43, 0x45d4, "Lords of Time /T&M (Commodore 64 Gfx)"},
- {0x903f, 0x6b, 0x603e, "Red Moon /T&M (Commodore 64 Gfx)"},
- {0x8f51, 0xb2, 0x6c9a, "Price of Magik /T&M (Commodore 64 Gfx)"},
-
- {0x8950, 0xa1, 0xbb16, "Lords of Time /T&M (Spectrum 48)"},
- {0x8813, 0x11, 0x22de, "Red Moon /T&M (Spectrum 48)"},
- {0x8a60, 0x2a, 0x29ed, "Price of Magik /T&M (Spectrum 48)"},
-
- {0xb260, 0xe5, 0xc5b2, "Lords of Time /T&M (PC/ST *USA*)"},
- {0xa3a4, 0xdf, 0x6732, "Red Moon /T&M (PC/ST *USA*)"},
- {0xb7a0, 0x7e, 0x2226, "Price of Magik /T&M (PC/ST *USA*)"},
-
- {0xb257, 0xf8, 0xfbd5, "Lords of Time /T&M (Amiga *USA*)"},
- {0xa398, 0x82, 0xd031, "Red Moon /T&M (Amiga *USA*)"},
- {0xb797, 0x1f, 0x84a9, "Price of Magik /T&M (Amiga *USA*)"},
-
- {0x8d78, 0x3a, 0xba6e, "Lords of Time /T&M (Commodore 64 Gfx *USA*)"},
- {0x8d56, 0xd3, 0x146a, "Red Moon /T&M (Commodore 64 Gfx *USA*)"},
- {0x8c46, 0xf0, 0xcaf6, "Price of Magik /T&M (Commodore 64 Gfx *USA*)"},
-
- {0xc0cf, 0x4e, 0xb7fa, "Lancelot, pt. 1 (Amiga/PC/ST)"},
- {0xd5e9, 0x6a, 0x4192, "Lancelot, pt. 2 (Amiga/PC/ST)"},
- {0xbb8f, 0x1a, 0x7487, "Lancelot, pt. 3 (Amiga/PC/ST)"},
-
- {0xc0bd, 0x57, 0x6ef1, "Lancelot, pt. 1 (Mac)"},
- {0xd5d7, 0x99, 0x770b, "Lancelot, pt. 2 (Mac)"},
- {0xbb7d, 0x17, 0xbc42, "Lancelot, pt. 3 (Mac)"},
-
- {0xb4c9, 0x94, 0xd784, "Lancelot, pt. 1 (Commodore 64 TO)"},
- {0xb729, 0x51, 0x8ee5, "Lancelot, pt. 2 (Commodore 64 TO)"},
- {0xb702, 0xe4, 0x1809, "Lancelot, pt. 3 (Commodore 64 TO)"},
-
- {0x8feb, 0xba, 0xa800, "Lancelot, pt. 1 (Commodore 64 Gfx)"},
- {0x8f6b, 0xfa, 0x0f7e, "Lancelot, pt. 2 (Commodore 64 Gfx)"},
- {0x8f71, 0x2f, 0x0ddc, "Lancelot, pt. 3 (Commodore 64 Gfx)"},
-
- {0x8ade, 0xf2, 0xfffb, "Lancelot, pt. 1 (Spectrum 48)"},
- {0x8b0e, 0xfb, 0x0bab, "Lancelot, pt. 2 (Spectrum 48)"},
- {0x8ab3, 0xc1, 0xcb62, "Lancelot, pt. 3 (Spectrum 48)"},
-
- {0xbba4, 0x94, 0x0871, "Lancelot, pt. 1 (Amiga/PC *USA*)"},
- {0xd0c0, 0x56, 0x8c48, "Lancelot, pt. 2 (Amiga/PC *USA*)"},
- {0xb6ac, 0xc6, 0xaea0, "Lancelot, pt. 3 (Amiga/PC *USA*)"},
-
- {0x8afc, 0x07, 0x8321, "Lancelot, pt. 1 (Commodore 64 Gfx *USA*)"},
- {0x8aec, 0x13, 0x6791, "Lancelot, pt. 2 (Commodore 64 Gfx *USA*)"},
- {0x8aba, 0x0d, 0x5602, "Lancelot, pt. 3 (Commodore 64 Gfx *USA*)"},
-
- {0xd19b, 0xad, 0x306d, "Ingrid's Back, pt. 1 (PC)"},
- {0xc5a5, 0xfe, 0x3c98, "Ingrid's Back, pt. 2 (PC)"},
- {0xd7ae, 0x9e, 0x1878, "Ingrid's Back, pt. 3 (PC)"},
-
- {0xd188, 0x13, 0xdc60, "Ingrid's Back, pt. 1 (Amiga)"},
- {0xc594, 0x03, 0xea95, "Ingrid's Back, pt. 2 (Amiga)"},
- {0xd79f, 0xb5, 0x1661, "Ingrid's Back, pt. 3 (Amiga)"},
-
- {0xd183, 0x83, 0xef72, "Ingrid's Back, pt. 1 (ST)"},
- {0xc58f, 0x65, 0xf337, "Ingrid's Back, pt. 2 (ST)"},
- {0xd79a, 0x57, 0x49c5, "Ingrid's Back, pt. 3 (ST)"},
-
- {0xb770, 0x03, 0x9a03, "Ingrid's Back, pt. 1 (Commodore 64 TO)"},
- {0xb741, 0xb6, 0x2aa5, "Ingrid's Back, pt. 2 (Commodore 64 TO)"},
- {0xb791, 0xa1, 0xd065, "Ingrid's Back, pt. 3 (Commodore 64 TO)"},
-
- {0x9089, 0xce, 0xc5e2, "Ingrid's Back, pt. 1 (Commodore 64 Gfx)"},
- {0x908d, 0x80, 0x30c7, "Ingrid's Back, pt. 2 (Commodore 64 Gfx)"},
- {0x909e, 0x9f, 0xdecc, "Ingrid's Back, pt. 3 (Commodore 64 Gfx)"},
-
- {0x8ab7, 0x68, 0xee57, "Ingrid's Back, pt. 1 (Spectrum 48)"},
- {0x8b1e, 0x84, 0x2538, "Ingrid's Back, pt. 2 (Spectrum 48)"},
- {0x8b1c, 0xa8, 0x9262, "Ingrid's Back, pt. 3 (Spectrum 48)"},
-
- {0xbeab, 0x2d, 0x94d9, "Scapeghost, pt. 1 (Amiga)"},
- {0xc132, 0x14, 0x7adc, "Scapeghost, pt. 1 (Amiga *bak*)"},
- {0xbe94, 0xcc, 0x04b8, "Scapeghost, pt. 1 (PC/ST)"},
- {0x99bd, 0x65, 0x032e, "Scapeghost, pt. 2 (Amiga/PC/ST)"},
- {0xbcb6, 0x7a, 0x7d4f, "Scapeghost, pt. 3 (Amiga/PC/ST)"},
-
- {0x9058, 0xcf, 0x9748, "Scapeghost, pt. 1 (Commodore 64 Gfx)"},
- {0x8f43, 0xc9, 0xeefd, "Scapeghost, pt. 2 (Commodore 64 Gfx)"},
- {0x90ac, 0x68, 0xb4a8, "Scapeghost, pt. 3 (Commodore 64 Gfx)"},
-
- {0x8a21, 0xf4, 0xd9e4, "Scapeghost, pt. 1 (Spectrum 48)"},
- {0x8a12, 0xe3, 0xc2ff, "Scapeghost, pt. 2 (Spectrum 48)"},
- {0x8a16, 0xcc, 0x4f3b, "Scapeghost, pt. 3 (Spectrum 48)"},
-
- {0x3ebb, 0x00, 0xf6dc, "Champion of the Raj (English) 1/2 GD (Amiga)"},
- {0x0fd8, 0x00, 0xf250, "Champion of the Raj (English) 2/2 GD (Amiga)"},
-
- {0x3e8f, 0x00, 0x5599, "Champion of the Raj (English) 1/2 GD (ST)"},
-
- {0x3e4f, 0x00, 0xb202, "Champion of the Raj (English) 1/2 GD (PC)"},
- {0x14a3, 0x00, 0xa288, "Champion of the Raj (English) 2/2 GD (PC)"},
-
- {0x1929, 0x00, 0xd4b2, "Champion of the Raj (demo), 1/2 GD (ST)"},
- {0x40e0, 0x02, 0x080d, "Champion of the Raj (demo), 2/2 GD (ST)"},
-
- {0x4872, 0x00, 0x9515, "Champion of the Raj (German) 1/2 GD (Amiga)"},
- {0x11f5, 0x00, 0xbf39, "Champion of the Raj (German) 2/2 GD (Amiga)"},
-
- {0x4846, 0x00, 0xd9c1, "Champion of the Raj (German) 1/2 GD (ST)"},
- {0x11f5, 0x00, 0x7aa4, "Champion of the Raj (German) 2/2 GD (ST)"},
-
- {0x110f, 0x00, 0x4b57, "Champion of the Raj (French) 2/2 GD (ST)"},
-
- {0x0000, 0x00, 0x0000, nullptr}
+ {0x8333, 0xb7, 0xe2ac, "adrianmole1", "Adrian Mole I", "pt. 1/Commodore 64"},
+ {0x844d, 0x50, 0x5353, "adrianmole1", "Adrian Mole I", "pt. 2/Commodore 64"},
+ {0x8251, 0x5f, 0x862a, "adrianmole1", "Adrian Mole I", "pt. 3/Commodore 64"},
+ {0x7a78, 0x5e, 0x6ea3, "adrianmole1", "Adrian Mole I", "pt. 4/Commodore 64"},
+
+ {0x7c6f, 0x0f, 0xba24, "adrianmole1", "Adrian Mole I", "pt. 1/Amstrad CPC"},
+
+ {0x72fa, 0x8b, 0x6f12, "adrianmole1", "Adrian Mole I", "pt. 1/Spectrum"},
+ {0x738e, 0x5b, 0x7e3d, "adrianmole1", "Adrian Mole I", "pt. 2/Spectrum"},
+ {0x7375, 0xe5, 0x3f3e, "adrianmole1", "Adrian Mole I", "pt. 3/Spectrum"},
+ {0x78d5, 0xe3, 0xcd7d, "adrianmole1", "Adrian Mole I", "pt. 4/Spectrum"},
+
+ {0x3a31, 0xe5, 0x0bdb, "adrianmole1", "Adrian Mole I", "pt. 1/BBC"},
+ {0x37f1, 0x77, 0xd231, "adrianmole1", "Adrian Mole I", "pt. 2/BBC"},
+ {0x3900, 0x1c, 0x5d9a, "adrianmole1", "Adrian Mole I", "pt. 3/BBC"},
+ {0x3910, 0xac, 0x07f9, "adrianmole1", "Adrian Mole I", "pt. 4/BBC"},
+ {0x3ad6, 0xa7, 0x95d2, "adrianmole1", "Adrian Mole I", "pt. 5/BBC"},
+ {0x38a5, 0x0f, 0xdefc, "adrianmole1", "Adrian Mole I", "pt. 6/BBC"},
+ {0x361e, 0x7e, 0xfd9f, "adrianmole1", "Adrian Mole I", "pt. 7/BBC"},
+ {0x3934, 0x75, 0xe141, "adrianmole1", "Adrian Mole I", "pt. 8/BBC"},
+ {0x3511, 0xcc, 0xd829, "adrianmole1", "Adrian Mole I", "pt. 9/BBC"},
+ {0x38dd, 0x31, 0x2534, "adrianmole1", "Adrian Mole I", "pt. 10/BBC"},
+ {0x39c0, 0x44, 0x89df, "adrianmole1", "Adrian Mole I", "pt. 11/BBC"},
+ {0x3a12, 0x8f, 0xc2bd, "adrianmole1", "Adrian Mole I", "pt. 12/BBC"},
+
+ {0x7931, 0xb9, 0xc51b, "adrianmole2", "Adrian Mole II", "pt. 1/Commodore 64/Amstrad CPC"},
+ {0x7cdf, 0xa5, 0x43e3, "adrianmole2", "Adrian Mole II", "pt. 2/Commodore 64/Amstrad CPC"},
+ {0x7a0c, 0x97, 0x4bea, "adrianmole2", "Adrian Mole II", "pt. 3/Commodore 64/Amstrad CPC"},
+ {0x7883, 0xe2, 0xee0e, "adrianmole2", "Adrian Mole II", "pt. 4/Commodore 64/Amstrad CPC"},
+
+ {0x6841, 0x4a, 0x94e7, "adrianmole2", "Adrian Mole II", "pt. 1/Spectrum"},
+ {0x6bc0, 0x62, 0xab3d, "adrianmole2", "Adrian Mole II", "pt. 2/Spectrum"},
+ {0x692c, 0x21, 0x2015, "adrianmole2", "Adrian Mole II", "pt. 3/Spectrum"},
+ {0x670a, 0x94, 0xa2a6, "adrianmole2", "Adrian Mole II", "pt. 4/Spectrum"},
+
+ {0x593a, 0xaf, 0x30e9, "adrianmole2", "Adrian Mole II", "pt. 1/BBC"},
+ {0x57e6, 0x8a, 0xc41a, "adrianmole2", "Adrian Mole II", "pt. 2/BBC"},
+ {0x5819, 0xcd, 0x1ba0, "adrianmole2", "Adrian Mole II", "pt. 3/BBC"},
+ {0x579b, 0xad, 0xa723, "adrianmole2", "Adrian Mole II", "pt. 4/BBC"},
+
+ {0x765d, 0xcd, 0xfc02, "thearchers", "The Archers", "pt. 1/Commodore 64"},
+ {0x6e58, 0x07, 0xbffc, "thearchers", "The Archers", "pt. 2/Commodore 64"},
+ {0x7e98, 0x6a, 0x95e5, "thearchers", "The Archers", "pt. 3/Commodore 64"},
+ {0x81e2, 0xd5, 0xb278, "thearchers", "The Archers", "pt. 4/Commodore 64"},
+
+ {0x6ce5, 0x58, 0x46de, "thearchers", "The Archers", "pt. 1/Spectrum"},
+ {0x68da, 0xc1, 0x3b8e, "thearchers", "The Archers", "pt. 2/Spectrum"},
+ {0x6c67, 0x9a, 0x9a6a, "thearchers", "The Archers", "pt. 3/Spectrum"},
+ {0x6d91, 0xb9, 0x12a7, "thearchers", "The Archers", "pt. 4/Spectrum"},
+
+ {0x5834, 0x42, 0xcc9d, "thearchers", "The Archers", "pt. 1/BBC"},
+ {0x56dd, 0x51, 0xe582, "thearchers", "The Archers", "pt. 2/BBC"},
+ {0x5801, 0x53, 0xf2ef, "thearchers", "The Archers", "pt. 3/BBC"},
+ {0x54a4, 0x01, 0xc0ab, "thearchers", "The Archers", "pt. 4/BBC"},
+
+ {0x5323, 0xb7, 0x8af7, "adventurequest", "Adventure Quest", "Amstrad CPC/Spectrum"},
+ {0x6e60, 0x83, 0x18e0, "adventurequest", "Adventure Quest /JoD", "Amiga/PC"},
+ {0x6e5c, 0xf6, 0xd356, "adventurequest", "Adventure Quest /JoD", "ST"},
+ {0x6970, 0xd6, 0xa820, "adventurequest", "Adventure Quest /JoD", "Spectrum 128"},
+ {0x6968, 0x32, 0x0c01, "adventurequest", "Adventure Quest /JoD", "Amstrad CPC128/Spectrum +3"},
+ {0x5b50, 0x66, 0x1800, "adventurequest", "Adventure Quest /JoD", "Amstrad CPC64"},
+ {0x63b6, 0x2e, 0xef38, "adventurequest", "Adventure Quest /JoD", "Commodore 64"},
+ {0x5b58, 0x50, 0x332e, "adventurequest", "Adventure Quest /JoD", "Atari"},
+ {0x5ace, 0x11, 0xdc12, "adventurequest", "Adventure Quest /JoD", "Spectrum 48"},
+
+ {0x3ebb, 0x00, 0xf6dc, "championraj", "Champion of the Raj", "English 1/2 GD/Amiga"},
+ {0x0fd8, 0x00, 0xf250, "championraj", "Champion of the Raj", "English 2/2 GD/Amiga"},
+
+ {0x3e8f, 0x00, 0x5599, "championraj", "Champion of the Raj", "English 1/2 GD/ST"},
+
+ {0x3e4f, 0x00, 0xb202, "championraj", "Champion of the Raj", "English 1/2 GD/PC"},
+ {0x14a3, 0x00, 0xa288, "championraj", "Champion of the Raj", "English 2/2 GD/PC"},
+
+ {0x1929, 0x00, 0xd4b2, "championraj", "Champion of the Raj", "demo, 1/2 GD/ST"},
+ {0x40e0, 0x02, 0x080d, "championraj", "Champion of the Raj", "demo, 2/2 GD/ST"},
+
+ {0x4872, 0x00, 0x9515, "championraj", "Champion of the Raj", "German 1/2 GD/Amiga"},
+ {0x11f5, 0x00, 0xbf39, "championraj", "Champion of the Raj", "German 2/2 GD/Amiga"},
+
+ {0x4846, 0x00, 0xd9c1, "championraj", "Champion of the Raj", "German 1/2 GD/ST"},
+ {0x11f5, 0x00, 0x7aa4, "championraj", "Champion of the Raj", "German 2/2 GD/ST"},
+
+ {0x110f, 0x00, 0x4b57, "championraj", "Champion of the Raj", "French 2/2 GD/ST"},
+
+ {0x76f4, 0x5e, 0x1fe5, "colossaladvjod", "Colossal Adventure /JoD", "Amiga/PC"},
+ {0x76f4, 0x5a, 0xcf4b, "colossaladvjod", "Colossal Adventure /JoD", "ST"},
+ {0x6f70, 0x40, 0xbd91, "colossaladvjod", "Colossal Adventure /JoD", "MSX"},
+ {0x6f6e, 0x78, 0x28cd, "colossaladvjod", "Colossal Adventure /JoD", "Spectrum 128"},
+ {0x6f4d, 0xcb, 0xe8f2, "colossaladvjod", "Colossal Adventure /JoD", "Amstrad CPC128[v1]/Spectrum +3"},
+ {0x6f6a, 0xa5, 0x8dd2, "colossaladvjod", "Colossal Adventure /JoD", "Amstrad CPC128[v2]"},
+ {0x5e31, 0x7c, 0xaa54, "colossaladvjod", "Colossal Adventure /JoD", "Amstrad CPC64"},
+ {0x6c8e, 0xb6, 0x9be3, "colossaladvjod", "Colossal Adventure /JoD", "Commodore 64"},
+ {0x5b16, 0x3b, 0xe2aa, "colossaladvjod", "Colossal Adventure /JoD", "Atari"},
+ {0x5a8e, 0xf2, 0x7cca, "colossaladvjod", "Colossal Adventure /JoD", "Spectrum 48"},
+
+ {0x630e, 0x8d, 0x7d7d, "dungeonadv", "Dungeon Adventure", "Amstrad CPC"},
+ {0x630e, 0xbe, 0x3374, "dungeonadv", "Dungeon Adventure", "MSX"},
+ {0x6de8, 0x4c, 0xd795, "dungeonadvjod", "Dungeon Adventure /JoD", "Spectrum 128"},
+ {0x6dc0, 0x63, 0x5d95, "dungeonadvjod", "Dungeon Adventure /JoD", "Amstrad CPC128/Spectrum +3"},
+ {0x58a6, 0x24, 0xb50f, "dungeonadvjod", "Dungeon Adventure /JoD", "Amstrad CPC64"},
+ {0x6bd2, 0x65, 0xa41f, "dungeonadvjod", "Dungeon Adventure /JoD", "Commodore 64"},
+ {0x593a, 0x80, 0x7a34, "dungeonadvjod", "Dungeon Adventure /JoD", "Atari"},
+ {0x58a3, 0x38, 0x8ce4, "dungeonadvjod", "Dungeon Adventure /JoD", "Spectrum 48"},
+ {0x6f0c, 0x95, 0x1f64, "dungeonadvjod", "Dungeon Adventure /JoD", "Amiga/PC/ST"},
+
+ {0x63be, 0xd6, 0xcf5d, "emeraldisle", "Emerald Isle", "Atari/Commodore 64/Amstrad CPC/Spectrum"},
+ {0x63be, 0x0a, 0x21ed, "emeraldisle", "Emerald Isle", "MSX *corrupt*"},
+ {0x378c, 0x8d, 0x3a21, "emeraldisle", "Emerald Isle", "BBC"},
+
+ {0x34b3, 0x20, 0xccda, "eriktheviking", "Erik the Viking", "BBC/Commodore 64"},
+ {0x34b3, 0x53, 0x8f00, "eriktheviking", "Erik the Viking", "Spectrum"},
+ {0x34b3, 0xc7, 0x9058, "eriktheviking", "Erik the Viking", "Amstrad CPC"},
+
+ {0x5ff0, 0xf8, 0x3a13, "gnomeranger", "Gnome Ranger", "pt. 1 GD/Amstrad CPC/Spectrum +3"},
+ {0x6024, 0x01, 0xaaa9, "gnomeranger", "Gnome Ranger", "pt. 2 GD/Amstrad CPC/Spectrum +3"},
+ {0x6036, 0x3d, 0x6c6c, "gnomeranger", "Gnome Ranger", "pt. 3 GD/Amstrad CPC/Spectrum +3"},
+
+ {0x52aa, 0xdf, 0x7b5b, "gnomeranger", "Gnome Ranger", "pt. 1 GD/Spectrum 128"},
+ {0x6ffa, 0xdb, 0xdde2, "gnomeranger", "Gnome Ranger", "pt. 2 GD/Spectrum 128"},
+ {0x723a, 0x69, 0x039b, "gnomeranger", "Gnome Ranger", "pt. 3 GD/Spectrum 128"},
+
+ {0xb1a9, 0x80, 0x5fb7, "gnomeranger", "Gnome Ranger", "pt. 1/Amiga/ST"},
+ {0xab9d, 0x31, 0xbe6d, "gnomeranger", "Gnome Ranger", "pt. 2/Amiga/ST"},
+ {0xae28, 0x87, 0xb6b6, "gnomeranger", "Gnome Ranger", "pt. 3/Amiga/ST"},
+
+ {0xb0ec, 0xc2, 0x0053, "gnomeranger", "Gnome Ranger", "pt. 1/ST[v1]"},
+ {0xaf82, 0x83, 0x19f7, "gnomeranger", "Gnome Ranger", "pt. 2/ST[v1]"},
+
+ {0xb1aa, 0xad, 0xaf47, "gnomeranger", "Gnome Ranger", "pt. 1/PC"},
+ {0xb19e, 0x92, 0x8f96, "gnomeranger", "Gnome Ranger", "pt. 1/ST[v2]"},
+ {0xab8b, 0xbf, 0x31e6, "gnomeranger", "Gnome Ranger", "pt. 2/PC/ST[v2]"},
+ {0xae16, 0x81, 0x8741, "gnomeranger", "Gnome Ranger", "pt. 3/PC/ST[v2]"},
+
+ {0xad41, 0xa8, 0x42c5, "gnomeranger", "Gnome Ranger", "pt. 1/Commodore 64 TO"},
+ {0xa735, 0xf7, 0x2e08, "gnomeranger", "Gnome Ranger", "pt. 2/Commodore 64 TO"},
+ {0xa9c0, 0x9e, 0x0d70, "gnomeranger", "Gnome Ranger", "pt. 3/Commodore 64 TO"},
+
+ {0x908e, 0x0d, 0x58a7, "gnomeranger", "Gnome Ranger", "pt. 1/Commodore 64 Gfx"},
+ {0x8f6f, 0x0a, 0x411a, "gnomeranger", "Gnome Ranger", "pt. 2/Commodore 64 Gfx"},
+ {0x9060, 0xbb, 0xe75d, "gnomeranger", "Gnome Ranger", "pt. 3/Commodore 64 Gfx"},
+
+ {0x8aab, 0xc0, 0xde5f, "gnomeranger", "Gnome Ranger", "pt. 1/Spectrum 48"},
+ {0x8ac8, 0x9a, 0xc89b, "gnomeranger", "Gnome Ranger", "pt. 2/Spectrum 48"},
+ {0x8a93, 0x4f, 0x10cc, "gnomeranger", "Gnome Ranger", "pt. 3/Spectrum 48"},
+
+ {0x5a38, 0xf7, 0x876e, "ingridsback", "Ingrid's Back", "pt. 1 GD/Amstrad CPC/Spectrum +3"},
+ {0x531a, 0xed, 0xcf3f, "ingridsback", "Ingrid's Back", "pt. 2 GD/Amstrad CPC/Spectrum +3"},
+ {0x57e4, 0x19, 0xb354, "ingridsback", "Ingrid's Back", "pt. 3 GD/Amstrad CPC/Spectrum +3"},
+
+ {0x76a0, 0x3a, 0xb803, "ingridsback", "Ingrid's Back", "pt. 1 GD/Spectrum 128"},
+ {0x7674, 0x0b, 0xe92f, "ingridsback", "Ingrid's Back", "pt. 2 GD/Spectrum 128"},
+ {0x765e, 0xba, 0x086d, "ingridsback", "Ingrid's Back", "pt. 3 GD/Spectrum 128"},
+
+ {0xd19b, 0xad, 0x306d, "ingridsback", "Ingrid's Back", "pt. 1/PC"},
+ {0xc5a5, 0xfe, 0x3c98, "ingridsback", "Ingrid's Back", "pt. 2/PC"},
+ {0xd7ae, 0x9e, 0x1878, "ingridsback", "Ingrid's Back", "pt. 3/PC"},
+
+ {0xd188, 0x13, 0xdc60, "ingridsback", "Ingrid's Back", "pt. 1/Amiga"},
+ {0xc594, 0x03, 0xea95, "ingridsback", "Ingrid's Back", "pt. 2/Amiga"},
+ {0xd79f, 0xb5, 0x1661, "ingridsback", "Ingrid's Back", "pt. 3/Amiga"},
+
+ {0xd183, 0x83, 0xef72, "ingridsback", "Ingrid's Back", "pt. 1/ST"},
+ {0xc58f, 0x65, 0xf337, "ingridsback", "Ingrid's Back", "pt. 2/ST"},
+ {0xd79a, 0x57, 0x49c5, "ingridsback", "Ingrid's Back", "pt. 3/ST"},
+
+ {0xb770, 0x03, 0x9a03, "ingridsback", "Ingrid's Back", "pt. 1/Commodore 64 TO"},
+ {0xb741, 0xb6, 0x2aa5, "ingridsback", "Ingrid's Back", "pt. 2/Commodore 64 TO"},
+ {0xb791, 0xa1, 0xd065, "ingridsback", "Ingrid's Back", "pt. 3/Commodore 64 TO"},
+
+ {0x9089, 0xce, 0xc5e2, "ingridsback", "Ingrid's Back", "pt. 1/Commodore 64 Gfx"},
+ {0x908d, 0x80, 0x30c7, "ingridsback", "Ingrid's Back", "pt. 2/Commodore 64 Gfx"},
+ {0x909e, 0x9f, 0xdecc, "ingridsback", "Ingrid's Back", "pt. 3/Commodore 64 Gfx"},
+
+ {0x8ab7, 0x68, 0xee57, "ingridsback", "Ingrid's Back", "pt. 1/Spectrum 48"},
+ {0x8b1e, 0x84, 0x2538, "ingridsback", "Ingrid's Back", "pt. 2/Spectrum 48"},
+ {0x8b1c, 0xa8, 0x9262, "ingridsback", "Ingrid's Back", "pt. 3/Spectrum 48"},
+
+ {0x46ec, 0x64, 0x2300, "knightorc", "Knight Orc", "pt. 1 GD/Amstrad CPC/Spectrum +3"},
+ {0x6140, 0x18, 0x4f66, "knightorc", "Knight Orc", "pt. 2 GD/Amstrad CPC/Spectrum +3"},
+ {0x640e, 0xc1, 0xfc69, "knightorc", "Knight Orc", "pt. 3 GD/Amstrad CPC/Spectrum +3"},
+
+ {0x74e0, 0x92, 0x885e, "knightorc", "Knight Orc", "pt. 1 GD/Spectrum 128"},
+ {0x6dbc, 0x97, 0x6f55, "knightorc", "Knight Orc", "pt. 2 GD/Spectrum 128"},
+ {0x7402, 0x07, 0x385f, "knightorc", "Knight Orc", "pt. 3 GD/Spectrum 128"},
+
+ {0xbb93, 0x36, 0x6a05, "knightorc", "Knight Orc", "pt. 1/Amiga"},
+ {0xbb6e, 0xad, 0x4d40, "knightorc", "Knight Orc", "pt. 1/ST"},
+ {0xc58e, 0x4a, 0x4e9d, "knightorc", "Knight Orc", "pt. 2/Amiga/ST"},
+ {0xcb9a, 0x0f, 0x0804, "knightorc", "Knight Orc", "pt. 3/Amiga/ST"},
+
+ {0xbb6e, 0xa6, 0x9753, "knightorc", "Knight Orc", "pt. 1/PC"},
+ {0xc58e, 0x43, 0xe9ce, "knightorc", "Knight Orc", "pt. 2/PC"},
+ {0xcb9a, 0x08, 0x6c36, "knightorc", "Knight Orc", "pt. 3/PC"},
+
+ {0x898a, 0x43, 0xfc8b, "knightorc", "Knight Orc", "pt. 1/Apple ]["},
+ {0x8b9f, 0x61, 0x7288, "knightorc", "Knight Orc", "pt. 2/Apple ]["},
+ {0x8af9, 0x61, 0x7542, "knightorc", "Knight Orc", "pt. 3/Apple ]["},
+
+ {0x8970, 0x6b, 0x3c7b, "knightorc", "Knight Orc", "pt. 1/Commodore 64 Gfx"},
+ {0x8b90, 0x4e, 0x098c, "knightorc", "Knight Orc", "pt. 2/Commodore 64 Gfx"},
+ {0x8aea, 0x4e, 0xca54, "knightorc", "Knight Orc", "pt. 3/Commodore 64 Gfx"},
+
+ {0x86d0, 0xb7, 0xadbd, "knightorc", "Knight Orc", "pt. 1/Spectrum 48"},
+ {0x8885, 0x22, 0xe293, "knightorc", "Knight Orc", "pt. 2/Spectrum 48"},
+ {0x87e5, 0x0e, 0xdc33, "knightorc", "Knight Orc", "pt. 3/Spectrum 48"},
+
+ {0x4fd2, 0x9d, 0x799a, "lancelot", "Lancelot", "pt. 1 GD/BBC"},
+ {0x4dac, 0xa8, 0x86ed, "lancelot", "Lancelot", "pt. 2 GD/BBC"},
+ {0x4f96, 0x22, 0x30f8, "lancelot", "Lancelot", "pt. 3 GD/BBC"},
+
+ {0x5c7a, 0x44, 0x460e, "lancelot", "Lancelot", "pt. 1 GD/Amstrad CPC/Spectrum +3"},
+ {0x53a2, 0x1e, 0x2fae, "lancelot", "Lancelot", "pt. 2 GD/Amstrad CPC/Spectrum +3"},
+ {0x5914, 0x22, 0x4a31, "lancelot", "Lancelot", "pt. 3 GD/Amstrad CPC/Spectrum +3"},
+
+ {0x768c, 0xe8, 0x8fc6, "lancelot", "Lancelot", "pt. 1 GD/Spectrum 128"},
+ {0x76b0, 0x1d, 0x0fcd, "lancelot", "Lancelot", "pt. 2 GD/Spectrum 128"},
+ {0x765e, 0x4f, 0x3b73, "lancelot", "Lancelot", "pt. 3 GD/Spectrum 128"},
+
+ {0xc0cf, 0x4e, 0xb7fa, "lancelot", "Lancelot", "pt. 1/Amiga/PC/ST"},
+ {0xd5e9, 0x6a, 0x4192, "lancelot", "Lancelot", "pt. 2/Amiga/PC/ST"},
+ {0xbb8f, 0x1a, 0x7487, "lancelot", "Lancelot", "pt. 3/Amiga/PC/ST"},
+
+ {0xc0bd, 0x57, 0x6ef1, "lancelot", "Lancelot", "pt. 1/Mac"},
+ {0xd5d7, 0x99, 0x770b, "lancelot", "Lancelot", "pt. 2/Mac"},
+ {0xbb7d, 0x17, 0xbc42, "lancelot", "Lancelot", "pt. 3/Mac"},
+
+ {0xb4c9, 0x94, 0xd784, "lancelot", "Lancelot", "pt. 1/Commodore 64 TO"},
+ {0xb729, 0x51, 0x8ee5, "lancelot", "Lancelot", "pt. 2/Commodore 64 TO"},
+ {0xb702, 0xe4, 0x1809, "lancelot", "Lancelot", "pt. 3/Commodore 64 TO"},
+
+ {0x8feb, 0xba, 0xa800, "lancelot", "Lancelot", "pt. 1/Commodore 64 Gfx"},
+ {0x8f6b, 0xfa, 0x0f7e, "lancelot", "Lancelot", "pt. 2/Commodore 64 Gfx"},
+ {0x8f71, 0x2f, 0x0ddc, "lancelot", "Lancelot", "pt. 3/Commodore 64 Gfx"},
+
+ {0x8ade, 0xf2, 0xfffb, "lancelot", "Lancelot", "pt. 1/Spectrum 48"},
+ {0x8b0e, 0xfb, 0x0bab, "lancelot", "Lancelot", "pt. 2/Spectrum 48"},
+ {0x8ab3, 0xc1, 0xcb62, "lancelot", "Lancelot", "pt. 3/Spectrum 48"},
+
+ {0xbba4, 0x94, 0x0871, "lancelot", "Lancelot", "pt. 1/Amiga/PC *USA*"},
+ {0xd0c0, 0x56, 0x8c48, "lancelot", "Lancelot", "pt. 2/Amiga/PC *USA*"},
+ {0xb6ac, 0xc6, 0xaea0, "lancelot", "Lancelot", "pt. 3/Amiga/PC *USA*"},
+
+ {0x8afc, 0x07, 0x8321, "lancelot", "Lancelot", "pt. 1/Commodore 64 Gfx *USA*"},
+ {0x8aec, 0x13, 0x6791, "lancelot", "Lancelot", "pt. 2/Commodore 64 Gfx *USA*"},
+ {0x8aba, 0x0d, 0x5602, "lancelot", "Lancelot", "pt. 3/Commodore 64 Gfx *USA*"},
+
+ {0x5eb9, 0x30, 0xe99a, "lordsoftime", "Lords of Time", "Amstrad CPC"},
+ {0x5eb9, 0x5d, 0xc098, "lordsoftime", "Lords of Time", "MSX"},
+ {0x5eb9, 0x6e, 0xc689, "lordsoftime", "Lords of Time", "Spectrum"},
+ {0x579e, 0x97, 0x9faa, "lordsoftimetmgd", "Lords of Time /T&M GD", "BBC"},
+ {0x69fe, 0x56, 0xecfb, "lordsoftimetmgd", "Lords of Time /T&M GD", "Amstrad CPC/Spectrum +3"},
+ {0x6f1e, 0xda, 0x2ce0, "lordsoftimetmgd", "Lords of Time /T&M GD", "Spectrum 128"},
+ {0xb57c, 0x44, 0x7779, "lordsoftimetm", "Lords of Time /T&M", "PC"},
+ {0xb579, 0x89, 0x3e89, "lordsoftimetm", "Lords of Time /T&M", "ST"},
+ {0xb576, 0x2a, 0x7239, "lordsoftimetm", "Lords of Time /T&M", "Amiga"},
+ {0xb563, 0x6a, 0x0c5c, "lordsoftimetm", "Lords of Time /T&M", "Mac"},
+ {0xb38c, 0x37, 0x9f8e, "lordsoftimetm", "Lords of Time /T&M", "Commodore 64 TO"},
+ {0x9070, 0x43, 0x45d4, "lordsoftimetm", "Lords of Time /T&M", "Commodore 64 Gfx"},
+ {0x8950, 0xa1, 0xbb16, "lordsoftimetm", "Lords of Time /T&M", "Spectrum 48"},
+ {0xb260, 0xe5, 0xc5b2, "lordsoftimetm", "Lords of Time /T&M", "PC/ST *USA*"},
+ {0xb257, 0xf8, 0xfbd5, "lordsoftimetm", "Lords of Time /T&M", "Amiga *USA*"},
+ {0x8d78, 0x3a, 0xba6e, "lordsoftimetm", "Lords of Time /T&M", "Commodore 64 Gfx *USA*"},
+
+ {0x7410, 0x5e, 0x60be, "princeofmagik", "Price of Magik", "Spectrum 128"},
+ {0x5aa4, 0xc1, 0x10a0, "princeofmagik", "Price of Magik", "Spectrum 48[v1]"},
+ {0x5aa4, 0xc1, 0xeda4, "princeofmagik", "Price of Magik", "Spectrum 48[v2]"},
+ {0x6fc6, 0x14, 0xf9b6, "princeofmagik", "Price of Magik", "Commodore 64"},
+ {0x5aa4, 0xc1, 0xbbf4, "princeofmagik", "Price of Magik", "Amstrad CPC"},
+ {0x5671, 0xbc, 0xff35, "princeofmagik", "Price of Magik", "BBC"},
+ {0x6108, 0xdd, 0xefe7, "princeofmagiktmgd", "Price of Magik /T&M GD", "Spectrum 128"},
+ {0x579a, 0x2a, 0x9373, "princeofmagiktmgd", "Price of Magik /T&M GD", "BBC"},
+ {0x5a50, 0xa9, 0xa5fa, "princeofmagiktmgd", "Price of Magik /T&M GD", "Amstrad CPC/Spectrum +3"},
+ {0xbac7, 0x7f, 0xddb2, "princeofmagiktm", "Price of Magik /T&M", "PC"},
+ {0xbac4, 0x80, 0xa750, "princeofmagiktm", "Price of Magik /T&M", "ST"},
+ {0xbaca, 0x3a, 0x221b, "princeofmagiktm", "Price of Magik /T&M", "Amiga"},
+ {0xbab2, 0x87, 0x09f5, "princeofmagiktm", "Price of Magik /T&M", "Mac"},
+ {0xb451, 0xa8, 0x2682, "princeofmagiktm", "Price of Magik /T&M", "Commodore 64 TO"},
+ {0x8f51, 0xb2, 0x6c9a, "princeofmagiktm", "Price of Magik /T&M", "Commodore 64 Gfx"},
+ {0x8a60, 0x2a, 0x29ed, "princeofmagiktm", "Price of Magik /T&M", "Spectrum 48"},
+ {0xb7a0, 0x7e, 0x2226, "princeofmagiktm", "Price of Magik /T&M", "PC/ST *USA*"},
+ {0xb797, 0x1f, 0x84a9, "princeofmagiktm", "Price of Magik /T&M", "Amiga *USA*"},
+ {0x8c46, 0xf0, 0xcaf6, "princeofmagiktm", "Price of Magik /T&M", "Commodore 64 Gfx *USA*"},
+
+ {0x506c, 0xf0, 0xba72, "redmoon", "Red Moon", "BBC/Commodore 64/Amstrad CPC/MSX"},
+ {0x505d, 0x32, 0x2dcf, "redmoon", "Red Moon", "Spectrum"},
+ {0x6da0, 0xb8, 0x3802, "redmoontmgd", "Red Moon /T&M GD", "Spectrum 128"},
+ {0x6888, 0x8d, 0x7f6a, "redmoontmgd", "Red Moon /T&M GD", "Amstrad CPC/Spectrum +3"},
+ {0x5500, 0x50, 0xca75, "redmoontmgd", "Red Moon /T&M GD", "BBC"},
+ {0xa69e, 0x6c, 0xb268, "redmoontm", "Red Moon /T&M", "PC"},
+ {0xa698, 0x41, 0xcaca, "redmoontm", "Red Moon /T&M", "ST"},
+ {0xa692, 0xd1, 0x6a99, "redmoontm", "Red Moon /T&M", "Amiga"},
+ {0xa67c, 0xb8, 0xff41, "redmoontm", "Red Moon /T&M", "Mac"},
+ {0xa4e2, 0xa6, 0x016d, "redmoontm", "Red Moon /T&M", "Commodore 64 TO"},
+ {0x903f, 0x6b, 0x603e, "redmoontm", "Red Moon /T&M", "Commodore 64 Gfx"},
+ {0x8813, 0x11, 0x22de, "redmoontm", "Red Moon /T&M", "Spectrum 48"},
+ {0xa3a4, 0xdf, 0x6732, "redmoontm", "Red Moon /T&M", "PC/ST *USA*"},
+ {0xa398, 0x82, 0xd031, "redmoontm", "Red Moon /T&M", "Amiga *USA*"},
+ {0x8d56, 0xd3, 0x146a, "redmoontm", "Red Moon /T&M", "Commodore 64 Gfx *USA*"},
+
+ {0x60c4, 0x28, 0x0154, "returntoeden", "Return to Eden", "Amstrad CPC/Commodore 64[v1]"},
+ {0x6064, 0x01, 0x5b3c, "returntoeden", "Return to Eden", "BBC[v1]"},
+ {0x6064, 0x95, 0x510c, "returntoeden", "Return to Eden", "Commodore 64[v2]"},
+ {0x6064, 0xda, 0xe610, "returntoeden", "Return to Eden", "Commodore 64[v2] *corrupt*"},
+ {0x6064, 0xbd, 0x73ec, "returntoeden", "Return to Eden", "Atari *corrupt*"},
+ {0x6047, 0x6c, 0x17ab, "returntoeden", "Return to Eden", "BBC[v2]"},
+ {0x5ca1, 0x33, 0x1c43, "returntoeden", "Return to Eden", "Spectrum[v1]"},
+ {0x5cb7, 0x64, 0x0790, "returntoeden", "Return to Eden", "Spectrum[v2]"},
+ {0x5cb7, 0xfe, 0x3533, "returntoeden", "Return to Eden", "MSX"},
+ {0x7d16, 0xe6, 0x5438, "returntoedensd", "Return to Eden /SD", "Amiga/ST"},
+ {0x7d14, 0xe8, 0xfbab, "returntoedensd", "Return to Eden /SD", "PC"},
+ {0x7cff, 0xf8, 0x6044, "returntoedensd", "Return to Eden /SD", "Amstrad CPC/Spectrum +3"},
+ {0x7cf8, 0x24, 0x9c1c, "returntoedensd", "Return to Eden /SD", "Mac"},
+ {0x7c55, 0x18, 0xdaee, "returntoedensd", "Return to Eden /SD", "Spectrum 128"},
+ {0x772f, 0xca, 0x8602, "returntoedensd", "Return to Eden /SD", "Commodore 64"},
+ {0x60f7, 0x68, 0xc2bc, "returntoedensd", "Return to Eden /SD", "Atari"},
+ {0x639c, 0x8b, 0x06e2, "returntoedensd", "Return to Eden /SD", "Apple ]["},
+ {0x5f43, 0xca, 0x828c, "returntoedensd", "Return to Eden /SD", "Spectrum 48"},
+
+ {0x55ce, 0xa1, 0xba12, "scapeghost", "Scapeghost", "pt. 1 GD/BBC"},
+ {0x54a6, 0xa9, 0xc9f3, "scapeghost", "Scapeghost", "pt. 2 GD/BBC"},
+ {0x51bc, 0xe3, 0x89c3, "scapeghost", "Scapeghost", "pt. 3 GD/BBC"},
+
+ {0x5cbc, 0xa5, 0x0dbe, "scapeghost", "Scapeghost", "pt. 1 GD/Amstrad CPC/Spectrum +3"},
+ {0x5932, 0x4e, 0xb2f5, "scapeghost", "Scapeghost", "pt. 2 GD/Amstrad CPC/Spectrum +3"},
+ {0x5860, 0x95, 0x3227, "scapeghost", "Scapeghost", "pt. 3 GD/Amstrad CPC/Spectrum +3"},
+
+ {0x762e, 0x82, 0x8848, "scapeghost", "Scapeghost", "pt. 1 GD/Spectrum 128"},
+ {0x5bd6, 0x35, 0x79ef, "scapeghost", "Scapeghost", "pt. 2 GD/Spectrum 128"},
+ {0x6fa8, 0xa4, 0x62c2, "scapeghost", "Scapeghost", "pt. 3 GD/Spectrum 128"},
+
+ {0xbeab, 0x2d, 0x94d9, "scapeghost", "Scapeghost", "pt. 1/Amiga"},
+ {0xc132, 0x14, 0x7adc, "scapeghost", "Scapeghost", "pt. 1/Amiga *bak*"},
+ {0xbe94, 0xcc, 0x04b8, "scapeghost", "Scapeghost", "pt. 1/PC/ST"},
+ {0x99bd, 0x65, 0x032e, "scapeghost", "Scapeghost", "pt. 2/Amiga/PC/ST"},
+ {0xbcb6, 0x7a, 0x7d4f, "scapeghost", "Scapeghost", "pt. 3/Amiga/PC/ST"},
+
+ {0x9058, 0xcf, 0x9748, "scapeghost", "Scapeghost", "pt. 1/Commodore 64 Gfx"},
+ {0x8f43, 0xc9, 0xeefd, "scapeghost", "Scapeghost", "pt. 2/Commodore 64 Gfx"},
+ {0x90ac, 0x68, 0xb4a8, "scapeghost", "Scapeghost", "pt. 3/Commodore 64 Gfx"},
+
+ {0x8a21, 0xf4, 0xd9e4, "scapeghost", "Scapeghost", "pt. 1/Spectrum 48"},
+ {0x8a12, 0xe3, 0xc2ff, "scapeghost", "Scapeghost", "pt. 2/Spectrum 48"},
+ {0x8a16, 0xcc, 0x4f3b, "scapeghost", "Scapeghost", "pt. 3/Spectrum 48"},
+
+ {0x5fab, 0x5c, 0xa309, "snowball", "Snowball", "Amstrad CPC"},
+ {0x5fab, 0x2f, 0x8aa2, "snowball", "Snowball", "MSX"},
+ {0x7b31, 0x6e, 0x2e2b, "snowballsd", "Snowball /SD", "Amiga/ST"},
+ {0x7b2f, 0x70, 0x6955, "snowballsd", "Snowball /SD", "Mac/PC/Spectrum 128"},
+ {0x7b2f, 0x70, 0x6f6c, "snowballsd", "Snowball /SD", "Amstrad CPC/Spectrum +3"},
+ {0x7363, 0x65, 0xa0ab, "snowballsd", "Snowball /SD", "Commodore 64"},
+ {0x6bf8, 0x3f, 0xc9f7, "snowballsd", "Snowball /SD", "Atari"},
+ {0x67a3, 0x9d, 0x1d05, "snowballsd", "Snowball /SD", "Apple ]["},
+ {0x6541, 0x02, 0x2e6c, "snowballsd", "Snowball /SD", "Spectrum 48"},
+
+ {0x772b, 0xcd, 0xa503, "worminparadise", "Worm in Paradise", "Spectrum 128"},
+ {0x546c, 0xb7, 0x9420, "worminparadise", "Worm in Paradise", "Spectrum 48"},
+ {0x6d84, 0xf9, 0x49ae, "worminparadise", "Worm in Paradise", "Commodore 64 *corrupt*"},
+ {0x6d84, 0xc8, 0x943f, "worminparadise", "Worm in Paradise", "Commodore 64 *fixed*"},
+ {0x6030, 0x47, 0x46ad, "worminparadise", "Worm in Paradise", "Amstrad CPC"},
+ {0x5828, 0xbd, 0xe7cb, "worminparadise", "Worm in Paradise", "BBC"},
+ {0x7cd9, 0x0c, 0x4df1, "worminparadise", "Worm in Paradise /SD", "Amiga/ST"},
+ {0x7cd7, 0x0e, 0x4feb, "worminparadisesd", "Worm in Paradise /SD", "Amstrad CPC/Mac/PC/Spectrum 128/Spectrum +3"},
+ {0x788d, 0x72, 0x888a, "worminparadisesd", "Worm in Paradise /SD", "Commodore 64"},
+ {0x6161, 0xf3, 0xe6d7, "worminparadisesd", "Worm in Paradise /SD", "Atari"},
+ {0x60dd, 0xf2, 0x5bb8, "worminparadisesd", "Worm in Paradise /SD", "Apple ]["},
+ {0x5ebb, 0xf1, 0x4dec, "worminparadisesd", "Worm in Paradise /SD", "Spectrum 48"},
+
+ {0x0000, 0x00, 0x0000, nullptr, nullptr, nullptr}
};
/**
@@ -803,13 +777,6 @@ const L9V1GameInfo L9_V1_GAMES[] = {
0x15, 0x6c, 284, -0x00f0, 0x0000, -0x0050, -0x0050, -0x0050, 0x0300, 0x1930, 0x3c17, /* Snowball */
};
-// TODO: The list of Level 9 games and detection entries needs to be filled out
-const PlainGameDescriptor LEVEL9_GAME_LIST[] = {
- { "level9", "Level 9 IF Game" },
-
- { nullptr, nullptr }
-};
-
const GlkDetectionEntry LEVEL9_GAMES[] = {
DT_END_MARKER
};
Commit: 64b4f13b36030963b507be6e8fc4c4eb7bf22206
https://github.com/scummvm/scummvm/commit/64b4f13b36030963b507be6e8fc4c4eb7bf22206
Author: Paul Gilbert (dreammaster at scummvm.org)
Date: 2019-10-26T11:13:30-07:00
Commit Message:
GLK: LEVEL9: Compilation fixes
Changed paths:
engines/glk/level9/detection.cpp
engines/glk/level9/detection.h
engines/glk/level9/level9_main.cpp
engines/glk/level9/level9_main.h
engines/glk/level9/os_glk.cpp
diff --git a/engines/glk/level9/detection.cpp b/engines/glk/level9/detection.cpp
index 93e8143..5788c0f 100644
--- a/engines/glk/level9/detection.cpp
+++ b/engines/glk/level9/detection.cpp
@@ -514,7 +514,7 @@ void Scanner::fullScan(byte *startFile, uint32 size) {
/*----------------------------------------------------------------------*/
-GameDetection::GameDetection(byte *&startData, size_t &fileSize) :
+GameDetection::GameDetection(byte *&startData, uint32 &fileSize) :
_startData(startData), _fileSize(fileSize), _crcInitialized(false), _gameName(nullptr) {
Common::fill(&_crcTable[0], &_crcTable[256], 0);
}
@@ -723,7 +723,7 @@ bool Level9MetaEngine::detectGames(const Common::FSList &fslist, DetectedGames &
if (!gameFile.open(*file))
continue;
- size_t fileSize = gameFile.size();
+ uint32 fileSize = gameFile.size();
if (fileSize > 0xffff) {
// Too big to possibly be a Level 9 game
gameFile.close();
diff --git a/engines/glk/level9/detection.h b/engines/glk/level9/detection.h
index ae876b9..f6832d1 100644
--- a/engines/glk/level9/detection.h
+++ b/engines/glk/level9/detection.h
@@ -101,7 +101,7 @@ public:
class GameDetection : public Scanner {
private:
byte *&_startData;
- size_t &_fileSize;
+ uint32 &_fileSize;
bool _crcInitialized;
uint16 _crcTable[256];
public:
@@ -110,7 +110,7 @@ public:
/**
* Constructor
*/
- GameDetection(byte *&startData, size_t &fileSize);
+ GameDetection(byte *&startData, uint32 &fileSize);
/**
* Identify a game from its data length, checksum, and CRC. Returns the
diff --git a/engines/glk/level9/level9_main.cpp b/engines/glk/level9/level9_main.cpp
index bbaad57..3cc027e 100644
--- a/engines/glk/level9/level9_main.cpp
+++ b/engines/glk/level9/level9_main.cpp
@@ -78,7 +78,7 @@ enum L9GfxTypes { GFX_V2, GFX_V3A, GFX_V3B, GFX_V3C };
/* Global Variables */
L9BYTE *startfile, *pictureaddress, *picturedata;
byte *startdata;
-size_t FileSize, picturesize;
+uint32 FileSize, picturesize;
byte *L9Pointers[12];
byte *absdatablock, *list2ptr, *list3ptr, *list9startptr, *acodeptr;
@@ -1808,6 +1808,10 @@ L9BOOL corruptinginput() {
} while (TRUE);
/* ip22 */
checknumber();
+
+ // Unused variables
+ (void)keywordnumber;
+
return TRUE;
}
diff --git a/engines/glk/level9/level9_main.h b/engines/glk/level9/level9_main.h
index 95d9f18..5bd1dff 100644
--- a/engines/glk/level9/level9_main.h
+++ b/engines/glk/level9/level9_main.h
@@ -87,7 +87,7 @@ struct Bitmap {
#define L9SETDWORD(x,val) WRITE_LE_UINT32(x, val)
extern byte *startdata;
-extern size_t FileSize;
+extern uint32 FileSize;
extern void level9_initialize();
diff --git a/engines/glk/level9/os_glk.cpp b/engines/glk/level9/os_glk.cpp
index ab171c3..ac9a4ee 100644
--- a/engines/glk/level9/os_glk.cpp
+++ b/engines/glk/level9/os_glk.cpp
@@ -1007,14 +1007,15 @@ static void gln_graphics_timeout() {
static int deferred_repaint = FALSE; /* Local delayed repaint flag */
static int ignore_counter; /* Count of calls ignored */
-
static int x_offset, y_offset; /* Point plot offsets */
+
+#ifndef GARGLK
static int yield_counter; /* Yields in rendering */
static int saved_layer; /* Saved current layer */
static int saved_x, saved_y; /* Saved x,y coord */
static int total_regions; /* Debug statistic */
-
+#endif
gln_byte *on_screen; /* On-screen image buffer */
gln_byte *off_screen; /* Off-screen image buffer */
long picture_size; /* Picture size in pixels */
@@ -1133,13 +1134,14 @@ static void gln_graphics_timeout() {
x_offset, y_offset,
GLN_GRAPHICS_PIXEL,
gln_graphics_width, gln_graphics_height);
-
+#ifndef GARGLK
/* Start a fresh picture rendering pass. */
yield_counter = 0;
saved_layer = 0;
saved_x = 0;
saved_y = 0;
total_regions = 0;
+#endif
/* Clear the new picture and deferred repaint flags. */
gln_graphics_new_picture = FALSE;
@@ -2333,7 +2335,7 @@ static void gln_status_print() {
/* Bracket, and output the extracted game name. */
g_vm->glk_put_string("[ ");
- g_vm->glk_put_string((char *) game_name);
+ g_vm->glk_put_string(game_name);
for (index = strlen(game_name);
index <= GLN_DEFAULT_STATUS_WIDTH; index++)
@@ -4545,12 +4547,6 @@ static const int GLN_WATCHDOG_TIMEOUT = 5,
GLN_WATCHDOG_PERIOD = 10240;
/*
- * The following values need to be passed between the startup_code and main
- * functions.
- */
-static const char *gln_game_message = nullptr; /* Error message. */
-
-/*
* gln_establish_picture_filename()
*
* Given a game name, try to create an (optional) graphics data file. For
More information about the Scummvm-git-logs
mailing list