[Scummvm-git-logs] scummvm master -> 9dd5469873b21ed4dcd931c011d59bd66af5f85b

bluegr noreply at scummvm.org
Sat May 28 14:34:59 UTC 2022


This automated email contains information about 1 new commit which have been
pushed to the 'scummvm' repo located at https://github.com/scummvm/scummvm .

Summary:
9dd5469873 AGI: Add files missing from commit 3be24c759b504ad026073c0106e9b337ebad821f


Commit: 9dd5469873b21ed4dcd931c011d59bd66af5f85b
    https://github.com/scummvm/scummvm/commit/9dd5469873b21ed4dcd931c011d59bd66af5f85b
Author: Filippos Karapetis (bluegr at gmail.com)
Date: 2022-05-28T17:34:40+03:00

Commit Message:
AGI: Add files missing from commit 3be24c759b504ad026073c0106e9b337ebad821f

Changed paths:
  A engines/agi/preagi/mickey.cpp
  A engines/agi/preagi/mickey.h
  A engines/agi/preagi/preagi.cpp
  A engines/agi/preagi/preagi.h
  A engines/agi/preagi/troll.cpp
  A engines/agi/preagi/troll.h
  A engines/agi/preagi/winnie.cpp
  A engines/agi/preagi/winnie.h


diff --git a/engines/agi/preagi/mickey.cpp b/engines/agi/preagi/mickey.cpp
new file mode 100644
index 00000000000..3126be1ab5b
--- /dev/null
+++ b/engines/agi/preagi/mickey.cpp
@@ -0,0 +1,2358 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "common/events.h"
+#include "common/savefile.h"
+#include "common/textconsole.h"
+
+#include "graphics/cursorman.h"
+
+#include "agi/preagi/preagi.h"
+#include "agi/preagi/mickey.h"
+#include "agi/graphics.h"
+
+namespace Agi {
+
+int MickeyEngine::getDat(int iRoom) {
+	if (((iRoom > 0) && (iRoom < 24)) || iRoom == 154 || iRoom == 155) return IDI_MSA_PLANET_EARTH;
+	if ((iRoom >= 30) && (iRoom <= 39)) return IDI_MSA_PLANET_VENUS;
+	if ((iRoom >= 40) && (iRoom <= 69)) return IDI_MSA_PLANET_NEPTUNE;
+	if ((iRoom >= 70) && (iRoom <= 82)) return IDI_MSA_PLANET_MERCURY;
+	if ((iRoom >= 83) && (iRoom <= 92)) return IDI_MSA_PLANET_SATURN;
+	if ((iRoom >= 93) && (iRoom <= 103)) return IDI_MSA_PLANET_PLUTO;
+	if ((iRoom >= 106) && (iRoom <= 120)) return IDI_MSA_PLANET_JUPITER;
+	if ((iRoom >= 121) && (iRoom <= 132)) return IDI_MSA_PLANET_MARS;
+	if ((iRoom >= 133) && (iRoom <= 145)) return IDI_MSA_PLANET_URANUS;
+	return IDI_MSA_PLANET_SPACESHIP;
+}
+
+void MickeyEngine::readExe(int ofs, uint8 *buffer, long buflen) {
+	Common::File infile;
+	if (!infile.open("mickey.exe"))
+		return;
+	infile.seek(ofs, SEEK_SET);
+	infile.read(buffer, buflen);
+	infile.close();
+}
+
+void MickeyEngine::getDatFileName(int iRoom, char *szFile) {
+	sprintf(szFile, IDS_MSA_PATH_DAT, IDS_MSA_NAME_DAT[getDat(iRoom)]);
+}
+
+void MickeyEngine::readDatHdr(char *szFile, MSA_DAT_HEADER *hdr) {
+	Common::File infile;
+
+	if (!infile.open(szFile))
+		return;
+
+	hdr->filelen = infile.readByte();
+	hdr->filelen += infile.readByte() * 0x100;
+
+	for (int i = 0; i < IDI_MSA_MAX_ROOM; i++) {
+		hdr->ofsRoom[i] = infile.readByte();
+		hdr->ofsRoom[i] += infile.readByte() * 0x100;
+	}
+	for (int i = 0; i < IDI_MSA_MAX_ROOM; i++) {
+		hdr->ofsDesc[i] = infile.readByte();
+		hdr->ofsDesc[i] += infile.readByte() * 0x100;
+	}
+	for (int i = 0; i < IDI_MSA_MAX_ROOM; i++) {
+		hdr->ofsStr[i] = infile.readByte();
+		hdr->ofsStr[i] += infile.readByte() * 0x100;
+	}
+
+	infile.close();
+}
+
+void MickeyEngine::readOfsData(int offset, int iItem, uint8 *buffer, long buflen) {
+	uint16 ofs[256];
+
+	readExe(offset, buffer, buflen);
+	memcpy(ofs, buffer, sizeof(ofs));
+
+	for (int i = 0; i < 256; i++)
+		ofs[i] = buffer[i * 2] + 256 * buffer[i * 2 + 1];
+
+	readExe(ofs[iItem] + IDI_MSA_OFS_EXE, buffer, buflen);
+}
+
+// User Interface
+
+bool MickeyEngine::chooseY_N(int ofsPrompt, bool fErrorMsg) {
+	printExeStr(ofsPrompt);
+
+	while (!shouldQuit()) {
+		switch (getSelection(kSelYesNo)) {
+		case 0:
+			return false;
+		case 1:
+			return true;
+		default:
+			if (fErrorMsg) {
+				printExeStr(IDO_MSA_PRESS_YES_OR_NO);
+				waitAnyKey();
+				printExeStr(ofsPrompt);
+			}
+			break;
+		}
+	}
+
+	return false;
+}
+
+int MickeyEngine::choose1to9(int ofsPrompt) {
+	int answer = 0;
+	printExeStr(ofsPrompt);
+
+	while (!shouldQuit()) {
+		answer = getSelection(kSelNumber);
+		if (answer == 10) {
+			printExeStr(IDO_MSA_PRESS_1_TO_9);
+			if (getSelection(kSelAnyKey) == 0)
+				return 0;
+			printExeStr(ofsPrompt);
+		} else return answer;
+	}
+
+	return 0;
+}
+
+void MickeyEngine::printStr(char *buffer) {
+	int pc = 1;
+	int nRows, iCol, iRow;
+
+	nRows = *buffer + IDI_MSA_ROW_MENU_0;
+
+	clearTextArea();
+
+	for (iRow = IDI_MSA_ROW_MENU_0; iRow < nRows; iRow++) {
+		iCol = *(buffer + pc++);
+		drawStr(iRow, iCol, IDA_DEFAULT, buffer + pc);
+		pc += strlen(buffer + pc) + 1;
+	}
+
+	// Show the string on screen
+	_gfx->updateScreen();
+}
+
+void MickeyEngine::printLine(const char *buffer) {
+	clearTextArea();
+
+	drawStr(22, 18 - strlen(buffer) / 2, IDA_DEFAULT, buffer);
+
+	// Show the string on screen
+	_gfx->updateScreen();
+
+	waitAnyKey(true);
+}
+
+void MickeyEngine::printExeStr(int ofs) {
+	uint8 buffer[256] = {0};
+
+	if (!ofs)
+		return;
+
+	readExe(ofs, buffer, sizeof(buffer));
+	printStr((char *)buffer);
+}
+
+void MickeyEngine::printExeMsg(int ofs) {
+	if (!ofs)
+		return;
+
+	printExeStr(ofs);
+	waitAnyKey(true);
+}
+
+void MickeyEngine::printDatString(int iStr) {
+	char buffer[256];
+	int iDat = getDat(_gameStateMickey.iRoom);
+
+	MSA_DAT_HEADER hdr;
+	char szFile[256] = {0};
+
+	sprintf(szFile, IDS_MSA_PATH_DAT, IDS_MSA_NAME_DAT[iDat]);
+	readDatHdr(szFile, &hdr);
+
+	Common::File infile;
+
+	if (!infile.open(szFile))
+		return;
+
+	infile.seek(hdr.ofsStr[iStr] + IDI_MSA_OFS_DAT, SEEK_SET);
+	infile.read((uint8 *)buffer, 256);
+	infile.close();
+
+	printStr(buffer);
+}
+
+void MickeyEngine::printDesc(int iRoom) {
+	MSA_DAT_HEADER hdr;
+	char szFile[256] = {0};
+
+	getDatFileName(iRoom, szFile);
+	readDatHdr(szFile, &hdr);
+
+	Common::File infile;
+
+	if (!infile.open(szFile))
+		return;
+
+	char *buffer = (char *)malloc(256);
+	memset(buffer, 0, 256);
+
+	infile.seek(hdr.ofsDesc[iRoom - 1] + IDI_MSA_OFS_DAT, SEEK_SET);
+	infile.read(buffer, 256);
+	infile.close();
+
+	printStr(buffer);
+	free(buffer);
+}
+
+bool MickeyEngine::checkMenu() {
+	MSA_MENU menu;
+	int iSel0, iSel1;
+	MSA_DAT_HEADER hdr;
+	char szFile[256] = {0};
+	Common::File infile;
+
+	getDatFileName(_gameStateMickey.iRoom, szFile);
+	readDatHdr(szFile, &hdr);
+	if (!infile.open(szFile))
+		return false;
+
+	char *buffer = new char[sizeof(MSA_MENU)];
+	infile.seek(hdr.ofsRoom[_gameStateMickey.iRoom - 1] + IDI_MSA_OFS_DAT, SEEK_SET);
+	infile.read((uint8 *)buffer, sizeof(MSA_MENU));
+	infile.close();
+
+	memcpy(&menu, buffer, sizeof(MSA_MENU));
+	patchMenu(&menu);
+	memcpy(buffer, &menu, sizeof(MSA_MENU));
+
+	getMenuSel(buffer, &iSel0, &iSel1);
+	delete[] buffer;
+
+	return parse(menu.cmd[iSel0].data[iSel1], menu.arg[iSel0].data[iSel1]);
+}
+
+void MickeyEngine::drawMenu(MSA_MENU &menu, int sel0, int sel1) {
+	int iWord;
+	int iRow;
+	int sel;
+	uint8 attr;
+
+	// draw menu
+
+	clearTextArea();
+
+	for (iRow = 0; iRow < 2; iRow++) {
+		for (iWord = 0; iWord < menu.row[iRow].count; iWord++) {
+			if (iRow)
+				sel = sel1;
+			else
+				sel = sel0;
+
+			if (iWord == sel)
+				attr = IDA_DEFAULT_REV;
+			else
+				attr = IDA_DEFAULT;
+
+			drawStr(IDI_MSA_ROW_MENU_0 + iRow, menu.row[iRow].entry[iWord].x0,
+			        attr, (char *)menu.row[iRow].entry[iWord].szText);
+		}
+	}
+
+	// Menu created, show it on screen
+	_gfx->updateScreen();
+}
+
+void MickeyEngine::getMouseMenuSelRow(MSA_MENU &menu, int *sel0, int *sel1, int iRow, int x, int y) {
+	int iWord;
+	int *sel = nullptr;
+
+	switch (iRow) {
+	case 0:
+		if (y != IDI_MSA_ROW_MENU_0) return;
+		sel = sel0;
+		break;
+	case 1:
+		if (y != IDI_MSA_ROW_MENU_1) return;
+		sel = sel1;
+		break;
+	default:
+		return;
+	}
+
+	for (iWord = 0; iWord < menu.row[iRow].count; iWord++) {
+		if ((x >= menu.row[iRow].entry[iWord].x0) &&
+		        (x < (int)(menu.row[iRow].entry[iWord].x0 +
+		                   strlen((char *)menu.row[iRow].entry[iWord].szText)))) {
+			*sel = iWord;
+			break;
+		}
+	}
+}
+
+bool MickeyEngine::getMenuSelRow(MSA_MENU &menu, int *sel0, int *sel1, int iRow) {
+	Common::Event event;
+	int *sel = nullptr;
+	int nWords;
+	int x, y;
+	int goIndex = -1, northIndex = -1, southIndex = -1, eastIndex = -1, westIndex = -1;
+
+	switch (iRow) {
+	case 0:
+		sel = sel0;
+		break;
+	case 1:
+		sel = sel1;
+		break;
+	default:
+		break;
+	}
+	nWords = menu.row[iRow].count;
+	_clickToMove = false;
+
+	for (int i = 0; i <= menu.row[0].count; i++)
+		if (menu.row[0].entry[i].szText[0] == 71 && menu.row[0].entry[i].szText[1] == 79)   // GO
+			goIndex = i;
+
+	if (goIndex >= 0) {
+		for (int j = 0; j <= menu.row[1].count; j++) {
+			if (menu.row[1].entry[j].szText[0] == 78 && menu.row[1].entry[j].szText[1] == 79 &&
+			        menu.row[1].entry[j].szText[2] == 82 && menu.row[1].entry[j].szText[3] == 84 &&
+			        menu.row[1].entry[j].szText[4] == 72)
+				northIndex = j;
+			if (menu.row[1].entry[j].szText[0] == 83 && menu.row[1].entry[j].szText[1] == 79 &&
+			        menu.row[1].entry[j].szText[2] == 85 && menu.row[1].entry[j].szText[3] == 84 &&
+			        menu.row[1].entry[j].szText[4] == 72)
+				southIndex = j;
+			if (menu.row[1].entry[j].szText[0] == 69 && menu.row[1].entry[j].szText[1] == 65 &&
+			        menu.row[1].entry[j].szText[2] == 83 && menu.row[1].entry[j].szText[3] == 84)
+				eastIndex = j;
+			if (menu.row[1].entry[j].szText[0] == 87 && menu.row[1].entry[j].szText[1] == 69 &&
+			        menu.row[1].entry[j].szText[2] == 83 && menu.row[1].entry[j].szText[3] == 84)
+				westIndex = j;
+		}
+	}
+
+	drawMenu(menu, *sel0, *sel1);
+
+	while (!shouldQuit()) {
+		while (_system->getEventManager()->pollEvent(event)) {
+			switch (event.type) {
+			case Common::EVENT_RETURN_TO_LAUNCHER:
+			case Common::EVENT_QUIT:
+				return 0;
+			case Common::EVENT_MOUSEMOVE:
+				if (iRow < 2) {
+					x = event.mouse.x / 8;
+					y = event.mouse.y / 8;
+					// If the mouse hovers over the menu, refresh the menu
+					if ((iRow == 0 && y == IDI_MSA_ROW_MENU_0) || (iRow == 1 && y == IDI_MSA_ROW_MENU_1)) {
+						getMouseMenuSelRow(menu, sel0, sel1, iRow, x, y);
+						drawMenu(menu, *sel0, *sel1);
+					}
+
+					// Change cursor
+					if (northIndex >= 0 && (event.mouse.x >= 20 && event.mouse.x <= (IDI_MSA_PIC_WIDTH + 10) * 2) &&
+					        (event.mouse.y >= 0 && event.mouse.y <= 10)) {
+						//_gfx->setCursorPalette(true);
+						// TODO:?????
+					} else if (southIndex >= 0 && (event.mouse.x >= 20 && event.mouse.x <= (IDI_MSA_PIC_WIDTH + 10) * 2) &&
+					           (event.mouse.y >= IDI_MSA_PIC_HEIGHT - 10 && event.mouse.y <= IDI_MSA_PIC_HEIGHT)) {
+						//_gfx->setCursorPalette(true);
+					} else if (westIndex >= 0 && (event.mouse.y >= 0  && event.mouse.y <= IDI_MSA_PIC_HEIGHT) &&
+					           (event.mouse.x >= 20 && event.mouse.x <= 30)) {
+						//_gfx->setCursorPalette(true);
+					} else if (eastIndex >= 0 && (event.mouse.y >= 0  && event.mouse.y <= IDI_MSA_PIC_HEIGHT) &&
+					           (event.mouse.x >= IDI_MSA_PIC_WIDTH * 2 && event.mouse.x <= (IDI_MSA_PIC_WIDTH + 10) * 2)) {
+						//_gfx->setCursorPalette(true);
+					} else {
+						//_gfx->setCursorPalette(false);
+					}
+				}
+				break;
+			case Common::EVENT_LBUTTONUP:
+				// Click to move
+				if (northIndex >= 0 && (event.mouse.x >= 20 && event.mouse.x <= (IDI_MSA_PIC_WIDTH + 10) * 2) &&
+				        (event.mouse.y >= 0 && event.mouse.y <= 10)) {
+					*sel0 = goIndex;
+					*sel1 = northIndex;
+
+					drawMenu(menu, *sel0, *sel1);
+
+					//_gfx->setCursorPalette(false);
+					// TODO???
+					_clickToMove = true;
+				} else if (southIndex >= 0 && (event.mouse.x >= 20 && event.mouse.x <= (IDI_MSA_PIC_WIDTH + 10) * 2) &&
+				           (event.mouse.y >= IDI_MSA_PIC_HEIGHT - 10 && event.mouse.y <= IDI_MSA_PIC_HEIGHT)) {
+					*sel0 = goIndex;
+					*sel1 = southIndex;
+
+					drawMenu(menu, *sel0, *sel1);
+
+					//_gfx->setCursorPalette(false);
+					// TODO???
+					_clickToMove = true;
+				} else if (westIndex >= 0 && (event.mouse.y >= 0  && event.mouse.y <= IDI_MSA_PIC_HEIGHT) &&
+				           (event.mouse.x >= 20 && event.mouse.x <= 30)) {
+					*sel0 = goIndex;
+					*sel1 = westIndex;
+
+					drawMenu(menu, *sel0, *sel1);
+
+					//_gfx->setCursorPalette(false);
+					// TODO???
+					_clickToMove = true;
+				} else if (eastIndex >= 0 && (event.mouse.y >= 0  && event.mouse.y <= IDI_MSA_PIC_HEIGHT) &&
+				           (event.mouse.x >= IDI_MSA_PIC_WIDTH * 2 && event.mouse.x <= (IDI_MSA_PIC_WIDTH + 10) * 2)) {
+					*sel0 = goIndex;
+					*sel1 = eastIndex;
+
+					drawMenu(menu, *sel0, *sel1);
+
+					//_gfx->setCursorPalette(false);
+					// TODO???
+					_clickToMove = true;
+				} else {
+					//_gfx->setCursorPalette(false);
+					// TODO???
+				}
+				return true;
+			case Common::EVENT_RBUTTONUP:
+				*sel0 = 0;
+				*sel1 = -1;
+				return false;
+			case Common::EVENT_WHEELUP:
+				if (iRow < 2) {
+					*sel -= 1;
+
+					if (*sel < 0)
+						*sel = nWords - 1;
+
+					drawMenu(menu, *sel0, *sel1);
+				}
+				break;
+			case Common::EVENT_WHEELDOWN:
+				if (iRow < 2) {
+					*sel += 1;
+
+					if (*sel == nWords)
+						*sel = 0;
+
+					drawMenu(menu, *sel0, *sel1);
+				}
+				break;
+			case Common::EVENT_KEYDOWN:
+				switch (event.kbd.keycode) {
+				case Common::KEYCODE_2:
+					// Hidden message
+					if (_gameStateMickey.iRoom == IDI_MSA_PIC_MERCURY_CAVE_0) {
+						for (int i = 0; i < 5; i++) {
+							printExeMsg(IDO_MSA_HIDDEN_MSG[i]);
+						}
+						clearTextArea();
+						waitAnyKey();
+					}
+					break;
+				case Common::KEYCODE_8:
+					if (event.kbd.flags & Common::KBD_CTRL) {
+						*sel0 = 0;
+						*sel1 = -1;
+
+						return false;
+					}
+					break;
+				case Common::KEYCODE_ESCAPE:
+					*sel0 = 0;
+					*sel1 = -1;
+
+					return false;
+				case Common::KEYCODE_s:
+					flipFlag(VM_FLAG_SOUND_ON);
+					break;
+				case Common::KEYCODE_c:
+					inventory();
+					drawRoom();
+
+					*sel0 = 0;
+					*sel1 = -1;
+
+					return false;
+				case Common::KEYCODE_b:
+					printRoomDesc();
+					drawMenu(menu, *sel0, *sel1);
+
+					*sel0 = 0;
+					*sel1 = -1;
+
+					return false;
+				case Common::KEYCODE_LEFT:
+				case Common::KEYCODE_KP4:
+				case Common::KEYCODE_4:
+					if (iRow < 2) {
+						*sel -= 1;
+
+						if (*sel < 0)
+							*sel = nWords - 1;
+
+						drawMenu(menu, *sel0, *sel1);
+					}
+					break;
+				case Common::KEYCODE_RIGHT:
+				case Common::KEYCODE_SPACE:
+				case Common::KEYCODE_KP6:
+				case Common::KEYCODE_6:
+					if (iRow < 2) {
+						*sel += 1;
+
+						if (*sel == nWords)
+							*sel = 0;
+
+						drawMenu(menu, *sel0, *sel1);
+					}
+					break;
+				case Common::KEYCODE_RETURN:
+				case Common::KEYCODE_KP_ENTER:
+					return true;
+				default:
+					break;
+				}
+				break;
+			default:
+				break;
+			}
+			animate();
+			drawMenu(menu, *sel0, *sel1);
+		}
+		animate();
+		drawMenu(menu, *sel0, *sel1);
+	}
+
+	return false;
+}
+
+void MickeyEngine::getMenuSel(char *buffer, int *sel0, int *sel1) {
+	MSA_MENU menu;
+
+	memcpy(&menu, buffer, sizeof(MSA_MENU));
+
+	*sel0 = 0;
+	*sel1 = -1;
+
+	// Show the mouse cursor for the menu
+	CursorMan.showMouse(true);
+
+	while (!shouldQuit()) {
+		while (!shouldQuit()) {
+			if (getMenuSelRow(menu, sel0, sel1, 0)) {
+				if (_clickToMove)
+					break;
+
+				*sel1 = 0;
+
+				if (getMenuSelRow(menu, sel0, sel1, 1)) {
+					break;
+				}
+			}
+		}
+
+		if (_clickToMove || getMenuSelRow(menu, sel0, sel1, 2)) {
+			break;
+		}
+	}
+
+	// Menu selection made, hide the mouse cursor
+	CursorMan.showMouse(false);
+}
+
+void MickeyEngine::centerMenu(MSA_MENU *menu) {
+	int iWord;
+	int iRow;
+	int w, x;
+
+	for (iRow = 0; iRow < 2; iRow++) {
+		w = 0;
+		for (iWord = 0; iWord < menu->row[iRow].count; iWord++) {
+			w += strlen((char *)menu->row[iRow].entry[iWord].szText);
+		}
+		w += menu->row[iRow].count - 1;
+		x = (40 - w) / 2;   // FIX
+
+		for (iWord = 0; iWord < menu->row[iRow].count; iWord++) {
+			menu->row[iRow].entry[iWord].x0 = x;
+			x += strlen((char *)menu->row[iRow].entry[iWord].szText) + 1;
+		}
+	}
+}
+
+void MickeyEngine::patchMenu(MSA_MENU *menu) {
+	uint8 buffer[512];
+	uint8 menubuf[sizeof(MSA_MENU)];
+	int nPatches;
+	int pBuf = 0;
+
+	// change planet name in ship airlock menu
+	if (_gameStateMickey.iRoom == IDI_MSA_PIC_SHIP_AIRLOCK) {
+		strcpy((char *)menu->row[1].entry[2].szText, IDS_MSA_NAME_PLANET[_gameStateMickey.iPlanet]);
+	}
+
+	// exit if fix unnecessary
+	if (!_gameStateMickey.iRmMenu[_gameStateMickey.iRoom]) {
+		centerMenu(menu);
+		return;
+	}
+
+	// copy menu to menubuf
+	memcpy(menubuf, menu, sizeof(menubuf));
+
+	// read patches
+	readOfsData(
+	    IDOFS_MSA_MENU_PATCHES,
+	    _gameStateMickey.nRmMenu[_gameStateMickey.iRoom] + _gameStateMickey.iRmMenu[_gameStateMickey.iRoom] - 1,
+	    buffer, sizeof(buffer)
+	);
+
+	// get number of patches
+	nPatches = buffer[pBuf++];
+
+	// patch menubuf
+	for (int iPatch = 0; iPatch < nPatches; iPatch++) {
+		if (buffer[pBuf] > sizeof(menubuf)) {
+			// patch address out of bounds
+		}
+		menubuf[buffer[pBuf]] = buffer[pBuf + 1];
+		pBuf += 2;
+	}
+
+	// copy menubuf back to menu
+	memcpy(menu, menubuf, sizeof(MSA_MENU));
+
+	// center menu
+	centerMenu(menu);
+}
+
+void MickeyEngine::printDatMessage(int iStr) {
+	printDatString(iStr);
+	waitAnyKey(true);
+}
+
+// Sound
+
+void MickeyEngine::playNote(MSA_SND_NOTE note) {
+	if (!note.counter) {
+		// Pause
+		_system->delayMillis((uint)(note.length / IDI_SND_TIMER_RESOLUTION));
+	} else {
+		PreAgiEngine::playNote(IDI_SND_OSCILLATOR_FREQUENCY / note.counter, (int32)(note.length / IDI_SND_TIMER_RESOLUTION));
+	}
+}
+
+void MickeyEngine::playSound(ENUM_MSA_SOUND iSound) {
+	if (!getFlag(VM_FLAG_SOUND_ON))
+		return;
+
+	Common::Event event;
+	MSA_SND_NOTE note;
+	uint8 *buffer = new uint8[1024];
+	int pBuf = 1;
+
+	switch (iSound) {
+	case IDI_MSA_SND_XL30:
+		for (int iNote = 0; iNote < 6; iNote++) {
+			note.counter = rnd(59600) + 59;
+			note.length = 4;
+			playNote(note);
+		}
+		break;
+	default:
+		readOfsData(IDOFS_MSA_SOUND_DATA, iSound, buffer, 1024);
+
+		for (;;) {
+			memcpy(&note, buffer + pBuf, sizeof(note));
+			if (!note.counter && !note.length)
+				break;
+
+			playNote(note);
+
+			pBuf += 3;
+
+			if (iSound == IDI_MSA_SND_THEME) {
+				while (_system->getEventManager()->pollEvent(event)) {
+					switch (event.type) {
+					case Common::EVENT_RETURN_TO_LAUNCHER:
+					case Common::EVENT_QUIT:
+					case Common::EVENT_LBUTTONUP:
+					case Common::EVENT_RBUTTONUP:
+					case Common::EVENT_KEYDOWN:
+						delete[] buffer;
+						return;
+					default:
+						break;
+					}
+				}
+			}
+		}
+
+		break;
+	}
+
+	delete[] buffer;
+}
+
+// Graphics
+
+void MickeyEngine::drawObj(ENUM_MSA_OBJECT iObj, int x0, int y0) {
+	char szFile[255] = {0};
+	sprintf(szFile, IDS_MSA_PATH_OBJ, IDS_MSA_NAME_OBJ[iObj]);
+
+	Common::File file;
+	if (!file.open(szFile))
+		return;
+
+	uint8 *buffer = new uint8[4096];
+	uint32 size = file.size();
+	file.read(buffer, size);
+	file.close();
+
+	if (iObj == IDI_MSA_OBJECT_CRYSTAL)
+		_picture->setPictureFlags(kPicFStep);
+
+	_picture->setOffset(x0, y0);
+	_picture->decodePicture(buffer, size, false, IDI_MSA_PIC_WIDTH, IDI_MSA_PIC_HEIGHT);
+	_picture->setOffset(0, 0);
+	_picture->showPic(10, 0, IDI_MSA_PIC_WIDTH, IDI_MSA_PIC_HEIGHT);
+}
+
+void MickeyEngine::drawPic(int iPic) {
+	char szFile[255] = {0};
+	sprintf(szFile, IDS_MSA_PATH_PIC, iPic);
+
+	Common::File file;
+	if (!file.open(szFile))
+		return;
+
+	uint8 *buffer = new uint8[4096];
+	uint32 size = file.size();
+	file.read(buffer, size);
+	file.close();
+
+	// Note that decodePicture clears the screen
+	_picture->setOffset(10, 0);
+	_picture->decodePicture(buffer, size, true, IDI_MSA_PIC_WIDTH, IDI_MSA_PIC_HEIGHT);
+	_picture->setOffset(0, 0);
+	_picture->showPic(10, 0, IDI_MSA_PIC_WIDTH, IDI_MSA_PIC_HEIGHT);
+}
+
+void MickeyEngine::drawRoomAnimation() {
+	uint8 objLight[] = {
+		0xF0, 1, 0xF9, 2, 43, 45, 0xFF
+	};
+
+	switch (_gameStateMickey.iRoom) {
+	case IDI_MSA_PIC_EARTH_SHIP:
+	case IDI_MSA_PIC_VENUS_SHIP:
+	case IDI_MSA_PIC_NEPTUNE_SHIP:
+	case IDI_MSA_PIC_MERCURY_SHIP:
+	case IDI_MSA_PIC_SATURN_SHIP:
+	case IDI_MSA_PIC_PLUTO_SHIP:
+	case IDI_MSA_PIC_JUPITER_SHIP:
+	case IDI_MSA_PIC_MARS_SHIP:
+	case IDI_MSA_PIC_URANUS_SHIP:
+	case IDI_MSA_PIC_SHIP_VENUS:
+	case IDI_MSA_PIC_SHIP_NEPTUNE:
+	case IDI_MSA_PIC_SHIP_MERCURY:
+	case IDI_MSA_PIC_SHIP_SATURN:
+	case IDI_MSA_PIC_SHIP_PLUTO:
+	case IDI_MSA_PIC_SHIP_JUPITER:
+	case IDI_MSA_PIC_SHIP_MARS:
+	case IDI_MSA_PIC_SHIP_URANUS: {
+		// draw blinking ship lights
+
+		uint8 iColor = 0;
+
+		_picture->setPattern(2, 0);
+
+		for (int i = 0; i < 12; i++) {
+			iColor = _gameStateMickey.nFrame + i;
+			if (iColor > 15)
+				iColor -= 15;
+
+			objLight[1] = iColor;
+			objLight[4] += 7;
+
+			_picture->setPictureData(objLight);
+			_picture->setPictureFlags(kPicFCircle);
+			_picture->drawPicture();
+		}
+		_picture->showPic(10, 0, IDI_MSA_PIC_WIDTH, IDI_MSA_PIC_HEIGHT);
+
+
+		_gameStateMickey.nFrame--;
+		if (_gameStateMickey.nFrame < 0)
+			_gameStateMickey.nFrame = 15;
+
+		playSound(IDI_MSA_SND_PRESS_BLUE);
+	}
+	break;
+
+	case IDI_MSA_PIC_SHIP_CONTROLS:
+
+		// draw XL30 screen
+		if (_gameStateMickey.fAnimXL30) {
+			if (_gameStateMickey.nFrame > 5)
+				_gameStateMickey.nFrame = 0;
+
+			drawObj((ENUM_MSA_OBJECT)(IDI_MSA_OBJECT_XL31 + _gameStateMickey.nFrame), 0, 4);
+			_gameStateMickey.nFrame++;
+		};
+
+		break;
+
+	default:
+
+		// draw crystal
+		if (_gameStateMickey.iRoom == IDI_MSA_XTAL_ROOM_XY[_gameStateMickey.iPlanet][0]) {
+			if (!_gameStateMickey.fHasXtal) {
+				switch (_gameStateMickey.iPlanet) {
+				case IDI_MSA_PLANET_VENUS:
+					if (_gameStateMickey.iRmMenu[_gameStateMickey.iRoom] != 2)
+						break;
+					// fall through
+				default:
+					drawObj(
+					    IDI_MSA_OBJECT_CRYSTAL,
+					    IDI_MSA_XTAL_ROOM_XY[_gameStateMickey.iPlanet][1],
+					    IDI_MSA_XTAL_ROOM_XY[_gameStateMickey.iPlanet][2]
+					);
+					break;
+				}
+			}
+		}
+
+		break;
+	}
+}
+
+void MickeyEngine::drawRoom() {
+	uint8 buffer[512];
+	int pBuf = 0;
+	int nObjs;
+
+	// Draw room picture
+	if (_gameStateMickey.iRoom == IDI_MSA_PIC_TITLE) {
+		drawPic(IDI_MSA_PIC_TITLE);
+	} else {
+		drawPic(_gameStateMickey.iRmPic[_gameStateMickey.iRoom]);
+
+		if (_gameStateMickey.iRoom == IDI_MSA_PIC_SHIP_CONTROLS) {
+			// Draw ship control room window
+			if (_gameStateMickey.fFlying) {
+				drawObj(IDI_MSA_OBJECT_W_SPACE, 0, 0);
+			} else {
+				drawObj((ENUM_MSA_OBJECT)(IDI_MSA_OBJECT_W_EARTH + _gameStateMickey.iPlanet), 0, 1);
+			}
+		}
+	}
+
+	// Draw room objects
+	if (_gameStateMickey.iRoom >= IDI_MSA_MAX_PIC_ROOM) {
+		drawRoomAnimation();
+		return;
+	}
+
+	if (_gameStateMickey.iRmObj[_gameStateMickey.iRoom] != IDI_MSA_OBJECT_NONE) {
+		readOfsData(IDO_MSA_ROOM_OBJECT_XY_OFFSETS,
+		            _gameStateMickey.iRmObj[_gameStateMickey.iRoom], buffer, sizeof(buffer));
+
+		nObjs = buffer[pBuf++];
+
+		for (int iObj = 0; iObj < nObjs; iObj++) {
+			drawObj((ENUM_MSA_OBJECT)buffer[pBuf], buffer[pBuf + 1], buffer[pBuf + 2]);
+			pBuf += 3;
+		}
+	}
+
+	// Draw room animation
+	drawRoomAnimation();
+}
+
+// Straight mapping, CGA colors to CGA
+const byte BCGColorMappingCGAToCGA[4] = {
+	0, 1, 2, 3
+};
+
+// Mapping table to map CGA colors to EGA
+const byte BCGColorMappingCGAToEGA[4] = {
+	0, 11, 13, 15
+};
+
+void MickeyEngine::drawLogo() {
+	const int width = 80;
+	const int height = 85 * 2;
+	byte  color1, color2, color3, color4;
+	byte  *fileBuffer = nullptr;
+	uint32 fileBufferSize = 0;
+	byte  *dataBuffer;
+	byte   curByte;
+	const byte *BCGColorMapping = BCGColorMappingCGAToEGA;
+
+	// disable color mapping in case we are in CGA mode
+	if (_renderMode == Common::kRenderCGA)
+		BCGColorMapping = BCGColorMappingCGAToCGA;
+
+	// read logos.bcg
+	Common::File infile;
+	if (!infile.open(IDS_MSA_PATH_LOGO))
+		return;
+
+	fileBufferSize = infile.size();
+	fileBuffer = new byte[fileBufferSize];
+	infile.read(fileBuffer, fileBufferSize);
+	infile.close();
+
+	if (fileBufferSize < (width * height / 4))
+		error("logos.bcg: unexpected end of file");
+
+	// Show BCG picture
+	// It's basically uncompressed CGA 4-color data (4 pixels per byte)
+	dataBuffer = fileBuffer;
+	for (int y = 0; y < height; y++) {
+		for (int x = 0; x < width; x++) {
+			curByte = *dataBuffer++;
+
+			color1 = BCGColorMapping[(curByte >> 6) & 0x03];
+			color2 = BCGColorMapping[(curByte >> 4) & 0x03];
+			color3 = BCGColorMapping[(curByte >> 2) & 0x03];
+			color4 = BCGColorMapping[(curByte >> 0) & 0x03];
+
+			_gfx->putPixelOnDisplay(x * 4 + 0, y, color1);
+			_gfx->putPixelOnDisplay(x * 4 + 1, y, color2);
+			_gfx->putPixelOnDisplay(x * 4 + 2, y, color3);
+			_gfx->putPixelOnDisplay(x * 4 + 3, y, color4);
+		}
+	}
+
+	_gfx->copyDisplayToScreen();
+
+	delete[] fileBuffer;
+}
+
+void MickeyEngine::animate() {
+	_system->delayMillis(IDI_MSA_ANIM_DELAY);
+	drawRoomAnimation();
+}
+
+void MickeyEngine::printRoomDesc() {
+	// print room description
+	printDesc(_gameStateMickey.iRoom);
+	waitAnyKey(true);
+
+	// print extended room description
+	if (_gameStateMickey.oRmTxt[_gameStateMickey.iRoom]) {
+		printExeMsg(_gameStateMickey.oRmTxt[_gameStateMickey.iRoom] + IDI_MSA_OFS_EXE);
+	}
+}
+
+bool MickeyEngine::loadGame() {
+	Common::InSaveFile *infile;
+	char szFile[256] = {0};
+	bool diskerror = true;
+	int sel;
+	int saveVersion = 0;
+	int i = 0;
+
+	while (diskerror) {
+		sel = choose1to9(IDO_MSA_LOAD_GAME[1]);
+		if (!sel)
+			return false;
+
+		// load game
+		sprintf(szFile, "%s.s%02d", getTargetName().c_str(), sel);
+		if (!(infile = getSaveFileMan()->openForLoading(szFile))) {
+			printLine("PLEASE CHECK THE DISK DRIVE");
+
+			if (getSelection(kSelAnyKey) == 0)
+				return false;
+		} else {
+			if (infile->readUint32BE() != MKTAG('M', 'I', 'C', 'K')) {
+				warning("MickeyEngine::loadGame wrong save game format");
+				return false;
+			}
+
+			saveVersion = infile->readByte();
+			if (saveVersion < 2) {
+				warning("The planet data in this save game is corrupted. Load aborted");
+				return false;
+			}
+
+			if (saveVersion != MSA_SAVEGAME_VERSION)
+				warning("Old save game version (%d, current version is %d). Will try and read anyway, but don't be surprised if bad things happen", saveVersion, MSA_SAVEGAME_VERSION);
+
+			_gameStateMickey.iRoom = infile->readByte();
+			_gameStateMickey.iPlanet = infile->readByte();
+			_gameStateMickey.iDisk = infile->readByte();
+
+			_gameStateMickey.nAir = infile->readByte();
+			_gameStateMickey.nButtons = infile->readByte();
+			_gameStateMickey.nRocks = infile->readByte();
+
+			_gameStateMickey.nXtals = infile->readByte();
+
+			for (i = 0; i < IDI_MSA_MAX_DAT; i++)
+				_gameStateMickey.iPlanetXtal[i] = infile->readByte();
+
+			for (i = 0; i < IDI_MSA_MAX_PLANET; i++)
+				_gameStateMickey.iClue[i] = infile->readUint16LE();
+
+			infile->read(_gameStateMickey.szAddr, IDI_MSA_MAX_BUTTON + 1);
+
+			_gameStateMickey.fHasXtal = infile->readByte() == 1;
+			_gameStateMickey.fIntro = infile->readByte() == 1;
+			_gameStateMickey.fSuit = infile->readByte() == 1;
+			_gameStateMickey.fShipDoorOpen = infile->readByte() == 1;
+			_gameStateMickey.fFlying = infile->readByte() == 1;
+			_gameStateMickey.fStoryShown = infile->readByte() == 1;
+			_gameStateMickey.fPlanetsInitialized = infile->readByte() == 1;
+			_gameStateMickey.fTempleDoorOpen = infile->readByte() == 1;
+			_gameStateMickey.fAnimXL30 = infile->readByte() == 1;
+
+			for (i = 0; i < IDI_MSA_MAX_ITEM; i++)
+				_gameStateMickey.fItem[i] = infile->readByte() == 1;
+
+			for (i = 0; i < IDI_MSA_MAX_ITEM; i++)
+				_gameStateMickey.fItemUsed[i] = infile->readByte() == 1;
+
+			for (i = 0; i < IDI_MSA_MAX_ITEM; i++)
+				_gameStateMickey.iItem[i] = infile->readSByte();
+
+			_gameStateMickey.nItems = infile->readByte();
+
+			for (i = 0; i < IDI_MSA_MAX_ROOM; i++)
+				_gameStateMickey.iRmObj[i] = infile->readSByte();
+
+			for (i = 0; i < IDI_MSA_MAX_ROOM; i++)
+				_gameStateMickey.iRmPic[i] = infile->readByte();
+
+			for (i = 0; i < IDI_MSA_MAX_ROOM; i++)
+				_gameStateMickey.oRmTxt[i] = infile->readUint16LE();
+
+			for (i = 0; i < IDI_MSA_MAX_ROOM; i++)
+				_gameStateMickey.iRmMenu[i] = infile->readByte();
+
+			for (i = 0; i < IDI_MSA_MAX_ROOM; i++)
+				_gameStateMickey.nRmMenu[i] = infile->readByte();
+
+			_gameStateMickey.nFrame = infile->readSByte();
+
+			diskerror = false;
+			delete infile;
+		}
+	}
+
+	printExeMsg(IDO_MSA_LOAD_GAME[2]);
+	return true;
+}
+
+void MickeyEngine::saveGame() {
+	Common::OutSaveFile *outfile;
+	char szFile[256] = {0};
+	bool diskerror = true;
+	int sel;
+	int i = 0;
+
+	bool fOldDisk = chooseY_N(IDO_MSA_SAVE_GAME[0], false);
+
+	if (fOldDisk)
+		printExeStr(IDO_MSA_SAVE_GAME[1]);
+	else
+		printExeStr(IDO_MSA_SAVE_GAME[2]);
+
+	if (getSelection(kSelAnyKey) == 0)
+		return;
+
+	while (diskerror) {
+		sel = choose1to9(IDO_MSA_SAVE_GAME[3]);
+		if (!sel)
+			return;
+
+		if (fOldDisk)
+			printExeStr(IDO_MSA_SAVE_GAME[5]);
+		else
+			printExeStr(IDO_MSA_SAVE_GAME[4]);
+
+		if (getSelection(kSelAnyKey) == 0)
+			return;
+
+		// save game
+		sprintf(szFile, "%s.s%02d", getTargetName().c_str(), sel);
+		if (!(outfile = getSaveFileMan()->openForSaving(szFile))) {
+			printLine("PLEASE CHECK THE DISK DRIVE");
+
+			if (getSelection(kSelAnyKey) == 0)
+				return;
+		} else {
+			outfile->writeUint32BE(MKTAG('M', 'I', 'C', 'K')); // header
+			outfile->writeByte(MSA_SAVEGAME_VERSION);
+
+			outfile->writeByte(_gameStateMickey.iRoom);
+			outfile->writeByte(_gameStateMickey.iPlanet);
+			outfile->writeByte(_gameStateMickey.iDisk);
+
+			outfile->writeByte(_gameStateMickey.nAir);
+			outfile->writeByte(_gameStateMickey.nButtons);
+			outfile->writeByte(_gameStateMickey.nRocks);
+
+			outfile->writeByte(_gameStateMickey.nXtals);
+
+			for (i = 0; i < IDI_MSA_MAX_DAT; i++)
+				outfile->writeByte(_gameStateMickey.iPlanetXtal[i]);
+
+			for (i = 0; i < IDI_MSA_MAX_PLANET; i++)
+				outfile->writeUint16LE(_gameStateMickey.iClue[i]);
+
+			outfile->write(_gameStateMickey.szAddr, IDI_MSA_MAX_BUTTON + 1);
+
+			outfile->writeByte(_gameStateMickey.fHasXtal ? 1 : 0);
+			outfile->writeByte(_gameStateMickey.fIntro ? 1 : 0);
+			outfile->writeByte(_gameStateMickey.fSuit ? 1 : 0);
+			outfile->writeByte(_gameStateMickey.fShipDoorOpen ? 1 : 0);
+			outfile->writeByte(_gameStateMickey.fFlying ? 1 : 0);
+			outfile->writeByte(_gameStateMickey.fStoryShown ? 1 : 0);
+			outfile->writeByte(_gameStateMickey.fPlanetsInitialized ? 1 : 0);
+			outfile->writeByte(_gameStateMickey.fTempleDoorOpen ? 1 : 0);
+			outfile->writeByte(_gameStateMickey.fAnimXL30 ? 1 : 0);
+
+			for (i = 0; i < IDI_MSA_MAX_ITEM; i++)
+				outfile->writeByte(_gameStateMickey.fItem[i] ? 1 : 0);
+
+			for (i = 0; i < IDI_MSA_MAX_ITEM; i++)
+				outfile->writeByte(_gameStateMickey.fItemUsed[i] ? 1 : 0);
+
+			for (i = 0; i < IDI_MSA_MAX_ITEM; i++)
+				outfile->writeSByte(_gameStateMickey.iItem[i]);
+
+			outfile->writeByte(_gameStateMickey.nItems);
+
+			for (i = 0; i < IDI_MSA_MAX_ROOM; i++)
+				outfile->writeSByte(_gameStateMickey.iRmObj[i]);
+
+			for (i = 0; i < IDI_MSA_MAX_ROOM; i++)
+				outfile->writeByte(_gameStateMickey.iRmPic[i]);
+
+			for (i = 0; i < IDI_MSA_MAX_ROOM; i++)
+				outfile->writeUint16LE(_gameStateMickey.oRmTxt[i]);
+
+			for (i = 0; i < IDI_MSA_MAX_ROOM; i++)
+				outfile->writeByte(_gameStateMickey.iRmMenu[i]);
+
+			for (i = 0; i < IDI_MSA_MAX_ROOM; i++)
+				outfile->writeByte(_gameStateMickey.nRmMenu[i]);
+
+			outfile->writeSByte(_gameStateMickey.nFrame);
+
+			outfile->finalize();
+
+			if (outfile->err())
+				warning("Can't write file '%s'. (Disk full?)", szFile);
+
+			diskerror = false;
+			delete outfile;
+		}
+	}
+
+	printExeMsg(IDO_MSA_SAVE_GAME[6]);
+}
+
+void MickeyEngine::showPlanetInfo() {
+	for (int i = 0; i < 4; i++) {
+		printExeStr(IDO_MSA_PLANET_INFO[_gameStateMickey.iPlanet][i]);
+		waitAnyKey();
+	}
+}
+
+void MickeyEngine::printStory() {
+	char buffer[IDI_MSA_LEN_STORY] = {0};
+	char szLine[41] = {0};
+	int iRow;
+	int pBuf = 0;
+
+	readExe(IDO_MSA_GAME_STORY, (uint8 *)buffer, sizeof(buffer));
+
+	clearScreen(IDA_DEFAULT);
+	for (iRow = 0; iRow < 25; iRow++) {
+		Common::strlcpy(szLine, buffer + pBuf, 41);
+		drawStr(iRow, 0, IDA_DEFAULT, szLine);
+		pBuf += strlen(szLine) + 1;
+	}
+	waitAnyKey();
+
+	clearScreen(IDA_DEFAULT);
+	for (iRow = 0; iRow < 21; iRow++) {
+		Common::strlcpy(szLine, buffer + pBuf, 41);
+		drawStr(iRow, 0, IDA_DEFAULT, szLine);
+		pBuf += strlen(szLine) + 1;
+	}
+	waitAnyKey();
+
+	//Set back to black
+	_gfx->clearDisplay(0);
+	_gfx->updateScreen();
+
+	drawRoom();
+
+	_gameStateMickey.fStoryShown = true;
+}
+
+int MickeyEngine::getPlanet() {
+	if (!_gameStateMickey.nButtons)
+		return -1;
+
+	for (int iPlanet = 0; iPlanet < IDI_MSA_MAX_DAT - 1; iPlanet++) {
+		if (!strcmp(IDS_MSA_ADDR_PLANET[iPlanet], _gameStateMickey.szAddr)) {
+			return iPlanet;
+		}
+	}
+
+	return -1;
+}
+
+void MickeyEngine::pressOB(int iButton) {
+	char szButtons[12] = {0};
+
+	// check if too many buttons pressed
+	if (_gameStateMickey.nButtons == IDI_MSA_MAX_BUTTON) {
+		_gameStateMickey.nButtons = 0;
+		memset(_gameStateMickey.szAddr, 0, sizeof(_gameStateMickey.szAddr));
+		printExeMsg(IDO_MSA_TOO_MANY_BUTTONS_PRESSED);
+		return;
+	}
+
+	// add button press to address
+	_gameStateMickey.nButtons++;
+	_gameStateMickey.szAddr[_gameStateMickey.nButtons - 1] = (char)iButton;
+
+	// format buttons string
+	for (int i = 0; i < IDI_MSA_MAX_BUTTON; i++) {
+		szButtons[i * 2] = _gameStateMickey.szAddr[i];
+		if (_gameStateMickey.szAddr[i + 1]) szButtons[(i * 2) + 1] = ',';
+	}
+
+	// print pressed buttons
+	printLine("MICKEY HAS PRESSED:                  ");
+	drawStr(20, 22, IDA_DEFAULT, szButtons);
+	waitAnyKey();
+}
+
+void MickeyEngine::insertDisk(int iDisk) {
+	clearTextArea();
+	drawStr(IDI_MSA_ROW_INSERT_DISK, IDI_MSA_COL_INSERT_DISK, IDA_DEFAULT, (const char *)IDS_MSA_INSERT_DISK[iDisk]);
+	waitAnyKey();
+}
+
+void MickeyEngine::gameOver() {
+	// We shouldn't run the game over segment if we're quitting.
+	if (shouldQuit())
+		return;
+
+	drawPic(IDI_MSA_PIC_EARTH_SHIP_LEAVING);
+	printExeMsg(IDO_MSA_GAME_OVER[3]);
+	playSound(IDI_MSA_SND_GAME_OVER);
+
+	if (_gameStateMickey.fItemUsed[IDI_MSA_ITEM_LETTER]) {
+		drawPic(IDI_MSA_PIC_EARTH_MINNIE);
+		printExeMsg(IDO_MSA_GAME_OVER[4]);
+		printExeMsg(IDO_MSA_GAME_OVER[5]);
+	} else {
+		printExeMsg(IDO_MSA_GAME_OVER[6]);
+		printExeMsg(IDO_MSA_GAME_OVER[7]);
+	}
+
+	waitAnyKey();
+}
+
+void MickeyEngine::flipSwitch() {
+	if (_gameStateMickey.fHasXtal || _gameStateMickey.nXtals) {
+		if (!_gameStateMickey.fStoryShown)
+			printStory();
+
+		// Initialize planet data
+		if (!_gameStateMickey.fPlanetsInitialized) {
+			int iHint = 0;
+			int iPlanet = 0;
+
+			memset(_gameStateMickey.iPlanetXtal, 0, sizeof(_gameStateMickey.iPlanetXtal));
+			memset(_gameStateMickey.iClue, 0, sizeof(_gameStateMickey.iClue));
+
+			_gameStateMickey.iPlanetXtal[0] = IDI_MSA_PLANET_EARTH;
+			_gameStateMickey.iPlanetXtal[8] = IDI_MSA_PLANET_URANUS;
+
+			for (int i = 1; i < IDI_MSA_MAX_PLANET; i++) {
+				if (i < 8) {
+					do {
+						// Earth (planet 0) and Uranus (planet 8) are excluded
+						iPlanet = rnd(IDI_MSA_MAX_PLANET - 2);
+					} while (planetIsAlreadyAssigned(iPlanet));
+				} else {
+					iPlanet = IDI_MSA_PLANET_URANUS;    // Uranus is always last
+				}
+
+				_gameStateMickey.iPlanetXtal[i] = iPlanet;
+				iHint = rnd(5) - 1; // clues are 0-4
+				_gameStateMickey.iClue[i] = IDO_MSA_NEXT_PIECE[iPlanet][iHint];
+			}
+
+			_gameStateMickey.fPlanetsInitialized = true;
+		}
+
+		// activate screen animation
+		_gameStateMickey.fAnimXL30 = true;
+
+		clearTextArea();
+		playSound(IDI_MSA_SND_XL30);
+		printExeMsg(IDO_MSA_XL30_SPEAKING);
+
+		if (_gameStateMickey.fHasXtal) {
+			_gameStateMickey.fHasXtal = false;
+			printExeMsg(IDO_MSA_CRYSTAL_PIECE_FOUND);
+		}
+
+		if (_gameStateMickey.nXtals == IDI_MSA_MAX_PLANET) {
+			printExeMsg(IDO_MSA_GAME_OVER[0]);
+			printExeMsg(IDO_MSA_GAME_OVER[1]);
+			printExeMsg(IDO_MSA_GAME_OVER[2]);
+
+#if 0
+			// DEBUG
+			strcpy(_gameStateMickey.szAddr, (char *)IDS_MSA_ADDR_PLANET[IDI_MSA_PLANET_EARTH]);
+			_gameStateMickey.nButtons = strlen(_gameStateMickey.szAddr);
+#endif
+
+		} else {
+			printExeStr(_gameStateMickey.iClue[_gameStateMickey.nXtals]);
+
+#if 0
+			// DEBUG
+			drawStr(24, 12, IDA_DEFAULT, (char *)IDS_MSA_NAME_PLANET_2[_gameStateMickey.iPlanetXtal[_gameStateMickey.nXtals]]);
+			drawStr(24, 22, IDA_DEFAULT, (char *)IDS_MSA_ADDR_PLANET[_gameStateMickey.iPlanetXtal[_gameStateMickey.nXtals]]);
+			strcpy(_gameStateMickey.szAddr, (char *)IDS_MSA_ADDR_PLANET[_gameStateMickey.iPlanetXtal[_gameStateMickey.nXtals]]);
+			_gameStateMickey.nButtons = strlen(_gameStateMickey.szAddr);
+			_gfx->doUpdate();
+#endif
+
+			waitAnyKey(true);
+		}
+	} else {
+		printStory();
+	}
+}
+
+void MickeyEngine::inventory() {
+	int iRow = IDI_MSA_ROW_INV_ITEMS;
+	char szCrystals[12] = {0};
+
+	sprintf(szCrystals, IDS_MSA_CRYSTALS, IDS_MSA_CRYSTAL_NO[_gameStateMickey.nXtals]);
+
+	CursorMan.showMouse(false);
+
+	clearScreen(IDA_DEFAULT);
+	drawStr(IDI_MSA_ROW_INV_TITLE, IDI_MSA_COL_INV_TITLE, IDA_DEFAULT, IDS_MSA_INVENTORY);
+	drawStr(IDI_MSA_ROW_INV_CRYSTALS, IDI_MSA_COL_INV_ITEMS, IDA_DEFAULT, szCrystals);
+
+	for (int iItem = 0; iItem < IDI_MSA_MAX_ITEM; iItem++) {
+		if (_gameStateMickey.fItem[_gameStateMickey.iItem[iItem]] && (_gameStateMickey.iItem[iItem] != IDI_MSA_OBJECT_NONE)) {
+			drawStr(iRow++, IDI_MSA_COL_INV_ITEMS, IDA_DEFAULT, (const char *)IDS_MSA_NAME_ITEM[_gameStateMickey.iItem[iItem]]);
+		}
+	}
+
+	waitAnyKey();
+
+	clearScreen(IDA_DEFAULT);
+
+	CursorMan.showMouse(true);
+}
+
+void MickeyEngine::intro() {
+	// Draw Sierra logo
+	drawLogo();     // Original does not even show this, so we skip it too
+	waitAnyKey();       // Not in the original, but needed so that the logo is visible
+
+	// draw title picture
+	_gameStateMickey.iRoom = IDI_MSA_PIC_TITLE;
+	drawRoom();
+
+	// show copyright and play theme
+	printExeMsg(IDO_MSA_COPYRIGHT);
+
+	// Quit if necessary
+	if (shouldQuit())
+		return;
+
+	playSound(IDI_MSA_SND_THEME);
+
+	// load game
+	_gameStateMickey.fIntro = true;
+	if (chooseY_N(IDO_MSA_LOAD_GAME[0], true)) {
+		if (loadGame()) {
+			_gameStateMickey.iPlanet = IDI_MSA_PLANET_EARTH;
+			_gameStateMickey.fIntro = false;
+			_gameStateMickey.iRoom = IDI_MSA_PIC_SHIP_CORRIDOR;
+			return;
+		}
+	}
+
+	// Quit if necessary
+	if (shouldQuit())
+		return;
+
+	// play spaceship landing scene
+	_gameStateMickey.iPlanet = IDI_MSA_PLANET_EARTH;
+	_gameStateMickey.iRoom = IDI_MSA_PIC_EARTH_ROAD_4;
+
+	drawRoom();
+	printRoomDesc();
+
+	// Quit if necessary
+	if (shouldQuit())
+		return;
+
+	playSound(IDI_MSA_SND_SHIP_LAND);
+
+	// Flash screen 3 times
+	for (byte i = 0; i < 3; i++) {
+		playSound(IDI_MSA_SND_PRESS_BLUE);
+
+		//Set screen to white
+		_gfx->clearDisplay(15);
+		_gfx->updateScreen();
+
+		_system->delayMillis(IDI_MSA_ANIM_DELAY);
+
+		//Set back to black
+		_gfx->clearDisplay(0);
+		_gfx->updateScreen();
+
+		drawRoom();
+		printDesc(_gameStateMickey.iRoom);
+	}
+
+	printExeMsg(IDO_MSA_INTRO);
+}
+
+void MickeyEngine::getItem(ENUM_MSA_ITEM iItem) {
+	_gameStateMickey.fItem[iItem] = true;
+	_gameStateMickey.iItem[_gameStateMickey.nItems++] = iItem;
+	_gameStateMickey.oRmTxt[_gameStateMickey.iRoom] = 0;
+	playSound(IDI_MSA_SND_TAKE);
+	drawRoom();
+}
+
+void MickeyEngine::getXtal(int iStr) {
+	_gameStateMickey.oRmTxt[_gameStateMickey.iRoom] = 0;
+	_gameStateMickey.fHasXtal = true;
+	_gameStateMickey.nXtals++;
+	playSound(IDI_MSA_SND_CRYSTAL);
+	drawRoom();
+	printDatMessage(iStr);
+}
+
+bool MickeyEngine::parse(int cmd, int arg) {
+	switch (cmd) {
+
+	// BASIC
+
+	case IDI_MSA_ACTION_GOTO_ROOM:
+		_gameStateMickey.iRoom = arg;
+		return true;
+	case IDI_MSA_ACTION_SHOW_INT_STR:
+		printLine(IDS_MSA_ERRORS[arg]);
+		break;
+	case IDI_MSA_ACTION_SHOW_DAT_STR:
+		printDatMessage(arg);
+		break;
+
+	// GENERAL
+
+	case IDI_MSA_ACTION_PLANET_INFO:
+		showPlanetInfo();
+		break;
+	case IDI_MSA_ACTION_SAVE_GAME:
+		saveGame();
+		break;
+	case IDI_MSA_ACTION_LOOK_MICKEY:
+		printLine("YOU CAN SEE MICKEY ALREADY");
+		break;
+
+	// EARTH
+
+	case IDI_MSA_ACTION_GET_ROPE:
+		if (_gameStateMickey.iRmMenu[_gameStateMickey.iRoom] == 2) {
+			_gameStateMickey.iRmObj[_gameStateMickey.iRoom] = IDI_MSA_OBJECT_NONE;
+			_gameStateMickey.iRmMenu[_gameStateMickey.iRoom] = 3;
+			getItem(IDI_MSA_ITEM_ROPE);
+			printLine("MICKEY TAKES THE ROPE");
+		} else {
+			_gameStateMickey.iRmMenu[_gameStateMickey.iRoom] = 1;
+			printDatMessage(11);
+		}
+		break;
+	case IDI_MSA_ACTION_UNTIE_ROPE:
+		_gameStateMickey.iRmPic[_gameStateMickey.iRoom] = IDI_MSA_PIC_EARTH_TIRE_SWING_1;
+		_gameStateMickey.iRmObj[_gameStateMickey.iRoom] = 0;
+		_gameStateMickey.iRmMenu[_gameStateMickey.iRoom] = 2;
+		drawRoom();
+		printDatMessage(12);
+		break;
+	case IDI_MSA_ACTION_GET_BONE:
+		_gameStateMickey.iRmObj[_gameStateMickey.iRoom] = IDI_MSA_OBJECT_NONE;
+		_gameStateMickey.iRmMenu[_gameStateMickey.iRoom] = 1;
+		getItem(IDI_MSA_ITEM_BONE);
+		printDatMessage(arg);
+		break;
+	case IDI_MSA_ACTION_GET_XTAL_EARTH:
+		_gameStateMickey.iRmMenu[_gameStateMickey.iRoom] = 1;
+		getXtal(arg);
+		break;
+	case IDI_MSA_ACTION_LOOK_DESK:
+		_gameStateMickey.iRmMenu[_gameStateMickey.iRoom] = 1;
+		_gameStateMickey.iRmObj[_gameStateMickey.iRoom] = 2;
+		drawRoom();
+		printDatMessage(arg);
+		break;
+	case IDI_MSA_ACTION_WRITE_LETTER:
+		_gameStateMickey.iRmMenu[_gameStateMickey.iRoom] = 3;
+		_gameStateMickey.iRmMenu[IDI_MSA_PIC_EARTH_MAILBOX] = 1;
+		_gameStateMickey.iRmObj[_gameStateMickey.iRoom] = IDI_MSA_OBJECT_NONE;
+		getItem(IDI_MSA_ITEM_LETTER);
+		printDatMessage(arg);
+		break;
+	case IDI_MSA_ACTION_MAIL_LETTER:
+		_gameStateMickey.fItemUsed[IDI_MSA_ITEM_LETTER] = true;
+		_gameStateMickey.fItem[IDI_MSA_ITEM_LETTER] = false;
+		_gameStateMickey.iRmMenu[_gameStateMickey.iRoom] = 0;
+		printDatMessage(arg);
+		break;
+	case IDI_MSA_ACTION_OPEN_MAILBOX:
+		if (_gameStateMickey.fItemUsed[IDI_MSA_ITEM_LETTER]) {
+			printDatMessage(110);
+		} else {
+			printDatMessage(arg);
+		}
+		break;
+	case IDI_MSA_ACTION_OPEN_CUPBOARD:
+		if (_gameStateMickey.iRmMenu[_gameStateMickey.iRoom]) {
+			if (_gameStateMickey.iRmObj[_gameStateMickey.iRoom] == IDI_MSA_OBJECT_NONE) {
+				printDatMessage(78);
+			} else {
+				printDatMessage(arg);
+			}
+		} else {
+			_gameStateMickey.iRmMenu[_gameStateMickey.iRoom] = 1;
+			_gameStateMickey.iRmPic[_gameStateMickey.iRoom] = IDI_MSA_PIC_EARTH_KITCHEN_1;
+			_gameStateMickey.iRmObj[_gameStateMickey.iRoom] = 3;
+			drawRoom();
+			printDatMessage(arg);
+		}
+		break;
+	case IDI_MSA_ACTION_GET_FLASHLIGHT:
+		if (!mickeyHasItem(IDI_MSA_ITEM_FLASHLIGHT)) {
+			_gameStateMickey.iRmObj[_gameStateMickey.iRoom] = IDI_MSA_OBJECT_NONE;
+			getItem(IDI_MSA_ITEM_FLASHLIGHT);
+			drawRoom();
+			printDatMessage(arg);
+		}
+		break;
+	case IDI_MSA_ACTION_OPEN_CABINET:
+		if (_gameStateMickey.iRmMenu[_gameStateMickey.iRoom]) {
+			printDatMessage(109);
+		} else {
+			_gameStateMickey.iRmMenu[_gameStateMickey.iRoom] = 1;
+			_gameStateMickey.iRmPic[_gameStateMickey.iRoom] = IDI_MSA_PIC_EARTH_GARAGE_1;
+			_gameStateMickey.iRmObj[_gameStateMickey.iRoom] = 15;
+			drawRoom();
+			printDatMessage(arg);
+		}
+		break;
+	case IDI_MSA_ACTION_GET_CROWBAR:
+		if (!mickeyHasItem(IDI_MSA_ITEM_CROWBAR)) {
+			_gameStateMickey.iRmObj[_gameStateMickey.iRoom]--;
+			getItem(IDI_MSA_ITEM_CROWBAR);
+			printDatMessage(arg);
+		}
+		break;
+	case IDI_MSA_ACTION_GET_WRENCH:
+		if (!mickeyHasItem(IDI_MSA_ITEM_WRENCH)) {
+			_gameStateMickey.iRmObj[_gameStateMickey.iRoom] -= 2;
+			getItem(IDI_MSA_ITEM_WRENCH);
+			printDatMessage(arg);
+		}
+		break;
+	case IDI_MSA_ACTION_OPEN_CLOSET:
+		if (_gameStateMickey.iRmMenu[_gameStateMickey.iRoom]) {
+			printDatMessage(99);
+		} else {
+			_gameStateMickey.iRmMenu[_gameStateMickey.iRoom] = 1;
+			_gameStateMickey.iRmPic[_gameStateMickey.iRoom] = IDI_MSA_PIC_EARTH_BEDROOM_1;
+			_gameStateMickey.iRmObj[_gameStateMickey.iRoom] = 7;
+			drawRoom();
+			printDatMessage(arg);
+		}
+		break;
+	case IDI_MSA_ACTION_GET_MATTRESS:
+		if (!mickeyHasItem(IDI_MSA_ITEM_MATTRESS)) {
+			_gameStateMickey.iRmObj[_gameStateMickey.iRoom]--;
+			getItem(IDI_MSA_ITEM_MATTRESS);
+			printDatMessage(arg);
+		}
+		break;
+	case IDI_MSA_ACTION_GET_SCARF:
+		if (!mickeyHasItem(IDI_MSA_ITEM_SCARF)) {
+			_gameStateMickey.iRmObj[_gameStateMickey.iRoom] -= 2;
+			getItem(IDI_MSA_ITEM_SCARF);
+			printDatMessage(arg);
+		}
+		break;
+	case IDI_MSA_ACTION_GET_SUNGLASSES:
+		if (!mickeyHasItem(IDI_MSA_ITEM_SUNGLASSES)) {
+			_gameStateMickey.iRmObj[_gameStateMickey.iRoom]--;
+			getItem(IDI_MSA_ITEM_SUNGLASSES);
+			printDatMessage(arg);
+		}
+		break;
+	case IDI_MSA_ACTION_GET_SCALE:
+		if (!mickeyHasItem(IDI_MSA_ITEM_SCALE)) {
+			_gameStateMickey.iRmMenu[IDI_MSA_PIC_VENUS_WEIGH] = 1;
+			_gameStateMickey.iRmMenu[IDI_MSA_PIC_NEPTUNE_WEIGH] = 1;
+			_gameStateMickey.iRmMenu[IDI_MSA_PIC_MERCURY_WEIGH] = 1;
+			_gameStateMickey.iRmMenu[IDI_MSA_PIC_SATURN_WEIGH] = 1;
+			_gameStateMickey.iRmMenu[IDI_MSA_PIC_PLUTO_WEIGH] = 1;
+			_gameStateMickey.iRmMenu[IDI_MSA_PIC_JUPITER_WEIGH] = 1;
+			_gameStateMickey.iRmMenu[IDI_MSA_PIC_MARS_WEIGH] = 1;
+			_gameStateMickey.iRmMenu[IDI_MSA_PIC_URANUS_WEIGH] = 1;
+			_gameStateMickey.iRmObj[_gameStateMickey.iRoom] -= 2;
+			getItem(IDI_MSA_ITEM_SCALE);
+			printDatMessage(arg);
+		}
+		break;
+	case IDI_MSA_ACTION_GOTO_SPACESHIP:
+		_gameStateMickey.iRoom = IDI_MSA_PIC_SHIP_AIRLOCK;
+		if (_gameStateMickey.iPlanet != IDI_MSA_PLANET_EARTH)
+			insertDisk(0);
+		return true;
+
+	// VENUS
+
+	case IDI_MSA_ACTION_DOWN_CHASM:
+		if (_gameStateMickey.fItem[IDI_MSA_ITEM_ROPE]) {
+			_gameStateMickey.iRmMenu[_gameStateMickey.iRoom] = 1;
+		}
+		printDatMessage(arg);
+		break;
+	case IDI_MSA_ACTION_DOWN_ROPE:
+		if (_gameStateMickey.fItemUsed[IDI_MSA_ITEM_ROPE]) {
+			_gameStateMickey.iRoom = IDI_MSA_PIC_VENUS_PROBE;
+			return true;
+		} else {
+			printDatMessage(arg);
+		}
+		break;
+	case IDI_MSA_ACTION_USE_ROPE:
+		if (_gameStateMickey.fItemUsed[IDI_MSA_ITEM_ROPE]) {
+			printDatMessage(22);
+		} else {
+			_gameStateMickey.fItemUsed[IDI_MSA_ITEM_ROPE] = true;
+			_gameStateMickey.fItem[IDI_MSA_ITEM_ROPE] = false;
+			_gameStateMickey.iRmPic[_gameStateMickey.iRoom] = IDI_MSA_PIC_VENUS_CHASM_1;
+			drawRoom();
+			printDatMessage(arg);
+		}
+		break;
+	case IDI_MSA_ACTION_OPEN_HATCH:
+		if (_gameStateMickey.fItemUsed[IDI_MSA_ITEM_WRENCH]) {
+			if ((_gameStateMickey.iRmMenu[_gameStateMickey.iRoom] == 3) || (_gameStateMickey.iRmPic[_gameStateMickey.iRoom] == IDI_MSA_PIC_VENUS_PROBE_1))
+				printDatMessage(39);
+			else {
+				_gameStateMickey.iRmMenu[_gameStateMickey.iRoom] = 2;
+				_gameStateMickey.iRmPic[_gameStateMickey.iRoom] = IDI_MSA_PIC_VENUS_PROBE_1;
+				drawRoom();
+				printDatMessage(24);
+			}
+		} else {
+			if (_gameStateMickey.fItem[IDI_MSA_ITEM_WRENCH]) {
+				_gameStateMickey.iRmMenu[_gameStateMickey.iRoom] = 1;
+			}
+
+			printDatMessage(arg);
+		}
+		break;
+	case IDI_MSA_ACTION_USE_WRENCH:
+		_gameStateMickey.fItemUsed[IDI_MSA_ITEM_WRENCH] = true;
+		printDatString(arg);
+
+		if (_gameStateMickey.iRmPic[_gameStateMickey.iRoom] == IDI_MSA_PIC_VENUS_PROBE_1) {
+			clearRow(22);
+		}
+
+		waitAnyKey();
+		break;
+	case IDI_MSA_ACTION_GET_XTAL_VENUS:
+		_gameStateMickey.iRmMenu[_gameStateMickey.iRoom] = 3;
+		getXtal(arg);
+		break;
+
+	// TRITON (NEPTUNE)
+
+	case IDI_MSA_ACTION_LOOK_CASTLE:
+		if (!_gameStateMickey.iRmMenu[_gameStateMickey.iRoom]) {
+			_gameStateMickey.iRmMenu[_gameStateMickey.iRoom] = 1;
+		}
+		printDatMessage(arg);
+		break;
+	case IDI_MSA_ACTION_ENTER_OPENING:
+		if (_gameStateMickey.fItemUsed[IDI_MSA_ITEM_CROWBAR]) {
+			_gameStateMickey.iRoom = IDI_MSA_PIC_NEPTUNE_CASTLE_4;
+
+			return true;
+		} else {
+			if (_gameStateMickey.fItem[IDI_MSA_ITEM_CROWBAR]) {
+				_gameStateMickey.iRmMenu[_gameStateMickey.iRoom] = 2;
+			}
+
+			printDatMessage(arg);
+		}
+		break;
+	case IDI_MSA_ACTION_USE_CROWBAR:
+		_gameStateMickey.fItemUsed[IDI_MSA_ITEM_CROWBAR] = true;
+		_gameStateMickey.iRmMenu[_gameStateMickey.iRoom] = 1;
+		_gameStateMickey.iRmPic[_gameStateMickey.iRoom] = IDI_MSA_PIC_NEPTUNE_ENTRANCE_1;
+		drawRoom();
+		printDatMessage(arg);
+		break;
+	case IDI_MSA_ACTION_GET_XTAL_NEPTUNE:
+		if (_gameStateMickey.fHasXtal) {
+			printDatMessage(71);
+		} else {
+			if (_gameStateMickey.fItem[IDI_MSA_ITEM_SCARF]) {
+				_gameStateMickey.iRmMenu[_gameStateMickey.iRoom] = 1;
+			}
+
+			printDatMessage(arg);
+		}
+		break;
+	case IDI_MSA_ACTION_TALK_LEADER:
+		_gameStateMickey.iRoom = IDI_MSA_PIC_NEPTUNE_ENTRYWAY;
+
+		printDatMessage(arg);
+		return true;
+	case IDI_MSA_ACTION_GIVE_SCARF:
+		_gameStateMickey.iRmObj[_gameStateMickey.iRoom] = 18;
+		getXtal(arg);
+		_gameStateMickey.fItem[IDI_MSA_ITEM_SCARF] = false;
+		_gameStateMickey.iRmMenu[_gameStateMickey.iRoom] = 0;
+		_gameStateMickey.iRmMenu[IDI_MSA_PIC_EARTH_BEDROOM] = 2;
+		_gameStateMickey.iRoom = IDI_MSA_PIC_NEPTUNE_ENTRYWAY;
+
+		return true;
+
+	// MERCURY
+
+	case IDI_MSA_ACTION_GET_XTAL_MERCURY:
+		if (_gameStateMickey.fHasXtal) {
+			_gameStateMickey.iRmMenu[_gameStateMickey.iRoom] = 2;
+			printDatMessage(32);
+		} else {
+			if (_gameStateMickey.fItem[IDI_MSA_ITEM_SUNGLASSES]) {
+				_gameStateMickey.iRmMenu[_gameStateMickey.iRoom] = 1;
+			}
+			printDatMessage(arg);
+		}
+		break;
+	case IDI_MSA_ACTION_GIVE_SUNGLASSES:
+		_gameStateMickey.iRmObj[_gameStateMickey.iRoom] = 17;
+		_gameStateMickey.iRmMenu[_gameStateMickey.iRoom] = 2;
+		_gameStateMickey.fItem[IDI_MSA_ITEM_SUNGLASSES] = false;
+
+		getXtal(arg);
+
+		break;
+
+	// TITAN (SATURN)
+
+	case IDI_MSA_ACTION_CROSS_LAKE:
+		if (_gameStateMickey.fItem[IDI_MSA_ITEM_MATTRESS]) {
+			_gameStateMickey.iRmMenu[IDI_MSA_PIC_SATURN_LAKE_0] = 1;
+			_gameStateMickey.iRmMenu[IDI_MSA_PIC_SATURN_LAKE_1] = 1;
+			_gameStateMickey.iRmMenu[IDI_MSA_PIC_SATURN_LAKE_2] = 1;
+		}
+
+		printDatMessage(arg);
+
+		break;
+	case IDI_MSA_ACTION_USE_MATTRESS:
+		_gameStateMickey.iRoom = IDI_MSA_PIC_SATURN_ISLAND;
+
+		printDatMessage(arg);
+
+		return true;
+	case IDI_MSA_ACTION_GET_XTAL_SATURN:
+		if (_gameStateMickey.fHasXtal) {
+			printDatMessage(29);
+		} else {
+			getXtal(arg);
+		}
+		break;
+	case IDI_MSA_ACTION_LEAVE_ISLAND:
+		_gameStateMickey.iRoom = IDI_MSA_PIC_SATURN_LAKE_1;
+
+		printDatMessage(arg);
+
+		return true;
+
+	// PLUTO
+
+	case IDI_MSA_ACTION_GET_XTAL_PLUTO:
+		if (_gameStateMickey.fHasXtal) {
+			printDatMessage(19);
+		} else {
+			if (_gameStateMickey.fItem[IDI_MSA_ITEM_BONE]) {
+				_gameStateMickey.iRmMenu[_gameStateMickey.iRoom] = 1;
+			}
+			printDatMessage(arg);
+		}
+		break;
+	case IDI_MSA_ACTION_GIVE_BONE:
+		_gameStateMickey.fItem[IDI_MSA_ITEM_BONE] = false;
+		_gameStateMickey.iRmMenu[_gameStateMickey.iRoom] = 0;
+		_gameStateMickey.iRmObj[_gameStateMickey.iRoom] = 16;
+
+		getXtal(arg);
+
+		break;
+
+	// IO (JUPITER)
+
+	case IDI_MSA_ACTION_GET_ROCK_0:
+	case IDI_MSA_ACTION_GET_ROCK_1:
+		if (_gameStateMickey.fItem[IDI_MSA_ITEM_ROCK]) {
+			printDatMessage(38);
+		} else {
+			_gameStateMickey.iRmMenu[_gameStateMickey.iRoom] = 1;
+			_gameStateMickey.iRmObj[_gameStateMickey.iRoom] = IDI_MSA_OBJECT_NONE;
+			getItem(IDI_MSA_ITEM_ROCK);
+			printDatMessage(arg);
+		}
+		break;
+	case IDI_MSA_ACTION_GET_XTAL_JUPITER:
+		if (_gameStateMickey.fHasXtal) {
+			printDatMessage(15);
+		} else {
+			switch (_gameStateMickey.nRocks) {
+			case 0:
+				if (_gameStateMickey.fItem[IDI_MSA_ITEM_ROCK]) {
+					_gameStateMickey.iRmMenu[_gameStateMickey.iRoom] = 1;
+				}
+				printDatMessage(arg);
+				break;
+			case 1:
+				if (_gameStateMickey.fItem[IDI_MSA_ITEM_ROCK]) {
+					_gameStateMickey.iRmMenu[_gameStateMickey.iRoom] = 1;
+				}
+				printDatMessage(34);
+				break;
+			case 2:
+				getXtal(35);
+				break;
+			default:
+				break;
+			}
+		}
+		break;
+	case IDI_MSA_ACTION_THROW_ROCK:
+		_gameStateMickey.fItem[IDI_MSA_ITEM_ROCK] = false;
+		_gameStateMickey.nItems--;
+		_gameStateMickey.iRmObj[_gameStateMickey.iRoom]++;
+		_gameStateMickey.iRmMenu[_gameStateMickey.iRoom] = 0;
+
+		drawRoom();
+
+		if (_gameStateMickey.nRocks) {
+			printDatMessage(37);
+		} else {
+			printDatMessage(arg);
+		}
+
+		_gameStateMickey.nRocks++;
+		break;
+
+	// MARS
+
+	case IDI_MSA_ACTION_GO_TUBE:
+		if (_gameStateMickey.fItem[IDI_MSA_ITEM_FLASHLIGHT]) {
+			_gameStateMickey.iRmMenu[_gameStateMickey.iRoom] = 1;
+		}
+
+		printDatMessage(arg);
+
+		break;
+	case IDI_MSA_ACTION_USE_FLASHLIGHT:
+		_gameStateMickey.iRoom = IDI_MSA_PIC_MARS_TUBE_1;
+
+		printDatMessage(15);
+
+		return true;
+	case IDI_MSA_ACTION_PLUTO_DIG:
+		if (_gameStateMickey.fHasXtal) {
+			printDatMessage(21);
+		} else {
+			getXtal(arg);
+		}
+		break;
+	case IDI_MSA_ACTION_GET_XTAL_MARS:
+		if (_gameStateMickey.fHasXtal) {
+			printDatMessage(23);
+		} else {
+			printDatMessage(arg);
+		}
+		break;
+
+	// OBERON (URANUS)
+
+	case IDI_MSA_ACTION_ENTER_TEMPLE:
+		_gameStateMickey.iRoom = IDI_MSA_PIC_URANUS_TEMPLE;
+
+		return true;
+	case IDI_MSA_ACTION_USE_CRYSTAL:
+		if (_gameStateMickey.iRmMenu[_gameStateMickey.iRoom]) {
+			printDatMessage(25);
+		} else {
+			_gameStateMickey.iRmMenu[_gameStateMickey.iRoom] = 1;
+			_gameStateMickey.iRmPic[_gameStateMickey.iRoom] = IDI_MSA_PIC_URANUS_TEMPLE_1;
+
+			drawRoom();
+
+			_gameStateMickey.iRmPic[_gameStateMickey.iRoom] = IDI_MSA_PIC_URANUS_TEMPLE;
+
+			printDatMessage(arg);
+		}
+		break;
+	case IDI_MSA_ACTION_OPEN_DOOR:
+		if (_gameStateMickey.fTempleDoorOpen) {
+			printDatMessage(36);
+		} else {
+			_gameStateMickey.fTempleDoorOpen = 1;
+			_gameStateMickey.iRmPic[_gameStateMickey.iRoom] = IDI_MSA_PIC_URANUS_TEMPLE_2;
+
+			drawRoom();
+
+			printDatMessage(arg);
+		}
+		break;
+	case IDI_MSA_ACTION_ENTER_DOOR:
+		if (_gameStateMickey.fTempleDoorOpen) {
+			_gameStateMickey.iRoom = IDI_MSA_PIC_URANUS_STEPS;
+
+			return true;
+		} else {
+			printDatMessage(arg);
+		}
+		break;
+	case IDI_MSA_ACTION_GET_XTAL_URANUS:
+		if (_gameStateMickey.fHasXtal) {
+			printDatMessage(34);
+		} else {
+			if (_gameStateMickey.fItem[IDI_MSA_ITEM_CROWBAR]) {
+				_gameStateMickey.iRmMenu[_gameStateMickey.iRoom] = 1;
+			}
+			printDatMessage(arg);
+		}
+		break;
+	case IDI_MSA_ACTION_USE_CROWBAR_1:
+		_gameStateMickey.iRmMenu[_gameStateMickey.iRoom] = 0;
+
+		getXtal(arg);
+
+		break;
+
+	// SPACESHIP
+
+	case IDI_MSA_ACTION_GO_NORTH:
+		if (_gameStateMickey.fShipDoorOpen) {
+			if (_gameStateMickey.fSuit) {
+				printDatMessage(45);
+			} else {
+				_gameStateMickey.iRoom = IDI_MSA_PIC_SHIP_CORRIDOR;
+				return true;
+			}
+		} else {
+			printDatMessage(arg);
+		}
+		break;
+	case IDI_MSA_ACTION_GO_PLANET:
+		if (!_gameStateMickey.fShipDoorOpen) {
+			if ((_gameStateMickey.nXtals == IDI_MSA_MAX_PLANET) && (_gameStateMickey.iPlanet == IDI_MSA_PLANET_EARTH))
+				gameOver();
+			if ((_gameStateMickey.iPlanet == _gameStateMickey.iPlanetXtal[_gameStateMickey.nXtals]) || (_gameStateMickey.iPlanet == IDI_MSA_PLANET_EARTH)) {
+				_gameStateMickey.fHasXtal = false;
+				_gameStateMickey.iRoom = IDI_MSA_HOME_PLANET[_gameStateMickey.iPlanet];
+
+				if (_gameStateMickey.iPlanet != IDI_MSA_PLANET_EARTH)
+					insertDisk(1);
+
+				return true;
+			} else {
+				_gameStateMickey.iRoom = IDI_MSA_SHIP_PLANET[_gameStateMickey.iPlanet];
+
+				return true;
+			}
+		} else {
+			printDatMessage(arg);
+		}
+		break;
+	case IDI_MSA_ACTION_PRESS_BUTTON:
+		if (_gameStateMickey.fShipDoorOpen) {       // inner door open
+			if (_gameStateMickey.iPlanet && !_gameStateMickey.fSuit) {
+				printDatMessage(arg);
+			} else {
+				_gameStateMickey.fShipDoorOpen = false;
+				_gameStateMickey.iRmPic[_gameStateMickey.iRoom]--;
+
+				drawRoom();
+
+				printDatMessage(2);
+			}
+		} else {
+			_gameStateMickey.fShipDoorOpen = true;
+			_gameStateMickey.iRmPic[_gameStateMickey.iRoom]++;
+
+			drawRoom();
+
+			printDatMessage(14);
+		}
+		break;
+	case IDI_MSA_ACTION_WEAR_SPACESUIT:
+		if (_gameStateMickey.fSuit) {
+			if (_gameStateMickey.fShipDoorOpen) {
+				_gameStateMickey.fSuit = false;
+				_gameStateMickey.iRmMenu[_gameStateMickey.iRoom] = 0;
+				_gameStateMickey.iRmPic[_gameStateMickey.iRoom] -= 2;
+
+				drawRoom();
+
+				printDatMessage(13);
+			} else {
+				printDatMessage(3);
+			}
+		} else {
+			if (_gameStateMickey.iPlanet) {
+				_gameStateMickey.fSuit = true;
+				_gameStateMickey.iRmMenu[_gameStateMickey.iRoom] = 1;
+				_gameStateMickey.iRmPic[_gameStateMickey.iRoom] += 2;
+
+				drawRoom();
+
+				printDatMessage(arg);
+			} else {
+				printDatMessage(12);
+			}
+		}
+		break;
+	case IDI_MSA_ACTION_READ_GAUGE:
+		printDatString(arg);
+		drawStr(21, 15, IDA_DEFAULT, (const char *)IDS_MSA_TEMP_C[_gameStateMickey.iPlanet]);
+		drawStr(21, 23, IDA_DEFAULT, (const char *)IDS_MSA_TEMP_F[_gameStateMickey.iPlanet]);
+
+		waitAnyKey();
+
+		break;
+	case IDI_MSA_ACTION_PRESS_ORANGE:
+		if (_gameStateMickey.fFlying) {
+			printDatMessage(4);
+		} else {
+			playSound(IDI_MSA_SND_PRESS_ORANGE);
+			printDatMessage(arg);
+			pressOB(IDI_MSA_BUTTON_ORANGE);
+		}
+		break;
+	case IDI_MSA_ACTION_PRESS_BLUE:
+		if (_gameStateMickey.fFlying) {
+			printDatMessage(4);
+		} else {
+			playSound(IDI_MSA_SND_PRESS_BLUE);
+			printDatMessage(arg);
+			pressOB(IDI_MSA_BUTTON_BLUE);
+		}
+		break;
+	case IDI_MSA_ACTION_FLIP_SWITCH:
+		flipSwitch();
+		break;
+	case IDI_MSA_ACTION_PUSH_THROTTLE:
+		if (_gameStateMickey.fFlying) {
+			_gameStateMickey.fFlying = false;
+			_gameStateMickey.nButtons = 0;
+
+			memset(_gameStateMickey.szAddr, 0, sizeof(_gameStateMickey.szAddr));
+
+			drawRoom();
+
+			printDatString(22);
+
+			drawStr(IDI_MSA_ROW_PLANET, IDI_MSA_COL_PLANET, IDA_DEFAULT,
+			        (const char *)IDS_MSA_PLANETS[_gameStateMickey.iPlanet]);
+
+			waitAnyKey(true);
+
+			showPlanetInfo();
+		} else {
+			printDatMessage(arg);
+		}
+		break;
+	case IDI_MSA_ACTION_PULL_THROTTLE:
+		if (_gameStateMickey.fFlying) {
+			printDatMessage(18);
+		} else {
+			if (getPlanet() != -1) {
+				_gameStateMickey.fFlying = true;
+				_gameStateMickey.iPlanet = getPlanet();
+
+				drawRoom();
+
+				printDatMessage(16);
+			} else {
+				_gameStateMickey.nButtons = 0;
+
+				memset(_gameStateMickey.szAddr, 0, sizeof(_gameStateMickey.szAddr));
+
+				printDatMessage(17);
+			}
+		}
+		break;
+	case IDI_MSA_ACTION_LEAVE_ROOM:
+		if (_gameStateMickey.fFlying) {
+			printDatMessage(24);
+		} else {
+			_gameStateMickey.iRoom = arg;
+			return true;
+		}
+		break;
+	case IDI_MSA_ACTION_OPEN_CABINET_1:
+		if (_gameStateMickey.iRmMenu[_gameStateMickey.iRoom]) {
+			printLine("THE CABINET IS ALREADY OPEN");
+		} else {
+			_gameStateMickey.iRmMenu[_gameStateMickey.iRoom] = 1;
+			_gameStateMickey.iRmPic[_gameStateMickey.iRoom] = IDI_MSA_PIC_SHIP_KITCHEN_1;
+
+			drawRoom();
+
+			printDatMessage(arg);
+		}
+		break;
+	case IDI_MSA_ACTION_READ_MAP:
+		_gameStateMickey.iRmPic[_gameStateMickey.iRoom] = IDI_MSA_PIC_STAR_MAP;
+
+		drawRoom();
+
+		printDatMessage(46);
+		printDatMessage(47);
+		printDatMessage(48);
+
+		_gameStateMickey.iRmPic[_gameStateMickey.iRoom] = IDI_MSA_PIC_SHIP_BEDROOM;
+
+		drawRoom();
+		break;
+	case IDI_MSA_ACTION_GO_WEST:
+		_gameStateMickey.nButtons = 0;
+
+		memset(_gameStateMickey.szAddr, 0, sizeof(_gameStateMickey.szAddr));
+
+		_gameStateMickey.iRoom = arg;
+
+		return true;
+		break;
+
+	default:
+		break;
+	}
+
+	return false;
+}
+
+// Keyboard
+
+void MickeyEngine::waitAnyKey(bool anim) {
+	Common::Event event;
+
+	if (!anim)
+		_gfx->updateScreen();
+
+	while (!shouldQuit()) {
+		while (_system->getEventManager()->pollEvent(event)) {
+			switch (event.type) {
+			case Common::EVENT_RETURN_TO_LAUNCHER:
+			case Common::EVENT_QUIT:
+			case Common::EVENT_KEYDOWN:
+			case Common::EVENT_LBUTTONUP:
+			case Common::EVENT_RBUTTONUP:
+				return;
+			default:
+				break;
+			}
+		}
+
+		if (anim) {
+			animate();
+		}
+
+		_gfx->updateScreen();
+		_system->delayMillis(10);
+	}
+}
+
+// Console-related functions
+
+void MickeyEngine::debugCurRoom() {
+	getDebugger()->debugPrintf("Current Room = %d\n", _gameStateMickey.iRoom);
+
+	if (_gameStateMickey.iRmObj[_gameStateMickey.iRoom] != IDI_MSA_OBJECT_NONE) {
+		getDebugger()->debugPrintf("Object %d is in the room\n", _gameStateMickey.iRmObj[_gameStateMickey.iRoom]);
+	}
+}
+
+void MickeyEngine::debugGotoRoom(int room) {
+	_gameStateMickey.iRoom = room;
+	drawRoom();
+}
+
+MickeyEngine::MickeyEngine(OSystem *syst, const AGIGameDescription *gameDesc) : PreAgiEngine(syst, gameDesc) {
+	setDebugger(new MickeyConsole(this));
+}
+
+MickeyEngine::~MickeyEngine() {
+	//_console deleted by Engine
+}
+
+void MickeyEngine::init() {
+	uint8 buffer[512];
+
+	// clear game struct
+	memset(&_gameStateMickey, 0, sizeof(_gameStateMickey));
+	memset(&_gameStateMickey.iItem, IDI_MSA_OBJECT_NONE, sizeof(_gameStateMickey.iItem));
+	// read room extended desc flags
+	//readExe(IDO_MSA_ROOM_TEXT, buffer, sizeof(buffer));
+	//memcpy(_gameStateMickey.fRmTxt, buffer, sizeof(_gameStateMickey.fRmTxt));
+
+	// read room extended desc offsets
+	readExe(IDO_MSA_ROOM_TEXT_OFFSETS, buffer, sizeof(buffer));
+	memcpy(_gameStateMickey.oRmTxt, buffer, sizeof(_gameStateMickey.oRmTxt));
+	for (int i = 0; i < IDI_MSA_MAX_ROOM; i++)
+		_gameStateMickey.oRmTxt[i] = buffer[i * 2] + 256 * buffer[i * 2 + 1];
+
+	// read room object indices
+	//readExe(IDO_MSA_ROOM_OBJECT, buffer, sizeof(buffer));
+	//memcpy(_gameStateMickey.iRmObj, buffer, sizeof(_gameStateMickey.iRmObj));
+
+	// read room picture indices
+	//readExe(IDO_MSA_ROOM_PICTURE, buffer, sizeof(buffer));
+	//memcpy(_gameStateMickey.iRmPic, buffer, sizeof(_gameStateMickey.iRmPic));
+
+	// read room menu patch indices
+	readExe(IDO_MSA_ROOM_MENU_FIX, buffer, sizeof(buffer));
+	memcpy(_gameStateMickey.nRmMenu, buffer, sizeof(_gameStateMickey.nRmMenu));
+
+	// set room picture and room object indices
+	for (int i = 0; i < IDI_MSA_MAX_ROOM; i++) {
+		_gameStateMickey.iRmPic[i] = i;
+		_gameStateMickey.iRmObj[i] = -1;
+	}
+	_gameStateMickey.iRmPic[IDI_MSA_PIC_SHIP_AIRLOCK] = IDI_MSA_PIC_SHIP_AIRLOCK_0;
+	_gameStateMickey.iRmObj[IDI_MSA_PIC_EARTH_BATHROOM] = 11;
+	_gameStateMickey.iRmObj[IDI_MSA_PIC_JUPITER_LAVA] = 21;
+	_gameStateMickey.iRmObj[IDI_MSA_PIC_JUPITER_ROCK_0] = 20;
+	_gameStateMickey.iRmObj[IDI_MSA_PIC_JUPITER_ROCK_1] = 19;
+	_gameStateMickey.iRmObj[IDI_MSA_PIC_EARTH_IN_DOGHOUSE] = 1;
+
+#if 0
+	// DEBUG
+	_gameStateMickey.iPlanet = IDI_MSA_PLANET_EARTH;
+	_gameStateMickey.iRoom = IDI_MSA_PIC_SHIP_CONTROLS;
+	_gameStateMickey.fHasXtal = true;
+	_gameStateMickey.nXtals = 9;
+	_gameStateMickey.fItemUsed[IDI_MSA_ITEM_LETTER] = true;
+
+#endif
+
+	setFlag(VM_FLAG_SOUND_ON, true); // enable sound
+}
+
+Common::Error MickeyEngine::go() {
+	init();
+
+	// Game intro
+	intro();
+
+	// Game loop
+	while (!shouldQuit()) {
+		drawRoom();
+
+		if (_gameStateMickey.fIntro) {
+			_gameStateMickey.fIntro = false;
+		} else {
+			printRoomDesc();
+		}
+
+		bool done;
+		if (_gameStateMickey.iRoom == IDI_MSA_PIC_NEPTUNE_GUARD) {
+			_gameStateMickey.iRoom = IDI_MSA_PIC_NEPTUNE_LEADER;
+			done = true;
+		} else {
+			done = false;
+		}
+
+		while (!done && !shouldQuit()) {
+			// Check air supply
+			if (_gameStateMickey.fSuit) {
+				_gameStateMickey.nAir -= 1;
+				for (int i = 0; i < 4; i++) {
+					if (_gameStateMickey.nAir == IDI_MSA_AIR_SUPPLY[i]) {
+						playSound(IDI_MSA_SND_XL30);
+						printExeMsg(IDO_MSA_XL30_SPEAKING);
+						printExeMsg(IDO_MSA_AIR_SUPPLY[i]);
+						if (i == 3)
+							return Common::kNoError;
+					}
+				}
+			} else {
+				_gameStateMickey.nAir = 50; // max air supply
+			}
+
+			done = checkMenu();
+		}
+
+		_gameStateMickey.nFrame = 0;
+	}
+
+	gameOver();
+
+	return Common::kNoError;
+}
+
+} // End of namespace Agi
diff --git a/engines/agi/preagi/mickey.h b/engines/agi/preagi/mickey.h
new file mode 100644
index 00000000000..a92fe82e36f
--- /dev/null
+++ b/engines/agi/preagi/mickey.h
@@ -0,0 +1,759 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef AGI_PREAGI_MICKEY_H
+#define AGI_PREAGI_MICKEY_H
+
+namespace Agi {
+
+#define MSA_SAVEGAME_VERSION            2
+
+// strings
+#define IDS_MSA_PATH_DAT    "dat/%s"
+#define IDS_MSA_PATH_OBJ    "obj/%s.ooo"
+#define IDS_MSA_PATH_PIC    "%d.pic"
+#define IDS_MSA_PATH_LOGO   "logos.bcg"
+
+#define IDS_MSA_INVENTORY   "MICKEY IS CARRYING THE FOLLOWING:"
+#define IDS_MSA_CRYSTALS    "%s CRYSTALS"
+
+const char IDS_MSA_CRYSTAL_NO[][3] = {
+	"NO", " 1", " 2", " 3", " 4", " 5", " 6", " 7", " 8", " 9"
+};
+const char IDS_MSA_TEMP_C[][5] = {
+	" 20 ", " 480", "-200", " 430", "-185", "-230", "-130", "-150", "-215"
+};
+const char IDS_MSA_TEMP_F[][5] = {
+	" 68 ", " 897", "-328", " 807", "-301", "-382", "-202", "-238", "-355"
+};
+const char IDS_MSA_PLANETS[][10] = {
+	"EARTH.  ", "VENUS.  ", "TRITON. ", "MERCURY.", "TITAN.  ",
+	"PLUTO.  ", "IO.     ", "MARS.   ", "OBERON. "
+};
+
+const char IDS_MSA_ERRORS[][40] = {
+	"THAT CANNOT BE UNDERSTOOD",
+	"TRY GOING THERE INSTEAD",
+	"THAT CAN'T BE DONE",
+	"MICKEY WOULDN'T WANT TO DO THAT!",
+	"WHICH DIRECTION?",
+	"THAT DOESN'T MAKE SENSE!",
+	"MICKEY WOULDN'T WANT TO DO THAT!"
+};
+
+// patch Mickey.exe offset 0x21E to value 0x01 to enable debug mode
+
+const char IDS_MSA_INSERT_DISK[][40] = {
+	"Please insert disk 1 and press any key", "Please insert disk 2 and press any key"
+};
+
+// max values
+
+#define IDI_MSA_MAX_PLANET              9
+#define IDI_MSA_MAX_DAT                 10
+#define IDI_MSA_MAX_PIC_ROOM            224
+#define IDI_MSA_MAX_ROOM                160
+
+#define IDI_MSA_MAX_BUTTON              6
+#define IDI_MSA_MAX_ITEM                11
+
+#define IDI_MSA_ANIM_DELAY              25
+
+#define IDI_MSA_LEN_STORY               1372
+
+// rows
+
+#define IDI_MSA_ROW_MENU_0              20
+#define IDI_MSA_ROW_MENU_1              21
+#define IDI_MSA_ROW_INV_TITLE           2
+#define IDI_MSA_ROW_INV_CRYSTALS        4
+#define IDI_MSA_ROW_INV_ITEMS           5
+#define IDI_MSA_ROW_TEMPERATURE         21
+#define IDI_MSA_ROW_PLANET              22
+#define IDI_MSA_ROW_INSERT_DISK         23
+
+#define IDI_MSA_COL_INV_TITLE           4
+#define IDI_MSA_COL_INV_ITEMS           15
+#define IDI_MSA_COL_PLANET              28
+#define IDI_MSA_COL_INSERT_DISK         1
+
+// screen
+
+#define IDI_MSA_PIC_WIDTH   140
+#define IDI_MSA_PIC_HEIGHT  159
+
+// pictures
+
+#define IDI_MSA_PIC_EARTH_TIRE_SWING    1
+#define IDI_MSA_PIC_EARTH_TIRE_SWING_1  200     // rope taken, swing on ground
+#define IDI_MSA_PIC_EARTH_DOGHOUSE      2
+#define IDI_MSA_PIC_EARTH_IN_DOGHOUSE   154
+#define IDI_MSA_PIC_EARTH_TREE          3
+#define IDI_MSA_PIC_EARTH_GARDEN        4
+#define IDI_MSA_PIC_EARTH_FRONT_HOUSE   5
+#define IDI_MSA_PIC_EARTH_HAMMOCK       6
+#define IDI_MSA_PIC_EARTH_BUTTERFLY     7
+#define IDI_MSA_PIC_EARTH_MAILBOX       8
+#define IDI_MSA_PIC_EARTH_ROAD_0        9
+#define IDI_MSA_PIC_EARTH_ROAD_1        10
+#define IDI_MSA_PIC_EARTH_ROAD_2        11
+#define IDI_MSA_PIC_EARTH_ROAD_3        12
+#define IDI_MSA_PIC_EARTH_ROAD_4        13      // starting room
+#define IDI_MSA_PIC_EARTH_ROAD_5        14
+#define IDI_MSA_PIC_EARTH_ROAD_6        15
+#define IDI_MSA_PIC_EARTH_ROAD_7        18
+#define IDI_MSA_PIC_EARTH_UNDER_TREE    16
+#define IDI_MSA_PIC_EARTH_UP_IN_TREE    155     // CRYSTAL
+#define IDI_MSA_PIC_EARTH_SHIP          17
+#define IDI_MSA_PIC_EARTH_LIVING_ROOM   19
+#define IDI_MSA_PIC_EARTH_KITCHEN       20
+#define IDI_MSA_PIC_EARTH_KITCHEN_1     159     // cupboard open
+#define IDI_MSA_PIC_EARTH_GARAGE        21
+#define IDI_MSA_PIC_EARTH_GARAGE_1      160     // cabinet open
+#define IDI_MSA_PIC_EARTH_BEDROOM       22
+#define IDI_MSA_PIC_EARTH_BEDROOM_1     161     // closet open
+#define IDI_MSA_PIC_EARTH_BATHROOM      23      // WEIGH MICKEY
+#define IDI_MSA_PIC_EARTH_SHIP_LEAVING  24
+#define IDI_MSA_PIC_EARTH_MINNIE        25
+
+#define IDI_MSA_PIC_SHIP_AIRLOCK        25
+#define IDI_MSA_PIC_SHIP_AIRLOCK_0      201     // door closed
+#define IDI_MSA_PIC_SHIP_AIRLOCK_1      202     // door open
+#define IDI_MSA_PIC_SHIP_AIRLOCK_2      203     // door closed, spacesuits on
+#define IDI_MSA_PIC_SHIP_AIRLOCK_3      204     // door open, spacesuits on
+#define IDI_MSA_PIC_SHIP_BEDROOM        29
+#define IDI_MSA_PIC_SHIP_CONTROLS       26
+#define IDI_MSA_PIC_SHIP_CORRIDOR       27
+#define IDI_MSA_PIC_SHIP_KITCHEN        28
+#define IDI_MSA_PIC_SHIP_KITCHEN_1      172     // cabinet open
+
+#define IDI_MSA_PIC_SHIP_VENUS          146
+#define IDI_MSA_PIC_SHIP_NEPTUNE        147
+#define IDI_MSA_PIC_SHIP_MERCURY        148
+#define IDI_MSA_PIC_SHIP_SATURN         149
+#define IDI_MSA_PIC_SHIP_PLUTO          150
+#define IDI_MSA_PIC_SHIP_JUPITER        151
+#define IDI_MSA_PIC_SHIP_MARS           152
+#define IDI_MSA_PIC_SHIP_URANUS         153
+
+#define IDI_MSA_PIC_VENUS_0             30
+#define IDI_MSA_PIC_VENUS_1             31
+#define IDI_MSA_PIC_VENUS_2             32
+#define IDI_MSA_PIC_VENUS_3             34
+#define IDI_MSA_PIC_VENUS_4             36
+#define IDI_MSA_PIC_VENUS_5             38
+#define IDI_MSA_PIC_VENUS_CHASM         35
+#define IDI_MSA_PIC_VENUS_CHASM_1       183     // rope lowered
+#define IDI_MSA_PIC_VENUS_PROBE         39      // CRYSTAL, USE WRENCH
+#define IDI_MSA_PIC_VENUS_PROBE_1       184     // hatch open
+#define IDI_MSA_PIC_VENUS_SHIP          33
+#define IDI_MSA_PIC_VENUS_WEIGH         37      // WEIGH MICKEY
+
+#define IDI_MSA_PIC_NEPTUNE_0           40
+#define IDI_MSA_PIC_NEPTUNE_1           42
+#define IDI_MSA_PIC_NEPTUNE_2           43
+#define IDI_MSA_PIC_NEPTUNE_3           44
+#define IDI_MSA_PIC_NEPTUNE_4           45
+#define IDI_MSA_PIC_NEPTUNE_5           48
+#define IDI_MSA_PIC_NEPTUNE_6           50
+#define IDI_MSA_PIC_NEPTUNE_7           52
+#define IDI_MSA_PIC_NEPTUNE_8           53
+#define IDI_MSA_PIC_NEPTUNE_9           54
+#define IDI_MSA_PIC_NEPTUNE_10          55
+#define IDI_MSA_PIC_NEPTUNE_11          56
+#define IDI_MSA_PIC_NEPTUNE_BABIES      61
+#define IDI_MSA_PIC_NEPTUNE_CASTLE_0    46
+#define IDI_MSA_PIC_NEPTUNE_CASTLE_1    51
+#define IDI_MSA_PIC_NEPTUNE_CASTLE_2    57
+#define IDI_MSA_PIC_NEPTUNE_CASTLE_3    58
+#define IDI_MSA_PIC_NEPTUNE_CASTLE_4    59
+#define IDI_MSA_PIC_NEPTUNE_CASTLE_5    60
+#define IDI_MSA_PIC_NEPTUNE_CASTLE_6    66
+#define IDI_MSA_PIC_NEPTUNE_CASTLE_7    67
+#define IDI_MSA_PIC_NEPTUNE_CASTLE_8    68
+#define IDI_MSA_PIC_NEPTUNE_EATING_AREA 62
+#define IDI_MSA_PIC_NEPTUNE_ENTRANCE    47
+#define IDI_MSA_PIC_NEPTUNE_ENTRANCE_1  185     // entrance open
+#define IDI_MSA_PIC_NEPTUNE_ENTRYWAY    63
+#define IDI_MSA_PIC_NEPTUNE_GUARD       69
+#define IDI_MSA_PIC_NEPTUNE_LEADER      64      // CRYSTAL, GIVE SCARF
+#define IDI_MSA_PIC_NEPTUNE_SHIP        49
+#define IDI_MSA_PIC_NEPTUNE_SLEEP_AREA  65
+#define IDI_MSA_PIC_NEPTUNE_WEIGH       41
+
+#define IDI_MSA_PIC_MERCURY_0           71
+#define IDI_MSA_PIC_MERCURY_1           73
+#define IDI_MSA_PIC_MERCURY_2           75
+#define IDI_MSA_PIC_MERCURY_3           77
+#define IDI_MSA_PIC_MERCURY_4           80
+#define IDI_MSA_PIC_MERCURY_ALIEN_0     72      // CRYSTAL, GIVE SUNGLASSES
+#define IDI_MSA_PIC_MERCURY_ALIEN_1     74
+#define IDI_MSA_PIC_MERCURY_ALIEN_2     81
+#define IDI_MSA_PIC_MERCURY_CAVE_0      70      // hidden feature, press '2' here
+#define IDI_MSA_PIC_MERCURY_CAVE_1      78
+#define IDI_MSA_PIC_MERCURY_CAVE_2      79
+#define IDI_MSA_PIC_MERCURY_SHIP        76
+#define IDI_MSA_PIC_MERCURY_WEIGH       82
+
+#define IDI_MSA_PIC_SATURN_0            84
+#define IDI_MSA_PIC_SATURN_1            86
+#define IDI_MSA_PIC_SATURN_2            90
+#define IDI_MSA_PIC_SATURN_3            91
+#define IDI_MSA_PIC_SATURN_ISLAND       89      // CRYSTAL
+#define IDI_MSA_PIC_SATURN_LAKE_0       85      // USE MATTRESS
+#define IDI_MSA_PIC_SATURN_LAKE_1       88      // USE MATTRESS
+#define IDI_MSA_PIC_SATURN_LAKE_2       92      // USE MATTRESS
+#define IDI_MSA_PIC_SATURN_SHIP         87
+#define IDI_MSA_PIC_SATURN_WEIGH        83      // WEIGH MICKEY
+
+#define IDI_MSA_PIC_PLUTO_0             93
+#define IDI_MSA_PIC_PLUTO_1             96
+#define IDI_MSA_PIC_PLUTO_2             97
+#define IDI_MSA_PIC_PLUTO_3             98
+#define IDI_MSA_PIC_PLUTO_4             101
+#define IDI_MSA_PIC_PLUTO_ALIENS        100     // CRYSTAL, GIVE BONE
+#define IDI_MSA_PIC_PLUTO_CAVE_0        99
+#define IDI_MSA_PIC_PLUTO_CAVE_1        103
+#define IDI_MSA_PIC_PLUTO_CRATER        102
+#define IDI_MSA_PIC_PLUTO_SHIP          95
+#define IDI_MSA_PIC_PLUTO_WEIGH         94      // WEIGH MICKEY
+
+#define IDI_MSA_PIC_JUPITER_0           106
+#define IDI_MSA_PIC_JUPITER_1           107
+#define IDI_MSA_PIC_JUPITER_2           108
+#define IDI_MSA_PIC_JUPITER_3           109
+#define IDI_MSA_PIC_JUPITER_4           113
+#define IDI_MSA_PIC_JUPITER_5           116
+#define IDI_MSA_PIC_JUPITER_6           117
+#define IDI_MSA_PIC_JUPITER_7           120
+#define IDI_MSA_PIC_JUPITER_CRACK       114
+#define IDI_MSA_PIC_JUPITER_LAVA        110     // CRYSTAL, THROW ROCK
+#define IDI_MSA_PIC_JUPITER_ROCK_0      112     // GET ROCK
+#define IDI_MSA_PIC_JUPITER_ROCK_1      119     // GET ROCK
+#define IDI_MSA_PIC_JUPITER_SHIP        115
+#define IDI_MSA_PIC_JUPITER_WEIGH       118     // WEIGH MICKEY
+
+#define IDI_MSA_PIC_MARS_0              121
+#define IDI_MSA_PIC_MARS_1              124
+#define IDI_MSA_PIC_MARS_2              125
+#define IDI_MSA_PIC_MARS_3              126
+#define IDI_MSA_PIC_MARS_4              127
+#define IDI_MSA_PIC_MARS_5              128
+#define IDI_MSA_PIC_MARS_6              130
+#define IDI_MSA_PIC_MARS_SHIP           123
+#define IDI_MSA_PIC_MARS_TUBE_0         129
+#define IDI_MSA_PIC_MARS_TUBE_1         131
+#define IDI_MSA_PIC_MARS_VOLCANO        132     // CRYSTAL, DIG PLUTO
+#define IDI_MSA_PIC_MARS_WEIGH          122     // WEIGH MICKEY
+
+#define IDI_MSA_PIC_URANUS_0            133
+#define IDI_MSA_PIC_URANUS_1            134
+#define IDI_MSA_PIC_URANUS_2            135
+#define IDI_MSA_PIC_URANUS_3            138
+#define IDI_MSA_PIC_URANUS_4            139
+#define IDI_MSA_PIC_URANUS_5            140
+#define IDI_MSA_PIC_URANUS_6            142
+#define IDI_MSA_PIC_URANUS_CHAMBER      145     // CRYSTAL, USE CROWBAR
+#define IDI_MSA_PIC_URANUS_SHIP         137
+#define IDI_MSA_PIC_URANUS_STEPS        144
+#define IDI_MSA_PIC_URANUS_ENTRANCE     141     // ENTER TEMPLE
+#define IDI_MSA_PIC_URANUS_TEMPLE       143     // USE CRYSTAL, ENTER DOOR
+#define IDI_MSA_PIC_URANUS_TEMPLE_1     206     // crystal used
+#define IDI_MSA_PIC_URANUS_TEMPLE_2     207     // door open
+#define IDI_MSA_PIC_URANUS_WEIGH        136     // WEIGH MICKEY
+
+#define IDI_MSA_PIC_STAR_MAP            165
+#define IDI_MSA_PIC_TITLE               240
+
+// objects
+
+enum ENUM_MSA_OBJECT {
+	IDI_MSA_OBJECT_NONE = -1,
+	IDI_MSA_OBJECT_ROCK_0,
+	IDI_MSA_OBJECT_WRENCH,
+	IDI_MSA_OBJECT_SCALE,
+	IDI_MSA_OBJECT_CROWBAR,
+	IDI_MSA_OBJECT_BONE,
+	IDI_MSA_OBJECT_SUNGLASSES,
+	IDI_MSA_OBJECT_DESK_STUFF,
+	IDI_MSA_OBJECT_MATTRESS,
+	IDI_MSA_OBJECT_SCARF,
+	IDI_MSA_OBJECT_FLASHLIGHT,
+	IDI_MSA_OBJECT_ROPE,
+	IDI_MSA_OBJECT_ROCK_1,
+	IDI_MSA_OBJECT_SCARF_C64,
+	IDI_MSA_OBJECT_ROCK_2,
+	IDI_MSA_OBJECT_ROCK_3,
+	IDI_MSA_OBJECT_W_EARTH,
+	IDI_MSA_OBJECT_W_VENUS,
+	IDI_MSA_OBJECT_W_TRITON,
+	IDI_MSA_OBJECT_W_MERCURY,
+	IDI_MSA_OBJECT_W_TITAN,
+	IDI_MSA_OBJECT_W_PLUTO,
+	IDI_MSA_OBJECT_W_IO,
+	IDI_MSA_OBJECT_W_MARS,
+	IDI_MSA_OBJECT_W_OBERON,
+	IDI_MSA_OBJECT_W_SPACE,
+	IDI_MSA_OBJECT_XL31,
+	IDI_MSA_OBJECT_XL31E,
+	IDI_MSA_OBJECT_XL32,
+	IDI_MSA_OBJECT_XL32E,
+	IDI_MSA_OBJECT_XL33,
+	IDI_MSA_OBJECT_XL33E,
+	IDI_MSA_OBJECT_CRYSTAL
+};
+
+const char IDS_MSA_NAME_OBJ[][9] = {
+	"rok1", "wrench", "scale", "cbar", "bone", "glasses", "deskstuf", "raft",
+	"scarf", "flashlit", "rope", "rok1", "scarfc64", "rok2", "rock35", "earthw",
+	"venw", "trw", "merw", "titw", "plw", "iow", "mrw", "obw", "spw", "xl31",
+	"xl31e", "xl32", "xl32e", "xl33", "xl33e", "crys1"
+};
+
+const int IDI_MSA_XTAL_ROOM_XY[IDI_MSA_MAX_PLANET][3] = {
+	// room                         x       y
+	{IDI_MSA_PIC_EARTH_UP_IN_TREE,  14,     76},
+	{IDI_MSA_PIC_VENUS_PROBE,       74,     80},
+	{IDI_MSA_PIC_NEPTUNE_LEADER,    70,     27},
+	{IDI_MSA_PIC_MERCURY_ALIEN_0,   123,    64},
+	{IDI_MSA_PIC_SATURN_ISLAND,     110,    115},
+	{IDI_MSA_PIC_PLUTO_ALIENS,      60,     104},
+	{IDI_MSA_PIC_JUPITER_LAVA,      56,     54},
+	{IDI_MSA_PIC_MARS_VOLCANO,      107,    100},
+	{IDI_MSA_PIC_URANUS_CHAMBER,    90,     4}
+};
+
+// planets
+
+enum ENUM_MSA_PLANET {
+	IDI_MSA_PLANET_EARTH = 0,
+	IDI_MSA_PLANET_VENUS,
+	IDI_MSA_PLANET_NEPTUNE,
+	IDI_MSA_PLANET_MERCURY,
+	IDI_MSA_PLANET_SATURN,
+	IDI_MSA_PLANET_PLUTO,
+	IDI_MSA_PLANET_JUPITER,
+	IDI_MSA_PLANET_MARS,
+	IDI_MSA_PLANET_URANUS,
+	IDI_MSA_PLANET_SPACESHIP
+};
+
+const char IDS_MSA_NAME_DAT[][13] = {
+	"earth.dat", "venus.dat", "neptune.dat", "mercury.dat", "saturn.dat",
+	"pluto.dat", "jupiter.dat", "mars.dat", "uranus.dat", "spacship.dat"
+};
+
+const char IDS_MSA_NAME_PLANET[][10] = {
+	"EARTH", "VENUS", "TRITON", "MERCURY", "TITAN",
+	"PLUTO", "IO", "MARS", "OBERON"
+};
+
+const char IDS_MSA_NAME_PLANET_2[][10] = {
+	"EARTH", "VENUS", "NEPTUNE", "MERCURY", "SATURN",
+	"PLUTO", "JUPITER", "MARS", "URANUS"
+};
+
+const char IDS_MSA_ADDR_PLANET[][7] = {
+	"OB", "B", "OOBBB", "O", "OOBB",
+	"OOOBBB", "OBB", "OOB", "OOOBB"
+};
+
+const int IDI_MSA_HOME_PLANET[] = {
+	IDI_MSA_PIC_EARTH_SHIP, IDI_MSA_PIC_VENUS_SHIP, IDI_MSA_PIC_NEPTUNE_SHIP, IDI_MSA_PIC_MERCURY_SHIP,
+	IDI_MSA_PIC_SATURN_SHIP, IDI_MSA_PIC_PLUTO_SHIP, IDI_MSA_PIC_JUPITER_SHIP, IDI_MSA_PIC_MARS_SHIP,
+	IDI_MSA_PIC_URANUS_SHIP
+};
+
+const int IDI_MSA_SHIP_PLANET[] = {
+	0, IDI_MSA_PIC_SHIP_VENUS, IDI_MSA_PIC_SHIP_NEPTUNE, IDI_MSA_PIC_SHIP_MERCURY, IDI_MSA_PIC_SHIP_SATURN,
+	IDI_MSA_PIC_SHIP_PLUTO, IDI_MSA_PIC_SHIP_JUPITER, IDI_MSA_PIC_SHIP_MARS, IDI_MSA_PIC_SHIP_URANUS
+};
+
+// items
+
+enum ENUM_MSA_ITEM {
+	IDI_MSA_ITEM_FLASHLIGHT = 0,
+	IDI_MSA_ITEM_ROPE,
+	IDI_MSA_ITEM_BONE,
+	IDI_MSA_ITEM_LETTER,
+	IDI_MSA_ITEM_CROWBAR,
+	IDI_MSA_ITEM_WRENCH,
+	IDI_MSA_ITEM_MATTRESS,
+	IDI_MSA_ITEM_SCARF,
+	IDI_MSA_ITEM_SUNGLASSES,
+	IDI_MSA_ITEM_SCALE,
+	IDI_MSA_ITEM_ROCK
+};
+
+const char IDS_MSA_NAME_ITEM[][15] = {
+	"A FLASHLIGHT", "A ROPE ", "A BONE ", "A LETTER", "A CROWBAR", "A WRENCH",
+	"A MATTRESS", "A SCARF", "SUNGLASSES", "A SCALE ", "A ROCK "
+};
+
+// buttons
+
+#define IDI_MSA_BUTTON_ORANGE       0x4F    // 'O'
+#define IDI_MSA_BUTTON_BLUE         0x42    // 'B'
+
+// file structures
+
+struct MSA_TEXT_ENTRY {
+	uint8   x0;
+	uint8   szText[11];
+};
+
+struct MSA_TEXT_BLOCK {
+	uint8   count;
+	MSA_TEXT_ENTRY  entry[5];
+};
+
+struct MSA_MSG_BLOCK {
+	uint8   data[5];
+};
+
+struct MSA_MENU {
+	MSA_TEXT_BLOCK row[2];
+	MSA_MSG_BLOCK cmd[5];
+	MSA_MSG_BLOCK arg[5];
+};
+
+struct MSA_DAT_HEADER {
+	uint16  filelen;
+	uint16  ofsRoom[IDI_MSA_MAX_ROOM];
+	uint16  ofsDesc[IDI_MSA_MAX_ROOM];
+	uint16  ofsStr[IDI_MSA_MAX_ROOM];
+};
+
+struct MSA_SND_NOTE {
+	uint16  counter;    // freq = 1193180 / counter
+	uint8   length;     // msec = length / 0.0182
+};
+
+// file offset modifiers
+
+#define IDI_MSA_OFS_DAT                 0x0002
+#define IDI_MSA_OFS_EXE                 0x35C0
+
+// actions
+
+#define IDI_MSA_ACTION_GOTO_ROOM        0x00
+#define IDI_MSA_ACTION_SHOW_INT_STR     0x01
+#define IDI_MSA_ACTION_UNUSED           0x02
+#define IDI_MSA_ACTION_SHOW_DAT_STR     0x03
+
+#define IDI_MSA_ACTION_GET_ROPE         0x7F
+#define IDI_MSA_ACTION_UNTIE_ROPE       0x80
+#define IDI_MSA_ACTION_GET_BONE         0x81
+#define IDI_MSA_ACTION_GET_XTAL_EARTH   0x82
+#define IDI_MSA_ACTION_LOOK_DESK        0x83
+#define IDI_MSA_ACTION_WRITE_LETTER     0x84
+#define IDI_MSA_ACTION_MAIL_LETTER      0x85
+#define IDI_MSA_ACTION_OPEN_CUPBOARD    0x86
+#define IDI_MSA_ACTION_GET_FLASHLIGHT   0x87
+#define IDI_MSA_ACTION_OPEN_CABINET     0x88
+#define IDI_MSA_ACTION_GET_CROWBAR      0x89
+#define IDI_MSA_ACTION_GET_WRENCH       0x8A
+#define IDI_MSA_ACTION_OPEN_CLOSET      0x8B
+#define IDI_MSA_ACTION_GET_MATTRESS     0x8C
+#define IDI_MSA_ACTION_GET_SCARF        0x8D
+#define IDI_MSA_ACTION_GET_SUNGLASSES   0x8E
+#define IDI_MSA_ACTION_GET_SCALE        0x8F
+#define IDI_MSA_ACTION_GOTO_SPACESHIP   0x90
+
+#define IDI_MSA_ACTION_DOWN_CHASM       0x91
+#define IDI_MSA_ACTION_DOWN_ROPE        0x92
+#define IDI_MSA_ACTION_USE_ROPE         0x93
+#define IDI_MSA_ACTION_OPEN_HATCH       0x94
+#define IDI_MSA_ACTION_USE_WRENCH       0x95
+#define IDI_MSA_ACTION_GET_XTAL_VENUS   0x96
+
+#define IDI_MSA_ACTION_LOOK_CASTLE      0x97
+#define IDI_MSA_ACTION_ENTER_OPENING    0x98
+#define IDI_MSA_ACTION_USE_CROWBAR      0x99
+#define IDI_MSA_ACTION_GET_XTAL_NEPTUNE 0x9A
+#define IDI_MSA_ACTION_TALK_LEADER      0x9B
+#define IDI_MSA_ACTION_GIVE_SCARF       0x9C
+
+#define IDI_MSA_ACTION_GET_XTAL_MERCURY 0x9D
+#define IDI_MSA_ACTION_GIVE_SUNGLASSES  0x9E
+#define IDI_MSA_ACTION_CROSS_LAKE       0x9F
+#define IDI_MSA_ACTION_USE_MATTRESS     0xA0
+#define IDI_MSA_ACTION_GET_XTAL_SATURN  0xA1
+#define IDI_MSA_ACTION_LEAVE_ISLAND     0xA2
+
+#define IDI_MSA_ACTION_GET_XTAL_PLUTO   0xA3
+#define IDI_MSA_ACTION_GIVE_BONE        0xA4
+
+#define IDI_MSA_ACTION_GET_ROCK_0       0xA5
+#define IDI_MSA_ACTION_GET_ROCK_1       0xA6
+#define IDI_MSA_ACTION_GET_XTAL_JUPITER 0xA7
+#define IDI_MSA_ACTION_THROW_ROCK       0xA8
+
+#define IDI_MSA_ACTION_GO_TUBE          0xA9
+#define IDI_MSA_ACTION_USE_FLASHLIGHT   0xAA
+#define IDI_MSA_ACTION_PLUTO_DIG        0xAB
+#define IDI_MSA_ACTION_GET_XTAL_MARS    0xAC
+
+#define IDI_MSA_ACTION_USE_CRYSTAL      0xAD
+#define IDI_MSA_ACTION_OPEN_DOOR        0xAE
+#define IDI_MSA_ACTION_ENTER_DOOR       0xAF
+#define IDI_MSA_ACTION_GET_XTAL_URANUS  0xB0
+#define IDI_MSA_ACTION_USE_CROWBAR_1    0xB1
+
+#define IDI_MSA_ACTION_GO_NORTH         0xB2
+#define IDI_MSA_ACTION_GO_PLANET        0xB3
+#define IDI_MSA_ACTION_PRESS_BUTTON     0xB4
+#define IDI_MSA_ACTION_WEAR_SPACESUIT   0xB5
+#define IDI_MSA_ACTION_READ_GAUGE       0xB6
+#define IDI_MSA_ACTION_PRESS_ORANGE     0xB7
+#define IDI_MSA_ACTION_PRESS_BLUE       0xB8
+#define IDI_MSA_ACTION_FLIP_SWITCH      0xB9
+#define IDI_MSA_ACTION_PUSH_THROTTLE    0xBA
+#define IDI_MSA_ACTION_PULL_THROTTLE    0xBB
+#define IDI_MSA_ACTION_LEAVE_ROOM       0xBC
+#define IDI_MSA_ACTION_OPEN_CABINET_1   0xBD
+#define IDI_MSA_ACTION_READ_MAP         0xBE
+#define IDI_MSA_ACTION_GO_WEST          0xBF
+
+#define IDI_MSA_ACTION_PLANET_INFO      0xC0
+#define IDI_MSA_ACTION_ENTER_TEMPLE     0xC1
+#define IDI_MSA_ACTION_OPEN_MAILBOX     0xC2
+#define IDI_MSA_ACTION_SAVE_GAME        0xC3
+#define IDI_MSA_ACTION_LOOK_MICKEY      0xC4
+
+// sounds
+
+enum ENUM_MSA_SOUND {
+	IDI_MSA_SND_THEME,
+	IDI_MSA_SND_CRYSTAL,
+	IDI_MSA_SND_TAKE,
+	IDI_MSA_SND_GAME_OVER,
+	IDI_MSA_SND_PRESS_BLUE,
+	IDI_MSA_SND_PRESS_ORANGE,
+	IDI_MSA_SND_SHIP_LAND,
+	IDI_MSA_SND_XL30
+};
+
+// message offsets within mickey.exe
+
+const int IDO_MSA_HIDDEN_MSG[] = {
+	0x8C44, 0x8C83, 0x8D23, 0x8D97, 0x8E2A
+};
+
+const int IDO_MSA_GAME_OVER[] = {
+	0x7914, 0x7978, 0x7A17, 0x7A94, 0x7B04, 0x7B8F, 0x7BEB, 0x7C79
+};
+
+const int IDO_MSA_SAVE_GAME[] = {
+	0x73FA, 0x7436, 0x746C, 0x74E9, 0x75F6, 0x766A, 0x758B
+	// do you have a formatted disk, insert disk, insert disk 2, save by number
+	// everything will be lost, previous game will be lost, game saved
+};
+
+const int IDO_MSA_LOAD_GAME[] = {
+	0x76CE, 0x770B, 0x7777
+	// do you want to load game, insert game save disk, game restored
+};
+
+const int IDO_MSA_AIR_SUPPLY[] = {
+	0x7D10, 0x7D31, 0x7D51, 0x7D9B
+	// be aware, low, dangerously low, out of air
+};
+
+const int IDI_MSA_AIR_SUPPLY[] = { 30, 20, 10, 0 };
+
+// planet information
+
+const int IDO_MSA_PLANET_INFO[IDI_MSA_MAX_PLANET][4] = {
+	{0x6313, 0x63B2, 0x6449, 0},        // EARTH
+	{0x61EB, 0x6288, 0,      0},        // VENUS
+	{0x6B64, 0x6C06, 0x6CA3, 0},        // NEPTUNE
+	{0x609B, 0x612C, 0x61CA, 0},        // MERCURY
+	{0x6879, 0x6916, 0x6984, 0},        // SATURN
+	{0x6CCF, 0x6D72, 0x6E10, 0},        // PLUTO
+	{0x667C, 0x6714, 0x67B1, 0x684E},   // JUPITER
+	{0x6471, 0x650F, 0x65AD, 0x6651},   // MARS
+	{0x69C3, 0x6A62, 0x6B00, 0}         // URANUS
+};
+
+// next crystal piece hints
+
+const int IDO_MSA_NEXT_PIECE[IDI_MSA_MAX_PLANET][5] = {
+	{0,     0,      0,      0,      0},             // earth
+	{0x4DCC,    0x4E20, 0x4E64, 0x4E9E, 0x4F0B},    // venus
+	{0x5900,    0x599B, 0x5A07, 0x5A8E, 0x5B07},    // neptune
+	{0x4F57,    0x4FA3, 0x4FF1, 0x5056, 0x50BD},    // mercury
+	{0x5471,    0x54DF, 0x5548, 0x55C2, 0x562A},    // saturn
+	{0x5B78,    0x5BB6, 0x5C29, 0x5C76, 0x5CE1},    // pluto
+	{0x526B,    0x52DA, 0x5340, 0x53A1, 0x540C},    // jupiter
+	{0x50F6,    0x512C, 0x5170, 0x51D5, 0x5228},    // mars
+	{0x56AA,    0x571C, 0x579E, 0x5807, 0x5875}     // uranus
+};
+
+// message offsets
+
+#define IDO_MSA_COPYRIGHT                       0x7801
+#define IDO_MSA_INTRO                           0x4679
+#define IDO_MSA_GAME_STORY                      0x6E9C
+
+#define IDO_MSA_PRESS_1_TO_9                    0x7530
+#define IDO_MSA_PRESS_YES_OR_NO                 0x480D
+#define IDO_MSA_TOO_MANY_BUTTONS_PRESSED        0x5DF7
+
+#define IDO_MSA_XL30_SPEAKING                   0x4725
+#define IDO_MSA_CRYSTAL_PIECE_FOUND             0x600C
+
+#define IDO_MSA_ROOM_TEXT_OFFSETS               0x8B01
+#define IDO_MSA_ROOM_OBJECT_XY_OFFSETS          0x8EA8
+#define IDO_MSA_ROOM_MENU_FIX                   0x4a27
+
+// offsets to offset arrays
+
+#define IDOFS_MSA_MENU_PATCHES                  0x5e7a
+#define IDOFS_MSA_SOUND_DATA                    0x9deb
+
+// game structure
+
+struct MSA_GAME {
+	uint8 iRoom;
+	uint8 iPlanet;
+	uint8 iDisk;
+
+	uint8 nAir;
+	uint8 nButtons;
+	uint8 nRocks;
+
+	uint8 nXtals;
+	uint8 iPlanetXtal[IDI_MSA_MAX_DAT];
+	uint16 iClue[IDI_MSA_MAX_PLANET];
+	char szAddr[IDI_MSA_MAX_BUTTON + 1];
+
+	// Flags
+	bool fHasXtal;
+	bool fIntro;
+	bool fSuit;
+	bool fShipDoorOpen;
+	bool fFlying;
+	bool fStoryShown;
+	bool fPlanetsInitialized;
+	bool fTempleDoorOpen;
+	bool fAnimXL30;
+	bool fItem[IDI_MSA_MAX_ITEM];
+	bool fItemUsed[IDI_MSA_MAX_ITEM];
+
+	int8 iItem[IDI_MSA_MAX_ITEM];
+	uint8 nItems;
+
+	//uint8 fRmTxt[IDI_MSA_MAX_ROOM];
+	int8 iRmObj[IDI_MSA_MAX_ROOM];
+	uint8 iRmPic[IDI_MSA_MAX_ROOM];
+	uint16 oRmTxt[IDI_MSA_MAX_ROOM];
+
+	uint8 iRmMenu[IDI_MSA_MAX_ROOM];
+	uint8 nRmMenu[IDI_MSA_MAX_ROOM];
+
+	int8 nFrame;
+};
+
+class PreAgiEngine;
+
+class MickeyEngine : public PreAgiEngine {
+public:
+	MickeyEngine(OSystem *syst, const AGIGameDescription *gameDesc);
+	~MickeyEngine() override;
+
+	void init();
+	Common::Error go() override;
+
+	void debugCurRoom();
+	void debugGotoRoom(int);
+	void drawPic(int);
+	void drawObj(ENUM_MSA_OBJECT, int, int);
+
+protected:
+	MSA_GAME _gameStateMickey;
+	bool _clickToMove;
+
+	int getDat(int);
+	void readExe(int, uint8 *, long);
+	void getDatFileName(int, char *);
+	void readDatHdr(char *, MSA_DAT_HEADER *);
+	void readOfsData(int, int, uint8 *, long);
+	bool chooseY_N(int, bool);
+	int choose1to9(int);
+	void printStr(char *);
+	void printLine(const char *);
+	void printExeStr(int);
+	void printExeMsg(int);
+	void printDesc(int);
+	bool checkMenu();
+	void drawMenu(MSA_MENU &, int, int);
+	void getMouseMenuSelRow(MSA_MENU &, int *, int *, int, int, int);
+	bool getMenuSelRow(MSA_MENU &, int *, int *, int);
+	void getMenuSel(char *, int *, int *);
+	void centerMenu(MSA_MENU *);
+	void patchMenu(MSA_MENU *);
+	void printDatString(int);
+	void printDatMessage(int);
+	void playNote(MSA_SND_NOTE);
+	void playSound(ENUM_MSA_SOUND);
+	void drawRoomAnimation();
+	void drawRoom();
+	void drawLogo();
+	void animate();
+	void printRoomDesc();
+	bool loadGame();
+	void saveGame();
+	void showPlanetInfo();
+	void printStory();
+	int getPlanet();
+	void pressOB(int);
+	void insertDisk(int);
+	void gameOver();
+	void inventory();
+	void intro();
+	void getItem(ENUM_MSA_ITEM);
+	void getXtal(int);
+	bool parse(int, int);
+	void flipSwitch();
+	void waitAnyKey(bool anim = false);
+
+	bool planetIsAlreadyAssigned(int planet) {
+		for (int j = 0; j < IDI_MSA_MAX_PLANET; j++) {
+			if (_gameStateMickey.iPlanetXtal[j] == planet)
+				return true;
+		}
+		return false;
+	}
+
+	bool mickeyHasItem(int item) {
+		if (_gameStateMickey.fItem[item]) {
+			printDatMessage(90);    // Mickey already has item
+			return true;
+		} else {
+			return false;
+		}
+	}
+};
+
+} // End of namespace Agi
+
+#endif
diff --git a/engines/agi/preagi/preagi.cpp b/engines/agi/preagi/preagi.cpp
new file mode 100644
index 00000000000..134435a9afa
--- /dev/null
+++ b/engines/agi/preagi/preagi.cpp
@@ -0,0 +1,263 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "audio/mixer.h"
+#include "audio/softsynth/pcspk.h"
+
+#include "common/debug-channels.h"
+#include "common/events.h"
+#include "common/random.h"
+
+#include "agi/preagi/preagi.h"
+#include "agi/graphics.h"
+
+namespace Agi {
+
+PreAgiEngine::PreAgiEngine(OSystem *syst, const AGIGameDescription *gameDesc) : AgiBase(syst, gameDesc) {
+
+	// Setup mixer
+	syncSoundSettings();
+
+	memset(&_debug, 0, sizeof(struct AgiDebug));
+
+	_speakerHandle = new Audio::SoundHandle();
+}
+
+void PreAgiEngine::initialize() {
+	initRenderMode();
+
+	_font = new GfxFont(this);
+	_gfx = new GfxMgr(this, _font);
+	_picture = new PictureMgr(this, _gfx);
+
+	_font->init();
+
+	_game.gameFlags = 0;
+
+	//_game._vm->_text->charAttrib_Set(15, 0);
+
+	_defaultColor = 0xF;
+
+	_game.name[0] = '\0';
+
+	//_game._vm->_text->configureScreen(0); // hardcoded
+
+	_gfx->initVideo();
+
+	_speakerStream = new Audio::PCSpeaker(_mixer->getOutputRate());
+	_mixer->playStream(Audio::Mixer::kSFXSoundType, _speakerHandle,
+	                   _speakerStream, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO, true);
+
+	debugC(2, kDebugLevelMain, "Detect game");
+
+	// clear all resources and events
+	for (int i = 0; i < MAX_DIRECTORY_ENTRIES; i++) {
+		_game.pictures[i].reset();
+		_game.sounds[i] = nullptr; // _game.sounds contains pointers now
+		_game.dirPic[i].reset();
+		_game.dirSound[i].reset();
+	}
+}
+
+PreAgiEngine::~PreAgiEngine() {
+	_mixer->stopHandle(*_speakerHandle);
+	delete _speakerStream;
+	delete _speakerHandle;
+
+	delete _picture;
+	delete _gfx;
+	delete _font;
+}
+
+int PreAgiEngine::rnd(int hi) {
+	return (_rnd->getRandomNumber(hi - 1) + 1);
+}
+
+// Screen functions
+void PreAgiEngine::clearScreen(int attr, bool overrideDefault) {
+	if (overrideDefault)
+		_defaultColor = attr;
+
+	_gfx->clearDisplay((attr & 0xF0) / 0x10);
+}
+
+void PreAgiEngine::clearGfxScreen(int attr) {
+	_gfx->drawDisplayRect(0, 0, DISPLAY_DEFAULT_WIDTH - 1, IDI_MAX_ROW_PIC * 8 - 1, (attr & 0xF0) / 0x10);
+}
+
+// String functions
+
+void PreAgiEngine::drawStr(int row, int col, int attr, const char *buffer) {
+	int code;
+
+	if (attr == kColorDefault)
+		attr = _defaultColor;
+
+	for (int iChar = 0; iChar < (int)strlen(buffer); iChar++) {
+		code = buffer[iChar];
+
+		switch (code) {
+		case '\n':
+		case 0x8D:
+			if (++row == 200 / 8) return;
+			col = 0;
+			break;
+
+		case '|':
+			// swap attribute nibbles
+			break;
+
+		default:
+			_gfx->drawCharacter(row, col, code, attr & 0x0f, attr >> 4, false);
+
+			if (++col == 320 / 8) {
+				col = 0;
+				if (++row == 200 / 8) return;
+			}
+		}
+	}
+}
+
+void PreAgiEngine::drawStrMiddle(int row, int attr, const char *buffer) {
+	int col = (25 / 2) - (strlen(buffer) / 2);  // 25 = 320 / 8 (maximum column)
+	drawStr(row, col, attr, buffer);
+}
+
+void PreAgiEngine::clearTextArea() {
+	int start = IDI_MAX_ROW_PIC;
+
+	if (getGameID() == GID_TROLL)
+		start = 21;
+
+	for (int row = start; row < 200 / 8; row++) {
+		clearRow(row);
+	}
+}
+
+void PreAgiEngine::clearRow(int row) {
+	drawStr(row, 0, IDA_DEFAULT, "                                        ");   // 40 spaces
+}
+
+void PreAgiEngine::printStr(const char *szMsg) {
+	clearTextArea();
+	drawStr(21, 0, IDA_DEFAULT, szMsg);
+	g_system->updateScreen();
+}
+
+void PreAgiEngine::XOR80(char *buffer) {
+	for (size_t i = 0; i < strlen(buffer); i++)
+		if (buffer[i] & 0x80)
+			buffer[i] ^= 0x80;
+}
+
+void PreAgiEngine::printStrXOR(char *szMsg) {
+	XOR80(szMsg);
+	printStr(szMsg);
+}
+
+// Input functions
+
+int PreAgiEngine::getSelection(SelectionTypes type) {
+	Common::Event event;
+
+	while (!shouldQuit()) {
+		while (_eventMan->pollEvent(event)) {
+			switch (event.type) {
+			case Common::EVENT_RETURN_TO_LAUNCHER:
+			case Common::EVENT_QUIT:
+				return 0;
+			case Common::EVENT_RBUTTONUP:
+				return 0;
+			case Common::EVENT_LBUTTONUP:
+				if (type == kSelYesNo || type == kSelAnyKey)
+					return 1;
+				break;
+			case Common::EVENT_KEYDOWN:
+				switch (event.kbd.keycode) {
+				case Common::KEYCODE_y:
+					if (type == kSelYesNo)
+						return 1;
+					break;
+				case Common::KEYCODE_n:
+					if (type == kSelYesNo)
+						return 0;
+					break;
+				case Common::KEYCODE_ESCAPE:
+					if (type == kSelNumber || type == kSelAnyKey)
+						return 0;
+					break;
+				case Common::KEYCODE_1:
+				case Common::KEYCODE_2:
+				case Common::KEYCODE_3:
+				case Common::KEYCODE_4:
+				case Common::KEYCODE_5:
+				case Common::KEYCODE_6:
+				case Common::KEYCODE_7:
+				case Common::KEYCODE_8:
+				case Common::KEYCODE_9:
+					if (type == kSelNumber)
+						return event.kbd.keycode - Common::KEYCODE_1 + 1;
+					break;
+				case Common::KEYCODE_SPACE:
+					if (type == kSelSpace)
+						return 1;
+					break;
+				case Common::KEYCODE_BACKSPACE:
+					if (type == kSelBackspace)
+						return 0;
+					break;
+				default:
+					if (event.kbd.flags & Common::KBD_CTRL)
+						break;
+					if (type == kSelYesNo) {
+						return 2;
+					} else if (type == kSelNumber) {
+						return 10;
+					} else if (type == kSelAnyKey || type == kSelBackspace) {
+						return 1;
+					}
+				}
+				break;
+			default:
+				break;
+			}
+		}
+		_system->updateScreen();
+		_system->delayMillis(10);
+	}
+	return 0;
+}
+
+void PreAgiEngine::playNote(int16 frequency, int32 length) {
+	_speakerStream->play(Audio::PCSpeaker::kWaveFormSquare, frequency, length);
+	waitForTimer(length);
+}
+
+void PreAgiEngine::waitForTimer(int msec_delay) {
+	uint32 start_time = _system->getMillis();
+
+	while (_system->getMillis() < start_time + msec_delay) {
+		g_system->updateScreen();
+		_system->delayMillis(10);
+	}
+}
+
+} // End of namespace Agi
diff --git a/engines/agi/preagi/preagi.h b/engines/agi/preagi/preagi.h
new file mode 100644
index 00000000000..2175e4b7af1
--- /dev/null
+++ b/engines/agi/preagi/preagi.h
@@ -0,0 +1,119 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef AGI_PREAGI_PREAGI_H
+#define AGI_PREAGI_PREAGI_H
+
+#include "agi/agi.h"
+
+namespace Audio {
+class SoundHandle;
+class PCSpeaker;
+}
+
+namespace Agi {
+
+// default attributes
+#define IDA_DEFAULT     0x0F
+#define IDA_DEFAULT_REV 0xF0
+
+#define IDI_SND_OSCILLATOR_FREQUENCY    1193180
+#define IDI_SND_TIMER_RESOLUTION        0.0182
+
+#define kColorDefault 0x1337
+
+#define IDI_MAX_ROW_PIC 20
+
+enum SelectionTypes {
+	kSelYesNo,
+	kSelNumber,
+	kSelSpace,
+	kSelAnyKey,
+	kSelBackspace
+};
+
+class PreAgiEngine : public AgiBase {
+	int _gameId;
+
+protected:
+	void initialize() override;
+
+	void pollTimer() {}
+	int getKeypress() override { return 0; }
+	bool isKeypress() override { return false; }
+	void clearKeyQueue() override {}
+
+	PreAgiEngine(OSystem *syst, const AGIGameDescription *gameDesc);
+	~PreAgiEngine() override;
+	int getGameId() {
+		return _gameId;
+	}
+
+	PictureMgr *_picture;
+
+	void clearImageStack() override {}
+	void recordImageStackCall(uint8 type, int16 p1, int16 p2, int16 p3,
+	                          int16 p4, int16 p5, int16 p6, int16 p7) override {}
+	void replayImageStackCall(uint8 type, int16 p1, int16 p2, int16 p3,
+	                          int16 p4, int16 p5, int16 p6, int16 p7) override {}
+	void releaseImageStack() override {}
+	int saveGame(const Common::String &fileName, const Common::String &saveName) { return -1; }
+	int loadGame(const Common::String &fileName, bool checkId = true) { return -1; }
+
+	// Game
+	Common::String getTargetName() { return _targetName; }
+
+	// Screen
+	void clearScreen(int attr, bool overrideDefault = true);
+	void clearGfxScreen(int attr);
+	void setDefaultTextColor(int attr) { _defaultColor = attr; }
+
+	// Keyboard
+	int getSelection(SelectionTypes type);
+
+	int rnd(int hi);
+
+	// Text
+	void drawStr(int row, int col, int attr, const char *buffer);
+	void drawStrMiddle(int row, int attr, const char *buffer);
+	void clearTextArea();
+	void clearRow(int row);
+	void XOR80(char *buffer);
+	void printStr(const char *szMsg);
+	void printStrXOR(char *szMsg);
+
+	// Saved Games
+	Common::SaveFileManager *getSaveFileMan() { return _saveFileMan; }
+
+	void playNote(int16 frequency, int32 length);
+	void waitForTimer(int msec_delay);
+
+private:
+	int _defaultColor;
+
+	Audio::PCSpeaker *_speakerStream;
+	Audio::SoundHandle *_speakerHandle;
+};
+
+} // End of namespace Agi
+
+
+#endif
diff --git a/engines/agi/preagi/troll.cpp b/engines/agi/preagi/troll.cpp
new file mode 100644
index 00000000000..6611f5f5f25
--- /dev/null
+++ b/engines/agi/preagi/troll.cpp
@@ -0,0 +1,770 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "agi/preagi/preagi.h"
+#include "agi/preagi/troll.h"
+#include "agi/graphics.h"
+
+#include "common/events.h"
+#include "common/textconsole.h"
+
+#include "graphics/cursorman.h"
+
+namespace Agi {
+
+TrollEngine::TrollEngine(OSystem *syst, const AGIGameDescription *gameDesc) : PreAgiEngine(syst, gameDesc) {
+}
+
+TrollEngine::~TrollEngine() {
+}
+
+// User Interface
+
+void TrollEngine::pressAnyKey(int col) {
+	drawStr(24, col, kColorDefault, IDS_TRO_PRESSANYKEY);
+	g_system->updateScreen();
+	getSelection(kSelAnyKey);
+}
+
+void TrollEngine::drawMenu(const char *szMenu, int iSel) {
+	clearTextArea();
+	drawStr(21, 0, kColorDefault, szMenu);
+	drawStr(22 + iSel, 0, kColorDefault, " *");
+	g_system->updateScreen();
+}
+
+bool TrollEngine::getMenuSel(const char *szMenu, int *iSel, int nSel) {
+	Common::Event event;
+	int y;
+
+	drawMenu(szMenu, *iSel);
+
+	while (!shouldQuit()) {
+		while (_system->getEventManager()->pollEvent(event)) {
+			switch (event.type) {
+			case Common::EVENT_RETURN_TO_LAUNCHER:
+			case Common::EVENT_QUIT:
+				return 0;
+			case Common::EVENT_MOUSEMOVE:
+				y = event.mouse.y / 8;
+
+				if (y >= 22)
+					if (nSel > y - 22)
+						*iSel = y - 22;
+
+				drawMenu(szMenu, *iSel);
+				break;
+			case Common::EVENT_LBUTTONUP:
+				return true;
+			case Common::EVENT_KEYDOWN:
+				switch (event.kbd.keycode) {
+				case Common::KEYCODE_t:
+				case Common::KEYCODE_f:
+					inventory();
+
+					return false;
+				case Common::KEYCODE_DOWN:
+				case Common::KEYCODE_SPACE:
+					*iSel += 1;
+
+					if (*iSel == nSel)
+						*iSel = IDI_TRO_SEL_OPTION_1;
+
+					drawMenu(szMenu, *iSel);
+					break;
+				case Common::KEYCODE_UP:
+					*iSel -= 1;
+
+					if (*iSel == IDI_TRO_SEL_OPTION_1 - 1)
+						*iSel = nSel - 1;
+
+					drawMenu(szMenu, *iSel);
+					break;
+				case Common::KEYCODE_RETURN:
+				case Common::KEYCODE_KP_ENTER:
+					return true;
+				case Common::KEYCODE_s:
+					if (event.kbd.hasFlags(Common::KBD_CTRL)) {
+						if (_soundOn) {
+							playTune(2, 1);
+							_soundOn = !_soundOn;
+						} else {
+							_soundOn = !_soundOn;
+							playTune(3, 1);
+						}
+					}
+				default:
+					break;
+				}
+				break;
+			default:
+				break;
+			}
+		}
+		_system->updateScreen();
+		_system->delayMillis(10);
+	}
+
+	return true;
+}
+
+// Graphics
+
+void TrollEngine::drawPic(int iPic, bool f3IsCont, bool clr, bool troll) {
+	_picture->setDimensions(IDI_TRO_PIC_WIDTH, IDI_TRO_PIC_HEIGHT);
+
+	if (clr) {
+		clearScreen(0x0f, false);
+		_picture->clear();
+	}
+
+	_picture->setPictureData(_gameData + IDO_TRO_FRAMEPIC);
+	_picture->drawPicture();
+
+	_picture->setPictureData(_gameData + _pictureOffsets[iPic]);
+
+	int addFlag = 0;
+
+	if (troll)
+		addFlag = kPicFTrollMode;
+
+	if (f3IsCont) {
+		_picture->setPictureFlags(kPicFf3Cont | addFlag);
+	} else {
+		_picture->setPictureFlags(kPicFf3Stop | addFlag);
+	}
+
+	_picture->drawPicture();
+
+	_picture->showPic(); // TODO: *HAVE* to add coordinates + height/width!!
+	g_system->updateScreen();
+}
+
+// Game Logic
+
+void TrollEngine::inventory() {
+	clearScreen(0x07);
+	drawStr(1, 12, kColorDefault, IDS_TRO_TREASURE_0);
+	drawStr(2, 12, kColorDefault, IDS_TRO_TREASURE_1);
+
+	for (int i = 0; i < IDI_TRO_MAX_TREASURE - _treasuresLeft; i++) {
+		int n = _inventory[i] - 1;
+		drawStr(2 + i, 10, _items[n].bg << 4 | 0x0f, Common::String::format(" %2d ", i + 1).c_str());
+		drawStr(2 + i, 14, _items[n].bg << 4 | _items[n].fg, _items[n].name);
+	}
+
+	switch (_treasuresLeft) {
+	case 1:
+		drawStr(20, 10, kColorDefault, Common::String::format(IDS_TRO_TREASURE_5, _treasuresLeft).c_str());
+		break;
+	case 0:
+		drawStr(20, 1, kColorDefault, IDS_TRO_TREASURE_6);
+		break;
+	case IDI_TRO_MAX_TREASURE:
+		drawStr(3, 17, kColorDefault, IDS_TRO_TREASURE_2);
+		break;
+	default:
+		drawStr(20, 10, kColorDefault, Common::String::format(IDS_TRO_TREASURE_4, _treasuresLeft).c_str());
+		break;
+	}
+
+	pressAnyKey(6);
+}
+
+void TrollEngine::waitAnyKeyIntro() {
+	Common::Event event;
+	int iMsg = 0;
+
+	while (!shouldQuit()) {
+		while (_system->getEventManager()->pollEvent(event)) {
+			switch (event.type) {
+			case Common::EVENT_RETURN_TO_LAUNCHER:
+			case Common::EVENT_QUIT:
+			case Common::EVENT_LBUTTONUP:
+			case Common::EVENT_KEYDOWN:
+				return;
+			default:
+				break;
+			}
+		}
+
+		switch (iMsg) {
+		case 200:
+			iMsg = 0;
+			// fall through
+		case 0:
+			drawStr(22, 3, kColorDefault, IDS_TRO_INTRO_2);
+			g_system->updateScreen();
+			break;
+		case 100:
+			drawStr(22, 3, kColorDefault, IDS_TRO_INTRO_3);
+			g_system->updateScreen();
+			break;
+		default:
+			break;
+		}
+
+		iMsg++;
+
+		_system->updateScreen();
+		_system->delayMillis(10);
+	}
+}
+
+void TrollEngine::credits() {
+	clearScreen(0x07);
+
+	drawStr(1, 2, kColorDefault, IDS_TRO_CREDITS_0);
+
+	int color = 10;
+	char str[2];
+
+	str[1] = 0;
+
+	for (uint i = 0; i < strlen(IDS_TRO_CREDITS_1); i++) {
+		str[0] = IDS_TRO_CREDITS_1[i];
+		drawStr(7, 19 + i, color++, str);
+		if (color > 15)
+			color = 9;
+	}
+
+	drawStr(8, 19, kColorDefault, IDS_TRO_CREDITS_2);
+
+	drawStr(13, 11, 9, IDS_TRO_CREDITS_3);
+	drawStr(15, 8, 10, IDS_TRO_CREDITS_4);
+	drawStr(17, 7, 12, IDS_TRO_CREDITS_5);
+	drawStr(19, 2, 14, IDS_TRO_CREDITS_6);
+
+	g_system->updateScreen();
+
+	pressAnyKey();
+}
+
+void TrollEngine::tutorial() {
+	bool done = false;
+	int iSel = 0;
+	//char szTreasure[16] = {0};
+
+	while (!shouldQuit()) {
+		clearScreen(0xFF);
+
+		printStr(IDS_TRO_TUTORIAL_0);
+		getSelection(kSelSpace);
+
+		clearScreen(0x55);
+		setDefaultTextColor(0x0F);
+
+		done = false;
+		while (!done && !shouldQuit()) {
+			getMenuSel(IDS_TRO_TUTORIAL_1, &iSel, IDI_TRO_MAX_OPTION);
+
+			switch (iSel) {
+			case IDI_TRO_SEL_OPTION_1:
+				clearScreen(0x22, false);
+				g_system->updateScreen();
+				break;
+			case IDI_TRO_SEL_OPTION_2:
+				clearScreen(0x00, false);
+				g_system->updateScreen();
+				break;
+			case IDI_TRO_SEL_OPTION_3:
+				done = true;
+				break;
+			default:
+				break;
+			}
+		}
+
+		// do you need more practice ?
+		clearScreen(0x4F);
+		drawStr(7, 4, kColorDefault, IDS_TRO_TUTORIAL_5);
+		drawStr(9, 4, kColorDefault, IDS_TRO_TUTORIAL_6);
+		g_system->updateScreen();
+
+		if (!getSelection(kSelYesNo))
+			break;
+	}
+
+	// show info texts
+	clearScreen(0x5F);
+	drawStr(4, 1, kColorDefault, IDS_TRO_TUTORIAL_7);
+	drawStr(5, 1, kColorDefault, IDS_TRO_TUTORIAL_8);
+	g_system->updateScreen();
+	pressAnyKey();
+
+	clearScreen(0x2F);
+	drawStr(6, 1, kColorDefault, IDS_TRO_TUTORIAL_9);
+	g_system->updateScreen();
+	pressAnyKey();
+
+	clearScreen(0x19);
+	drawStr(7, 1, kColorDefault, IDS_TRO_TUTORIAL_10);
+	drawStr(8, 1, kColorDefault, IDS_TRO_TUTORIAL_11);
+	g_system->updateScreen();
+	pressAnyKey();
+
+	clearScreen(0x6E);
+	drawStr(9, 1, kColorDefault, IDS_TRO_TUTORIAL_12);
+	drawStr(10, 1, kColorDefault, IDS_TRO_TUTORIAL_13);
+	g_system->updateScreen();
+	pressAnyKey();
+
+	clearScreen(0x4C);
+	drawStr(11, 1, kColorDefault, IDS_TRO_TUTORIAL_14);
+	drawStr(12, 1, kColorDefault, IDS_TRO_TUTORIAL_15);
+	g_system->updateScreen();
+	pressAnyKey();
+
+	clearScreen(0x5D);
+	drawStr(13, 1, kColorDefault, IDS_TRO_TUTORIAL_16);
+	drawStr(14, 1, kColorDefault, IDS_TRO_TUTORIAL_17);
+	drawStr(15, 1, kColorDefault, IDS_TRO_TUTORIAL_18);
+	g_system->updateScreen();
+	pressAnyKey();
+
+	// show treasures
+	clearScreen(0x2A);
+	drawStr(2, 1, kColorDefault, IDS_TRO_TUTORIAL_19);
+	for (int i = 0; i < IDI_TRO_MAX_TREASURE; i++)
+		drawStr(19 - i, 11, kColorDefault, _items[i].name);
+
+	g_system->updateScreen();
+
+	pressAnyKey();
+}
+
+void TrollEngine::intro() {
+	// sierra on-line presents
+	clearScreen(0x2F);
+	drawStr(9, 10, kColorDefault, IDS_TRO_INTRO_0);
+	drawStr(14, 15, kColorDefault, IDS_TRO_INTRO_1);
+	g_system->updateScreen();
+	_system->delayMillis(3200);
+
+	CursorMan.showMouse(true);
+
+	// Draw logo
+	setDefaultTextColor(0x0f);
+	drawPic(45, false, true);
+	g_system->updateScreen();
+
+	// wait for keypress and alternate message
+	waitAnyKeyIntro();
+
+	// have you played this game before?
+	drawStr(22, 3, kColorDefault, IDS_TRO_INTRO_4);
+	drawStr(23, 6, kColorDefault, IDS_TRO_INTRO_5);
+	g_system->updateScreen();
+
+	if (!getSelection(kSelYesNo))
+		tutorial();
+
+	credits();
+}
+
+void TrollEngine::gameOver() {
+	// We do a check to see if the game should quit. Without this, the game show the picture, plays the
+	// music, and then quits. So if the game is quitting, we shouldn't run the "game over" part.
+	if (shouldQuit())
+		return;
+
+	char szMoves[40];
+
+	clearTextArea();
+	drawPic(42, true, true);
+
+	playTune(4, 25);
+
+	printUserMessage(16);
+
+	printUserMessage(33);
+
+	clearTextArea();
+
+	drawPic(46, true, true);
+
+	sprintf(szMoves, IDS_TRO_GAMEOVER_0, _moves);
+	drawStr(21, 1, kColorDefault, szMoves);
+	drawStr(22, 1, kColorDefault, IDS_TRO_GAMEOVER_1);
+	g_system->updateScreen();
+
+	pressAnyKey();
+}
+
+void TrollEngine::drawTroll() {
+	for (int i = 0; i < IDI_TRO_NUM_NONTROLL; i++)
+		if (_currentRoom == _nonTrollRooms[i]) {
+			_isTrollAway = true;
+			return;
+		}
+
+	drawPic(43, false, false, true);
+}
+
+int TrollEngine::drawRoom(char *menu) {
+	bool contFlag = false;
+
+	if (_currentRoom == 1) {
+		_picture->setDimensions(IDI_TRO_PIC_WIDTH, IDI_TRO_PIC_HEIGHT);
+		clearScreen(0x00, false);
+		_picture->clear();
+	} else {
+
+		if (_currentRoom != 42) {
+			if (_roomPicDeltas[_currentRoom]) {
+				contFlag = true;
+			}
+		}
+
+		drawPic(_currentRoom, contFlag, true);
+		g_system->updateScreen();
+
+		if (_currentRoom == 42) {
+			drawPic(44, false, false); // don't clear
+		} else {
+			if (!_isTrollAway) {
+				drawTroll();
+			}
+		}
+	}
+
+	g_system->updateScreen();
+
+	int n = 0;
+	strncat(menu, (char *)_gameData + _locMessagesIdx[_currentRoom], 39);
+
+	for (int i = 0; i < 3; i++) {
+		if (_roomDescs[_roomPicture - 1].options[i]) {
+			strncat(menu, Common::String::format("\n  %d.", i).c_str(), 5);
+
+			strncat(menu, (char *)_gameData + _options[_roomDescs[_roomPicture - 1].options[i] - 1], 35);
+
+			n = i + 1;
+		}
+	}
+
+	return n;
+}
+
+void TrollEngine::playTune(int tune, int len) {
+	if (!_soundOn)
+		return;
+
+	int freq, duration;
+	int ptr = _tunes[tune - 1];
+
+	for (int i = 0; i < len; i++) {
+		freq = READ_LE_UINT16(_gameData + ptr);
+		ptr += 2;
+		duration = READ_LE_UINT16(_gameData + ptr);
+		ptr += 2;
+
+		playNote(freq, duration);
+	}
+}
+
+void TrollEngine::pickupTreasure(int treasureId) {
+	_inventory[IDI_TRO_MAX_TREASURE - _treasuresLeft] = treasureId;
+
+	if (_currentRoom != 24) {
+		clearTextArea();
+		drawPic(_currentRoom, false, true);
+		g_system->updateScreen();
+	}
+
+	printUserMessage(treasureId + 16);
+
+	clearTextArea();
+
+	_treasuresLeft--;
+
+	switch (_treasuresLeft) {
+	case 1:
+		drawStr(22, 1, kColorDefault, IDS_TRO_TREASURE_7);
+		break;
+	case 0:
+		drawStr(22, 1, kColorDefault, IDS_TRO_TREASURE_8);
+		drawStr(23, 4, kColorDefault, IDS_TRO_TREASURE_9);
+
+		_roomStates[6] = 1;
+
+		_locMessagesIdx[6] = IDO_TRO_ALLTREASURES;
+		break;
+	default:
+		drawStr(22, 1, kColorDefault, Common::String::format(IDS_TRO_TREASURE_3, _treasuresLeft).c_str());
+		break;
+	}
+
+	pressAnyKey();
+}
+
+void TrollEngine::printUserMessage(int msgId) {
+	int i;
+
+	clearTextArea();
+
+	for (i = 0; i < _userMessages[msgId - 1].num; i++) {
+		drawStr(21 + i, 1, kColorDefault, _userMessages[msgId - 1].msg[i]);
+	}
+
+	if (msgId == 34) {
+		for (i = 0; i < 2; i++)
+			playTune(5, 11);
+	}
+	pressAnyKey();
+}
+
+void TrollEngine::gameLoop() {
+	bool done = false;
+	char menu[160 + 5];
+	int currentOption, numberOfOptions;
+	int roomParam;
+	int haveFlashlight;
+
+	_moves = 0;
+	_roomPicture = 1;
+	_treasuresLeft = IDI_TRO_MAX_TREASURE;
+	haveFlashlight = false;
+	_currentRoom = 0;
+	_isTrollAway = true;
+	_soundOn = true;
+
+	memset(_roomStates, 0, sizeof(_roomStates));
+
+	memset(_inventory, 0, sizeof(_inventory));
+
+	while (!done && !shouldQuit()) {
+		*menu = 0;
+
+		currentOption = 0;
+
+		numberOfOptions = drawRoom(menu);
+
+		if (getMenuSel(menu, &currentOption, numberOfOptions)) {
+			_moves++;
+		} else {
+			continue;
+		}
+
+		roomParam = _roomDescs[_roomPicture - 1].roomDescIndex[currentOption];
+
+		switch (_roomDescs[_roomPicture - 1].optionTypes[currentOption]) {
+		case OT_FLASHLIGHT:
+			if (!haveFlashlight) {
+				printUserMessage(13);
+				break;
+			}
+			// fall through
+		case OT_GO:
+			_currentRoom = roomParam;
+			_roomPicture = _roomPicStartIdx[_currentRoom];
+			_roomPicture += _roomStates[_currentRoom];
+
+			if (_currentRoom < 6 || _treasuresLeft == 0) {
+				_isTrollAway = true;
+			} else { // make odd 1:3
+				_isTrollAway = (rnd(3) != 2);
+			}
+			break;
+		case OT_GET:
+			if (!_isTrollAway) {
+				printUserMessage(34);
+			} else {
+				for (int i = 0; i < 4; i++) {
+					playTune(1, 3);
+					// delayMillis()
+				}
+
+				_roomStates[_currentRoom] = 1;
+				_roomPicDeltas[_currentRoom] = 0;
+
+				_roomPicture++;
+
+				if (_roomConnects[roomParam - 1] != 0xff) {
+					_roomStates[_roomConnects[roomParam - 1]] = 1;
+				}
+
+				if (roomParam == 1)
+					haveFlashlight = true;
+
+				_locMessagesIdx[_currentRoom] = IDO_TRO_LOCMESSAGES +
+				                                (roomParam + 42) * 39;
+
+				pickupTreasure(roomParam);
+			}
+			break;
+		case OT_DO:
+			if (roomParam != 16) {
+				printUserMessage(roomParam);
+				break;
+			}
+
+			done = true;
+			break;
+		default:
+			break;
+		}
+	}
+
+}
+
+void TrollEngine::fillOffsets() {
+	int i;
+
+	for (i = 0; i < IDI_TRO_PICNUM; i++)
+		_pictureOffsets[i] = READ_LE_UINT16(_gameData + IDO_TRO_PIC_START + i * 2);
+
+	for (i = 0; i < IDI_TRO_NUM_OPTIONS; i++)
+		_options[i] = READ_LE_UINT16(_gameData + IDO_TRO_OPTIONS + i * 2);
+
+	for (i = 0; i < IDI_TRO_NUM_NUMROOMS; i++) {
+		_roomPicStartIdx[i] = _gameData[IDO_TRO_PICSTARTIDX + i];
+		_roomPicDeltas[i] = _gameData[IDO_TRO_ROOMPICDELTAS + i];
+		_roomConnects[i] = _gameData[IDO_TRO_ROOMCONNECTS + i];
+	}
+
+	for (i = 0; i < IDI_TRO_NUM_LOCDESCS; i++)
+		_locMessagesIdx[i] = IDO_TRO_LOCMESSAGES + i * 39;
+
+	int start = READ_LE_UINT16(_gameData + IDO_TRO_ROOMDESCS);
+	int ptr;
+	int j;
+
+	for (i = 0; i < IDI_TRO_NUM_ROOMDESCS; i++, start += 2) {
+		ptr = READ_LE_UINT16(_gameData + start);
+
+		for (j = 0; j < 3; j++)
+			_roomDescs[i].options[j] = _gameData[ptr++];
+
+		for (j = 0; j < 3; j++) {
+			switch (_gameData[ptr++]) {
+			case 0:
+				_roomDescs[i].optionTypes[j] = OT_GO;
+				break;
+			case 1:
+				_roomDescs[i].optionTypes[j] = OT_GET;
+				break;
+			case 2:
+				_roomDescs[i].optionTypes[j] = OT_DO;
+				break;
+			case 3:
+				_roomDescs[i].optionTypes[j] = OT_FLASHLIGHT;
+				break;
+			default:
+				error("Bad data @ (%x) %d", ptr - 1, i);
+			}
+		}
+
+		for (j = 0; j < 3; j++)
+			_roomDescs[i].roomDescIndex[j] = _gameData[ptr++];
+	}
+
+	start = IDO_TRO_USERMESSAGES;
+
+	for (i = 0; i < IDI_TRO_NUM_USERMSGS; i++, start += 2) {
+		ptr = READ_LE_UINT16(_gameData + start);
+
+		_userMessages[i].num = _gameData[ptr++];
+
+		for (j = 0; j < _userMessages[i].num; j++, ptr += 39) {
+			memcpy(_userMessages[i].msg[j], _gameData + ptr, 39);
+			_userMessages[i].msg[j][39] = 0;
+		}
+	}
+
+	start = IDO_TRO_ITEMS;
+
+	for (i = 0; i < IDI_TRO_MAX_TREASURE; i++, start += 2) {
+		ptr = READ_LE_UINT16(_gameData + start);
+		_items[i].bg = _gameData[ptr++];
+		_items[i].fg = _gameData[ptr++];
+		memcpy(_items[i].name, _gameData + ptr, 15);
+		_items[i].name[15] = 0;
+	}
+
+	for (i = 0; i < IDO_TRO_NONTROLLROOMS; i++)
+		_nonTrollRooms[i] = _gameData[IDO_TRO_NONTROLLROOMS + i];
+
+	_tunes[0] = 0x3BFD;
+	_tunes[1] = 0x3C09;
+	_tunes[2] = 0x3C0D;
+	_tunes[3] = 0x3C11;
+	_tunes[4] = 0x3C79;
+	_tunes[5] = 0x3CA5;
+}
+
+// Init
+
+void TrollEngine::init() {
+	_picture->setPictureVersion(AGIPIC_V15);
+	//SetScreenPar(320, 200, (char *)ibm_fontdata);
+
+	const int gaps[] = { 0x3A40,  0x4600,  0x4800,  0x5800,  0x5a00,  0x6a00,
+	                     0x6c00,  0x7400,  0x7600,  0x7c00,  0x7e00,  0x8e00,
+	                     0x9000,  0xa000,  0xa200,  0xb200,  0xb400,  0xc400,
+	                     0xc600,  0xd600,  0xd800,  0xe800,  0xea00,  0xfa00,
+	                     0xfc00,  0x10c00, 0x10e00, 0x11e00, 0x12000, 0x13000
+	                   };
+
+	Common::File infile;
+	if (!infile.open(IDA_TRO_BINNAME))
+		return;
+
+	_gameData = (byte *)malloc(0xD9C0);
+
+	bool flip = true;
+	byte *ptr = _gameData;
+	int diff;
+
+	for (int i = 0; i < ARRAYSIZE(gaps) - 1; i++) {
+		diff = gaps[i + 1] - gaps[i];
+
+		if (flip) {
+			infile.seek(gaps[i]);
+			infile.read(ptr, diff);
+			ptr += diff;
+		} else {
+		}
+		flip = !flip;
+	}
+
+	// One sector is off
+	infile.seek(0x18470);
+	infile.read(_gameData + 15632, 592);
+
+	infile.close();
+
+	fillOffsets();
+}
+
+Common::Error TrollEngine::go() {
+	init();
+
+	while (!shouldQuit()) {
+		intro();
+		gameLoop();
+		gameOver();
+	}
+
+	return Common::kNoError;
+}
+
+} // End of namespace Agi
diff --git a/engines/agi/preagi/troll.h b/engines/agi/preagi/troll.h
new file mode 100644
index 00000000000..7c4c765c4c8
--- /dev/null
+++ b/engines/agi/preagi/troll.h
@@ -0,0 +1,224 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef AGI_PREAGI_TROLL_H
+#define AGI_PREAGI_TROLL_H
+
+namespace Agi {
+
+// strings
+
+#define IDS_TRO_DISK        "ERROR ERROR !"
+#define IDS_TRO_PATH_PIC    "%s"
+
+#define IDS_TRO_PRESSANYKEY "PRESS ANY KEY TO CONTINUE:"
+
+#define IDS_TRO_INTRO_0     "SIERRA ON-LINE INC."
+#define IDS_TRO_INTRO_1     "Presents :"
+#define IDS_TRO_INTRO_2     "Copyright 1984 Sierra On-Line Inc."
+#define IDS_TRO_INTRO_3     "    Press any key to continue.    "
+#define IDS_TRO_INTRO_4     "HAVE YOU PLAYED THIS GAME BEFORE ?"
+#define IDS_TRO_INTRO_5     "PRESS <Y> OR <N>"
+
+#define IDS_TRO_TUTORIAL_0  " First press the <space bar>.\n  1. Turn the screen GREEN.\n  2. Turn the screen BLACK.\n *3. SEE a SURPRISE, and then more."
+#define IDS_TRO_TUTORIAL_1  " Press <return> to make your choice.\n  1. Turn the screen GREEN.\n  2. Turn the screen BLACK.\n  3. SEE a SURPRISE, and then more."
+//#define IDS_TRO_TUTORIAL_0    "First press the <space bar>."
+//#define IDS_TRO_TUTORIAL_1    "1. Turn the screen GREEN."
+//#define IDS_TRO_TUTORIAL_2    "2. Turn the screen BLACK."
+//#define IDS_TRO_TUTORIAL_3    "3. SEE a SURPRISE, and then more."
+//#define IDS_TRO_TUTORIAL_4    "Press <return> to make your choice."
+#define IDS_TRO_TUTORIAL_5  "Would you like more practice ?"
+#define IDS_TRO_TUTORIAL_6  "Press <Y> for yes, <N> for no."
+#define IDS_TRO_TUTORIAL_7  "The evil TROLL has hidden all the"
+#define IDS_TRO_TUTORIAL_8  "Treasures of MARK, the Dwarf King."
+#define IDS_TRO_TUTORIAL_9  "Help KING MARK find his Treasures."
+#define IDS_TRO_TUTORIAL_10 "You can't take a Treasure if the TROLL"
+#define IDS_TRO_TUTORIAL_11 "is in the same picture as the Treasure."
+#define IDS_TRO_TUTORIAL_12 "To make the TROLL go away you have to"
+#define IDS_TRO_TUTORIAL_13 "make the picture change."
+#define IDS_TRO_TUTORIAL_14 "During the game see the Treasures you"
+#define IDS_TRO_TUTORIAL_15 "have already found by pressing <F>."
+#define IDS_TRO_TUTORIAL_16 "During the game you can turn the sound"
+#define IDS_TRO_TUTORIAL_17 "on or off by pressing the <S> key "
+#define IDS_TRO_TUTORIAL_18 "while holding down the <Ctrl> key."
+#define IDS_TRO_TUTORIAL_19 "The TROLL has hidden these Treasures:"
+
+#define IDS_TRO_CREDITS_0   "Prepare to enter the world of . . ."
+#define IDS_TRO_CREDITS_1   "TROLL'S TALE (tm)"
+#define IDS_TRO_CREDITS_2   "------------"
+#define IDS_TRO_CREDITS_3   "Written by MIKE MACCHESNEY"
+#define IDS_TRO_CREDITS_4   "Conversion by PETER OLIPHANT"
+#define IDS_TRO_CREDITS_5   "Graphic Art by DOUG MACNEILL"
+#define IDS_TRO_CREDITS_6   "Original Version by AL LOWE"
+
+#define IDS_TRO_TREASURE_0  "TREASURES FOUND"
+#define IDS_TRO_TREASURE_1  "---------------"
+#define IDS_TRO_TREASURE_2  "NONE"
+#define IDS_TRO_TREASURE_3  "THERE ARE STILL %d TREASURES TO FIND"
+#define IDS_TRO_TREASURE_4  "%d TREASURES TO FIND"
+#define IDS_TRO_TREASURE_5  "%d TREASURE TO FIND"
+#define IDS_TRO_TREASURE_6  "YOU HAVE FOUND ALL OF THE TREASURES!!"
+#define IDS_TRO_TREASURE_7  "THERE'S ONLY ONE MORE TREASURE TO FIND."
+#define IDS_TRO_TREASURE_8  "GREAT!! YOU HAVE FOUND EVERY TREASURE."
+#define IDS_TRO_TREASURE_9  "TAKE THE TREASURES TO THE GUARD."
+
+#define IDS_TRO_GAMEOVER_0  "You took %d moves to complete TROLL'S"
+#define IDS_TRO_GAMEOVER_1  "TALE. Do you think you can do better?"
+
+// picture
+
+#define IDI_TRO_PICNUM 47
+
+#define IDI_TRO_PIC_WIDTH   160
+#define IDI_TRO_PIC_HEIGHT  168
+#define IDI_TRO_PIC_X0      0
+#define IDI_TRO_PIC_Y0      0
+#define IDI_TRO_PIC_FLAGS   IDF_AGI_PIC_V15
+
+// max values
+
+#define IDI_TRO_MAX_TREASURE    16
+#define IDI_TRO_MAX_OPTION      3
+
+#define IDI_TRO_SEL_OPTION_1    0
+#define IDI_TRO_SEL_OPTION_2    1
+#define IDI_TRO_SEL_OPTION_3    2
+
+#define IDI_TRO_MAX_ROW_PIC 21
+
+#define IDI_TRO_NUM_ROOMDESCS   65
+#define IDI_TRO_NUM_OPTIONS     129
+#define IDI_TRO_NUM_NUMROOMS    43
+
+#define IDI_TRO_NUM_USERMSGS    34
+
+#define IDI_TRO_NUM_LOCDESCS    59
+
+#define IDI_TRO_NUM_NONTROLL    9
+
+// offsets
+
+#define IDA_TRO_BINNAME "troll.img"
+
+#define IDO_TRO_DATA_START    0x3A40
+#define IDO_TRO_PIC_START     0x3EF5
+#define IDO_TRO_LOCMESSAGES   0x1F7C
+#define IDO_TRO_USERMESSAGES  0x34A4
+#define IDO_TRO_ROOMDESCS     0x0082
+#define IDO_TRO_OPTIONS       0x0364
+#define IDO_TRO_PICSTARTIDX   0x02CD
+#define IDO_TRO_ROOMPICDELTAS 0x030C
+#define IDO_TRO_ALLTREASURES  0x3B24
+#define IDO_TRO_ITEMS         0x34E8
+#define IDO_TRO_FRAMEPIC      0x3EC2
+#define IDO_TRO_ROOMCONNECTS  0x02FA
+#define IDO_TRO_NONTROLLROOMS 0x3CF9
+
+enum OptionType {
+	OT_GO,
+	OT_GET,
+	OT_DO,
+	OT_FLASHLIGHT
+};
+
+struct RoomDesc {
+	int options[3];
+	OptionType optionTypes[3];
+	int roomDescIndex[3];
+};
+
+struct UserMsg {
+	int num;
+	char msg[3][40];
+};
+
+struct Item {
+	byte bg;
+	byte fg;
+	char name[16];
+};
+
+class TrollEngine : public PreAgiEngine {
+public:
+	TrollEngine(OSystem *syst, const AGIGameDescription *gameDesc);
+	~TrollEngine() override;
+
+	Common::Error go() override;
+
+private:
+	int _roomPicture;
+	int _treasuresLeft;
+	int _currentRoom;
+	int _moves;
+
+	bool _isTrollAway;
+
+	int _inventory[IDI_TRO_MAX_TREASURE];
+
+	bool _soundOn;
+
+	byte *_gameData;
+
+	void init();
+	void intro();
+	void drawPic(int iPic, bool f3IsCont, bool clear, bool troll = false);
+	void drawTroll();
+	void gameLoop();
+	void gameOver();
+	void tutorial();
+	void credits();
+
+	void inventory();
+	void pickupTreasure(int treasureId);
+
+	int drawRoom(char *menu);
+	void printUserMessage(int msgId);
+
+	void pressAnyKey(int col = 4);
+	void waitAnyKeyIntro();
+
+	void playTune(int tune, int len);
+
+	bool getMenuSel(const char *, int *, int);
+
+	void drawMenu(const char *szMenu, int iSel);
+
+	void fillOffsets();
+
+	// These are come from game data
+	int _pictureOffsets[IDI_TRO_PICNUM];
+	int _roomPicStartIdx[IDI_TRO_NUM_NUMROOMS];
+	int _roomPicDeltas[IDI_TRO_NUM_NUMROOMS];
+	int _roomStates[IDI_TRO_NUM_NUMROOMS];
+	UserMsg _userMessages[IDI_TRO_NUM_USERMSGS];
+	int _locMessagesIdx[IDI_TRO_NUM_LOCDESCS];
+	RoomDesc _roomDescs[IDI_TRO_NUM_ROOMDESCS];
+	int _options[IDI_TRO_NUM_OPTIONS];
+	Item _items[IDI_TRO_MAX_TREASURE];
+	int _roomConnects[IDI_TRO_NUM_OPTIONS];
+	int _nonTrollRooms[IDO_TRO_NONTROLLROOMS];
+
+	int _tunes[6];
+};
+
+} // End of namespace Agi
+
+#endif
diff --git a/engines/agi/preagi/winnie.cpp b/engines/agi/preagi/winnie.cpp
new file mode 100644
index 00000000000..9f993df8d1c
--- /dev/null
+++ b/engines/agi/preagi/winnie.cpp
@@ -0,0 +1,1392 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "agi/preagi/preagi.h"
+#include "agi/preagi/winnie.h"
+#include "agi/graphics.h"
+
+#include "graphics/cursorman.h"
+
+#include "common/events.h"
+#include "common/memstream.h"
+#include "common/savefile.h"
+#include "common/textconsole.h"
+
+#include "audio/mididrv.h"
+
+namespace Agi {
+
+void WinnieEngine::parseRoomHeader(WTP_ROOM_HDR *roomHdr, byte *buffer, int len) {
+	int i;
+
+	Common::MemoryReadStreamEndian readS(buffer, len, _isBigEndian);
+
+	roomHdr->roomNumber = readS.readByte();
+	roomHdr->objId = readS.readByte();
+	roomHdr->ofsPic = readS.readUint16();
+	roomHdr->fileLen = readS.readUint16();
+	roomHdr->reserved0 = readS.readUint16();
+
+	for (i = 0; i < IDI_WTP_MAX_DIR; i++)
+		roomHdr->roomNew[i] = readS.readByte();
+
+	roomHdr->objX = readS.readByte();
+	roomHdr->objY = readS.readByte();
+
+	roomHdr->reserved1 = readS.readUint16();
+
+	for (i = 0; i < IDI_WTP_MAX_BLOCK; i++)
+		roomHdr->ofsDesc[i] = readS.readUint16();
+
+	for (i = 0; i < IDI_WTP_MAX_BLOCK; i++)
+		roomHdr->ofsBlock[i] = readS.readUint16();
+
+	for (i = 0; i < IDI_WTP_MAX_STR; i++)
+		roomHdr->ofsStr[i] = readS.readUint16();
+
+	roomHdr->reserved2 = readS.readUint32();
+
+	for (i = 0; i < IDI_WTP_MAX_BLOCK; i++)
+		for (byte j = 0; j < IDI_WTP_MAX_BLOCK; j++)
+			roomHdr->opt[i].ofsOpt[j] = readS.readUint16();
+}
+
+void WinnieEngine::parseObjHeader(WTP_OBJ_HDR *objHdr, byte *buffer, int len) {
+	int i;
+
+	Common::MemoryReadStreamEndian readS(buffer, len, _isBigEndian);
+
+	objHdr->fileLen = readS.readUint16();
+	objHdr->objId = readS.readUint16();
+
+	for (i = 0; i < IDI_WTP_MAX_OBJ_STR_END; i++)
+		objHdr->ofsEndStr[i] = readS.readUint16();
+
+	for (i = 0; i < IDI_WTP_MAX_OBJ_STR; i++)
+		objHdr->ofsStr[i] = readS.readUint16();
+
+	objHdr->ofsPic = readS.readUint16();
+}
+
+uint32 WinnieEngine::readRoom(int iRoom, uint8 *buffer, WTP_ROOM_HDR &roomHdr) {
+	Common::String fileName;
+
+	if (getPlatform() == Common::kPlatformDOS)
+		fileName = Common::String::format(IDS_WTP_ROOM_DOS, iRoom);
+	else if (getPlatform() == Common::kPlatformAmiga)
+		fileName = Common::String::format(IDS_WTP_ROOM_AMIGA, iRoom);
+	else if (getPlatform() == Common::kPlatformC64)
+		fileName = Common::String::format(IDS_WTP_ROOM_C64, iRoom);
+	else if (getPlatform() == Common::kPlatformApple2)
+		fileName = Common::String::format(IDS_WTP_ROOM_APPLE, iRoom);
+
+	Common::File file;
+	if (!file.open(fileName)) {
+		warning("Could not open file \'%s\'", fileName.c_str());
+		return 0;
+	}
+
+	uint32 filelen = file.size();
+	if (getPlatform() == Common::kPlatformC64) { // Skip the loading address
+		filelen -= 2;
+		file.seek(2, SEEK_CUR);
+	}
+
+	memset(buffer, 0, 4096);
+	file.read(buffer, filelen);
+	file.close();
+
+	parseRoomHeader(&roomHdr, buffer, filelen);
+
+	return filelen;
+}
+
+uint32 WinnieEngine::readObj(int iObj, uint8 *buffer) {
+	Common::String fileName;
+
+	if (getPlatform() == Common::kPlatformDOS)
+		fileName = Common::String::format(IDS_WTP_OBJ_DOS, iObj);
+	else if (getPlatform() == Common::kPlatformAmiga)
+		fileName = Common::String::format(IDS_WTP_OBJ_AMIGA, iObj);
+	else if (getPlatform() == Common::kPlatformC64)
+		fileName = Common::String::format(IDS_WTP_OBJ_C64, iObj);
+	else if (getPlatform() == Common::kPlatformApple2)
+		fileName = Common::String::format(IDS_WTP_OBJ_APPLE, iObj);
+
+	Common::File file;
+	if (!file.open(fileName)) {
+		warning("Could not open file \'%s\'", fileName.c_str());
+		return 0;
+	}
+
+	uint32 filelen = file.size();
+	if (getPlatform() == Common::kPlatformC64) { // Skip the loading address
+		filelen -= 2;
+		file.seek(2, SEEK_CUR);
+	}
+
+	memset(buffer, 0, 2048);
+	file.read(buffer, filelen);
+	file.close();
+	return filelen;
+}
+
+void WinnieEngine::randomize() {
+	int iObj = 0;
+	int iRoom = 0;
+	bool done;
+
+	for (int i = 0; i < IDI_WTP_MAX_OBJ_MISSING; i++) {
+		done = false;
+
+		while (!done) {
+			iObj = rnd(IDI_WTP_MAX_OBJ - 1);
+			done = true;
+
+			for (int j = 0; j < IDI_WTP_MAX_OBJ_MISSING; j++) {
+				if (_gameStateWinnie.iUsedObj[j] == iObj) {
+					done = false;
+					break;
+				}
+			}
+		}
+
+		_gameStateWinnie.iUsedObj[i] = iObj;
+
+		done = false;
+		while (!done) {
+			iRoom = rnd(IDI_WTP_MAX_ROOM_NORMAL);
+			done = true;
+
+			for (int j = 0; j < IDI_WTP_MAX_ROOM_OBJ; j++) {
+				if (_gameStateWinnie.iObjRoom[j] == iRoom) {
+					done = false;
+					break;
+				}
+			}
+		}
+
+		_gameStateWinnie.iObjRoom[iObj] = iRoom;
+	}
+}
+
+void WinnieEngine::intro() {
+	drawPic(IDS_WTP_FILE_LOGO);
+	printStr(IDS_WTP_INTRO_0);
+	g_system->updateScreen();
+	_system->delayMillis(0x640);
+
+	if (getPlatform() == Common::kPlatformAmiga)
+		_gfx->clearDisplay(0);
+
+	drawPic(IDS_WTP_FILE_TITLE);
+
+	printStr(IDS_WTP_INTRO_1);
+	g_system->updateScreen();
+	_system->delayMillis(0x640);
+
+	if (!playSound(IDI_WTP_SND_POOH_0))
+		return;
+
+	if (!playSound(IDI_WTP_SND_POOH_1))
+		return;
+
+	if (!playSound(IDI_WTP_SND_POOH_2))
+		return;
+}
+
+int WinnieEngine::getObjInRoom(int iRoom) {
+	for (int iObj = 1; iObj < IDI_WTP_MAX_ROOM_OBJ; iObj++)
+		if (_gameStateWinnie.iObjRoom[iObj] == iRoom)
+			return iObj;
+	return 0;
+}
+
+void WinnieEngine::setTakeDrop(int fCanSel[]) {
+	fCanSel[IDI_WTP_SEL_TAKE] = getObjInRoom(_room);
+	fCanSel[IDI_WTP_SEL_DROP] = _gameStateWinnie.iObjHave;
+}
+
+void WinnieEngine::setWinnieFlag(int iFlag) {
+	_gameStateWinnie.fGame[iFlag] = 1;
+}
+
+void WinnieEngine::clearWinnieFlag(int iFlag) {
+	_gameStateWinnie.fGame[iFlag] = 0;
+}
+
+int WinnieEngine::parser(int pc, int index, uint8 *buffer) {
+	WTP_ROOM_HDR hdr;
+	int startpc = pc;
+	int8 opcode;
+	int iNewRoom = 0;
+
+	int iSel, iDir, iBlock;
+	int fCanSel[IDI_WTP_SEL_LAST + 1];
+	char szMenu[121] = {0};
+	bool done;
+	int fBlock;
+
+	// extract header from buffer
+	parseRoomHeader(&hdr, buffer, sizeof(WTP_ROOM_HDR));
+
+	while (!shouldQuit()) {
+		pc = startpc;
+
+		// check if block is to be run
+
+		iBlock = *(buffer + pc++);
+		if (iBlock == 0)
+			return IDI_WTP_PAR_OK;
+
+		fBlock = *(buffer + pc++);
+		if (_gameStateWinnie.fGame[iBlock] != fBlock)
+			return IDI_WTP_PAR_OK;
+
+		// extract text from block
+
+		opcode = *(buffer + pc);
+		switch (opcode) {
+		case 0:
+		case IDO_WTP_OPTION_0:
+		case IDO_WTP_OPTION_1:
+		case IDO_WTP_OPTION_2:
+			// clear fCanSel block
+			memset(fCanSel, 0, sizeof(fCanSel));
+
+			// check if NSEW directions should be displayed
+			if (hdr.roomNew[0]) {
+				fCanSel[IDI_WTP_SEL_NORTH] = fCanSel[IDI_WTP_SEL_SOUTH] =
+				fCanSel[IDI_WTP_SEL_EAST] = fCanSel[IDI_WTP_SEL_WEST] = true;
+			}
+
+			// check if object in room or player carrying one
+			setTakeDrop(fCanSel);
+
+			// check which rows have a menu option
+			for (iSel = 0; iSel < IDI_WTP_MAX_OPTION; iSel++) {
+				opcode = *(buffer + pc++);
+				if (opcode) {
+					fCanSel[opcode - IDO_WTP_OPTION_0] = true;
+					fCanSel[iSel + IDI_WTP_SEL_REAL_OPT_1] = opcode - 0x14;
+				}
+			}
+
+			// extract menu string
+			Common::strlcpy(szMenu, (char *)(buffer + pc), 121);
+			XOR80(szMenu);
+			break;
+		default:
+			// print description
+			printStrWinnie((char *)(buffer + pc));
+			if (getSelection(kSelBackspace) == 1)
+				return IDI_WTP_PAR_OK;
+			else
+				return IDI_WTP_PAR_BACK;
+		}
+
+		// input handler
+
+		done = false;
+		while (!done) {
+			// run wind if it's time
+			if (_doWind)
+				wind();
+
+			// get menu selection
+			getMenuSel(szMenu, &iSel, fCanSel);
+
+			if (++_gameStateWinnie.nMoves == IDI_WTP_MAX_MOVES_UNTIL_WIND)
+				_doWind = true;
+
+			if (_winnieEvent && (_room <= IDI_WTP_MAX_ROOM_TELEPORT)) {
+				if (!_tiggerMist) {
+					_tiggerMist = 1;
+					tigger();
+				} else {
+					_tiggerMist = 0;
+					mist();
+				}
+				_winnieEvent = false;
+				return IDI_WTP_PAR_GOTO;
+			}
+
+			// process selection
+			switch (iSel) {
+			case IDI_WTP_SEL_HOME:
+				switch (_room) {
+				case IDI_WTP_ROOM_HOME:
+				case IDI_WTP_ROOM_MIST:
+				case IDI_WTP_ROOM_TIGGER:
+					break;
+				default:
+					_room = IDI_WTP_ROOM_HOME;
+					return IDI_WTP_PAR_GOTO;
+				}
+				break;
+			case IDI_WTP_SEL_BACK:
+				return IDI_WTP_PAR_BACK;
+			case IDI_WTP_SEL_OPT_1:
+			case IDI_WTP_SEL_OPT_2:
+			case IDI_WTP_SEL_OPT_3:
+				done = true;
+				break;
+			case IDI_WTP_SEL_NORTH:
+			case IDI_WTP_SEL_SOUTH:
+			case IDI_WTP_SEL_EAST:
+			case IDI_WTP_SEL_WEST:
+				iDir = iSel - IDI_WTP_SEL_NORTH;
+
+				if (hdr.roomNew[iDir] == IDI_WTP_ROOM_NONE) {
+					printStr(IDS_WTP_CANT_GO);
+					getSelection(kSelAnyKey);
+				} else {
+					_room = hdr.roomNew[iDir];
+					return IDI_WTP_PAR_GOTO;
+				}
+				break;
+			case IDI_WTP_SEL_TAKE:
+				takeObj(_room);
+				setTakeDrop(fCanSel);
+				break;
+			case IDI_WTP_SEL_DROP:
+				dropObj(_room);
+				setTakeDrop(fCanSel);
+				break;
+			default:
+				break;
+			}
+		}
+
+		// jump to the script block of the selected option
+		pc = hdr.opt[index].ofsOpt[iSel] - _roomOffset;
+
+		opcode = *(buffer + pc);
+		if (!opcode) pc++;
+
+		// process script
+		do {
+			opcode = *(buffer + pc++);
+			switch (opcode) {
+			case IDO_WTP_GOTO_ROOM:
+				opcode = *(buffer + pc++);
+				iNewRoom = opcode;
+				break;
+			case IDO_WTP_PRINT_MSG:
+				opcode = *(buffer + pc++);
+				printRoomStr(_room, opcode);
+				getSelection(kSelAnyKey);
+				break;
+			case IDO_WTP_PRINT_STR:
+				opcode = *(buffer + pc++);
+				printRoomStr(_room, opcode);
+				break;
+			case IDO_WTP_DROP_OBJ:
+				opcode = *(buffer + pc++);
+				opcode = -1;
+				dropObjRnd();
+				break;
+			case IDO_WTP_FLAG_CLEAR:
+				opcode = *(buffer + pc++);
+				clearWinnieFlag(opcode);
+				break;
+			case IDO_WTP_FLAG_SET:
+				opcode = *(buffer + pc++);
+				setWinnieFlag(opcode);
+				break;
+			case IDO_WTP_GAME_OVER:
+				gameOver();
+				break;
+			case IDO_WTP_WALK_MIST:
+				_mist--;
+				if (!_mist) {
+					_room = rnd(IDI_WTP_MAX_ROOM_TELEPORT) + 1;
+					return IDI_WTP_PAR_GOTO;
+				}
+				break;
+			case IDO_WTP_PLAY_SOUND:
+				opcode = *(buffer + pc++);
+				playSound((ENUM_WTP_SOUND)opcode);
+				break;
+			case IDO_WTP_SAVE_GAME:
+				saveGame();
+				_room = IDI_WTP_ROOM_HOME;
+				return IDI_WTP_PAR_GOTO;
+			case IDO_WTP_LOAD_GAME:
+				loadGame();
+				_room = IDI_WTP_ROOM_HOME;
+				return IDI_WTP_PAR_GOTO;
+			case IDO_WTP_OWL_HELP:
+				opcode = *(buffer + pc++);
+				showOwlHelp();
+				break;
+			case IDO_WTP_GOTO_RND:
+				_room = rnd(IDI_WTP_MAX_ROOM_TELEPORT) + 1;
+				return IDI_WTP_PAR_GOTO;
+			default:
+				opcode = 0;
+				break;
+			}
+		} while (opcode && !shouldQuit());
+
+		if (iNewRoom) {
+			_room = iNewRoom;
+			return IDI_WTP_PAR_GOTO;
+		}
+
+		if (iBlock == 1)
+			return IDI_WTP_PAR_OK;
+		g_system->updateScreen();
+	}
+
+	return IDI_WTP_PAR_OK;
+}
+
+void WinnieEngine::keyHelp() {
+	playSound(IDI_WTP_SND_KEYHELP);
+	printStr(IDS_WTP_HELP_0);
+	getSelection(kSelAnyKey);
+	printStr(IDS_WTP_HELP_1);
+	getSelection(kSelAnyKey);
+}
+
+void WinnieEngine::inventory() {
+	if (_gameStateWinnie.iObjHave)
+		printObjStr(_gameStateWinnie.iObjHave, IDI_WTP_OBJ_TAKE);
+	else {
+		clearTextArea();
+		drawStr(IDI_WTP_ROW_MENU, IDI_WTP_COL_MENU, IDA_DEFAULT, IDS_WTP_INVENTORY_0);
+	}
+
+	Common::String missing = Common::String::format(IDS_WTP_INVENTORY_1, _gameStateWinnie.nObjMiss);
+
+	drawStr(IDI_WTP_ROW_OPTION_4, IDI_WTP_COL_MENU, IDA_DEFAULT, missing.c_str());
+	g_system->updateScreen();
+	getSelection(kSelAnyKey);
+}
+
+void WinnieEngine::printObjStr(int iObj, int iStr) {
+	WTP_OBJ_HDR hdr;
+	uint8 *buffer = (uint8 *)malloc(2048);
+
+	readObj(iObj, buffer);
+	parseObjHeader(&hdr, buffer, sizeof(hdr));
+
+	printStrWinnie((char *)(buffer + hdr.ofsStr[iStr] - _objOffset));
+	free(buffer);
+}
+
+bool WinnieEngine::isRightObj(int iRoom, int iObj, int *iCode) {
+	WTP_ROOM_HDR roomhdr;
+	WTP_OBJ_HDR objhdr;
+	uint8 *roomdata = (uint8 *)malloc(4096);
+	uint8 *objdata = (uint8 *)malloc(2048);
+
+	readRoom(iRoom, roomdata, roomhdr);
+	readObj(iObj, objdata);
+	parseObjHeader(&objhdr, objdata, sizeof(WTP_OBJ_HDR));
+
+	free(roomdata);
+	free(objdata);
+
+	*iCode = objhdr.objId;
+
+	if (objhdr.objId == 11) objhdr.objId = 34;
+
+	if (roomhdr.objId == objhdr.objId)
+		return true;
+	else
+		return false;
+}
+
+void WinnieEngine::takeObj(int iRoom) {
+	if (_gameStateWinnie.iObjHave) {
+		// player is already carrying an object, can't take
+		printStr(IDS_WTP_CANT_TAKE);
+		getSelection(kSelAnyKey);
+	} else {
+		// take object
+		int iObj = getObjInRoom(iRoom);
+
+		_gameStateWinnie.iObjHave = iObj;
+		_gameStateWinnie.iObjRoom[iObj] = 0;
+
+		printStr(IDS_WTP_OK);
+		playSound(IDI_WTP_SND_TAKE);
+
+		drawRoomPic();
+
+		// print object "take" string
+		printObjStr(_gameStateWinnie.iObjHave, IDI_WTP_OBJ_TAKE);
+		getSelection(kSelAnyKey);
+
+		// HACK WARNING
+		if (iObj == 18) {
+			_gameStateWinnie.fGame[0x0d] = 1;
+		}
+	}
+}
+
+void WinnieEngine::dropObj(int iRoom) {
+	int iCode;
+
+	if (getObjInRoom(iRoom)) {
+		// there already is an object in the room, can't drop
+		printStr(IDS_WTP_CANT_DROP);
+		getSelection(kSelAnyKey);
+	} else {
+		// HACK WARNING
+		if (_gameStateWinnie.iObjHave == 18) {
+			_gameStateWinnie.fGame[0x0d] = 0;
+		}
+
+		if (isRightObj(iRoom, _gameStateWinnie.iObjHave, &iCode)) {
+			// object has been dropped in the right place
+			printStr(IDS_WTP_OK);
+			getSelection(kSelAnyKey);
+			playSound(IDI_WTP_SND_DROP_OK);
+			printObjStr(_gameStateWinnie.iObjHave, IDI_WTP_OBJ_DROP);
+			getSelection(kSelAnyKey);
+
+			// increase amount of objects returned, decrease amount of objects missing
+			_gameStateWinnie.nObjMiss--;
+			_gameStateWinnie.nObjRet++;
+
+			// xor the dropped object with 0x80 to signify it has been dropped in the right place
+			for (int i = 0; i < IDI_WTP_MAX_OBJ_MISSING; i++) {
+				if (_gameStateWinnie.iUsedObj[i] == _gameStateWinnie.iObjHave) {
+					_gameStateWinnie.iUsedObj[i] ^= 0x80;
+					break;
+				}
+			}
+
+			// set flag according to dropped object's id
+			_gameStateWinnie.fGame[iCode] = 1;
+
+			// player is carrying nothing
+			_gameStateWinnie.iObjHave = 0;
+
+			if (!_gameStateWinnie.nObjMiss) {
+				// all objects returned, tell player to find party
+				playSound(IDI_WTP_SND_FANFARE);
+				printStr(IDS_WTP_GAME_OVER_0);
+				getSelection(kSelAnyKey);
+				printStr(IDS_WTP_GAME_OVER_1);
+				getSelection(kSelAnyKey);
+			}
+		} else {
+			// drop object in the given room
+			_gameStateWinnie.iObjRoom[_gameStateWinnie.iObjHave] = iRoom;
+
+			// object has been dropped in the wrong place
+			printStr(IDS_WTP_WRONG_PLACE);
+			getSelection(kSelAnyKey);
+
+			playSound(IDI_WTP_SND_DROP);
+			drawRoomPic();
+
+			printStr(IDS_WTP_WRONG_PLACE);
+			getSelection(kSelAnyKey);
+
+			// print object description
+			printObjStr(_gameStateWinnie.iObjHave, IDI_WTP_OBJ_DESC);
+			getSelection(kSelAnyKey);
+
+			_gameStateWinnie.iObjHave = 0;
+		}
+	}
+}
+
+void WinnieEngine::dropObjRnd() {
+	if (!_gameStateWinnie.iObjHave)
+		return;
+
+	int iRoom = 0;
+	bool done = false;
+
+	while (!done) {
+		iRoom = rnd(IDI_WTP_MAX_ROOM_NORMAL);
+		done = true;
+		if (iRoom == _room)
+			done = false;
+		for (int j = 0; j < IDI_WTP_MAX_ROOM_OBJ; j++) {
+			if (_gameStateWinnie.iObjRoom[j] == iRoom) {
+				done = false;
+			}
+		}
+	}
+
+	_gameStateWinnie.iObjRoom[_gameStateWinnie.iObjHave] = iRoom;
+	_gameStateWinnie.iObjHave = 0;
+}
+
+void WinnieEngine::wind() {
+	int iRoom = 0;
+	bool done;
+
+	_doWind = 0;
+	_gameStateWinnie.nMoves = 0;
+	if (!_gameStateWinnie.nObjMiss)
+		return;
+
+	printStr(IDS_WTP_WIND_0);
+	playSound(IDI_WTP_SND_WIND_0);
+	getSelection(kSelAnyKey);
+
+	printStr(IDS_WTP_WIND_1);
+	playSound(IDI_WTP_SND_WIND_0);
+	getSelection(kSelAnyKey);
+
+	dropObjRnd();
+
+	// randomize positions of objects at large
+	for (int i = 0; i < IDI_WTP_MAX_OBJ_MISSING; i++) {
+		if (!(_gameStateWinnie.iUsedObj[i] & IDI_XOR_KEY)) {
+			done = false;
+			while (!done) {
+				iRoom = rnd(IDI_WTP_MAX_ROOM_NORMAL);
+				done = true;
+
+				for (int j = 0; j < IDI_WTP_MAX_ROOM_OBJ; j++) {
+					if (_gameStateWinnie.iObjRoom[j] == iRoom) {
+						done = false;
+					}
+				}
+			}
+			_gameStateWinnie.iObjRoom[_gameStateWinnie.iUsedObj[i]] = iRoom;
+		}
+	}
+}
+
+void WinnieEngine::mist() {
+	// mist length in turns is (2-5)
+	_mist = rnd(4) + 2;
+
+	_room = IDI_WTP_ROOM_MIST;
+	drawRoomPic();
+
+	printStr(IDS_WTP_MIST);
+}
+
+void WinnieEngine::tigger() {
+	_room = IDI_WTP_ROOM_TIGGER;
+
+	drawRoomPic();
+	printStr(IDS_WTP_TIGGER);
+
+	dropObjRnd();
+}
+
+void WinnieEngine::showOwlHelp() {
+	if (_gameStateWinnie.iObjHave) {
+		printStr(IDS_WTP_OWL_0);
+		getSelection(kSelAnyKey);
+		printObjStr(_gameStateWinnie.iObjHave, IDI_WTP_OBJ_HELP);
+		getSelection(kSelAnyKey);
+	}
+	if (getObjInRoom(_room)) {
+		printStr(IDS_WTP_OWL_0);
+		getSelection(kSelAnyKey);
+		printObjStr(getObjInRoom(_room), IDI_WTP_OBJ_HELP);
+		getSelection(kSelAnyKey);
+	}
+}
+
+
+void WinnieEngine::drawMenu(char *szMenu, int iSel, int fCanSel[]) {
+	int iRow = 0, iCol = 0;
+
+	clearTextArea();
+	drawStr(IDI_WTP_ROW_MENU, IDI_WTP_COL_MENU, IDA_DEFAULT, szMenu);
+
+	if (fCanSel[IDI_WTP_SEL_NORTH])
+		drawStr(IDI_WTP_ROW_OPTION_4, IDI_WTP_COL_NSEW, IDA_DEFAULT, IDS_WTP_NSEW);
+	if (fCanSel[IDI_WTP_SEL_TAKE])
+		drawStr(IDI_WTP_ROW_OPTION_4, IDI_WTP_COL_TAKE, IDA_DEFAULT, IDS_WTP_TAKE);
+	if (fCanSel[IDI_WTP_SEL_DROP])
+		drawStr(IDI_WTP_ROW_OPTION_4, IDI_WTP_COL_DROP, IDA_DEFAULT, IDS_WTP_DROP);
+
+	switch (iSel) {
+	case IDI_WTP_SEL_OPT_1:
+	case IDI_WTP_SEL_OPT_2:
+	case IDI_WTP_SEL_OPT_3:
+		iRow = IDI_WTP_ROW_OPTION_1 + iSel;
+		iCol = IDI_WTP_COL_OPTION;
+		break;
+	case IDI_WTP_SEL_NORTH:
+		iRow = IDI_WTP_ROW_OPTION_4;
+		iCol = IDI_WTP_COL_NORTH;
+		break;
+	case IDI_WTP_SEL_SOUTH:
+		iRow = IDI_WTP_ROW_OPTION_4;
+		iCol = IDI_WTP_COL_SOUTH;
+		break;
+	case IDI_WTP_SEL_EAST:
+		iRow = IDI_WTP_ROW_OPTION_4;
+		iCol = IDI_WTP_COL_EAST;
+		break;
+	case IDI_WTP_SEL_WEST:
+		iRow = IDI_WTP_ROW_OPTION_4;
+		iCol = IDI_WTP_COL_WEST;
+		break;
+	case IDI_WTP_SEL_TAKE:
+		iRow = IDI_WTP_ROW_OPTION_4;
+		iCol = IDI_WTP_COL_TAKE;
+		break;
+	case IDI_WTP_SEL_DROP:
+		iRow = IDI_WTP_ROW_OPTION_4;
+		iCol = IDI_WTP_COL_DROP;
+		break;
+	default:
+		break;
+	}
+	drawStr(iRow, iCol - 1, IDA_DEFAULT, ">");
+	g_system->updateScreen();
+}
+
+void WinnieEngine::incMenuSel(int *iSel, int fCanSel[]) {
+	do {
+		*iSel += 1;
+		if (*iSel > IDI_WTP_SEL_DROP) *iSel = IDI_WTP_SEL_OPT_1;
+	} while (!fCanSel[*iSel]);
+}
+
+void WinnieEngine::decMenuSel(int *iSel, int fCanSel[]) {
+	do {
+		*iSel -= 1;
+		if (*iSel < IDI_WTP_SEL_OPT_1) *iSel = IDI_WTP_SEL_DROP;
+	} while (!fCanSel[*iSel]);
+}
+
+void WinnieEngine::getMenuMouseSel(int *iSel, int fCanSel[], int x, int y) {
+	switch (y) {
+	case IDI_WTP_ROW_OPTION_1:
+	case IDI_WTP_ROW_OPTION_2:
+	case IDI_WTP_ROW_OPTION_3:
+		if (fCanSel[y - IDI_WTP_ROW_OPTION_1])  *iSel = y - IDI_WTP_ROW_OPTION_1;
+		break;
+	case IDI_WTP_ROW_OPTION_4:
+		if (fCanSel[IDI_WTP_SEL_NORTH] && (x > IDI_WTP_COL_NORTH - 1) && (x < 6)) *iSel = IDI_WTP_SEL_NORTH;
+		if (fCanSel[IDI_WTP_SEL_SOUTH] && (x > IDI_WTP_COL_SOUTH - 1) && (x < 13)) *iSel = IDI_WTP_SEL_SOUTH;
+		if (fCanSel[IDI_WTP_SEL_EAST] && (x > IDI_WTP_COL_EAST - 1) && (x < 19)) *iSel = IDI_WTP_SEL_EAST;
+		if (fCanSel[IDI_WTP_SEL_WEST] && (x > IDI_WTP_COL_WEST - 1) && (x < 25)) *iSel = IDI_WTP_SEL_WEST;
+		if (fCanSel[IDI_WTP_SEL_TAKE] && (x > IDI_WTP_COL_TAKE - 1) && (x < 33)) *iSel = IDI_WTP_SEL_TAKE;
+		if (fCanSel[IDI_WTP_SEL_DROP] && (x > IDI_WTP_COL_DROP - 1) && (x < 39)) *iSel = IDI_WTP_SEL_DROP;
+		break;
+	default:
+		break;
+	}
+}
+
+void WinnieEngine::makeSel(int *iSel, int fCanSel[]) {
+	if (fCanSel[*iSel])
+		return;
+
+	keyHelp();
+	clrMenuSel(iSel, fCanSel);
+}
+
+void WinnieEngine::getMenuSel(char *szMenu, int *iSel, int fCanSel[]) {
+	Common::Event event;
+	int x, y;
+
+	clrMenuSel(iSel, fCanSel);
+	drawMenu(szMenu, *iSel, fCanSel);
+
+	// Show the mouse cursor for the menu
+	CursorMan.showMouse(true);
+
+	while (!shouldQuit()) {
+		while (_system->getEventManager()->pollEvent(event)) {
+			switch (event.type) {
+			case Common::EVENT_RETURN_TO_LAUNCHER:
+			case Common::EVENT_QUIT:
+				return;
+			case Common::EVENT_MOUSEMOVE:
+				x = event.mouse.x / 8;
+				y = event.mouse.y / 8;
+				getMenuMouseSel(iSel, fCanSel, x, y);
+
+				// Change cursor
+				if (fCanSel[IDI_WTP_SEL_NORTH] && hotspotNorth.contains(event.mouse.x, event.mouse.y)) {
+					//_gfx->setCursorPalette(true);
+					// ????
+				} else if (fCanSel[IDI_WTP_SEL_SOUTH] && hotspotSouth.contains(event.mouse.x, event.mouse.y)) {
+					//_gfx->setCursorPalette(true);
+				} else if (fCanSel[IDI_WTP_SEL_WEST] && hotspotWest.contains(event.mouse.x, event.mouse.y)) {
+					//_gfx->setCursorPalette(true);
+				} else if (fCanSel[IDI_WTP_SEL_EAST] && hotspotEast.contains(event.mouse.x, event.mouse.y)) {
+					//_gfx->setCursorPalette(true);
+				} else {
+					//_gfx->setCursorPalette(false);
+				}
+
+				break;
+			case Common::EVENT_LBUTTONUP:
+				// Click to move
+				if (fCanSel[IDI_WTP_SEL_NORTH] && hotspotNorth.contains(event.mouse.x, event.mouse.y)) {
+					*iSel = IDI_WTP_SEL_NORTH;
+					makeSel(iSel, fCanSel);
+					//_gfx->setCursorPalette(false);
+					// TODO???
+					return;
+				} else if (fCanSel[IDI_WTP_SEL_SOUTH] && hotspotSouth.contains(event.mouse.x, event.mouse.y)) {
+					*iSel = IDI_WTP_SEL_SOUTH;
+					makeSel(iSel, fCanSel);
+					//_gfx->setCursorPalette(false);
+					// TODO???
+					return;
+				} else if (fCanSel[IDI_WTP_SEL_WEST] && hotspotWest.contains(event.mouse.x, event.mouse.y)) {
+					*iSel = IDI_WTP_SEL_WEST;
+					makeSel(iSel, fCanSel);
+					//_gfx->setCursorPalette(false);
+					// TODO???
+					return;
+				} else if (fCanSel[IDI_WTP_SEL_EAST] && hotspotEast.contains(event.mouse.x, event.mouse.y)) {
+					*iSel = IDI_WTP_SEL_EAST;
+					makeSel(iSel, fCanSel);
+					//_gfx->setCursorPalette(false);
+					// TODO???
+					return;
+				} else {
+					//_gfx->setCursorPalette(false);
+					// TODO???
+				}
+
+				switch (*iSel) {
+				case IDI_WTP_SEL_OPT_1:
+				case IDI_WTP_SEL_OPT_2:
+				case IDI_WTP_SEL_OPT_3:
+					for (int iSel2 = 0; iSel2 < IDI_WTP_MAX_OPTION; iSel2++) {
+						if (*iSel == (fCanSel[iSel2 + IDI_WTP_SEL_REAL_OPT_1] - 1)) {
+							*iSel = iSel2;
+							// Menu selection made, hide the mouse cursor
+							CursorMan.showMouse(false);
+							return;
+						}
+					}
+					break;
+				default:
+					if (fCanSel[*iSel]) {
+						// Menu selection made, hide the mouse cursor
+						CursorMan.showMouse(false);
+						return;
+					}
+					break;
+				}
+				break;
+			case Common::EVENT_RBUTTONUP:
+				*iSel = IDI_WTP_SEL_BACK;
+				// Menu selection made, hide the mouse cursor
+				CursorMan.showMouse(false);
+				return;
+			case Common::EVENT_WHEELUP:
+				decMenuSel(iSel, fCanSel);
+				break;
+			case Common::EVENT_WHEELDOWN:
+				incMenuSel(iSel, fCanSel);
+				break;
+			case Common::EVENT_KEYDOWN:
+				switch (event.kbd.keycode) {
+				case Common::KEYCODE_ESCAPE:
+					*iSel = IDI_WTP_SEL_HOME;
+					// Menu selection made, hide the mouse cursor
+					CursorMan.showMouse(false);
+					return;
+				case Common::KEYCODE_BACKSPACE:
+					*iSel = IDI_WTP_SEL_BACK;
+					// Menu selection made, hide the mouse cursor
+					CursorMan.showMouse(false);
+					return;
+				case Common::KEYCODE_c:
+					inventory();
+					break;
+				case Common::KEYCODE_SPACE:
+				case Common::KEYCODE_RIGHT:
+				case Common::KEYCODE_DOWN:
+					incMenuSel(iSel, fCanSel);
+					break;
+				case Common::KEYCODE_LEFT:
+				case Common::KEYCODE_UP:
+					decMenuSel(iSel, fCanSel);
+					break;
+				case Common::KEYCODE_1:
+				case Common::KEYCODE_2:
+				case Common::KEYCODE_3:
+					*iSel = event.kbd.keycode - Common::KEYCODE_1;
+					if (fCanSel[*iSel + IDI_WTP_SEL_REAL_OPT_1]) {
+						// Menu selection made, hide the mouse cursor
+						CursorMan.showMouse(false);
+						return;
+					} else {
+						keyHelp();
+						clrMenuSel(iSel, fCanSel);
+					}
+					break;
+				case Common::KEYCODE_n:
+					*iSel = IDI_WTP_SEL_NORTH;
+					makeSel(iSel, fCanSel);
+					break;
+				case Common::KEYCODE_s:
+					if (event.kbd.flags & Common::KBD_CTRL) {
+						flipFlag(VM_FLAG_SOUND_ON);
+					} else {
+						*iSel = IDI_WTP_SEL_SOUTH;
+						makeSel(iSel, fCanSel);
+					}
+					break;
+				case Common::KEYCODE_e:
+					*iSel = IDI_WTP_SEL_EAST;
+					makeSel(iSel, fCanSel);
+					break;
+				case Common::KEYCODE_w:
+					*iSel = IDI_WTP_SEL_WEST;
+					makeSel(iSel, fCanSel);
+					break;
+				case Common::KEYCODE_t:
+					*iSel = IDI_WTP_SEL_TAKE;
+					makeSel(iSel, fCanSel);
+					break;
+				case Common::KEYCODE_d:
+					*iSel = IDI_WTP_SEL_DROP;
+					makeSel(iSel, fCanSel);
+					break;
+				case Common::KEYCODE_RETURN:
+					switch (*iSel) {
+					case IDI_WTP_SEL_OPT_1:
+					case IDI_WTP_SEL_OPT_2:
+					case IDI_WTP_SEL_OPT_3:
+						for (int iSel2 = 0; iSel2 < IDI_WTP_MAX_OPTION; iSel2++) {
+							if (*iSel == (fCanSel[iSel2 + IDI_WTP_SEL_REAL_OPT_1] - 1)) {
+								*iSel = iSel2;
+								// Menu selection made, hide the mouse cursor
+								CursorMan.showMouse(false);
+								return;
+							}
+						}
+						break;
+					default:
+						if (fCanSel[*iSel]) {
+							// Menu selection made, hide the mouse cursor
+							CursorMan.showMouse(false);
+							return;
+						}
+						break;
+					}
+					break;
+				default:
+					if (!event.kbd.flags) { // if the control/alt/shift keys are not pressed
+						keyHelp();
+						clrMenuSel(iSel, fCanSel);
+					}
+					break;
+				}
+				break;
+			default:
+				break;
+			}
+
+			drawMenu(szMenu, *iSel, fCanSel);
+		}
+	}
+}
+
+void WinnieEngine::gameLoop() {
+	WTP_ROOM_HDR hdr;
+	uint8 *roomdata = (uint8 *)malloc(4096);
+	int iBlock;
+	uint8 decodePhase = 0;
+
+	while (!shouldQuit()) {
+		if (decodePhase == 0) {
+			if (!_gameStateWinnie.nObjMiss && (_room == IDI_WTP_ROOM_PICNIC))
+				_room = IDI_WTP_ROOM_PARTY;
+
+			readRoom(_room, roomdata, hdr);
+			drawRoomPic();
+			g_system->updateScreen();
+			decodePhase = 1;
+		}
+
+		if (decodePhase == 1) {
+			if (getObjInRoom(_room)) {
+				printObjStr(getObjInRoom(_room), IDI_WTP_OBJ_DESC);
+				getSelection(kSelAnyKey);
+			}
+			decodePhase = 2;
+		}
+
+		if (decodePhase == 2) {
+			for (iBlock = 0; iBlock < IDI_WTP_MAX_BLOCK; iBlock++) {
+				if (parser(hdr.ofsDesc[iBlock] - _roomOffset, iBlock, roomdata) == IDI_WTP_PAR_BACK) {
+					decodePhase = 1;
+					break;
+				}
+			}
+			if (decodePhase == 2)
+				decodePhase = 3;
+		}
+
+		if (decodePhase == 3) {
+			for (iBlock = 0; iBlock < IDI_WTP_MAX_BLOCK; iBlock++) {
+				if (parser(hdr.ofsBlock[iBlock] - _roomOffset, iBlock, roomdata) == IDI_WTP_PAR_GOTO) {
+					decodePhase = 0;
+					break;
+				} else if (parser(hdr.ofsBlock[iBlock] - _roomOffset, iBlock, roomdata) == IDI_WTP_PAR_BACK) {
+					decodePhase = 2;
+					break;
+				}
+			}
+		}
+	}
+
+	free(roomdata);
+}
+
+void WinnieEngine::drawPic(const char *szName) {
+	Common::String fileName = szName;
+
+	if (getPlatform() != Common::kPlatformAmiga)
+		fileName += ".pic";
+	else
+		fileName = "misc/" + fileName;
+
+	Common::File file;
+
+	if (!file.open(fileName)) {
+		warning("Could not open file \'%s\'", fileName.c_str());
+		return;
+	}
+
+	uint8 *buffer = (uint8 *)malloc(4096);
+	uint32 size = file.size();
+	file.read(buffer, size);
+	file.close();
+
+	_picture->decodePicture(buffer, size, 1, IDI_WTP_PIC_WIDTH, IDI_WTP_PIC_HEIGHT);
+	_picture->showPic(IDI_WTP_PIC_X0, IDI_WTP_PIC_Y0, IDI_WTP_PIC_WIDTH, IDI_WTP_PIC_HEIGHT);
+
+	free(buffer);
+}
+
+void WinnieEngine::drawObjPic(int iObj, int x0, int y0) {
+	if (!iObj)
+		return;
+
+	WTP_OBJ_HDR objhdr;
+	uint8 *buffer = (uint8 *)malloc(2048);
+	uint32 objSize = readObj(iObj, buffer);
+	parseObjHeader(&objhdr, buffer, sizeof(WTP_OBJ_HDR));
+
+	_picture->setOffset(x0, y0);
+	_picture->decodePicture(buffer + objhdr.ofsPic - _objOffset, objSize, 0, IDI_WTP_PIC_WIDTH, IDI_WTP_PIC_HEIGHT);
+	_picture->setOffset(0, 0);
+	_picture->showPic(10, 0, IDI_WTP_PIC_WIDTH, IDI_WTP_PIC_HEIGHT);
+
+	free(buffer);
+}
+
+void WinnieEngine::drawRoomPic() {
+	WTP_ROOM_HDR roomhdr;
+	uint8 *buffer = (uint8 *)malloc(4096);
+	int iObj = getObjInRoom(_room);
+
+	// clear gfx screen
+	_gfx->clearDisplay(0);
+
+	// read room picture
+	readRoom(_room, buffer, roomhdr);
+
+	// draw room picture
+	_picture->decodePicture(buffer + roomhdr.ofsPic - _roomOffset, 4096, 1, IDI_WTP_PIC_WIDTH, IDI_WTP_PIC_HEIGHT);
+	_picture->showPic(IDI_WTP_PIC_X0, IDI_WTP_PIC_Y0, IDI_WTP_PIC_WIDTH, IDI_WTP_PIC_HEIGHT);
+
+	// draw object picture
+	drawObjPic(iObj, IDI_WTP_PIC_X0 + roomhdr.objX, IDI_WTP_PIC_Y0 + roomhdr.objY);
+
+	free(buffer);
+}
+
+bool WinnieEngine::playSound(ENUM_WTP_SOUND iSound) {
+	// TODO: Only DOS sound is supported, currently
+	if (getPlatform() != Common::kPlatformDOS) {
+		warning("STUB: playSound(%d)", iSound);
+		return false;
+	}
+
+	Common::String fileName = Common::String::format(IDS_WTP_SND_DOS, iSound);
+
+	Common::File file;
+	if (!file.open(fileName))
+		return false;
+
+	uint32 size = file.size();
+	byte *data = new byte[size];
+	file.read(data, size);
+	file.close();
+
+	_game.sounds[0] = AgiSound::createFromRawResource(data, size, 0, _soundemu);
+	_sound->startSound(0, 0);
+
+	bool cursorShowing = CursorMan.showMouse(false);
+	_system->updateScreen();
+
+	// Loop until the sound is done
+	bool skippedSound = false;
+	while (!shouldQuit() && _game.sounds[0]->isPlaying()) {
+		Common::Event event;
+		while (_system->getEventManager()->pollEvent(event)) {
+			switch (event.type) {
+			case Common::EVENT_KEYDOWN:
+				_sound->stopSound();
+				skippedSound = true;
+				break;
+			default:
+				break;
+			}
+		}
+
+		_system->delayMillis(10);
+	}
+
+	if (cursorShowing) {
+		CursorMan.showMouse(true);
+		_system->updateScreen();
+	}
+
+	delete _game.sounds[0];
+	_game.sounds[0] = nullptr;
+
+	return !shouldQuit() && !skippedSound;
+}
+
+void WinnieEngine::clrMenuSel(int *iSel, int fCanSel[]) {
+	*iSel = IDI_WTP_SEL_OPT_1;
+	while (!fCanSel[*iSel]) {
+		*iSel += 1;
+	}
+	//_gfx->setCursorPalette(false);
+	// TODO???
+}
+
+void WinnieEngine::printRoomStr(int iRoom, int iStr) {
+	WTP_ROOM_HDR hdr;
+	uint8 *buffer = (uint8 *)malloc(4096);
+
+	readRoom(iRoom, buffer, hdr);
+	printStrWinnie((char *)(buffer + hdr.ofsStr[iStr - 1] - _roomOffset));
+
+	free(buffer);
+}
+
+void WinnieEngine::gameOver() {
+	// sing the Pooh song forever
+	while (!shouldQuit()) {
+		printStr(IDS_WTP_SONG_0);
+		playSound(IDI_WTP_SND_POOH_0);
+		printStr(IDS_WTP_SONG_1);
+		playSound(IDI_WTP_SND_POOH_1);
+		printStr(IDS_WTP_SONG_2);
+		playSound(IDI_WTP_SND_POOH_2);
+		getSelection(kSelAnyKey);
+	}
+}
+
+void WinnieEngine::saveGame() {
+	int i = 0;
+
+	Common::OutSaveFile *outfile = getSaveFileMan()->openForSaving(IDS_WTP_FILE_SAVEGAME);
+
+	if (!outfile)
+		return;
+
+	outfile->writeUint32BE(MKTAG('W', 'I', 'N', 'N')); // header
+	outfile->writeByte(WTP_SAVEGAME_VERSION);
+
+	outfile->writeByte(_gameStateWinnie.fSound);
+	outfile->writeByte(_gameStateWinnie.nMoves);
+	outfile->writeByte(_gameStateWinnie.nObjMiss);
+	outfile->writeByte(_gameStateWinnie.nObjRet);
+	outfile->writeByte(_gameStateWinnie.iObjHave);
+
+	for (i = 0; i < IDI_WTP_MAX_FLAG; i++)
+		outfile->writeByte(_gameStateWinnie.fGame[i]);
+
+	for (i = 0; i < IDI_WTP_MAX_OBJ_MISSING; i++)
+		outfile->writeByte(_gameStateWinnie.iUsedObj[i]);
+
+	for (i = 0; i < IDI_WTP_MAX_ROOM_OBJ; i++)
+		outfile->writeByte(_gameStateWinnie.iObjRoom[i]);
+
+	outfile->finalize();
+
+	if (outfile->err())
+		warning("Can't write file '%s'. (Disk full?)", IDS_WTP_FILE_SAVEGAME);
+
+	delete outfile;
+}
+
+void WinnieEngine::loadGame() {
+	int saveVersion = 0;
+	int i = 0;
+
+	Common::InSaveFile *infile = getSaveFileMan()->openForLoading(IDS_WTP_FILE_SAVEGAME);
+
+	if (!infile)
+		return;
+
+	if (infile->readUint32BE() == MKTAG('W', 'I', 'N', 'N')) {
+		saveVersion = infile->readByte();
+		if (saveVersion != WTP_SAVEGAME_VERSION)
+			warning("Old save game version (%d, current version is %d). Will try and read anyway, but don't be surprised if bad things happen", saveVersion, WTP_SAVEGAME_VERSION);
+
+		_gameStateWinnie.fSound = infile->readByte();
+		_gameStateWinnie.nMoves = infile->readByte();
+		_gameStateWinnie.nObjMiss = infile->readByte();
+		_gameStateWinnie.nObjRet = infile->readByte();
+		_gameStateWinnie.iObjHave = infile->readByte();
+	} else {
+		// This is probably a save from the original interpreter, throw a warning and attempt
+		// to read it as LE
+		warning("No header found in save game, assuming it came from the original interpreter");
+		// Note that the original saves variables as 16-bit integers, but only 8 bits are used.
+		// Since we read the save file data as little-endian, we skip the first byte of each
+		// variable
+
+		infile->seek(0);                    // Jump back to the beginning of the file
+
+		infile->readUint16LE();             // skip unused field
+		infile->readByte();                 // first 8 bits of fSound
+		_gameStateWinnie.fSound = infile->readByte();
+		infile->readByte();                 // first 8 bits of nMoves
+		_gameStateWinnie.nMoves = infile->readByte();
+		infile->readByte();                 // first 8 bits of nObjMiss
+		_gameStateWinnie.nObjMiss = infile->readByte();
+		infile->readByte();                 // first 8 bits of nObjRet
+		_gameStateWinnie.nObjRet = infile->readByte();
+		infile->readUint16LE();             // skip unused field
+		infile->readUint16LE();             // skip unused field
+		infile->readUint16LE();             // skip unused field
+		infile->readByte();                 // first 8 bits of iObjHave
+		_gameStateWinnie.iObjHave = infile->readByte();
+		infile->readUint16LE();             // skip unused field
+		infile->readUint16LE();             // skip unused field
+		infile->readUint16LE();             // skip unused field
+	}
+
+	for (i = 0; i < IDI_WTP_MAX_FLAG; i++)
+		_gameStateWinnie.fGame[i] = infile->readByte();
+
+	for (i = 0; i < IDI_WTP_MAX_OBJ_MISSING; i++)
+		_gameStateWinnie.iUsedObj[i] = infile->readByte();
+
+	for (i = 0; i < IDI_WTP_MAX_ROOM_OBJ; i++)
+		_gameStateWinnie.iObjRoom[i] = infile->readByte();
+
+	// Note that saved games from the original interpreter have 2 more 16-bit fields here
+	// which are ignored
+
+	delete infile;
+}
+
+void WinnieEngine::printStrWinnie(char *szMsg) {
+	if (getPlatform() != Common::kPlatformAmiga)
+		printStrXOR(szMsg);
+	else
+		printStr(szMsg);
+}
+
+// Console-related functions
+
+void WinnieEngine::debugCurRoom() {
+	getDebugger()->debugPrintf("Current Room = %d\n", _room);
+}
+
+WinnieEngine::WinnieEngine(OSystem *syst, const AGIGameDescription *gameDesc) : PreAgiEngine(syst, gameDesc) {
+	setDebugger(new WinnieConsole(this));
+}
+
+WinnieEngine::~WinnieEngine() {
+}
+
+void WinnieEngine::init() {
+	// Initialize sound
+
+	switch (MidiDriver::getMusicType(MidiDriver::detectDevice(MDT_PCSPK | MDT_PCJR))) {
+	case MT_PCSPK:
+		_soundemu = SOUND_EMU_PC;
+		break;
+	case MT_PCJR:
+		_soundemu = SOUND_EMU_PCJR;
+		break;
+	default:
+		_soundemu = SOUND_EMU_NONE;
+		break;
+	}
+
+	_sound = new SoundMgr(this, _mixer);
+	setFlag(VM_FLAG_SOUND_ON, true); // enable sound
+
+	memset(&_gameStateWinnie, 0, sizeof(_gameStateWinnie));
+	_gameStateWinnie.fSound = 1;
+	_gameStateWinnie.nObjMiss = IDI_WTP_MAX_OBJ_MISSING;
+	_gameStateWinnie.nObjRet = 0;
+	_gameStateWinnie.fGame[0] = 1;
+	_gameStateWinnie.fGame[1] = 1;
+	_room = IDI_WTP_ROOM_HOME;
+
+	_mist = -1;
+	_doWind = false;
+	_winnieEvent = false;
+
+	if (getPlatform() != Common::kPlatformAmiga) {
+		_isBigEndian = false;
+		_roomOffset = IDI_WTP_OFS_ROOM;
+		_objOffset = IDI_WTP_OFS_OBJ;
+	} else {
+		_isBigEndian = true;
+		_roomOffset = 0;
+		_objOffset = 0;
+	}
+
+	if (getPlatform() == Common::kPlatformC64 || getPlatform() == Common::kPlatformApple2)
+		_picture->setPictureVersion(AGIPIC_C64);
+
+	hotspotNorth = Common::Rect(20, 0, (IDI_WTP_PIC_WIDTH + 10) * 2, 10);
+	hotspotSouth = Common::Rect(20, IDI_WTP_PIC_HEIGHT - 10, (IDI_WTP_PIC_WIDTH + 10) * 2, IDI_WTP_PIC_HEIGHT);
+	hotspotEast  = Common::Rect(IDI_WTP_PIC_WIDTH * 2, 0, (IDI_WTP_PIC_WIDTH + 10) * 2, IDI_WTP_PIC_HEIGHT);
+	hotspotWest  = Common::Rect(20, 0, 30, IDI_WTP_PIC_HEIGHT);
+}
+
+Common::Error WinnieEngine::go() {
+	init();
+	randomize();
+
+	// The intro is not supported on these platforms yet
+	if (getPlatform() != Common::kPlatformC64 && getPlatform() != Common::kPlatformApple2)
+		intro();
+
+	gameLoop();
+
+	return Common::kNoError;
+}
+
+} // End of namespace AGI
diff --git a/engines/agi/preagi/winnie.h b/engines/agi/preagi/winnie.h
new file mode 100644
index 00000000000..399f640b82b
--- /dev/null
+++ b/engines/agi/preagi/winnie.h
@@ -0,0 +1,355 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef AGI_PREAGI_WINNIE_H
+#define AGI_PREAGI_WINNIE_H
+
+namespace Agi {
+
+#define WTP_SAVEGAME_VERSION    1
+#define IDI_XOR_KEY             0x80
+
+// strings
+#define IDS_WTP_ROOM_DOS        "rooms/rm.%02d"
+#define IDS_WTP_ROOM_AMIGA      "rooms/room.%d"
+#define IDS_WTP_ROOM_C64        "room%02d"
+#define IDS_WTP_ROOM_APPLE      "room%d.obj"
+#define IDS_WTP_OBJ_DOS         "obj.%02d"
+#define IDS_WTP_OBJ_AMIGA       "objects/object.%d"
+#define IDS_WTP_OBJ_C64         "object%02d"
+#define IDS_WTP_OBJ_APPLE       "object%d.obj"
+#define IDS_WTP_SND_DOS         "snd.%02d"
+#define IDS_WTP_SND_AMIGA       "Sounds"
+#define IDS_WTP_SND_C64         "sound.obj"
+#define IDS_WTP_SND_APPLE       "sound.obj"
+
+#define IDS_WTP_FILE_LOGO       "logo"
+#define IDS_WTP_FILE_TITLE      "title"
+#define IDS_WTP_FILE_SAVEGAME   "savegame"
+#define IDS_WTP_FILE_RND        "rnd"
+
+#define IDS_WTP_DISK_ERROR      "There is a problem with your disk drive.Please make sure your Winnie-the-Pooh   disk is in the drive correctly."
+
+#define IDS_WTP_INTRO_0         "                 PRESENT"
+#define IDS_WTP_INTRO_1         "       TM designates trademark of\n          Sierra On-Line, Inc.\n    (c) 1985 Walt Disney Productions"
+
+#define IDS_WTP_HELP_0          "The <SPACE BAR> moves the pointer.      Press <RETURN> when it is by the choice you want.  Press the <Backspace> key to see what you just finished reading."
+#define IDS_WTP_HELP_1          "Press <C> to see what you are carrying. <Ctrl-S> turns the sound off and on.    <ESC> takes you to the playroom (in caseyou get lost or want to save the game)."
+
+#define IDS_WTP_GAME_OVER_0     "Congratulations!!  You did it!  You     returned everything that was lost.  Now,Christopher Robin invites you to a Hero party."
+#define IDS_WTP_GAME_OVER_1     "The good news is:  YOU are the Hero!!   The bad news is:  you have to find the  party by yourself.  Good luck!"
+
+#define IDS_WTP_OWL_0           "\"For example, that object you are       carrying now is interesting.  I know    I've seen it before.  Hmmm.  Let me     think about this . . .\""
+#define IDS_WTP_OWL_1           "\"You know, this object here beside me isfamiliar.  I'm sure I could give you    some sort of clue about it.             Let me see. . .\""
+
+#define IDS_WTP_WIND_0          "Oh, no!  The Blustery Wind begins to    howl.  It has returned, and mixed up    all the objects in the Wood."
+#define IDS_WTP_WIND_1          "But don't worry.  Everyone still has theobjects you returned to them.\n\n             (Today must be Winds-day!)"
+#define IDS_WTP_TIGGER          "\"Hallooooo, there!!!!  It's ME, Tigger! Let's BOUNCE!\""
+#define IDS_WTP_MIST            "Oh, look out!  The mysterious mist is   coming in.  It gets so thick that you   can't see through it.  Just keep walkingand it will soon clear up."
+
+#define IDS_WTP_SONG_0          "Winnie-the-Pooh, Winnie-the-Pooh, Tubby little cubby all stuffed with fluff,    He's Winnie-the-Pooh, Winnie-the-Pooh,  Willy, nilly, silly, old bear."
+#define IDS_WTP_SONG_1          "Deep in the Hundred Acre Wood,          Where Christopher Robin plays,          You will find the enchanted neighborhoodof Christopher's childhood days."
+#define IDS_WTP_SONG_2          "A donkey named Eeyore is his friend,    and Kanga and little Roo.  There's      Rabbit and Piglet and there's Owl       But most of all Winnie-the-Pooh!"
+
+#define IDS_WTP_NSEW            "North  South  East  West"
+#define IDS_WTP_TAKE            "Take"
+#define IDS_WTP_DROP            "Drop"
+#define IDS_WTP_CANT_GO         "\nSorry, but you can't go that way."
+#define IDS_WTP_CANT_TAKE       "You can't take it.  You can only carry  one object at a time."
+#define IDS_WTP_CANT_DROP       "You can't drop it.  Another object is   already here."
+#define IDS_WTP_WRONG_PLACE     "\nOk, but it doesn't belong here."
+#define IDS_WTP_OK              "\nOk."
+
+#define IDS_WTP_INVENTORY_0     "You are carrying nothing."
+#define IDS_WTP_INVENTORY_1     "Number of objects still missing:  %d"
+
+// COMMODORE 64 version strings
+
+#define IDS_WTP_FILE_SAVEGAME_C64   "saved game"
+#define IDS_WTP_DISK_ERROR_C64      "There is a problem with your disk drive.Please make sure your disk is in the    drive correctly."
+#define IDS_WTP_HELP_0_C64          "The <SPACE BAR> moves the pointer.      Press <RETURN> when it is by the choice you want.  <F1> brings back what you    have already read."
+#define IDS_WTP_HELP_1_C64          "<F3> takes you back to the playroom (if you get lost, or want to save the game).<F5> turns the sound off and on.        <F7> shows what you're carrying."
+#define IDS_WTP_WRONG_PLACE_C64 "\nOk, but this is not the right place."
+
+// maximum values
+
+#define IDI_WTP_MAX_OBJ_MISSING 10
+
+#define IDI_WTP_MAX_ROOM        62
+#define IDI_WTP_MAX_OBJ         40
+#define IDI_WTP_MAX_SND         14
+#define IDI_WTP_MAX_PIC         2
+
+#define IDI_WTP_MAX_ROOM_NORMAL         57
+#define IDI_WTP_MAX_ROOM_TELEPORT       30
+#define IDI_WTP_MAX_ROOM_OBJ            42
+#define IDI_WTP_MAX_BLOCK               4
+#define IDI_WTP_MAX_STR                 6
+#define IDI_WTP_MAX_OBJ_STR             4
+#define IDI_WTP_MAX_OBJ_STR_END         2
+#define IDI_WTP_MAX_FLAG                40
+#define IDI_WTP_MAX_OPTION              3
+#define IDI_WTP_MAX_DIR                 4
+#define IDI_WTP_MAX_MOVES_UNTIL_WIND    150
+
+// positions
+
+#define IDI_WTP_ROW_MENU        21
+#define IDI_WTP_ROW_OPTION_1    21
+#define IDI_WTP_ROW_OPTION_2    22
+#define IDI_WTP_ROW_OPTION_3    23
+#define IDI_WTP_ROW_OPTION_4    24
+
+#define IDI_WTP_COL_MENU        0
+#define IDI_WTP_COL_OPTION      1
+#define IDI_WTP_COL_NSEW        1
+#define IDI_WTP_COL_NORTH       1
+#define IDI_WTP_COL_SOUTH       8
+#define IDI_WTP_COL_EAST        15
+#define IDI_WTP_COL_WEST        21
+#define IDI_WTP_COL_TAKE        29
+#define IDI_WTP_COL_DROP        35
+#define IDI_WTP_COL_PRESENT     17
+
+// data file offset modifiers
+
+#define IDI_WTP_OFS_ROOM        0x5400
+#define IDI_WTP_OFS_OBJ         0x0800
+
+// picture
+
+#define IDI_WTP_PIC_WIDTH   140
+#define IDI_WTP_PIC_HEIGHT  159
+#define IDI_WTP_PIC_X0      10
+#define IDI_WTP_PIC_Y0      0
+#define IDI_WTP_PIC_FLAGS   IDF_AGI_PIC_V2
+
+// selections
+
+enum {
+	IDI_WTP_SEL_HOME = -2,
+	IDI_WTP_SEL_BACK,
+	IDI_WTP_SEL_OPT_1,
+	IDI_WTP_SEL_OPT_2,
+	IDI_WTP_SEL_OPT_3,
+	IDI_WTP_SEL_NORTH,
+	IDI_WTP_SEL_SOUTH,
+	IDI_WTP_SEL_EAST,
+	IDI_WTP_SEL_WEST,
+	IDI_WTP_SEL_TAKE,
+	IDI_WTP_SEL_DROP,
+	IDI_WTP_SEL_REAL_OPT_1,
+	IDI_WTP_SEL_REAL_OPT_2,
+	IDI_WTP_SEL_REAL_OPT_3
+};
+
+#define IDI_WTP_SEL_LAST    IDI_WTP_SEL_REAL_OPT_3
+
+// rooms
+
+enum {
+	IDI_WTP_ROOM_NONE = -1,
+	IDI_WTP_ROOM_NORTH,
+	IDI_WTP_ROOM_SOUTH,
+	IDI_WTP_ROOM_EAST,
+	IDI_WTP_ROOM_WEST
+};
+
+#define IDI_WTP_ROOM_HIDE   0
+
+#define IDI_WTP_ROOM_PICNIC 2
+#define IDI_WTP_ROOM_HOME   28
+#define IDI_WTP_ROOM_PARTY  58
+#define IDI_WTP_ROOM_MIST   59
+#define IDI_WTP_ROOM_TIGGER 61
+
+// sound
+
+enum ENUM_WTP_SOUND {
+	IDI_WTP_SND_POOH_0 = 1,
+	IDI_WTP_SND_TIGGER,
+	IDI_WTP_SND_TAKE,
+	IDI_WTP_SND_DROP,
+	IDI_WTP_SND_DROP_OK,
+	IDI_WTP_SND_FANFARE,
+	IDI_WTP_SND_POOH_1,
+	IDI_WTP_SND_KEYHELP,
+	IDI_WTP_SND_POOH_2,
+	IDI_WTP_SND_WIND_0,
+	IDI_WTP_SND_WIND_1
+};
+
+// script opcodes
+
+#define IDO_WTP_GOTO_ROOM   0x06
+#define IDO_WTP_PRINT_MSG   0x08
+#define IDO_WTP_PRINT_STR   0x0A
+#define IDO_WTP_DROP_OBJ    0x0C
+#define IDO_WTP_FLAG_CLEAR  0x0E
+#define IDO_WTP_FLAG_SET    0x10
+#define IDO_WTP_GAME_OVER   0x12
+#define IDO_WTP_WALK_MIST   0x14
+#define IDO_WTP_PLAY_SOUND  0x16
+#define IDO_WTP_SAVE_GAME   0x18
+#define IDO_WTP_LOAD_GAME   0x1A
+#define IDO_WTP_OWL_HELP    0x1C
+#define IDO_WTP_GOTO_RND    0x1E
+
+#define IDO_WTP_OPTION_0    0x15
+#define IDO_WTP_OPTION_1    0x16
+#define IDO_WTP_OPTION_2    0x17
+
+enum {
+	IDI_WTP_OBJ_DESC = 0,
+	IDI_WTP_OBJ_TAKE,
+	IDI_WTP_OBJ_DROP,
+	IDI_WTP_OBJ_HELP
+};
+
+enum {
+	IDI_WTP_PAR_OK = 0,
+	IDI_WTP_PAR_GOTO,
+	IDI_WTP_PAR_BACK
+};
+
+// room file option block
+
+struct WTP_ROOM_BLOCK {
+	uint16  ofsOpt[IDI_WTP_MAX_BLOCK];
+};
+
+// room file header
+
+struct WTP_ROOM_HDR {
+	uint8   roomNumber;
+	uint8   objId;
+	uint16  ofsPic;
+	uint16  fileLen;
+	uint16  reserved0;
+	int8    roomNew[IDI_WTP_MAX_DIR];
+	uint8   objX;
+	uint8   objY;
+	uint16  reserved1;
+	uint16  ofsDesc[IDI_WTP_MAX_BLOCK];
+	uint16  ofsBlock[IDI_WTP_MAX_BLOCK];
+	uint16  ofsStr[IDI_WTP_MAX_STR];
+	uint32  reserved2;
+	WTP_ROOM_BLOCK  opt[IDI_WTP_MAX_BLOCK];
+};
+
+// object file header
+
+struct WTP_OBJ_HDR {
+	uint16  fileLen;
+	uint16  objId;
+	uint16  ofsEndStr[IDI_WTP_MAX_OBJ_STR_END];
+	uint16  ofsStr[IDI_WTP_MAX_OBJ_STR];
+	uint16  ofsPic;
+};
+
+// savegame
+
+struct WTP_SAVE_GAME {
+	uint8   fSound;
+	uint8   nMoves;
+	uint8   nObjMiss;
+	uint8   nObjRet;
+	uint8   iObjHave;
+	uint8   fGame[IDI_WTP_MAX_FLAG];
+	uint8   iUsedObj[IDI_WTP_MAX_OBJ_MISSING];
+	uint8   iObjRoom[IDI_WTP_MAX_ROOM_OBJ];
+};
+
+class PreAgiEngine;
+
+class WinnieEngine : public PreAgiEngine {
+public:
+	WinnieEngine(OSystem *syst, const AGIGameDescription *gameDesc);
+	~WinnieEngine() override;
+
+	void init();
+	Common::Error go() override;
+
+	void debugCurRoom();
+
+private:
+	WTP_SAVE_GAME _gameStateWinnie;
+	int _room;
+	int _mist;
+	bool _doWind;
+	bool _winnieEvent;
+	int _tiggerMist;
+
+	int _roomOffset;
+	int _objOffset;
+	bool _isBigEndian;
+	Common::Rect hotspotNorth, hotspotSouth, hotspotEast, hotspotWest;
+
+	void randomize();
+	void intro();
+	void drawPic(const char *);
+	void gameLoop();
+
+	void parseRoomHeader(WTP_ROOM_HDR *roomHdr, byte *buffer, int len);
+	void parseObjHeader(WTP_OBJ_HDR *objHdr, byte *buffer, int len);
+	uint32 readRoom(int, uint8 *, WTP_ROOM_HDR &);
+
+	void drawRoomPic();
+	int parser(int, int, uint8 *);
+	int getObjInRoom(int);
+	bool getSelOkBack();
+	void getMenuSel(char *, int *, int[]);
+	void keyHelp();
+	void clrMenuSel(int *, int[]);
+	void incMenuSel(int *, int[]);
+	void decMenuSel(int *, int[]);
+	void drawMenu(char *, int, int[]);
+	void printRoomStr(int, int);
+	void inventory();
+	void printObjStr(int, int);
+	uint32 readObj(int, uint8 *);
+	void takeObj(int);
+	void dropObj(int);
+	bool isRightObj(int, int, int *);
+	void drawObjPic(int, int, int);
+	void getMenuMouseSel(int *, int[], int, int);
+	void setWinnieFlag(int);
+	void clearWinnieFlag(int);
+	void gameOver();
+	void saveGame();
+	void loadGame();
+	void dropObjRnd();
+	void setTakeDrop(int[]);
+	void makeSel(int *, int[]);
+
+	void wind();
+	void mist();
+	void tigger();
+
+	void showOwlHelp();
+	bool playSound(ENUM_WTP_SOUND);
+
+	void printStrWinnie(char *szMsg);
+};
+
+} // End of namespace Agi
+
+#endif




More information about the Scummvm-git-logs mailing list