[Scummvm-git-logs] scummvm master -> 74aab598c031b9ed536492cf95fc62c9a4065b34

npjg noreply at scummvm.org
Wed Jan 1 17:16:07 UTC 2025


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:
74aab598c0 MEDIASTATION: Add initial engine prototype that can play a couple opening movies.


Commit: 74aab598c031b9ed536492cf95fc62c9a4065b34
    https://github.com/scummvm/scummvm/commit/74aab598c031b9ed536492cf95fc62c9a4065b34
Author: Nathanael Gentry (nathanael.gentrydb8 at gmail.com)
Date: 2025-01-01T12:15:42-05:00

Commit Message:
MEDIASTATION: Add initial engine prototype that can play a couple opening movies.

This engine is very incomplete, as rendering and sound playback are not handled well at all yet. However, most of the data structures in this engine are handled.

Changed paths:
  A engines/mediastation/POTFILES
  A engines/mediastation/asset.cpp
  A engines/mediastation/asset.h
  A engines/mediastation/assetheader.cpp
  A engines/mediastation/assetheader.h
  A engines/mediastation/assets/canvas.cpp
  A engines/mediastation/assets/canvas.h
  A engines/mediastation/assets/hotspot.cpp
  A engines/mediastation/assets/hotspot.h
  A engines/mediastation/assets/image.cpp
  A engines/mediastation/assets/image.h
  A engines/mediastation/assets/movie.cpp
  A engines/mediastation/assets/movie.h
  A engines/mediastation/assets/palette.cpp
  A engines/mediastation/assets/palette.h
  A engines/mediastation/assets/path.cpp
  A engines/mediastation/assets/path.h
  A engines/mediastation/assets/sound.cpp
  A engines/mediastation/assets/sound.h
  A engines/mediastation/assets/sprite.cpp
  A engines/mediastation/assets/sprite.h
  A engines/mediastation/assets/timer.cpp
  A engines/mediastation/assets/timer.h
  A engines/mediastation/bitmap.cpp
  A engines/mediastation/bitmap.h
  A engines/mediastation/boot.cpp
  A engines/mediastation/boot.h
  A engines/mediastation/chunk.cpp
  A engines/mediastation/chunk.h
  A engines/mediastation/configure.engine
  A engines/mediastation/context.cpp
  A engines/mediastation/context.h
  A engines/mediastation/contextparameters.cpp
  A engines/mediastation/contextparameters.h
  A engines/mediastation/credits.pl
  A engines/mediastation/datafile.cpp
  A engines/mediastation/datafile.h
  A engines/mediastation/datum.cpp
  A engines/mediastation/datum.h
  A engines/mediastation/debugchannels.h
  A engines/mediastation/detection.cpp
  A engines/mediastation/detection.h
  A engines/mediastation/detection_tables.h
  A engines/mediastation/mediascript/builtins.h
  A engines/mediastation/mediascript/codechunk.cpp
  A engines/mediastation/mediascript/codechunk.h
  A engines/mediastation/mediascript/eventhandler.cpp
  A engines/mediastation/mediascript/eventhandler.h
  A engines/mediastation/mediascript/function.cpp
  A engines/mediastation/mediascript/function.h
  A engines/mediastation/mediascript/operand.cpp
  A engines/mediastation/mediascript/operand.h
  A engines/mediastation/mediascript/variable.cpp
  A engines/mediastation/mediascript/variable.h
  A engines/mediastation/mediastation.cpp
  A engines/mediastation/mediastation.h
  A engines/mediastation/metaengine.cpp
  A engines/mediastation/metaengine.h
  A engines/mediastation/module.mk
  A engines/mediastation/subfile.cpp
  A engines/mediastation/subfile.h


diff --git a/engines/mediastation/POTFILES b/engines/mediastation/POTFILES
new file mode 100644
index 00000000000..f6bdee92b39
--- /dev/null
+++ b/engines/mediastation/POTFILES
@@ -0,0 +1 @@
+engines/mediastation/metaengine.cpp
diff --git a/engines/mediastation/asset.cpp b/engines/mediastation/asset.cpp
new file mode 100644
index 00000000000..39848548310
--- /dev/null
+++ b/engines/mediastation/asset.cpp
@@ -0,0 +1,48 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "mediastation/asset.h"
+#include "mediastation/assetheader.h"
+
+namespace MediaStation {
+
+Asset::~Asset() {
+	delete _header;
+	_header = nullptr;
+}
+
+void Asset::readChunk(Chunk &chunk) {
+	error("Asset::readChunk(): Chunk reading for asset type 0x%x is not implemented", _header->_type);
+}
+
+void Asset::readSubfile(Subfile &subfile, Chunk &chunk) {
+	error("Asset::readSubfile(): Subfile reading for asset type 0x%x is not implemented", _header->_type);
+}
+
+AssetType Asset::type() const {
+	return _header->_type;
+}
+
+uint Asset::zIndex() const {
+	return _header->_zIndex;
+}
+
+} // End of namespace MediaStation
diff --git a/engines/mediastation/asset.h b/engines/mediastation/asset.h
new file mode 100644
index 00000000000..08b6e7a0f41
--- /dev/null
+++ b/engines/mediastation/asset.h
@@ -0,0 +1,77 @@
+/* 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 MEDIASTATION_ASSET_H
+#define MEDIASTATION_ASSET_H
+
+#include "common/func.h"
+
+#include "mediastation/subfile.h"
+#include "mediastation/chunk.h"
+#include "mediastation/mediascript/builtins.h"
+#include "mediastation/mediascript/operand.h"
+
+namespace MediaStation {
+
+enum class AssetType;
+class AssetHeader;
+
+class Asset {
+public:
+	Asset(AssetHeader *header) : _header(header) {};
+	virtual ~Asset();
+
+	// Does any needed frame drawing, audio playing, event handlers, etc.
+	virtual void process() {
+		return;
+	}
+	// Runs built-in bytecode methods.
+	virtual Operand callMethod(BuiltInMethod methodId, Common::Array<Operand> &args) = 0;
+	// Called to have the asset do any processing, like drawing new frames,
+	// handling time-based event handlers, and such. Some assets don't have any
+	// processing to do.
+	virtual bool isPlaying() const {
+		return _isPlaying;
+	}
+
+	// These are not pure virtual so if an asset doesnʻt read any chunks or
+	// subfiles it doesnʻt need to just implement these with an error message.
+	virtual void readChunk(Chunk &chunk);
+	virtual void readSubfile(Subfile &subfile, Chunk &chunk);
+
+	AssetType type() const;
+	uint zIndex() const;
+	AssetHeader *getHeader() const {
+		return _header;
+	}
+
+protected:
+	AssetHeader *_header = nullptr;
+	bool _isPlaying = false;
+	uint _startTime = 0;
+	uint _lastProcessedTime = 0;
+	// TODO: Rename this to indicate the time is in milliseconds.
+	uint _duration = 0;
+};
+
+} // End of namespace MediaStation
+
+#endif
\ No newline at end of file
diff --git a/engines/mediastation/assetheader.cpp b/engines/mediastation/assetheader.cpp
new file mode 100644
index 00000000000..aefb80ce90e
--- /dev/null
+++ b/engines/mediastation/assetheader.cpp
@@ -0,0 +1,265 @@
+/* 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 "mediastation/datum.h"
+#include "mediastation/assetheader.h"
+#include "mediastation/debugchannels.h"
+
+namespace MediaStation {
+
+AssetHeader::AssetHeader(Chunk &chunk) {
+	// I arbitrarily chose the bitmap as the default union member,
+	// but they are all pointers so it doesn't matter.
+	_fileNumber = Datum(chunk).u.i;
+	// TODO: Cast to an asset type.
+	_type = AssetType(Datum(chunk).u.i);
+	_id = Datum(chunk).u.i;
+	debugC(4, kDebugLoading, "AssetHeader::AssetHeader(): _type = 0x%x, _id = 0x%x (@0x%llx)", _type, _id, chunk.pos());
+
+	AssetHeader::SectionType sectionType = getSectionType(chunk);
+	bool moreSectionsToRead = (AssetHeader::SectionType::EMPTY != sectionType);
+	while (moreSectionsToRead) {
+		readSection(sectionType, chunk);
+		sectionType = getSectionType(chunk);
+		moreSectionsToRead = (AssetHeader::SectionType::EMPTY != sectionType);
+	}
+}
+
+AssetHeader::~AssetHeader() {
+	delete _boundingBox;
+	delete _mouseActiveArea;
+	delete _palette;
+	delete _name;
+	delete _startPoint;
+	delete _endPoint;
+}
+
+void AssetHeader::readSection(AssetHeader::SectionType sectionType, Chunk& chunk) {
+	debugC(5, kDebugLoading, "AssetHeader::AssetHeader(): sectionType = 0x%x (@0x%llx)", sectionType, chunk.pos());
+	switch (sectionType) {
+	case AssetHeader::SectionType::EMPTY: {
+		break;
+	}
+
+	case AssetHeader::SectionType::EVENT_HANDLER: {
+		EventHandler *eventHandler = new EventHandler(chunk);
+		switch (eventHandler->_type) {
+		case EventHandler::Type::Time: {
+			_timeHandlers.push_back(eventHandler);
+			break;
+		}
+
+		case EventHandler::Type::KeyDown: {
+			_keyDownHandlers.push_back(eventHandler);
+			break;
+		}
+
+		case EventHandler::Type::Input: {
+			_inputHandlers.push_back(eventHandler);
+			break;
+		}
+
+		case EventHandler::Type::LoadComplete: {
+			_loadCompleteHandlers.push_back(eventHandler);
+			break;
+		}
+
+		default: {
+			if (eventHandler->_argumentType != EventHandler::ArgumentType::Null && eventHandler->_argumentType != EventHandler::ArgumentType::Unk1) {
+				error("AssetHeader::readSection(): Event handler of type %d has a non-null argument type %d", eventHandler->_type, eventHandler->_argumentType);
+			}
+
+			if (_eventHandlers.contains(eventHandler->_type)) {
+				error("AssetHeader::readSection(): Event handler type %d already exists", eventHandler->_type);
+			} else {
+				_eventHandlers.setVal(eventHandler->_type, eventHandler);
+			}
+			break;
+		}
+		}
+		break;
+	}
+
+	case AssetHeader::SectionType::STAGE_ID: {
+		_stageId = Datum(chunk).u.i;
+		break;
+	}
+
+	case AssetHeader::SectionType::ASSET_ID: {
+		// We already have this asset's ID, so we will just verify it is the same
+		// as the ID we have already read.
+		uint32 duplicateAssetId = Datum(chunk).u.i;
+		if (duplicateAssetId != _id) {
+			warning("AssetHeader::readSection(): AssetHeader ID %d does not match original asset ID %d", duplicateAssetId, _id);
+		}
+		break;
+	}
+
+	case AssetHeader::SectionType::CHUNK_REFERENCE: {
+		// These are references to the chunk(s) that hold the data for this asset.
+		// The references and the chunks have the following format "a501".
+		// There is no guarantee where these chunk(s) might actually be located:
+		//  - They might be in the same RIFF subfile as this header,
+		//  - They might be in a different RIFF subfile in the same CXT file,
+		//  - They might be in a different CXT file entirely.
+		_chunkReference = Datum(chunk, DatumType::REFERENCE).u.i;
+		break;
+	}
+
+	case AssetHeader::SectionType::MOVIE_AUDIO_CHUNK_REFERENCE: {
+		_audioChunkReference = Datum(chunk, DatumType::REFERENCE).u.i;
+		break;
+	}
+
+	case AssetHeader::SectionType::MOVIE_ANIMATION_CHUNK_REFERENCE: {
+		_animationChunkReference = Datum(chunk, DatumType::REFERENCE).u.i;
+		break;
+	}
+
+	case AssetHeader::SectionType::BOUNDING_BOX: {
+		_boundingBox = Datum(chunk, DatumType::BOUNDING_BOX).u.bbox;
+		break;
+	}
+
+	case AssetHeader::SectionType::MOUSE_ACTIVE_AREA: {
+		_mouseActiveArea = Datum(chunk, DatumType::POLYGON).u.polygon;
+		break;
+	}
+
+	case AssetHeader::SectionType::Z_INDEX: {
+		_zIndex = Datum(chunk).u.i;
+		break;
+	}
+
+	case AssetHeader::SectionType::ASSET_REFERENCE: {
+		_assetReference = Datum(chunk).u.i;
+		break;
+	}
+
+	case AssetHeader::SectionType::STARTUP: {
+		_startup = Datum(chunk).u.i;
+		break;
+	}
+
+	case AssetHeader::SectionType::TRANSPARENCY: {
+		_transparency = Datum(chunk).u.i;
+		break;
+	}
+
+	case AssetHeader::SectionType::HAS_OWN_SUBFILE: {
+		_hasOwnSubfile = Datum(chunk).u.i;
+		break;
+	}
+
+	case AssetHeader::SectionType::CURSOR_RESOURCE_ID: {
+		_cursorResourceId = Datum(chunk).u.i;
+		break;
+	}
+
+	case AssetHeader::SectionType::FRAME_RATE: {
+		_frameRate = Datum(chunk).u.i;
+		break;
+	}
+
+	case AssetHeader::SectionType::LOAD_TYPE: {
+		_loadType = Datum(chunk).u.i;
+		break;
+	}
+
+	case AssetHeader::SectionType::SOUND_INFO: {
+		_totalChunks = Datum(chunk).u.i;
+		_rate = Datum(chunk).u.i;
+		break;
+	}
+
+	case AssetHeader::SectionType::MOVIE_LOAD_TYPE: {
+		_loadType = Datum(chunk).u.i;
+		break;
+	}
+
+	case AssetHeader::SectionType::GET_OFFSTAGE_EVENTS: {
+		_getOffstageEvents = Datum(chunk).u.i;
+		break;
+	}
+
+	case AssetHeader::SectionType::PALETTE: {
+		// TODO: Avoid the copying here!
+		const uint PALETTE_ENTRIES = 256;
+		const uint PALETTE_BYTES = PALETTE_ENTRIES * 3;
+		byte* buffer = new byte[PALETTE_BYTES];
+		chunk.read(buffer, PALETTE_BYTES);
+		_palette = new Graphics::Palette(buffer, PALETTE_ENTRIES);
+		delete[] buffer;
+		break;
+	}
+
+	case AssetHeader::SectionType::DISSOLVE_FACTOR: {
+		_dissolveFactor = Datum(chunk).u.i;
+		break;
+	}
+
+	case AssetHeader::SectionType::SOUND_ENCODING_1:
+	case AssetHeader::SectionType::SOUND_ENCODING_2: {
+		_soundEncoding = SoundEncoding(Datum(chunk).u.i);
+		break;
+	}
+
+	case AssetHeader::SectionType::SPRITE_CHUNK_COUNT: {
+		_chunkCount = Datum(chunk).u.i;
+		break;
+	}
+
+	case AssetHeader::SectionType::START_POINT: {
+		_startPoint = Datum(chunk, DatumType::POINT_2).u.point;
+		break;
+	}
+
+	case AssetHeader::SectionType::END_POINT: {
+		_endPoint = Datum(chunk, DatumType::POINT_2).u.point;
+		break;
+	}
+
+	case AssetHeader::SectionType::STEP_RATE: {
+		_stepRate = (uint32)(Datum(chunk, DatumType::FLOAT64_2).u.f);
+		break;
+	}
+
+	case AssetHeader::SectionType::DURATION: {
+		// These are stored in the file as fractional seconds,
+		// but we want milliseconds.
+		_duration = (uint32)(Datum(chunk).u.f * 1000);
+		break;
+	}
+
+	default: {
+		error("AssetHeader::readSection(): Unknown section type 0x%x (@0x%llx)", sectionType, chunk.pos());
+		break;
+	}
+	}
+}
+
+AssetHeader::SectionType AssetHeader::getSectionType(Chunk &chunk) {
+	Datum datum = Datum(chunk, DatumType::UINT16_1);
+	AssetHeader::SectionType sectionType = static_cast<AssetHeader::SectionType>(datum.u.i);
+	return sectionType;
+}
+
+} // end of namespace MediaStation
\ No newline at end of file
diff --git a/engines/mediastation/assetheader.h b/engines/mediastation/assetheader.h
new file mode 100644
index 00000000000..f8ffe3eb41d
--- /dev/null
+++ b/engines/mediastation/assetheader.h
@@ -0,0 +1,202 @@
+/* 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 MEDIASTATION_ASSET_HEADER_H
+#define MEDIASTATION_ASSET_HEADER_H
+
+#include "common/hashmap.h"
+#include "common/str.h"
+#include "graphics/palette.h"
+
+#include "mediastation/chunk.h"
+#include "mediastation/mediascript/eventhandler.h"
+
+// Specialize the Common::Hash template for EventHandler::Type and ArgumentType
+namespace Common {
+
+template <>
+struct Hash<MediaStation::EventHandler::Type> {
+	uint operator()(const MediaStation::EventHandler::Type &key) const {
+		return static_cast<uint>(key);
+	}
+};
+
+template <>
+struct Hash<MediaStation::EventHandler::ArgumentType> {
+	uint operator()(const MediaStation::EventHandler::ArgumentType &key) const {
+		return static_cast<uint>(key);
+	}
+};
+
+} // End of namespace Common
+
+namespace MediaStation {
+
+struct MovieChunkReference {
+	uint32 headerChunkId;
+	uint32 audioChunkId;
+	uint32 videoChunkId;
+};
+
+typedef uint32 ChunkReference;
+
+enum class AssetType {
+	SCREEN  = 0x0001, // SCR
+	STAGE  = 0x0002, // STG
+	PATH  = 0x0004, // PTH
+	SOUND  = 0x0005, // SND
+	TIMER  = 0x0006, // TMR
+	IMAGE  = 0x0007, // IMG
+	HOTSPOT  = 0x000b, // HSP
+	SPRITE  = 0x000e, // SPR
+	LK_ZAZU = 0x000f,
+	LK_CONSTELLATIONS = 0x0010,
+	IMAGE_SET = 0x001d,
+	CURSOR  = 0x000c, // CSR
+	PRINTER  = 0x0019, // PRT
+	MOVIE  = 0x0016, // MOV
+	PALETTE  = 0x0017,
+	TEXT  = 0x001a, // TXT
+	FONT  = 0x001b, // FON
+	CAMERA  = 0x001c, // CAM
+	CANVAS  = 0x001e, // CVS
+	// TODO: Discover how the XSND differs from regular sounds.
+	// Only appears in Ariel.
+	XSND = 0x001f,
+	XSND_MIDI = 0x0020,
+	// TODO: Figure out what this is. Only appears in Ariel.
+	RECORDER = 0x0021,
+	FUNCTION  = 0x0069 // FUN
+};
+
+typedef uint32 AssetId;
+
+class AssetHeader {
+public:
+	enum class SectionType {
+		EMPTY = 0x0000,
+		SOUND_ENCODING_1 = 0x0001,
+		SOUND_ENCODING_2 = 0x0002,
+		EVENT_HANDLER = 0x0017,
+		STAGE_ID = 0x0019,
+		ASSET_ID = 0x001a,
+		CHUNK_REFERENCE = 0x001b,
+		MOVIE_ANIMATION_CHUNK_REFERENCE = 0x06a4,
+		MOVIE_AUDIO_CHUNK_REFERENCE = 0x06a5,
+		ASSET_REFERENCE = 0x077b,
+		BOUNDING_BOX = 0x001c,
+		MOUSE_ACTIVE_AREA = 0x001d,
+		Z_INDEX = 0x001e,
+		STARTUP = 0x001f,
+		TRANSPARENCY = 0x0020,
+		HAS_OWN_SUBFILE = 0x0021,
+		CURSOR_RESOURCE_ID = 0x0022,
+		FRAME_RATE = 0x0024,
+		LOAD_TYPE = 0x0032,
+		SOUND_INFO = 0x0033,
+		MOVIE_LOAD_TYPE = 0x0037,
+		SPRITE_CHUNK_COUNT = 0x03e8,
+
+		PALETTE = 0x05aa,
+		DISSOLVE_FACTOR = 0x05dc,
+		GET_OFFSTAGE_EVENTS = 0x05dd,
+		X = 0x05de,
+		Y = 0x05df,
+
+		// PATH FIELDS.
+		START_POINT = 0x060e,
+		END_POINT = 0x060f,
+		PATH_UNK1 = 0x0610,
+		STEP_RATE = 0x0611,
+		DURATION = 0x0612,
+
+		// CAMERA FIELDS.
+		VIEWPORT_ORIGIN = 0x076f,
+		LENS_OPEN = 0x770,
+
+		// STAGE FIELDS.
+		STAGE_UNK1 = 0x771,
+		CYLINDRICAL_X = 0x772,
+		CYLINDRICAL_Y = 0x773,
+
+		ASSET_NAME = 0x0bb8,
+	};
+
+	enum class SoundEncoding {
+		PCM_S16LE_MONO_22050 = 0x0010, // Uncompressed linear PCM
+		IMA_ADPCM_S16LE_MONO_22050 = 0x0004 // IMA ADPCM encoding, must be decoded
+	};
+
+	AssetHeader(Chunk &chunk);
+	~AssetHeader();
+
+	uint32 _fileNumber = 0;
+	AssetType _type;
+	AssetId _id;
+
+	ChunkReference _chunkReference = 0;
+	// These two are only used in movies.
+	ChunkReference _audioChunkReference = 0;
+	ChunkReference _animationChunkReference = 0;
+	Common::Rect *_boundingBox = nullptr;
+	Common::Array<Common::Point *> *_mouseActiveArea = nullptr;
+	uint32 _zIndex = 0;
+	uint32 _assetReference = 0;
+	uint32 _startup = 0;
+	bool _transparency = false;
+	bool _hasOwnSubfile = false;
+	uint32 _cursorResourceId = 0;
+	uint32 _frameRate = 10; // This is the default for sprites, which are the only ones that use this field.
+	uint32 _loadType = 0;
+	uint32 _totalChunks = 0;
+	uint32 _rate = 0;
+	bool _editable = 0;
+	Graphics::Palette *_palette = nullptr;
+	bool _getOffstageEvents = 0;
+	uint32 _x = 0; // Image only.
+	uint32 _y = 0; // Image only.
+	Common::String *_name = nullptr;
+	uint32 _stageId = 0;
+	SoundEncoding _soundEncoding;
+	uint32 _chunkCount = 0;
+
+	// PATH FIELDS.
+	uint32 _dissolveFactor = 0;
+	Common::Point *_startPoint = nullptr;
+	Common::Point *_endPoint = nullptr;
+	uint32 _stepRate = 0;
+	uint32 _duration = 0;
+
+	// EVENT HANDLER FIELDS.
+	Common::HashMap<EventHandler::Type, EventHandler *> _eventHandlers;
+	Common::Array<EventHandler *> _timeHandlers;
+	Common::Array<EventHandler *> _keyDownHandlers;
+	Common::Array<EventHandler *> _inputHandlers;
+	Common::Array<EventHandler *> _loadCompleteHandlers;
+
+private:
+	void readSection(AssetHeader::SectionType sectionType, Chunk &chunk);
+	AssetHeader::SectionType getSectionType(Chunk &chunk);
+};
+
+} // End of namespace MediaStation
+
+#endif
\ No newline at end of file
diff --git a/engines/mediastation/assets/canvas.cpp b/engines/mediastation/assets/canvas.cpp
new file mode 100644
index 00000000000..a945d172805
--- /dev/null
+++ b/engines/mediastation/assets/canvas.cpp
@@ -0,0 +1,38 @@
+/* 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 "mediastation/assets/canvas.h"
+
+namespace MediaStation {
+
+Operand Canvas::callMethod(BuiltInMethod methodId, Common::Array<Operand> &args) {
+	switch (methodId) {
+	case BuiltInMethod::clearToPalette: {
+		error("Canvas::callMethod(): BuiltInFunction::clearToPalette is not implemented yet");
+	}
+
+	default: {
+		error("Got unimplemented method ID %d", methodId);
+	}
+	}
+}
+
+} // End of namespace MediaStation
diff --git a/engines/mediastation/assets/canvas.h b/engines/mediastation/assets/canvas.h
new file mode 100644
index 00000000000..029a1e015a3
--- /dev/null
+++ b/engines/mediastation/assets/canvas.h
@@ -0,0 +1,41 @@
+/* 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 MEDIASTATION_CANVAS_H
+#define MEDIASTATION_CANVAS_H
+
+#include "mediastation/asset.h"
+#include "mediastation/assetheader.h"
+#include "mediastation/mediascript/operand.h"
+
+namespace MediaStation {
+
+class Canvas : public Asset {
+public:
+	Canvas(AssetHeader *header) : Asset(header) {};
+	virtual ~Canvas() override = default;
+
+	virtual Operand callMethod(BuiltInMethod methodId, Common::Array<Operand> &args) override;
+};
+
+} // End of namespace MediaStation
+
+#endif
\ No newline at end of file
diff --git a/engines/mediastation/assets/hotspot.cpp b/engines/mediastation/assets/hotspot.cpp
new file mode 100644
index 00000000000..6f181a3c292
--- /dev/null
+++ b/engines/mediastation/assets/hotspot.cpp
@@ -0,0 +1,46 @@
+/* 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 "mediastation/assets/hotspot.h"
+
+namespace MediaStation {
+
+Operand Hotspot::callMethod(BuiltInMethod methodId, Common::Array<Operand> &args) {
+	switch (methodId) {
+	case BuiltInMethod::mouseActivate: {
+		assert(args.empty());
+		warning("Hotspot::callMethod(): BuiltInFunction::mouseActivate is not implemented");
+		return Operand();
+	}
+
+	case BuiltInMethod::mouseDeactivate: {
+		assert(args.empty());
+		warning("Hotspot::callMethod(): BuiltInFunction::mouseDeactivate is not implemented");
+		return Operand();
+	}
+
+	default: {
+		error("Got unimplemented method ID %d", methodId);
+	}
+	}
+}
+
+} // End of namespace MediaStation
diff --git a/engines/mediastation/assets/hotspot.h b/engines/mediastation/assets/hotspot.h
new file mode 100644
index 00000000000..93cc89a84a3
--- /dev/null
+++ b/engines/mediastation/assets/hotspot.h
@@ -0,0 +1,41 @@
+/* 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 MEDIASTATION_HOTSPOT_H
+#define MEDIASTATION_HOTSPOT_H
+
+#include "mediastation/asset.h"
+#include "mediastation/assetheader.h"
+#include "mediastation/mediascript/operand.h"
+
+namespace MediaStation {
+
+class Hotspot : public Asset {
+public:
+	Hotspot(AssetHeader *header) : Asset(header) {};
+	virtual ~Hotspot() override = default;
+
+	virtual Operand callMethod(BuiltInMethod methodId, Common::Array<Operand> &args) override;
+};
+
+} // End of namespace MediaStation
+
+#endif
\ No newline at end of file
diff --git a/engines/mediastation/assets/image.cpp b/engines/mediastation/assets/image.cpp
new file mode 100644
index 00000000000..24eee8c5ef0
--- /dev/null
+++ b/engines/mediastation/assets/image.cpp
@@ -0,0 +1,46 @@
+/* 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 "mediastation/assets/image.h"
+
+namespace MediaStation {
+
+Image::~Image() {
+	delete _bitmap;
+	_bitmap = nullptr;
+}
+
+Operand Image::callMethod(BuiltInMethod methodId, Common::Array<Operand> &args) {
+	switch (methodId) {
+	// TODO: Add methods here.
+
+	default: {
+		error("Got unimplemented method ID %d", methodId);
+	}
+	}
+}
+
+void Image::readChunk(Chunk &chunk) {
+	BitmapHeader *bitmapHeader = new BitmapHeader(chunk);
+	_bitmap = new Bitmap(chunk, bitmapHeader);
+}
+
+} // End of namespace MediaStation
diff --git a/engines/mediastation/assets/image.h b/engines/mediastation/assets/image.h
new file mode 100644
index 00000000000..efaa4b7e56d
--- /dev/null
+++ b/engines/mediastation/assets/image.h
@@ -0,0 +1,51 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "graphics/managed_surface.h"
+
+#include "mediastation/asset.h"
+#include "mediastation/chunk.h"
+#include "mediastation/subfile.h"
+#include "mediastation/bitmap.h"
+#include "mediastation/assetheader.h"
+#include "mediastation/mediascript/operand.h"
+
+#ifndef MEDIASTATION_IMAGE_H
+#define MEDIASTATION_IMAGE_H
+
+namespace MediaStation {
+
+class Image : public Asset {
+public:
+	Image(AssetHeader *header) : Asset(header) {};
+	virtual ~Image() override;
+
+	virtual void readChunk(Chunk &chunk) override;
+
+	virtual Operand callMethod(BuiltInMethod methodId, Common::Array<Operand> &args) override;
+
+private:
+	Bitmap *_bitmap = nullptr;
+};
+
+} // End of namespace MediaStation
+
+#endif
\ No newline at end of file
diff --git a/engines/mediastation/assets/movie.cpp b/engines/mediastation/assets/movie.cpp
new file mode 100644
index 00000000000..8723357e07e
--- /dev/null
+++ b/engines/mediastation/assets/movie.cpp
@@ -0,0 +1,463 @@
+/* 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/decoders/raw.h"
+#include "audio/decoders/adpcm.h"
+
+#include "mediastation/mediastation.h"
+#include "mediastation/assets/movie.h"
+#include "mediastation/datum.h"
+#include "mediastation/debugchannels.h"
+
+namespace MediaStation {
+
+MovieFrameHeader::MovieFrameHeader(Chunk &chunk) : BitmapHeader(chunk) {
+	_index = Datum(chunk).u.i;
+	debugC(5, kDebugLoading, "MovieFrameHeader::MovieFrameHeader(): _index = 0x%x (@0x%llx)", _index, chunk.pos());
+	_keyframeEndInMilliseconds = Datum(chunk).u.i;
+}
+
+MovieFrameFooter::MovieFrameFooter(Chunk &chunk) {
+	_unk1 = Datum(chunk).u.i;
+	_unk2 = Datum(chunk).u.i;
+	if (g_engine->isFirstGenerationEngine()) { // TODO: Add the version number check.
+		_startInMilliseconds = Datum(chunk).u.i;
+		_endInMilliseconds = Datum(chunk).u.i;
+		_left = Datum(chunk).u.i;
+		_top = Datum(chunk).u.i;
+		_unk3 = Datum(chunk).u.i;
+		_unk4 = Datum(chunk).u.i;
+		_index = Datum(chunk).u.i;
+	} else {
+		_unk4 = Datum(chunk).u.i;
+		_startInMilliseconds = Datum(chunk).u.i;
+		_endInMilliseconds = Datum(chunk).u.i;
+		_left = Datum(chunk).u.i;
+		_top = Datum(chunk).u.i;
+		_zIndex = Datum(chunk).u.i;
+		_unk6 = Datum(chunk).u.i;
+		_unk7 = Datum(chunk).u.i;
+		_index = Datum(chunk).u.i;
+		_unk8 = Datum(chunk).u.i;
+		_unk9 = Datum(chunk).u.i;
+		debugC(5, kDebugLoading, "MovieFrameFooter::MovieFrameFooter(): _startInMilliseconds = 0x%x, _endInMilliseconds = 0x%x, _left = 0x%x, _top = 0x%x, _index = 0x%x (@0x%llx)", _startInMilliseconds, _endInMilliseconds, _left, _top, _index, chunk.pos());
+		debugC(5, kDebugLoading, "MovieFrameFooter::MovieFrameFooter(): _unk4 = 0x%x, _unk5 = 0x%x, _unk6 = 0x%x, _unk7 = 0x%x, _unk8 = 0x%x, _unk9 = 0x%x", _unk4, _zIndex, _unk6, _unk7, _unk8, _unk9);
+	}
+}
+
+MovieFrame::MovieFrame(Chunk &chunk, MovieFrameHeader *header) :
+	Bitmap(chunk, header),
+	_footer(nullptr),
+	_showing(false) {
+	_bitmapHeader = header;
+}
+
+void MovieFrame::setFooter(MovieFrameFooter *footer) {
+	if (footer != nullptr) {
+		assert(footer->_index == _bitmapHeader->_index);
+	}
+	_footer = footer;
+}
+
+uint32 MovieFrame::left() {
+	if (_footer != nullptr) {
+		return _footer->_left;
+	} else {
+		error("MovieFrame::left(): Cannot get the left coordinate of a keyframe");
+	}
+}
+
+uint32 MovieFrame::top() {
+	if (_footer != nullptr) {
+		return _footer->_top;
+	} else {
+		error("MovieFrame::left(): Cannot get the top coordinate of a keyframe");
+	}
+}
+
+Common::Point MovieFrame::topLeft() {
+	if (_footer != nullptr) {
+		return Common::Point(_footer->_left, _footer->_top);
+	} else {
+		error("MovieFrame::topLeft(): Cannot get the top-left coordinate of a keyframe");
+	}
+}
+
+Common::Rect MovieFrame::boundingBox() {
+	if (_footer != nullptr) {
+		return Common::Rect(Common::Point(_footer->_left, _footer->_top), width(), height());
+	} else {
+		error("MovieFrame::boundingBox(): Cannot get the bounding box of a keyframe");
+	}
+}
+
+uint32 MovieFrame::index() {
+	if (_footer != nullptr) {
+		return _footer->_index;
+	} else {
+		error("MovieFrame::index(): Cannot get the index of a keyframe");
+	}
+}
+
+uint32 MovieFrame::startInMilliseconds() {
+	if (_footer != nullptr) {
+		return _footer->_startInMilliseconds;
+	} else {
+		error("MovieFrame::startInMilliseconds(): Cannot get the start time of a keyframe");
+	}
+}
+
+uint32 MovieFrame::endInMilliseconds() {
+	if (_footer != nullptr) {
+		return _footer->_endInMilliseconds;
+	} else {
+		error("MovieFrame::endInMilliseconds(): Cannot get the end time of a keyframe");
+	}
+}
+
+uint32 MovieFrame::zCoordinate() {
+	if (_footer != nullptr) {
+		return _footer->_zIndex;
+	} else {
+		error("MovieFrame::zCoordinate(): Cannot get the z-coordinate of a keyframe");
+	}
+}
+
+uint32 MovieFrame::keyframeEndInMilliseconds() {
+	return _bitmapHeader->_keyframeEndInMilliseconds;
+}
+
+MovieFrame::~MovieFrame() {
+	// The base class destructor takes care of deleting the bitmap header, so 
+	// we don't need to delete that here.
+	delete _footer;
+	_footer = nullptr;
+}
+
+Movie::~Movie() {
+	for (MovieFrame *frame : _stills) {
+		delete frame;
+	}
+	_stills.clear();
+	for (MovieFrame *frame : _frames) {
+		delete frame;
+	}
+	_frames.clear();
+	_audioStreams.clear();
+	for (MovieFrameFooter *footer : _footers) {
+		delete footer;
+	}
+	_footers.clear();
+}
+
+Operand Movie::callMethod(BuiltInMethod methodId, Common::Array<Operand> &args) {
+	switch (methodId) {
+	case BuiltInMethod::timePlay: {
+		assert(args.empty());
+		timePlay();
+		return Operand();
+	}
+
+	default: {
+		error("Got unimplemented method ID %d", methodId);
+	}
+	}
+}
+
+void Movie::timePlay() {
+	debugC(5, kDebugScript, "Called Movie::timePlay()");
+	// TODO: Play movies one chunk at a time, which more directly approximates
+	// the original's reading from the CD one chunk at a time.
+	if (_isPlaying) {
+		error("Movie::play(): Attempted to play a movie that is already playing");
+		return;
+	}
+
+	// SET ANIMATION VARIABLES.
+	_isPlaying = true;
+	_startTime = g_system->getMillis();
+	_lastProcessedTime = 0;
+	g_engine->addPlayingAsset(this);
+
+	// GET THE DURATION OF THE MOVIE.
+	_duration = 0;
+	for (MovieFrame *frame : _frames) {
+		if (frame->endInMilliseconds() > _duration) {
+			_duration = frame->endInMilliseconds();
+		}
+	}
+
+	// START PLAYING SOUND.
+	// TODO: This won't work when we have some chunks that don't have audio.
+	if (!_audioStreams.empty()) {
+		Audio::QueuingAudioStream *audio = Audio::makeQueuingAudioStream(22050, false);
+		for (Audio::SeekableAudioStream *stream : _audioStreams) {
+			audio->queueAudioStream(stream);
+		}
+		// Then play the audio!
+		Audio::SoundHandle handle;
+		g_engine->_mixer->playStream(Audio::Mixer::kPlainSoundType, &handle, audio, -1, Audio::Mixer::kMaxChannelVolume);
+	}
+
+	// RUN THE MOVIE START EVENT HANDLER.
+	EventHandler *startEvent = _header->_eventHandlers.getValOrDefault(EventHandler::Type::MovieBegin);
+	if (startEvent != nullptr) {
+		debugC(5, kDebugScript, "Movie::timePlay(): Executing movie start event handler");
+		startEvent->execute(_header->_id);
+	} else {
+		debugC(5, kDebugScript, "Movie::timePlay(): No movie start event handler");
+	}
+}
+
+void Movie::timeStop() {
+	// RESET ANIMATION VARIABLES.
+	_isPlaying = false;
+	_startTime = 0;
+	_lastProcessedTime = 0;
+
+	// RUN THE MOVIE STOPPED EVENT HANDLER.
+	EventHandler *endEvent = _header->_eventHandlers.getValOrDefault(EventHandler::Type::MovieStopped);
+	if (endEvent != nullptr) {
+		debugC(5, kDebugScript, "Movie::play(): Executing movie stopped event handler");
+		endEvent->execute(_header->_id);
+	} else {
+		debugC(5, kDebugScript, "Movie::timePlay(): No movie stopped event handler");
+	}
+}
+
+void Movie::process() {
+	processTimeEventHandlers();
+	drawNextFrame();
+}
+
+void Movie::processTimeEventHandlers() {
+	if (!_isPlaying) {
+		warning("Movie::processTimeEventHandlers(): Attempted to process time event handlers while movie is not playing");
+		return;
+	}
+
+	uint currentTime = g_system->getMillis();
+	for (EventHandler *timeEvent : _header->_timeHandlers) {
+		double timeEventInFractionalSeconds = timeEvent->_argumentValue.u.f;
+		uint timeEventInMilliseconds = timeEventInFractionalSeconds * 1000;
+		bool timeEventAlreadyProcessed = timeEventInMilliseconds < _lastProcessedTime;
+		bool timeEventNeedsToBeProcessed = timeEventInMilliseconds <= currentTime - _startTime;
+		if (!timeEventAlreadyProcessed && timeEventNeedsToBeProcessed) {
+			debugC(5, kDebugScript, "Movie::processTimeEventHandlers(): Running On Time handler for movie time %d ms (real movie time: %d ms)", timeEventInMilliseconds, currentTime - _startTime);
+			timeEvent->execute(_header->_id);
+		}
+	}
+	_lastProcessedTime = currentTime - _startTime;
+}
+
+bool Movie::drawNextFrame() {
+	// TODO: We'll need to support persistent frames in movies too. Do movies
+	// have the same distinction between spatialShow and timePlay that sprites
+	// do?
+	
+	uint currentTime = g_system->getMillis();
+	uint movieTime = currentTime - _startTime;
+	debugC(5, kDebugGraphics, "GRAPHICS (Movie %d): Starting blitting (movie time: %d)", _header->_id, movieTime);
+	bool donePlaying = movieTime > _duration;
+	if (donePlaying) {
+		_isPlaying = false;
+		_startTime = 0;
+		_lastProcessedTime = 0;
+
+		// Run the movie end event handler.
+		EventHandler *endEvent = _header->_eventHandlers.getValOrDefault(EventHandler::Type::MovieEnd);
+		if (endEvent != nullptr) {
+			debugC(5, kDebugScript, "Movie::drawNextFrame(): Executing movie end event handler");
+			endEvent->execute(_header->_id);
+		} else {
+			debugC(5, kDebugScript, "Movie::drawNextFrame(): No movie end event handler");
+		}
+		return false;
+	}
+
+	Common::Array<MovieFrame *> framesToDraw;
+	for (MovieFrame *frame : _frames) {
+		bool isAfterStart = _startTime + frame->startInMilliseconds() <= currentTime;
+		bool isBeforeEnd = _startTime + frame->endInMilliseconds() >= currentTime;
+		if (!isAfterStart || (isAfterStart && !isBeforeEnd)) {
+			continue;
+		}
+		debugC(5, kDebugGraphics, "    (time: %d ms) Must re-draw frame %d (%d x %d) @ (%d, %d); start: %d ms, end: %d ms, keyframeEnd: %d ms, zIndex = %d", movieTime, frame->index(), frame->width(), frame->height(), frame->left(), frame->top(), frame->startInMilliseconds(), frame->endInMilliseconds(), frame->keyframeEndInMilliseconds(), frame->zCoordinate());
+		framesToDraw.push_back(frame);
+	}
+
+	Common::sort(framesToDraw.begin(), framesToDraw.end(), [](MovieFrame * a, MovieFrame * b) {
+		return a->zCoordinate() > b->zCoordinate();
+	});
+	for (MovieFrame *frame : framesToDraw) {
+		g_engine->_screen->transBlitFrom(frame->surface, Common::Point(frame->left(), frame->top()), 0, false);
+	}
+
+	uint blitEnd = g_system->getMillis() - _startTime;
+	uint elapsedTime = blitEnd - movieTime;
+	debugC(5, kDebugGraphics, "GRAPHICS (Movie %d): Finished blitting in %d ms (movie time: %d ms)", _header->_id, elapsedTime, blitEnd);
+	return true;
+}
+
+void Movie::readChunk(Chunk &chunk) {
+	// Individual chunks are "stills" and are stored in the first subfile.
+	uint sectionType = Datum(chunk).u.i;
+	switch ((SectionType)sectionType) {
+	case SectionType::FRAME: {
+		debugC(5, kDebugLoading, "Movie::readStill(): Reading frame");
+		MovieFrameHeader *header = new MovieFrameHeader(chunk);
+		MovieFrame *frame = new MovieFrame(chunk, header);
+		_stills.push_back(frame);
+		break;
+	}
+
+	case SectionType::FOOTER: {
+		debugC(5, kDebugLoading, "Movie::readStill(): Reading footer");
+		MovieFrameFooter *footer = new MovieFrameFooter(chunk);
+		_footers.push_back(footer);
+		break;
+	}
+
+	default: {
+		error("Unknown movie still section type");
+	}
+	}
+}
+
+void Movie::readSubfile(Subfile &subfile, Chunk &chunk) {
+	// READ THE METADATA FOR THE WHOLE MOVIE.
+	uint expectedRootSectionType = Datum(chunk).u.i;
+	debugC(5, kDebugLoading, "Movie::readSubfile(): sectionType = 0x%x (@0x%llx)", expectedRootSectionType, chunk.pos());
+	if (Movie::SectionType::ROOT != (Movie::SectionType)expectedRootSectionType) {
+		error("Expected ROOT section type, got 0x%x", expectedRootSectionType);
+	}
+	uint chunkCount = Datum(chunk).u.i;
+	debugC(5, kDebugLoading, "Movie::readSubfile(): chunkCount = 0x%x (@0x%llx)", chunkCount, chunk.pos());
+
+	uint dataStartOffset = Datum(chunk).u.i;
+	debugC(5, kDebugLoading, "Movie::readSubfile(): dataStartOffset = 0x%x (@0x%llx)", dataStartOffset, chunk.pos());
+
+	Common::Array<uint> chunkLengths;
+	for (uint i = 0; i < chunkCount; i++) {
+		uint chunkLength = Datum(chunk).u.i;
+		debugC(5, kDebugLoading, "Movie::readSubfile(): chunkLength = 0x%x (@0x%llx)", chunkLength, chunk.pos());
+		chunkLengths.push_back(chunkLength);
+	}
+
+	// RAD THE INTERLEAVED AUDIO AND ANIMATION DATA.
+	for (uint i = 0; i < chunkCount; i++) {
+		debugC(5, kDebugLoading, "\nMovie::readSubfile(): Reading frameset %d of %d in subfile (@0x%llx)", i, chunkCount, chunk.pos());
+		chunk = subfile.nextChunk();
+
+		// READ ALL THE FRAMES IN THIS CHUNK.
+		debugC(5, kDebugLoading, "Movie::readSubfile(): (Frameset %d of %d) Reading animation chunks... (@0x%llx)", i, chunkCount, chunk.pos());
+		bool isAnimationChunk = (chunk.id == _header->_animationChunkReference);
+		if (!isAnimationChunk) {
+			warning("Movie::readSubfile(): (Frameset %d of %d) No animation chunks found (@0x%llx)", i, chunkCount, chunk.pos());
+		}
+		MovieFrameHeader *header = nullptr;
+		MovieFrame *frame = nullptr;
+		while (isAnimationChunk) {
+			uint sectionType = Datum(chunk).u.i;
+			debugC(5, kDebugLoading, "Movie::readSubfile(): sectionType = 0x%x (@0x%llx)", sectionType, chunk.pos());
+			switch (Movie::SectionType(sectionType)) {
+			case Movie::SectionType::FRAME: {
+				header = new MovieFrameHeader(chunk);
+				frame = new MovieFrame(chunk, header);
+				_frames.push_back(frame);
+				break;
+			}
+
+			case Movie::SectionType::FOOTER: {
+				MovieFrameFooter *footer = new MovieFrameFooter(chunk);
+				// _footers.push_back(footer);
+				// TODO: This does NOT handle the case where there are
+				// keyframes. We need to match the footer to an arbitrary
+				// frame, since some keyframes don't have footers, sigh.
+				if (header == nullptr) {
+					error("Movie::readSubfile(): No frame to match footer to");
+				}
+				if (header->_index == footer->_index) {
+					frame->setFooter(footer);
+				} else {
+					error("Movie::readSubfile(): Footer index does not match frame index: %d != %d", header->_index, footer->_index);
+				}
+				break;
+			}
+
+			default: {
+				error("Movie::readSubfile(): Unknown movie animation section type 0x%x (@0x%llx)", sectionType, chunk.pos());
+			}
+			}
+
+			// READ THE NEXT CHUNK.
+			chunk = subfile.nextChunk();
+			isAnimationChunk = (chunk.id == _header->_animationChunkReference);
+		}
+
+		// READ THE AUDIO.
+		debugC(5, kDebugLoading, "Movie::readSubfile(): (Frameset %d of %d) Reading audio chunk... (@0x%llx)", i, chunkCount, chunk.pos());
+		bool isAudioChunk = (chunk.id = _header->_audioChunkReference);
+		if (isAudioChunk) {
+			byte *buffer = (byte *)malloc(chunk.length);
+			chunk.read((void *)buffer, chunk.length);
+			Audio::SeekableAudioStream *stream = nullptr;
+			switch (_header->_soundEncoding) {
+			case AssetHeader::SoundEncoding::PCM_S16LE_MONO_22050:
+				stream = Audio::makeRawStream(buffer, chunk.length, 22050, Audio::FLAG_16BITS | Audio::FLAG_LITTLE_ENDIAN, DisposeAfterUse::YES);
+				break;
+
+			case AssetHeader::SoundEncoding::IMA_ADPCM_S16LE_MONO_22050:
+				// TODO: The interface here is different. We can't pass in the
+				// buffers directly. We have to make a stream first.
+				// stream = Audio::makeADPCMStream(buffer, chunk.length,
+				// DisposeAfterUse::NO, Audio::ADPCMType::kADPCMMSIma, 22050, 1,
+				// 4);
+				error("Movie::readSubfile(): ADPCM decoding not implemented yet");
+				break;
+
+			default:
+				error("Sound::readChunk(): Unknown audio encoding 0x%x", (uint)_header->_soundEncoding);
+			}
+			_audioStreams.push_back(stream);
+			chunk = subfile.nextChunk();
+		} else {
+			debugC(5, kDebugLoading, "Movie::readSubfile(): (Frameset %d of %d) No audio chunk to read. (@0x%llx)", i, chunkCount, chunk.pos());
+		}
+
+		// READ THE FOOTER FOR THIS SUBFILE.
+		debugC(5, kDebugLoading, "Movie::readSubfile(): (Frameset %d of %d) Reading header chunk... (@0x%llx)", i, chunkCount, chunk.pos());
+		bool isHeaderChunk = (chunk.id == _header->_chunkReference);
+		if (isHeaderChunk) {
+			if (chunk.length != 0x04) {
+				error("Movie::readSubfile(): Expected movie header chunk of size 0x04, got 0x%x (@0x%llx)", chunk.length, chunk.pos());
+			}
+			chunk.skip(chunk.length);
+		} else {
+			error("Movie::readSubfile(): Expected header chunk, got %s (@0x%llx)", tag2str(chunk.id), chunk.pos());
+		}
+	}
+
+	// SET THE MOVIE FRAME FOOTERS.
+	// TODO: We donʻt do anything with this yet!
+}
+
+} // End of namespace MediaStation
\ No newline at end of file
diff --git a/engines/mediastation/assets/movie.h b/engines/mediastation/assets/movie.h
new file mode 100644
index 00000000000..32e548d7569
--- /dev/null
+++ b/engines/mediastation/assets/movie.h
@@ -0,0 +1,121 @@
+/* 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/audiostream.h"
+
+#include "mediastation/assetheader.h"
+#include "mediastation/bitmap.h"
+#include "mediastation/mediascript/builtins.h"
+
+#ifndef MEDIASTATION_MOVIE_H
+#define MEDIASTATION_MOVIE_H
+
+namespace MediaStation {
+
+class MovieFrameHeader : public BitmapHeader {
+public:
+	MovieFrameHeader(Chunk &chunk);
+
+	uint _index;
+	uint _keyframeEndInMilliseconds;
+};
+
+class MovieFrameFooter {
+public:
+	MovieFrameFooter(Chunk &chunk);
+
+	uint _unk1;
+	uint _unk2;
+	uint _startInMilliseconds;
+	uint _endInMilliseconds;
+	uint _left;
+	uint _top;
+	uint _unk3;
+	uint _unk4;
+	uint _zIndex; // TODO: This is still unconfirmed but seems likely.
+	uint _unk6;
+	uint _unk7;
+	uint _unk8;
+	uint _unk9;
+	uint _index;
+};
+
+class MovieFrame : public Bitmap {
+public:
+	MovieFrame(Chunk &chunk, MovieFrameHeader *header);
+	~MovieFrame();
+
+	void setFooter(MovieFrameFooter *footer);
+	uint32 left();
+	uint32 top();
+	Common::Point topLeft();
+	Common::Rect boundingBox();
+	uint32 index();
+	uint32 startInMilliseconds();
+	uint32 endInMilliseconds();
+	uint32 keyframeEndInMilliseconds();
+	// This is called zCoordinate because zIndex is too close to "index" and
+	// that could be confusing.
+	uint32 zCoordinate();
+
+	bool _showing;
+
+private:
+	MovieFrameHeader *_bitmapHeader;
+	MovieFrameFooter *_footer;
+};
+
+class Movie : public Asset {
+private:
+	enum class SectionType {
+		ROOT = 0x06a8,
+		FRAME = 0x06a9,
+		FOOTER = 0x06aa
+	};
+
+public:
+	Movie(AssetHeader *header) : Asset(header) {};
+	virtual ~Movie() override;
+
+	virtual void readChunk(Chunk &chunk) override;
+	virtual void readSubfile(Subfile &subfile, Chunk &chunk) override;
+
+	virtual Operand callMethod(BuiltInMethod methodId, Common::Array<Operand> &args) override;
+	virtual void process() override;
+
+private:
+	Common::Array<MovieFrame *> _frames;
+	Common::Array<MovieFrame *> _stills;
+	Common::Array<MovieFrameFooter *> _footers;
+	Common::Array<Audio::SeekableAudioStream *> _audioStreams;
+
+	// Method implementations. These should be called from callMethod.
+	void timePlay();
+	void timeStop();
+
+	// Internal helper functions.
+	bool drawNextFrame();
+	void processTimeEventHandlers();
+};
+
+} // End of namespace MediaStation
+
+#endif
\ No newline at end of file
diff --git a/engines/mediastation/assets/palette.cpp b/engines/mediastation/assets/palette.cpp
new file mode 100644
index 00000000000..37a417a6950
--- /dev/null
+++ b/engines/mediastation/assets/palette.cpp
@@ -0,0 +1,37 @@
+/* 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 "mediastation/mediastation.h"
+#include "mediastation/assets/palette.h"
+#include "mediastation/debugchannels.h"
+
+namespace MediaStation {
+
+Operand Palette::callMethod(BuiltInMethod methodId, Common::Array<Operand> &args) {
+	switch (methodId) {
+	default: {
+		error("Got unimplemented method ID %d", methodId);
+	}
+	}
+}
+
+} // End of namespace MediaStation
+
diff --git a/engines/mediastation/assets/palette.h b/engines/mediastation/assets/palette.h
new file mode 100644
index 00000000000..059194f5df2
--- /dev/null
+++ b/engines/mediastation/assets/palette.h
@@ -0,0 +1,41 @@
+/* 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 MEDIASTATION_PALETTE_H
+#define MEDIASTATION_PALETTE_H
+
+#include "mediastation/assetheader.h"
+#include "mediastation/asset.h"
+#include "mediastation/mediascript/operand.h"
+
+namespace MediaStation {
+
+class Palette : public Asset {
+public:
+	Palette(AssetHeader *header) : Asset(header) {};
+	~Palette() = default;
+
+	virtual Operand callMethod(BuiltInMethod methodId, Common::Array<Operand> &args) override;
+};
+
+} // End of namespace MediaStation
+
+#endif
\ No newline at end of file
diff --git a/engines/mediastation/assets/path.cpp b/engines/mediastation/assets/path.cpp
new file mode 100644
index 00000000000..a586ecdbdfb
--- /dev/null
+++ b/engines/mediastation/assets/path.cpp
@@ -0,0 +1,129 @@
+/* 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 "mediastation/assets/path.h"
+#include "mediastation/debugchannels.h"
+
+namespace MediaStation {
+
+Path::~Path() {
+	_percentComplete = 0;
+}
+
+Operand Path::callMethod(BuiltInMethod methodId, Common::Array<Operand> &args) {
+	switch (methodId) {
+	case BuiltInMethod::timePlay: {
+		assert(args.size() == 0);
+		timePlay();
+		return Operand();
+	}
+
+	case BuiltInMethod::setDuration: {
+		assert(args.size() == 1);
+		uint durationInMilliseconds = (uint)(args[0].getDouble() * 1000);
+		setDuration(durationInMilliseconds);
+		return Operand();
+	}
+
+	case BuiltInMethod::percentComplete: {
+		assert(args.size() == 0);
+		Operand returnValue(Operand::Type::Float1);
+		returnValue.putDouble(percentComplete());
+		return returnValue;
+	}
+
+	default: {
+		error("Got unimplemented method ID %d", methodId);
+	}
+	}
+}
+
+void Path::timePlay() {
+	// TODO: Check that itʻs zero before we reset it, since this function isn't re-entrant!
+	_percentComplete = 0.0;
+
+	if (_header->_duration == 0) {
+		warning("Path::timePlay(): Got zero duration");
+	} else if (_header->_stepRate == 0) {
+		error("Path::timePlay(): Got zero step rate");
+	}
+	debugC(5, kDebugScript, "Path::timePlay(): Path playback started");
+	uint totalSteps = (_header->_duration * _header->_stepRate) / 1000;
+	uint stepDurationInMilliseconds = 1000 / _header->_stepRate;
+
+	// RUN THE START EVENT HANDLER.
+	EventHandler *startEventHandler = nullptr; // TODO: Haven't seen a path start event in the wild yet, don't know its ID.
+	if (startEventHandler != nullptr) {
+		debugC(5, kDebugScript, "Path::timePlay(): Running PathStart event handler");
+		startEventHandler->execute(_header->_id);
+	} else {
+		debugC(5, kDebugScript, "Path::timePlay(): No PathStart event handler");
+	}
+
+	// STEP THE PATH.
+	EventHandler *pathStepHandler = _header->_eventHandlers[EventHandler::Type::Step];
+	for (uint i = 0; i < totalSteps; i++) {
+		_percentComplete = (double)(i + 1) / totalSteps;
+		debugC(5, kDebugScript, "Path::timePlay(): Step %d of %d", i, totalSteps);
+		// TODO: Actually step the path. It seems they mostly just use this for
+		// palette animation in the On Step event handler, so nothing is actually drawn on the screen now.
+
+		// RUN THE ON STEP EVENT HANDLER.
+		// TODO: Is this supposed to come after or before we step the path?
+		if (pathStepHandler != nullptr) {
+			debugC(5, kDebugScript, "Path::timePlay(): Running PathStep event handler");
+			pathStepHandler->execute(_header->_id);
+		} else {
+			debugC(5, kDebugScript, "Path::timePlay(): No PathStep event handler");
+		}
+	}
+
+	// RUN THE END EVENT HANDLER.
+	EventHandler *endEventHandler = _header->_eventHandlers[EventHandler::Type::PathEnd];
+	if (endEventHandler != nullptr) {
+		debugC(5, kDebugScript, "Path::timePlay(): Running PathEnd event handler");
+		endEventHandler->execute(_header->_id);
+	} else {
+		debugC(5, kDebugScript, "Path::timePlay(): No PathEnd event handler");
+	}
+
+	// CLEAN UP.
+	_percentComplete = 0;
+}
+
+void Path::process() {
+	// TODO: Handle this case.
+}
+
+void Path::setDuration(uint durationInMilliseconds) {
+	// TODO: Do we need to save the original duration?
+	debugC(5, kDebugScript, "Path::setDuration(): Setting duration to %d ms", durationInMilliseconds);
+	_header->_duration = durationInMilliseconds;
+}
+
+
+double Path::percentComplete() {
+	debugC(5, kDebugScript, "Path::percentComplete(): Returning percent complete %f%%", _percentComplete * 100);
+	return _percentComplete;
+}
+
+} // End of namespace MediaStation
+
diff --git a/engines/mediastation/assets/path.h b/engines/mediastation/assets/path.h
new file mode 100644
index 00000000000..69eba91462f
--- /dev/null
+++ b/engines/mediastation/assets/path.h
@@ -0,0 +1,51 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef MEDIASTATION_PATH_H
+#define MEDIASTATION_PATH_H
+
+#include "mediastation/assetheader.h"
+#include "mediastation/asset.h"
+#include "mediastation/mediascript/operand.h"
+
+namespace MediaStation {
+
+class Path : public Asset {
+public:
+	Path(AssetHeader *header) : Asset(header) {};
+	virtual ~Path() override;
+
+	virtual void process() override;
+
+	virtual Operand callMethod(BuiltInMethod methodId, Common::Array<Operand> &args) override;
+
+private:
+	double _percentComplete = 0.0;
+
+	// Method implementations.
+	void timePlay();
+	void setDuration(uint durationInMilliseconds);
+	double percentComplete();
+};
+
+} // End of namespace MediaStation
+
+#endif
\ No newline at end of file
diff --git a/engines/mediastation/assets/sound.cpp b/engines/mediastation/assets/sound.cpp
new file mode 100644
index 00000000000..d82e565aa99
--- /dev/null
+++ b/engines/mediastation/assets/sound.cpp
@@ -0,0 +1,101 @@
+/* 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/decoders/adpcm.h"
+
+#include "mediastation/debugchannels.h"
+#include "mediastation/assets/sound.h"
+
+namespace MediaStation {
+
+Sound::Sound(AssetHeader *header) : Asset(header) {
+	if (_header != nullptr) {
+		_encoding = _header->_soundEncoding;
+	}
+}
+
+Sound::~Sound() {
+	delete _samples;
+	_samples = nullptr;
+	//for (Audio::SeekableAudioStream *stream : _streams) {
+	//    delete stream;
+	//}
+}
+
+Operand Sound::callMethod(BuiltInMethod methodId, Common::Array<Operand> &args) {
+	switch (methodId) {
+	default: {
+		error("Got unimplemented method ID %d", methodId);
+	}
+	}
+}
+
+void Sound::process() {
+	// TODO: Process more playing.
+}
+
+void Sound::readChunk(Chunk &chunk) {
+	// TODO: Can we read the chunk directly into the audio stream?
+	debugC(5, kDebugLoading, "Sound::readChunk(): (encoding = 0x%x) Reading audio chunk (@0x%llx)", (uint)_encoding, chunk.pos());
+	byte *buffer = (byte *)malloc(chunk.length);
+	chunk.read((void *)buffer, chunk.length);
+
+	switch (_encoding) {
+	case AssetHeader::SoundEncoding::PCM_S16LE_MONO_22050: {
+		// Audio::SeekableAudioStream *stream = Audio::makeRawStream(buffer, chunk.length, Sound::RATE, Sound::FLAGS, DisposeAfterUse::NO);
+		//_streams.push_back(stream);
+		break;
+	}
+
+	case AssetHeader::SoundEncoding::IMA_ADPCM_S16LE_MONO_22050: {
+		// TODO: Support ADPCM decoding.
+		// Audio::SeekableAudioStream *stream = nullptr; // Audio::makeADPCMStream(buffer, chunk.length, DisposeAfterUse::NO, Audio::ADPCMType::kADPCMMSIma, Sound::RATE, 1, 4);
+		//_streams.push_back(stream);
+		break;
+	}
+
+	default: {
+		error("Sound::readChunk(): Unknown audio encoding 0x%x", (uint)_encoding);
+		break;
+	}
+	}
+	debugC(5, kDebugLoading, "Sound::readChunk(): Finished reading audio chunk (@0x%llx)", chunk.pos());
+}
+
+void Sound::readSubfile(Subfile &subfile, Chunk &chunk) {
+	//if (_streams.size() != 0) {
+	//    warning("Sound::readSubfile(): Some audio has already been read.");
+	//}
+	uint32 totalChunks = _header->_chunkCount;
+	uint32 expectedChunkId = chunk.id;
+
+	readChunk(chunk);
+	for (uint i = 0; i < totalChunks; i++) {
+		chunk = subfile.nextChunk();
+		if (chunk.id != expectedChunkId) {
+			// TODO: Make this show the chunk IDs as strings, not numbers.
+			error("Sound::readSubfile(): Expected chunk %s, got %s", tag2str(expectedChunkId), tag2str(chunk.id));
+		}
+		readChunk(chunk);
+	}
+}
+
+}
\ No newline at end of file
diff --git a/engines/mediastation/assets/sound.h b/engines/mediastation/assets/sound.h
new file mode 100644
index 00000000000..a9fcc00d17c
--- /dev/null
+++ b/engines/mediastation/assets/sound.h
@@ -0,0 +1,65 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef MEDIASTATION_SOUND_H
+#define MEDIASTATION_SOUND_H
+
+#include "audio/mixer.h"
+#include "audio/audiostream.h"
+#include "audio/decoders/raw.h"
+
+#include "mediastation/asset.h"
+#include "mediastation/chunk.h"
+#include "mediastation/subfile.h"
+#include "mediastation/assetheader.h"
+#include "mediastation/mediascript/operand.h"
+
+namespace MediaStation {
+
+class Sound : public Asset {
+public:
+	// For standalone Sound assets.
+	Sound(AssetHeader *header);
+
+	// For sounds that are part of a movie.
+	// TODO: Since these aren't Assets they should be handled elsewhere.
+	//Sound(AssetHeader::SoundEncoding encoding);
+	~Sound();
+
+	virtual Operand callMethod(BuiltInMethod methodId, Common::Array<Operand> &args) override;
+	virtual void process() override;
+
+	virtual void readChunk(Chunk& chunk) override;
+	virtual void readSubfile(Subfile &subFile, Chunk &chunk) override;
+
+	// All Media Station audio is signed 16-bit little-endian mono at 22050 Hz.
+	// Some defaults must be overridden in the flags.
+	static const uint RATE = 22050;
+	static const byte FLAGS = Audio::FLAG_16BITS | Audio::FLAG_LITTLE_ENDIAN;
+
+private:
+	AssetHeader::SoundEncoding _encoding;
+	byte *_samples = nullptr;
+};
+
+} // End of namespace MediaStation
+
+#endif
\ No newline at end of file
diff --git a/engines/mediastation/assets/sprite.cpp b/engines/mediastation/assets/sprite.cpp
new file mode 100644
index 00000000000..99509993dba
--- /dev/null
+++ b/engines/mediastation/assets/sprite.cpp
@@ -0,0 +1,228 @@
+/* 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 "mediastation/datum.h"
+#include "mediastation/assets/sprite.h"
+#include "mediastation/debugchannels.h"
+#include "mediastation/mediastation.h"
+
+namespace MediaStation {
+
+SpriteFrameHeader::SpriteFrameHeader(Chunk &chunk) : BitmapHeader(chunk) {
+	_index = Datum(chunk).u.i;
+	debugC(5, kDebugLoading, "SpriteFrameHeader::SpriteFrameHeader(): _index = 0x%x (@0x%llx)", _index, chunk.pos());
+	_boundingBox = Datum(chunk, DatumType::POINT_2).u.point;
+	debugC(5, kDebugLoading, "SpriteFrameHeader::SpriteFrameHeader(): _boundingBox (@0x%llx)", chunk.pos());
+}
+
+SpriteFrameHeader::~SpriteFrameHeader() {
+	delete _boundingBox;
+	_boundingBox = nullptr;
+}
+
+SpriteFrame::SpriteFrame(Chunk &chunk, SpriteFrameHeader *header) : Bitmap(chunk, header) {
+	_bitmapHeader = header;
+}
+
+SpriteFrame::~SpriteFrame() {
+	// The base class destructor takes care of deleting the bitmap header.
+}
+
+uint32 SpriteFrame::left() {
+	return _bitmapHeader->_boundingBox->x;
+}
+
+uint32 SpriteFrame::top() {
+	return _bitmapHeader->_boundingBox->y;
+}
+
+Common::Point SpriteFrame::topLeft() {
+	return Common::Point(left(), top());
+}
+
+Common::Rect SpriteFrame::boundingBox() {
+	return Common::Rect(topLeft(), width(), height());
+}
+
+uint32 SpriteFrame::index() {
+	return _bitmapHeader->_index;
+}
+
+Sprite::~Sprite() {
+	_frames.clear();
+}
+
+Operand Sprite::callMethod(BuiltInMethod methodId, Common::Array<Operand> &args) {
+	switch (methodId) {
+	case BuiltInMethod::spatialShow: {
+		assert(args.size() == 0);
+		spatialShow();
+		return Operand();
+	}
+
+	case BuiltInMethod::timePlay: {
+		assert(args.size() == 0);
+		timePlay();
+		return Operand();
+	}
+
+	case BuiltInMethod::movieReset: {
+		assert(args.size() == 0);
+		movieReset();
+		return Operand();
+	}
+
+	default: {
+		error("Sprite::callMethod(): Got unimplemented method ID %d", methodId);
+	}
+	}
+}
+
+void Sprite::spatialShow() {
+	debugC(5, kDebugScript, "Called Sprite::spatialShow");
+	_isPlaying = true;
+	g_engine->addPlayingAsset(this);
+
+	// Persist the first frame.
+	// TODO: Is there anything that says what the persisted frame should be?
+	SpriteFrame *firstFrame = _frames[0];
+	for (SpriteFrame *frame : _frames) {
+		if (frame->index() < firstFrame->index()) {
+			firstFrame = frame;
+		}
+	}
+	_persistFrame = firstFrame;
+}
+
+void Sprite::timePlay() {
+	debugC(5, kDebugScript, "Called Sprite::timePlay");
+	_isPlaying = true;
+	_persistFrame = nullptr;
+	_startTime = g_system->getMillis();
+	_lastProcessedTime = 0;
+	_nextFrameTime = 0;
+	g_engine->addPlayingAsset(this);
+
+	if (_header->_frameRate == 0) {
+		_header->_frameRate = 10;
+	}
+
+	// RUN THE MOVIE START EVENT HANDLER.
+	EventHandler *startEvent = _header->_eventHandlers.getValOrDefault(EventHandler::Type::MovieBegin);
+	if (startEvent != nullptr) {
+		debugC(5, kDebugScript, "Sprite::timePlay(): Executing start event handler");
+		startEvent->execute(_header->_id);
+	} else {
+		debugC(5, kDebugScript, "Sprite::timePlay(): No start event handler");
+	}
+}
+
+void Sprite::movieReset() {
+	debugC(5, kDebugScript, "Called Sprite::movieReset");
+	_isPlaying = true;
+	// We do NOT reset the persisting frame, because it should keep showing!
+	_startTime = 0;
+	_currentFrameIndex = 0;
+	_nextFrameTime = 0;
+	_lastProcessedTime = 0;
+}
+
+void Sprite::process() {
+	drawNextFrame();
+
+	// TODO: I don't think sprites support time-based event handlers. Because we
+	// have a separate timer for restarting the sprite when it expires.
+}
+
+void Sprite::readChunk(Chunk &chunk) {
+	// Reads one frame from the sprite.
+	debugC(5, kDebugLoading, "Sprite::readFrame(): Reading sprite frame (@0x%llx)", chunk.pos());
+	SpriteFrameHeader *header = new SpriteFrameHeader(chunk);
+	SpriteFrame *frame = new SpriteFrame(chunk, header);
+	_frames.push_back(frame);
+
+	// TODO: Are these in exactly reverse order? If we can just reverse the
+	// whole thing once.
+	Common::sort(_frames.begin(), _frames.end(), [](SpriteFrame * a, SpriteFrame * b) {
+		return a->index() < b->index();
+	});
+}
+
+void Sprite::drawNextFrame() {
+	// TODO: With a dirty rect-based system, we would only need to redraw the frame
+	// when it NEEDS to be redrawn. But since the whole screen is currently redrawn
+	// every time, the persisting frame needs to be redrawn too.
+	bool redrawPersistentFrame = _persistFrame != nullptr;
+	if (redrawPersistentFrame) {
+		debugC(5, kDebugGraphics, "GRAPHICS (Sprite %d): Drawing persistent frame %d", _header->_id, _persistFrame->index());
+		drawFrame(_persistFrame);
+		return;
+	}
+
+	uint currentTime = g_system->getMillis() - _startTime;
+	bool redrawCurrentFrame = currentTime <= _nextFrameTime;
+	if (redrawCurrentFrame) {
+		// Just redraw the current frame in case it was covered over.
+		// See TODO above.
+		SpriteFrame *currentFrame = _frames[_currentFrameIndex];
+		debugC(5, kDebugGraphics, "GRAPHICS (Sprite %d): Re-drawing current frame %d", _header->_id, currentFrame->index());
+		drawFrame(currentFrame);
+		return;
+	}
+
+	SpriteFrame *nextFrame = _frames[_currentFrameIndex];
+	debugC(5, kDebugGraphics, "GRAPHICS (Sprite %d): Drawing next frame %d (@%d)", _header->_id, nextFrame->index(), _nextFrameTime);
+	uint frameDuration = 1000 / _header->_frameRate;
+	_nextFrameTime = _currentFrameIndex * frameDuration;
+	drawFrame(nextFrame);
+
+	bool spriteFinishedPlaying = (++_currentFrameIndex == _frames.size());
+	if (spriteFinishedPlaying) {
+		// Sprites always keep their last frame showing until they are hidden
+		// with spatialHide.
+		_persistFrame = _frames[_currentFrameIndex - 1];
+		_isPlaying = true;
+
+		// But otherwise, the sprite's params should be reset.
+		_startTime = 0;
+		_lastProcessedTime = 0;
+		_currentFrameIndex = 0;
+		_nextFrameTime = 0;
+
+		// RUN THE SPRITE END EVENT HANDLER.
+		EventHandler *endEvent = _header->_eventHandlers.getValOrDefault(EventHandler::Type::MovieEnd);
+		if (endEvent != nullptr) {
+			debugC(5, kDebugScript, "Sprite::drawNextFrame(): Executing end event handler");
+			endEvent->execute(_header->_id);
+		} else {
+			debugC(5, kDebugScript, "Sprite::drawNextFrame(): No end event handler");
+		}
+	}
+}
+
+void Sprite::drawFrame(SpriteFrame *frame) {
+	uint frameLeft = frame->left() + _header->_boundingBox->left;
+	uint frameTop = frame->top() + _header->_boundingBox->top;
+	debugC(5, kDebugGraphics, "    Sprite frame %d (%d x %d) @ (%d, %d)", frame->index(), frame->width(), frame->height(), frameLeft, frameTop);
+	g_engine->_screen->transBlitFrom(frame->surface, Common::Point(frameLeft, frameTop), 0, false);
+}
+
+} // End of namespace MediaStation
\ No newline at end of file
diff --git a/engines/mediastation/assets/sprite.h b/engines/mediastation/assets/sprite.h
new file mode 100644
index 00000000000..55380a783da
--- /dev/null
+++ b/engines/mediastation/assets/sprite.h
@@ -0,0 +1,84 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 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 MEDIASTATION_SPRITE_H
+#define MEDIASTATION_SPRITE_H
+
+#include "mediastation/asset.h"
+#include "mediastation/assetheader.h"
+#include "mediastation/bitmap.h"
+#include "mediastation/mediascript/operand.h"
+
+namespace MediaStation {
+
+class SpriteFrameHeader : public BitmapHeader {
+public:
+	SpriteFrameHeader(Chunk &chunk);
+	~SpriteFrameHeader();
+
+	uint _index;
+	Common::Point *_boundingBox;
+};
+
+class SpriteFrame : public Bitmap {
+public:
+	SpriteFrame(Chunk &chunk, SpriteFrameHeader *header);
+	~SpriteFrame();
+
+	uint32 left();
+	uint32 top();
+	Common::Point topLeft();
+	Common::Rect boundingBox();
+	uint32 index();
+
+private:
+	SpriteFrameHeader *_bitmapHeader = nullptr;
+};
+
+class Sprite : public Asset {
+public:
+	Sprite(AssetHeader *header) : Asset(header) {};
+	~Sprite();
+
+	virtual Operand callMethod(BuiltInMethod methodId, Common::Array<Operand> &args) override;
+	virtual void process() override;
+
+	virtual void readChunk(Chunk &chunk) override;
+
+private:
+	Common::Array<SpriteFrame *> _frames;
+	SpriteFrame *_persistFrame = nullptr;
+	uint _currentFrameIndex = 0;
+	uint _nextFrameTime = 0;
+
+	// Method implementations.
+	void spatialShow();
+	void timePlay();
+	void movieReset();
+
+	// Helper functions.
+	void drawNextFrame();
+	void drawFrame(SpriteFrame *frame);
+};
+
+} // End of namespace MediaStation
+
+#endif
\ No newline at end of file
diff --git a/engines/mediastation/assets/timer.cpp b/engines/mediastation/assets/timer.cpp
new file mode 100644
index 00000000000..72912176215
--- /dev/null
+++ b/engines/mediastation/assets/timer.cpp
@@ -0,0 +1,128 @@
+/* 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 "mediastation/mediastation.h"
+#include "mediastation/debugchannels.h"
+
+#include "mediastation/assets/timer.h"
+
+namespace MediaStation {
+
+Operand Timer::callMethod(BuiltInMethod methodId, Common::Array<Operand> &args) {
+	switch (methodId) {
+	case BuiltInMethod::timePlay: {
+		assert(args.size() == 0);
+		timePlay();
+		return Operand();
+	}
+
+	case BuiltInMethod::timeStop: {
+		assert(args.size() == 0);
+		timeStop();
+		return Operand();
+	}
+
+	default: {
+		error("Got unimplemented method ID %d", methodId);
+	}
+	}
+}
+
+void Timer::timePlay() {
+	if (_isPlaying) {
+		warning("Timer::timePlay(): Attempted to play a timer that is already playing");
+		//return;
+	}
+
+	// SET TIMER VARIABLES.
+	_isPlaying = true;
+	_startTime = g_system->getMillis();
+	_lastProcessedTime = 0;
+	g_engine->addPlayingAsset(this);
+
+	// GET THE DURATION OF THE TIMER.
+	// TODO: Is there a better way to find out what the max time is? Do we have to look
+	// through each of the timer event handlers to figure it out?
+	_duration = 0;
+	for (EventHandler *timeEvent : _header->_timeHandlers) {
+		// TODO: Centralize this converstion to milliseconds, as the same logic
+		// is being used in several places.
+		double timeEventInFractionalSeconds = timeEvent->_argumentValue.u.f;
+		uint timeEventInMilliseconds = timeEventInFractionalSeconds * 1000;
+		if (timeEventInMilliseconds > _duration) {
+			_duration = timeEventInMilliseconds;
+		}
+	}
+
+	debugC(5, kDebugScript, "Timer::timePlay(): Now playing for %d ms", _duration);
+}
+
+void Timer::timeStop() {
+	if (!_isPlaying) {
+		warning("Timer::stop(): Attempted to stop a timer that is not playing");
+		return;
+	}
+
+	_isPlaying = false;
+	_startTime = 0;
+	_lastProcessedTime = 0;
+}
+
+void Timer::process() {
+	if (!_isPlaying) {
+		error("Timer::processTimeEventHandlers(): Attempted to process time event handlers while not playing");
+		return;
+	}
+
+	uint currentTime = g_system->getMillis();
+	uint movieTime = currentTime - _startTime;
+	//if (movieTime > _duration) {
+	// We are done processing the timer.
+	//    _isPlaying = false;
+	//}
+	debugC(7, kDebugScript, "** Timer %d: ON TIME Event Handlers **", _header->_id);
+	for (EventHandler *timeEvent : _header->_timeHandlers) {
+		double timeEventInFractionalSeconds = timeEvent->_argumentValue.u.f;
+		uint timeEventInMilliseconds = timeEventInFractionalSeconds * 1000;
+		bool timeEventAlreadyProcessed = timeEventInMilliseconds < _lastProcessedTime;
+		bool timeEventNeedsToBeProcessed = timeEventInMilliseconds <= currentTime - _startTime;
+		if (!timeEventAlreadyProcessed && timeEventNeedsToBeProcessed) {
+			// TODO: What happens when we try re-run the timer when itʻs already
+			// running? Seems like this would cause re-entrancy issues.
+			timeEvent->execute(_header->_id);
+		}
+	}
+	debugC(7, kDebugScript, "** Timer %d: End ON TIME Event Handlers **", _header->_id);
+	_lastProcessedTime = currentTime - _startTime;
+
+	// This has to be at the end becuase the duration of the timer is the
+	// longest time event there is in the timer. And of course it will be called
+	// some amount of time after this time value.
+	// if (movieTime > _duration) {
+	//     // We are done processing the timer.
+	//     _isPlaying = false;
+	//     _startTime = 0;
+	//     _lastProcessedTime = 0;
+	//     debugC(5, kDebugScript, "Timer::timePlay(): Reached end of timer");
+	// }
+}
+
+} // End of namespace MediaStation
diff --git a/engines/mediastation/assets/timer.h b/engines/mediastation/assets/timer.h
new file mode 100644
index 00000000000..9bbd844cf75
--- /dev/null
+++ b/engines/mediastation/assets/timer.h
@@ -0,0 +1,46 @@
+/* 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 MEDIASTATION_TIMER_H
+#define MEDIASTATION_TIMER_H
+
+#include "mediastation/assetheader.h"
+#include "mediastation/mediascript/operand.h"
+
+namespace MediaStation {
+
+class Timer : public Asset {
+public:
+	Timer(AssetHeader *header) : Asset(header) {};
+	virtual ~Timer() override = default;
+
+	virtual Operand callMethod(BuiltInMethod methodId, Common::Array<Operand> &args) override;
+	virtual void process() override;
+
+private:
+	// Method implementations.
+	void timePlay();
+	void timeStop();
+};
+
+} // End of namespace MediaStation
+
+#endif
\ No newline at end of file
diff --git a/engines/mediastation/bitmap.cpp b/engines/mediastation/bitmap.cpp
new file mode 100644
index 00000000000..439a4da7211
--- /dev/null
+++ b/engines/mediastation/bitmap.cpp
@@ -0,0 +1,209 @@
+/* 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 "mediastation/datum.h"
+#include "mediastation/bitmap.h"
+#include "mediastation/debugchannels.h"
+
+namespace MediaStation {
+
+BitmapHeader::BitmapHeader(Chunk &chunk) {
+	uint header_size_in_bytes = Datum(chunk, DatumType::UINT16_1).u.i;
+	dimensions = Datum(chunk).u.point;
+	compression_type = BitmapHeader::CompressionType(Datum(chunk, DatumType::UINT16_1).u.i);
+	debugC(5, kDebugLoading, "BitmapHeader::BitmapHeader(): _compressionType = 0x%x", compression_type);
+	// TODO: Figure out what this is.
+	// This has something to do with the width of the bitmap but is always
+	// a few pixels off from the width. And in rare cases it seems to be
+	// the true width!
+	unk2 = Datum(chunk, DatumType::UINT16_1).u.i;
+}
+
+BitmapHeader::~BitmapHeader() {
+	delete dimensions;
+	dimensions = nullptr;
+}
+
+bool BitmapHeader::isCompressed() {
+	return (compression_type != BitmapHeader::CompressionType::UNCOMPRESSED) &&
+	       (compression_type != BitmapHeader::CompressionType::UNCOMPRESSED_2);
+}
+
+Bitmap::Bitmap(Chunk &chunk, BitmapHeader *bitmapHeader) :
+	_bitmapHeader(bitmapHeader) {
+	// The header must be constructed beforehand.
+	uint16 width = _bitmapHeader->dimensions->x;
+	uint16 height = _bitmapHeader->dimensions->y;
+	surface.create(width, height, Graphics::PixelFormat::createFormatCLUT8());
+	uint8 *pixels = (uint8 *)surface.getPixels();
+	if (_bitmapHeader->isCompressed()) {
+		// DECOMPRESS THE IMAGE.
+		// chunk.skip(chunk.bytesRemaining());
+		debugC(5, kDebugLoading, "Bitmap::Bitmap(): Decompressing bitmap");
+		decompress(chunk);
+	} else {
+		// READ THE UNCOMPRESSED IMAGE DIRECTLY.
+		// TODO: Understand why we need to ignore these 2 bytes.
+		chunk.skip(2);
+		chunk.read(pixels, chunk.bytesRemaining());
+	}
+}
+
+Bitmap::~Bitmap() {
+	delete _bitmapHeader;
+	_bitmapHeader = nullptr;
+}
+
+uint16 Bitmap::width() {
+	return _bitmapHeader->dimensions->x;
+}
+
+uint16 Bitmap::height() {
+	return _bitmapHeader->dimensions->y;
+}
+
+void Bitmap::decompress(Chunk &chunk) {
+	// GET THE COMPRESSED DATA.
+	uint compressed_image_data_size_in_bytes = chunk.bytesRemaining();
+	char *compressed_image_start = new char[compressed_image_data_size_in_bytes];
+	char *compressed_image = compressed_image_start;
+	chunk.read(compressed_image, compressed_image_data_size_in_bytes);
+
+	// MAKE SURE WE READ PAST THE FIRST 2 BYTES.
+	char *compressed_image_data_start = compressed_image;
+	if ((*compressed_image++ == 0) && (*compressed_image++ == 0)) {
+		// This condition is empty, we just put it first since this is the expected case
+		// and the negated logic would be not as readable.
+	} else {
+		compressed_image = compressed_image_data_start;
+	}
+	char *compressed_image_data_end = compressed_image + compressed_image_data_size_in_bytes;
+
+	// GET THE DECOMPRESSED PIXELS BUFFER.
+	// Media Station has 8 bits per pixel, so the decompression buffer is
+	// simple.
+	// TODO: Do we have to set the pixels ourselves?
+	char *decompressed_image = (char *)surface.getPixels();
+
+	// DECOMPRESS THE RLE-COMPRESSED BITMAP STREAM.
+	bool transparency_run_ever_read = false;
+	size_t transparency_run_top_y_coordinate = 0;
+	size_t transparency_run_left_x_coordinate = 0;
+	bool image_fully_read = false;
+	size_t current_y_coordinate = 0;
+	while (current_y_coordinate < height()) {
+		size_t current_x_coordinate = 0;
+		bool reading_transparency_run = false;
+		while (true) {
+			uint8_t operation = *compressed_image++;
+			if (operation == 0x00) {
+				// ENTER CONTROL MODE.
+				operation = *compressed_image++;
+				if (operation == 0x00) {
+					// MARK THE END OF THE LINE.
+					// Also check if the image is finished being read.
+					if (compressed_image >= compressed_image_data_end) {
+						image_fully_read = true;
+					}
+					break;
+				} else if (operation == 0x01) {
+					// MARK THE END OF THE IMAGE.
+					// TODO: When is this actually used?
+					image_fully_read = true;
+					break;
+				} else if (operation == 0x02) {
+					// MARK THE START OF A KEYFRAME TRANSPARENCY REGION.
+					// Until a color index other than 0x00 (usually white) is read on this line,
+					// all pixels on this line will be marked transparent.
+					// If no transparency regions are present in this image, all 0x00 color indices are treated
+					// as transparent. Otherwise, only the 0x00 color indices within transparency regions
+					// are considered transparent. Only intraframes (frames that are not keyframes) have been
+					// observed to have transparency regions, and these intraframes have them so the keyframe
+					// can extend outside the boundary of the intraframe and
+					// still be removed.
+					reading_transparency_run = true;
+					transparency_run_top_y_coordinate = current_y_coordinate;
+					transparency_run_left_x_coordinate = current_x_coordinate;
+					transparency_run_ever_read = true;
+				} else if (operation == 0x03) {
+					// ADJUST THE PIXEL POSITION.
+					// This permits jumping to a different part of the same row without
+					// needing a run of pixels in between. But the actual data consumed
+					// seems to actually be higher this way, as you need the control byte
+					// first.
+					// So to skip 10 pixels using this approach, you would encode 00 03 0a 00.
+					// But to "skip" 10 pixels by encoding them as blank (0xff), you would encode 0a ff.
+					// What gives? I'm not sure.
+					uint8_t x_change = *compressed_image++;
+					current_x_coordinate += x_change;
+					uint8_t y_change = *compressed_image++;
+					current_y_coordinate += y_change;
+				} else if (operation >= 0x04) {
+					// READ A RUN OF UNCOMPRESSED PIXELS.
+					size_t y_offset = current_y_coordinate * width();
+					size_t run_starting_offset = y_offset + current_x_coordinate;
+					char *run_starting_pointer = decompressed_image + run_starting_offset;
+					uint8_t run_length = operation;
+					memcpy(run_starting_pointer, compressed_image, run_length);
+					compressed_image += operation;
+					current_x_coordinate += operation;
+
+					if (((uintptr_t)compressed_image) % 2 == 1) {
+						compressed_image++;
+					}
+				}
+			} else {
+				// READ A RUN OF LENGTH ENCODED PIXELS.
+				size_t y_offset = current_y_coordinate * width();
+				size_t run_starting_offset = y_offset + current_x_coordinate;
+				char *run_starting_pointer = decompressed_image + run_starting_offset;
+				uint8_t color_index_to_repeat = *compressed_image++;
+				uint8_t repetition_count = operation;
+				memset(run_starting_pointer, color_index_to_repeat, repetition_count);
+				current_x_coordinate += repetition_count;
+
+				if (reading_transparency_run) {
+					// GET THE TRANSPARENCY RUN STARTING OFFSET.
+					size_t transparency_run_y_offset = transparency_run_top_y_coordinate * width();
+					size_t transparency_run_start_offset = transparency_run_y_offset + transparency_run_left_x_coordinate;
+					size_t transparency_run_ending_offset = y_offset + current_x_coordinate;
+					size_t transparency_run_length = transparency_run_ending_offset - transparency_run_start_offset;
+					// char *transparency_run_src_pointer = keyframe_image + run_starting_offset;
+					// char *transparency_run_dest_pointer = decompressed_image + run_starting_offset;
+
+					// COPY THE TRANSPARENT AREA FROM THE KEYFRAME.
+					// The "interior" of transparency regions is always encoded by a single run of
+					// pixels, usually 0x00 (white).
+					// memcpy(transparency_run_dest_pointer, transparency_run_src_pointer, transparency_run_length);
+					reading_transparency_run = false;
+				}
+			}
+		}
+
+		current_y_coordinate++;
+		if (image_fully_read) {
+			break;
+		}
+	}
+	delete[] compressed_image_start;
+}
+
+}
\ No newline at end of file
diff --git a/engines/mediastation/bitmap.h b/engines/mediastation/bitmap.h
new file mode 100644
index 00000000000..6e1efe1407e
--- /dev/null
+++ b/engines/mediastation/bitmap.h
@@ -0,0 +1,68 @@
+/* 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 "graphics/managed_surface.h"
+
+#include "mediastation/chunk.h"
+#include "mediastation/assetheader.h"
+
+#ifndef MEDIASTATION_BITMAP_H
+#define MEDIASTATION_BITMAP_H
+
+namespace MediaStation {
+
+class BitmapHeader {
+public:
+	enum class CompressionType {
+		UNCOMPRESSED = 0,
+		RLE_COMPRESSED = 1,
+		UNK1 = 6,
+		UNCOMPRESSED_2 = 7,
+	};
+
+	BitmapHeader(Chunk &chunk);
+	~BitmapHeader();
+
+	bool isCompressed();
+
+	Common::Point *dimensions = nullptr;
+	CompressionType compression_type;
+	uint unk2;
+};
+
+class Bitmap {
+public:
+	BitmapHeader *_bitmapHeader = nullptr;
+
+	Bitmap(Chunk &chunk, BitmapHeader *bitmapHeader);
+	~Bitmap();
+
+	uint16 width();
+	uint16 height();
+	Graphics::ManagedSurface surface;
+
+private:
+	void decompress(Chunk &chunk);
+};
+
+}
+
+#endif
\ No newline at end of file
diff --git a/engines/mediastation/boot.cpp b/engines/mediastation/boot.cpp
new file mode 100644
index 00000000000..c5c67d24d8c
--- /dev/null
+++ b/engines/mediastation/boot.cpp
@@ -0,0 +1,442 @@
+/* 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 "mediastation/boot.h"
+#include "mediastation/datum.h"
+#include "mediastation/subfile.h"
+#include "mediastation/chunk.h"
+#include "mediastation/debugchannels.h"
+
+namespace MediaStation {
+
+#pragma region VersionInfo
+VersionInfo::VersionInfo(Chunk &chunk) {
+	majorVersion = Datum(chunk, DatumType::UINT16_1).u.i;
+	minorVersion = Datum(chunk, DatumType::UINT16_1).u.i;
+	revision = Datum(chunk, DatumType::UINT16_1).u.i;
+	string = Datum(chunk, DatumType::STRING).u.string;
+}
+
+VersionInfo::~VersionInfo() {
+	if (string != nullptr) {
+		delete string;
+	}
+}
+#pragma endregion
+
+#pragma region ContextDeclaration
+ContextDeclaration::ContextDeclaration(Chunk &chunk) {
+	// ENSURE WE HAVEN'T REACHED THE END OF THE DECLARATIONS.
+	ContextDeclaration::SectionType sectionType = getSectionType(chunk);
+	if (ContextDeclaration::SectionType::EMPTY == sectionType) {
+		isLast = true;
+		return;
+	} else {
+		// There may be more declarations in the stream.
+		isLast = false;
+	}
+
+	// READ THE FILE REFERENCES.
+	while (ContextDeclaration::SectionType::FILE_REFERENCE == sectionType) {
+		int fileReference = Datum(chunk).u.i;
+		fileReferences.push_back(fileReference);
+		sectionType = getSectionType(chunk);
+	}
+
+	// READ THE OTHER CONTEXT METADATA.
+	if (ContextDeclaration::SectionType::PLACEHOLDER == sectionType) {
+		// READ THE FILE NUMBER.
+		sectionType = getSectionType(chunk);
+		if (ContextDeclaration::SectionType::FILE_NUMBER_1 == sectionType) {
+			fileNumber = Datum(chunk).u.i;
+		} else {
+			error("ContextDeclaration(): Expected section type FILE_NUMBER_1, got 0x%x", sectionType);
+		}
+		// I don't know why the file number is always repeated.
+		// Is it just for data integrity, or is there some other reason?
+		sectionType = getSectionType(chunk);
+		if (ContextDeclaration::SectionType::FILE_NUMBER_2 == sectionType) {
+			uint32 repeatedFileNumber = Datum(chunk).u.i;
+			if (repeatedFileNumber != fileNumber) {
+				warning("ContextDeclaration(): Expected file numbers to match, but 0x%d != 0x%d", fileNumber, repeatedFileNumber);
+			}
+		} else {
+			error("ContextDeclaration(): Expected section type FILE_NUMBER_2, got 0x%x", sectionType);
+		}
+
+		// READ THE CONTEXT NAME.
+		// Only some titles have context names, and unfortunately we can't
+		// determine which just by relying on the title compiler version
+		// number.
+		// TODO: Find a better way to read the context name without relying
+		// on reading and rewinding.
+		int rewindOffset = chunk.pos();
+		sectionType = getSectionType(chunk);
+		if (ContextDeclaration::SectionType::CONTEXT_NAME == sectionType) {
+			contextName = Datum(chunk, DatumType::STRING).u.string;
+		} else {
+			// THERE IS NO CONTEXT NAME.
+			// We have instead read into the next declaration, so let's undo that.
+			chunk.seek(rewindOffset);
+		}
+	} else if (ContextDeclaration::SectionType::EMPTY == sectionType) {
+		isLast = true;
+	} else {
+		error("ContextDeclaration::ContextDeclaration(): Unknown section type 0x%x", sectionType);
+	}
+}
+
+ContextDeclaration::SectionType ContextDeclaration::getSectionType(Chunk &chunk) {
+	Datum datum = Datum(chunk, DatumType::UINT16_1);
+	ContextDeclaration::SectionType sectionType = static_cast<ContextDeclaration::SectionType>(datum.u.i);
+	return sectionType;
+}
+
+ContextDeclaration::~ContextDeclaration() {
+	delete contextName;
+	contextName = nullptr;
+}
+#pragma endregion
+
+#pragma region UnknownDeclaration
+UnknownDeclaration::UnknownDeclaration(Chunk &chunk) {
+	// ENSURE THIS DECLARATION IS NOT EMPTY.
+	UnknownDeclaration::SectionType sectionType = getSectionType(chunk);
+	if (UnknownDeclaration::SectionType::EMPTY == sectionType) {
+		_isLast = true;
+		return;
+	} else {
+		// There may be more declarations in the stream.
+		_isLast = false;
+	}
+
+	// READ THE UNKNOWN VALUE.
+	sectionType = getSectionType(chunk);
+	if (UnknownDeclaration::SectionType::UNK_1 == sectionType) {
+		_unk = Datum(chunk, DatumType::UINT16_1).u.i;
+	} else {
+		error("UnknownDeclaration(): Expected section type UNK_1, got 0x%x", sectionType);
+	}
+	sectionType = getSectionType(chunk);
+	if (UnknownDeclaration::SectionType::UNK_2 == sectionType) {
+		uint16 repeatedUnk = Datum(chunk, DatumType::UINT16_1).u.i;
+		if (repeatedUnk != _unk) {
+			warning("UnknownDeclaration(): Expected unknown values to match, but 0x%x != 0x%x", _unk, repeatedUnk);
+		}
+	} else {
+		error("UnknownDeclaration(): Expected section type UNK_2, got 0x%x", sectionType);
+	}
+}
+
+UnknownDeclaration::SectionType UnknownDeclaration::getSectionType(Chunk &chunk) {
+	Datum datum = Datum(chunk, DatumType::UINT16_1);
+	UnknownDeclaration::SectionType sectionType = static_cast<UnknownDeclaration::SectionType>(datum.u.i);
+	return sectionType;
+}
+#pragma endregion
+
+#pragma region FileDeclaration
+FileDeclaration::FileDeclaration(Chunk &chunk) {
+	// ENSURE THIS DECLARATION IS NOT EMPTY.
+	FileDeclaration::SectionType sectionType = getSectionType(chunk);
+	if (FileDeclaration::SectionType::EMPTY == sectionType) {
+		_isLast = true;
+		return;
+	} else {
+		// There may be more declarations in the stream.
+		_isLast = false;
+	}
+
+	// READ THE FILE ID.
+	sectionType = getSectionType(chunk);
+	if (FileDeclaration::SectionType::FILE_ID == sectionType) {
+		_id = Datum(chunk, DatumType::UINT16_1).u.i;
+	} else {
+		error("FileDeclaration(): Expected section type FILE_ID, got 0x%x", sectionType);
+	}
+
+	// READ THE INTENDED LOCATION OF THE FILE.
+	sectionType = getSectionType(chunk);
+	if (FileDeclaration::SectionType::FILE_NAME_AND_TYPE == sectionType) {
+		Datum datum = Datum(chunk, DatumType::UINT16_1);
+		// TODO: Verify we actually read a valid enum member.
+		_intendedLocation = static_cast<FileDeclaration::IntendedLocation>(datum.u.i);
+	} else {
+		error("FileDeclaration(): Expected section type FILE_NAME_AND_TYPE, got 0x%x", sectionType);
+	}
+
+	// READ THE CASE-INSENSITIVE FILENAME.
+	// Since the platforms that Media Station originally targeted were case-insensitive,
+	// the case of these filenames might not match the case of the files actually in
+	// the directory. All files should be matched case-insensitively.
+	_name = Datum(chunk, DatumType::FILENAME).u.string;
+}
+
+FileDeclaration::SectionType FileDeclaration::getSectionType(Chunk &chunk) {
+	Datum datum = Datum(chunk, DatumType::UINT16_1);
+	FileDeclaration::SectionType sectionType = static_cast<FileDeclaration::SectionType>(datum.u.i);
+	return sectionType;
+}
+
+FileDeclaration::~FileDeclaration() {
+	delete _name;
+	_name = nullptr;
+}
+#pragma endregion
+
+#pragma region SubfileDeclaration
+SubfileDeclaration::SubfileDeclaration(Chunk &chunk) {
+	// ENSURE THIS DECLARATION IS NOT EMPTY.
+	SubfileDeclaration::SectionType sectionType = getSectionType(chunk);
+	if (SubfileDeclaration::SectionType::EMPTY == sectionType) {
+		_isLast = true;
+		return;
+	} else {
+		// There may be more declarations in the stream.
+		_isLast = false;
+	}
+
+	// READ THE ASSET ID.
+	sectionType = getSectionType(chunk);
+	if (SubfileDeclaration::SectionType::ASSET_ID == sectionType) {
+		_assetId = Datum(chunk, DatumType::UINT16_1).u.i;
+	} else {
+		error("SubfileDeclaration(): Expected section type ASSET_ID, got 0x%x", sectionType);
+	}
+
+	// READ THE FILE ID.
+	sectionType = getSectionType(chunk);
+	if (SubfileDeclaration::SectionType::FILE_ID == sectionType) {
+		_fileId = Datum(chunk, DatumType::UINT16_1).u.i;
+	} else {
+		error("SubfileDeclaration(): Expected section type FILE_ID, got 0x%x", sectionType);
+	}
+
+	// READ THE START OFFSET IN THE GIVEN FILE.
+	// This is from the absolute start of the given file.
+	sectionType = getSectionType(chunk);
+	if (SubfileDeclaration::SectionType::START_OFFSET == sectionType) {
+		_startOffsetInFile = Datum(chunk, DatumType::UINT32_1).u.i;
+	} else {
+		error("SubfileDeclaration(): Expected section type START_OFFSET, got 0x%x", sectionType);
+	}
+}
+
+SubfileDeclaration::SectionType SubfileDeclaration::getSectionType(Chunk &chunk) {
+	Datum datum = Datum(chunk, DatumType::UINT16_1);
+	SubfileDeclaration::SectionType sectionType = static_cast<SubfileDeclaration::SectionType>(datum.u.i);
+	return sectionType;
+}
+#pragma endregion
+
+#pragma region CursorDeclaration
+CursorDeclaration::CursorDeclaration(Chunk& chunk) {
+	// READ THE CURSOR RESOURCE.
+	uint16 unk1 = Datum(chunk, DatumType::UINT16_1).u.i; // Always 0x0001
+	_id = Datum(chunk, DatumType::UINT16_1).u.i;
+	_unk = Datum(chunk, DatumType::UINT16_1).u.i;
+	_name = Datum(chunk, DatumType::FILENAME).u.string;
+	debugC(5, kDebugLoading, " - CursorDeclaration(): unk1 = 0x%x, id = 0x%x, unk = 0x%x, name = %s", unk1, _id, _unk, _name->c_str());
+}
+
+CursorDeclaration::~CursorDeclaration() {
+	delete _name;
+	_name = nullptr;
+}
+#pragma endregion
+
+#pragma region Engine Resource Declaration
+EngineResourceDeclaration::EngineResourceDeclaration(Common::String *resourceName, int resourceId) : _resourceName(resourceName), _resourceId(resourceId) {}
+
+EngineResourceDeclaration::~EngineResourceDeclaration() {
+	delete _resourceName;
+	_resourceName = nullptr;
+}
+#pragma endregion
+
+#pragma region Boot
+Boot::Boot(const Common::Path &path) : Datafile(path) {
+	// OPEN THE FILE FOR READING.
+	subfile = Subfile(_stream);
+	Chunk chunk = subfile.nextChunk();
+
+	uint32 beforeSectionTypeUnk = Datum(chunk, DatumType::UINT16_1).u.i; // Usually 0x0001
+	debugC(5, kDebugLoading, "Boot::Boot(): unk1 = 0x%x", beforeSectionTypeUnk);
+
+	Boot::SectionType sectionType = getSectionType(chunk);
+	bool notLastSection = (Boot::SectionType::LAST != sectionType);
+	while (notLastSection) {
+		debugC(5, kDebugLoading, "Boot::Boot(): sectionType = 0x%x", sectionType);
+		switch (sectionType) {
+		case Boot::SectionType::VERSION_INFORMATION: {
+			_gameTitle = Datum(chunk, DatumType::STRING).u.string;
+			debugC(5, kDebugLoading, " - gameTitle = %s", _gameTitle->c_str());
+			uint32 unk = chunk.readUint16LE();
+			debugC(5, kDebugLoading, " - unk = 0x%x", unk);
+			_versionInfo = new VersionInfo(chunk);
+			debugC(5, kDebugLoading, " - versionInfo = %d.%d.%d (%s)", _versionInfo->majorVersion, _versionInfo->minorVersion, _versionInfo->revision, _versionInfo->string->c_str());
+			_sourceString = Datum(chunk, DatumType::STRING).u.string;
+			debugC(5, kDebugLoading, " - sourceString = %s", _sourceString->c_str());
+			break;
+		}
+
+		case Boot::SectionType::UNK1:
+		case Boot::SectionType::UNK2:
+		case Boot::SectionType::UNK3: {
+			uint32 unk = Datum(chunk).u.i;
+			debugC(5, kDebugLoading, " - unk = 0x%x", unk);
+			break;
+		}
+
+		case Boot::SectionType::UNK4: {
+			double unk = Datum(chunk).u.f;
+			debugC(5, kDebugLoading, " - unk = %f", unk);
+			break;
+		}
+
+		case Boot::SectionType::ENGINE_RESOURCE: {
+			Common::String *resourceName = Datum(chunk, DatumType::STRING).u.string;
+			sectionType = getSectionType(chunk);
+			if (sectionType == Boot::SectionType::ENGINE_RESOURCE_ID) {
+				int resourceId = Datum(chunk).u.i;
+				EngineResourceDeclaration *resourceDeclaration = new EngineResourceDeclaration(resourceName, resourceId);
+				_engineResourceDeclarations.setVal(resourceId, resourceDeclaration);
+			} else {
+				error("Boot::Boot(): Got section type 0x%x when expecting ENGINE_RESOURCE_ID", sectionType);
+			}
+			break;
+		}
+
+		case Boot::SectionType::CONTEXT_DECLARATION: {
+			ContextDeclaration *contextDeclaration = new ContextDeclaration(chunk);
+			while (!contextDeclaration->isLast) {
+				_contextDeclarations.setVal(contextDeclaration->fileNumber, contextDeclaration);
+				contextDeclaration = new ContextDeclaration(chunk);
+			}
+			break;
+		}
+
+		case Boot::SectionType::UNKNOWN_DECLARATION: {
+			UnknownDeclaration *unknownDeclaration = new UnknownDeclaration(chunk);
+			while (!unknownDeclaration->_isLast) {
+				_unknownDeclarations.push_back(unknownDeclaration);
+				unknownDeclaration = new UnknownDeclaration(chunk);
+			}
+			break;
+		}
+
+		case Boot::SectionType::FILE_DECLARATION: {
+			FileDeclaration *fileDeclaration = new FileDeclaration(chunk);
+			while (!fileDeclaration->_isLast) {
+				_fileDeclarations.setVal(fileDeclaration->_id, fileDeclaration);
+				fileDeclaration = new FileDeclaration(chunk);
+			}
+			break;
+		}
+
+		case Boot::SectionType::SUBFILE_DECLARATION: {
+			SubfileDeclaration *subfileDeclaration = new SubfileDeclaration(chunk);
+			while (!subfileDeclaration->_isLast) {
+				_subfileDeclarations.setVal(subfileDeclaration->_assetId, subfileDeclaration);
+				subfileDeclaration = new SubfileDeclaration(chunk);
+			}
+			break;
+		}
+
+		case Boot::SectionType::CURSOR_DECLARATION: {
+			CursorDeclaration *cursorDeclaration = new CursorDeclaration(chunk);
+			_cursorDeclarations.setVal(cursorDeclaration->_id, cursorDeclaration);
+			break;
+		}
+
+		case Boot::SectionType::EMPTY: {
+			// This semems to separate the cursor declarations from whatever comes
+			// after it (what I formerly called the "footer"), but it has no data
+			// itself.
+			break;
+		}
+
+		case Boot::SectionType::ENTRY_SCREEN: {
+			_entryContextId = Datum(chunk).u.i;
+			debugC(5, kDebugLoading, " - _entryContextId = %d", _entryContextId);
+			break;
+		}
+
+		case Boot::SectionType::ALLOW_MULTIPLE_SOUNDS: {
+			_allowMultipleSounds = (Datum(chunk).u.i == 1);
+			debugC(5, kDebugLoading, " - _allowMultipleSounds = %d", _allowMultipleSounds);
+			break;
+		}
+
+		case Boot::SectionType::ALLOW_MULTIPLE_STREAMS: {
+			_allowMultipleStreams = (Datum(chunk).u.i == 1);
+			debugC(5, kDebugLoading, " - _allowMultipleStreams = %d", _allowMultipleStreams);
+			break;
+		}
+
+		case Boot::SectionType::UNK5: {
+			uint32 unk1 = Datum(chunk).u.i;
+			uint32 unk2 = Datum(chunk).u.i;
+			debugC(5, kDebugLoading, " - unk1 = 0x%x, unk2 = 0x%x", unk1, unk2);
+			break;
+		}
+
+		default: {
+			warning("Boot::Boot(): Unknown section type 0x%x", sectionType);
+			break;
+		}
+		}
+
+		sectionType = getSectionType(chunk);
+		notLastSection = Boot::SectionType::LAST != sectionType;
+	}
+}
+
+Boot::SectionType Boot::getSectionType(Chunk& chunk) {
+	Datum datum = Datum(chunk, DatumType::UINT16_1);
+	Boot::SectionType sectionType = static_cast<Boot::SectionType>(datum.u.i);
+	return sectionType;
+}
+
+uint32 Boot::getRootContextId() {
+	// TODO: Is the ID of the root context actually stored somewhere so
+	// we don't need to find it ourselves? Maybe it is always the
+	for (auto &declaration : _contextDeclarations) {
+		if (declaration._value->fileReferences.empty()) {
+			return declaration._value->fileNumber;
+		}
+	}
+	return 0;
+}
+
+Boot::~Boot() {
+	delete _gameTitle;
+	_gameTitle = nullptr;
+
+	_contextDeclarations.clear();
+	_subfileDeclarations.clear();
+	_cursorDeclarations.clear();
+	_engineResourceDeclarations.clear();
+	_unknownDeclarations.clear();
+}
+#pragma endregion
+
+} // End of namespace MediaStation
diff --git a/engines/mediastation/boot.h b/engines/mediastation/boot.h
new file mode 100644
index 00000000000..fc95d59ffe1
--- /dev/null
+++ b/engines/mediastation/boot.h
@@ -0,0 +1,217 @@
+/* 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 "mediastation/datafile.h"
+#include "mediastation/subfile.h"
+
+#ifndef MEDIASTATION_BOOT_H
+#define MEDIASTATION_BOOT_H
+
+namespace MediaStation {
+
+// Contains information about the engine (also called
+//  "title compiler") used in this particular game.
+// Engine version information is not present in early games.
+class VersionInfo {
+public:
+	VersionInfo(Chunk &chunk);
+	~VersionInfo();
+
+	// The version number of this engine,
+	// in the form 4.0r8 (major . minor r revision).
+	uint32 majorVersion;
+	uint32 minorVersion;
+	uint32 revision;
+
+	// A textual description of this engine.
+	// Example: "Title Compiler T4.0r8 built Feb 13 1998 10:16:52"
+	//           ^^^^^^^^^^^^^^  ^^^^^
+	//           | Engine name   | Version number
+	Common::String *string;
+};
+
+class ContextDeclaration {
+public:
+	enum class SectionType {
+		EMPTY = 0x0000,
+		PLACEHOLDER = 0x0003,
+		FILE_NUMBER_1 = 0x0004,
+		FILE_NUMBER_2 = 0x0005,
+		FILE_REFERENCE = 0x0006,
+		CONTEXT_NAME = 0x0bb8
+	};
+
+	ContextDeclaration(Chunk &chunk);
+	~ContextDeclaration();
+
+	Common::Array<uint32> fileReferences;
+	uint32 fileNumber;
+	Common::String *contextName;
+	// Signal that there are no more declarations to read.
+	bool isLast;
+
+private:
+	ContextDeclaration::SectionType getSectionType(Chunk &chunk);
+};
+
+class UnknownDeclaration {
+public:
+	enum class SectionType {
+		EMPTY = 0x0000,
+		UNK_1 = 0x0009,
+		UNK_2 = 0x0004
+	};
+
+	uint16 _unk;
+	// Signal that there are no more declarations to read.
+	bool _isLast;
+
+	UnknownDeclaration(Chunk &chunk);
+
+private:
+	UnknownDeclaration::SectionType getSectionType(Chunk& chunk);
+};
+
+class FileDeclaration {
+public:
+	enum class SectionType {
+		EMPTY = 0x0000,
+		FILE_ID = 0x002b,
+		FILE_NAME_AND_TYPE = 0x002d
+	};
+
+	// Indicates where this file is intended to be stored.
+	// NOTE: This might not be correct and this might be a more general "file type".
+	enum class IntendedLocation {
+		// Usually all files that have numbers remain on the CD-ROM.
+		CD_ROM = 0x0007,
+		// These UNKs only appear in George Shrinks.
+		UNK1 = 0x0008,
+		UNK2 = 0x0009,
+		// Usually only INSTALL.CXT is copied to the hard disk.
+		HARD_DISK = 0x000b
+	};
+
+	FileDeclaration(Chunk &chunk);
+	~FileDeclaration();
+
+	uint32 _id;
+	IntendedLocation _intendedLocation;
+	Common::String *_name;
+	// Signal that there are no more declarations to read.
+	bool _isLast;
+
+private:
+	FileDeclaration::SectionType getSectionType(Chunk &chunk);
+};
+
+class SubfileDeclaration {
+public:
+	enum class SectionType {
+		EMPTY = 0x0000,
+		ASSET_ID = 0x002a,
+		FILE_ID = 0x002b,
+		START_OFFSET = 0x002c
+	};
+
+	SubfileDeclaration(Chunk &chunk);
+
+	uint16 _assetId;
+	uint16 _fileId;
+	uint32 _startOffsetInFile;
+	// Signal that there are no more context declarations to read.
+	bool _isLast;
+
+private:
+	SubfileDeclaration::SectionType getSectionType(Chunk &chunk);
+};
+
+// Declares a cursor, which is stored as a cursor resource in the game executable.
+class CursorDeclaration {
+public:
+	CursorDeclaration(Chunk &chunk);
+	~CursorDeclaration();
+
+	uint16 _id;
+	uint16 _unk;
+	Common::String *_name;
+};
+
+class EngineResourceDeclaration {
+public:
+	Common::String *_resourceName;
+	int _resourceId;
+
+	EngineResourceDeclaration(Common::String *resourceName, int resourceId);
+	~EngineResourceDeclaration();
+};
+
+class Boot : Datafile {
+private:
+	enum class SectionType {
+		LAST = 0x0000,
+		EMPTY = 0x002e,
+		CONTEXT_DECLARATION = 0x0002,
+		VERSION_INFORMATION = 0x0190,
+		UNK1 = 0x0191,
+		UNK2 = 0x0192,
+		UNK3 = 0x0193,
+		ENGINE_RESOURCE = 0x0bba,
+		ENGINE_RESOURCE_ID = 0x0bbb,
+		UNKNOWN_DECLARATION = 0x0007,
+		FILE_DECLARATION = 0x000a,
+		SUBFILE_DECLARATION = 0x000b,
+		UNK5 = 0x000c,
+		CURSOR_DECLARATION = 0x0015,
+		ENTRY_SCREEN = 0x002f,
+		ALLOW_MULTIPLE_SOUNDS = 0x0035,
+		ALLOW_MULTIPLE_STREAMS = 0x0036,
+		UNK4 = 0x057b
+	};
+
+	Subfile subfile;
+
+	Boot::SectionType getSectionType(Chunk &chunk);
+
+public:
+	Common::String *_gameTitle = nullptr;
+	VersionInfo *_versionInfo = nullptr;
+	Common::String *_sourceString = nullptr;
+	Common::HashMap<uint32, ContextDeclaration *> _contextDeclarations;
+	Common::Array<UnknownDeclaration *> _unknownDeclarations;
+	Common::HashMap<uint32, FileDeclaration *> _fileDeclarations;
+	Common::HashMap<uint32, SubfileDeclaration *> _subfileDeclarations;
+	Common::HashMap<uint32, CursorDeclaration *> _cursorDeclarations;
+	Common::HashMap<uint32, EngineResourceDeclaration *> _engineResourceDeclarations;
+
+	uint32 _entryContextId = 0;
+	bool _allowMultipleSounds = false;
+	bool _allowMultipleStreams = false;
+
+	Boot(const Common::Path &path);
+	~Boot();
+
+	uint32 getRootContextId();
+};
+
+} // End of namespace MediaStation
+
+#endif
\ No newline at end of file
diff --git a/engines/mediastation/chunk.cpp b/engines/mediastation/chunk.cpp
new file mode 100644
index 00000000000..58d14785c82
--- /dev/null
+++ b/engines/mediastation/chunk.cpp
@@ -0,0 +1,39 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 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 "mediastation/chunk.h"
+#include "mediastation/debugchannels.h"
+
+namespace MediaStation {
+
+Chunk::Chunk(Common::SeekableReadStream *stream) : _input(stream), _dataStartOffset(0), _dataEndOffset(0) {
+	// READ THE HEADER.
+	id = _input->readUint32BE();
+	length = _input->readUint32LE();
+	_dataStartOffset = pos();
+	_dataEndOffset = _dataStartOffset + length;
+	debugC(5, kDebugLoading, "Chunk::Chunk(): Got chunk with ID \"%s\" and size 0x%x", tag2str(id), length);
+	if (length == 0) {
+		error("Encountered a zero-length chunk. This usually indicates corrupted data - maybe a CD-ROM read error.");
+	}
+}
+
+} // End of namespace MediaStation
\ No newline at end of file
diff --git a/engines/mediastation/chunk.h b/engines/mediastation/chunk.h
new file mode 100644
index 00000000000..1a4eacf906a
--- /dev/null
+++ b/engines/mediastation/chunk.h
@@ -0,0 +1,93 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "common/file.h"
+
+#ifndef MEDIASTATION_CHUNK_H
+#define MEDIASTATION_CHUNK_H
+
+namespace MediaStation {
+
+class Chunk : public Common::SeekableReadStream {
+private:
+	Common::SeekableReadStream *_input = nullptr;
+	uint32 _dataStartOffset = 0;
+	uint32 _dataEndOffset = 0;
+
+public:
+	uint32 id = 0;
+	uint32 length = 0;
+
+	Chunk() = default;
+	Chunk(Common::SeekableReadStream *stream);
+
+	uint32 bytesRemaining() {
+		return _dataEndOffset - pos();
+	}
+
+	// ReadStream implementation
+	bool eos() const {
+		return _input->eos();
+	}
+	bool err() const {
+		return _input->err();
+	}
+	void clearErr() {
+		_input->clearErr();
+	}
+	uint32 read(void *dataPtr, uint32 dataSize) {
+		if (pos() > _dataEndOffset) {
+			uint overrun = pos() - _dataEndOffset;
+			error("Attempted to read 0x%x bytes at a location 0x%x bytes past end of chunk (@0x%llx)", dataSize, overrun, pos());
+		} else {
+			return _input->read(dataPtr, dataSize);
+		}
+	}
+	int64 pos() const {
+		return _input->pos();
+	}
+	int64 size() const {
+		return _input->size();
+	}
+	bool seek(int64 offset, int whence = SEEK_SET) {
+		// TODO: This is a bad hack and should be cleaned up!
+		bool result = _input->seek(offset, whence);
+		if (result == false) {
+			return false;
+		}
+
+		if (pos() < _dataStartOffset) {
+			uint overrun = _dataStartOffset - offset;
+			error("Attempted to seek 0x%x bytes before start of chunk (@0x%llx)", overrun, pos());
+		} else if (pos() > _dataEndOffset) {
+			uint overrun = offset - _dataEndOffset;
+			error("Attempted to seek 0x%x bytes past end of chunk (@0x%llx)", overrun, pos());
+		}
+		return true;
+	}
+	bool skip(uint32 offset) {
+		return seek(offset, SEEK_CUR);
+	}
+};
+
+} // End of namespace MediaStation
+
+#endif
\ No newline at end of file
diff --git a/engines/mediastation/configure.engine b/engines/mediastation/configure.engine
new file mode 100644
index 00000000000..c8c45c48b07
--- /dev/null
+++ b/engines/mediastation/configure.engine
@@ -0,0 +1,3 @@
+# This file is included from the main "configure" script
+# add_engine [name] [desc] [build-by-default] [subengines] [base games] [deps]
+add_engine mediastation "MediaStation" no "" "" ""
diff --git a/engines/mediastation/context.cpp b/engines/mediastation/context.cpp
new file mode 100644
index 00000000000..bbdb7e8b1af
--- /dev/null
+++ b/engines/mediastation/context.cpp
@@ -0,0 +1,306 @@
+/* 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 "mediastation/mediastation.h"
+#include "mediastation/context.h"
+#include "mediastation/datum.h"
+#include "mediastation/debugchannels.h"
+
+#include "mediastation/bitmap.h"
+#include "mediastation/assets/canvas.h"
+#include "mediastation/assets/palette.h"
+#include "mediastation/assets/image.h"
+#include "mediastation/assets/path.h"
+#include "mediastation/assets/sound.h"
+#include "mediastation/assets/movie.h"
+#include "mediastation/assets/sprite.h"
+#include "mediastation/assets/hotspot.h"
+#include "mediastation/assets/timer.h"
+
+namespace MediaStation {
+
+Context::Context(const Common::Path &path) :
+	Datafile(path) {
+	// This stuff isn't part of any graphics palette.
+	readPreamble();
+
+	// READ THE FIRST SUBFILE.
+	Subfile subfile = Subfile(_stream);
+	Chunk chunk = subfile.nextChunk();
+	// First, read the header sections.
+	if (g_engine->isFirstGenerationEngine()) {
+		readOldStyleHeaderSections(subfile, chunk);
+	} else {
+		readNewStyleHeaderSections(subfile, chunk);
+	}
+	// Then, read any asset data.
+	chunk = subfile.currentChunk;
+	while (!subfile.atEnd()) {
+		readAssetInFirstSubfile(chunk);
+		if (!subfile.atEnd()) {
+			chunk = subfile.nextChunk();
+		}
+	}
+
+	// Then, assets in the rest of the subfiles.
+	for (uint i = 1; i < subfile_count; i++) {
+		subfile = Subfile(_stream);
+		readAssetFromLaterSubfile(subfile);
+	}
+}
+
+Context::~Context() {
+	delete _palette;
+	_palette = nullptr;
+	delete _parameters;
+	_parameters = nullptr;
+}
+
+bool Context::readPreamble() {
+	uint16 signature = _stream->readUint16LE();
+	if (signature != 0x4949) { // "II"
+		warning("Datafile::openFile(): Wrong signature for file %s. Got 0x%04X", _path.toString(Common::Path::kNativeSeparator).c_str(), signature);
+		close();
+		return false;
+	}
+	_stream->skip(2); // 0x00 0x00
+
+	unk1 = _stream->readUint32LE();
+	debugC(5, kDebugLoading, "Context::openFile(): unk1 = 0x%x", unk1);
+
+	subfile_count = _stream->readUint32LE();
+	// The total size of this file, including this header.
+	// (Basically the true file size shown on the filesystem.)
+	file_size = _stream->readUint32LE();
+	return true;
+}
+
+void Context::readOldStyleHeaderSections(Subfile &subfile, Chunk &chunk) {
+	error("Context::readOldStyleHeaderSections(): Not implemented yet");
+}
+
+void Context::readNewStyleHeaderSections(Subfile &subfile, Chunk &chunk) {
+	// READ THE PALETTE.
+	bool moreSectionsToRead = (chunk.id == MKTAG('i', 'g', 'o', 'd'));
+	if (!moreSectionsToRead) {
+		warning("Context::readNewStyleHeaderSections(): Got no header sections (@0x%llx)", chunk.pos());
+	}
+
+	while (moreSectionsToRead) {
+		// VERIFY THIS CHUNK IS A HEADER.
+		// TODO: What are the situations when it's not?
+		uint16 sectionType = Datum(chunk, DatumType::UINT16_1).u.i;
+		debugC(5, kDebugLoading, "Context::readNewStyleHeaderSections(): sectionType = 0x%x (@0x%llx)", sectionType, chunk.pos());
+		bool chunkIsHeader = (sectionType == 0x000d);
+		if (!chunkIsHeader) {
+			error("Context::readNewStyleHeaderSections(): Expected header chunk, got %s (@0x%llx)", tag2str(chunk.id), chunk.pos());
+		}
+
+		// READ THIS HEADER SECTION.
+		moreSectionsToRead = readHeaderSection(subfile, chunk);
+		if (subfile.atEnd()) {
+			break;
+		} else {
+			debugC(5, kDebugLoading, "\nContext::readNewStyleHeaderSections(): Getting next chunk (@0x%llx)", chunk.pos());
+			chunk = subfile.nextChunk();
+			moreSectionsToRead = (chunk.id == MKTAG('i', 'g', 'o', 'd'));
+		}
+	}
+	debugC(5, kDebugLoading, "Context::readNewStyleHeaderSections(): Finished reading sections (@0x%llx)", chunk.pos());
+}
+
+void Context::readAssetInFirstSubfile(Chunk &chunk) {
+	if (chunk.id == MKTAG('i', 'g', 'o', 'd')) {
+		warning("Context::readAssetInFirstSubfile(): Skippping \"igod\" asset link chunk");
+		chunk.skip(chunk.bytesRemaining());
+		return;
+	}
+
+	// TODO: Make sure this is not an asset link.
+	Asset *asset = g_engine->_assetsByChunkReference.getValOrDefault(chunk.id);
+	if (asset == nullptr) {
+		error("Context::readAssetInFirstSubfile(): Asset for chunk \"%s\" (0x%x) does not exist or has not been read yet in this title. (@0x%llx)", tag2str(chunk.id), chunk.id, chunk.pos());
+	}
+	debugC(5, kDebugLoading, "\nContext::readAssetInFirstSubfile(): Got asset with chunk ID %s in first subfile (type: 0x%x) (@0x%llx)", tag2str(chunk.id), asset->type(), chunk.pos());
+	asset->readChunk(chunk);
+}
+
+void Context::readAssetFromLaterSubfile(Subfile &subfile) {
+	Chunk chunk = subfile.nextChunk();
+	Asset *asset = g_engine->_assetsByChunkReference.getValOrDefault(chunk.id);
+	if (asset == nullptr) {
+		error("Context::readAssetFromLaterSubfile(): Asset for chunk \"%s\" (0x%x) does not exist or has not been read yet in this title. (@0x%llx)", tag2str(chunk.id), chunk.id, chunk.pos());
+	}
+	debugC(5, kDebugLoading, "\nContext::readAssetFromLaterSubfile(): Got asset with chunk ID %s in later subfile (type: 0x%x) (@0x%llx)", tag2str(chunk.id), asset->type(), chunk.pos());
+	asset->readSubfile(subfile, chunk);
+}
+
+bool Context::readHeaderSection(Subfile &subfile, Chunk &chunk) {
+	uint16 sectionType = Datum(chunk, DatumType::UINT16_1).u.i;
+	debugC(5, kDebugLoading, "Context::readHeaderSection(): sectionType = 0x%x (@0x%llx)", sectionType, chunk.pos());
+	switch ((SectionType)sectionType) {
+	case SectionType::PARAMETERS: {
+		if (_parameters != nullptr) {
+			error("Context::readHeaderSection(): Got multiple parameters (@0x%llx)", chunk.pos());
+		}
+		_parameters = new ContextParameters(chunk);
+		break;
+	}
+
+	case SectionType::ASSET_LINK: {
+		warning("Context::readHeaderSection(): ASSET_LINK not implemented yet");
+		break;
+	}
+
+	case SectionType::PALETTE: {
+		if (_palette != nullptr) {
+			error("Context::readHeaderSection(): Got multiple palettes (@0x%llx)", chunk.pos());
+		}
+		// TODO: Avoid the copying here!
+		const uint PALETTE_ENTRIES = 256;
+		const uint PALETTE_BYTES = PALETTE_ENTRIES * 3;
+		byte* buffer = new byte[PALETTE_BYTES];
+		chunk.read(buffer, PALETTE_BYTES);
+		_palette = new Graphics::Palette(buffer, PALETTE_ENTRIES);
+		delete[] buffer;
+		debugC(5, kDebugLoading, "Context::readHeaderSection(): Read palette");
+		// This is likely just an ending flag that we expect to be zero.
+		Datum(chunk, DatumType::UINT16_1).u.i;
+		break;
+	}
+
+	case SectionType::ASSET_HEADER: {
+		Asset *asset = nullptr;
+		AssetHeader *header = new AssetHeader(chunk);
+		switch (header->_type) {
+		case AssetType::IMAGE:
+			asset = new Image(header);
+			break;
+
+		case AssetType::MOVIE:
+			asset = new Movie(header);
+			break;
+
+		case AssetType::SOUND:
+			asset = new Sound(header);
+			break;
+
+		case AssetType::PALETTE:
+			asset = new Palette(header);
+			break;
+
+		case AssetType::PATH:
+			asset = new Path(header);
+			break;
+
+		case AssetType::TIMER:
+			asset = new Timer(header);
+			break;
+
+		case AssetType::HOTSPOT:
+			asset = new Hotspot(header);
+			break;
+
+		case AssetType::SPRITE:
+			asset = new Sprite(header);
+			break;
+
+		case AssetType::CANVAS:
+			asset = new Canvas(header);
+			break;
+
+		case AssetType::SCREEN:
+			if (_screenAsset != nullptr) {
+				error("Context::readHeaderSection(): Got multiple screen assets in the same context");
+			}
+			_screenAsset = header;
+			break;
+
+		default:
+			error("Context::readHeaderSection(): No class for asset type 0x%x (@0x%llx)", header->_type, chunk.pos());
+		}
+
+		if (g_engine->_assets.contains(header->_id)) {
+			error("Context::readHeaderSection(): Asset with ID 0x%d was already defined in this title", header->_id);
+		}
+		g_engine->_assets.setVal(header->_id, asset);
+		if (header->_chunkReference != 0) {
+			debugC(5, kDebugLoading, "Context::readHeaderSection(): Storing asset with chunk ID \"%s\" (0x%x)", tag2str(header->_chunkReference), header->_chunkReference);
+			g_engine->_assetsByChunkReference.setVal(header->_chunkReference, asset);
+		}
+		// TODO: Store the movie chunk references better.
+		if (header->_audioChunkReference != 0) {
+			g_engine->_assetsByChunkReference.setVal(header->_audioChunkReference, asset);
+		}
+		if (header->_animationChunkReference != 0) {
+			g_engine->_assetsByChunkReference.setVal(header->_animationChunkReference, asset);
+		}
+		// TODO: This datum only appears sometimes.
+		Datum(chunk).u.i;
+		break;
+	}
+
+	case SectionType::FUNCTION: {
+		Function *function = new Function(chunk);
+		g_engine->_functions.setVal(function->_id, function);
+		if (!g_engine->isFirstGenerationEngine()) {
+			Datum(chunk).u.i; // Should be zero.
+		}
+		break;
+	}
+
+	case SectionType::END: {
+		error("Context::readHeaderSection(): END Not implemented yet");
+		return false;
+	}
+
+	case SectionType::EMPTY: {
+		error("Context::readHeaderSection(): EMPTY Not implemented yet");
+		break;
+	}
+
+	case SectionType::POOH: {
+		error("Context::readHeaderSection(): POOH Not implemented yet");
+		break;
+	}
+
+	default: {
+		error("Context::readHeaderSection(): Unknown section type 0x%x (@0x%llx)", sectionType, chunk.pos());
+	}
+	}
+
+	return true;
+}
+
+void Context::play() {
+	// FIND AND EXECUTE THE ENTRY SCRIPT.
+	// The entry script is stored in the asset with the same ID as the context.
+	// It's the asset that has a SCREEN asset type.
+	if (_screenAsset == nullptr) {
+		error("Context::play(): No entry script exists for this context, cannot play it");
+	}
+	EventHandler *entryHandler = nullptr; //_screenAsset->_eventHandlers.getVal(uint32(EventHandler::Type::Entry));
+	// So how can we actually execute this script?
+
+	// FIND AND EXECUTE THE EXIT SCRIPT.
+}
+
+} // End of namespace MediaStation
diff --git a/engines/mediastation/context.h b/engines/mediastation/context.h
new file mode 100644
index 00000000000..4e67c92205f
--- /dev/null
+++ b/engines/mediastation/context.h
@@ -0,0 +1,69 @@
+/* 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 MEDIASTATION_CONTEXT_H
+#define MEDIASTATION_CONTEXT_H
+
+#include "mediastation/datafile.h"
+#include "mediastation/contextparameters.h"
+#include "mediastation/assetheader.h"
+#include "mediastation/mediascript/function.h"
+
+namespace MediaStation {
+
+class Context : Datafile {
+public:
+	Context(const Common::Path &path);
+	~Context();
+
+	bool readPreamble();
+
+	uint32 unk1;
+	uint32 subfile_count;
+	uint32 file_size;
+	Graphics::Palette *_palette = nullptr;
+	ContextParameters *_parameters = nullptr;
+	AssetHeader *_screenAsset = nullptr;
+
+private:
+	enum class SectionType {
+		EMPTY = 0x0000,
+		OLD_STYLE = 0x000d,
+		PARAMETERS = 0x000e,
+		PALETTE = 0x05aa,
+		END = 0x0010,
+		ASSET_HEADER = 0x0011,
+		POOH = 0x057a,
+		ASSET_LINK = 0x0013,
+		FUNCTION = 0x0031
+	};
+	void readOldStyleHeaderSections(Subfile &subfile, Chunk &chunk);
+	void readNewStyleHeaderSections(Subfile &subfile, Chunk &chunk);
+	bool readHeaderSection(Subfile &subfile, Chunk &chunk);
+
+	void readAssetInFirstSubfile(Chunk &chunk);
+	void readAssetFromLaterSubfile(Subfile &subfile);
+	void play();
+};
+
+} // End of namespace MediaStation
+
+#endif
\ No newline at end of file
diff --git a/engines/mediastation/contextparameters.cpp b/engines/mediastation/contextparameters.cpp
new file mode 100644
index 00000000000..d2327d9577a
--- /dev/null
+++ b/engines/mediastation/contextparameters.cpp
@@ -0,0 +1,93 @@
+/* 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 "mediastation/mediastation.h"
+#include "mediastation/datum.h"
+#include "mediastation/contextparameters.h"
+#include "mediastation/mediascript/variable.h"
+#include "mediastation/debugchannels.h"
+
+namespace MediaStation {
+
+ContextParameters::ContextParameters(Chunk &chunk) : contextName(nullptr) {
+	fileNumber = Datum(chunk, DatumType::UINT16_1).u.i;
+	uint sectionType = Datum(chunk, DatumType::UINT16_1).u.i;
+	while ((SectionType)sectionType != SectionType::EMPTY) {
+		debugC(5, kDebugLoading, "ContextParameters::ContextParameters: sectionType = 0x%x (@0x%llx)", sectionType, chunk.pos());
+		switch ((SectionType)sectionType) {
+		case SectionType::NAME: {
+			uint repeatedFileNumber = Datum(chunk, DatumType::UINT16_1).u.i;
+			if (repeatedFileNumber != fileNumber) {
+				warning("ContextParameters::ContextParameters(): Repeated file number didn't match: %d != %d", repeatedFileNumber, fileNumber);
+			}
+			contextName = Datum(chunk, DatumType::STRING).u.string;
+			// TODO: This is likely just an end flag.
+			uint unk1 = Datum(chunk, DatumType::UINT16_1).u.i;
+		}
+
+		case SectionType::FILE_NUMBER: {
+			error("ContextParameters::ContextParameters(): Section type FILE_NUMBER not implemented yet");
+			break;
+		}
+
+		case SectionType::VARIABLE: {
+			uint repeatedFileNumber = Datum(chunk, DatumType::UINT16_1).u.i;
+			if (repeatedFileNumber != fileNumber) {
+				warning("ContextParameters::ContextParameters(): Repeated file number didn't match: %d != %d", repeatedFileNumber, fileNumber);
+			}
+			// The trouble here is converting the variable to an operand.
+			// They are two totally separate types!
+			Variable *variable = new Variable(chunk);
+			Operand operand;
+			if (g_engine->_variables.contains(variable->id)) {
+				error("ContextParameters::ContextParameters(): Variable with ID 0x%x already exists", variable->id);
+			} else {
+				g_engine->_variables.setVal(variable->id, variable);
+				debugC(5, kDebugScript, "ContextParameters::ContextParameters(): Created global variable %d", variable->id);
+			}
+			break;
+		}
+
+		case SectionType::BYTECODE: {
+			Function *function = new Function(chunk);
+			_functions.setVal(function->_id, function);
+			break;
+		}
+
+		default: {
+			error("ContextParameters::ContextParameters(): Unknown section type 0x%x", sectionType);
+		}
+		}
+		sectionType = Datum(chunk, DatumType::UINT16_1).u.i;
+	}
+}
+
+ContextParameters::~ContextParameters() {
+	delete contextName;
+	contextName = nullptr;
+
+	for (auto it = _functions.begin(); it != _functions.end(); ++it) {
+		delete it->_value;
+	}
+	_functions.clear();
+}
+
+} // End of namespace MediaStation
diff --git a/engines/mediastation/contextparameters.h b/engines/mediastation/contextparameters.h
new file mode 100644
index 00000000000..ff4114bfbb0
--- /dev/null
+++ b/engines/mediastation/contextparameters.h
@@ -0,0 +1,55 @@
+/* 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 MEDIASTATION_CONTEXTPARAMETERS_H
+#define MEDIASTATION_CONTEXTPARAMETERS_H
+
+#include "mediastation/mediastation.h"
+#include "mediastation/mediascript/variable.h"
+#include "mediastation/mediascript/function.h"
+
+namespace MediaStation {
+
+class ContextParameters {
+private:
+	enum class SectionType {
+		EMPTY = 0x0000,
+		VARIABLE = 0x0014,
+		NAME = 0x0bb9,
+		FILE_NUMBER = 0x0011,
+		BYTECODE = 0x0017
+	};
+
+public:
+	ContextParameters(Chunk &chunk);
+	~ContextParameters();
+
+	// This is not an internal file ID, but the number of the file
+	// as it appears in the filename. For instance, the context in
+	// "100.cxt" would have file number 100.
+	uint fileNumber;
+	Common::String *contextName;
+	Common::HashMap<uint32, Function *> _functions;
+};
+
+} // End of namespace MediaStation
+
+#endif
\ No newline at end of file
diff --git a/engines/mediastation/credits.pl b/engines/mediastation/credits.pl
new file mode 100644
index 00000000000..7db9f527098
--- /dev/null
+++ b/engines/mediastation/credits.pl
@@ -0,0 +1,3 @@
+begin_section("Media Station");
+	add_person("Nathanael Gentry", "npjg", "");
+end_section();
diff --git a/engines/mediastation/datafile.cpp b/engines/mediastation/datafile.cpp
new file mode 100644
index 00000000000..132e7d834bf
--- /dev/null
+++ b/engines/mediastation/datafile.cpp
@@ -0,0 +1,55 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "common/file.h"
+
+#include "mediastation/mediastation.h"
+#include "mediastation/datafile.h"
+
+namespace MediaStation {
+
+Datafile::Datafile(const Common::Path &path) {
+	openFile(path);
+}
+
+Datafile::~Datafile() {
+	close();
+}
+
+bool Datafile::openFile(const Common::Path &path) {
+	Common::File *file = new Common::File();
+	if (path.empty() || !file->open(path)) {
+		error("Datafile::openFile(): Error opening file %s", path.toString(Common::Path::kNativeSeparator).c_str());
+		delete file;
+		return false;
+	}
+
+	_path = path;
+	_stream = file;
+	return true;
+}
+
+void Datafile::close() {
+	delete _stream;
+	_stream = nullptr;
+}
+
+} // End of namespace MediaStation
diff --git a/engines/mediastation/datafile.h b/engines/mediastation/datafile.h
new file mode 100644
index 00000000000..0332596fe7e
--- /dev/null
+++ b/engines/mediastation/datafile.h
@@ -0,0 +1,44 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "common/file.h"
+
+#ifndef MEDIASTATION_DATAFILE_H
+#define MEDIASTATION_DATAFILE_H
+
+namespace MediaStation {
+
+class Datafile {
+public:
+	Datafile(const Common::Path &path);
+	virtual ~Datafile();
+
+	virtual bool openFile(const Common::Path &path);
+	virtual void close();
+
+protected:
+	Common::Path _path;
+	Common::SeekableReadStream *_stream = nullptr;
+};
+
+} // End of namespace MediaStation
+
+#endif
\ No newline at end of file
diff --git a/engines/mediastation/datum.cpp b/engines/mediastation/datum.cpp
new file mode 100644
index 00000000000..3475ec2f51c
--- /dev/null
+++ b/engines/mediastation/datum.cpp
@@ -0,0 +1,104 @@
+/* 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 "mediastation/mediastation.h"
+#include "mediastation/chunk.h"
+#include "mediastation/datum.h"
+#include "mediastation/debugchannels.h"
+
+namespace MediaStation {
+
+Datum::Datum() {
+	t = DatumType::INVALID;
+	u.i = 0;
+}
+
+Datum::Datum(Common::SeekableReadStream &chunk) {
+	t = static_cast<DatumType>(chunk.readUint16LE());
+	readWithType(chunk);
+}
+
+Datum::Datum(Common::SeekableReadStream &chunk, DatumType expectedType) {
+	t = static_cast<DatumType>(chunk.readUint16LE());
+	if (t != expectedType) {
+		error("Datum::Datum(): Expected datum type 0x%x, got 0x%x (@0x%llx)", expectedType, t, chunk.pos());
+	}
+	readWithType(chunk);
+}
+
+void Datum::readWithType(Common::SeekableReadStream &chunk) {
+	debugC(9, kDebugLoading, "Datum::Datum(): Type 0x%x (@0x%llx)", t, chunk.pos());
+	if (DatumType::UINT8 == t) {
+		u.i = chunk.readByte();
+
+	} else if (DatumType::UINT16_1 == t || DatumType::UINT16_2 == t) {
+		u.i = chunk.readUint16LE();
+
+	} else if (DatumType::INT16_1 == t || DatumType::INT16_2 == t) {
+		u.i = chunk.readSint16LE();
+
+	} else if (DatumType::UINT32_1 == t || DatumType::UINT32_2 == t) {
+		u.i = chunk.readUint32LE();
+
+	} else if (DatumType::FLOAT64_1 == t || DatumType::FLOAT64_2 == t) {
+		u.f = chunk.readDoubleLE();
+
+	} else if (DatumType::STRING == t || DatumType::FILENAME == t) {
+		// TODO: This copies the string. Can we read it directly from the chunk?
+		int size = Datum(chunk, DatumType::UINT32_1).u.i;
+		char *buffer = new char[size + 1];
+		chunk.read(buffer, size);
+		buffer[size] = '\0';
+		u.string = new Common::String(buffer);
+		delete[] buffer;
+
+	} else if (DatumType::POINT_1 == t || DatumType::POINT_2 == t) {
+		uint16 x = Datum(chunk, DatumType::INT16_2).u.i;
+		uint16 y = Datum(chunk, DatumType::INT16_2).u.i;
+		u.point = new Common::Point(x, y);
+
+	} else if (DatumType::BOUNDING_BOX == t) {
+		Common::Point *left_top = Datum(chunk, DatumType::POINT_2).u.point;
+		Common::Point *dimensions = Datum(chunk, DatumType::POINT_1).u.point;
+		u.bbox = new Common::Rect(*left_top, dimensions->x, dimensions->y);
+		delete left_top;
+		delete dimensions;
+
+	} else if (DatumType::POLYGON == t) {
+		uint16 total_points = Datum(chunk, DatumType::UINT16_1).u.i;
+		for (int i = 0; i < total_points; i++) {
+			Common::Point *point = Datum(chunk, DatumType::POINT_1).u.point;
+			u.polygon->push_back(point);
+		}
+
+	} else if (DatumType::PALETTE == t) {
+		u.palette = new unsigned char[0x300];
+		chunk.read(u.palette, 0x300);
+
+	} else if (DatumType::REFERENCE == t) {
+		u.chunkRef = chunk.readUint32BE();
+
+	} else {
+		error("Unknown datum type: 0x%x (@0x%llx)", t, chunk.pos());
+	}
+}
+
+} // End of namespace MediaStation
\ No newline at end of file
diff --git a/engines/mediastation/datum.h b/engines/mediastation/datum.h
new file mode 100644
index 00000000000..2336bd06c9f
--- /dev/null
+++ b/engines/mediastation/datum.h
@@ -0,0 +1,92 @@
+/* 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/str.h"
+#include "common/rect.h"
+
+#include "mediastation/chunk.h"
+
+#ifndef MEDIASTATION_DATUM_H
+#define MEDIASTATION_DATUM_H
+
+namespace MediaStation {
+
+enum class DatumType {
+	// The INVALID type isn't a type we see in data files; it is just a
+	// default initialization value.
+	INVALID = 0x0000,
+
+	UINT8 = 0x0002,
+	// TODO: Understand why there are different (u)int16 type codes.
+	UINT16_1 = 0x0003,
+	UINT16_2 = 0x0013,
+	INT16_1 = 0x0006,
+	INT16_2 = 0x0010,
+	// TODO: Understand why there are two different uint32 type codes.
+	UINT32_1 = 0x0004,
+	UINT32_2 = 0x0007,
+	// TODO: Understand why there are two different float64 type codes.
+	FLOAT64_1 = 0x0011,
+	FLOAT64_2 = 0x0009,
+	STRING = 0x0012,
+	FILENAME = 0x000a,
+	POINT_1 = 0x000f,
+	POINT_2 = 0x000e,
+	BOUNDING_BOX = 0x000d,
+	POLYGON = 0x001d,
+	// These are other types.
+	PALETTE = 0x05aa,
+	REFERENCE = 0x001b
+};
+
+class Point;
+class BoundingBox;
+class Polygon;
+class Reference;
+
+// It is the caller's responsibility to delete any heap items
+// that are created as part of a datum. The datum is really
+// just a container.
+class Datum {
+public:
+	DatumType t;
+	union {
+		int i;
+		double f;
+		uint32 chunkRef;
+		Common::String *string;
+		Common::Array<Common::Point *> *polygon;
+		Common::Point *point;
+		Common::Rect *bbox;
+		unsigned char *palette;
+	} u;
+
+	Datum();
+	Datum(Common::SeekableReadStream &chunk);
+	Datum(Common::SeekableReadStream &chunk, DatumType expectedType);
+
+private:
+	void readWithType(Common::SeekableReadStream &chunk);
+};
+
+} // End of namespace MediaStation
+
+#endif
diff --git a/engines/mediastation/debugchannels.h b/engines/mediastation/debugchannels.h
new file mode 100644
index 00000000000..7f26b04dfbb
--- /dev/null
+++ b/engines/mediastation/debugchannels.h
@@ -0,0 +1,49 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 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 MEDIASTATION_DEBUG_CHANNELS_H
+#define MEDIASTATION_DEBUG_CHANNELS_H
+
+// This is a convenience so we don't have to include
+// two files in every translation unit where we want
+// debugging support.
+#include "common/debug.h"
+
+namespace MediaStation {
+
+// TODO: Finish comments that describe the various debug levels
+enum DebugChannels {
+	kDebugGraphics = 1,
+	kDebugPath,
+	kDebugScan,
+
+	// Level 5: Decompiled Script Lines
+	// Level 7: Instruction Types & Opcodes
+	kDebugScript,
+	kDebugEvents,
+
+	// Level 9: Individual Datums
+	kDebugLoading
+};
+
+} // End of namespace MediaStation
+
+#endif
\ No newline at end of file
diff --git a/engines/mediastation/detection.cpp b/engines/mediastation/detection.cpp
new file mode 100644
index 00000000000..a88cee1ce07
--- /dev/null
+++ b/engines/mediastation/detection.cpp
@@ -0,0 +1,50 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "base/plugins.h"
+
+#include "common/config-manager.h"
+#include "common/file.h"
+#include "common/md5.h"
+#include "common/str-array.h"
+#include "common/translation.h"
+#include "common/util.h"
+
+#include "mediastation/detection.h"
+#include "mediastation/detection_tables.h"
+#include "mediastation/debugchannels.h"
+#include "mediastation/mediastation.h"
+
+const DebugChannelDef MediaStationMetaEngineDetection::debugFlagList[] = {
+	{ MediaStation::kDebugGraphics, "graphics", "Graphics debug level" },
+	{ MediaStation::kDebugPath, "path", "Pathfinding debug level" },
+	{ MediaStation::kDebugScan, "scan", "Scan for unrecognised games" },
+	{ MediaStation::kDebugScript, "script", "Enable debug script dump" },
+	{ MediaStation::kDebugEvents, "events", "Events processing" },
+	{ MediaStation::kDebugLoading, "loading", "File loading" },
+	DEBUG_CHANNEL_END
+};
+
+MediaStationMetaEngineDetection::MediaStationMetaEngineDetection() : AdvancedMetaEngineDetection(
+	    MediaStation::gameDescriptions, MediaStation::mediastationGames) {
+}
+
+REGISTER_PLUGIN_STATIC(MEDIASTATION_DETECTION, PLUGIN_TYPE_ENGINE_DETECTION, MediaStationMetaEngineDetection);
diff --git a/engines/mediastation/detection.h b/engines/mediastation/detection.h
new file mode 100644
index 00000000000..03f201ecbfb
--- /dev/null
+++ b/engines/mediastation/detection.h
@@ -0,0 +1,61 @@
+/* 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 MEDIASTATION_DETECTION_H
+#define MEDIASTATION_DETECTION_H
+
+#include "engines/advancedDetector.h"
+
+namespace MediaStation {
+
+extern const PlainGameDescriptor mediastationGames[];
+
+extern const ADGameDescription gameDescriptions[];
+
+#define GAMEOPTION_ORIGINAL_SAVELOAD GUIO_GAMEOPTIONS1
+
+} // End of namespace MediaStation
+
+class MediaStationMetaEngineDetection : public AdvancedMetaEngineDetection<ADGameDescription> {
+	static const DebugChannelDef debugFlagList[];
+
+public:
+	MediaStationMetaEngineDetection();
+	~MediaStationMetaEngineDetection() override {}
+
+	const char *getName() const override {
+		return "mediastation";
+	}
+
+	const char *getEngineName() const override {
+		return "Media Station";
+	}
+
+	const char *getOriginalCopyright() const override {
+		return "(C) 1994 - 1999 Media Station, Inc.";
+	}
+
+	const DebugChannelDef *getDebugChannels() const override {
+		return debugFlagList;
+	}
+};
+
+#endif // MEDIASTATION_DETECTION_H
diff --git a/engines/mediastation/detection_tables.h b/engines/mediastation/detection_tables.h
new file mode 100644
index 00000000000..5224962d9ca
--- /dev/null
+++ b/engines/mediastation/detection_tables.h
@@ -0,0 +1,49 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 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/>.
+ *
+ */
+
+namespace MediaStation {
+
+const PlainGameDescriptor mediastationGames[] = {
+	// Sample Title
+	{ "mediastation", "Media Station Game" },
+
+	// Commercially released games
+
+	// Internal betas/unreleased games
+	{ 0, 0 }
+};
+
+const ADGameDescription gameDescriptions[] = {
+	// For testing purposes, any folder with a "MediaStation" file in it can be run.
+	{
+		"mediastation",
+		nullptr,
+		AD_ENTRY1s("MediaStation", 0, AD_NO_SIZE),
+		Common::EN_ANY,
+		Common::kPlatformWindows,
+		ADGF_UNSTABLE,
+		GUIO1(GUIO_NOASPECT)
+	},
+
+	AD_TABLE_END_MARKER
+};
+
+} // End of namespace MediaStation
diff --git a/engines/mediastation/mediascript/builtins.h b/engines/mediastation/mediascript/builtins.h
new file mode 100644
index 00000000000..cf7afedcb64
--- /dev/null
+++ b/engines/mediastation/mediascript/builtins.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 MEDIASTATION_MEDIASCRIPT_BUILTINS_H
+#define MEDIASTATION_MEDIASCRIPT_BUILTINS_H
+
+namespace MediaStation {
+
+enum class BuiltInFunction {
+	// TODO: Figure out if effectTransitionOnSync = 13 is consistent across titles?
+	effectTransition = 12, // PARAMS: 1
+	drawing = 37, // PARAMS: 5
+	// TODO: Figure out if TimeOfDay = 101 is consistent across titles.
+	DebugPrint = 180, // PARAMS: 1+
+	// TODO: Figure out code for DebugPrint.
+	// TODO: Figure out code for Quit.
+};
+
+enum class BuiltInMethod {
+	// TODO: What object types does CursorSet apply to?
+	// Currently it's only in var_7be1_cursor_currentTool in
+	// IBM/Crayola.
+	cursorSet = 200, // PARAMS: 0
+	spatialHide = 203, // PARAMS: 1
+	spatialMoveTo = 204, // PARAMS: 2
+	spatialZMoveTo = 216, // PARAMS: 1
+	spatialShow = 202, // PARAMS: 1
+	timePlay = 206, // PARAMS: 1
+	timeStop = 207, // PARAMS: 0
+	isPlaying = 372, // PARAMS: 0
+	setDissolveFactor = 241, // PARAMS: 1
+
+	// HOTSPOT METHODS.
+	mouseActivate = 210, // PARAMS: 1
+	mouseDeactivate = 211, // PARAMS: 0
+	xPosition = 233, // PARAMS: 0
+	yPosiion = 234, // PARAMS: 0
+	TriggerAbsXPosition = 321, // PARAMS: 0
+	TriggerAbsYPosition = 322, // PARAMS: 0
+	isActive = 371, // PARAMS: 0
+
+	// IMAGE METHODS.
+	Width = 235, // PARAMS: 0
+	Height = 236, // PARAMS: 0
+	isVisible = 269,
+
+	// SPRITE METHODS.
+	movieReset = 219, // PARAMS: 0
+
+	// STAGE METHODS.
+	setWorldSpaceExtent = 363, // PARAMS: 2
+	setBounds = 287, // PARAMS: 4
+
+	// CAMERA METHODS.
+	stopPan = 350, // PARAMS: 0
+	viewportMoveTo = 352, // PARAMS: 2
+	yViewportPosition = 357, // PARAMS: 0
+	panTo = 370, // PARAMS: 4
+
+	// CANVAS METHODS.
+	clearToPalette = 379, // PARAMS: 1
+
+	// DOCUMENT METHODS.
+	loadContext = 374, // PARAMS: 1
+	releaseContext = 375, // PARAMS: 1
+	branchToScreen = 201, // PARAMS: 1
+	isLoaded = 376, // PARAMS: 1
+
+	// PATH METHODS.
+	setDuration = 262, // PARAMS: 1
+	percentComplete = 263,
+
+	// TEXT METHODS.
+	text = 290,
+	setText = 291,
+	setMaximumTextLength = 293, // PARAM: 1
+
+	// COLLECTION METHODS.
+	// These aren't assets but arrays used in Media Script.
+	isEmpty = 254, // PARAMS: 0
+	empty = 252, // PARAMS: 0
+	append = 247, // PARAMS: 1+
+	getAt = 253, // PARAMS: 1
+	count = 249, // PARAMS: 0
+	// Looks like this lets you call a method on all the items in a collection.
+	// Examples look like : var_7be1_collect_shapes.send(spatialHide);
+	send = 257, // PARAMS: 1+. Looks like the first param is the function,
+	// and the next params are any arguments you want to send.
+	// Seeking seems to be finding the index where a certain item is.
+	seek = 256, // PARAMS: 1
+	sort = 266, // PARAMS: 0
+	deleteAt = 258, // PARAMS: 1
+
+	// PRINTER METHODS.
+	openLens = 346, // PARAMS: 0
+	closeLens = 347, // PARAMS: 0
+};
+
+} // End of namespace MediaStation
+
+#endif
\ No newline at end of file
diff --git a/engines/mediastation/mediascript/codechunk.cpp b/engines/mediastation/mediascript/codechunk.cpp
new file mode 100644
index 00000000000..92b7bc83387
--- /dev/null
+++ b/engines/mediastation/mediascript/codechunk.cpp
@@ -0,0 +1,343 @@
+/* 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 "mediastation/mediastation.h"
+#include "mediastation/mediascript/codechunk.h"
+#include "mediastation/datum.h"
+#include "mediastation/chunk.h"
+#include "mediastation/debugchannels.h"
+
+#include "mediastation/assets/movie.h"
+#include "mediastation/assets/path.h"
+
+namespace MediaStation {
+
+CodeChunk::CodeChunk(Common::SeekableReadStream &chunk) : _args(nullptr) {
+	uint lengthInBytes = Datum(chunk, DatumType::UINT32_1).u.i;
+	debugC(5, kDebugLoading, "CodeChunk::CodeChunk(): Length 0x%x (@0x%llx)", lengthInBytes, chunk.pos());
+	_bytecode = chunk.readStream(lengthInBytes);
+}
+
+Operand CodeChunk::execute(Common::Array<Operand> *args) {
+	_args = args;
+	Operand returnValue;
+	while (_bytecode->pos() < _bytecode->size()) {
+		debugC(8, kDebugScript, "-------- Statement --------");
+		returnValue = executeNextStatement();
+	}
+
+	// Rewind the stream once we're finished, in case we need to execute
+	// this code again!
+	_bytecode->seek(0);
+	// We don't own the args, so we will prevent a potentially out-of-scope
+	// variable from being re-accessed.
+	_args = nullptr;
+	return returnValue;
+}
+
+Operand CodeChunk::executeNextStatement() {
+	if (_bytecode->eos()) {
+		error("CodeChunk::executeNextStatement(): Attempt to read past end of bytecode chunk");
+	}
+
+	InstructionType instructionType = InstructionType(Datum(*_bytecode).u.i);
+	debugC(8, kDebugScript, " instructionType = %d", (uint)instructionType);
+	switch (instructionType) {
+	case InstructionType::EMPTY: {
+		return Operand();
+	}
+
+	case InstructionType::FUNCTION_CALL: {
+		Opcode opcode = Opcode(Datum(*_bytecode).u.i);
+		debugC(8, kDebugScript, "  *** Opcode %d ***", (uint)opcode);
+		switch (opcode) {
+		case Opcode::AssignVariable: {
+			uint32 id = Datum(*_bytecode).u.i;
+			VariableScope scope = VariableScope(Datum(*_bytecode).u.i);
+			Operand newValue = executeNextStatement();
+			// TODO: Print the new variable value for easier debugging.
+			debugC(5, kDebugScript, "SCRIPT: [ %d (scope: %d) ] = [ ? (showing value assigned to var not implemented yet) ]", (uint)scope, id);
+			putVariable(id, scope, newValue);
+			return Operand();
+		}
+
+		case Opcode::CallRoutine: {
+			uint functionId = Datum(*_bytecode).u.i;
+			uint32 parameterCount = Datum(*_bytecode).u.i;
+			Common::Array<Operand> args;
+			for (uint i = 0; i < parameterCount; i++) {
+				debugC(8, kDebugScript, "   -- Argument %d of %d --", (i + 1), parameterCount);
+				Operand arg = executeNextStatement();
+				args.push_back(arg);
+			}
+
+			// Call the routine.
+			debugC(5, kDebugScript, "SCRIPT: [ %d ]( %d args )", functionId, parameterCount);
+			Operand returnValue;
+			Function *function = g_engine->_functions.getValOrDefault(functionId);
+			if (function != nullptr) {
+				returnValue = function->execute(args);
+			} else {
+				returnValue = callBuiltInFunction(functionId, args);
+			}
+			return returnValue;
+		}
+
+		case Opcode::CallMethod: {
+			uint32 methodId = Datum(*_bytecode).u.i;
+			uint32 parameterCount = Datum(*_bytecode).u.i;
+			Operand selfObject = executeNextStatement();
+			if (selfObject.getType() != Operand::Type::AssetId) {
+				error("CodeChunk::executeNextStatement(): (Opcode::CallMethod) Attempt to call method on operand that is not an asset (type 0x%x)", selfObject.getType());
+			}
+			Common::Array<Operand> args;
+			for (uint i = 0; i < parameterCount; i++) {
+				debugC(8, kDebugScript, "   -- Argument %d of %d --", (i + 1), parameterCount);
+				Operand arg = executeNextStatement();
+				args.push_back(arg);
+			}
+
+			// Call the method.
+			// TODO: Resolve asset IDs to names in this decompilation so
+			// itʻe easier to read.
+			debugC(5, kDebugScript, "SCRIPT: @[ %d ].[ %d ]()", selfObject.getAssetId(), methodId);
+			// TODO: Where do we get the method from? And can we define
+			// our own methods? Or are only the built-in methods
+			// supported?
+			Operand returnValue = callBuiltInMethod(methodId, selfObject, args);
+			return returnValue;
+		}
+
+		case Opcode::DeclareVariables: {
+			uint32 localVariableCount = Datum(*_bytecode).u.i;
+			debugC(5, kDebugScript, "   Declaring %d local variables", localVariableCount);
+			_locals.resize(localVariableCount);
+			return Operand();
+		}
+
+		case Opcode::Subtract: {
+			Operand value1 = executeNextStatement();
+			Operand value2 = executeNextStatement();
+
+			Operand returnValue = value1 - value2;
+			return returnValue;
+		}
+
+		default: {
+			error("CodeChunk::getNextStatement(): Got unknown opcode 0x%x (%d)", opcode, opcode);
+		}
+		}
+		break;
+	}
+
+	case (InstructionType::OPERAND): {
+		Operand::Type operandType = Operand::Type(Datum(*_bytecode).u.i);
+		debugC(8, kDebugScript, "  *** Operand %d ***", (uint)operandType);
+		Operand operand(operandType);
+		switch (operandType) {
+		// TODO: Add clearer debugging printouts for these.
+		case Operand::Type::AssetId: {
+			uint32 assetId = Datum(*_bytecode).u.i;
+			operand.putAsset(assetId);
+			return operand;
+		}
+
+		case Operand::Type::Literal1:
+		case Operand::Type::Literal2:
+		case Operand::Type::DollarSignVariable: {
+			int literal = Datum(*_bytecode).u.i;
+			operand.putInteger(literal);
+			return operand;
+		}
+
+		case Operand::Type::Float1:
+		case Operand::Type::Float2: {
+			double d = Datum(*_bytecode).u.f;
+			operand.putDouble(d);
+			return operand;
+		}
+
+		default: {
+			error("CodeChunk::getNextStatement(): Got unknown operand type 0x%d", operandType);
+		}
+		}
+		break;
+	}
+
+	case (InstructionType::VARIABLE_REF): {
+		// TODO: Add debug printout for this.
+		uint32 id = Datum(*_bytecode).u.i;
+		VariableScope scope = VariableScope(Datum(*_bytecode).u.i);
+		Operand variable = getVariable(id, scope);
+		return variable;
+	}
+
+	default: {
+		error("CodeChunk::getNextStatement(): Got unknown instruction type 0x%x", instructionType);
+	}
+	}
+}
+
+Operand CodeChunk::getVariable(uint32 id, VariableScope scope) {
+	switch (scope) {
+	case VariableScope::Global: {
+		Operand returnValue(Operand::Type::VariableDeclaration);
+		Variable *variable = g_engine->_variables.getVal(id);
+		returnValue.putVariable(variable);
+		return returnValue;
+	}
+
+	case VariableScope::Local: {
+		uint index = id - 1;
+		return _locals.operator[](index);
+		break;
+	}
+
+	case VariableScope::Parameter: {
+		uint32 index = id - 1;
+		if (_args == nullptr) {
+			error("CodeChunk::getVariable(): Requested a parameter in a code chunk that has no parameters.");
+		}
+		return _args->operator[](index);
+		break;
+	}
+
+	default: {
+		error("CodeChunk::getVariable(): Got unknown variable scope %d", (uint)scope);
+	}
+	}
+}
+
+void CodeChunk::putVariable(uint32 id, VariableScope scope, Operand value) {
+	switch (scope) {
+	case VariableScope::Global: {
+		Variable *variable = g_engine->_variables.getVal(id);
+		if (variable == nullptr) {
+			error("CodeChunk::putVariable(): Attempted to assign to a non-existent global variable %d", id);
+		}
+
+		switch (value.getType()) {
+		case Operand::Type::Literal1:
+		case Operand::Type::Literal2: {
+			variable->value.i = value.getInteger();
+			break;
+		}
+
+		case Operand::Type::Float1:
+		case Operand::Type::Float2: {
+			variable->value.d = value.getDouble();
+			break;
+		}
+
+		case Operand::Type::String: {
+			variable->value.string = value.getString();
+			break;
+		}
+
+		case Operand::Type::AssetId: {
+			variable->value.assetId = value.getAssetId();
+			break;
+		}
+
+		case Operand::Type::VariableDeclaration: {
+			// TODO: Will this cause a memory leak?
+			// variable = value.u.variable;
+			error("Assigning variable to another variable not supported yet");
+			break;
+		}
+
+		default: {
+			error("CodeChunk::putVariable(): Cannot put operand type 0x%x into variable", (uint)value.getType());
+		}
+		}
+		break;
+	}
+
+	case VariableScope::Local: {
+		uint index = id - 1;
+		_locals[index] = value;
+		break;
+	}
+
+	case VariableScope::Parameter: {
+		error("CodeChunk::putVariable(): Attempted to assign to a parameter");
+		break;
+	}
+
+	default: {
+		error("VariableAssignment::evaluate(): Got unknown variable scope 0x%x", (uint)scope);
+	}
+	}
+}
+
+Operand CodeChunk::callBuiltInFunction(uint32 id, Common::Array<Operand> &args) {
+	switch ((BuiltInFunction)id) {
+	case BuiltInFunction::effectTransition: {
+		switch (args.size()) {
+		// TODO: Discover and handle the different ways
+		// effectTransition can be called.
+		case 1: {
+			uint dollarSignVariable = args[0].getInteger();
+			break;
+		}
+
+		case 3: {
+			uint dollarSignVariable = args[0].getInteger();
+			double percentComplete = args[1].getDouble();
+
+			// TODO: Verify that this is a palette!
+			Asset *asset = args[2].getAsset();
+			g_engine->setPalette(asset);
+			break;
+		}
+
+		default: {
+			error("CodeChunk::callBuiltInFunction(): (BuiltInFunction::effectTransition) Got %d args, which is unexpected", args.size());
+		}
+		}
+
+		warning("CodeChunk::callBuiltInFunction(): effectTransition is not implemented");
+		return Operand();
+		break;
+	}
+
+	default: {
+		error("CodeChunk::callBuiltInFunction(): Got unknown built-in function ID %d", id);
+	}
+	}
+}
+
+Operand CodeChunk::callBuiltInMethod(uint32 id, Operand self, Common::Array<Operand> &args) {
+	Asset *selfAsset = self.getAsset();
+	assert(selfAsset != nullptr);
+	Operand returnValue = selfAsset->callMethod((BuiltInMethod)id, args);
+	return returnValue;
+}
+
+CodeChunk::~CodeChunk() {
+	// We don't own the args, so we don't need to delete it.
+	_args = nullptr;
+
+	delete _bytecode;
+	_bytecode = nullptr;
+}
+
+} // End of namespace MediaStation
diff --git a/engines/mediastation/mediascript/codechunk.h b/engines/mediastation/mediascript/codechunk.h
new file mode 100644
index 00000000000..43085433572
--- /dev/null
+++ b/engines/mediastation/mediascript/codechunk.h
@@ -0,0 +1,101 @@
+/* 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 MEDIASTATION_MEDIASCRIPT_CODECHUNK_H
+#define MEDIASTATION_MEDIASCRIPT_CODECHUNK_H
+
+#include "common/array.h"
+#include "common/stream.h"
+
+#include "mediastation/mediascript/variable.h"
+#include "mediastation/mediascript/operand.h"
+#include "mediastation/mediascript/builtins.h"
+
+namespace MediaStation {
+
+enum class InstructionType {
+	EMPTY = 0x0000,
+	FUNCTION_CALL = 0x0067,
+	OPERAND = 0x0066,
+	VARIABLE_REF = 0x0065
+};
+
+enum class Opcode {
+	IfElse = 202,
+	AssignVariable = 203,
+	Or = 204,
+	And = 206,
+	Equals = 207,
+	NotEquals = 208,
+	LessThan = 209,
+	GreaterThan = 210,
+	LessThanOrEqualTo = 211,
+	GreaterThanOrEqualTo = 212,
+	Add = 213,
+	Subtract = 214,
+	Multiply = 215,
+	Divide = 216,
+	Modulo = 217,
+	Unk2 = 218, // TODO: Likely something with ## constants like ##DOWN?
+	CallRoutine = 219,
+	// Method calls are like routine calls, but they have an implicit "self"
+	// parameter that is always the first. For example:
+	//  @self . mouseActivate ( TRUE ) ;
+	CallMethod = 220,
+	// This seems to appear at the start of a function to declare the number of
+	// local variables used in the function. It seems to be the `Declare`
+	// keyword. In the observed examples, the number of variables to create is
+	// given, then the next instructions are variable assignments for that number
+	// of variables.
+	DeclareVariables = 221,
+	While = 224,
+	Return = 222,
+	Unk1 = 223
+};
+
+enum class VariableScope {
+	Local = 1,
+	Parameter = 2,
+	Global = 4
+};
+
+class CodeChunk {
+public:
+	CodeChunk(Common::SeekableReadStream &chunk);
+	~CodeChunk();
+
+	Operand execute(Common::Array<Operand> *args = nullptr);
+
+private:
+	Operand executeNextStatement();
+	Operand callBuiltInFunction(uint32 id, Common::Array<Operand> &args);
+	Operand callBuiltInMethod(uint32 id, Operand self, Common::Array<Operand> &args);
+	Operand getVariable(uint32 id, VariableScope scope);
+	void putVariable(uint32 id, VariableScope scope, Operand value);
+
+	Common::Array<Operand> _locals;
+	Common::Array<Operand> *_args;
+	Common::SeekableReadStream *_bytecode;
+};
+
+} // End of namespace MediaStation
+
+#endif
diff --git a/engines/mediastation/mediascript/eventhandler.cpp b/engines/mediastation/mediascript/eventhandler.cpp
new file mode 100644
index 00000000000..f34e370e38b
--- /dev/null
+++ b/engines/mediastation/mediascript/eventhandler.cpp
@@ -0,0 +1,80 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "mediastation/mediascript/eventhandler.h"
+#include "mediastation/debugchannels.h"
+
+namespace MediaStation {
+
+EventHandler::EventHandler(Chunk &chunk) {
+	_type = (EventHandler::Type)(Datum(chunk).u.i);
+	debugC(5, kDebugLoading, "EventHandler::EventHandler(): Type 0x%x (@0x%llx)", _type, chunk.pos());
+	_argumentType = (EventHandler::ArgumentType)(Datum(chunk).u.i);
+	debugC(5, kDebugLoading, "EventHandler::EventHandler(): Argument type 0x%x (@0x%llx)", _argumentType, chunk.pos());
+	_argumentValue = Datum(chunk);
+
+	if (_argumentType != EventHandler::ArgumentType::Null) {
+		uint lengthInBytes = Datum(chunk, DatumType::UINT32_1).u.i;
+		debugC(5, kDebugLoading, "EventHandler::EventHandler(): Null argument type, length = 0x%x (@0x%llx)", lengthInBytes, chunk.pos());
+	}
+
+	_code = new CodeChunk(chunk);
+}
+
+Operand EventHandler::execute(uint assetId) {
+	// TODO: The assetId is only passed in for debug visibility, there should be
+	// a better way to handle that.
+	switch (_argumentType) {
+	case EventHandler::ArgumentType::Null: {
+		debugC(5, kDebugScript, "\n********** EVENT HANDLER (asset %d) (type = %d) (no argument) **********", assetId, (uint)_type);
+		break;
+	}
+
+	case EventHandler::ArgumentType::AsciiCode: {
+		debugC(5, kDebugScript, "\n********** EVENT HANDLER (asset %d) (type = %d) (ASCII code = %d) **********", assetId, (uint)_type, _argumentValue.u.i);
+		break;
+	}
+
+	case EventHandler::ArgumentType::Context: {
+		debugC(5, kDebugScript, "\n********** EVENT HANDLER (asset %d) (type = %d) (context = %d) **********", assetId, (uint)_type, _argumentValue.u.i);
+		break;
+	}
+
+	case EventHandler::ArgumentType::Time:
+	case EventHandler::ArgumentType::Unk1: {
+		debugC(5, kDebugScript, "\n********** EVENT HANDLER (asset %d) (type = %d) (time = %f) **********", assetId, (uint)_type, _argumentValue.u.f);
+		break;
+	}
+	}
+
+	// The only argument that can be provided to an event handler is the
+	// _argumentValue.
+	Operand returnValue = _code->execute();
+	debugC(5, kDebugScript, "********** END EVENT HANDLER **********");
+	return returnValue;
+}
+
+EventHandler::~EventHandler() {
+	delete _code;
+	_code = nullptr;
+}
+
+} // End of namespace MediaStation
diff --git a/engines/mediastation/mediascript/eventhandler.h b/engines/mediastation/mediascript/eventhandler.h
new file mode 100644
index 00000000000..3e6736543ae
--- /dev/null
+++ b/engines/mediastation/mediascript/eventhandler.h
@@ -0,0 +1,110 @@
+/* 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 MEDIASTATION_MEDIASCRIPT_EVENTHANDLER_H
+#define MEDIASTATION_MEDIASCRIPT_EVENTHANDLER_H
+
+#include "common/hashmap.h"
+#include "common/func.h"
+
+#include "mediastation/datafile.h"
+#include "mediastation/datum.h"
+
+#include "mediastation/mediascript/codechunk.h"
+
+namespace MediaStation {
+
+class EventHandler {
+public:
+	enum class Type {
+		// TIMER EVENTS.
+		Time = 5,
+
+		// HOTSPOT EVENTS.
+		MouseDown = 6,
+		MouseUp = 7,
+		MouseMoved = 8,
+		MouseEntered = 9,
+		MouseExited = 10,
+		KeyDown = 13, // PARAMS: 1 - ASCII code.
+
+		// SOUND EVENTS.
+		SoundEnd = 14,
+		SoundAbort = 19,
+		SoundFailure = 20,
+		SoundStopped = 29,
+		SoundBegin = 30,
+
+		// MOVIE EVENTS.
+		MovieEnd = 15,
+		MovieAbort = 21,
+		MovieFailure = 22,
+		MovieStopped = 31,
+		MovieBegin = 32,
+
+		//SPRITE EVENTS.
+		// Just "MovieEnd" in source.
+		SpriteMovieEnd = 23,
+
+		// SCREEN EVENTS.
+		Entry = 17,
+		Exit = 27,
+
+		// CONTEXT EVENTS.
+		LoadComplete = 44, // PARAMS: 1 - Context ID
+
+		// TEXT EVENTS.
+		Input = 37,
+		Error = 38,
+
+		// CAMERA EVENTS.
+		PanAbort = 43,
+		PanEnd = 42,
+
+		// PATH EVENTS.
+		Step = 28,
+		PathStopped = 33,
+		PathEnd = 16
+	};
+
+	enum class ArgumentType {
+		Null = 0,
+		AsciiCode = 1, // TODO: Why is this datum type a float?
+		Time = 3,
+		Unk1 = 4, // Appars to happen with MovieStart?
+		Context = 5
+	};
+
+	EventHandler(Chunk &chunk);
+	~EventHandler();
+
+	Operand execute(uint assetId);
+	EventHandler::Type _type;
+	EventHandler::ArgumentType _argumentType;
+	Datum _argumentValue;
+
+private:
+	CodeChunk *_code = nullptr;
+};
+
+} // End of namespace MediaStation
+
+#endif
diff --git a/engines/mediastation/mediascript/function.cpp b/engines/mediastation/mediascript/function.cpp
new file mode 100644
index 00000000000..d44afa0016a
--- /dev/null
+++ b/engines/mediastation/mediascript/function.cpp
@@ -0,0 +1,51 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "common/array.h"
+
+#include "mediastation/datum.h"
+#include "mediastation/mediascript/function.h"
+#include "mediastation/debugchannels.h"
+
+namespace MediaStation {
+
+Function::Function(Chunk &chunk) {
+	_fileId = Datum(chunk).u.i;
+	_id = Datum(chunk).u.i; // + 19900;
+	uint lengthInBytes = Datum(chunk, DatumType::UINT32_1).u.i;
+	debugC(5, kDebugLoading, "Function::Function(): id = 0x%x, size = 0x%x bytes", _id, lengthInBytes);
+	_code = new CodeChunk(chunk);
+}
+
+Function::~Function() {
+	delete _code;
+	_code = nullptr;
+}
+
+Operand Function::execute(Common::Array<Operand> args) {
+	debugC(5, kDebugScript, "\n********** FUNCTION %d **********", _id);
+	Operand returnValue = _code->execute(&args);
+	debugC(5, kDebugScript, "********** END FUNCTION **********");
+	return returnValue;
+}
+
+} // End of namespace MediaStation
+
diff --git a/engines/mediastation/mediascript/function.h b/engines/mediastation/mediascript/function.h
new file mode 100644
index 00000000000..4db14d8cb18
--- /dev/null
+++ b/engines/mediastation/mediascript/function.h
@@ -0,0 +1,46 @@
+/* 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 MEDIASTATION_MEDIASCRIPT_FUNCTION_H
+#define MEDIASTATION_MEDIASCRIPT_FUNCTION_H
+
+#include "mediastation/datafile.h"
+#include "mediastation/mediascript/codechunk.h"
+
+namespace MediaStation {
+
+class Function {
+public:
+	Function(Chunk &chunk);
+	~Function();
+
+	Operand execute(Common::Array<Operand> args);
+
+	uint _fileId;
+	uint _id;
+
+private:
+	CodeChunk *_code = nullptr;
+};
+
+} // End of namespace MediaStation
+
+#endif
diff --git a/engines/mediastation/mediascript/operand.cpp b/engines/mediastation/mediascript/operand.cpp
new file mode 100644
index 00000000000..62674df8012
--- /dev/null
+++ b/engines/mediastation/mediascript/operand.cpp
@@ -0,0 +1,261 @@
+/* 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 "mediastation/mediastation.h"
+#include "mediastation/mediascript/operand.h"
+#include "mediastation/mediascript/function.h"
+
+namespace MediaStation {
+
+void Operand::putInteger(int i) {
+	switch (_type) {
+	case Operand::Type::Literal1:
+	case Operand::Type::Literal2:
+	case Operand::Type::DollarSignVariable: {
+		_u.i = i;
+		break;
+	}
+
+	case Operand::Type::VariableDeclaration: {
+		_u.variable->value.i = i;
+		break;
+	}
+
+	default: {
+		error("Operand::putInteger(): Attempt to put unsupported value into operand (type 0x%x)", _type);
+	}
+	}
+}
+
+int Operand::getInteger() {
+	switch (_type) {
+	case Operand::Type::Literal1:
+	case Operand::Type::Literal2:
+	case Operand::Type::DollarSignVariable: {
+		return _u.i;
+	}
+
+	case Operand::Type::VariableDeclaration: {
+		return _u.variable->value.i;
+	}
+
+	default: {
+		error("Operand::getInteger(): Attempt to get unsupported value from operand (type 0x%x)", _type);
+	}
+	}
+}
+
+void Operand::putDouble(double d) {
+	switch (_type) {
+	case Operand::Type::Float1:
+	case Operand::Type::Float2: {
+		_u.d = d;
+		break;
+	}
+
+	case Operand::Type::VariableDeclaration: {
+		// TODO: Add assertion.
+		_u.variable->value.d = d;
+		break;
+	}
+
+	default: {
+		error("Operand::putDouble(): Attempt to put unsupported value in operand (type 0x%x)", _type);
+	}
+	}
+}
+
+double Operand::getDouble() {
+	switch (_type) {
+	case Operand::Type::Float1:
+	case Operand::Type::Float2: {
+		return _u.d;
+	}
+
+	case Operand::Type::VariableDeclaration: {
+		// TODO: Add assertion that this is the proper type.
+		return _u.variable->value.d;
+	}
+
+	default: {
+		error("Operand::getDouble(): Attempt to get unsupported value from operand (type 0x%x)", _type);
+	}
+	}
+}
+
+void Operand::putString(Common::String *string) {
+	switch (_type) {
+	case Operand::Type::String: {
+		_u.string = string;
+		break;
+	}
+
+	case Operand::Type::VariableDeclaration: {
+		assert(_u.variable->type == Variable::Type::STRING);
+		_u.variable->value.string = string;
+		break;
+	}
+
+	default: {
+		error("Operand::putString(): Attempt to put unsupported value into operand (type 0x%x)", _type);
+	}
+	}
+}
+
+Common::String *Operand::getString() {
+	switch (_type) {
+	case Operand::Type::String: {
+		return _u.string;
+	}
+
+	case Operand::Type::VariableDeclaration: {
+		assert(_u.variable->type == Variable::Type::STRING);
+		return _u.variable->value.string;
+	}
+
+	default: {
+		error("Operand::getString(): Attempt to get unsupported value from operand (type 0x%x)", _type);
+	}
+	}
+}
+
+void Operand::putVariable(Variable *variable) {
+	switch (_type) {
+	case Operand::Type::VariableDeclaration: {
+		_u.variable = variable;
+		break;
+	}
+
+	default: {
+		error("Operand::putVariable(): Attempt to put unsupported value into operand that is not a variable (type 0x%x)", _type);
+	}
+	}
+}
+
+Variable *Operand::getVariable() {
+	switch (_type) {
+	case Operand::Type::VariableDeclaration: {
+		return _u.variable;
+	}
+
+	default: {
+		error("Operand::getVariable(): Attempt to get unsupported value from operand that is not a variable (type 0x%x)", _type);
+	}
+	}
+}
+
+void Operand::putFunction(Function *function) {
+	switch (_type) {
+	case Operand::Type::Function: {
+		_u.function = function;
+		break;
+	}
+
+	default: {
+		error("Operand::putFunction(): Attempt to put unsupported value into operand that is not a function (type 0x%x)", _type);
+	}
+	}
+}
+
+Function *Operand::getFunction() {
+	switch (_type) {
+	case Operand::Type::Function: {
+		return _u.function;
+	}
+
+	default: {
+		error("Operand::getFunction(): Attempt to get unsupported value from operand that is not a function (type 0x%x)", _type);
+	}
+	}
+}
+
+void Operand::putAsset(uint32 assetId) {
+	switch (_type) {
+	case Operand::Type::AssetId: {
+		_u.assetId = assetId;
+		break;
+	}
+
+	case Operand::Type::VariableDeclaration: {
+		assert(_u.variable->type == Variable::Type::ASSET_ID);
+		_u.variable->value.assetId = assetId;
+		break;
+	}
+
+	default: {
+		error("Operand::putAsset(): Attempt to put asset into operand that is not an asset (type 0x%x)", _type);
+	}
+	}
+}
+
+Asset *Operand::getAsset() {
+	switch (_type) {
+	case Operand::Type::AssetId: {
+		if (_u.assetId == 0) {
+			return nullptr;
+		} else {
+			return g_engine->_assets.getVal(_u.assetId);
+		}
+	}
+
+	case Operand::Type::VariableDeclaration: {
+		assert(_u.variable->type == Variable::Type::ASSET_ID);
+		return g_engine->_assets.getVal(_u.variable->value.assetId);
+	}
+
+	default: {
+		error("Operand::getAsset(): Attempt to get asset from operand that is not an asset (type 0x%x)", _type);
+	}
+	}
+}
+
+uint32 Operand::getAssetId() {
+	switch (_type) {
+	case Operand::Type::AssetId: {
+		return _u.assetId;
+	}
+
+	case Operand::Type::VariableDeclaration: {
+		assert(_u.variable->type == Variable::Type::ASSET_ID);
+		return _u.variable->value.assetId;
+	}
+
+	default: {
+		error("Operand::getAssetId(): Attempt to get asset ID from operand that is not an asset (type 0x%x)", _type);
+	}
+	}
+}
+
+Operand Operand::operator-(const Operand &other) const {
+	Operand returnValue;
+	if (this->_type == Operand::Type::Literal1 && other._type == Operand::Type::Literal1) {
+		returnValue._type = Operand::Type::Literal1;
+		returnValue._u.i = this->_u.i - other._u.i;
+	} else if (this->_type == Operand::Type::Float1 && other._type == Operand::Type::Float1) {
+		returnValue._type = Operand::Type::Float1;
+		returnValue._u.d = this->_u.d - other._u.d;
+	} else {
+		error("Operand::operator-(): Unsupported operand types %d and %d", this->_type, other._type);
+	}
+	return returnValue;
+}
+
+} // End of namespace MediaStation
diff --git a/engines/mediastation/mediascript/operand.h b/engines/mediastation/mediascript/operand.h
new file mode 100644
index 00000000000..ed3a64b87af
--- /dev/null
+++ b/engines/mediastation/mediascript/operand.h
@@ -0,0 +1,98 @@
+/* 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 MEDIASTATION_MEDIASCRIPT_OPERAND_H
+#define MEDIASTATION_MEDIASCRIPT_OPERAND_H
+
+#include "common/array.h"
+
+#include "mediastation/mediascript/variable.h"
+
+namespace MediaStation {
+
+class Function;
+class Asset;
+
+class Operand {
+public:
+	enum class Type {
+		Empty = 0, // a flag for C++ code, not real operand type.
+		// TODO: Figure out the difference between these two.
+		Literal1 = 151,
+		Literal2 = 153,
+		// TODO: Figure out the difference between these two.
+		Float1 = 152,
+		Float2 = 157,
+		String = 154,
+		// TODO: This only seems to be used in effectTransition:
+		//  effectTransition ( $FadeToPalette )
+		// compiles to:
+		//  [219, 102, 1]
+		//  [155, 301]
+		DollarSignVariable = 155,
+		AssetId = 156,
+		VariableDeclaration = 158,
+		Function = 160
+	};
+
+	Operand() : _type(Operand::Type::Empty) {}
+	Operand(Operand::Type type) : _type(type) {}
+
+	Operand::Type getType() const {
+		return _type;
+	}
+
+	void putInteger(int i);
+	int getInteger();
+
+	void putDouble(double d);
+	double getDouble();
+
+	void putString(Common::String *string);
+	Common::String *getString();
+
+	void putVariable(Variable *variable);
+	Variable *getVariable();
+
+	void putFunction(Function *function);
+	Function *getFunction();
+
+	void putAsset(uint32 assetId);
+	Asset *getAsset();
+	uint32 getAssetId();
+
+	Operand operator-(const Operand &other) const;
+
+private:
+	Operand::Type _type = Operand::Type::Empty;
+	union {
+		uint assetId = 0;
+		Common::String *string;
+		Variable *variable;
+		Function *function;
+		int i;
+		double d;
+	} _u;
+};
+
+} // End of namespace MediaStation
+
+#endif
\ No newline at end of file
diff --git a/engines/mediastation/mediascript/variable.cpp b/engines/mediastation/mediascript/variable.cpp
new file mode 100644
index 00000000000..cec3f2cc8e2
--- /dev/null
+++ b/engines/mediastation/mediascript/variable.cpp
@@ -0,0 +1,114 @@
+/* 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 "mediastation/mediascript/variable.h"
+#include "mediastation/chunk.h"
+#include "mediastation/datum.h"
+#include "mediastation/datafile.h"
+#include "mediastation/debugchannels.h"
+
+namespace MediaStation {
+
+Variable::Variable(Chunk &chunk) {
+	id = Datum(chunk, DatumType::UINT16_1).u.i;
+	type = Variable::Type(Datum(chunk, DatumType::UINT8).u.i);
+	debugC(5, kDebugLoading, "Variable::Variable(): id = 0x%x, type 0x%x (@0x%llx)", id, type, chunk.pos());
+	switch ((Type)type) {
+	case Type::COLLECTION: {
+		uint totalItems = Datum(chunk).u.i;
+		value.collection = new Common::Array<Variable *>;
+		for (uint i = 0; i < totalItems; i++) {
+			debugC(7, kDebugLoading, "Variable::Variable(): COLLECTION: Value %d of %d", i, totalItems);
+			Variable *variableDeclaration = new Variable(chunk);
+			value.collection->push_back(variableDeclaration);
+		}
+		break;
+	}
+
+	case Type::STRING: {
+		// TODO: This copies the string. Can we read it directly from the chunk?
+		int size = Datum(chunk).u.i;
+		char *buffer = new char[size + 1];
+		chunk.read(buffer, size);
+		buffer[size] = '\0';
+		value.string = new Common::String(buffer);
+		delete[] buffer;
+		debugC(7, kDebugLoading, "Variable::Variable(): STRING: %s", value.string->c_str());
+		break;
+	}
+
+	case Type::ASSET_ID: {
+		value.assetId = Datum(chunk, DatumType::UINT16_1).u.i;
+		debugC(7, kDebugLoading, "Variable::Variable(): ASSET ID: %d", value.assetId);
+		break;
+	}
+
+	case Type::BOOLEAN: {
+		uint rawValue = Datum(chunk, DatumType::UINT8).u.i;
+		debugC(7, kDebugLoading, " Variable::Variable(): BOOL: %d", rawValue);
+		value.b = (rawValue == 1);
+		break;
+	}
+
+	case Type::LITERAL: {
+		// Client code can worry about extracting the value.
+		value.datum = new Datum(chunk);
+		debugC(7, kDebugLoading, "Variable::Variable(): LITERAL");
+		break;
+	}
+
+	default: {
+		warning("Variable::Variable(): Got unknown variable value type 0x%x", type);
+		value.datum = new Datum(chunk);
+	}
+	}
+}
+
+Variable::~Variable() {
+	switch ((Type)type) {
+	case Type::ASSET_ID:
+	case Type::BOOLEAN: {
+		break;
+	}
+
+	case Type::COLLECTION: {
+		delete value.collection;
+		break;
+	}
+
+	case Type::STRING: {
+		delete value.string;
+		break;
+	}
+
+	case Type::LITERAL: {
+		delete value.datum;
+		break;
+	}
+
+	default: {
+		delete value.datum;
+		break;
+	}
+	}
+}
+
+} // End of namespace MediaStation
\ No newline at end of file
diff --git a/engines/mediastation/mediascript/variable.h b/engines/mediastation/mediascript/variable.h
new file mode 100644
index 00000000000..7f087450fb3
--- /dev/null
+++ b/engines/mediastation/mediascript/variable.h
@@ -0,0 +1,76 @@
+/* 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 MEDIASTATION_MEDIASCRIPT_VARIABLE_DECLARATION_H
+#define MEDIASTATION_MEDIASCRIPT_VARIABLE_DECLARATION_H
+
+#include "mediastation/chunk.h"
+#include "mediastation/datafile.h"
+#include "mediastation/datum.h"
+
+namespace MediaStation {
+
+class Variable {
+public:
+	enum class Type {
+		// This is an invalid type used for initialization only.
+		EMPTY = 0x0000,
+
+		// This is an "array", but the IMT sources
+		// use the term "collection".
+		COLLECTION = 0x0007,
+		STRING = 0x0006,
+		ASSET_ID = 0x0005,
+		// These seem to be used in Dalmatians, but I don't know what they are
+		// used for.
+		UNK1 = 0x0004,
+		// These seem to be constants of some sort? This is what some of these
+		// IDs look like in PROFILE._ST:
+		//  - $downEar 10026
+		//  - $sitDown 10027
+		// Seems like these can also reference variables:
+		//  - var_6c14_bool_FirstThingLev3 315
+		//  - var_6c14_NextEncouragementSound 316
+		UNK2 = 0x0003,
+		BOOLEAN = 0x0002,
+		LITERAL = 0x0001
+	};
+
+	uint32 id = 0;
+	Variable::Type type = Type::EMPTY;
+	union {
+		Datum *datum = nullptr;
+		Common::String *string;
+		Common::Array<Variable *> *collection;
+		bool b;
+		int i;
+		double d;
+		uint assetId;
+	} value;
+
+	Variable();
+	Variable(Chunk &chunk);
+	~Variable();
+};
+
+} // End of namespace MediaStation
+
+#endif
\ No newline at end of file
diff --git a/engines/mediastation/mediastation.cpp b/engines/mediastation/mediastation.cpp
new file mode 100644
index 00000000000..326187efb2b
--- /dev/null
+++ b/engines/mediastation/mediastation.cpp
@@ -0,0 +1,258 @@
+/* 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 "graphics/framelimiter.h"
+#include "common/scummsys.h"
+#include "common/config-manager.h"
+#include "common/debug-channels.h"
+#include "common/events.h"
+#include "common/system.h"
+#include "engines/util.h"
+#include "graphics/paletteman.h"
+
+#include "mediastation/mediastation.h"
+#include "mediastation/debugchannels.h"
+#include "mediastation/detection.h"
+#include "mediastation/boot.h"
+#include "mediastation/context.h"
+#include "mediastation/asset.h"
+#include "mediastation/assets/movie.h"
+
+namespace MediaStation {
+
+MediaStationEngine *g_engine;
+
+MediaStationEngine::MediaStationEngine(OSystem *syst, const ADGameDescription *gameDesc) : Engine(syst),
+	_gameDescription(gameDesc),
+	_randomSource("MediaStation") {
+	g_engine = this;
+	_mixer = g_system->getMixer();
+}
+
+MediaStationEngine::~MediaStationEngine() {
+	_mixer = nullptr;
+
+	delete _screen;
+	_screen = nullptr;
+
+	delete _boot;
+	_boot = nullptr;
+
+	for (auto it = _assets.begin(); it != _assets.end(); ++it) {
+		delete it->_value;
+	}
+	_assets.clear();
+	_assetsByChunkReference.clear();
+
+	for (auto it = _functions.begin(); it != _functions.end(); ++it) {
+		delete it->_value;
+	}
+	_functions.clear();
+
+	for (auto it = _variables.begin(); it != _variables.end(); ++it) {
+		delete it->_value;
+	}
+	_variables.clear();
+}
+
+uint32 MediaStationEngine::getFeatures() const {
+	return _gameDescription->flags;
+}
+
+Common::String MediaStationEngine::getGameId() const {
+	return _gameDescription->gameId;
+}
+
+bool MediaStationEngine::isFirstGenerationEngine() {
+	if (_boot == nullptr) {
+		error("Attempted to get engine version before BOOT.STM was read");
+	} else {
+		return (_boot->_versionInfo == nullptr);
+	}
+}
+
+Common::Error MediaStationEngine::run() {
+	// INITIALIZE SUBSYSTEMS.
+	// All Media Station games run at 640x480.
+	initGraphics(SCREEN_WIDTH, SCREEN_HEIGHT);
+	_screen = new Graphics::Screen();
+	// TODO: Determine if all titles blank the screen to 0xff.
+	_screen->fillRect(Common::Rect(SCREEN_WIDTH, SCREEN_HEIGHT), 0xff);
+
+	// LOAD BOOT.STM.
+	Common::Path bootStmFilepath = Common::Path("BOOT.STM");
+	_boot = new Boot(bootStmFilepath);
+
+	// LOAD THE ROOT CONTEXT.
+	// This is because we might have assets that always need to be loaded.
+	Context *root = nullptr;
+	uint32 rootContextId = _boot->getRootContextId();
+	if (rootContextId != 0) {
+		root = loadContext(rootContextId);
+	} else {
+		warning("MediaStation::run(): Title has no root context");
+	}
+
+	Context *activeScreen = loadContext(_boot->_entryContextId);
+	if (activeScreen->_screenAsset != nullptr) {
+		// GET THE PALETTE.
+		setPaletteFromHeader(activeScreen->_screenAsset);
+
+		// PROCESS THE OPENING EVENT HANDLER.
+		EventHandler *entryEvent = activeScreen->_screenAsset->_eventHandlers.getValOrDefault(EventHandler::Type::Entry);
+		if (entryEvent != nullptr) {
+			debugC(5, kDebugScript, "Executing context entry event handler");
+			entryEvent->execute(activeScreen->_screenAsset->_id);
+		} else {
+			debugC(5, kDebugScript, "No context entry event handler");
+		}
+	}
+
+	while (true) {
+		processEvents();
+		if (shouldQuit()) {
+			break;
+		}
+
+		// PROCESS ANY ASSETS CURRENTLY PLAYING.
+		// TODO: Implement a dirty-rect based rendering system rather than
+		// redrawing the screen each time. This will require keeping track of
+		// all the images on screen at any given time, rather than just letting
+		// the movies handle their own drawing.
+		//
+		// First, they all need to be sorted by z-coordinate.
+		debugC(5, kDebugGraphics, "***** START RENDERING ***");
+		Common::sort(_assetsPlaying.begin(), _assetsPlaying.end(), [](Asset * a, Asset * b) {
+			return a->zIndex() > b->zIndex();
+		});
+		for (auto it = _assetsPlaying.begin(); it != _assetsPlaying.end();) {
+			(*it)->process();
+			if (!(*it)->isPlaying()) {
+				it = _assetsPlaying.erase(it);
+			} else {
+				++it;
+			}
+		}
+		debugC(5, kDebugGraphics, "***** END RENDERING ***");
+
+		// UPDATE THE SCREEN.
+		g_engine->_screen->update();
+		g_system->delayMillis(10);
+	}
+
+	// CLEAN UP.
+	return Common::kNoError;
+}
+
+void MediaStationEngine::processEvents() {
+	while (g_system->getEventManager()->pollEvent(e)) {
+		debugC(9, kDebugEvents, "\n@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@");
+		debugC(9, kDebugEvents, "@@@@   Processing events");
+		debugC(9, kDebugEvents, "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@\n");
+
+		switch (e.type) {
+		case Common::EVENT_QUIT: {
+			// TODO: Do any necessary clean-up.
+			return;
+		}
+
+		case Common::EVENT_KEYDOWN: {
+			break;
+		}
+
+		case Common::EVENT_LBUTTONDOWN: {
+			break;
+		}
+
+		case Common::EVENT_RBUTTONDOWN: {
+			// We are using the right button as a quick exit since the Media
+			// Station engine doesn't seem to use the right button itself.
+			warning("EVENT_RBUTTONDOWN: Quitting for development purposes");
+			quitGame();
+		}
+
+		default: {
+			break;
+		}
+		}
+	}
+}
+
+Context *MediaStationEngine::loadContext(uint32 contextId) {
+	if (_boot == nullptr) {
+		error("Cannot load contexts before BOOT.STM is read");
+	}
+
+	// GET THE FILE ID.
+	SubfileDeclaration *subfileDeclaration = _boot->_subfileDeclarations.getValOrDefault(contextId);
+	if (subfileDeclaration == nullptr) {
+		warning("MediaStationEngine::loadContext(): Couldn't find subfile declaration with ID 0x%x", contextId);
+		return nullptr;
+	}
+	// The subfile declarations have other assets too, so we need to make sure
+	if (subfileDeclaration->_startOffsetInFile != 16) {
+		warning("MediaStationEngine::loadContext(): Requested ID wasn't for a context.");
+		return nullptr;
+	}
+	uint32 fileId = subfileDeclaration->_fileId;
+
+	// GET THE FILENAME.
+	FileDeclaration *fileDeclaration = _boot->_fileDeclarations.getValOrDefault(fileId);
+	if (fileDeclaration == nullptr) {
+		warning("MediaStationEngine::loadContext(): Couldn't find file declaration with ID 0x%x", fileId);
+		return nullptr;
+	}
+	Common::String *fileName = fileDeclaration->_name;
+
+	// LOAD THE CONTEXT.
+	Common::Path entryCxtFilepath = Common::Path(*fileName);
+	Context *context = new Context(entryCxtFilepath);
+
+	// SET THE VARIABLES.
+	return context;
+}
+
+void MediaStationEngine::setPalette(Asset *palette) {
+	assert(palette != nullptr);
+	setPaletteFromHeader(palette->getHeader());
+}
+
+void MediaStationEngine::setPaletteFromHeader(AssetHeader *header) {
+	assert(header != nullptr);
+	if (header->_palette != nullptr) {
+		_screen->setPalette(*header->_palette);
+	} else {
+		warning("MediaStationEngine::setPaletteFromHeader(): Asset %d does not have a palette. Current palette will be unchanged.", header->_id);
+	}
+}
+
+void MediaStationEngine::addPlayingAsset(Asset *assetToAdd) {
+	// If we're already marking the asset as played, we don't need to mark it
+	// played again.
+	for (Asset *asset : g_engine->_assetsPlaying) {
+		if (asset == assetToAdd) {
+			return;
+		}
+	}
+	g_engine->_assetsPlaying.push_back(assetToAdd);
+}
+
+} // End of namespace MediaStation
diff --git a/engines/mediastation/mediastation.h b/engines/mediastation/mediastation.h
new file mode 100644
index 00000000000..b1f951db88b
--- /dev/null
+++ b/engines/mediastation/mediastation.h
@@ -0,0 +1,96 @@
+/* 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 MEDIASTATION_H
+#define MEDIASTATION_H
+
+#include "audio/mixer.h"
+#include "common/scummsys.h"
+#include "common/system.h"
+#include "common/error.h"
+#include "common/fs.h"
+#include "common/hash-str.h"
+#include "common/random.h"
+#include "common/serializer.h"
+#include "common/events.h"
+#include "common/util.h"
+#include "engines/engine.h"
+#include "engines/savestate.h"
+#include "graphics/screen.h"
+
+#include "mediastation/detection.h"
+#include "mediastation/datafile.h"
+#include "mediastation/boot.h"
+#include "mediastation/context.h"
+#include "mediastation/asset.h"
+
+namespace MediaStation {
+
+struct MediaStationGameDescription;
+
+class MediaStationEngine : public Engine {
+public:
+	MediaStationEngine(OSystem *syst, const ADGameDescription *gameDesc);
+	~MediaStationEngine() override;
+
+	uint32 getFeatures() const;
+	Common::String getGameId() const;
+	bool hasFeature(EngineFeature f) const override {
+		return
+		    (f == kSupportsReturnToLauncher);
+	};
+	bool isFirstGenerationEngine();
+	void processEvents();
+
+	void setPalette(Asset *palette);
+	void addPlayingAsset(Asset *assetToAdd);
+
+	Common::HashMap<uint, Asset *> _assets;
+	Common::HashMap<uint, Function *> _functions;
+	Common::HashMap<uint32, Variable *> _variables;
+	Common::HashMap<uint, Asset *> _assetsByChunkReference;
+	Graphics::Screen *_screen = nullptr;
+	Audio::Mixer *_mixer = nullptr;
+
+	// All Media Station titles run at 640x480.
+	const uint16 SCREEN_WIDTH = 640;
+	const uint16 SCREEN_HEIGHT = 480;
+
+protected:
+	Common::Error run() override;
+
+private:
+	Common::Event e;
+	const ADGameDescription *_gameDescription;
+	Common::RandomSource _randomSource;
+	Boot *_boot = nullptr;
+	Common::Array<Asset *> _assetsPlaying;
+
+	Context *loadContext(uint32 contextId);
+	void setPaletteFromHeader(AssetHeader *header);
+};
+
+extern MediaStationEngine *g_engine;
+#define SHOULD_QUIT ::MediaStation::g_engine->shouldQuit()
+
+} // End of namespace MediaStation
+
+#endif // MEDIASTATION_H
diff --git a/engines/mediastation/metaengine.cpp b/engines/mediastation/metaengine.cpp
new file mode 100644
index 00000000000..d110b15e766
--- /dev/null
+++ b/engines/mediastation/metaengine.cpp
@@ -0,0 +1,58 @@
+/* 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/translation.h"
+
+#include "mediastation/metaengine.h"
+#include "mediastation/detection.h"
+#include "mediastation/mediastation.h"
+
+namespace MediaStation {
+
+static const ADExtraGuiOptionsMap optionsList[] = {
+	AD_EXTRA_GUI_OPTIONS_TERMINATOR
+};
+
+} // End of namespace MediaStation
+
+const char *MediaStationMetaEngine::getName() const {
+	return "mediastation";
+}
+
+const ADExtraGuiOptionsMap *MediaStationMetaEngine::getAdvancedExtraGuiOptions() const {
+	return MediaStation::optionsList;
+}
+
+Common::Error MediaStationMetaEngine::createInstance(OSystem *syst, Engine **engine, const ADGameDescription *desc) const {
+	*engine = new MediaStation::MediaStationEngine(syst, desc);
+	return Common::kNoError;
+}
+
+bool MediaStationMetaEngine::hasFeature(MetaEngineFeature f) const {
+	return checkExtendedSaves(f) ||
+	       (f == kSupportsLoadingDuringStartup);
+}
+
+#if PLUGIN_ENABLED_DYNAMIC(MEDIASTATION)
+	REGISTER_PLUGIN_DYNAMIC(MEDIASTATION, PLUGIN_TYPE_ENGINE, MediaStationMetaEngine);
+#else
+	REGISTER_PLUGIN_STATIC(MEDIASTATION, PLUGIN_TYPE_ENGINE, MediaStationMetaEngine);
+#endif
diff --git a/engines/mediastation/metaengine.h b/engines/mediastation/metaengine.h
new file mode 100644
index 00000000000..3439a986600
--- /dev/null
+++ b/engines/mediastation/metaengine.h
@@ -0,0 +1,43 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef MEDIASTATION_METAENGINE_H
+#define MEDIASTATION_METAENGINE_H
+
+#include "engines/advancedDetector.h"
+
+class MediaStationMetaEngine : public AdvancedMetaEngine<ADGameDescription> {
+public:
+	const char *getName() const override;
+
+	Common::Error createInstance(OSystem *syst, Engine **engine, const ADGameDescription *desc) const override;
+
+	/**
+	 * Determine whether the engine supports the specified MetaEngine feature.
+	 *
+	 * Used by e.g. the launcher to determine whether to enable the Load button.
+	 */
+	bool hasFeature(MetaEngineFeature f) const override;
+
+	const ADExtraGuiOptionsMap *getAdvancedExtraGuiOptions() const override;
+};
+
+#endif // MEDIASTATION_METAENGINE_H
diff --git a/engines/mediastation/module.mk b/engines/mediastation/module.mk
new file mode 100644
index 00000000000..ffecac42e15
--- /dev/null
+++ b/engines/mediastation/module.mk
@@ -0,0 +1,40 @@
+MODULE := engines/mediastation
+
+MODULE_OBJS = \
+	mediastation.o \
+	asset.o \
+	assetheader.o \
+	chunk.o \
+	context.o \
+	contextparameters.o \
+	bitmap.o \
+	assets/image.o \
+	assets/palette.o \
+	assets/sound.o \
+	assets/movie.o \
+	assets/sprite.o \
+	assets/path.o \
+	assets/hotspot.o \
+	assets/timer.o \
+	assets/canvas.o \
+	mediascript/eventhandler.o \
+	mediascript/codechunk.o \
+	mediascript/function.o \
+	mediascript/variable.o \
+	mediascript/operand.o \
+	subfile.o \
+	boot.o \
+	datum.o \
+	datafile.o \
+	metaengine.o
+
+# This module can be built as a plugin
+ifeq ($(ENABLE_MEDIASTATION), DYNAMIC_PLUGIN)
+PLUGIN := 1
+endif
+
+# Include common rules
+include $(srcdir)/rules.mk
+
+# Detection objects
+DETECT_OBJS += $(MODULE)/detection.o
diff --git a/engines/mediastation/subfile.cpp b/engines/mediastation/subfile.cpp
new file mode 100644
index 00000000000..874225eb1e7
--- /dev/null
+++ b/engines/mediastation/subfile.cpp
@@ -0,0 +1,70 @@
+/* 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 "mediastation/mediastation.h"
+#include "mediastation/chunk.h"
+#include "mediastation/subfile.h"
+#include "mediastation/debugchannels.h"
+
+namespace MediaStation {
+
+Subfile::Subfile() : _stream(nullptr) {}
+
+Subfile::Subfile(Common::SeekableReadStream *stream) : _stream(stream) {
+	// VERIFY FILE SIGNATURE.
+	debugC(5, kDebugLoading, "\n*** Subfile::Subfile(): Got new subfile (@0x%llx) ***", _stream->pos());
+	rootChunk = nextChunk();
+	if (rootChunk.id != MKTAG('R', 'I', 'F', 'F'))
+		// TODO: These need to be interpreted as ASCII.
+		error("Subfile::Subfile(): Expected \"RIFF\" chunk, got %s", tag2str(rootChunk.id));
+	_stream->skip(4); // IMTS
+
+	// READ RATE CHUNK.
+	// This chunk shoudl always contain just one piece of data - the "rate"
+	// (whatever that is). Usually it is zero.
+	// TODO: Figure out what this actually is.
+	Chunk rateChunk = nextChunk();
+	if (rateChunk.id != MKTAG('r', 'a', 't', 'e'))
+		error("Subfile::Subfile(): Expected \"rate\" chunk, got %s", tag2str(rootChunk.id));
+	rate = _stream->readUint32LE();
+
+	// READ PAST LIST CHUNK.
+	nextChunk();
+
+	// QUEUE UP THE FIRST DATA CHUNK.
+	if (_stream->readUint32BE() != MKTAG('d', 'a', 't', 'a'))
+		error("Subfile::Subfile(): Expected \"data\" as first bytes of subfile, got %s", tag2str(rateChunk.id));
+}
+
+Chunk Subfile::nextChunk() {
+	// Chunks always start on even-indexed bytes.
+	if (_stream->pos() & 1)
+		_stream->skip(1);
+	currentChunk = Chunk(_stream);
+	return currentChunk;
+}
+
+bool Subfile::atEnd() {
+	// TODO: Is this the best place to put this and approach to use?
+	return rootChunk.bytesRemaining() == 0;
+}
+
+} // End of namespace MediaStation
diff --git a/engines/mediastation/subfile.h b/engines/mediastation/subfile.h
new file mode 100644
index 00000000000..812970a0b28
--- /dev/null
+++ b/engines/mediastation/subfile.h
@@ -0,0 +1,51 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "common/file.h"
+
+#include "mediastation/chunk.h"
+
+#ifndef MEDIASTATION_SUBFILE_H
+#define MEDIASTATION_SUBFILE_H
+
+namespace MediaStation {
+
+class Subfile {
+public:
+	Chunk rootChunk;
+	Chunk currentChunk;
+
+	Subfile();
+	Subfile(Common::SeekableReadStream *stream);
+
+	Chunk nextChunk();
+	bool atEnd();
+
+	uint32 rate;
+
+private:
+	Common::SeekableReadStream *_stream;
+
+};
+
+} // End of namespace MediaStation
+
+#endif
\ No newline at end of file




More information about the Scummvm-git-logs mailing list