[Scummvm-git-logs] scummvm master -> 62980da2baf35f7fa6d818b3306dfb0a83649a97

mgerhardy noreply at scummvm.org
Sun Aug 23 19:14:04 UTC 2026


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

Summary:
e56e4430c1 MACS2: implemented soft restart from action bar
8222ad0a06 MACS2: removed duplicated version check
29ff99203f MACS2: prepare for v2 loading
e2f7dd8e1c MACS2: load v2 scene images
e14828f78a MACS2: read v2 resource file layout
158e5d307f MACS2: this data is not available for v2, too
314fdbcf4b MACS2: more v2 data points connected
cbf51f43ea MACS2: support v2 scaling for shading tables
80404bd795 MACS2: removed magic numbers
7fa9a6deeb MACS2: fixed member name
bee5a75f26 MACS2: replaced magic numbers
9905b340f2 MACS2: removed magic numbers
88cfefbc7f MACS2: renamed variables - they are no constants anymore
8ff8fa25e6 MACS2: reduced scope
7fa2422e40 MACS2: minor opcode cleanup and fixed max hotspots
f97da85669 MACS2: ignore enhancement here if there is a native action bar
959699743c MACS2: fixed cursor hotspots
62980da2ba MACS2: fixed v2 map input


Commit: e56e4430c1d91a864e0655779b2662d414f1f26f
    https://github.com/scummvm/scummvm/commit/e56e4430c1d91a864e0655779b2662d414f1f26f
Author: Martin Gerhardy (martin.gerhardy at gmail.com)
Date: 2026-08-23T20:53:56+02:00

Commit Message:
MACS2: implemented soft restart from action bar

Changed paths:
    engines/macs2/actionbar.cpp
    engines/macs2/macs2.cpp
    engines/macs2/macs2.h


diff --git a/engines/macs2/actionbar.cpp b/engines/macs2/actionbar.cpp
index 9874e9b76e4..3baefde36aa 100644
--- a/engines/macs2/actionbar.cpp
+++ b/engines/macs2/actionbar.cpp
@@ -845,11 +845,8 @@ bool ActionBar::handleClickNative(const Common::Point &pos) {
 		g_engine->_optionsSubMode = 2;
 		refreshSaveSlotNames();
 	} else if (id == 0x20) {
-		// Soft restart requires dialect-v2 reinit; not available until that loader lands.
-		debugC(1, kDebugScript, "ActionBar: restart button ignored (no soft-restart yet)");
-		g_engine->_menuMode = 1;
-		g_engine->_optionsSubMode = 0;
-		g_engine->setCursorMode(Script::MouseMode::PanelCursor);
+		g_engine->softRestart();
+		return true;
 	} else if (id == 0x21) {
 		::GUI::MessageDialog quitDialog(
 			Common::U32String("Quit the game?"),
diff --git a/engines/macs2/macs2.cpp b/engines/macs2/macs2.cpp
index 2f920291468..e204bfb1fc9 100644
--- a/engines/macs2/macs2.cpp
+++ b/engines/macs2/macs2.cpp
@@ -361,6 +361,71 @@ void Macs2Engine::readExecutable() {
 	exeFileStream->read(containerInventoryIconIndices.data(), 12);
 }
 
+void Macs2Engine::softRestart() {
+	getMusic()->stopMusic();
+	stopSample();
+	stopSpeech();
+	clearDeltaAnim();
+	_skipSpeed = 1;
+	_menuMode = 1;
+	_optionsSubMode = 0;
+	_inventScroll = 1;
+	_saveListScroll = 1;
+
+	if (_scriptExecutor != nullptr) {
+		_scriptExecutor->_waitForDeltaAnim = false;
+		_scriptExecutor->_waitForDeltaSpeed = false;
+		_scriptExecutor->_waitForPcmSound = false;
+		_scriptExecutor->_waitForMusicControl = false;
+		_scriptExecutor->_waitForAdlibReady = false;
+		_scriptExecutor->_waitForObjectAnimStep = false;
+		_scriptExecutor->_waitForSpecialAnimStep = false;
+		_scriptExecutor->_waitingForUiClick = false;
+		_scriptExecutor->endFrameWait();
+		_scriptExecutor->releaseObjectStream();
+	}
+
+	View1 *currentView = (View1 *)findView("View1");
+	if (currentView != nullptr) {
+		for (Character *c : currentView->_characters)
+			delete c;
+		currentView->_characters.clear();
+		currentView->flushPendingCharacterDeletes();
+		currentView->_inventoryItems.clear();
+		currentView->_activeInventoryItem = nullptr;
+		currentView->_isShowingDialoguePanel = false;
+		currentView->_isDialogueChoiceInputActive = false;
+		currentView->_isShowingTextBox = false;
+		currentView->currentSpeechActData = SpeechActData();
+	}
+
+	for (uint i = 0; i < GameObjects::instance()._objects.size(); i++)
+		delete GameObjects::instance()._objects[i];
+	GameObjects::instance()._objects.clear();
+
+	delete Scenes::instance()._currentSceneScript;
+	delete Scenes::instance()._currentSceneStrings;
+	Scenes::instance()._currentSceneScript = nullptr;
+	Scenes::instance()._currentSceneStrings = nullptr;
+	Scenes::instance()._currentSceneSpecialAnimOffsets.clear();
+
+	_backgroundAnimations.clear();
+	_backgroundAnimationsBlobs.clear();
+	clearDeltaAnim();
+
+	delete _fileStream;
+	_fileStream = nullptr;
+
+	readResourceFile();
+
+	if (currentView != nullptr) {
+		currentView->_backgroundSurface.copyFrom(_sceneBackground);
+		currentView->_paletteDirty = true;
+		currentView->redraw();
+	}
+	runScriptExecutor();
+}
+
 void Macs2Engine::loadBootstrapResources() {
 	if (isAmiga())
 		readAmigaResources();
diff --git a/engines/macs2/macs2.h b/engines/macs2/macs2.h
index f55bcd9f747..a3a9e2d1465 100644
--- a/engines/macs2/macs2.h
+++ b/engines/macs2/macs2.h
@@ -294,6 +294,8 @@ public:
 	McsFileVersion detectMcsFileVersion(Common::SeekableReadStream &stream) const;
 	/** Load AHFFMACS0100 layout (loadResourceFile @ 1008:2e8d). */
 	void loadResourceFileV1();
+	/** Soft restart (options button 0x20 / Macs2PretReInit). */
+	void softRestart();
 	const char *getResourceMcsFilename() const;
 	/** Amiga: open DataA/Mdir, load OO objects as GameObjects, cursors, and scene stubs. */
 	void readAmigaResources();


Commit: 8222ad0a0626bb801dadc33efcd7ba1b7bd468b0
    https://github.com/scummvm/scummvm/commit/8222ad0a0626bb801dadc33efcd7ba1b7bd468b0
Author: Martin Gerhardy (martin.gerhardy at gmail.com)
Date: 2026-08-23T20:53:57+02:00

Commit Message:
MACS2: removed duplicated version check

Changed paths:
    engines/macs2/macs2.cpp


diff --git a/engines/macs2/macs2.cpp b/engines/macs2/macs2.cpp
index e204bfb1fc9..5f4cd19894e 100644
--- a/engines/macs2/macs2.cpp
+++ b/engines/macs2/macs2.cpp
@@ -158,17 +158,12 @@ void Macs2Engine::readResourceFile() {
 			file->read(fileData, size);
 			delete file;
 			_fileStream = new Common::MemoryReadStream(fileData, (uint32)size, DisposeAfterUse::YES);
+			loadResourceFileV1();
 		} else {
 			delete file;
 			error("readResourceFile(): unrecognized MCS magic in %s", mcsName);
 		}
 	}
-
-	_mcsFileVersion = detectMcsFileVersion(*_fileStream);
-	if (_mcsFileVersion != McsFileVersion::V1)
-		error("readResourceFile(): unrecognized MCS magic (expected %s)", kMcsMagicV1);
-
-	loadResourceFileV1();
 }
 
 void Macs2Engine::loadResourceFileV1() {


Commit: 29ff99203f5a3279761bde488205972735793101
    https://github.com/scummvm/scummvm/commit/29ff99203f5a3279761bde488205972735793101
Author: Martin Gerhardy (martin.gerhardy at gmail.com)
Date: 2026-08-23T20:53:57+02:00

Commit Message:
MACS2: prepare for v2 loading

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


diff --git a/engines/macs2/macs2.cpp b/engines/macs2/macs2.cpp
index 5f4cd19894e..6fda5df6e6e 100644
--- a/engines/macs2/macs2.cpp
+++ b/engines/macs2/macs2.cpp
@@ -611,6 +611,10 @@ bool Macs2Engine::loadSceneGraphics(uint32 sceneIndex) {
 		return true;
 	}
 
+	return loadSceneGraphicsV1(sceneIndex);
+}
+
+bool Macs2Engine::loadSceneGraphicsV1(uint32 sceneIndex) {
 	const uint32 newSceneIndex = sceneIndex;
 
 	// Background image
diff --git a/engines/macs2/macs2.h b/engines/macs2/macs2.h
index a3a9e2d1465..2f26eac5718 100644
--- a/engines/macs2/macs2.h
+++ b/engines/macs2/macs2.h
@@ -828,6 +828,8 @@ public:
 	 * Called from changeScene; Amiga uses native MXMM from DataA.
 	 */
 	bool loadSceneGraphics(uint32 sceneIndex);
+	/** AHFFMACS0100 scene package (RLE maps). */
+	bool loadSceneGraphicsV1(uint32 sceneIndex);
 
 	/**
 	 * Returns the game Id


Commit: e2f7dd8e1c64115f51841e85aece143cef4c3fdd
    https://github.com/scummvm/scummvm/commit/e2f7dd8e1c64115f51841e85aece143cef4c3fdd
Author: Martin Gerhardy (martin.gerhardy at gmail.com)
Date: 2026-08-23T20:53:57+02:00

Commit Message:
MACS2: load v2 scene images

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


diff --git a/engines/macs2/macs2.cpp b/engines/macs2/macs2.cpp
index 6fda5df6e6e..4722b9d3fe3 100644
--- a/engines/macs2/macs2.cpp
+++ b/engines/macs2/macs2.cpp
@@ -611,6 +611,9 @@ bool Macs2Engine::loadSceneGraphics(uint32 sceneIndex) {
 		return true;
 	}
 
+	if (isV2())
+		return loadSceneGraphicsV2(sceneIndex);
+
 	return loadSceneGraphicsV1(sceneIndex);
 }
 
@@ -785,6 +788,177 @@ bool Macs2Engine::loadSceneGraphicsV1(uint32 sceneIndex) {
 	return true;
 }
 
+bool Macs2Engine::loadSceneGraphicsV2(uint32 sceneIndex) {
+	if (_fileStream == nullptr)
+		return false;
+
+	Common::SeekableReadStream *stream = _fileStream;
+	stream->seek(_mcsDirectoryOffset + 0xC * sceneIndex - 0xC, SEEK_SET);
+	const uint32 bgImageOffset = stream->readUint32LE();
+	(void)stream->readUint32LE();
+	(void)stream->readUint32LE();
+
+	if (bgImageOffset == 0 || bgImageOffset >= (uint32)stream->size())
+		return false;
+
+	stream->seek(bgImageOffset, SEEK_SET);
+	if (!readMegaPicImage(stream, kWinScreenWidth, kWinGameHeight, _sceneBackground))
+		return false;
+
+	stream->read(_palVanilla, 0x300);
+	memcpy(_pal, _palVanilla, 0x300);
+	for (int i = 0; i < 256 * 3; i++)
+		_pal[i] = (_pal[i] * 259 + 33) >> 6;
+
+	if (_panelRemapTable.size() != 0x100)
+		_panelRemapTable.resize(0x100);
+	stream->read(_panelRemapTable.data(), 0x100);
+	stream->readByte();
+	stream->readByte();
+	stream->readByte();
+	_shadingTable.clear();
+	_shadingTable.resize(0x2000, 0);
+	if (stream->read(_shadingTable.data(), 0x2000) != 0x2000)
+		return false;
+
+	Graphics::ManagedSurface depthFull;
+	if (!readMegaPicImage(stream, kWinScreenWidth, kWinGameHeight, depthFull))
+		return false;
+	_depthMap.copyFrom(depthFull);
+
+	auto upscaleHalfRes = [](const Graphics::ManagedSurface &half, Graphics::ManagedSurface &full) {
+		full.create(kWinScreenWidth, kWinGameHeight, Graphics::PixelFormat::createFormatCLUT8());
+		for (int y = 0; y < half.h; y++) {
+			for (int x = 0; x < half.w; x++) {
+				const byte p = half.getPixel(x, y);
+				const int dx = x * 2;
+				const int dy = y * 2;
+				full.setPixel(dx, dy, p);
+				full.setPixel(dx + 1, dy, p);
+				full.setPixel(dx, dy + 1, p);
+				full.setPixel(dx + 1, dy + 1, p);
+			}
+		}
+	};
+
+	Graphics::ManagedSurface half;
+	if (!readMegaPicImage(stream, kScreenWidth, kGameHeight, half))
+		return false;
+	upscaleHalfRes(half, _pathfindingMap);
+
+	if (!readMegaPicImage(stream, kScreenWidth, kGameHeight, half))
+		return false;
+	upscaleHalfRes(half, _shadowMap);
+
+	if (!readMegaPicImage(stream, kScreenWidth, kGameHeight, half))
+		return false;
+	upscaleHalfRes(half, _hotspotMap);
+
+	pathfindingPoints.clear();
+	for (int i = 0; i < 16; i++) {
+		PathfindingPoint current;
+		current._index = i;
+		current._position.x = (int16)(stream->readUint16LE() << 1);
+		current._position.y = (int16)(stream->readUint16LE() << 1);
+		uint8 adj[8];
+		stream->read(adj, 8);
+		stream->skip(8);
+		const uint16 numConnections = stream->readUint16LE();
+		current._adjacentPoints.clear();
+		for (uint16 j = 0; j < numConnections && j < 4; j++)
+			current._adjacentPoints.push_back(adj[j]);
+		pathfindingPoints.push_back(current);
+	}
+	stream->skip(0x2c0 - 0x160);
+
+	_numHotspots = stream->readUint16LE();
+	_hotspotColorTable.clear();
+	_hotspotColorTable.resize(0x40 / sizeof(uint16));
+	stream->read(_hotspotColorTable.data(), 0x40);
+
+	const uint16 numBackgroundAnimations = stream->readUint16LE();
+	_backgroundAnimations.clear();
+	_backgroundAnimationsBlobs.clear();
+	_backgroundAnimations.resize(numBackgroundAnimations);
+	_backgroundAnimationsBlobs.resize(numBackgroundAnimations);
+	for (uint16 i = 0; i < numBackgroundAnimations; i++) {
+		BackgroundAnimationBlob &currentBlob = _backgroundAnimationsBlobs[i];
+		BackgroundAnimation &current = _backgroundAnimations[i];
+		const uint16 halfX = stream->readUint16LE();
+		const uint16 halfY = stream->readUint16LE();
+		const uint32 animSize = stream->readUint32LE();
+		currentBlob._blob.clear();
+		if (animSize > 0 && animSize < 0x1000000) {
+			currentBlob._blob.resize(animSize);
+			if (stream->read(currentBlob._blob.data(), animSize) != animSize)
+				return false;
+		}
+		currentBlob._unknown0C = stream->readUint16LE();
+		(void)stream->readByte();
+		const uint8 flagX = stream->readByte();
+		const uint8 flagY = stream->readByte();
+		currentBlob._unknown0E = stream->readByte();
+		(void)stream->readByte();
+
+		uint16 x = (uint16)(halfX << 1);
+		uint16 y = (uint16)(halfY << 1);
+		if (flagX)
+			x = (uint16)(x + 1);
+		if (flagY)
+			y = (uint16)(y + 1);
+		current._x = x;
+		current._y = y;
+		currentBlob._x = x;
+		currentBlob._y = y;
+
+		AnimBlobView blobView(currentBlob._blob);
+		const uint16 numFrames = blobView.isValid() ? blobView.sequenceLength() : 0;
+		current._frameIndex = 0;
+		current._frames.resize(numFrames);
+		const uint16 actualFrameCount = blobView.isValid() ? blobView.frameCount() : 0;
+		for (uint16 j = 0; j < actualFrameCount && j < numFrames; j++) {
+			AnimBlobView::FrameInfo fi;
+			if (!blobView.getFrameInfo(j, fi))
+				break;
+			current._frames[j]._width = fi.width;
+			current._frames[j]._height = fi.height;
+			current._frames[j]._data.resize((uint)fi.width * (uint)fi.height);
+			memcpy(current._frames[j]._data.data(), fi.pixels, (uint)fi.width * (uint)fi.height);
+		}
+	}
+
+	_numPathfindingPoints = stream->readUint16LE();
+	if (_numPathfindingPoints == 0 || _numPathfindingPoints > 16)
+		_numPathfindingPoints = 16;
+	(void)stream->readUint16LE();
+	(void)stream->readUint16LE();
+	_walkDepthThresholdY = (uint16)(stream->readUint16LE() << 1);
+	_walkDepthScaleFactor = stream->readUint16LE();
+	_walkBaseSpeedPct = stream->readUint16LE();
+	_scenePaletteMode = stream->readUint16LE();
+	_paletteDarkenPercent = stream->readUint16LE();
+
+	_mapImageFileOffset = 0;
+	_mapSubSceneTableFilePos = 0;
+
+	stream->seek(_mcsDirectoryOffset + 0xC * sceneIndex - 0x8, SEEK_SET);
+	const uint32 scriptBlobOffset = stream->readUint32LE();
+	_sceneResourceOffsets.clear();
+	clearDeltaAnim();
+	if (scriptBlobOffset != 0 && scriptBlobOffset < (uint32)stream->size()) {
+		const int64 saved = stream->pos();
+		stream->seek(scriptBlobOffset, SEEK_SET);
+		_sceneResourceOffsets.resize(0x200 / 4);
+		if (stream->read(_sceneResourceOffsets.data(), 0x200) != 0x200)
+			_sceneResourceOffsets.clear();
+		stream->seek(saved, SEEK_SET);
+	}
+
+	applyPaletteDarkening();
+	return true;
+}
+
+
 void Macs2Engine::changeScene(uint32 newSceneIndex, bool executeScript) {
 	// Release old scene resources
 	_backgroundAnimations.clear();
diff --git a/engines/macs2/macs2.h b/engines/macs2/macs2.h
index 2f26eac5718..af39f8610fd 100644
--- a/engines/macs2/macs2.h
+++ b/engines/macs2/macs2.h
@@ -830,6 +830,8 @@ public:
 	bool loadSceneGraphics(uint32 sceneIndex);
 	/** AHFFMACS0100 scene package (RLE maps). */
 	bool loadSceneGraphicsV1(uint32 sceneIndex);
+	/** AHFFMACS0200 ReadyModule scene package (MegaPic + half-res masks). */
+	bool loadSceneGraphicsV2(uint32 sceneIndex);
 
 	/**
 	 * Returns the game Id


Commit: e14828f78a867e376cadfbe967453ef72a472daa
    https://github.com/scummvm/scummvm/commit/e14828f78a867e376cadfbe967453ef72a472daa
Author: Martin Gerhardy (martin.gerhardy at gmail.com)
Date: 2026-08-23T20:53:57+02:00

Commit Message:
MACS2: read v2 resource file layout

Changed paths:
    engines/macs2/gameobjects.h
    engines/macs2/macs2.cpp
    engines/macs2/macs2.h


diff --git a/engines/macs2/gameobjects.h b/engines/macs2/gameobjects.h
index 4064f1dcf34..92f91956833 100644
--- a/engines/macs2/gameobjects.h
+++ b/engines/macs2/gameobjects.h
@@ -141,6 +141,8 @@ public:
 	// Runtime +0x186: per-object flag loaded from file. When set, character sprites
 	// are scaled based on Y position (perspective depth scaling).
 	bool _hasScaling = false;
+	// V2: half-res anim data drawn at 2x
+	bool _hasDoubleResAnim = false;
 	// Runtime field +0x231: "frozen/attached" flag. Set by scriptSetObjectBounds (opcode 0x35).
 	// When set, the object cannot be walked (opcode 0x11 returns error 0x1F)
 	// and walkAlongPath skips movement for this object.
diff --git a/engines/macs2/macs2.cpp b/engines/macs2/macs2.cpp
index 4722b9d3fe3..20396da4143 100644
--- a/engines/macs2/macs2.cpp
+++ b/engines/macs2/macs2.cpp
@@ -70,7 +70,7 @@ void resetCharacterWalkPath(Character *character) {
 
 Macs2Engine *g_engine;
 
-Graphics::ManagedSurface Macs2Engine::readRLEImage(int64 offs, Common::MemoryReadStream *stream) {
+Graphics::ManagedSurface Macs2Engine::readRLEImage(int64 offs, Common::SeekableReadStream *stream) {
 	stream->seek(offs);
 
 	Graphics::ManagedSurface result;
@@ -159,10 +159,33 @@ void Macs2Engine::readResourceFile() {
 			delete file;
 			_fileStream = new Common::MemoryReadStream(fileData, (uint32)size, DisposeAfterUse::YES);
 			loadResourceFileV1();
+		} else if (_mcsFileVersion == McsFileVersion::V2) {
+			_mcsDirectoryOffset = kMcsV2DirectoryOffset;
+			debugC(1, kDebugFilePath, "MCS %s: AHFFMACS0200, directory @ 0x%x", mcsName, _mcsDirectoryOffset);
+			file->seek(0, SEEK_SET);
+			_fileStream = file; // large archives stay file-backed
+			loadResourceFileV2();
+			_scriptExecutor->setOpcodeTable(Script::ScriptExecutor::kV2OpcodeTable,
+											Script::ScriptExecutor::kV2OpcodeTableSize);
 		} else {
 			delete file;
 			error("readResourceFile(): unrecognized MCS magic in %s", mcsName);
 		}
+
+		// Initialize border sprites from cursor image array entries at fixed indices.
+		// Original loadResourceFile (1008:2e8d) calls changeScene(g_wCurrentSceneIndex) before
+		// returning, ensuring all scene data (pathfinding maps, depth map, palette, background)
+		// is loaded before the game loop processes any input.
+		// The original allocates the 0x75E0-byte scene data buffer (which includes space for
+		// all RLE-decoded maps) before calling changeScene. Create the surfaces here.
+		const int sw = screenWidth();
+		const int gh = gameHeight();
+		_sceneBackground.create(sw, gh, Graphics::PixelFormat::createFormatCLUT8());
+		_depthMap.create(sw, gh, Graphics::PixelFormat::createFormatCLUT8());
+		_pathfindingMap.create(sw, gh, Graphics::PixelFormat::createFormatCLUT8());
+		_shadowMap.create(sw, gh, Graphics::PixelFormat::createFormatCLUT8());
+		_hotspotMap.create(sw, gh, Graphics::PixelFormat::createFormatCLUT8());
+		changeScene(Scenes::instance()._currentSceneIndex);
 	}
 }
 
@@ -208,13 +231,259 @@ void Macs2Engine::loadResourceFileV1() {
 	}
 
 	_fileStream->seek(kMcsV1ActorIndexOffset, SEEK_SET);
-	Scenes::instance()._currentActorIndex = _fileStream->readUint16LE();
+	bootstrapMcsActorsObjectsAndScene();
+}
+
+void Macs2Engine::loadResourceFileV2() {
+	// File layout after Directory:
+	//   0x300 sprite palette
+	//   4 words -> ShowText recolor colors
+	//   panelTopY + panelHeight
+	//   6 megapics (flag word; if nonzero, panelH rows of RLE)
+	//   button count + per-button metadata + inline anim blobs
+	//   inventory grid + text layout words
+	//   TalkVol + Font1 + SysFont + 0x400 map offsets
+	_shadingTable.clear();
+	_shadingTable.resize(0x800, 0);
+	numGlyphs = 0;
+	numPanelGlyphs = 0;
+	memset(_mapSceneOffsets, 0, sizeof(_mapSceneOffsets));
+	_imageResources.clear();
+	_imageResources.resize(33);
+	for (int i = 0; i < 33; i++)
+		_cursorHotspots[i] = Common::Point(0, 0);
+	_hudButtons.clear();
+	for (int i = 0; i < 6; i++) {
+		_hudMegapicLoaded[i] = false;
+		_hudMegapics[i].free();
+	}
+	_panelTopY = 0;
+	_panelHeight = 0;
+	_menuMode = 1;
+	_optionsSubMode = 0;
+	_savedMenuCursorMode = Script::MouseMode::Walk;
+	_inventScroll = 1;
+	memset(_hudTextLayout, 0, sizeof(_hudTextLayout));
+	memset(_hudTextRecolor, 0, sizeof(_hudTextRecolor));
+	_talkVol = 0;
+
+	if (_fileStream == nullptr)
+		return;
+
+	_fileStream->seek(_mcsDirectoryOffset + 0x3000, SEEK_SET);
+	_fileStream->read(_palVanilla, 0x300);
+	memcpy(_pal, _palVanilla, 0x300);
+
+	for (int i = 0; i < 4; i++)
+		_hudTextRecolor[i] = _fileStream->readUint16LE();
+
+	_panelTopY = _fileStream->readUint16LE();
+	_panelHeight = _fileStream->readUint16LE();
+	if (_panelTopY == 0 || _panelHeight == 0) {
+		warning("readGlobalAssetsV2: invalid panel geometry %u+%u", _panelTopY, _panelHeight);
+		_panelTopY = 280;
+		_panelHeight = 146;
+	}
+
+	for (int i = 0; i < 6; i++) {
+		const uint16 flag = _fileStream->readUint16LE();
+		if (flag == 0)
+			continue;
+		if (!readMegaPicImage(_fileStream, kWinScreenWidth, _panelHeight, _hudMegapics[i])) {
+			warning("readGlobalAssetsV2: failed loading UI megapic %d", i);
+			return;
+		}
+		_hudMegapicLoaded[i] = true;
+	}
+
+	const uint16 buttonCount = _fileStream->readUint16LE();
+	struct CursorMap {
+		uint16 cid;
+		uint16 mouseNr;
+		bool active;
+	};
+	static const CursorMap kCursorMap[] = {
+		{0x6E, 0x16, true},
+		{0x6F, 0x16, false},
+		{0x69, 0x13, true},
+		{0x6A, 0x13, false},
+		{0x64, 0x14, true},
+		{0x65, 0x14, false},
+		{0x6B, 0x15, true},
+		{0x6C, 0x15, false},
+		{0x66, 0x17, true},
+		{0x67, 0x17, false},
+		{0x68, 0x19, true},
+		{0x6D, 0x1A, true},
+	};
+
+	auto extractAnimStepFrame = [](Common::Array<uint8> blob, uint16 step, AnimFrame &out) -> bool {
+		if (blob.empty() || step == 0)
+			return false;
+		const uint32 offset = BackgroundAnimationBlob::advanceAnimFrame(blob, true, (uint16)(step + 0x64));
+		if (offset == 0 || offset + 10 > blob.size())
+			return false;
+		const uint32 frameOff = offset + 6;
+		out._width = READ_LE_UINT16(&blob[frameOff]);
+		out._height = READ_LE_UINT16(&blob[frameOff + 2]);
+		if (out._width == 0 || out._width > 640 || out._height == 0 || out._height > 400)
+			return false;
+		const uint32 pix = (uint32)out._width * (uint32)out._height;
+		if (frameOff + 4 + pix > blob.size())
+			return false;
+		out._data.resize(pix);
+		memcpy(out._data.data(), &blob[frameOff + 4], pix);
+		return true;
+	};
+
+	auto loadBigAnimFirstFrame = [&](int64 animStart, AnimFrame &out) -> bool {
+		_fileStream->seek(animStart + 10, SEEK_SET);
+		const uint16 local8 = _fileStream->readUint16LE();
+		_fileStream->seek(animStart + local8 + 0x0E, SEEK_SET);
+		_fileStream->skip(6);
+		out._width = _fileStream->readUint16LE();
+		out._height = _fileStream->readUint16LE();
+		if (out._width == 0 || out._width > 640 || out._height == 0 || out._height > 400)
+			return false;
+		out._data.resize((uint)out._width * (uint)out._height);
+		return _fileStream->read(out._data.data(), out._data.size()) == out._data.size();
+	};
+
+	for (uint16 b = 1; b <= buttonCount && !_fileStream->eos(); b++) {
+		HudButton button;
+		button.x = (int16)_fileStream->readUint16LE();
+		button.y = (int16)_fileStream->readUint16LE();
+		button.inactiveStep = _fileStream->readUint16LE();
+		button.activeStep = _fileStream->readUint16LE();
+		button.hoverStep = _fileStream->readUint16LE();
+		button.buttonId = _fileStream->readUint16LE();
+		button.menuId = _fileStream->readUint16LE();
+		const uint32 animSize = _fileStream->readUint32LE();
+		if (animSize == 0 || animSize > 0x1000000 || _fileStream->eos())
+			break;
+
+		const int64 animStart = _fileStream->pos();
+		Common::Array<uint8> animBlob;
+		animBlob.resize(animSize);
+		if (_fileStream->read(animBlob.data(), animSize) != animSize)
+			break;
+
+		AnimFrame frame;
+		bool gotFrame = extractAnimStepFrame(animBlob, button.inactiveStep ? button.inactiveStep : 1, frame);
+		if (!gotFrame)
+			gotFrame = loadBigAnimFirstFrame(animStart, frame);
+
+		AnimFrame activeFrame;
+		const bool gotActive = (button.activeStep != 0 && button.activeStep != button.inactiveStep) && extractAnimStepFrame(animBlob, button.activeStep, activeFrame);
+		AnimFrame hoverFrame;
+		const bool gotHover = (button.hoverStep != 0 && button.hoverStep != button.inactiveStep) && extractAnimStepFrame(animBlob, button.hoverStep, hoverFrame);
+
+		if (button.menuId == 7 && gotFrame) {
+			uint16 mouseNr = 0;
+			bool prefer = false;
+			for (const CursorMap &entry : kCursorMap) {
+				if (entry.cid == button.buttonId) {
+					mouseNr = entry.mouseNr;
+					prefer = entry.active;
+					break;
+				}
+			}
+			const int slot = (int)mouseNr - 1;
+			if (mouseNr != 0 && slot >= 0 && slot < 33) {
+				const bool empty = _imageResources[slot]._data.empty();
+				if (empty || prefer) {
+					_imageResources[slot] = prefer && gotActive ? activeFrame : frame;
+					_cursorHotspots[slot] = Common::Point(button.x, button.y);
+				}
+			}
+		} else if (gotFrame) {
+			button.animBlob = Common::move(animBlob);
+			button.frame = Common::move(frame);
+			if (gotActive)
+				button.activeFrame = Common::move(activeFrame);
+			if (gotHover)
+				button.hoverFrame = Common::move(hoverFrame);
+			_hudButtons.push_back(Common::move(button));
+		}
+
+		_fileStream->seek(animStart + (int64)animSize, SEEK_SET);
+	}
+
+	_inventOriginX = _fileStream->readUint16LE();
+	_inventOriginY = _fileStream->readUint16LE();
+	_inventCols = _fileStream->readUint16LE();
+	_inventRows = _fileStream->readUint16LE();
+	_inventSlotW = _fileStream->readUint16LE();
+	_inventSlotH = _fileStream->readUint16LE();
+	_inventLayoutMode = _fileStream->readUint16LE();
+	for (int i = 0; i < 7; i++)
+		_hudTextLayout[i] = _fileStream->readUint16LE();
+
+	if (_inventCols == 0)
+		_inventCols = 4;
+	if (_inventRows == 0)
+		_inventRows = 2;
+
+	_talkVol = _fileStream->readUint16LE();
+	auto loadSizedFont = [&](GlyphData *out, uint16 &outCount, uint16 &outMaxH) -> bool {
+		outCount = 0;
+		outMaxH = 0;
+		const uint32 fontSize = _fileStream->readUint32LE();
+		if (fontSize == 0 || fontSize > 0x100000)
+			return false;
+		const int64 fontStart = _fileStream->pos();
+		const uint16 glyphCount = _fileStream->readUint16LE();
+		if (glyphCount == 0 || glyphCount > 256) {
+			_fileStream->seek(fontStart + (int64)fontSize, SEEK_SET);
+			return false;
+		}
+		for (uint16 i = 0; i < glyphCount; i++) {
+			out[i].readFromMemory(_fileStream);
+			outMaxH = MAX(outMaxH, out[i]._height);
+		}
+		outCount = glyphCount;
+		_fileStream->seek(fontStart + (int64)fontSize, SEEK_SET);
+		return true;
+	};
+	if (!loadSizedFont(_glyphs, numGlyphs, maxGlyphHeight))
+		warning("readGlobalAssetsV2: failed loading Font1");
+	if (!loadSizedFont(_panelGlyphs, numPanelGlyphs, maxPanelGlyphHeight))
+		warning("readGlobalAssetsV2: failed loading SysFont");
+
+	for (int i = 0; i < 256; i++)
+		_mapSceneOffsets[i] = _fileStream->readUint32LE();
+
+	_saveListScroll = 1;
+	_saveSlotNames.clear();
+
+	uint installed = 0;
+	for (uint i = 0; i < _imageResources.size(); i++) {
+		if (!_imageResources[i]._data.empty())
+			installed++;
+	}
+	uint megas = 0;
+	for (int i = 0; i < 6; i++) {
+		if (_hudMegapicLoaded[i])
+			megas++;
+	}
+	debugC(1, kDebugFilePath,
+		   "readGlobalAssetsV2: panel=%u+%u megapics=%u buttons=%u cursors=%u invent=%ux%u @(%u,%u) fonts=%u/%u",
+		   _panelTopY, _panelHeight, megas, (uint)_hudButtons.size(), installed,
+		   _inventCols, _inventRows, _inventOriginX, _inventOriginY,
+		   numGlyphs, numPanelGlyphs);
+	_fileStream->seek(kMcsV2ActorIndexOffset, SEEK_SET);
+	bootstrapMcsActorsObjectsAndScene();
+}
+
+void Macs2Engine::bootstrapMcsActorsObjectsAndScene() {
+	Scenes &scenes = Scenes::instance();
+	scenes._currentActorIndex = _fileStream->readUint16LE();
 	uint16 firstSceneIndex = _fileStream->readUint16LE();
-	Scenes::instance()._currentSceneIndex = firstSceneIndex;
-	Scenes::instance()._currentSceneScript = Scenes::instance().readSceneScript(firstSceneIndex, _fileStream);
-	Scenes::instance()._currentSceneStrings = Scenes::instance().readSceneStrings(firstSceneIndex, _fileStream);
-	Scenes::instance()._currentSceneSpecialAnimOffsets = Scenes::instance().readSpecialAnimsOffsets(firstSceneIndex, _fileStream);
-	_scriptExecutor->setScript(Scenes::instance()._currentSceneScript);
+	scenes._currentSceneIndex = firstSceneIndex;
+	scenes._currentSceneScript = scenes.readSceneScript(firstSceneIndex, _fileStream);
+	scenes._currentSceneStrings = scenes.readSceneStrings(firstSceneIndex, _fileStream);
+	scenes._currentSceneSpecialAnimOffsets = scenes.readSpecialAnimsOffsets(firstSceneIndex, _fileStream);
+	_scriptExecutor->setScript(scenes._currentSceneScript);
 
 	// Load object data (512 entries max, matching original loadResourceFile)
 	// Original allocates all 512 slots, then frees unused ones. We pre-fill with nullptr.
@@ -235,44 +504,63 @@ void Macs2Engine::loadResourceFileV1() {
 		gameObject->_dataOffset = objectOffset;
 
 		// Object header (ReadyObject / initGameObject): x, y, scene, orientation, vertical scale
-		uint16 x = _fileStream->readUint16LE();
+		uint16 x = _fileStream->readUint16LE(); // TODO: use _engine->scaleScriptCoord
 		uint16 y = _fileStream->readUint16LE();
+		if (isV2()) {
+			x = (uint16)(x << 1);
+			y = (uint16)(y << 1);
+		}
 		gameObject->_position = Common::Point(x, y);
 		gameObject->_sceneIndex = _fileStream->readUint16LE();
 		gameObject->_orientation = _fileStream->readUint16LE();
 		gameObject->_verticalOffsetScale = _fileStream->readUint16LE();
 
 		const uint16 animSlotCount = maxAnimSlots();
-		for (int j = 1; j <= (int)animSlotCount; j++) {
-			// Per-slot: animID, sourceKey, dataSize, data, speed, mirrorFlag, discarded byte
-			_fileStream->readUint16LE(); // runtime+0x24: animation slot ID (editor metadata)
-			uint16 blobSourceKey = _fileStream->readUint16LE();
-			uint32 dataSize = _fileStream->readUint32LE();
-			uint8 *data = new uint8[dataSize];
-			_fileStream->read(data, dataSize);
-			gameObject->_blobs.push_back(Common::Array<uint8>(data, dataSize));
-			delete[] data;
-			gameObject->_blobSourceKeys.push_back(blobSourceKey);
-			uint16 blobSpeed = _fileStream->readUint16LE();
-			gameObject->_blobWalkSpeeds.push_back(blobSpeed);
-			uint16 blobMirrorFlag = _fileStream->readByte();
-			_fileStream->readByte(); // slot loaded flag (runtime-only, discarded from file)
-			gameObject->_blobMirrorFlags.push_back(blobMirrorFlag != 0);
-
-			if (blobMirrorFlag != 0) {
-				debugC(kDebugScript, "Object %.4x need to mirror blob %4.x", i, j);
-				if (dataSize > 0) {
+		if (isV2()) {
+			// ReadyObject: lead word, then slots; payload filled later by loadObjectData.
+			_fileStream->readUint16LE();
+			for (int j = 0; j < (int)animSlotCount; j++) {
+				_fileStream->readUint16LE(); // animID
+				_fileStream->readUint16LE(); // sourceKey
+				uint32 dataSize = _fileStream->readUint32LE();
+				if (dataSize > 0)
+					_fileStream->skip(dataSize);
+				_fileStream->readUint16LE(); // speed
+				_fileStream->readByte();     // mirror
+				_fileStream->readByte();     // pad
+				gameObject->_blobs.push_back(Common::Array<uint8>());
+				gameObject->_blobSourceKeys.push_back(0);
+				gameObject->_blobWalkSpeeds.push_back(0);
+				gameObject->_blobMirrorFlags.push_back(false);
+			}
+			_fileStream->readByte();
+			gameObject->_hasShading = _fileStream->readByte() != 0;
+			gameObject->_hasScaling = _fileStream->readByte() != 0;
+			gameObject->_hasDoubleResAnim = _fileStream->readByte() != 0;
+		} else {
+			for (int j = 1; j <= (int)animSlotCount; j++) {
+				_fileStream->readUint16LE(); // animID
+				uint16 blobSourceKey = _fileStream->readUint16LE();
+				uint32 dataSize = _fileStream->readUint32LE();
+				uint8 *data = new uint8[dataSize];
+				_fileStream->read(data, dataSize);
+				gameObject->_blobs.push_back(Common::Array<uint8>(data, dataSize));
+				delete[] data;
+				gameObject->_blobSourceKeys.push_back(blobSourceKey);
+				uint16 blobSpeed = _fileStream->readUint16LE();
+				gameObject->_blobWalkSpeeds.push_back(blobSpeed);
+				uint16 blobMirrorFlag = _fileStream->readByte();
+				_fileStream->readByte();
+				gameObject->_blobMirrorFlags.push_back(blobMirrorFlag != 0);
+				if (blobMirrorFlag != 0 && dataSize > 0)
 					BackgroundAnimationBlob::mirrorAnimBlob(gameObject->_blobs.back());
-				}
 			}
+			_fileStream->readByte();
+			gameObject->_hasShading = _fileStream->readByte() != 0;
+			gameObject->_hasScaling = _fileStream->readByte() != 0;
 		}
-		// Per-object flags after anim slots (loadObjectData -> runtime+0x184..+0x186)
-		_fileStream->readByte();                                // hasInventoryIcon (derived from slot 0x13)
-		gameObject->_hasShading = _fileStream->readByte() != 0; // runtime+0x185
-		gameObject->_hasScaling = _fileStream->readByte() != 0; // runtime+0x186
 
-		// Object SCRIPT ptr in directory (+0x17F8). Zero SCRIPT keeps the object (no script table).
-		const uint32 scriptPtrOffset = kMcsV1DirectoryOffset + kMcsV1ObjectScriptPtrRel + (uint32)i * 0xC;
+		const uint32 scriptPtrOffset = dir + kMcsV1ObjectScriptPtrRel + (uint32)i * 0xC;
 		_fileStream->seek(scriptPtrOffset, SEEK_SET);
 
 		objectOffset = _fileStream->readUint32LE();
@@ -294,27 +582,17 @@ void Macs2Engine::loadResourceFileV1() {
 		for (uint r = 0; r < maxObjRes; r++) {
 			gameObject->_resourceOffsets[r] = _fileStream->readUint32LE();
 		}
+		if (isV2()) {
+			_fileStream->skip(0x200 - maxObjRes * 4);
+			_fileStream->readUint16LE();
+			_fileStream->readUint16LE();
+		}
 		uint16 scriptLength = _fileStream->readUint16LE();
 		gameObject->_script.resize(scriptLength);
 		_fileStream->read(gameObject->_script.data(), scriptLength);
 
 		GameObjects::instance()._objects[i - 1] = gameObject;
 	}
-
-	// Initialize border sprites from cursor image array entries at fixed indices.
-	// Original loadResourceFile (1008:2e8d) calls changeScene(g_wCurrentSceneIndex) before
-	// returning, ensuring all scene data (pathfinding maps, depth map, palette, background)
-	// is loaded before the game loop processes any input.
-	// The original allocates the 0x75E0-byte scene data buffer (which includes space for
-	// all RLE-decoded maps) before calling changeScene. Create the surfaces here.
-	const int sw = screenWidth();
-	const int gh = gameHeight();
-	_sceneBackground.create(sw, gh, Graphics::PixelFormat::createFormatCLUT8());
-	_depthMap.create(sw, gh, Graphics::PixelFormat::createFormatCLUT8());
-	_pathfindingMap.create(sw, gh, Graphics::PixelFormat::createFormatCLUT8());
-	_shadowMap.create(sw, gh, Graphics::PixelFormat::createFormatCLUT8());
-	_hotspotMap.create(sw, gh, Graphics::PixelFormat::createFormatCLUT8());
-	changeScene(Scenes::instance()._currentSceneIndex);
 }
 
 void Macs2Engine::readExecutable() {
@@ -428,7 +706,7 @@ void Macs2Engine::loadBootstrapResources() {
 		readResourceFile();
 }
 
-void Macs2Engine::readBackgroundAnimations(Common::MemoryReadStream *stream) {
+void Macs2Engine::readBackgroundAnimations(Common::SeekableReadStream *stream) {
 	// changeScene (1008:2574): background animation loading at scene+0x50F5.
 	// Per-entry runtime struct (0x10 bytes stride):
 	//   +0x00: X position (word)
@@ -485,7 +763,7 @@ void Macs2Engine::readBackgroundAnimations(Common::MemoryReadStream *stream) {
 	}
 }
 
-void Macs2Engine::readImageResources(Common::MemoryReadStream *stream) {
+void Macs2Engine::readImageResources(Common::SeekableReadStream *stream) {
 	// l0037_3355: Read 33 entries, preserving index alignment (zero-length = empty placeholder).
 	// Binary uses g_pCursorImageArray[index] directly; indices must match.
 	for (int i = 0; i < 0x21; i++) {
@@ -2750,6 +3028,10 @@ bool Macs2Engine::loadObjectData(GameObject *obj) {
 	_fileStream->readByte(); // runtime+0x184 hasInventoryIcon (derived from slot 0x13 in C++)
 	obj->_hasShading = _fileStream->readByte() != 0;
 	obj->_hasScaling = _fileStream->readByte() != 0;
+	if (isV2())
+		obj->_hasDoubleResAnim = _fileStream->readByte() != 0;
+	else
+		obj->_hasDoubleResAnim = false;
 
 	if (obj->_blobs.size() > 0x11 && !obj->_blobs[0x11].empty()) {
 		const uint16 frameCount = BackgroundAnimationBlob::getAnimFrameCount(obj->_blobs[0x11]);
@@ -2988,7 +3270,7 @@ void GlyphData::readFromeFile(Common::File &file) {
 	file.read(_data.data(), _width * _height);
 }
 
-void GlyphData::readFromMemory(Common::MemoryReadStream *stream) {
+void GlyphData::readFromMemory(Common::SeekableReadStream *stream) {
 	_ascii = stream->readByte();
 	_width = stream->readUint16LE();
 	_height = stream->readUint16LE();
@@ -3003,7 +3285,7 @@ void AnimFrame::readFromeFile(Common::File &file) {
 	file.read(_data.data(), _width * _height);
 }
 
-void AnimFrame::readFromStream(Common::MemoryReadStream *stream) {
+void AnimFrame::readFromStream(Common::SeekableReadStream *stream) {
 	_width = stream->readUint16LE();
 	_height = stream->readUint16LE();
 	_data.resize(_width * _height);
diff --git a/engines/macs2/macs2.h b/engines/macs2/macs2.h
index af39f8610fd..894a921bfb7 100644
--- a/engines/macs2/macs2.h
+++ b/engines/macs2/macs2.h
@@ -109,7 +109,7 @@ struct GlyphData : public Sprite {
 	char _ascii = 0;
 
 	void readFromeFile(Common::File &file);
-	void readFromMemory(Common::MemoryReadStream *stream);
+	void readFromMemory(Common::SeekableReadStream *stream);
 };
 
 struct AnimFrame : public Sprite {
@@ -117,7 +117,7 @@ struct AnimFrame : public Sprite {
 	int16 _offsetY = 0;
 
 	void readFromeFile(Common::File &file);
-	void readFromStream(Common::MemoryReadStream *stream);
+	void readFromStream(Common::SeekableReadStream *stream);
 	bool pixelHit(const Common::Point &point) const;
 	Common::Point getBottomMiddleOffset(uint16 scale = 100) const;
 };
@@ -281,7 +281,7 @@ protected:
 	}
 
 public:
-	Graphics::ManagedSurface readRLEImage(int64 offs, Common::MemoryReadStream *stream);
+	Graphics::ManagedSurface readRLEImage(int64 offs, Common::SeekableReadStream *stream);
 
 	/** Open primary MCS archive, check magic, load v1 or v2 layout. */
 	void readResourceFile();
@@ -294,6 +294,10 @@ public:
 	McsFileVersion detectMcsFileVersion(Common::SeekableReadStream &stream) const;
 	/** Load AHFFMACS0100 layout (loadResourceFile @ 1008:2e8d). */
 	void loadResourceFileV1();
+	/** Load AHFFMACS0200 layout */
+	void loadResourceFileV2();
+	/** Shared actor/scene/object directory load after dialect-specific globals */
+	void bootstrapMcsActorsObjectsAndScene();
 	/** Soft restart (options button 0x20 / Macs2PretReInit). */
 	void softRestart();
 	const char *getResourceMcsFilename() const;
@@ -332,10 +336,10 @@ public:
 	void readExecutable();
 
 	// Assumes that the stream is at the location of the number of background animations
-	void readBackgroundAnimations(Common::MemoryReadStream *stream);
+	void readBackgroundAnimations(Common::SeekableReadStream *stream);
 
 	// Assumes that the stream is at the start of the right section
-	void readImageResources(Common::MemoryReadStream *stream);
+	void readImageResources(Common::SeekableReadStream *stream);
 
 public:
 	Macs2Engine(OSystem *osystem, const ADGameDescription *gameDesc);
@@ -410,6 +414,8 @@ public:
 	Common::Array<uint16> _hotspotOverrides;
 
 	Common::Array<Macs2::AnimFrame> _imageResources;
+	/** Per-cursor hotspot from native HUD button metadata (v2); (0,0) = use center. */
+	Common::Point _cursorHotspots[33];
 
 	GlyphData _glyphs[256];
 	GlyphData _panelGlyphs[256]; // Font 2: clean sans-serif font used by save/load panel
@@ -513,7 +519,7 @@ public:
 	Common::Array<BackgroundAnimation> _backgroundAnimations;
 	Common::Array<BackgroundAnimationBlob> _backgroundAnimationsBlobs;
 
-	Common::MemoryReadStream *_fileStream = nullptr;
+	Common::SeekableReadStream *_fileStream = nullptr;
 	McsFileVersion _mcsFileVersion = McsFileVersion::Unknown;
 	/** Absolute file offset of the 0x3000-byte scene/object directory. */
 	uint32 _mcsDirectoryOffset = kMcsV1DirectoryOffset;


Commit: 158e5d307f322ac20877b31626c206519d4ef416
    https://github.com/scummvm/scummvm/commit/158e5d307f322ac20877b31626c206519d4ef416
Author: Martin Gerhardy (martin.gerhardy at gmail.com)
Date: 2026-08-23T20:53:57+02:00

Commit Message:
MACS2: this data is not available for v2, too

Changed paths:
    engines/macs2/macs2.cpp


diff --git a/engines/macs2/macs2.cpp b/engines/macs2/macs2.cpp
index 20396da4143..3e7a94707f8 100644
--- a/engines/macs2/macs2.cpp
+++ b/engines/macs2/macs2.cpp
@@ -599,7 +599,7 @@ void Macs2Engine::readExecutable() {
 	inventoryIconIndices.resize(6);
 	containerInventoryIconIndices.resize(6);
 
-	if (isAmiga()) {
+	if (isAmiga() || isV2()) {
 		for (uint i = 0; i < 6; i++) {
 			inventoryIconIndices[i] = (uint16)(i + 1);
 			containerInventoryIconIndices[i] = (uint16)(i + 1);


Commit: 314fdbcf4b3673029c479454077b76ba499834f8
    https://github.com/scummvm/scummvm/commit/314fdbcf4b3673029c479454077b76ba499834f8
Author: Martin Gerhardy (martin.gerhardy at gmail.com)
Date: 2026-08-23T20:53:57+02:00

Commit Message:
MACS2: more v2 data points connected

Changed paths:
    engines/macs2/macs2.cpp
    engines/macs2/scriptexecutor.cpp
    engines/macs2/view1.cpp


diff --git a/engines/macs2/macs2.cpp b/engines/macs2/macs2.cpp
index 3e7a94707f8..66e19db8f84 100644
--- a/engines/macs2/macs2.cpp
+++ b/engines/macs2/macs2.cpp
@@ -1374,6 +1374,14 @@ void Macs2Engine::changeScene(uint32 newSceneIndex, bool executeScript) {
 	if (!loadSceneGraphics(newSceneIndex))
 		error("changeScene(): Failed to load scene graphics for scene %u", newSceneIndex);
 
+	// V2 starts with the main DisplayMenu bar visible; scene
+	// scripts (e.g. world map / overview map) may hideActionBar during isSceneInit
+	if (isV2()) {
+		_menuMode = 1;
+		_optionsSubMode = 0;
+		_bottomHudVisible = true;
+	}
+
 	// Refresh characters
 	View1 *currentView = (View1 *)findView("View1");
 	if (!currentView) {
diff --git a/engines/macs2/scriptexecutor.cpp b/engines/macs2/scriptexecutor.cpp
index bfa6c360783..5682710e879 100644
--- a/engines/macs2/scriptexecutor.cpp
+++ b/engines/macs2/scriptexecutor.cpp
@@ -3895,6 +3895,7 @@ OpcodeResult ScriptExecutor::scriptLoadDistanceMask() {
 	debugC(kDebugScript, "SCRIPT::loadDistanceMask(index=%u)", resourceIndex);
 	clearScriptError();
 	scriptSkipOpcodeRemainder(0x69);
+	// Depth mask is full playfield resolution on both dialects.
 	if (!_engine->loadMaskFromResource(resourceIndex, _executingScriptObjectId, _engine->_depthMap,
 									   _engine->screenWidth(), _engine->gameHeight(), false))
 		warning("loadDistanceMask: failed resource %u", resourceIndex);
@@ -3906,8 +3907,13 @@ OpcodeResult ScriptExecutor::scriptLoadAreaMask() {
 	debugC(kDebugScript, "SCRIPT::loadAreaMask(index=%u)", resourceIndex);
 	clearScriptError();
 	scriptSkipOpcodeRemainder(0x6A);
+	// V2 stores hotspot/path/shadow masks at half res (320x200) and upscales
+	// depth above is already full 640x400
+	const bool halfRes = _engine->isV2();
+	const int w = halfRes ? kScreenWidth : _engine->screenWidth();
+	const int h = halfRes ? kGameHeight : _engine->gameHeight();
 	if (!_engine->loadMaskFromResource(resourceIndex, _executingScriptObjectId, _engine->_hotspotMap,
-									   _engine->screenWidth(), _engine->gameHeight(), false))
+									   w, h, halfRes))
 		warning("loadAreaMask: failed resource %u", resourceIndex);
 	return OpcodeResult::Continue;
 }
@@ -3917,8 +3923,11 @@ OpcodeResult ScriptExecutor::scriptLoadWalkMask() {
 	debugC(kDebugScript, "SCRIPT::loadWalkMask(index=%u)", resourceIndex);
 	clearScriptError();
 	scriptSkipOpcodeRemainder(0x6B);
+	const bool halfRes = _engine->isV2();
+	const int w = halfRes ? kScreenWidth : _engine->screenWidth();
+	const int h = halfRes ? kGameHeight : _engine->gameHeight();
 	if (!_engine->loadMaskFromResource(resourceIndex, _executingScriptObjectId, _engine->_pathfindingMap,
-									   _engine->screenWidth(), _engine->gameHeight(), false))
+									   w, h, halfRes))
 		warning("loadWalkMask: failed resource %u", resourceIndex);
 	return OpcodeResult::Continue;
 }
@@ -3928,8 +3937,11 @@ OpcodeResult ScriptExecutor::scriptLoadShadowMask() {
 	debugC(kDebugScript, "SCRIPT::loadShadowMask(index=%u)", resourceIndex);
 	clearScriptError();
 	scriptSkipOpcodeRemainder(0x6C);
+	const bool halfRes = _engine->isV2();
+	const int w = halfRes ? kScreenWidth : _engine->screenWidth();
+	const int h = halfRes ? kGameHeight : _engine->gameHeight();
 	if (!_engine->loadMaskFromResource(resourceIndex, _executingScriptObjectId, _engine->_shadowMap,
-									   _engine->screenWidth(), _engine->gameHeight(), false))
+									   w, h, halfRes))
 		warning("loadShadowMask: failed resource %u", resourceIndex);
 	return OpcodeResult::Continue;
 }
diff --git a/engines/macs2/view1.cpp b/engines/macs2/view1.cpp
index fcb467ef2bf..bb76c160290 100644
--- a/engines/macs2/view1.cpp
+++ b/engines/macs2/view1.cpp
@@ -555,16 +555,32 @@ void View1::drawBackgroundAnimations(Graphics::ManagedSurface &s) {
 		}
 		// Binary drawAllCharacters (1008:929c): drawAnimFrame(2, y, x+1, blob) - one
 		// advanceAnimFrame(save=1, mode=2) per frame, not a separate tick advance.
-		uint16 frameStart = BackgroundAnimationBlob::advanceAnimFrame(blob, true, 2);
-		int16 frameOffsetX = (int16)READ_LE_UINT16(&blob[frameStart]);
-		int16 frameOffsetY = (int16)READ_LE_UINT16(&blob[frameStart + 2]);
+		const uint32 frameStart = BackgroundAnimationBlob::advanceAnimFrame(blob, true, 2);
+		if (frameStart == 0 || frameStart + 10 > blob.size())
+			continue;
+		const int16 frameOffsetX = (int16)READ_LE_UINT16(&blob[frameStart]);
+		const int16 frameOffsetY = (int16)READ_LE_UINT16(&blob[frameStart + 2]);
 		AnimFrame currentFrame;
 		currentFrame._width = READ_LE_UINT16(&blob[frameStart + 6]);
 		currentFrame._height = READ_LE_UINT16(&blob[frameStart + 8]);
-		currentFrame._data.resize(currentFrame._width * currentFrame._height);
-		memcpy(currentFrame._data.data(), &blob[frameStart + 10],
-			   currentFrame._width * currentFrame._height);
-		drawSprite(current._x + 1 + frameOffsetX, current._y + frameOffsetY, currentFrame, s, false);
+		const uint32 pix = (uint32)currentFrame._width * (uint32)currentFrame._height;
+		// V2 blobs can be >64KB; reject corrupt/oversized frame headers.
+		if (currentFrame._width == 0 || currentFrame._height == 0 ||
+			currentFrame._width > 640 || currentFrame._height > 400 ||
+			frameStart + 10 + pix > blob.size()) {
+			continue;
+		}
+		currentFrame._data.resize(pix);
+		memcpy(currentFrame._data.data(), &blob[frameStart + 10], pix);
+		if (g_engine->isV2()) {
+			const int16 ox = (int16)(frameOffsetX << 1);
+			const int16 oy = (int16)(frameOffsetY << 1);
+			drawSpriteTransparent(0, 0, 200, current._x + 1 + ox, current._y + oy,
+								  currentFrame._width, currentFrame._height,
+								  currentFrame._data.data(), s);
+		} else {
+			drawSprite(current._x + 1 + frameOffsetX, current._y + frameOffsetY, currentFrame, s, false);
+		}
 	}
 }
 
@@ -915,6 +931,7 @@ void View1::closeScriptActionBar(Script::MouseMode &outSavedCursorMode) {
 void View1::enterMapMode() {
 	// Binary handleInput end-block when scene+0x61db != 0 (1008:e8bf): fade, load map
 	// from scene+0x5DDB (_mapSceneOffsets[0]), set cursor 0x18 (PanelUse).
+	// this path is the DOS help-map overlay
 	uint32 helpOffset = g_engine->_mapSceneOffsets[0];
 	if (helpOffset == 0 || helpOffset >= (uint32)g_engine->_fileStream->size()) {
 		return;
@@ -1885,11 +1902,12 @@ bool View1::handleInput(const MouseDownMessage &msg) {
 			return true;
 		}
 		if (hasPersistentActionBar()) {
-			if (shouldShowActionBar()) {
+			const bool canCycleVerbs = shouldShowActionBar() || g_engine->hasNativeHudAssets();
+			if (canCycleVerbs) {
 				g_engine->nextCursorMode();
 				_activeInventoryItem = nullptr;
 				g_engine->_scriptExecutor->_interactedInventoryItemId = 0;
-				if (_actionBar)
+				if (_actionBar && shouldShowActionBar())
 					_actionBar->syncActiveVerbFromCursorMode();
 				updateCursor();
 				presentFrame();
@@ -2293,6 +2311,9 @@ void View1::draw() {
 			Graphics::ManagedSurface fullScreen(*g_events->getScreen(), Common::Rect(0, 0, sw, sh));
 			if (shouldShowActionBar()) {
 				_actionBar->draw(fullScreen);
+			} else if (g_engine->hasNativeHudAssets() && g_engine->_menuMode == 0) {
+				// hideActionBar / overview map: leave playfield pixels alone so
+				// scene art (and hotspots) remain visible in the former panel band.
 			} else {
 				const int top = actionBarTopY();
 				if (top >= 0 && top < sh)
@@ -2735,7 +2756,9 @@ void View1::drawAllCharacters(Graphics::ManagedSurface *surface, bool fullUpdate
 			// drawAllCharacters @ 1008:93f8-9440 (inlined; not a separate EXE function)
 			int32 depthOffset = ((int32)charY - (int32)g_engine->_walkDepthThresholdY) *
 								(int32)g_engine->_walkDepthScaleFactor / 100;
-			const uint16 scalingFactor = (uint16)((int32)g_engine->_walkBaseSpeedPct + depthOffset);
+			uint16 scalingFactor = (uint16)((int32)g_engine->_walkBaseSpeedPct + depthOffset);
+			if (obj->_hasDoubleResAnim)
+				scalingFactor = (uint16)(scalingFactor * 2);
 			if (obj->_index == 1) {
 				_scalingValues.characterY = (uint16)charY;
 				_scalingValues.scalingFactor = scalingFactor;
@@ -2747,6 +2770,8 @@ void View1::drawAllCharacters(Graphics::ManagedSurface *surface, bool fullUpdate
 				if (Macs2Engine::isWalkabilityBlocking((uint16)walkabilityOffset))
 					walkabilityOffset = 0;
 			}
+			if (g_engine->isV2())
+				walkabilityOffset = (int16)(walkabilityOffset << 1);
 			if (obj->_verticalOffsetScale != 0)
 				walkabilityOffset = (scalingFactor * obj->_verticalOffsetScale) / 100;
 
@@ -2759,16 +2784,27 @@ void View1::drawAllCharacters(Graphics::ManagedSurface *surface, bool fullUpdate
 
 			uint16 frameWidth;
 			uint16 frameHeight;
+			int16 offsetX = frame._offsetX;
+			int16 offsetY = frame._offsetY;
+			// Frame header offsets are authored in half-res when +0x2e3 is set.
+			if (obj->_hasDoubleResAnim) {
+				offsetX = (int16)(offsetX << 1);
+				offsetY = (int16)(offsetY << 1);
+			}
 			if (obj->_hasScaling) {
 				frameWidth = (frame._width * scalingFactor) / 100;
 				frameHeight = (frame._height * scalingFactor) / 100;
+			} else if (obj->_hasDoubleResAnim) {
+				// exact 2x blit of half-res anim data.
+				frameWidth = (uint16)(frame._width << 1);
+				frameHeight = (uint16)(frame._height << 1);
 			} else {
 				frameWidth = frame._width;
 				frameHeight = frame._height;
 			}
 
-			const int16 drawX = charX - (frameWidth >> 1) + frame._offsetX;
-			const int16 drawY = (charY - frameHeight) - walkabilityOffset + frame._offsetY;
+			const int16 drawX = charX - (frameWidth >> 1) + offsetX + (int16)obj->_objectAdjust1;
+			const int16 drawY = (charY - frameHeight) - walkabilityOffset + offsetY + (int16)obj->_objectAdjust2;
 			const uint8 depthThreshold = g_engine->depthThresholdForY(charY);
 			const byte *pixelData = frame._data.data();
 


Commit: cbf51f43ea6c72fa3df7ba2607fca13cbc2516e3
    https://github.com/scummvm/scummvm/commit/cbf51f43ea6c72fa3df7ba2607fca13cbc2516e3
Author: Martin Gerhardy (martin.gerhardy at gmail.com)
Date: 2026-08-23T20:53:57+02:00

Commit Message:
MACS2: support v2 scaling for shading tables

Changed paths:
    engines/macs2/view1.cpp
    engines/macs2/view1.h


diff --git a/engines/macs2/view1.cpp b/engines/macs2/view1.cpp
index bb76c160290..c8d537457c3 100644
--- a/engines/macs2/view1.cpp
+++ b/engines/macs2/view1.cpp
@@ -2810,12 +2810,18 @@ void View1::drawAllCharacters(Graphics::ManagedSurface *surface, bool fullUpdate
 
 			// drawAllCharacters @ 1008:9573-9754: drawAnimFrame / drawAnimFrameShaded / drawAnimFrameDepth
 			const bool clipGameArea = hasPersistentActionBar();
+			const bool useMaskedShading = g_engine->isV2() && (obj->_hasScaling || obj->_hasShading);
 			if (obj->_hasScaling) {
 				drawSpriteTransparent(shadingTableOffset, depthThreshold, scalingFactor,
-									  drawX, drawY, frame._width, frame._height, pixelData, *surface);
+									  drawX, drawY, frame._width, frame._height, pixelData, *surface,
+									  useMaskedShading);
+			} else if (obj->_hasDoubleResAnim) {
+				drawSpriteTransparent(obj->_hasShading ? shadingTableOffset : 0, depthThreshold, 200,
+									  drawX, drawY, frame._width, frame._height, pixelData, *surface,
+									  useMaskedShading);
 			} else if (obj->_hasShading) {
 				drawSpriteScaled(shadingTableOffset, depthThreshold, drawX, drawY,
-								 frame._width, frame._height, pixelData, *surface);
+								 frame._width, frame._height, pixelData, *surface, useMaskedShading);
 			} else {
 				drawSprite(drawX, drawY, frame._width, frame._height,
 						   const_cast<byte *>(pixelData), *surface, false, false, 0, clipGameArea);
@@ -3126,7 +3132,26 @@ void View1::drawSpriteFitted(const Common::Rect &bounds, const Sprite &sprite, G
 	}
 }
 
-static byte applyShadingTable(byte color, int shadingTableOffset) {
+static byte applyShadingTable(byte color, int shadingTableOffset, byte bgColor, bool useMaskedShading) {
+	if (g_engine->_shadingTable.empty())
+		return color;
+
+	if (useMaskedShading) {
+		const uint intensity = (uint)CLIP(shadingTableOffset, 0, 0x1f);
+		if (color == 1) {
+			const uint idx = (uint)bgColor * 0x20u + intensity;
+			if (idx < g_engine->_shadingTable.size())
+				return g_engine->_shadingTable[idx];
+			return color;
+		}
+		if (intensity == 0)
+			return color;
+		const uint idx = (uint)color * 0x20u + intensity;
+		if (idx >= g_engine->_shadingTable.size())
+			return color;
+		return g_engine->_shadingTable[idx];
+	}
+
 	if (shadingTableOffset == 0)
 		return color;
 	// drawSpriteTransparent @ 1010:0fba: (color - 0xC0) * 0x20 + shadingTableOffset + scene+0x53D3
@@ -3141,7 +3166,7 @@ static byte applyShadingTable(byte color, int shadingTableOffset) {
 // drawSpriteScaled @ 1010:102b
 void View1::drawSpriteScaled(int shadingTableOffset, uint8 depthThreshold, int16 drawX, int16 drawY,
 							 uint16 srcWidth, uint16 srcHeight, const byte *srcPixels,
-							 Graphics::ManagedSurface &s) {
+							 Graphics::ManagedSurface &s, bool useMaskedShading) {
 	int screenY = drawY;
 	int srcRow = 0;
 	int remainingRows = srcHeight;
@@ -3153,8 +3178,11 @@ void View1::drawSpriteScaled(int shadingTableOffset, uint8 depthThreshold, int16
 					const uint8 bgDepth = g_engine->_depthMap.getPixel(screenX, screenY);
 					if (bgDepth < depthThreshold) {
 						const uint8 color = srcPixels[srcRow + srcX];
-						if (color != 0)
-							s.setPixel(screenX, screenY, applyShadingTable(color, shadingTableOffset));
+						if (color != 0) {
+							const byte bg = s.getPixel(screenX, screenY);
+							s.setPixel(screenX, screenY,
+									   applyShadingTable(color, shadingTableOffset, bg, useMaskedShading));
+						}
 					}
 				}
 				screenX++;
@@ -3169,7 +3197,7 @@ void View1::drawSpriteScaled(int shadingTableOffset, uint8 depthThreshold, int16
 // drawSpriteTransparent @ 1010:0ed1
 void View1::drawSpriteTransparent(int shadingTableOffset, uint8 depthThreshold, uint16 scalingFactor,
 								  int16 drawX, int16 drawY, uint16 srcWidth, uint16 srcHeight,
-								  const byte *srcPixels, Graphics::ManagedSurface &s) {
+								  const byte *srcPixels, Graphics::ManagedSurface &s, bool useMaskedShading) {
 	int screenY = drawY;
 	int srcRowOffset = 0;
 	int remainingRows = (int)srcHeight;
@@ -3187,7 +3215,9 @@ void View1::drawSpriteTransparent(int shadingTableOffset, uint8 depthThreshold,
 				const uint8 color = *srcPtr;
 				if (color != 0 && screenX >= 0 && screenX < s.w &&
 					g_engine->_depthMap.getPixel(screenX, screenY) < depthThreshold) {
-					s.setPixel(screenX, screenY, applyShadingTable(color, shadingTableOffset));
+					const byte bg = s.getPixel(screenX, screenY);
+					s.setPixel(screenX, screenY,
+							   applyShadingTable(color, shadingTableOffset, bg, useMaskedShading));
 				}
 
 				screenX++;
diff --git a/engines/macs2/view1.h b/engines/macs2/view1.h
index 1b5351f7642..b1ee2f18784 100644
--- a/engines/macs2/view1.h
+++ b/engines/macs2/view1.h
@@ -192,11 +192,11 @@ private:
 	// drawSpriteTransparent @ 1010:0ed1 (drawAnimFrameDepth @ 1010:172c)
 	void drawSpriteTransparent(int shadingTableOffset, uint8 depthThreshold, uint16 scalingFactor,
 							   int16 drawX, int16 drawY, uint16 srcWidth, uint16 srcHeight,
-							   const byte *srcPixels, Graphics::ManagedSurface &s);
+							   const byte *srcPixels, Graphics::ManagedSurface &s, bool useMaskedShading = false);
 	// drawSpriteScaled @ 1010:102b (drawAnimFrameShaded @ 1010:1785)
 	void drawSpriteScaled(int shadingTableOffset, uint8 depthThreshold, int16 drawX, int16 drawY,
 						  uint16 srcWidth, uint16 srcHeight, const byte *srcPixels,
-						  Graphics::ManagedSurface &s);
+						  Graphics::ManagedSurface &s, bool useMaskedShading = false);
 
 	// Set by action bar map button on press; enterMapMode() runs on panel release.
 	bool _pendingMapOpen = false;


Commit: 80404bd7956ab2242b73ebf05d65dd28c89e31bf
    https://github.com/scummvm/scummvm/commit/80404bd7956ab2242b73ebf05d65dd28c89e31bf
Author: Martin Gerhardy (martin.gerhardy at gmail.com)
Date: 2026-08-23T21:11:42+02:00

Commit Message:
MACS2: removed magic numbers

Changed paths:
    engines/macs2/actionbar.cpp
    engines/macs2/macs2.cpp
    engines/macs2/view1.cpp


diff --git a/engines/macs2/actionbar.cpp b/engines/macs2/actionbar.cpp
index 3baefde36aa..e8e00fa31b8 100644
--- a/engines/macs2/actionbar.cpp
+++ b/engines/macs2/actionbar.cpp
@@ -120,7 +120,7 @@ void ActionBar::syncActiveVerbFromCursorMode() {
 		return;
 	}
 
-	for (int i = 0; i < 4; i++) {
+	for (int i = 0; i < ARRAYSIZE(kVerbs); i++) {
 		if (kVerbs[i].mode == mode) {
 			_activeVerbIndex = i;
 			return;
@@ -202,7 +202,7 @@ void ActionBar::drawSentenceLine(Graphics::ManagedSurface &s) {
 }
 
 void ActionBar::drawVerbBar(Graphics::ManagedSurface &s) {
-	for (int i = 0; i < 4; i++) {
+	for (int i = 0; i < ARRAYSIZE(kVerbs); i++) {
 		const Common::Rect r = getVerbRect(i);
 		const bool isActive = (i == _activeVerbIndex);
 		const bool isHovered = (i == _hoveredVerb);
@@ -298,7 +298,7 @@ bool ActionBar::handleClick(const Common::Point &pos, bool scriptsRunning) {
 }
 
 bool ActionBar::handleClickScumm(const Common::Point &pos, bool scriptsRunning) {
-	for (int i = 0; i < 4; i++) {
+	for (int i = 0; i < ARRAYSIZE(kVerbs); i++) {
 		if (getVerbRect(i).contains(pos)) {
 			_activeVerbIndex = i;
 			g_engine->setCursorMode(kVerbs[i].mode);
@@ -410,7 +410,7 @@ void ActionBar::handleMouseMoveScumm(const Common::Point &pos) {
 	_hoveredScrollButton = -1;
 	clearSentenceObject();
 
-	for (int i = 0; i < 4; i++) {
+	for (int i = 0; i < ARRAYSIZE(kVerbs); i++) {
 		if (getVerbRect(i).contains(pos)) {
 			_hoveredVerb = i;
 			break;
diff --git a/engines/macs2/macs2.cpp b/engines/macs2/macs2.cpp
index 66e19db8f84..9d060783d12 100644
--- a/engines/macs2/macs2.cpp
+++ b/engines/macs2/macs2.cpp
@@ -416,8 +416,9 @@ void Macs2Engine::loadResourceFileV2() {
 	_inventSlotW = _fileStream->readUint16LE();
 	_inventSlotH = _fileStream->readUint16LE();
 	_inventLayoutMode = _fileStream->readUint16LE();
-	for (int i = 0; i < 7; i++)
+	for (int i = 0; i < ARRAYSIZE(_hudTextLayout); i++) {
 		_hudTextLayout[i] = _fileStream->readUint16LE();
+	}
 
 	if (_inventCols == 0)
 		_inventCols = 4;
diff --git a/engines/macs2/view1.cpp b/engines/macs2/view1.cpp
index c8d537457c3..ac3e39d009e 100644
--- a/engines/macs2/view1.cpp
+++ b/engines/macs2/view1.cpp
@@ -1334,7 +1334,7 @@ bool View1::handleInventoryClick(const MouseDownMessage &msg) {
 		return true;
 	}
 
-	for (int i = 0; i < 6; i++) {
+	for (int i = 0; i < (int)_inventoryButtonLocations.size(); i++) {
 		const Common::Rect &current = _inventoryButtonLocations[i];
 		if (!current.contains(msg._pos)) {
 			continue;
@@ -1482,7 +1482,7 @@ bool View1::handleContainerInventoryClick(const MouseDownMessage &msg) {
 		return true;
 	}
 
-	for (int i = 0; i < 6; i++) {
+	for (int i = 0; i < (int)_inventoryButtonLocations.size(); i++) {
 		const Common::Rect &current = _inventoryButtonLocations[i];
 		if (!current.contains(msg._pos)) {
 			continue;
@@ -2270,7 +2270,7 @@ void View1::draw() {
 	}
 
 	if (_uiPanelState == kUiPanelInventory && g_engine->enhancementEnabled(kEnhUIUX)) {
-		for (int i = 0; i < 6; i++) {
+		for (int i = 0; i < (int)_inventoryButtonLocations.size(); i++) {
 			if (_inventoryButtonLocations[i].contains(mousePos)) {
 				static const char *const buttonNames[] = {
 					"Schauen", "Benutzen", "Hoch", "Runter", "Ablegen", "Schliessen"};
@@ -2291,7 +2291,7 @@ void View1::draw() {
 	}
 
 	if (_uiPanelState == kUiPanelSaveLoad && g_engine->enhancementEnabled(kEnhUIUX)) {
-		for (int i = 0; i < 7; i++) {
+		for (int i = 0; i < ARRAYSIZE(_saveLoadButtonRects); i++) {
 			if (_saveLoadButtonRects[i].contains(mousePos)) {
 				static const char *const buttonNames[] = {
 					"Laden", "Speichern", "Musik an/aus",


Commit: 7fa9a6deeb1c7e274fb616bdf676c018d1280479
    https://github.com/scummvm/scummvm/commit/7fa9a6deeb1c7e274fb616bdf676c018d1280479
Author: Martin Gerhardy (martin.gerhardy at gmail.com)
Date: 2026-08-23T21:11:42+02:00

Commit Message:
MACS2: fixed member name

Changed paths:
    engines/macs2/amiga_resources.cpp
    engines/macs2/debugtools.cpp
    engines/macs2/macs2.cpp
    engines/macs2/macs2.h
    engines/macs2/view1.cpp


diff --git a/engines/macs2/amiga_resources.cpp b/engines/macs2/amiga_resources.cpp
index 04105dcb47b..75b398a86ad 100644
--- a/engines/macs2/amiga_resources.cpp
+++ b/engines/macs2/amiga_resources.cpp
@@ -166,7 +166,7 @@ bool Macs2Engine::loadAmigaSceneBackground(uint32 sceneResourceId) {
 	// load_scene_mxmm @ 00221d90). Without nodes, calculatePath always fails and
 	// walkAlongPath cancels with finalDest=current - waitForWalk then completes
 	// immediately while the actor is still short of the script target.
-	pathfindingPoints.clear();
+	_pathfindingPoints.clear();
 	_numPathfindingPoints = 0;
 	uint16 numPfPoints = 0;
 	Common::Array<AmigaPathfindingNode> pfNodes;
@@ -183,7 +183,7 @@ bool Macs2Engine::loadAmigaSceneBackground(uint32 sceneResourceId) {
 				if (pfNodes[i].adjacent[j] != 0)
 					current._adjacentPoints.push_back(pfNodes[i].adjacent[j]);
 			}
-			pathfindingPoints.push_back(current);
+			_pathfindingPoints.push_back(current);
 		}
 	}
 
diff --git a/engines/macs2/debugtools.cpp b/engines/macs2/debugtools.cpp
index 50019205a44..1a19ddd66f7 100644
--- a/engines/macs2/debugtools.cpp
+++ b/engines/macs2/debugtools.cpp
@@ -1325,7 +1325,7 @@ static void showSceneMapsWindow() {
 			if (view) {
 				// Draw pathfinding point nodes and connections
 				for (int i = 0; i < 16; i++) {
-					PathfindingPoint &pt = g_engine->pathfindingPoints[i];
+					PathfindingPoint &pt = g_engine->_pathfindingPoints[i];
 					if (pt._position.x >= 0 && pt._position.x < kScreenWidth && pt._position.y >= 0 && pt._position.y < kGameHeight) {
 						// Draw cross at node
 						for (int d = -2; d <= 2; d++) {
@@ -1341,7 +1341,7 @@ static void showSceneMapsWindow() {
 						for (uint8 adj : pt._adjacentPoints) {
 							if (adj == 0 || adj > 16)
 								continue;
-							PathfindingPoint &other = g_engine->pathfindingPoints[adj - 1];
+							PathfindingPoint &other = g_engine->_pathfindingPoints[adj - 1];
 							overlayComposite.drawLine(pt._position.x, pt._position.y, other._position.x, other._position.y, 0xFE);
 						}
 					}
@@ -1409,7 +1409,7 @@ static void showSceneMapsWindow() {
 					ImDrawList *dl = ImGui::GetWindowDrawList();
 					ImVec2 imgOrigin = ImGui::GetItemRectMin();
 					for (int i = 0; i < 16; i++) {
-						PathfindingPoint &pt = g_engine->pathfindingPoints[i];
+						PathfindingPoint &pt = g_engine->_pathfindingPoints[i];
 						if (pt._position.x >= 0 && pt._position.x < kScreenWidth && pt._position.y >= 0 && pt._position.y < kGameHeight) {
 							char buf[4];
 							snprintf(buf, sizeof(buf), "%d", i);
@@ -1534,7 +1534,7 @@ static void showSceneMapsWindow() {
 		ImGui::Text("_walkDepthThresholdY=%u  _walkDepthScaleFactor=%u  _walkBaseSpeedPct=%u",
 					g_engine->_walkDepthThresholdY, g_engine->_walkDepthScaleFactor, g_engine->_walkBaseSpeedPct);
 		ImGui::Text("Pathfinding points: %u  Path nodes: %u",
-					(uint)g_engine->pathfindingPoints.size(), (uint)g_engine->_path.size());
+					(uint)g_engine->_pathfindingPoints.size(), (uint)g_engine->_path.size());
 
 		// Node detail table
 		if (ImGui::CollapsingHeader("Node Graph", ImGuiTreeNodeFlags_DefaultOpen)) {
@@ -1542,8 +1542,8 @@ static void showSceneMapsWindow() {
 			Character *protagonist = view ? view->getCharacterByIndex(Scenes::instance()._currentActorIndex) : nullptr;
 			Common::Point charPos = protagonist ? protagonist->getPosition() : Common::Point(0, 0);
 
-			for (int i = 0; i < (int)g_engine->pathfindingPoints.size(); i++) {
-				const PathfindingPoint &pt = g_engine->pathfindingPoints[i];
+			for (int i = 0; i < (int)g_engine->_pathfindingPoints.size(); i++) {
+				const PathfindingPoint &pt = g_engine->_pathfindingPoints[i];
 				// Check reachability from character
 				bool reachable = protagonist && g_engine->isPathWalkable(charPos.y, charPos.x, pt._position.y, pt._position.x);
 				// Check if node is in current path
diff --git a/engines/macs2/macs2.cpp b/engines/macs2/macs2.cpp
index 9d060783d12..960a94aefbe 100644
--- a/engines/macs2/macs2.cpp
+++ b/engines/macs2/macs2.cpp
@@ -1002,7 +1002,7 @@ bool Macs2Engine::loadSceneGraphicsV1(uint32 sceneIndex) {
 
 	// Pretty sure that this is the pathfinding points. We address them starting
 	// Load pathfinding nodes (16 entries x 10 bytes at scene+0x5023)
-	pathfindingPoints.clear();
+	_pathfindingPoints.clear();
 	for (int i = 0; i < 16; i++) {
 		PathfindingPoint current;
 		current._index = i;
@@ -1014,7 +1014,7 @@ bool Macs2Engine::loadSceneGraphicsV1(uint32 sceneIndex) {
 		current._adjacentPoints.clear();
 		for (uint16 j = 0; j < numConnections && j < 4; j++)
 			current._adjacentPoints.push_back(adj[j]);
-		pathfindingPoints.push_back(current);
+		_pathfindingPoints.push_back(current);
 	}
 
 	_numHotspots = _fileStream->readUint16LE();
@@ -1133,7 +1133,7 @@ bool Macs2Engine::loadSceneGraphicsV2(uint32 sceneIndex) {
 		return false;
 	upscaleHalfRes(half, _hotspotMap);
 
-	pathfindingPoints.clear();
+	_pathfindingPoints.clear();
 	for (int i = 0; i < 16; i++) {
 		PathfindingPoint current;
 		current._index = i;
@@ -1146,7 +1146,7 @@ bool Macs2Engine::loadSceneGraphicsV2(uint32 sceneIndex) {
 		current._adjacentPoints.clear();
 		for (uint16 j = 0; j < numConnections && j < 4; j++)
 			current._adjacentPoints.push_back(adj[j]);
-		pathfindingPoints.push_back(current);
+		_pathfindingPoints.push_back(current);
 	}
 	stream->skip(0x2c0 - 0x160);
 
@@ -2104,8 +2104,8 @@ int Macs2Engine::euclideanDistance(const Common::Point &a, const Common::Point &
 // Binary walkableDistance (1008:1293): distance between two nodes IF walkable, else 0x500.
 // Uses binary search on precomputed squared-distance table (scene+0x61DC) for O(log n) sqrt.
 int Macs2Engine::walkableDistance(int nodeA, int nodeB) {
-	const Common::Point &a = pathfindingPoints[nodeA - 1]._position;
-	const Common::Point &b = pathfindingPoints[nodeB - 1]._position;
+	const Common::Point &a = _pathfindingPoints[nodeA - 1]._position;
+	const Common::Point &b = _pathfindingPoints[nodeB - 1]._position;
 	if (!isPathWalkable(a.y, a.x, b.y, b.x))
 		return 0x500;
 	// Binary search for integer sqrt(dx^2 + dy^2), matching binary at 1008:1293
@@ -2139,7 +2139,7 @@ int Macs2Engine::computeMinCostToReachable(int nodeIndex, int prevNode, uint16 a
 	visitedStack[visitedCount] = nodeIndex;
 
 	int result;
-	const Common::Point &nodePos = pathfindingPoints[nodeIndex - 1]._position;
+	const Common::Point &nodePos = _pathfindingPoints[nodeIndex - 1]._position;
 
 	if (reachable[nodeIndex]) {
 		// Terminal: return walkable distance from this node to finalDest
@@ -2168,7 +2168,7 @@ int Macs2Engine::computeMinCostToReachable(int nodeIndex, int prevNode, uint16 a
 
 	int bestCost = 0x7777;
 	int bestAdj = 0;
-	const PathfindingPoint &pt = pathfindingPoints[nodeIndex - 1];
+	const PathfindingPoint &pt = _pathfindingPoints[nodeIndex - 1];
 	int adjCount = (int)pt._adjacentPoints.size();
 
 	if (adjCount > 0) {
diff --git a/engines/macs2/macs2.h b/engines/macs2/macs2.h
index 894a921bfb7..551fbe25dbb 100644
--- a/engines/macs2/macs2.h
+++ b/engines/macs2/macs2.h
@@ -380,8 +380,7 @@ public:
 	Common::Array<PathfindingAreaOverride> _pathfindingOverrides;
 	// Area override table at scene+value*5+0x4EA8 (for getAreaAtPoint)
 	uint16 _areaOverrides[AREA_OVERRIDE_COUNT] = {0};
-	uint16 _pathfindingPoints[32];
-	Common::Array<PathfindingPoint> pathfindingPoints;
+	Common::Array<PathfindingPoint> _pathfindingPoints;
 	Common::Array<Common::Point> _path;
 
 	bool getPathfindingOverride(uint16 index, uint16 &result);
diff --git a/engines/macs2/view1.cpp b/engines/macs2/view1.cpp
index ac3e39d009e..d3262a1fd64 100644
--- a/engines/macs2/view1.cpp
+++ b/engines/macs2/view1.cpp
@@ -797,17 +797,17 @@ void View1::drawPathfindingPoints(Graphics::ManagedSurface &s) {
 		yOffset = xData._height / 2;
 	}
 	for (int i = 0; i < 16; i++) {
-		PathfindingPoint &current = g_engine->pathfindingPoints[i];
+		PathfindingPoint &current = g_engine->_pathfindingPoints[i];
 		renderString(current._position.x - xOffset, current._position.y - yOffset, "x");
 
 		Common::String number = Common::String::format("%u", i);
 		renderString(current._position.x - xOffset + 10, current._position.y - yOffset + 10, number.c_str());
 
 		for (uint8 adjacentIndex : current._adjacentPoints) {
-			if (adjacentIndex >= g_engine->pathfindingPoints.size()) {
+			if (adjacentIndex >= g_engine->_pathfindingPoints.size()) {
 				continue;
 			}
-			PathfindingPoint &other = g_engine->pathfindingPoints[adjacentIndex - 1];
+			PathfindingPoint &other = g_engine->_pathfindingPoints[adjacentIndex - 1];
 			s.drawLine(current._position.x, current._position.y, other._position.x, other._position.y, 0xFFFFFFFF);
 		}
 	}
@@ -3636,7 +3636,7 @@ bool Character::calculatePath(Common::Point target) {
 	// scene[i + 0x50C2] = isPathWalkable(finalDest, node[i])
 	bool reachable[MAX_NODES + 1] = {};
 	for (int i = 1; i <= nodeCount; i++) {
-		const Common::Point &nodePos = g_engine->pathfindingPoints[i - 1]._position;
+		const Common::Point &nodePos = g_engine->_pathfindingPoints[i - 1]._position;
 		reachable[i] = g_engine->isPathWalkable(target.y, target.x, nodePos.y, nodePos.x);
 	}
 
@@ -3644,7 +3644,7 @@ bool Character::calculatePath(Common::Point target) {
 	int bestCost = 0x7777;
 	int bestNode = 0;
 	for (int i = 1; i <= nodeCount; i++) {
-		const Common::Point &nodePos = g_engine->pathfindingPoints[i - 1]._position;
+		const Common::Point &nodePos = g_engine->_pathfindingPoints[i - 1]._position;
 		int costToDest = g_engine->euclideanDistance(nodePos, target);
 		int costToChar = g_engine->euclideanDistance(nodePos, charPos);
 		if (costToDest + costToChar < bestCost) {
@@ -3681,7 +3681,7 @@ bool Character::calculatePath(Common::Point target) {
 	_path.push_back(bestNode);
 	int currentNode = bestNode;
 	while (!reachable[currentNode]) {
-		const PathfindingPoint &curPt = g_engine->pathfindingPoints[currentNode - 1];
+		const PathfindingPoint &curPt = g_engine->_pathfindingPoints[currentNode - 1];
 		int localBestCost = 0x7777;
 		int nextNode = currentNode;
 		for (uint a = 0; a < curPt._adjacentPoints.size(); a++) {
@@ -3701,8 +3701,8 @@ bool Character::calculatePath(Common::Point target) {
 
 	// Step 4: Validate path - consecutive nodes must be walkable to each other
 	for (uint i = 0; i + 1 < _path.size(); i++) {
-		const Common::Point &p1 = g_engine->pathfindingPoints[_path[i + 1] - 1]._position;
-		const Common::Point &p2 = g_engine->pathfindingPoints[_path[i] - 1]._position;
+		const Common::Point &p1 = g_engine->_pathfindingPoints[_path[i + 1] - 1]._position;
+		const Common::Point &p2 = g_engine->_pathfindingPoints[_path[i] - 1]._position;
 		if (!g_engine->isPathWalkable(p1.y, p1.x, p2.y, p2.x)) {
 			// Path invalid - abort, go directly to target
 			_path.clear();
@@ -3717,14 +3717,14 @@ bool Character::calculatePath(Common::Point target) {
 	// is actually the character position.
 	_currentPathIndex = 0;
 	while (_currentPathIndex + 1 < (int16)_path.size()) {
-		const Common::Point &nextNodePos = g_engine->pathfindingPoints[_path[_currentPathIndex + 1] - 1]._position;
+		const Common::Point &nextNodePos = g_engine->_pathfindingPoints[_path[_currentPathIndex + 1] - 1]._position;
 		if (!g_engine->isPathWalkable(nextNodePos.y, nextNodePos.x, charPos.y, charPos.x))
 			break;
 		_currentPathIndex++;
 	}
 
 	// Set immediate target to the current path node
-	const Common::Point &firstTarget = g_engine->pathfindingPoints[_path[_currentPathIndex] - 1]._position;
+	const Common::Point &firstTarget = g_engine->_pathfindingPoints[_path[_currentPathIndex] - 1]._position;
 	_targetPosition = firstTarget;
 	return true;
 }
@@ -3735,7 +3735,7 @@ bool Character::canNodeConnectSourceToTarget(uint16 nodeIndex, const Common::Poi
 	// 1. Node must be able to see the target
 	// 2. Flood-fill connected component from node
 	// 3. Some node in component must see target AND some node must be seen from source
-	const Common::Point &nodePos = g_engine->pathfindingPoints[nodeIndex - 1]._position;
+	const Common::Point &nodePos = g_engine->_pathfindingPoints[nodeIndex - 1]._position;
 	if (!g_engine->isPathWalkable(nodePos.y, nodePos.x, target.y, target.x))
 		return false;
 
@@ -3749,7 +3749,7 @@ bool Character::canNodeConnectSourceToTarget(uint16 nodeIndex, const Common::Poi
 	for (int i = 1; i <= nodeCount; i++) {
 		if (!visited[i])
 			continue;
-		const Common::Point &p = g_engine->pathfindingPoints[i - 1]._position;
+		const Common::Point &p = g_engine->_pathfindingPoints[i - 1]._position;
 		if (g_engine->isPathWalkable(p.y, p.x, target.y, target.x))
 			anySeesTarget = true;
 		if (g_engine->isPathWalkable(charPos.y, charPos.x, p.y, p.x))
@@ -3764,7 +3764,7 @@ void Character::floodFillConnectedNodes(int nodeIndex, bool *visited, int nodeCo
 	if (visited[nodeIndex])
 		return;
 	visited[nodeIndex] = true;
-	const PathfindingPoint &pt = g_engine->pathfindingPoints[nodeIndex - 1];
+	const PathfindingPoint &pt = g_engine->_pathfindingPoints[nodeIndex - 1];
 	for (uint i = 0; i < pt._adjacentPoints.size(); i++) {
 		floodFillConnectedNodes(pt._adjacentPoints[i], visited, nodeCount);
 	}
@@ -3802,7 +3802,7 @@ bool Character::walkAlongPath() {
 	// Binary: if (pathNodeIndex != 0) posX/Y = nodeCoords[pathNodes[pathNodeIndex]]
 	if (_currentPathIndex >= 0 && _currentPathIndex < (int16)_path.size()) {
 		const uint16 snapIdx = _path[_currentPathIndex];
-		const Common::Point &snapPos = g_engine->pathfindingPoints[snapIdx - 1]._position;
+		const Common::Point &snapPos = g_engine->_pathfindingPoints[snapIdx - 1]._position;
 		_gameObject->_position = snapPos;
 	}
 	_currentPathIndex++;
@@ -3816,7 +3816,7 @@ bool Character::walkAlongPath() {
 		return false; // No more path segments after this
 	}
 	const uint16 nodeIdx = _path[_currentPathIndex];
-	const Common::Point &nodePos = g_engine->pathfindingPoints[nodeIdx - 1]._position;
+	const Common::Point &nodePos = g_engine->_pathfindingPoints[nodeIdx - 1]._position;
 	_targetPosition = nodePos;
 	_stepDeltaX = abs(_targetPosition.x - _gameObject->_position.x);
 	_stepDeltaY = abs(_targetPosition.y - _gameObject->_position.y);


Commit: bee5a75f2689edeb2a5e7bcd7f56b8b23276d8b6
    https://github.com/scummvm/scummvm/commit/bee5a75f2689edeb2a5e7bcd7f56b8b23276d8b6
Author: Martin Gerhardy (martin.gerhardy at gmail.com)
Date: 2026-08-23T21:11:42+02:00

Commit Message:
MACS2: replaced magic numbers

Changed paths:
    engines/macs2/debugtools.cpp
    engines/macs2/macs2.cpp
    engines/macs2/music.cpp
    engines/macs2/saveload.cpp


diff --git a/engines/macs2/debugtools.cpp b/engines/macs2/debugtools.cpp
index 1a19ddd66f7..0e37a09cf7b 100644
--- a/engines/macs2/debugtools.cpp
+++ b/engines/macs2/debugtools.cpp
@@ -26,6 +26,7 @@
 #include "common/str-enc.h"
 #include "common/system.h"
 #include "common/ustr.h"
+#include "common/util.h"
 #include "macs2/detection.h"
 #include "macs2/gameobjects.h"
 #include "macs2/macs2.h"
@@ -2050,7 +2051,7 @@ static void showSoundWindow() {
 	// Channel selector
 	static int selectedVoice = 0;
 	if (ImGui::BeginCombo("Voice", Common::String::format("Voice %d", selectedVoice).c_str())) {
-		for (int i = 0; i < 9; i++) {
+		for (int i = 0; i < ARRAYSIZE(ds.voices); i++) {
 			bool selected = (selectedVoice == i);
 			const char *label = ds.voices[i].active
 									? Common::String::format("Voice %d [CH%d N%02X]", i, ds.voices[i].channel, ds.voices[i].note).c_str()
@@ -2080,7 +2081,7 @@ static void showSoundWindow() {
 	// All voices overview
 	ImGui::Separator();
 	ImGui::TextUnformatted("All Voices:");
-	for (int i = 0; i < 9; i++) {
+	for (int i = 0; i < ARRAYSIZE(ds.regHistory); i++) {
 		float voiceData[Music::kDebugRingSize];
 		for (int j = 0; j < Music::kDebugRingSize; j++) {
 			voiceData[j] = ds.regHistory[i][(ringPos + j) % Music::kDebugRingSize];
diff --git a/engines/macs2/macs2.cpp b/engines/macs2/macs2.cpp
index 960a94aefbe..d969deb11e9 100644
--- a/engines/macs2/macs2.cpp
+++ b/engines/macs2/macs2.cpp
@@ -250,11 +250,14 @@ void Macs2Engine::loadResourceFileV2() {
 	memset(_mapSceneOffsets, 0, sizeof(_mapSceneOffsets));
 	_imageResources.clear();
 	_imageResources.resize(33);
-	for (int i = 0; i < 33; i++)
+	for (int i = 0; i < ARRAYSIZE(_cursorHotspots); i++) {
 		_cursorHotspots[i] = Common::Point(0, 0);
+	}
 	_hudButtons.clear();
-	for (int i = 0; i < 6; i++) {
+	for (int i = 0; i < ARRAYSIZE(_hudMegapicLoaded); i++) {
 		_hudMegapicLoaded[i] = false;
+	}
+	for (int i = 0; i < ARRAYSIZE(_hudMegapics); i++) {
 		_hudMegapics[i].free();
 	}
 	_panelTopY = 0;
@@ -274,8 +277,9 @@ void Macs2Engine::loadResourceFileV2() {
 	_fileStream->read(_palVanilla, 0x300);
 	memcpy(_pal, _palVanilla, 0x300);
 
-	for (int i = 0; i < 4; i++)
+	for (int i = 0; i < ARRAYSIZE(_hudTextRecolor); i++) {
 		_hudTextRecolor[i] = _fileStream->readUint16LE();
+	}
 
 	_panelTopY = _fileStream->readUint16LE();
 	_panelHeight = _fileStream->readUint16LE();
@@ -285,7 +289,7 @@ void Macs2Engine::loadResourceFileV2() {
 		_panelHeight = 146;
 	}
 
-	for (int i = 0; i < 6; i++) {
+	for (int i = 0; i < ARRAYSIZE(_hudMegapicLoaded); i++) {
 		const uint16 flag = _fileStream->readUint16LE();
 		if (flag == 0)
 			continue;
@@ -451,7 +455,7 @@ void Macs2Engine::loadResourceFileV2() {
 	if (!loadSizedFont(_panelGlyphs, numPanelGlyphs, maxPanelGlyphHeight))
 		warning("readGlobalAssetsV2: failed loading SysFont");
 
-	for (int i = 0; i < 256; i++)
+	for (int i = 0; i < ARRAYSIZE(_mapSceneOffsets); i++)
 		_mapSceneOffsets[i] = _fileStream->readUint32LE();
 
 	_saveListScroll = 1;
@@ -463,7 +467,7 @@ void Macs2Engine::loadResourceFileV2() {
 			installed++;
 	}
 	uint megas = 0;
-	for (int i = 0; i < 6; i++) {
+	for (int i = 0; i < ARRAYSIZE(_hudMegapicLoaded); i++) {
 		if (_hudMegapicLoaded[i])
 			megas++;
 	}
diff --git a/engines/macs2/music.cpp b/engines/macs2/music.cpp
index 5722afcc8f4..77eda940e1a 100644
--- a/engines/macs2/music.cpp
+++ b/engines/macs2/music.cpp
@@ -491,7 +491,7 @@ void Music::updateDebugState() {
 	_debug.masterVolume = _masterVolume;
 	_debug.numOplChannels = _numOplChannels;
 
-	for (int i = 0; i < 9; i++) {
+	for (int i = 0; i < ARRAYSIZE(_debug.voices); i++) {
 		_debug.voices[i].note = _voiceNote[i];
 		_debug.voices[i].channel = _voiceMidiChannel[i];
 		_debug.voices[i].active = (_voiceAge[i] == 0);
diff --git a/engines/macs2/saveload.cpp b/engines/macs2/saveload.cpp
index c8c83fce90d..5e10aa61177 100644
--- a/engines/macs2/saveload.cpp
+++ b/engines/macs2/saveload.cpp
@@ -19,6 +19,7 @@
  *
  */
 
+#include "common/util.h"
 #include "macs2/gameobjects.h"
 #include "macs2/macs2.h"
 #include "macs2/view1.h"
@@ -372,10 +373,11 @@ Common::Error Macs2Engine::syncGame(Common::Serializer &s) {
 			_hotspotOverrides[i + 1] = val;
 		}
 	}
-	if (s.isLoading() && _hotspotOverrides.size() < 0x21)
+	if (s.isLoading() && _hotspotOverrides.size() < 0x21) {
 		_hotspotOverrides.resize(0x21, 0xFFFF);
+	}
 
-	for (int i = 0; i < 4; i++) {
+	for (int i = 0; i < ARRAYSIZE(_sceneTimerParams); i++) {
 		s.syncAsUint32LE(_sceneTimerParams[i]);
 	}
 


Commit: 9905b340f2ab5a16bab1155c15bafd3faf0ed816
    https://github.com/scummvm/scummvm/commit/9905b340f2ab5a16bab1155c15bafd3faf0ed816
Author: Martin Gerhardy (martin.gerhardy at gmail.com)
Date: 2026-08-23T21:11:42+02:00

Commit Message:
MACS2: removed magic numbers

Changed paths:
    engines/macs2/view1.cpp


diff --git a/engines/macs2/view1.cpp b/engines/macs2/view1.cpp
index d3262a1fd64..d66044f6e86 100644
--- a/engines/macs2/view1.cpp
+++ b/engines/macs2/view1.cpp
@@ -851,16 +851,16 @@ void View1::drawPath(Graphics::ManagedSurface &s) {
 }
 
 void View1::layoutActionBarButtons() {
+	_mainMenuButtonLocations.resize(9);
 	uint16 maxW = 0, maxH = 0;
-	for (int i = 0; i < 9 && i < (int)g_engine->_imageResources.size(); i++) {
+	for (int i = 0; i < (int)_mainMenuButtonLocations.size() && i < (int)g_engine->_imageResources.size(); i++) {
 		maxW = MAX(maxW, g_engine->_imageResources[i]._width);
 		maxH = MAX(maxH, g_engine->_imageResources[i]._height);
 	}
 	const uint16 btnW = maxW + 6;
 	const uint16 btnH = maxH + 6;
 
-	_mainMenuButtonLocations.resize(9);
-	for (int i = 0; i < 9; i++) {
+	for (int i = 0; i < (int)_mainMenuButtonLocations.size(); i++) {
 		const int col = i % 3;
 		const int row = i / 3;
 		const uint16 cellX = _mainMenuRect.left + 4 + col * (btnW + 4);
@@ -959,7 +959,7 @@ void View1::drawMainMenu(Graphics::ManagedSurface &s) {
 	drawBorderSide(Common::Point(_mainMenuRect.left, _mainMenuRect.top), Common::Point(_mainMenuRect.width(), _mainMenuRect.height()), s);
 	drawNinePatchBorder(Common::Point(_mainMenuRect.left, _mainMenuRect.top), Common::Point(_mainMenuRect.width(), _mainMenuRect.height()), kBorderRaised, false, false, s);
 
-	for (int i = 0; i < 9 && i < (int)g_engine->_imageResources.size(); i++) {
+	for (int i = 0; i < (int)_mainMenuButtonLocations.size() && i < (int)g_engine->_imageResources.size(); i++) {
 		const Common::Rect &cell = _mainMenuButtonLocations[i];
 		const bool pressed = (_clickedButtonIndex == (uint16)(i + 1));
 		const BorderStyle &border = pressed ? kBorderPressed : kBorderRaised;


Commit: 88cfefbc7fda14c5a2084cb0e26c840a496bcc34
    https://github.com/scummvm/scummvm/commit/88cfefbc7fda14c5a2084cb0e26c840a496bcc34
Author: Martin Gerhardy (martin.gerhardy at gmail.com)
Date: 2026-08-23T21:11:43+02:00

Commit Message:
MACS2: renamed variables - they are no constants anymore

Changed paths:
    engines/macs2/events.cpp


diff --git a/engines/macs2/events.cpp b/engines/macs2/events.cpp
index a9acb40fbfb..02d5795d69a 100644
--- a/engines/macs2/events.cpp
+++ b/engines/macs2/events.cpp
@@ -53,8 +53,8 @@ void Events::runGame() {
 	// ISR counter exceeds 1 (every 2 ticks) -> ~10.8fps / ~92.6ms.
 	// Tick period / ticks-per-frame come from engine facades for later platforms.
 	uint32 lastTickTime = g_system->getMillis();
-	const uint32 kTimerTickMs = g_engine->timerTickMs();
-	const uint16 kNormalTicksPerGameFrame = g_engine->ticksPerGameFrame();
+	const uint32 timerTickMs = g_engine->timerTickMs();
+	const uint16 normalTicksPerGameFrame = g_engine->ticksPerGameFrame();
 	uint16 timerTickCounter = 0;
 
 	Common::Event e;
@@ -76,10 +76,10 @@ void Events::runGame() {
 
 		// Accumulate timer ticks based on elapsed wall-clock time
 		const uint32 elapsed = currentMillis - lastTickTime;
-		if (elapsed >= kTimerTickMs) {
-			const uint16 ticks = elapsed / kTimerTickMs;
+		if (elapsed >= timerTickMs) {
+			const uint16 ticks = elapsed / timerTickMs;
 			timerTickCounter += ticks;
-			lastTickTime += ticks * kTimerTickMs;
+			lastTickTime += ticks * timerTickMs;
 		}
 
 		bool doTick = false;
@@ -91,7 +91,7 @@ void Events::runGame() {
 			doTick = (timerTickCounter >= 0x12); // TODO: this is running at different speed compared to running this in dosbox
 			break;
 		default: // normal: every kNormalTicksPerGameFrame timer ticks
-			doTick = (timerTickCounter >= kNormalTicksPerGameFrame);
+			doTick = (timerTickCounter >= normalTicksPerGameFrame);
 			break;
 		}
 


Commit: 8ff8fa25e669c424db68f6defbc3f81280f8a50b
    https://github.com/scummvm/scummvm/commit/8ff8fa25e669c424db68f6defbc3f81280f8a50b
Author: Martin Gerhardy (martin.gerhardy at gmail.com)
Date: 2026-08-23T21:11:43+02:00

Commit Message:
MACS2: reduced scope

Changed paths:
    engines/macs2/gameobjects.cpp


diff --git a/engines/macs2/gameobjects.cpp b/engines/macs2/gameobjects.cpp
index e9451a29217..107c009f7b1 100644
--- a/engines/macs2/gameobjects.cpp
+++ b/engines/macs2/gameobjects.cpp
@@ -438,9 +438,8 @@ const Common::Array<uint8> *Macs2::GameObject::getAnimSlotBlob(uint16 slot) cons
 
 bool Macs2::GameObject::isAnimSlotLoaded(uint16 orient) const {
 	const uint16 overloadSlot = g_engine->overloadAnimSlot();
-	const uint16 maxOrient = g_engine->maxOrientations();
 	if (g_engine->isV2()) {
-		for (uint i = 0; i < 5; i++) {
+		for (uint i = 0; i < ARRAYSIZE(_specialAnimTriggers); i++) {
 			const uint16 trig = _specialAnimTriggers[i];
 			if ((int16)trig >= 0 && trig == orient) {
 				const uint16 animSlot = Macs2Engine::specialAnimSlotToAnimSlot(i + 1);
@@ -458,6 +457,7 @@ bool Macs2::GameObject::isAnimSlotLoaded(uint16 orient) const {
 		const Common::Array<uint8> *blob = getAnimSlotBlob(overloadSlot);
 		return blob != nullptr && !blob->empty();
 	}
+	const uint16 maxOrient = g_engine->maxOrientations();
 	if (orient < 1 || orient > maxOrient)
 		return false;
 	const uint slot = orient - 1;


Commit: 7fa2422e40737c0b51bb774620d3671565fe678c
    https://github.com/scummvm/scummvm/commit/7fa2422e40737c0b51bb774620d3671565fe678c
Author: Martin Gerhardy (martin.gerhardy at gmail.com)
Date: 2026-08-23T21:11:43+02:00

Commit Message:
MACS2: minor opcode cleanup and fixed max hotspots

Changed paths:
    engines/macs2/macs2.cpp
    engines/macs2/macs2.h
    engines/macs2/scriptexecutor.cpp


diff --git a/engines/macs2/macs2.cpp b/engines/macs2/macs2.cpp
index d969deb11e9..f3d42f3b583 100644
--- a/engines/macs2/macs2.cpp
+++ b/engines/macs2/macs2.cpp
@@ -1423,6 +1423,8 @@ void Macs2Engine::changeScene(uint32 newSceneIndex, bool executeScript) {
 	// Binary changeScene (1008:2574): loadObjectData for scene objects except current actor.
 	GameObject *actorObject = GameObjects::getObjectByIndex(Scenes::instance()._currentActorIndex);
 	if (actorObject != nullptr && actorObject->_sceneIndex == newSceneIndex) {
+		if (isV2())
+			loadObjectData(actorObject);
 		Character *actorChar = new Character();
 		actorChar->_gameObject = actorObject;
 		currentView->_characters.push_back(actorChar);
@@ -3056,6 +3058,8 @@ bool Macs2Engine::loadObjectData(GameObject *obj) {
 	}
 
 	obj->_overloadAnimTriggerDirection = 0x7FFF;
+	for (uint i = 0; i < ARRAYSIZE(obj->_specialAnimTriggers); i++)
+		obj->_specialAnimTriggers[i] = 0x7FFF;
 	obj->_useOverloadAnimation = false;
 	obj->_overloadAnimation.clear();
 	obj->_snapToTarget = false;
@@ -3171,7 +3175,7 @@ uint16 Macs2Engine::resolveAnimSlotIndex(const GameObject *obj) const {
 	if (obj == nullptr)
 		return 0;
 	if (isV2()) {
-		for (uint i = 0; i < 5; i++) {
+		for (uint i = 0; i < ARRAYSIZE(obj->_specialAnimTriggers); i++) {
 			const uint16 trig = obj->_specialAnimTriggers[i];
 			if ((int16)trig >= 0 && trig == obj->_orientation)
 				return specialAnimSlotToAnimSlot(i + 1);
diff --git a/engines/macs2/macs2.h b/engines/macs2/macs2.h
index 551fbe25dbb..720c079152a 100644
--- a/engines/macs2/macs2.h
+++ b/engines/macs2/macs2.h
@@ -789,7 +789,8 @@ public:
 	uint16 overloadAnimSlot() const { return maxAnimSlots(); }
 	static uint16 specialAnimSlotToAnimSlot(uint16 specialSlot);
 	/** Scene hotspot override table entries (1-based inclusive max). */
-	uint16 maxHotspots() const { return 0x10; }
+	/** Hotspot remap table indices (1-based). DOS scene+0x5BD1: 16; V2 ActModule+0x6161: 32. */
+	uint16 maxHotspots() const { return isV2() ? 0x20 : 0x10; }
 	/** Per-object resource offset table entries. */
 	uint maxObjectResources() const { return 32; }
 	/** Anim slot used for the current orientation (overload-direction rule). */
diff --git a/engines/macs2/scriptexecutor.cpp b/engines/macs2/scriptexecutor.cpp
index 5682710e879..1f8ddd0415f 100644
--- a/engines/macs2/scriptexecutor.cpp
+++ b/engines/macs2/scriptexecutor.cpp
@@ -27,6 +27,7 @@
 #include "common/memstream.h"
 #include "common/path.h"
 #include "common/system.h"
+#include "common/util.h"
 #include "engines/enhancements.h"
 #include "macs2/amiga_archive.h"
 #include "macs2/amiga_decode.h"
@@ -2185,19 +2186,19 @@ OpcodeResult Script::ScriptExecutor::scriptSetDirection() {
 		return OpcodeResult::Continue;
 	}
 	if (_engine->isV2()) {
-		if (specialSlot < 1 || specialSlot > 5) {
+		if (specialSlot < 1 || specialSlot > ARRAYSIZE(object->_specialAnimTriggers)) {
 			setScriptError(0x2e);
 			return OpcodeResult::Continue;
 		}
-		// Binary: clear any other special slot that already uses this direction.
-		for (uint i = 0; i < 5; i++) {
+		// clear any other slot that already uses this direction to 0.
+		for (uint i = 0; i < ARRAYSIZE(object->_specialAnimTriggers); i++) {
 			if (object->_specialAnimTriggers[i] == value)
 				object->_specialAnimTriggers[i] = 0;
 		}
 		object->_specialAnimTriggers[specialSlot - 1] = value;
-		return OpcodeResult::Continue;
+	} else {
+		object->_overloadAnimTriggerDirection = value;
 	}
-	object->_overloadAnimTriggerDirection = value;
 	return OpcodeResult::Continue;
 }
 
@@ -2237,13 +2238,13 @@ OpcodeResult Script::ScriptExecutor::scriptStopAnimation() {
 			obj->_useOverloadAnimation = false;
 			obj->_overloadAnimation.clear();
 		}
-		return OpcodeResult::Continue;
+	} else {
+		obj->_overloadAnimTriggerDirection = 0x7FFF;
+		obj->_useOverloadAnimation = false;
+		obj->_overloadAnimation.clear();
+		if (obj->_blobs.size() > 20)
+			obj->_blobs[20].clear();
 	}
-	obj->_overloadAnimTriggerDirection = 0x7FFF;
-	obj->_useOverloadAnimation = false;
-	obj->_overloadAnimation.clear();
-	if (obj->_blobs.size() > 20)
-		obj->_blobs[20].clear();
 	return OpcodeResult::Continue;
 }
 
@@ -2541,7 +2542,8 @@ OpcodeResult Script::ScriptExecutor::scriptSetHotspotOverride() {
 
 	clearScriptError();
 	const uint16 maxHotspot = _engine->maxHotspots();
-	if (v1 < 1 || v1 > maxHotspot || v2 < 1 || v2 > maxHotspot) {
+	// v1 validates both ids; v2 only rejects an out-of-range source
+	if (v1 < 1 || v1 > maxHotspot || (!_engine->isV2() && (v2 < 1 || v2 > maxHotspot))) {
 		setScriptError(0x1e);
 		return OpcodeResult::Continue;
 	}


Commit: f97da856698a7bd5a90d8d285b9d006bd9f16b9b
    https://github.com/scummvm/scummvm/commit/f97da856698a7bd5a90d8d285b9d006bd9f16b9b
Author: Martin Gerhardy (martin.gerhardy at gmail.com)
Date: 2026-08-23T21:11:43+02:00

Commit Message:
MACS2: ignore enhancement here if there is a native action bar

Changed paths:
    engines/macs2/view1.cpp


diff --git a/engines/macs2/view1.cpp b/engines/macs2/view1.cpp
index d66044f6e86..c60164a88b5 100644
--- a/engines/macs2/view1.cpp
+++ b/engines/macs2/view1.cpp
@@ -444,8 +444,9 @@ void View1::updateCursor(const byte *palette) {
 	// The array has 33 entries (indices 0-32). Cursor modes 0x13-0x1A map to entries 18-25.
 	int mode = (int)g_engine->_scriptExecutor->_cursorMode - 1;
 
-	// SCUMM-style UI: gameplay verbs share the walk cursor; the sentence line shows the active verb.
-	if (hasPersistentActionBar()) {
+	// SCUMM-style enhancement only: gameplay verbs share the walk cursor; the sentence
+	// line shows the active verb. Native V2 HUD must keep per-MouseNr graphics.
+	if (g_engine->enhancementEnabled(kEnhUIUX) && !g_engine->hasNativeHudAssets()) {
 		const Script::MouseMode cursorMode = g_engine->_scriptExecutor->_cursorMode;
 		switch (cursorMode) {
 		case Script::MouseMode::Talk:


Commit: 959699743c6c4b2e4a8d059c4c0c63d0dc97aa89
    https://github.com/scummvm/scummvm/commit/959699743c6c4b2e4a8d059c4c0c63d0dc97aa89
Author: Martin Gerhardy (martin.gerhardy at gmail.com)
Date: 2026-08-23T21:11:43+02:00

Commit Message:
MACS2: fixed cursor hotspots

Changed paths:
    engines/macs2/actionbar.cpp
    engines/macs2/view1.cpp


diff --git a/engines/macs2/actionbar.cpp b/engines/macs2/actionbar.cpp
index e8e00fa31b8..6870c42b0c1 100644
--- a/engines/macs2/actionbar.cpp
+++ b/engines/macs2/actionbar.cpp
@@ -129,11 +129,11 @@ void ActionBar::syncActiveVerbFromCursorMode() {
 }
 
 bool ActionBar::useScummSkin() const {
-	return g_engine->enhancementEnabled(kEnhUIUX);
+	return !useNativeSkin() && g_engine->enhancementEnabled(kEnhUIUX);
 }
 
 bool ActionBar::useNativeSkin() const {
-	return !useScummSkin() && g_engine->hasNativeHudAssets();
+	return g_engine->hasNativeHudAssets();
 }
 
 int ActionBar::gameAreaBottomY() const {
diff --git a/engines/macs2/view1.cpp b/engines/macs2/view1.cpp
index c60164a88b5..01e2176295e 100644
--- a/engines/macs2/view1.cpp
+++ b/engines/macs2/view1.cpp
@@ -489,7 +489,16 @@ void View1::updateCursor(const byte *palette) {
 		rgbaCursor[i] = rgbaCursorFormat.RGBToColor(paletteEntry[0], paletteEntry[1], paletteEntry[2]);
 	}
 
-	CursorMan.replaceCursor(rgbaCursor.data(), width, height, width >> 1, height >> 1, 0, &rgbaCursorFormat);
+	int hotX = width >> 1;
+	int hotY = height >> 1;
+	if (mode >= 0 && mode < ARRAYSIZE(g_engine->_cursorHotspots)) {
+		const Common::Point &hot = g_engine->_cursorHotspots[mode];
+		if (hot.x != 0 || hot.y != 0) {
+			hotX = hot.x;
+			hotY = hot.y;
+		}
+	}
+	CursorMan.replaceCursor(rgbaCursor.data(), width, height, hotX, hotY, 0, &rgbaCursorFormat);
 	// Enable a cursor palette so the backend won't re-blit the cursor on
 	// every screen palette change. The macs2 engine uses RGBA cursors with
 	// baked-in palette colors, so the cursor palette content is irrelevant -


Commit: 62980da2baf35f7fa6d818b3306dfb0a83649a97
    https://github.com/scummvm/scummvm/commit/62980da2baf35f7fa6d818b3306dfb0a83649a97
Author: Martin Gerhardy (martin.gerhardy at gmail.com)
Date: 2026-08-23T21:11:43+02:00

Commit Message:
MACS2: fixed v2 map input

Changed paths:
    engines/macs2/scriptexecutor.cpp


diff --git a/engines/macs2/scriptexecutor.cpp b/engines/macs2/scriptexecutor.cpp
index 1f8ddd0415f..e2b268c7a73 100644
--- a/engines/macs2/scriptexecutor.cpp
+++ b/engines/macs2/scriptexecutor.cpp
@@ -1252,8 +1252,12 @@ OpcodeResult Script::ScriptExecutor::scriptChangeScene() {
 		currentView->startFadingWithSpeed(transitionSpeed);
 	}
 
-	// Binary step 8: set cursor to Walk (0x16) after scene change
-	_engine->setCursorMode(Script::MouseMode::Walk);
+	// V1 scriptChangeScene (1008:ad6e) forces Walk (0x16) after init
+	// V2 does not: it leaves it at the value scripts left it (typically via opcode 0x67 setCursorType)
+	// Overview/world-map init sets Use (0x15) so hotspot special 0x01 matches; forcing Walk here would
+	// send left-clicks through the walk path instead
+	if (!_engine->isV2())
+		_engine->setCursorMode(Script::MouseMode::Walk);
 	_interactedObjectID = 0;
 	_interactedInventoryItemId = 0;
 	// Binary: after init completes synchronously, terminate outer script context and




More information about the Scummvm-git-logs mailing list