[Scummvm-git-logs] scummvm master -> ddb8e9e2442d8bb028192534b8b778c42d38f8ce

npjg noreply at scummvm.org
Sat Feb 15 20:23:36 UTC 2025


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

Summary:
f5b1241869 MEDIASTATION: Make screen branching more correct
50ad6a3972 MEDIASTATION: Associate context references with correct context
8faa86f132 MEDIASTATION: Clean up detection entries
8093f55654 MEDIASTATION: Add executables to detection
b764d1d3a0 MEDIASTATION: Add initial cursor handlng
dfca86df33 MEDIASTATION: Fix sounds repeating when they shouldn't
c6f4f45484 MEDIASTATION: Stub unknown context section
ddb8e9e244 MEDIASTATION: Implement isActive script method on hotspots & sprites


Commit: f5b12418697c7c9c56cdc2a78a09a3ff8472ab4f
    https://github.com/scummvm/scummvm/commit/f5b12418697c7c9c56cdc2a78a09a3ff8472ab4f
Author: Nathanael Gentry (nathanael.gentrydb8 at gmail.com)
Date: 2025-02-15T15:10:52-05:00

Commit Message:
MEDIASTATION: Make screen branching more correct

Changed paths:
    engines/mediastation/mediastation.cpp
    engines/mediastation/mediastation.h


diff --git a/engines/mediastation/mediastation.cpp b/engines/mediastation/mediastation.cpp
index ecc1f5b25ff..cecc82a9c6a 100644
--- a/engines/mediastation/mediastation.cpp
+++ b/engines/mediastation/mediastation.cpp
@@ -136,7 +136,9 @@ Common::Error MediaStationEngine::run() {
 		warning("MediaStation::run(): Title has no root context");
 	}
 
-	branchToScreen(_boot->_entryContextId);
+	_requestedScreenBranchId = _boot->_entryContextId;
+	doBranchToScreen();
+
 	while (true) {
 		processEvents();
 		if (shouldQuit()) {
@@ -146,6 +148,15 @@ Common::Error MediaStationEngine::run() {
 		debugC(5, kDebugGraphics, "***** START SCREEN UPDATE ***");
 		for (auto it = _assetsPlaying.begin(); it != _assetsPlaying.end();) {
 			(*it)->process();
+
+			// If we're changing screens, exit out now so we don't try to access
+			// any assets that no longer exist.
+			if (_requestedScreenBranchId != 0) {
+				doBranchToScreen();
+				_requestedScreenBranchId = 0;
+				break;
+			}
+
 			if (!(*it)->isActive()) {
 				it = _assetsPlaying.erase(it);
 			} else {
@@ -265,18 +276,20 @@ Context *MediaStationEngine::loadContext(uint32 contextId) {
 		error("Cannot load contexts before BOOT.STM is read");
 	}
 
+	debugC(5, kDebugLoading, "MediaStationEngine::loadContext(): Loading context %d", contextId);
 	if (_loadedContexts.contains(contextId)) {
-		warning("MediaStationEngine::loadContext(): Context 0x%x already loaded, returning existing context", contextId);
+		warning("MediaStationEngine::loadContext(): Context %d already loaded, returning existing context", contextId);
 		return _loadedContexts.getVal(contextId);
 	}
 
 	// 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);
+		error("MediaStationEngine::loadContext(): Couldn't find subfile declaration with ID %d", contextId);
 		return nullptr;
 	}
-	// The subfile declarations have other assets too, so we need to make sure
+	// There are other assets in a subfile too, so we need to make sure we're
+	// referencing the screen asset, at the start of the file.
 	if (subfileDeclaration->_startOffsetInFile != 16) {
 		warning("MediaStationEngine::loadContext(): Requested ID wasn't for a context.");
 		return nullptr;
@@ -344,9 +357,13 @@ void MediaStationEngine::addPlayingAsset(Asset *assetToAdd) {
 Operand MediaStationEngine::callMethod(BuiltInMethod methodId, Common::Array<Operand> &args) {
 	switch (methodId) {
 	case kBranchToScreenMethod: {
-		assert(args.size() == 1);
+		assert(args.size() >= 1);
+		if (args.size() > 1) {
+			// TODO: Figure out what the rest of the args can be.
+			warning("MediaStationEngine::callMethod(): branchToScreen got more than one arg");
+		}
 		uint32 contextId = args[0].getAssetId();
-		branchToScreen(contextId);
+		_requestedScreenBranchId = contextId;
 		return Operand();
 	}
 
@@ -363,7 +380,7 @@ Operand MediaStationEngine::callMethod(BuiltInMethod methodId, Common::Array<Ope
 	}
 }
 
-void MediaStationEngine::branchToScreen(uint32 contextId) {
+void MediaStationEngine::doBranchToScreen() {
 	if (_currentContext != nullptr) {
 		EventHandler *exitEvent = _currentContext->_screenAsset->_eventHandlers.getValOrDefault(kExitEvent);
 		if (exitEvent != nullptr) {
@@ -372,9 +389,11 @@ void MediaStationEngine::branchToScreen(uint32 contextId) {
 		} else {
 			debugC(5, kDebugScript, "No context exit event handler");
 		}
+
+		releaseContext(_currentContext->_screenAsset->_id);
 	}
 
-	Context *context = loadContext(contextId);
+	Context *context = loadContext(_requestedScreenBranchId);
 	_currentContext = context;
 	_dirtyRects.push_back(Common::Rect(SCREEN_WIDTH, SCREEN_HEIGHT));
 	_currentHotspot = nullptr;
@@ -390,6 +409,8 @@ void MediaStationEngine::branchToScreen(uint32 contextId) {
 			debugC(5, kDebugScript, "No context entry event handler");
 		}
 	}
+
+	_requestedScreenBranchId = 0;
 }
 
 void MediaStationEngine::releaseContext(uint32 contextId) {
diff --git a/engines/mediastation/mediastation.h b/engines/mediastation/mediastation.h
index 991908c37e9..298c599c1d0 100644
--- a/engines/mediastation/mediastation.h
+++ b/engines/mediastation/mediastation.h
@@ -103,10 +103,13 @@ private:
 	const ADGameDescription *_gameDescription;
 	Common::RandomSource _randomSource;
 	Boot *_boot = nullptr;
-	Common::Array<Asset *> _assetsPlaying;
+	Common::List<Asset *> _assetsPlaying;
 	Common::HashMap<uint, Context *> _loadedContexts;
 	Asset *_currentHotspot = nullptr;
 
+	uint _requestedScreenBranchId = 0;
+	void doBranchToScreen();
+
 	Context *loadContext(uint32 contextId);
 	void setPaletteFromHeader(AssetHeader *header);
 	void branchToScreen(uint32 contextId);


Commit: 50ad6a397236b1d1a696a8a8907abebf924900f8
    https://github.com/scummvm/scummvm/commit/50ad6a397236b1d1a696a8a8907abebf924900f8
Author: Nathanael Gentry (nathanael.gentrydb8 at gmail.com)
Date: 2025-02-15T15:10:52-05:00

Commit Message:
MEDIASTATION: Associate context references with correct context

This also means we don't need the hacky function to find
the root context, since all contexts will now load it as
a child context.

Changed paths:
    engines/mediastation/boot.cpp
    engines/mediastation/boot.h
    engines/mediastation/mediastation.cpp


diff --git a/engines/mediastation/boot.cpp b/engines/mediastation/boot.cpp
index 2a08337e7ce..7d08bd2b84a 100644
--- a/engines/mediastation/boot.cpp
+++ b/engines/mediastation/boot.cpp
@@ -52,13 +52,6 @@ ContextDeclaration::ContextDeclaration(Chunk &chunk) {
 		_isLast = false;
 	}
 
-	// READ THE FILE REFERENCES.
-	while (kContextDeclarationFileReference == sectionType) {
-		int fileReference = Datum(chunk).u.i;
-		_fileReferences.push_back(fileReference);
-		sectionType = getSectionType(chunk);
-	}
-
 	// READ THE OTHER CONTEXT METADATA.
 	if (kContextDeclarationPlaceholder == sectionType) {
 		// READ THE FILE NUMBER.
@@ -100,6 +93,19 @@ ContextDeclaration::ContextDeclaration(Chunk &chunk) {
 	} else {
 		error("ContextDeclaration::ContextDeclaration(): Unknown section type 0x%x", static_cast<uint>(sectionType));
 	}
+
+	// READ THE FILE REFERENCES.
+	// We don't know how many file references there are beforehand, so we'll
+	// just read until we get something else.
+	int rewindOffset = 0;
+	sectionType = getSectionType(chunk);
+	while (kContextDeclarationFileReference == sectionType) {
+		int fileReference = Datum(chunk).u.i;
+		_fileReferences.push_back(fileReference);
+		rewindOffset = chunk.pos();
+		sectionType = getSectionType(chunk);
+	}
+	chunk.seek(rewindOffset);
 }
 
 ContextDeclarationSectionType ContextDeclaration::getSectionType(Chunk &chunk) {
@@ -426,17 +432,6 @@ BootSectionType Boot::getSectionType(Chunk& chunk) {
 	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;
diff --git a/engines/mediastation/boot.h b/engines/mediastation/boot.h
index afec01c6b8f..e2b046269b8 100644
--- a/engines/mediastation/boot.h
+++ b/engines/mediastation/boot.h
@@ -214,8 +214,6 @@ public:
 
 	Boot(const Common::Path &path);
 	~Boot();
-
-	uint32 getRootContextId();
 };
 
 } // End of namespace MediaStation
diff --git a/engines/mediastation/mediastation.cpp b/engines/mediastation/mediastation.cpp
index cecc82a9c6a..71db25f9722 100644
--- a/engines/mediastation/mediastation.cpp
+++ b/engines/mediastation/mediastation.cpp
@@ -126,15 +126,6 @@ Common::Error MediaStationEngine::run() {
 	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) {
-		loadContext(rootContextId);
-	} else {
-		warning("MediaStation::run(): Title has no root context");
-	}
 
 	_requestedScreenBranchId = _boot->_entryContextId;
 	doBranchToScreen();


Commit: 8faa86f132262cfff3fcf329fadea9a6b2f1e062
    https://github.com/scummvm/scummvm/commit/8faa86f132262cfff3fcf329fadea9a6b2f1e062
Author: Nathanael Gentry (nathanael.gentrydb8 at gmail.com)
Date: 2025-02-15T15:10:52-05:00

Commit Message:
MEDIASTATION: Clean up detection entries

Changed paths:
    engines/mediastation/detection_tables.h


diff --git a/engines/mediastation/detection_tables.h b/engines/mediastation/detection_tables.h
index a741af8283f..ed658053af4 100644
--- a/engines/mediastation/detection_tables.h
+++ b/engines/mediastation/detection_tables.h
@@ -22,11 +22,10 @@
 namespace MediaStation {
 
 const PlainGameDescriptor mediastationGames[] = {
-	// Commercially released games
 	{ "georgeshrinks", "George Shrinks Interactive Storybook" },
 	{ "mousecookie", "If You Give a Mouse a Cookie Interactive Storybook" },
 	{ "lionking", "Disney's Animated Storybook: The Lion King" },
-	{ "lambchop", "Lambchop Loves Music" },
+	{ "lambchop", "Lamb Chop Loves Music" },
 	{ "frogprince", "Fractured Fairy Tales: The Frog Prince" },
 	{ "honeytree", "Disney's Animated Storybook: Winnie the Pooh and the Honey Tree" },
 	{ "notredame", "Disney's Animated Storybook: The Hunchback of Notre Dame" },
@@ -34,7 +33,6 @@ const PlainGameDescriptor mediastationGames[] = {
 	{ "ibmcrayola", "IBM/Crayola Print Factory" },
 	{ "ibmcrayolaholiday", "IBM/Crayola Print Factory Holiday Activity Pack" },
 	{ "101dalmatians", "Disney's Animated Storybook: 101 Dalmatians" },
-	{ "rupertsinteractiveadventures", "Rupert's Interactive Adventures" },
 	{ "herculesasb", "Disney's Animated Storybook: Hercules" },
 	{ "barbieasrapunzel", "Magic Fairy Tales: Barbie as Rapunzel" },
 	{ "tonkasearchandrescue", "Tonka Search and Rescue" },
@@ -44,30 +42,13 @@ const PlainGameDescriptor mediastationGames[] = {
 	{ "tonkaworkshop", "Tonka Workshop" },
 	{ "tonkaraceway", "Tonka Raceway" },
 	{ "stuartlittlebigcity", "Stuart Little: Big City Adventures"},
-
-	// Released demos
-	{ "puzzlecastledemo", "Puzzle Castle Demo" }, // From Frog Prince CD-ROM
-
-	// For development purposes - detect any folder as a game
-	{ "mediastation", "Media Station Game" },
-	{ 0, 0 }
+	{ nullptr, nullptr }
 };
 
 const ADGameDescription gameDescriptions[] = {
-	// For testing purposes, any folder with a "MediaStation" file in it can be run.
+	// George Shrinks Interactive Storybook
 	{
-		"mediastation",
-		nullptr,
-		AD_ENTRY1s("MediaStation", 0, AD_NO_SIZE),
-		Common::EN_ANY,
-		Common::kPlatformWindows,
-		ADGF_UNSTABLE,
-		GUIO1(GUIO_NOASPECT)
-	},
-
-	// Commercially released games
-	{
-		"georgeshrinks", 
+		"georgeshrinks",
 		nullptr,
 		AD_ENTRY2s(
 			"BOOT.STM", "5b7c08398fe6ae016db9d94ad9240241", 6744,
@@ -78,6 +59,8 @@ const ADGameDescription gameDescriptions[] = {
 		ADGF_UNSTABLE,
 		GUIO1(GUIO_NOASPECT)
 	},
+
+	// If You Give a Mouse a Cookie Interactive Storybook
 	{
 		"mousecookie",
 		nullptr,
@@ -90,9 +73,11 @@ const ADGameDescription gameDescriptions[] = {
 		ADGF_UNSTABLE,
 		GUIO1(GUIO_NOASPECT)
 	},
+
+	// Disney's Animated Storybook: The Lion King
 	{
 		"lionking",
-		"2.0GB",
+		"2.0/GB",
 		AD_ENTRY2s(
 			"BOOT.STM", "72e9211eb97b968e8db20c0fec919eb4", 23610,
 			"100.CXT", "069b86f0912627bc1fffb1dee9b68afa", 1455740
@@ -102,6 +87,8 @@ const ADGameDescription gameDescriptions[] = {
 		ADGF_UNSTABLE,
 		GUIO1(GUIO_NOASPECT)
 	},
+
+	// Lamb Chop Loves Music
 	{
 		"lambchop",
 		nullptr,
@@ -114,6 +101,8 @@ const ADGameDescription gameDescriptions[] = {
 		ADGF_UNSTABLE,
 		GUIO1(GUIO_NOASPECT)
 	},
+
+	// Fractured Fairy Tales: The Frog Prince
 	{
 		"frogprince",
 		nullptr,
@@ -126,18 +115,22 @@ const ADGameDescription gameDescriptions[] = {
 		ADGF_UNSTABLE,
 		GUIO1(GUIO_NOASPECT)
 	},
+
+	// Disney's Animated Storybook: Winnie the Pooh and the Honey Tree
 	{
 		"honeytree",
 		nullptr,
 		AD_ENTRY2s(
-			"BOOT.STM", "694bcc9887f159137f3a0d937cfbbb08", 53904,
-			"100.CXT", "fde1e528d69fbd060dfc2691320bc05b", 1971658
+			"BOOT.STM", "9b9f528bf9c9b8ebe194b0c47dbe485e", 55422,
+			"100.CXT", "30f010077fd0489933989a562db81ad6", 1971940
 		),
 		Common::EN_USA,
 		Common::kPlatformWindows,
 		ADGF_UNSTABLE,
 		GUIO1(GUIO_NOASPECT)
 	},
+
+	// Disney's Animated Storybook: The Hunchback of Notre Dame
 	{
 		"notredame",
 		nullptr,
@@ -150,6 +143,8 @@ const ADGameDescription gameDescriptions[] = {
 		ADGF_UNSTABLE,
 		GUIO1(GUIO_NOASPECT)
 	},
+
+	// Puzzle Castle
 	{
 		"puzzlecastle",
 		nullptr,
@@ -163,17 +158,19 @@ const ADGameDescription gameDescriptions[] = {
 		GUIO1(GUIO_NOASPECT)
 	},
 	{
-		"puzzlecastledemo",
-		nullptr,
+		"puzzlecastle",
+		"Demo",
 		AD_ENTRY2s(
 			"BOOT.STM", "b7ce005e0d67021f792ebb73e7fbe34c", 5960,
 			"100.CXT", "cc64a6fcb3af2736d622658cff3ef2b5", 1262
 		),
 		Common::EN_USA,
 		Common::kPlatformWindows,
-		ADGF_UNSTABLE,
+		ADGF_UNSTABLE | ADGF_DEMO,
 		GUIO1(GUIO_NOASPECT)
 	},
+
+	// IBM/Crayola Print Factory
 	{
 		"ibmcrayola",
 		nullptr,
@@ -186,6 +183,8 @@ const ADGameDescription gameDescriptions[] = {
 		ADGF_UNSTABLE,
 		GUIO1(GUIO_NOASPECT)
 	},
+
+	// IBM/Crayola Print Factory Holiday Activity Pack
 	{
 		"ibmcrayolaholiday",
 		nullptr,
@@ -198,30 +197,22 @@ const ADGameDescription gameDescriptions[] = {
 		ADGF_UNSTABLE,
 		GUIO1(GUIO_NOASPECT)
 	},
+
+	// Disney's Animated Storybook: 101 Dalmatians
 	{
 		"101dalmatians",
 		nullptr,
 		AD_ENTRY2s(
 			"BOOT.STM", "ee6725a718cbce640d02acec2b84825f", 47970,
 			"100.CXT", "2df853283a3fd2d079b06bc27b50527f", 6784502
-		),	
-		Common::EN_USA,
-		Common::kPlatformWindows,
-		ADGF_UNSTABLE,
-		GUIO1(GUIO_NOASPECT)
-	},
-	{
-		"rupertsinteractiveadventures",
-		nullptr,
-		AD_ENTRY2s(
-			"BOOT.STM", "051ff838587d43edc9836dc3a9888c16", 13112,
-			"100.CXT", "65326647eedc2ad9a8c0ccef274b3389", 5180650
 		),
 		Common::EN_USA,
 		Common::kPlatformWindows,
 		ADGF_UNSTABLE,
 		GUIO1(GUIO_NOASPECT)
 	},
+
+	// Disney's Animated Storybook: Hercules
 	{
 		"herculesasb",
 		nullptr,
@@ -234,6 +225,8 @@ const ADGameDescription gameDescriptions[] = {
 		ADGF_UNSTABLE,
 		GUIO1(GUIO_NOASPECT)
 	},
+
+	// Magic Fairy Tales: Barbie as Rapunzel
 	{
 		"barbieasrapunzel",
 		nullptr,
@@ -246,18 +239,22 @@ const ADGameDescription gameDescriptions[] = {
 		ADGF_UNSTABLE,
 		GUIO1(GUIO_NOASPECT)
 	},
+
+	// Tonka Search and Rescue
 	{
 		"tonkasearchandrescue",
 		nullptr,
 		AD_ENTRY2s(
-			"BOOT.STM", "eef6bdf54d2ae25af0ec29361fd4c126", 17530,
-			"100.CXT", "f0bcc27b61bfb33328db2dd537b2b6e3", 1688902
+			"BOOT.STM", "90c5f17734219c3a442316d21e6833f8", 25362,
+			"100.CXT", "85a05487b6c499ba3ce86d043305ddfd", 6410562
 		),
 		Common::EN_USA,
 		Common::kPlatformWindows,
 		ADGF_UNSTABLE,
 		GUIO1(GUIO_NOASPECT)
 	},
+
+	// Disney presents Ariel's Story Studio
 	{
 		"arielstorystudio",
 		"1.0",
@@ -282,6 +279,8 @@ const ADGameDescription gameDescriptions[] = {
 		ADGF_UNSTABLE,
 		GUIO1(GUIO_NOASPECT)
 	},
+
+	// Tonka Garage
 	{
 		"tonkagarage",
 		nullptr,
@@ -294,6 +293,8 @@ const ADGameDescription gameDescriptions[] = {
 		ADGF_UNSTABLE,
 		GUIO1(GUIO_NOASPECT)
 	},
+
+	// D.W. the Picky Eater (Living Books)
 	{
 		"dwpickyeater",
 		nullptr,
@@ -306,6 +307,8 @@ const ADGameDescription gameDescriptions[] = {
 		ADGF_UNSTABLE,
 		GUIO1(GUIO_NOASPECT)
 	},
+
+	// Tonka Workshop
 	{
 		"tonkaworkshop",
 		nullptr,
@@ -318,6 +321,8 @@ const ADGameDescription gameDescriptions[] = {
 		ADGF_UNSTABLE,
 		GUIO1(GUIO_NOASPECT)
 	},
+
+	// Tonka Raceway
 	{
 		"tonkaraceway",
 		nullptr,
@@ -330,6 +335,8 @@ const ADGameDescription gameDescriptions[] = {
 		ADGF_UNSTABLE,
 		GUIO1(GUIO_NOASPECT)
 	},
+
+	// Stuart Little: Big City Adventures
 	{
 		"stuartlittlebigcity",
 		nullptr,


Commit: 8093f5565483bd9cd8205b305a39f98ffe725cd5
    https://github.com/scummvm/scummvm/commit/8093f5565483bd9cd8205b305a39f98ffe725cd5
Author: Nathanael Gentry (nathanael.gentrydb8 at gmail.com)
Date: 2025-02-15T15:10:52-05:00

Commit Message:
MEDIASTATION: Add executables to detection

This is needed to get the name of the executable
where we need to get game cursors. This executable
name is different for every game, and some cursors
are different between Macintosh and Windows versions.

Since these cursors are being loaded from the Windows/Mac
executable resources and not some hardcoded offsets, it was
judged best to just add the executables to the detection rather
than hardcoding all the different cursors we'd need.

The Macintosh version of 101 Dalmatians is added to the detection,
to provide a basic test of the planned Macintosh cursor handling.
Macintosh versions of many other titles exist and should be
added later.

Changed paths:
    engines/mediastation/detection_tables.h
    engines/mediastation/mediastation.cpp
    engines/mediastation/mediastation.h


diff --git a/engines/mediastation/detection_tables.h b/engines/mediastation/detection_tables.h
index ed658053af4..543cb62f29c 100644
--- a/engines/mediastation/detection_tables.h
+++ b/engines/mediastation/detection_tables.h
@@ -45,12 +45,17 @@ const PlainGameDescriptor mediastationGames[] = {
 	{ nullptr, nullptr }
 };
 
+// In these entries, the executable must always be listed first.
+// The title version can be obtained by pressing Ctrl-V while running
+// the original interpreter. Some titles include a built-in language code
+// (e.g. "v1.0/DE" or "v1.0/US") but others do not (e.g. "v1.1").
 const ADGameDescription gameDescriptions[] = {
 	// George Shrinks Interactive Storybook
 	{
 		"georgeshrinks",
-		nullptr,
-		AD_ENTRY2s(
+		"v1.0",
+		AD_ENTRY3s(
+			"GEORGE.EX_", "ae70a2efbe5fbe66ad7bb9f269ea0a2f", 139674, // Packed executable
 			"BOOT.STM", "5b7c08398fe6ae016db9d94ad9240241", 6744,
 			"103.CXT", "e7d563ff79f1b1416e5f1e0c803f78ec", 1474802
 		),
@@ -63,8 +68,9 @@ const ADGameDescription gameDescriptions[] = {
 	// If You Give a Mouse a Cookie Interactive Storybook
 	{
 		"mousecookie",
-		nullptr,
-		AD_ENTRY2s(
+		"v2.0",
+		AD_ENTRY3s(
+			"MOUSECKE.EXE", "58350e268ec0cdf4fa21281a9d83fd80", 329568,
 			"BOOT.STM", "11d11b2067519d8368175cc8e8caa94f", 59454,
 			"100.CXT", "cac48b9bb5f327d035a831cd15f1688c", 1762032
 		),
@@ -77,8 +83,9 @@ const ADGameDescription gameDescriptions[] = {
 	// Disney's Animated Storybook: The Lion King
 	{
 		"lionking",
-		"2.0/GB",
-		AD_ENTRY2s(
+		"v2.0/GB",
+		AD_ENTRY3s(
+			"LIONKING.EXE", "9cc25600be13c402f4edf15772989393", 358368,
 			"BOOT.STM", "72e9211eb97b968e8db20c0fec919eb4", 23610,
 			"100.CXT", "069b86f0912627bc1fffb1dee9b68afa", 1455740
 		),
@@ -91,8 +98,9 @@ const ADGameDescription gameDescriptions[] = {
 	// Lamb Chop Loves Music
 	{
 		"lambchop",
-		nullptr,
-		AD_ENTRY2s(
+		"v1.0",
+		AD_ENTRY3s(
+			"LCMUSIC.EXE", "1830080b410abd103c5064f583bdca1e", 329504,
 			"BOOT.STM", "c90200e52bcaad52524520d461caef2b", 29884,
 			"100.CXT", "ce40843604b8c52701694cd543072a88", 3253600
 		),
@@ -105,8 +113,9 @@ const ADGameDescription gameDescriptions[] = {
 	// Fractured Fairy Tales: The Frog Prince
 	{
 		"frogprince",
-		nullptr,
-		AD_ENTRY2s(
+		"v1.1",
+		AD_ENTRY3s(
+			"FPRINCE.EXE", "cd7aff763bb4879cc3a11def90dd7cb7", 513984,
 			"BOOT.STM", "1c6d14c87790d009702be8ba4e4e5906", 13652,
 			"100.CXT", "a5ec9a32c3741a20b82e1793e76234b2", 1630762
 		),
@@ -119,8 +128,9 @@ const ADGameDescription gameDescriptions[] = {
 	// Disney's Animated Storybook: Winnie the Pooh and the Honey Tree
 	{
 		"honeytree",
-		nullptr,
-		AD_ENTRY2s(
+		"v2.0/US", // Also includes Spanish as an in-game language option.
+		AD_ENTRY3s(
+			"WPHTASB.EXE", "916666c49efeeaeae61eb669405fc66f", 433024,
 			"BOOT.STM", "9b9f528bf9c9b8ebe194b0c47dbe485e", 55422,
 			"100.CXT", "30f010077fd0489933989a562db81ad6", 1971940
 		),
@@ -133,8 +143,9 @@ const ADGameDescription gameDescriptions[] = {
 	// Disney's Animated Storybook: The Hunchback of Notre Dame
 	{
 		"notredame",
-		nullptr,
-		AD_ENTRY2s(
+		"v1.0/US",
+		AD_ENTRY3s(
+			"HB_ASB.EXE", "f3f2e83562d7941a99d299ae31600f07", 533120,
 			"BOOT.STM", "7949e1253a62531e53963a2fffe57211", 55300,
 			"100.CXT", "54c11a94888a1b747e1c8935b7315889", 4766278
 		),
@@ -147,8 +158,9 @@ const ADGameDescription gameDescriptions[] = {
 	// Puzzle Castle
 	{
 		"puzzlecastle",
-		nullptr,
-		AD_ENTRY2s(
+		"v1.0",
+		AD_ENTRY3s(
+			"PZCASTLE.EXE", "ce44597dcbad42f2396d4963c06714d5", 528224,
 			"BOOT.STM", "7b0faf38da2d76df40b4085eed6f4fc8", 22080,
 			"100.CXT", "ebc4b6247b742733c81456dfd299aa55", 3346944
 		),
@@ -159,8 +171,9 @@ const ADGameDescription gameDescriptions[] = {
 	},
 	{
 		"puzzlecastle",
-		"Demo",
-		AD_ENTRY2s(
+		"v1.0 Demo",
+		AD_ENTRY3s(
+			"DEMO.EXE", "63dedc1e2cdf8a39725ef9ca99273cc4", 514496,
 			"BOOT.STM", "b7ce005e0d67021f792ebb73e7fbe34c", 5960,
 			"100.CXT", "cc64a6fcb3af2736d622658cff3ef2b5", 1262
 		),
@@ -173,8 +186,9 @@ const ADGameDescription gameDescriptions[] = {
 	// IBM/Crayola Print Factory
 	{
 		"ibmcrayola",
-		nullptr,
-		AD_ENTRY2s(
+		"v1.0/US",
+		AD_ENTRY3s(
+			"PRINTFAC.EXE", "2571746dcb8b8d386f2ef07255e715ba", 721248,
 			"BOOT.STM", "359542015c6665c70252cf21a8467cdb", 11044,
 			"100.CXT", "42bffe4165640dd1e64a6e8565f48af3", 5125226
 		),
@@ -187,8 +201,9 @@ const ADGameDescription gameDescriptions[] = {
 	// IBM/Crayola Print Factory Holiday Activity Pack
 	{
 		"ibmcrayolaholiday",
-		nullptr,
-		AD_ENTRY2s(
+		"v1.0/US",
+		AD_ENTRY3s(
+			"HOLIDAY.EXE", "10b70a2cb94f92295d26f43540129f14", 742048,
 			"BOOT.STM", "50f30298bf700f357d98c4390f75cb7a", 10932,
 			"100.CXT", "8110f70f1d01d0f42cac9b1bb6d2de12", 4967390
 		),
@@ -201,8 +216,9 @@ const ADGameDescription gameDescriptions[] = {
 	// Disney's Animated Storybook: 101 Dalmatians
 	{
 		"101dalmatians",
-		nullptr,
-		AD_ENTRY2s(
+		"v1.0/US",
+		AD_ENTRY3s(
+			"101_ASB.EXE", "42d7d258652bdc7ecd0e39e8b326bc38", 528736,
 			"BOOT.STM", "ee6725a718cbce640d02acec2b84825f", 47970,
 			"100.CXT", "2df853283a3fd2d079b06bc27b50527f", 6784502
 		),
@@ -215,8 +231,9 @@ const ADGameDescription gameDescriptions[] = {
 	// Disney's Animated Storybook: Hercules
 	{
 		"herculesasb",
-		nullptr,
-		AD_ENTRY2s(
+		"v1.0/US",
+		AD_ENTRY3s(
+			"HERC_ASB.EXE", "23663fabde2db43a2e8f6a23e7495e01", 543040,
 			"BOOT.STM", "afc773416e46e30873f743e234794957", 26924,
 			"100.CXT", "56875e1640320909e9697f11b5a8c9a6", 4895998
 		),
@@ -229,8 +246,9 @@ const ADGameDescription gameDescriptions[] = {
 	// Magic Fairy Tales: Barbie as Rapunzel
 	{
 		"barbieasrapunzel",
-		nullptr,
-		AD_ENTRY2s(
+		"v1.0",
+		AD_ENTRY3s(
+			"RAPUNZEL.EXE", "e47a752fe748258ebc0f5ee6f31b385b", 535840,
 			"BOOT.STM", "eef6bdf54d2ae25af0ec29361fd4c126", 17530,
 			"100.CXT", "f0bcc27b61bfb33328db2dd537b2b6e3", 1688902
 		),
@@ -243,8 +261,9 @@ const ADGameDescription gameDescriptions[] = {
 	// Tonka Search and Rescue
 	{
 		"tonkasearchandrescue",
-		nullptr,
-		AD_ENTRY2s(
+		"v1.0/US",
+		AD_ENTRY3s(
+			"TONKA_SR.EXE", "46037a28e0cbf6df9ed3218e58ee1ae2", 561984, // 32-bit (PE)
 			"BOOT.STM", "90c5f17734219c3a442316d21e6833f8", 25362,
 			"100.CXT", "85a05487b6c499ba3ce86d043305ddfd", 6410562
 		),
@@ -257,8 +276,9 @@ const ADGameDescription gameDescriptions[] = {
 	// Disney presents Ariel's Story Studio
 	{
 		"arielstorystudio",
-		"1.0",
-		AD_ENTRY2s(
+		"v1.0/US",
+		AD_ENTRY3s(
+			"ARIEL_SS.EXE", "bb2afc5205a852e59d77631c454fde5d", 606720,
 			"BOOT.STM", "297670b908f887ed6c97b364406575d0", 65480,
 			"100.CXT", "c12c5b784ad931eca293a9816c11043b", 6532022
 		),
@@ -269,8 +289,9 @@ const ADGameDescription gameDescriptions[] = {
 	},
 	{
 		"arielstorystudio",
-		"1.1",
-		AD_ENTRY2s(
+		"v1.1/US",
+		AD_ENTRY3s(
+			"MERMAID.EXE", "2eabe2910cf5a2df32dcc889ebd90cea", 634240,
 			"BOOT.STM", "7d53a551efde620fe5b332d7b1f009ab", 65450,
 			"100.CXT", "993252bca0aa6791ca3da30b1ae6f5f8", 6532022
 		),
@@ -283,8 +304,9 @@ const ADGameDescription gameDescriptions[] = {
 	// Tonka Garage
 	{
 		"tonkagarage",
-		nullptr,
-		AD_ENTRY2s(
+		"v1.1/US",
+		AD_ENTRY3s(
+			"TONKA_GR.EXE", "4e7e75ac11c996454b334f9add38c691", 1297408,
 			"BOOT.STM", "fc8863bb302e94d3b778b3a97556601b", 25208,
 			"100.CXT", "13683c2a06275920181d9dda5b2b69e7", 2691398
 		),
@@ -297,8 +319,9 @@ const ADGameDescription gameDescriptions[] = {
 	// D.W. the Picky Eater (Living Books)
 	{
 		"dwpickyeater",
-		nullptr,
-		AD_ENTRY2s(
+		"v1.0/US (32-bit)",
+		AD_ENTRY3s(
+			"DW_32.EXE", "3612aa19a2809f9cb6ee48046e5d7068", 1079296, // 32-bit (PE)
 			"BOOT.STM", "80cc94e3e894ee8c5a22a9c07a33d891", 26402,
 			"100.CXT", "e65e359ab25d7a639cf369a01b9a21c0", 2163750
 		),
@@ -311,8 +334,9 @@ const ADGameDescription gameDescriptions[] = {
 	// Tonka Workshop
 	{
 		"tonkaworkshop",
-		nullptr,
-		AD_ENTRY2s(
+		"v1.0/US",
+		AD_ENTRY3s(
+			"TONKA_W.EXE", "f3e480c57967093b87db68cb8f3f3a18", 1097728, // 32-bit (PE)
 			"BOOT.STM", "15e6d32925f557f3196fd0bb79b25375", 38190,
 			"100.CXT", "1cb35998f2e044eee59a96120b3bda6c", 2691398
 		),
@@ -325,8 +349,9 @@ const ADGameDescription gameDescriptions[] = {
 	// Tonka Raceway
 	{
 		"tonkaraceway",
-		nullptr,
-		AD_ENTRY2s(
+		"v1.0/US",
+		AD_ENTRY3s(
+			"TONKA_RA.EXE", "cccd33d4d9e824bada6a1ca115794226", 1735680, // 32-bit (PE)
 			"BOOT.STM", "da512cb9bcd18465294e544ed790881c", 12272,
 			"100.CXT", "30802327b29fbfa722a707c3d3b0f8f8", 2691398
 		),
@@ -339,8 +364,9 @@ const ADGameDescription gameDescriptions[] = {
 	// Stuart Little: Big City Adventures
 	{
 		"stuartlittlebigcity",
-		nullptr,
-		AD_ENTRY2s(
+		"v1.0/US",
+		AD_ENTRY3s(
+			"STUARTCD.EXE", "8aaa593c9a1a17a0e41f424d046b3de8", 1191936, // 32-bit (PE)
 			"BOOT.STM", "992787bf30104a4b7aa2ead64dda21ff", 10974,
 			"100.CXT", "21f44a1d1de6abf8bd67341c155dfead", 2691398
 		),
diff --git a/engines/mediastation/mediastation.cpp b/engines/mediastation/mediastation.cpp
index 71db25f9722..59d33359dc5 100644
--- a/engines/mediastation/mediastation.cpp
+++ b/engines/mediastation/mediastation.cpp
@@ -106,6 +106,10 @@ Common::String MediaStationEngine::getGameId() const {
 	return _gameDescription->gameId;
 }
 
+const char *MediaStationEngine::getAppName() const {
+	return _gameDescription->filesDescriptions[0].fileName;
+}
+
 bool MediaStationEngine::isFirstGenerationEngine() {
 	if (_boot == nullptr) {
 		error("Attempted to get engine version before BOOT.STM was read");
diff --git a/engines/mediastation/mediastation.h b/engines/mediastation/mediastation.h
index 298c599c1d0..cadb698a591 100644
--- a/engines/mediastation/mediastation.h
+++ b/engines/mediastation/mediastation.h
@@ -66,10 +66,12 @@ public:
 
 	uint32 getFeatures() const;
 	Common::String getGameId() const;
+	const char *getAppName() const;
 	bool hasFeature(EngineFeature f) const override {
 		return
 		    (f == kSupportsReturnToLauncher);
 	};
+
 	bool isFirstGenerationEngine();
 	void processEvents();
 	void redraw();


Commit: b764d1d3a0d91225aac7d4c69844e6a939b82c3f
    https://github.com/scummvm/scummvm/commit/b764d1d3a0d91225aac7d4c69844e6a939b82c3f
Author: Nathanael Gentry (nathanael.gentrydb8 at gmail.com)
Date: 2025-02-15T15:10:52-05:00

Commit Message:
MEDIASTATION: Add initial cursor handlng

Changed paths:
  A engines/mediastation/cursors.cpp
  A engines/mediastation/cursors.h
    engines/mediastation/assets/hotspot.cpp
    engines/mediastation/detection_tables.h
    engines/mediastation/mediastation.cpp
    engines/mediastation/mediastation.h
    engines/mediastation/module.mk


diff --git a/engines/mediastation/assets/hotspot.cpp b/engines/mediastation/assets/hotspot.cpp
index 7bc08218d22..bdcac5210a8 100644
--- a/engines/mediastation/assets/hotspot.cpp
+++ b/engines/mediastation/assets/hotspot.cpp
@@ -74,12 +74,14 @@ Operand Hotspot::callMethod(BuiltInMethod methodId, Common::Array<Operand> &args
 		assert(args.empty());
 		_isActive = true;
 		g_engine->addPlayingAsset(this);
+		g_engine->refreshActiveHotspot();
 		return Operand();
 	}
 
 	case kMouseDeactivateMethod: {
 		assert(args.empty());
 		_isActive = false;
+		g_engine->refreshActiveHotspot();
 		return Operand();
 	}
 
diff --git a/engines/mediastation/cursors.cpp b/engines/mediastation/cursors.cpp
new file mode 100644
index 00000000000..49071e18ffc
--- /dev/null
+++ b/engines/mediastation/cursors.cpp
@@ -0,0 +1,136 @@
+/* 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/cursors.h"
+#include "mediastation/debugchannels.h"
+
+#include "common/system.h"
+#include "common/file.h"
+#include "common/formats/winexe_ne.h"
+#include "common/formats/winexe_pe.h"
+#include "graphics/cursorman.h"
+#include "graphics/maccursor.h"
+#include "graphics/wincursor.h"
+
+namespace MediaStation {
+
+void CursorManager::showCursor() {
+	CursorMan.showMouse(true);
+}
+
+void CursorManager::hideCursor() {
+	CursorMan.showMouse(false);
+}
+
+void CursorManager::setDefaultCursor() {
+	Graphics::Cursor *cursor = Graphics::makeDefaultWinCursor();
+	CursorMan.replaceCursor(cursor);
+	delete cursor;
+}
+
+WindowsCursorManager::WindowsCursorManager(const Common::Path &appName) {
+	loadCursors(appName);
+}
+
+WindowsCursorManager::~WindowsCursorManager() {
+	for (auto it = _cursors.begin(); it != _cursors.end(); ++it) {
+		delete it->_value;
+	}
+	_cursors.clear();
+}
+
+void WindowsCursorManager::loadCursors(const Common::Path &appName) {
+	if (appName.empty()) {
+		error("WindowsCursorManager::loadCursors(): No executable to load cursors from");
+	} else if (!Common::File::exists(appName)) {
+		error("WindowsCursorManager::loadCursors(): Executable %s doesn't exist", appName.toString().c_str());
+	}
+
+	Common::WinResources *exe = Common::WinResources::createFromEXE(appName);
+	if (!exe->loadFromEXE(appName)) {
+		error("WindowsCursorManager::loadCursors(): Could not load resources from executable %s", appName.toString().c_str());
+	}
+
+	const Common::Array<Common::WinResourceID> cursorGroups = exe->getIDList(Common::kWinGroupCursor);
+	for (Common::WinResourceID cursorGroup : cursorGroups) {
+		Common::String resourceString = cursorGroup.getString();
+		if (resourceString.empty()) {
+			warning("WindowsCursorManager::loadCursors(): Got Windows cursor group with no string ID");
+			continue;
+		}
+		Graphics::WinCursorGroup *group = Graphics::WinCursorGroup::createCursorGroup(exe, cursorGroup);
+		_cursors.setVal(resourceString, group);
+	}
+	delete exe;
+}
+
+void WindowsCursorManager::setCursor(const Common::String &name) {
+	Graphics::WinCursorGroup *group = _cursors.getValOrDefault(name);
+	if (group != nullptr) {
+		Graphics::Cursor *cursor = group->cursors[0].cursor;
+		CursorMan.replaceCursor(cursor);
+	} else {
+		error("WindowsCursorManager::setCursor(): Reqested Windows cursor %s not found", name.c_str());
+	}
+}
+
+MacCursorManager::MacCursorManager(const Common::Path &appName) {
+	if (appName.empty()) {
+		error("MacCursorManager::loadCursors(): No file to load cursors from");
+	} else if (!Common::File::exists(appName)) {
+		error("MacCursorManager::loadCursors(): File %s doesn't exist", appName.toString().c_str());
+	}
+
+	_resFork = new Common::MacResManager();
+	if (!_resFork->open(appName) || !_resFork->hasResFork()) {
+		error("MacCursorManager::loadCursors(): Could not load resource fork from %s", appName.toString().c_str());
+	}
+}
+
+MacCursorManager::~MacCursorManager() {
+	delete _resFork;
+	_resFork = nullptr;
+}
+
+void MacCursorManager::setCursor(const Common::String &name) {
+	// Try to load a color cursor first.
+	Common::SeekableReadStream *stream = _resFork->getResource(MKTAG('c', 'r', 's', 'r'), name);
+	if (stream == nullptr) {
+		// Fall back to attempting to load a mnochrome cursor.
+		stream = _resFork->getResource(MKTAG('C', 'U', 'R', 'S'), name);
+	}
+
+	// Make sure we got a resource.
+	if (stream == nullptr) {
+		error("MacCursorManager::setCursor(): Reqested Mac cursor %s not found", name.c_str());
+	}
+
+	Graphics::MacCursor *macCursor = new Graphics::MacCursor();
+	if (!macCursor->readFromStream(*stream)) {
+		error("MacCursorManager::setCursor(): Error parsing cursor %s from stream", name.c_str());
+	}
+	CursorMan.replaceCursor(macCursor);
+
+	delete macCursor;
+	delete stream;
+}
+
+} // End of namespace MediaStation
diff --git a/engines/mediastation/cursors.h b/engines/mediastation/cursors.h
new file mode 100644
index 00000000000..6ecf5bf3347
--- /dev/null
+++ b/engines/mediastation/cursors.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_CURSORS_H
+#define MEDIASTATION_CURSORS_H
+
+#include "common/scummsys.h"
+#include "common/hashmap.h"
+#include "common/str.h"
+#include "common/macresman.h"
+#include "common/formats/winexe.h"
+#include "graphics/wincursor.h"
+
+namespace MediaStation {
+
+// Media Station stores cursors in the executable as named resources.
+class CursorManager {
+public:
+	CursorManager() {}
+	virtual ~CursorManager() {}
+
+	virtual void showCursor();
+	virtual void hideCursor();
+
+	virtual void setCursor(const Common::String &name) = 0;
+
+protected:
+	virtual void setDefaultCursor();
+};
+
+class WindowsCursorManager : public CursorManager {
+public:
+	WindowsCursorManager(const Common::Path &appName);
+	~WindowsCursorManager() override;
+
+	virtual void setCursor(const Common::String &name) override;
+
+protected:
+	void loadCursors(const Common::Path &appName);
+
+private:
+	Common::HashMap<Common::String, Graphics::WinCursorGroup *> _cursors;
+};
+
+class MacCursorManager : public CursorManager {
+public:
+	explicit MacCursorManager(const Common::Path &appName);
+	~MacCursorManager() override;
+
+	virtual void setCursor(const Common::String &name) override;
+
+private:
+	Common::MacResManager *_resFork;
+};
+
+} // End of namespace MediaStation
+
+#endif
\ No newline at end of file
diff --git a/engines/mediastation/detection_tables.h b/engines/mediastation/detection_tables.h
index 543cb62f29c..fe538641022 100644
--- a/engines/mediastation/detection_tables.h
+++ b/engines/mediastation/detection_tables.h
@@ -227,6 +227,19 @@ const ADGameDescription gameDescriptions[] = {
 		ADGF_UNSTABLE,
 		GUIO1(GUIO_NOASPECT)
 	},
+	{
+		"101dalmatians",
+		"v1.0/US",
+		AD_ENTRY3s(
+			"101 Dalmatians StoryBook", "1611f83747b3ac4dd33c8b866535e425", 1046272,
+			"BOOT.STM", "ee6725a718cbce640d02acec2b84825f", 47970,
+			"100.CXT", "2df853283a3fd2d079b06bc27b50527f", 6784502
+		),
+		Common::EN_USA,
+		Common::kPlatformMacintosh,
+		ADGF_UNSTABLE,
+		GUIO1(GUIO_NOASPECT)
+	},
 
 	// Disney's Animated Storybook: Hercules
 	{
diff --git a/engines/mediastation/mediastation.cpp b/engines/mediastation/mediastation.cpp
index 59d33359dc5..f9eff7099db 100644
--- a/engines/mediastation/mediastation.cpp
+++ b/engines/mediastation/mediastation.cpp
@@ -54,6 +54,9 @@ MediaStationEngine::~MediaStationEngine() {
 	delete _screen;
 	_screen = nullptr;
 
+	delete _cursor;
+	_cursor = nullptr;
+
 	delete _boot;
 	_boot = nullptr;
 
@@ -106,6 +109,10 @@ Common::String MediaStationEngine::getGameId() const {
 	return _gameDescription->gameId;
 }
 
+Common::Platform MediaStationEngine::getPlatform() const {
+	return _gameDescription->platform;
+}
+
 const char *MediaStationEngine::getAppName() const {
 	return _gameDescription->filesDescriptions[0].fileName;
 }
@@ -130,6 +137,14 @@ Common::Error MediaStationEngine::run() {
 	Common::Path bootStmFilepath = Common::Path("BOOT.STM");
 	_boot = new Boot(bootStmFilepath);
 
+	if (getPlatform() == Common::kPlatformWindows) {
+		_cursor = new WindowsCursorManager(getAppName());
+	} else if (getPlatform() == Common::kPlatformMacintosh) {
+		_cursor = new MacCursorManager(getAppName());
+	} else {
+		error("MediaStationEngine::run(): Attempted to use unsupported platform %s", Common::getPlatformDescription(getPlatform()));
+	}
+	_cursor->showCursor();
 
 	_requestedScreenBranchId = _boot->_entryContextId;
 	doBranchToScreen();
@@ -182,29 +197,7 @@ void MediaStationEngine::processEvents() {
 		}
 
 		case Common::EVENT_MOUSEMOVE: {
-			Asset *hotspot = findAssetToAcceptMouseEvents(e.mouse);
-			if (hotspot != nullptr) {
-				if (_currentHotspot == nullptr) {
-					_currentHotspot = hotspot;
-					debugC(5, kDebugEvents, "EVENT_MOUSEMOVE (%d, %d): Entered hotspot %d", e.mouse.x, e.mouse.y, hotspot->getHeader()->_id);
-					hotspot->runEventHandlerIfExists(kMouseEnteredEvent);
-				} else if (_currentHotspot == hotspot) {
-					// We are still in the same hotspot.
-				} else {
-					_currentHotspot->runEventHandlerIfExists(kMouseExitedEvent);
-					_currentHotspot = hotspot;
-					debugC(5, kDebugEvents, "EVENT_MOUSEMOVE (%d, %d): Exited hotspot %d", e.mouse.x, e.mouse.y, hotspot->getHeader()->_id);
-					hotspot->runEventHandlerIfExists(kMouseEnteredEvent);
-				}
-				debugC(5, kDebugEvents, "EVENT_MOUSEMOVE (%d, %d): Sent to hotspot %d", e.mouse.x, e.mouse.y, hotspot->getHeader()->_id);
-				hotspot->runEventHandlerIfExists(kMouseMovedEvent);
-			} else {
-				if (_currentHotspot != nullptr) {
-					_currentHotspot->runEventHandlerIfExists(kMouseExitedEvent);
-					debugC(5, kDebugEvents, "EVENT_MOUSEMOVE (%d, %d): Exited hotspot %d", e.mouse.x, e.mouse.y, _currentHotspot->getHeader()->_id);
-					_currentHotspot = nullptr;
-				}
-			}
+			refreshActiveHotspot();
 			break;
 		}
 
@@ -242,6 +235,43 @@ void MediaStationEngine::processEvents() {
 	}
 }
 
+void MediaStationEngine::setCursor(uint id) {
+	// The cursor ID is not a resource ID in the executable, but a numeric ID
+	// that's a lookup into BOOT.STM, which gives actual name the name of the
+	// resource in the executable.
+	if (id != 0) {
+		CursorDeclaration *cursorDeclaration = _boot->_cursorDeclarations.getValOrDefault(id);
+		if (cursorDeclaration == nullptr) {
+			error("MediaStationEngine::setCursor(): Cursor %d not declared", id);
+		}
+		_cursor->setCursor(*cursorDeclaration->_name);
+	}
+}
+
+void MediaStationEngine::refreshActiveHotspot() {
+	Asset *hotspot = findAssetToAcceptMouseEvents(_eventMan->getMousePos());
+	if (hotspot != _currentHotspot) {
+		if (_currentHotspot != nullptr) {
+			_currentHotspot->runEventHandlerIfExists(kMouseExitedEvent);
+			debugC(5, kDebugEvents, "EVENT_MOUSEMOVE (%d, %d): Exited hotspot %d", e.mouse.x, e.mouse.y, _currentHotspot->getHeader()->_id);
+		}
+		_currentHotspot = hotspot;
+		if (hotspot != nullptr) {
+			debugC(5, kDebugEvents, "EVENT_MOUSEMOVE (%d, %d): Entered hotspot %d", e.mouse.x, e.mouse.y, hotspot->getHeader()->_id);
+			setCursor(hotspot->getHeader()->_cursorResourceId);
+			hotspot->runEventHandlerIfExists(kMouseEnteredEvent);
+		} else {
+			// There is no hotspot, so set the default cursor for this screen instead.
+			setCursor(_currentContext->_screenAsset->_cursorResourceId);
+		}
+	}
+
+	if (hotspot != nullptr) {
+		debugC(5, kDebugEvents, "EVENT_MOUSEMOVE (%d, %d): Sent to hotspot %d", e.mouse.x, e.mouse.y, hotspot->getHeader()->_id);
+		hotspot->runEventHandlerIfExists(kMouseMovedEvent);
+	}
+}
+
 void MediaStationEngine::redraw() {
 	if (_dirtyRects.empty()) {
 		return;
diff --git a/engines/mediastation/mediastation.h b/engines/mediastation/mediastation.h
index cadb698a591..efe93a361f7 100644
--- a/engines/mediastation/mediastation.h
+++ b/engines/mediastation/mediastation.h
@@ -41,6 +41,7 @@
 #include "mediastation/boot.h"
 #include "mediastation/context.h"
 #include "mediastation/asset.h"
+#include "mediastation/cursors.h"
 
 namespace MediaStation {
 
@@ -66,6 +67,7 @@ public:
 
 	uint32 getFeatures() const;
 	Common::String getGameId() const;
+	Common::Platform getPlatform() const;
 	const char *getAppName() const;
 	bool hasFeature(EngineFeature f) const override {
 		return
@@ -74,6 +76,7 @@ public:
 
 	bool isFirstGenerationEngine();
 	void processEvents();
+	void refreshActiveHotspot();
 	void redraw();
 
 	void setPalette(Asset *palette);
@@ -104,6 +107,12 @@ private:
 	Common::FSNode _gameDataDir;
 	const ADGameDescription *_gameDescription;
 	Common::RandomSource _randomSource;
+
+	// In Media Station, only the cursors are stored in the executable; everything
+	// else is in the Context (*.CXT) data files.
+	CursorManager *_cursor;
+	void setCursor(uint id);
+
 	Boot *_boot = nullptr;
 	Common::List<Asset *> _assetsPlaying;
 	Common::HashMap<uint, Context *> _loadedContexts;
diff --git a/engines/mediastation/module.mk b/engines/mediastation/module.mk
index de5b968da16..69231ed7c1a 100644
--- a/engines/mediastation/module.mk
+++ b/engines/mediastation/module.mk
@@ -20,6 +20,7 @@ MODULE_OBJS = \
 	chunk.o \
 	context.o \
 	contextparameters.o \
+	cursors.o \
 	datafile.o \
 	datum.o \
 	mediascript/codechunk.o \


Commit: dfca86df3372ff3ccce3e178d41354a69a7ae5f9
    https://github.com/scummvm/scummvm/commit/dfca86df3372ff3ccce3e178d41354a69a7ae5f9
Author: Nathanael Gentry (nathanael.gentrydb8 at gmail.com)
Date: 2025-02-15T15:10:52-05:00

Commit Message:
MEDIASTATION: Fix sounds repeating when they shouldn't

Changed paths:
    engines/mediastation/assets/sound.cpp
    engines/mediastation/assets/sound.h


diff --git a/engines/mediastation/assets/sound.cpp b/engines/mediastation/assets/sound.cpp
index ae42081897d..3de081af481 100644
--- a/engines/mediastation/assets/sound.cpp
+++ b/engines/mediastation/assets/sound.cpp
@@ -45,10 +45,10 @@ Sound::~Sound() {
 
 void Sound::process() {
 	processTimeEventHandlers();
-	if (!g_engine->_mixer->isSoundHandleActive(_handle)) {
-		_isActive = false;
-		_startTime = 0;
-		_lastProcessedTime = 0;
+
+	if (_isActive && !g_engine->_mixer->isSoundHandleActive(_handle)) {
+		_isPlaying = false;
+		setInactive();
 		_handle = Audio::SoundHandle();
 
 		runEventHandlerIfExists(kSoundEndEvent);
@@ -122,15 +122,17 @@ void Sound::timePlay() {
 		warning("Sound::timePlay(): Attempt to play a sound that is already playing");
 		return;
 	}
-	_isActive = true;
-	g_engine->addPlayingAsset(this);
 
-	_startTime = g_system->getMillis();
-	_lastProcessedTime = 0;
-	_handle = Audio::SoundHandle();
+	if (_streams.empty()) {
+		warning("Sound::timePlay(): Sound has no contents, probably because the sound is in INSTALL.CXT and isn't loaded yet");
+		return;
+	}
+	_isPlaying = true;
+	setActive();
 
 	runEventHandlerIfExists(kSoundBeginEvent);
 
+	_handle = Audio::SoundHandle();
 	if (!_streams.empty()) {
 		Audio::QueuingAudioStream *audio = Audio::makeQueuingAudioStream(22050, false);
 		for (Audio::SeekableAudioStream *stream : _streams) {
@@ -148,9 +150,7 @@ void Sound::timeStop() {
 		return;
 	}
 
-	_isActive = false;
-	_startTime = 0;
-	_lastProcessedTime = 0;
+	setInactive();
 
 	g_engine->_mixer->stopHandle(_handle);
 	_handle = Audio::SoundHandle();
diff --git a/engines/mediastation/assets/sound.h b/engines/mediastation/assets/sound.h
index adba464e98c..16ec5416e02 100644
--- a/engines/mediastation/assets/sound.h
+++ b/engines/mediastation/assets/sound.h
@@ -48,6 +48,7 @@ private:
 	SoundEncoding _encoding;
 	Audio::SoundHandle _handle;
 	Common::Array<Audio::SeekableAudioStream *> _streams;
+	bool _isPlaying = true;
 
 	// Script method implementations
 	void timePlay();


Commit: c6f4f4548480fd52998f7439c75b89e91a50695e
    https://github.com/scummvm/scummvm/commit/c6f4f4548480fd52998f7439c75b89e91a50695e
Author: Nathanael Gentry (nathanael.gentrydb8 at gmail.com)
Date: 2025-02-15T15:10:52-05:00

Commit Message:
MEDIASTATION: Stub unknown context section

This is so the first story screen of Dalmatians can load.

Changed paths:
    engines/mediastation/context.cpp
    engines/mediastation/context.h


diff --git a/engines/mediastation/context.cpp b/engines/mediastation/context.cpp
index f475ba05e40..f03b0a20470 100644
--- a/engines/mediastation/context.cpp
+++ b/engines/mediastation/context.cpp
@@ -328,8 +328,10 @@ bool Context::readHeaderSection(Subfile &subfile, Chunk &chunk) {
 		break;
 	}
 
-	case kContextEndSection: {
-		error("Context::readHeaderSection(): END Not implemented yet");
+	case kContextUnkAtEndSection: {
+		int unk1 = Datum(chunk).u.i;
+		int unk2 = Datum(chunk).u.i;
+		debugC(5, kDebugLoading, "Context::readHeaderSection(): unk1 = %d, unk2 = %d", unk1, unk2);
 		return false;
 	}
 
diff --git a/engines/mediastation/context.h b/engines/mediastation/context.h
index 618c3c9d310..4738cacdb81 100644
--- a/engines/mediastation/context.h
+++ b/engines/mediastation/context.h
@@ -38,7 +38,7 @@ enum ContextSectionType {
 	kContextOldStyleSection = 0x000d,
 	kContextParametersSection = 0x000e,
 	kContextPaletteSection = 0x05aa,
-	kContextEndSection = 0x0010,
+	kContextUnkAtEndSection = 0x0010,
 	kContextAssetHeaderSection = 0x0011,
 	kContextPoohSection = 0x057a,
 	kContextAssetLinkSection = 0x0013,


Commit: ddb8e9e2442d8bb028192534b8b778c42d38f8ce
    https://github.com/scummvm/scummvm/commit/ddb8e9e2442d8bb028192534b8b778c42d38f8ce
Author: Nathanael Gentry (nathanael.gentrydb8 at gmail.com)
Date: 2025-02-15T15:10:52-05:00

Commit Message:
MEDIASTATION: Implement isActive script method on hotspots & sprites

Changed paths:
    engines/mediastation/assets/hotspot.cpp
    engines/mediastation/assets/timer.cpp


diff --git a/engines/mediastation/assets/hotspot.cpp b/engines/mediastation/assets/hotspot.cpp
index bdcac5210a8..69debeaec0f 100644
--- a/engines/mediastation/assets/hotspot.cpp
+++ b/engines/mediastation/assets/hotspot.cpp
@@ -85,6 +85,14 @@ Operand Hotspot::callMethod(BuiltInMethod methodId, Common::Array<Operand> &args
 		return Operand();
 	}
 
+	case kIsActiveMethod: {
+		assert(args.empty());
+		Operand returnValue(kOperandTypeLiteral1);
+		returnValue.putInteger(static_cast<int>(_isActive));
+		return returnValue;
+	}
+
+
 	default: {
 		error("Hotspot::callMethod(): Got unimplemented method ID %d", methodId);
 	}
diff --git a/engines/mediastation/assets/timer.cpp b/engines/mediastation/assets/timer.cpp
index 5af7507b856..6948e7c2c49 100644
--- a/engines/mediastation/assets/timer.cpp
+++ b/engines/mediastation/assets/timer.cpp
@@ -40,6 +40,13 @@ Operand Timer::callMethod(BuiltInMethod methodId, Common::Array<Operand> &args)
 		return Operand();
 	}
 
+	case kIsPlayingMethod: {
+		assert(args.size() == 0);
+		Operand returnValue(kOperandTypeLiteral1);
+		returnValue.putInteger(static_cast<int>(_isActive));
+		return returnValue;
+	}
+
 	default: {
 		error("Got unimplemented method ID %d", methodId);
 	}




More information about the Scummvm-git-logs mailing list