[Scummvm-git-logs] scummvm master -> 2cc985a4956206136f5d27d81b04b5bfaac641d3

mgerhardy noreply at scummvm.org
Fri Aug 28 15:34:08 UTC 2026


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

Summary:
5311131f33 MACS2: replaced magic number
2ee49c6841 MACS2: fixed mid dialog (or other wait state) saving and loading afterwards
94184a697a MACS2: fixed some rendering issues because the scene depth map was not properly updated
3af31fc293 MACS2: removed todo comment
a1bab853f8 MACS2: replaced magic number and extract to local var
2cc985a495 MACS2: use Graphics::Palette class


Commit: 5311131f33be5daf96efc217dfc09237bb7999d5
    https://github.com/scummvm/scummvm/commit/5311131f33be5daf96efc217dfc09237bb7999d5
Author: Martin Gerhardy (martin.gerhardy at gmail.com)
Date: 2026-08-28T10:57:50+02:00

Commit Message:
MACS2: replaced magic number

Changed paths:
    engines/macs2/macs2.cpp


diff --git a/engines/macs2/macs2.cpp b/engines/macs2/macs2.cpp
index b5ea69e9a21..61c26725df0 100644
--- a/engines/macs2/macs2.cpp
+++ b/engines/macs2/macs2.cpp
@@ -393,7 +393,7 @@ void Macs2Engine::loadResourceFileV2() {
 				}
 			}
 			const int slot = (int)mouseNr - 1;
-			if (mouseNr != 0 && slot >= 0 && slot < 33) {
+			if (mouseNr != 0 && slot >= 0 && slot < ARRAYSIZE(_cursorHotspots)) {
 				const bool empty = _imageResources[slot]._data.empty();
 				if (empty || prefer) {
 					_imageResources[slot] = prefer && gotActive ? activeFrame : frame;


Commit: 2ee49c6841ee36ffdb895f1bbf96da4565d35401
    https://github.com/scummvm/scummvm/commit/2ee49c6841ee36ffdb895f1bbf96da4565d35401
Author: Martin Gerhardy (martin.gerhardy at gmail.com)
Date: 2026-08-27T20:02:41+02:00

Commit Message:
MACS2: fixed mid dialog (or other wait state) saving and loading afterwards

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


diff --git a/engines/macs2/saveload.cpp b/engines/macs2/saveload.cpp
index 5e10aa61177..35debc76055 100644
--- a/engines/macs2/saveload.cpp
+++ b/engines/macs2/saveload.cpp
@@ -36,6 +36,11 @@ Common::Error Macs2Engine::syncGame(Common::Serializer &s) {
 	if (view1 == nullptr)
 		return Common::kUnknownError;
 
+	bool pendingScriptRestore = false;
+	bool pendingScriptIsExecuting = false;
+	uint16 pendingExecutingObjectId = 0;
+	uint16 pendingScriptPosition = 0;
+	uint16 pendingScriptEndPosition = 0;
 	// --- Header: 12-byte magic ---
 	if (s.isSaving()) {
 		byte magic[12];
@@ -105,13 +110,13 @@ Common::Error Macs2Engine::syncGame(Common::Serializer &s) {
 		_scriptExecutor->_soundSystemActive = soundSystemActive != 0;
 
 	// --- Script execution state ---
-	// g_wScriptIsExecuting [0xf88]: 1 byte
-	uint8 scriptIsExecuting = _scriptExecutor->isExecuting() ? 1 : 0;
-	s.syncAsByte(scriptIsExecuting);
-	if (s.isLoading()) {
-		if (scriptIsExecuting == 0)
-			_scriptExecutor->setIdle();
+	// g_wScriptIsExecuting [0xf88]: 1 byte — binary: g_wScriptPosition < g_wScriptEndPosition
+	uint8 scriptIsExecuting = 0;
+	if (s.isSaving()) {
+		_scriptExecutor->prepareScriptStateForSave();
+		scriptIsExecuting = _scriptExecutor->isScriptMidExecution() ? 1 : 0;
 	}
+	s.syncAsByte(scriptIsExecuting);
 
 	// g_wScriptPosition [0xf8a]: 2 bytes
 	uint16 scriptPosition = (uint16)_scriptExecutor->getScriptPosition();
@@ -121,35 +126,18 @@ Common::Error Macs2Engine::syncGame(Common::Serializer &s) {
 	uint16 scriptEndPosition = (uint16)_scriptExecutor->getScriptEndPosition();
 	s.syncAsUint16LE(scriptEndPosition);
 
-	// g_wExecutingScriptObjectId [0xf92]: 2 bytes
+	// g_wExecutingScriptObjectId [0xf92]: 2 bytes (0 = scene script)
 	uint16 executingObjectId = _scriptExecutor->getExecutingObjectId();
 	s.syncAsUint16LE(executingObjectId);
 
 	if (s.isLoading()) {
-		// Restore script execution state matching binary loadGameFromFile (1008:747e).
-		// The original sets g_wScriptDataPtrLow/High based on executingObjectId:
-		//   objectId == 0: use scene script (sceneData+0x5207/0x5209)
-		//   objectId != 0: use object's runtime script (runtime+0x187/0x189)
-		// Then g_wScriptPosition is used by the executor to seek within that script.
-		if (scriptIsExecuting) {
-			if (executingObjectId == 0) {
-				// Scene script
-				_scriptExecutor->setCurrentSceneScriptAt(scriptPosition);
-			} else {
-				// Object script: find the object and set its script stream
-				GameObject *execObj = GameObjects::getObjectByIndex(executingObjectId);
-				if (execObj && !execObj->_script.empty()) {
-					Common::MemoryReadStream *objStream = execObj->getScriptStream();
-					_scriptExecutor->setScript(objStream);
-					if (objStream && scriptPosition < objStream->size())
-						objStream->seek(scriptPosition, SEEK_SET);
-				}
-			}
-			_scriptExecutor->setExecutingObjectId(executingObjectId);
-		} else {
-			_scriptExecutor->setIdle();
-			_scriptExecutor->setExecutingObjectId(executingObjectId);
-		}
+		// Object script bytes are restored later in the per-object loop; defer
+		// reattaching the script stream until after runtime+0x187 is loaded.
+		pendingScriptRestore = true;
+		pendingScriptIsExecuting = scriptIsExecuting != 0;
+		pendingExecutingObjectId = executingObjectId;
+		pendingScriptPosition = scriptPosition;
+		pendingScriptEndPosition = scriptEndPosition;
 	}
 
 	// g_wScriptClickFlag [0xf94]: 2 bytes
@@ -844,6 +832,13 @@ Common::Error Macs2Engine::syncGame(Common::Serializer &s) {
 
 	// --- Post-load: rebuild view state ---
 	if (s.isLoading()) {
+		if (pendingScriptRestore) {
+			_scriptExecutor->restoreScriptExecutionAfterLoad(pendingScriptIsExecuting,
+															pendingExecutingObjectId,
+															pendingScriptPosition,
+															pendingScriptEndPosition);
+		}
+
 		// NOTE: Characters were already created right after changeScene (above) so
 		// the per-object loop could populate their runtime walk/draw/dirty state.
 		// Do NOT recreate them here - that would discard the loaded fields.
diff --git a/engines/macs2/scriptexecutor.cpp b/engines/macs2/scriptexecutor.cpp
index 8c168aa6824..53935d819da 100644
--- a/engines/macs2/scriptexecutor.cpp
+++ b/engines/macs2/scriptexecutor.cpp
@@ -496,8 +496,10 @@ void ScriptExecutor::runSceneScriptPass(bool initRun, bool repeatRun) {
 	_repeatRunFlag = repeatRun;
 	_scriptExecutionState = ScriptExecutionState::ExecutingSceneScript;
 	setScript(Scenes::instance()._currentSceneScript);
-	if (_stream && _stream->size() > 0)
+	if (_stream && _stream->size() > 0) {
 		_stream->seek(0, SEEK_SET);
+		_scriptEndPosition = _stream->size();
+	}
 	const ExecutorState previousState = _state;
 	_state = ExecutorState::Executing;
 	step();
@@ -610,7 +612,8 @@ void ScriptExecutor::step() {
 			// Continue execution
 
 			// Check if the currently executing script is at the end
-			if (_stream->pos() >= _stream->size()) {
+			if (_stream && _stream->pos() >= effectiveScriptEnd()) {
+				syncScriptIsExecutingFlag();
 				// Binary (runScriptExecutor 1008:e3e7): if script finishes while
 				// g_wScriptSkippable is still set, treat as error 0x11 and abort.
 				if (_scriptSkippable) {
@@ -632,6 +635,7 @@ void ScriptExecutor::step() {
 				if (result == OpcodeResult::WaitForCallback) {
 					// We need to change our state as well now
 					_state = ExecutorState::WaitingForCallback;
+					syncScriptIsExecutingFlag();
 					if (!_debugPaused && !_waitingForUiClick) {
 						// Binary sets hourglass inline in executeOpcodes for blocking
 						// waits. UI waits (0x0A/0x0D/0x17) set _waitingForUiClick instead.
@@ -639,6 +643,16 @@ void ScriptExecutor::step() {
 					}
 					return;
 				}
+				syncScriptIsExecutingFlag();
+				if (_stream && _stream->pos() >= effectiveScriptEnd()) {
+					if (_scriptSkippable) {
+						setScriptError(0x11);
+						_scriptSkippable = false;
+						shouldContinue = false;
+						break;
+					}
+					shouldContinue = loadNextScript();
+				}
 			}
 			break;
 		}
@@ -650,10 +664,12 @@ void ScriptExecutor::step() {
 		}
 	}
 	// Rewind and reset to the scene script after we are done executing
+	_scriptIsExecuting = false;
 	_executingObjectIndex = Scenes::instance()._currentSceneIndex;
 	setScript(Scenes::instance()._currentSceneScript);
 	if (_stream && _stream->size() > 0) {
 		_stream->seek(0, SEEK_SET);
+		_scriptEndPosition = _stream->size();
 	}
 	_scriptExecutionState = ScriptExecutionState::ExecutingSceneScript;
 	_state = ExecutorState::Idle;
@@ -702,6 +718,8 @@ bool ScriptExecutor::loadNextScript() {
 				}
 				_stream = candidateObject->getScriptStream();
 				_executingScriptObjectId = candidateObject->_index;
+				_scriptEndPosition = candidateObject->_script.size();
+				clampScriptEndToStream();
 				debugC(kDebugScript, "----- Switching execution to script for object: %.4x", candidateObject->_index);
 				return true;
 			}
@@ -734,6 +752,7 @@ bool ScriptExecutor::loadNextScript() {
 				return false;
 			}
 			_stream->seek(0, SEEK_SET);
+			_scriptEndPosition = _stream->size();
 			// Fresh scene-script pass (same as runSceneScriptPass): clear any prior
 			// >0x200 sentinel so the early guard above does not skip object scripts.
 			_executingScriptObjectId = 0;
@@ -1129,6 +1148,8 @@ OpcodeResult Script::ScriptExecutor::scriptMoveObject() {
 	// terminate its script (original: sets scriptEndPosition=0, scriptPosition=0)
 	if ((int)objectID == _executingScriptObjectId) {
 		_stream->seek(0, SEEK_END);
+		_scriptEndPosition = _stream->pos();
+		_scriptIsExecuting = false;
 	}
 
 	// Binary (1008:aa83): when runtime data exists, sync target/finalDest to the new
@@ -4175,12 +4196,11 @@ OpcodeResult Script::ScriptExecutor::executeOpcodes() {
 		if (hasScriptError()) {
 			break;
 		}
-		// TODO: Just for breaking out at the moment when end conditions fail to work
-		if (_stream->eos()) {
+		const uint32 scriptEnd = effectiveScriptEnd();
+		if (_stream->eos() || _stream->pos() >= scriptEnd) {
 			break;
 		}
-		// TODO: Probably only one of these is necessary
-		if (_stream->size() == 0 || _stream->pos() >= _stream->size() - 1) {
+		if (_stream->size() == 0) {
 			break;
 		}
 
@@ -4262,20 +4282,50 @@ void ScriptExecutor::run(bool firstRun) {
 		return;
 	}
 
-	const bool resumingAfterCallback = (_state == ExecutorState::WaitingForCallback) && !firstRun;
-	if (!resumingAfterCallback) {
+	// Binary runScriptExecutor (1008:e3e7): when g_wScriptIsExecuting != 0, continue
+	// from the current script pointer/position (no rewind).
+	const bool resumingMidScript = _scriptIsExecuting && !firstRun;
+	if (!resumingMidScript) {
 		clearScriptError();
-		// TODO: Not sure if this is the right place and condition to reset this
-		// variable. Context here is that we might have an object that triggers several
-		// description strings in a row, and we would disable the executing object
-		// if we always reset this object
-		// TODO: Watch out for issues caused by this
+		// Binary: g_wScriptIsExecuting == 0 resets to scene script at position 0.
+		_scriptIsExecuting = false;
 		_executingScriptObjectId = 0;
 		_repeatRunFlag = false;
 		_isSceneInitRun = firstRun;
+		_scriptExecutionState = ScriptExecutionState::ExecutingSceneScript;
+		setScript(Scenes::instance()._currentSceneScript);
+		if (_stream && _stream->size() > 0) {
+			_stream->seek(0, SEEK_SET);
+			_scriptEndPosition = _stream->size();
+		} else {
+			_scriptEndPosition = 0;
+		}
 	}
 	_state = ExecutorState::Executing;
 	step();
+	syncScriptIsExecutingFlag();
+}
+
+uint32 ScriptExecutor::effectiveScriptEnd() const {
+	if (!_stream || _scriptEndPosition == 0)
+		return _scriptEndPosition;
+	return MIN(_scriptEndPosition, (uint32)_stream->size());
+}
+
+void ScriptExecutor::clampScriptEndToStream() {
+	if (_stream)
+		_scriptEndPosition = MIN(_scriptEndPosition, (uint32)_stream->size());
+	else
+		_scriptEndPosition = 0;
+}
+
+void ScriptExecutor::syncScriptIsExecutingFlag() {
+	const uint32 end = effectiveScriptEnd();
+	if (!_stream || end == 0) {
+		_scriptIsExecuting = false;
+		return;
+	}
+	_scriptIsExecuting = _stream->pos() < end;
 }
 
 void ScriptExecutor::setScript(Common::MemoryReadStream *stream) {
@@ -4291,7 +4341,113 @@ void ScriptExecutor::releaseObjectStream() {
 
 void ScriptExecutor::setCurrentSceneScriptAt(uint32 offset) {
 	setScript(Scenes::instance()._currentSceneScript);
-	_stream->seek(offset, SEEK_SET);
+	if (_stream) {
+		if (_scriptEndPosition == 0)
+			_scriptEndPosition = _stream->size();
+		clampScriptEndToStream();
+		const uint32 seekPos = MIN(offset, effectiveScriptEnd());
+		_stream->seek(seekPos, SEEK_SET);
+	}
+}
+
+void ScriptExecutor::prepareScriptStateForSave() {
+	if (!_stream)
+		return;
+
+	Common::MemoryReadStream *sceneScript = Scenes::instance()._currentSceneScript;
+	const uint32 streamPos = (uint32)_stream->pos();
+
+	if (_executingScriptObjectId != 0) {
+		GameObject *obj = GameObjects::getObjectByIndex(_executingScriptObjectId);
+		const uint32 objScriptSize = obj ? obj->_script.size() : 0;
+		// Saved position/end must fit the object script blob; otherwise the stream is
+		// the scene script and the object id is stale (see mid-dialogue scene saves).
+		if (objScriptSize == 0 || streamPos >= objScriptSize || _scriptEndPosition > objScriptSize) {
+			_executingScriptObjectId = 0;
+			_scriptExecutionState = ScriptExecutionState::ExecutingSceneScript;
+			_executingObjectIndex = Scenes::instance()._currentSceneIndex;
+			if (sceneScript && sceneScript->size() > 0)
+				_scriptEndPosition = sceneScript->size();
+		} else {
+			_scriptEndPosition = objScriptSize;
+		}
+	} else if (sceneScript && sceneScript->size() > 0) {
+		_scriptEndPosition = sceneScript->size();
+	} else if (_stream->size() > 0) {
+		_scriptEndPosition = _stream->size();
+	}
+
+	syncScriptIsExecutingFlag();
+}
+
+void ScriptExecutor::restoreScriptExecutionAfterLoad(bool isExecuting, uint16 executingObjectId,
+													 uint16 scriptPosition, uint16 scriptEndPosition) {
+	// Binary loadGameFromFile (1008:747e / 1008:8395): if g_wScriptIsExecuting,
+	// reattach g_wScriptDataPtr from scene (objectId==0) or object runtime.
+	clearScriptError();
+	_scriptEndPosition = scriptEndPosition;
+	_scriptIsExecuting = isExecuting;
+	if (!isExecuting) {
+		setIdle();
+		_executingScriptObjectId = executingObjectId;
+		return;
+	}
+
+	uint16 resolvedObjectId = executingObjectId;
+	if (executingObjectId != 0) {
+		GameObject *execObj = GameObjects::getObjectByIndex(executingObjectId);
+		const uint32 objScriptSize = execObj ? execObj->_script.size() : 0;
+		if (objScriptSize == 0 || scriptPosition >= objScriptSize || scriptEndPosition > objScriptSize) {
+			Common::MemoryReadStream *sceneScript = Scenes::instance()._currentSceneScript;
+			const uint32 sceneSize = sceneScript ? sceneScript->size() : 0;
+			if (sceneSize == 0 || scriptPosition >= sceneSize || scriptEndPosition > sceneSize) {
+				warning("Cannot restore script execution: saved pos %u end %u does not fit object %u or scene",
+						scriptPosition, scriptEndPosition, executingObjectId);
+				_scriptIsExecuting = false;
+				setIdle();
+				_executingScriptObjectId = executingObjectId;
+				return;
+			}
+			resolvedObjectId = 0;
+		}
+	}
+
+	if (resolvedObjectId == 0) {
+		setCurrentSceneScriptAt(scriptPosition);
+		_scriptExecutionState = ScriptExecutionState::ExecutingSceneScript;
+		_executingObjectIndex = Scenes::instance()._currentSceneIndex;
+	} else {
+		GameObject *execObj = GameObjects::getObjectByIndex(resolvedObjectId);
+		if (execObj == nullptr || execObj->_script.empty()) {
+			warning("Cannot restore script execution: object %u script missing after load",
+					resolvedObjectId);
+			_scriptIsExecuting = false;
+			setIdle();
+			_executingScriptObjectId = executingObjectId;
+			return;
+		}
+		if (_stream && _stream != Scenes::instance()._currentSceneScript) {
+			delete _stream;
+		}
+		Common::MemoryReadStream *objStream = execObj->getScriptStream();
+		setScript(objStream);
+		clampScriptEndToStream();
+		if (objStream) {
+			const uint32 seekPos = MIN((uint32)scriptPosition, effectiveScriptEnd());
+			objStream->seek(seekPos, SEEK_SET);
+		}
+		_scriptExecutionState = ScriptExecutionState::ExecutingOtherScripts;
+		_executingObjectIndex = resolvedObjectId;
+	}
+
+	_executingScriptObjectId = resolvedObjectId;
+	syncScriptIsExecutingFlag();
+	if (!_scriptIsExecuting) {
+		setIdle();
+		return;
+	}
+	// Binary does not call runScriptExecutor on load; next click/tick resumes.
+	_state = ExecutorState::WaitingForCallback;
 }
 
 void ScriptExecutor::tick() {
@@ -4363,7 +4519,7 @@ uint32 ScriptExecutor::getDebugOpcodePosition() const {
 }
 
 uint32 ScriptExecutor::getScriptEndPosition() const {
-	return _stream ? (uint32)_stream->size() : 0;
+	return _scriptEndPosition;
 }
 
 uint32 ScriptExecutor::getVariableValue(int index) const {
diff --git a/engines/macs2/scriptexecutor.h b/engines/macs2/scriptexecutor.h
index 5b4fc6ff911..c082384a607 100644
--- a/engines/macs2/scriptexecutor.h
+++ b/engines/macs2/scriptexecutor.h
@@ -383,16 +383,38 @@ private:
 	// e.g. opcode 0x29). The binary drives the whole script-selection flow off
 	// this single value; there is no separate scene-vs-object state flag.
 	uint16 _executingScriptObjectId = 0;
+	// Binary g_wScriptEndPosition [0xf90] and g_wScriptIsExecuting [0xf88].
+	// End is the active script byte limit (scene+0x520b or object runtime+0x18b);
+	// isExecuting is (position < end), persisted in savegames.
+	uint32 _scriptEndPosition = 0;
+	bool _scriptIsExecuting = false;
+
+	uint32 effectiveScriptEnd() const;
+	void clampScriptEndToStream();
 
 public:
 	ScriptExecutor(Macs2::Macs2Engine *engine);
 	~ScriptExecutor();
 
+	void syncScriptIsExecutingFlag();
+
 	/** Strip a trailing audio extension (.wav/.ogg/...) if present. */
 	static Common::String stripAudioExtension(const Common::String &fileName);
 
 	void setIdle() { _state = ExecutorState::Idle; }
 
+	/**
+	 * Restore mid-script execution after loadGameFromFile (1008:747e).
+	 * When isExecuting, mirrors binary: reattach scene/object script at saved
+	 * position and leave the executor resumable (WaitingForCallback) so the next
+	 * runScriptExecutor continues instead of rewinding to a fresh scene pass.
+	 */
+	void restoreScriptExecutionAfterLoad(bool isExecuting, uint16 executingObjectId,
+										 uint16 scriptPosition, uint16 scriptEndPosition);
+
+	/** Align executing object id / end position with the active script stream before save. */
+	void prepareScriptStateForSave();
+
 	Common::Array<uint16> _dialogueChoiceScriptIndices;
 	Common::Array<Common::StringArray> _dialogueChoices;
 
@@ -538,18 +560,41 @@ public:
 		return _state != ExecutorState::Idle;
 	}
 
+	/** Binary g_wScriptIsExecuting: script VM paused mid-bytecode (incl. UI waits). */
+	bool isScriptMidExecution() const {
+		return _scriptIsExecuting;
+	}
+
 	uint32 getScriptPosition() const;
 	// Returns the position of the last executed/executing opcode (for debugger highlight)
 	uint32 getDebugOpcodePosition() const;
-	bool isWaitingForCallback() const { return _state == ExecutorState::WaitingForCallback; }
+	bool isWaitingForCallback() const {
+		return _state == ExecutorState::WaitingForCallback;
+	}
 	uint32 getScriptEndPosition() const;
-	uint16 getExecutingObjectId() const { return _executingObjectIndex; }
-	void setExecutingObjectId(uint16 id) { _executingObjectIndex = id; }
-	uint16 getFrameWaitCounter() const { return _frameWaitTicksRemaining; }
-	void setFrameWaitCounter(uint16 val) { _frameWaitTicksRemaining = val; }
-	bool isFrameWaitActive() const { return _isFrameWaitActive; }
-	bool getRepeatRunFlag() const { return _repeatRunFlag; }
-	void setRepeatRunFlag(bool val) { _repeatRunFlag = val; }
+	// Binary g_wExecutingScriptObjectId [0xf92]: 0 = scene script, 1..0x200 = object.
+	// Not _executingObjectIndex (iteration cursor / last scene index after Idle).
+	uint16 getExecutingObjectId() const {
+		return _executingScriptObjectId;
+	}
+	void setExecutingObjectId(uint16 id) {
+		_executingScriptObjectId = id;
+	}
+	uint16 getFrameWaitCounter() const {
+		return _frameWaitTicksRemaining;
+	}
+	void setFrameWaitCounter(uint16 val) {
+		_frameWaitTicksRemaining = val;
+	}
+	bool isFrameWaitActive() const {
+		return _isFrameWaitActive;
+	}
+	bool getRepeatRunFlag() const {
+		return _repeatRunFlag;
+	}
+	void setRepeatRunFlag(bool val) {
+		_repeatRunFlag = val;
+	}
 	uint32 getVariableValue(int index) const;
 
 	// Plate / walk debugging: log actor position, area, walkability, script waits.
@@ -575,9 +620,15 @@ public:
 	void saveOpenInventoryScriptContext();
 	void restoreOpenInventoryScriptContext();
 	void setScriptError(uint16 code);
-	bool hasScriptError() const { return _scriptErrorCode != 0; }
-	void clearScriptError() { _scriptErrorCode = 0; }
-	uint16 getScriptErrorCode() const { return _scriptErrorCode; }
+	bool hasScriptError() const {
+		return _scriptErrorCode != 0;
+	}
+	void clearScriptError() {
+		_scriptErrorCode = 0;
+	}
+	uint16 getScriptErrorCode() const {
+		return _scriptErrorCode;
+	}
 
 	// Debug globals PTR_LOOP_1020_06c2 / PTR_LOOP_1020_06c4 (saved on script halt).
 	uint32 _errorScriptPosition = 0;
diff --git a/engines/macs2/view1.cpp b/engines/macs2/view1.cpp
index 01e2176295e..c5431066d04 100644
--- a/engines/macs2/view1.cpp
+++ b/engines/macs2/view1.cpp
@@ -1740,7 +1740,7 @@ bool View1::handleInput(const MouseDownMessage &msg) {
 		// From handleInput (1008:f1d4): clicks during script execution are ONLY processed
 		// if cursor is not Disabled (0x1A). When cursor is Disabled (walk/wait in progress),
 		// clicks are completely ignored.
-		if (g_engine->_scriptExecutor->isExecuting() &&
+		if (g_engine->_scriptExecutor->isScriptMidExecution() &&
 			g_engine->_scriptExecutor->_cursorMode != Script::MouseMode::Disabled) {
 			// Binary handleInput (1008:f1d4-f225): exact sequence of unconditional checks
 			// 1. if g_wIsShowingTextBox != 0: handleTextBoxInput()


Commit: 94184a697ad26329f9a5b56c78d34f54feb60ef2
    https://github.com/scummvm/scummvm/commit/94184a697ad26329f9a5b56c78d34f54feb60ef2
Author: Martin Gerhardy (martin.gerhardy at gmail.com)
Date: 2026-08-27T20:07:35+02:00

Commit Message:
MACS2: fixed some rendering issues because the scene depth map was not properly updated

e.g. in the demo in scene 28 where you reach at butlers farm the gate is going
to be opened, and while the actors were walking through the opened gate, the depthmap
of the closed one was preventing the actors from being visible

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


diff --git a/engines/macs2/macs2.cpp b/engines/macs2/macs2.cpp
index 61c26725df0..1f1f7988cef 100644
--- a/engines/macs2/macs2.cpp
+++ b/engines/macs2/macs2.cpp
@@ -991,6 +991,7 @@ bool Macs2Engine::loadSceneGraphicsV1(uint32 sceneIndex) {
 	Graphics::ManagedSurface depthRLE = readRLEImage(_fileStream->pos(), _fileStream);
 	// Confirmed: depth map at scene offset 0x1013
 	_depthMap.blitFrom(depthRLE);
+	_sceneDepthMap.copyFrom(_depthMap);
 
 	// Offset 2017h
 	Graphics::ManagedSurface pathfindingRLE = readRLEImage(_fileStream->pos(), _fileStream);
@@ -1030,6 +1031,7 @@ bool Macs2Engine::loadSceneGraphicsV1(uint32 sceneIndex) {
 
 	// TODO: Remove the now superfluous one
 	readBackgroundAnimations(_fileStream);
+	updateAllBackgroundAnimationDepthMaps();
 
 	// Offset 51F7h
 	_numPathfindingPoints = _fileStream->readUint16LE();
@@ -1893,6 +1895,68 @@ uint16 Macs2Engine::getWalkabilityAt(int16 y, int16 x) {
 	return value;
 }
 
+void Macs2Engine::updateBackgroundAnimationDepthMap(size_t animIndex) {
+	if (isV2() || _sceneDepthMap.w == 0 || animIndex >= _backgroundAnimations.size())
+		return;
+
+	BackgroundAnimation &anim = _backgroundAnimations[animIndex];
+	BackgroundAnimationBlob &blobEntry = _backgroundAnimationsBlobs[animIndex];
+	Common::Array<uint8> &blob = blobEntry.activeBlob();
+	if (blob.empty())
+		return;
+
+	const uint32 frameStart = BackgroundAnimationBlob::advanceAnimFrame(blob, false, 0);
+	if (frameStart == 0 || frameStart + 10 > blob.size())
+		return;
+
+	const uint16 pixelFrameNum = BackgroundAnimationBlob::getCurrentPixelFrameNumber(blob);
+	const int16 frameOffsetX = (int16)READ_LE_UINT16(&blob[frameStart]);
+	const int16 frameOffsetY = (int16)READ_LE_UINT16(&blob[frameStart + 2]);
+	const uint16 width = READ_LE_UINT16(&blob[frameStart + 6]);
+	const uint16 height = READ_LE_UINT16(&blob[frameStart + 8]);
+	if (width == 0 || height == 0 || frameStart + 10 + (uint32)width * height > blob.size())
+		return;
+
+	const int16 baseX = (int16)anim._x + 1 + frameOffsetX;
+	const int16 baseY = (int16)anim._y + frameOffsetY;
+	const byte *pixels = &blob[frameStart + 10];
+
+	if (pixelFrameNum <= 1) {
+		// First pixel frame (closed gate): restore authored depth under opaque pixels.
+		for (uint16 yy = 0; yy < height; yy++) {
+			for (uint16 xx = 0; xx < width; xx++) {
+				if (pixels[yy * width + xx] == 0)
+					continue;
+				const int px = baseX + (int)xx;
+				const int py = baseY + (int)yy;
+				if (px >= 0 && px < _depthMap.w && py >= 0 && py < _depthMap.h)
+					_depthMap.setPixel(px, py, _sceneDepthMap.getPixel(px, py));
+			}
+		}
+		return;
+	}
+
+	// Later pixel frames (open gate): walkable tiles under opaque pixels use path height.
+	for (uint16 yy = 0; yy < height; yy++) {
+		for (uint16 xx = 0; xx < width; xx++) {
+			if (pixels[yy * width + xx] == 0)
+				continue;
+			const int px = baseX + (int)xx;
+			const int py = baseY + (int)yy;
+			if (px < 0 || px >= _depthMap.w || py < 0 || py >= _depthMap.h)
+				continue;
+			const uint16 walkVal = getWalkabilityAt((int16)py, (int16)px);
+			if (isWalkabilityWalkable(walkVal))
+				_depthMap.setPixel(px, py, (byte)walkVal);
+		}
+	}
+}
+
+void Macs2Engine::updateAllBackgroundAnimationDepthMaps() {
+	for (size_t i = 0; i < _backgroundAnimations.size(); i++)
+		updateBackgroundAnimationDepthMap(i);
+}
+
 // snapToWalkablePosition (1008:9be2)
 // Params: (pTargetY, pTargetX, charY, charX)
 // Modifies *pTargetY and *pTargetX in place.
@@ -3462,6 +3526,50 @@ uint16 BackgroundAnimationBlob::advanceAnimFrame(Common::Array<uint8> &blob, boo
 	return bp12;
 }
 
+uint16 BackgroundAnimationBlob::getCurrentPixelFrameNumber(const Common::Array<uint8> &blob) {
+	if (blob.size() < 14)
+		return 1;
+
+	Common::MemorySeekableReadWriteStream stream(const_cast<byte *>(blob.data()), blob.size());
+	stream.readUint16LE();       // unknown
+	uint16 bp6 = stream.readUint16LE(); // sequence position
+	stream.readUint16LE();       // repeat counter
+	stream.readUint16LE();       // loop start
+	stream.readUint16LE();       // delay counter
+	const uint16 bp0E = stream.readUint16LE() + 1;
+
+	if (bp6 >= bp0E)
+		bp6 = 1;
+
+	uint8 bp0C = 0;
+	while (true) {
+		if (bp6 >= bp0E)
+			bp6 = 1;
+		stream.seek(0x0B + bp6, SEEK_SET);
+		bp0C = stream.readByte();
+		if (bp0C == 0x01) {
+			bp6++;
+			stream.readByte();
+			bp6++;
+		} else if (bp0C == 0x02) {
+			bp6++;
+			stream.readByte();
+			bp6++;
+		} else if (bp0C == 0x03) {
+			bp6 = stream.readByte();
+		} else {
+			break;
+		}
+	}
+
+	uint16 cx = bp0C - 0xA;
+	stream.seek(0xB + bp0E, SEEK_SET);
+	const uint16 frameCount = stream.readUint16LE();
+	if (cx == 0 || cx > frameCount)
+		cx = 1;
+	return cx;
+}
+
 // Matches binary decodeAnimBlob (1010:184d) + mirrorAnimFrame (1010:1319).
 // Iterates each frame in the blob and horizontally flips its pixel data in-place.
 void BackgroundAnimationBlob::mirrorAnimBlob(Common::Array<uint8> &blob) {
diff --git a/engines/macs2/macs2.h b/engines/macs2/macs2.h
index 720c079152a..b6a983a4330 100644
--- a/engines/macs2/macs2.h
+++ b/engines/macs2/macs2.h
@@ -166,6 +166,8 @@ struct BackgroundAnimationBlob {
 	}
 	AnimFrame getCurrentFrame();
 	static uint16 advanceAnimFrame(Common::Array<uint8> &blob, bool bpp6, uint16 bpp8);
+	/** 1-based pixel frame index from the current sequence position (advanceAnimFrame cx). */
+	static uint16 getCurrentPixelFrameNumber(const Common::Array<uint8> &blob);
 	static uint16 getAnimFrameCount(Common::Array<uint8> &blob);
 	// Mirrors (horizontally flips) all frames in an animation blob in-place.
 	// Matches binary decodeAnimBlob (1010:184d) which calls the row-flip at 1010:1319.
@@ -361,6 +363,8 @@ public:
 
 	// This is the depth map
 	Graphics::ManagedSurface _depthMap;
+	// Scene-load snapshot used to restore depth under background animation frame 0.
+	Graphics::ManagedSurface _sceneDepthMap;
 
 	// Shadow/shading intensity map (scene+0x301B). Per-pixel values 0-32
 	// control character sprite darkening via the shading table.
@@ -400,6 +404,9 @@ public:
 	void removePathfindingOverride(uint16 index);
 
 	uint16 getWalkabilityAt(int16 y, int16 x);
+	/** Sync depth map with the current background animation frame (v1 gate fix). */
+	void updateBackgroundAnimationDepthMap(size_t animIndex);
+	void updateAllBackgroundAnimationDepthMaps();
 	bool isPathWalkable(int16 y1, int16 x1, int16 y2, int16 x2);
 	void snapToWalkablePosition(int16 *pTargetY, int16 *pTargetX, int16 charY, int16 charX);
 	int getPathfindingNodeCount() const { return (int)_numPathfindingPoints; }
diff --git a/engines/macs2/saveload.cpp b/engines/macs2/saveload.cpp
index 35debc76055..9a97ec488ff 100644
--- a/engines/macs2/saveload.cpp
+++ b/engines/macs2/saveload.cpp
@@ -405,6 +405,7 @@ Common::Error Macs2Engine::syncGame(Common::Serializer &s) {
 			}
 		}
 	}
+	updateAllBackgroundAnimationDepthMaps();
 
 	// --- PCM sound: size (2 bytes) + data (variable) ---
 	uint16 pcmSoundSize = (uint16)_currentSoundData.size();
diff --git a/engines/macs2/scriptexecutor.cpp b/engines/macs2/scriptexecutor.cpp
index 53935d819da..8d39571a767 100644
--- a/engines/macs2/scriptexecutor.cpp
+++ b/engines/macs2/scriptexecutor.cpp
@@ -293,6 +293,9 @@ OpcodeResult ScriptExecutor::scriptChangeAnimation() {
 	// even when the jump parser settles on a command byte instead of a frame byte.
 	if (blob._blob.size() >= 4)
 		WRITE_LE_UINT16(&blob._blob[2], targetFrameIndex);
+	// Gate and similar props only change pixels on changeAnimation; depth must follow.
+	if (!_engine->isV2() && backgroundAnimationIndex <= _engine->_backgroundAnimations.size())
+		_engine->updateBackgroundAnimationDepthMap(backgroundAnimationIndex - 1);
 	// Blob state is updated immediately but the script executor often runs several
 	// more opcodes before the next game tick. Push the new frame to the screen now
 	// so door/state changes are visible before any wait opcode yields.


Commit: 3af31fc293101936dd0b1959aeb6ec22e878da8e
    https://github.com/scummvm/scummvm/commit/3af31fc293101936dd0b1959aeb6ec22e878da8e
Author: Martin Gerhardy (martin.gerhardy at gmail.com)
Date: 2026-08-27T20:27:02+02:00

Commit Message:
MACS2: removed todo comment

Changed paths:
    engines/macs2/scriptexecutor.h


diff --git a/engines/macs2/scriptexecutor.h b/engines/macs2/scriptexecutor.h
index c082384a607..059c663b860 100644
--- a/engines/macs2/scriptexecutor.h
+++ b/engines/macs2/scriptexecutor.h
@@ -436,7 +436,7 @@ public:
 	bool _pathWalkableResult = false;
 	bool _isRepeatRun = false;
 
-	// Scene data [di+53B7h] - TODO: Confirm that we use a script variable as well as this thing
+	// chosen dialogue script index
 	int _chosenDialogueOption = 0;
 	uint16 _dialogueSpeakerObjectID = 0;
 
@@ -530,9 +530,8 @@ public:
 	// executeOpcodes (1008:db56): the opcode dispatch loop for the current script.
 	OpcodeResult executeOpcodes();
 
-	// Will execute the script and any object scripts until execution should be stopped
-	// TODO: Consider if we should let the executor also figure out where to get the
-	// first script from
+	// Execute the scene script and any object scripts until execution should stop.
+	// Fresh runs bind Scenes::_currentSceneScript at offset 0; mid-script resumes continue in place.
 	void run(bool firstRun = false);
 
 	void setScript(Common::MemoryReadStream *stream);


Commit: a1bab853f87d61601741bd19fe6b31d689fa347a
    https://github.com/scummvm/scummvm/commit/a1bab853f87d61601741bd19fe6b31d689fa347a
Author: Martin Gerhardy (martin.gerhardy at gmail.com)
Date: 2026-08-27T20:27:01+02:00

Commit Message:
MACS2: replaced magic number and extract to local var

Changed paths:
    engines/macs2/view1.cpp


diff --git a/engines/macs2/view1.cpp b/engines/macs2/view1.cpp
index c5431066d04..7ea45adb7e5 100644
--- a/engines/macs2/view1.cpp
+++ b/engines/macs2/view1.cpp
@@ -4535,7 +4535,7 @@ void View1::handleOriginalSaveLoadClick(const Common::Point &pos) {
 		}
 	}
 
-	for (int i = 1; i <= 7; i++) {
+	for (int i = 1; i < ARRAYSIZE(kLookupTable); i++) {
 		int imgIdx = kLookupTable[i] - 1; // 0-based
 		Common::Point btnPos(_saveLoadButtonRects[i - 1].left, _saveLoadButtonRects[i - 1].top);
 
@@ -4549,6 +4549,7 @@ void View1::handleOriginalSaveLoadClick(const Common::Point &pos) {
 			hasData = (!frame._data.empty() && frame._width > 0);
 		}
 
+		Script::ScriptExecutor *scriptExecutor = g_engine->_scriptExecutor;
 		bool isHit = (btnPos.x < clickX && btnPos.y < clickY &&
 					  clickX < btnPos.x + btnW && clickY < btnPos.y + btnH &&
 					  hasData &&
@@ -4566,8 +4567,7 @@ void View1::handleOriginalSaveLoadClick(const Common::Point &pos) {
 			// Process button action
 			if (i == 3) {
 				// Toggle music, reset clickedButton, redraw
-				g_engine->_scriptExecutor->_soundSystemActive =
-					!g_engine->_scriptExecutor->_soundSystemActive;
+				scriptExecutor->_soundSystemActive = !scriptExecutor->_soundSystemActive;
 				_clickedButtonIndex = 0;
 				redraw();
 			} else if (i == 4) {
@@ -4577,27 +4577,27 @@ void View1::handleOriginalSaveLoadClick(const Common::Point &pos) {
 					_saveConfirmArmed = true;
 				} else {
 					// Binary: second click arms error 0x1C and closes via button 7
-					g_engine->_scriptExecutor->setScriptError(0x1C);
+					scriptExecutor->setScriptError(0x1C);
 					_clickedButtonIndex = 7;
 				}
 			} else if (i == 6) {
 				if (!_loadConfirmArmed) {
 					_loadConfirmArmed = true;
 				} else {
-					g_engine->_scriptExecutor->setScriptError(0x1B);
+					scriptExecutor->setScriptError(0x1B);
 					_clickedButtonIndex = 7;
 				}
 			} else if (i == 7) {
 				// Binary: if music enabled AND sound active, play active music
-				if (g_engine->_scriptExecutor->_musicEnabled &&
-					g_engine->_scriptExecutor->_soundSystemActive) {
-					uint16 slot = g_engine->_scriptExecutor->_activeMusicSlot;
-					if (slot != 0 && !g_engine->_scriptExecutor->_musicSlots[slot - 1].empty() &&
-						g_engine->getMusic()->playSongData(g_engine->_scriptExecutor->_musicSlots[slot - 1])) {
+				if (scriptExecutor->_musicEnabled &&
+					scriptExecutor->_soundSystemActive) {
+					uint16 slot = scriptExecutor->_activeMusicSlot;
+					if (slot != 0 && !scriptExecutor->_musicSlots[slot - 1].empty() &&
+						g_engine->getMusic()->playSongData(scriptExecutor->_musicSlots[slot - 1])) {
 						// Original's adlibTickHandler resets g_bAdlibMasterVolume=0 (full volume).
 						// ScummVM layers user volume on top via scaledMusicVolume, so re-apply it.
-						g_engine->_scriptExecutor->_musicControlMode = 0;
-						g_engine->_scriptExecutor->_musicControlVolume = 0;
+						scriptExecutor->_musicControlMode = 0;
+						scriptExecutor->_musicControlVolume = 0;
 						g_engine->getMusic()->setVolume(g_engine->scaledMusicVolume(0));
 					}
 				}


Commit: 2cc985a4956206136f5d27d81b04b5bfaac641d3
    https://github.com/scummvm/scummvm/commit/2cc985a4956206136f5d27d81b04b5bfaac641d3
Author: Martin Gerhardy (martin.gerhardy at gmail.com)
Date: 2026-08-27T20:35:05+02:00

Commit Message:
MACS2: use Graphics::Palette class

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


diff --git a/engines/macs2/amiga_decode.cpp b/engines/macs2/amiga_decode.cpp
index 4418e268c84..48bf8984821 100644
--- a/engines/macs2/amiga_decode.cpp
+++ b/engines/macs2/amiga_decode.cpp
@@ -404,15 +404,12 @@ static bool decompressPp20ToBuffer(const byte *src, uint32 srcLen, Common::Array
 
 bool decodeAmigaMxmmSceneBackground(const byte *mxmm, uint32 mxmmSize,
 									Common::Array<byte> &outPixels,
-									byte outPaletteRgb[768],
+									Graphics::Palette &outPalette,
 									uint &outColorCount) {
 	outPixels.clear();
 	outColorCount = 0;
-	if (outPaletteRgb) {
-		for (uint i = 0; i < 768; i++)
-			outPaletteRgb[i] = 0;
-	}
-	if (!mxmm || mxmmSize < 14 || !outPaletteRgb)
+	outPalette = Graphics::Palette(Graphics::PALETTE_COUNT);
+	if (!mxmm || mxmmSize < 14)
 		return false;
 	if (READ_BE_UINT32(mxmm) != MKTAG('M', 'X', 'M', 'M'))
 		return false;
@@ -482,23 +479,17 @@ bool decodeAmigaMxmmSceneBackground(const byte *mxmm, uint32 mxmmSize,
 	// Reserve 0..31 for Amiga COLOR registers (sprites) and 32..63 for EHB.
 	byte staticPal[32][3];
 	buildPal32(0, staticPal);
-	for (uint i = 0; i < 32; i++) {
-		outPaletteRgb[i * 3 + 0] = staticPal[i][0];
-		outPaletteRgb[i * 3 + 1] = staticPal[i][1];
-		outPaletteRgb[i * 3 + 2] = staticPal[i][2];
-	}
-	for (uint i = 0; i < 32; i++) {
-		outPaletteRgb[(32 + i) * 3 + 0] = (byte)(staticPal[i][0] / 2);
-		outPaletteRgb[(32 + i) * 3 + 1] = (byte)(staticPal[i][1] / 2);
-		outPaletteRgb[(32 + i) * 3 + 2] = (byte)(staticPal[i][2] / 2);
-	}
+	for (uint i = 0; i < 32; i++)
+		outPalette.set(i, staticPal[i][0], staticPal[i][1], staticPal[i][2]);
+	for (uint i = 0; i < 32; i++)
+		outPalette.set(32 + i, (byte)(staticPal[i][0] / 2), (byte)(staticPal[i][1] / 2), (byte)(staticPal[i][2] / 2));
 	outColorCount = 64;
 
 	Common::HashMap<uint32, byte> colorToIndex;
 	for (uint i = 0; i < 64; i++) {
-		const uint32 key = ((uint32)outPaletteRgb[i * 3 + 0] << 16) |
-						   ((uint32)outPaletteRgb[i * 3 + 1] << 8) |
-						   outPaletteRgb[i * 3 + 2];
+		byte r, g, b;
+		outPalette.get(i, r, g, b);
+		const uint32 key = ((uint32)r << 16) | ((uint32)g << 8) | b;
 		if (!colorToIndex.contains(key))
 			colorToIndex[key] = (byte)i;
 	}
@@ -529,9 +520,7 @@ bool decodeAmigaMxmmSceneBackground(const byte *mxmm, uint32 mxmmSize,
 			} else if (outColorCount < 256) {
 				outIdx = (byte)outColorCount;
 				colorToIndex[key] = outIdx;
-				outPaletteRgb[outIdx * 3 + 0] = r;
-				outPaletteRgb[outIdx * 3 + 1] = g;
-				outPaletteRgb[outIdx * 3 + 2] = b;
+				outPalette.set(outIdx, r, g, b);
 				outColorCount++;
 			} else {
 				outIdx = idx < 64 ? idx : (byte)(idx & 31);
diff --git a/engines/macs2/amiga_decode.h b/engines/macs2/amiga_decode.h
index 4dbc633e7a0..bc21397725b 100644
--- a/engines/macs2/amiga_decode.h
+++ b/engines/macs2/amiga_decode.h
@@ -24,6 +24,7 @@
 
 #include "common/array.h"
 #include "common/scummsys.h"
+#include "graphics/palette.h"
 
 namespace Macs2 {
 
@@ -130,7 +131,7 @@ enum : uint16 {
  */
 bool decodeAmigaMxmmSceneBackground(const byte *mxmm, uint32 mxmmSize,
 									Common::Array<byte> &outPixels,
-									byte outPaletteRgb[768],
+									Graphics::Palette &outPalette,
 									uint &outColorCount);
 
 /**
diff --git a/engines/macs2/amiga_resources.cpp b/engines/macs2/amiga_resources.cpp
index 75b398a86ad..1c4afeab99d 100644
--- a/engines/macs2/amiga_resources.cpp
+++ b/engines/macs2/amiga_resources.cpp
@@ -27,6 +27,7 @@
 #include "common/system.h"
 #include "common/util.h"
 #include "graphics/managed_surface.h"
+#include "graphics/palette.h"
 
 #include "macs2/amiga_decode.h"
 #include "macs2/detection.h"
@@ -55,7 +56,7 @@ bool Macs2Engine::loadAmigaSceneBackground(uint32 sceneResourceId) {
 		return false;
 
 	Common::Array<byte> pixels;
-	byte paletteRgb[768];
+	Graphics::Palette paletteRgb(Graphics::PALETTE_COUNT);
 	uint colorCount = 0;
 	if (!decodeAmigaMxmmSceneBackground(mxmm.data(), size, pixels, paletteRgb, colorCount))
 		return false;
@@ -69,15 +70,12 @@ bool Macs2Engine::loadAmigaSceneBackground(uint32 sceneResourceId) {
 
 	// _palVanilla holds raw 6-bit VGA values (fade math subtracts from these).
 	// _pal is the 8-bit display palette - must be expanded via applyPaletteDarkening().
-	memset(_pal, 0, sizeof(_pal));
-	memset(_palVanilla, 0, sizeof(_palVanilla));
-	for (uint i = 0; i < colorCount && i < 256; i++) {
-		const byte r8 = paletteRgb[i * 3 + 0];
-		const byte g8 = paletteRgb[i * 3 + 1];
-		const byte b8 = paletteRgb[i * 3 + 2];
-		_palVanilla[i * 3 + 0] = (byte)((r8 * 63) / 255);
-		_palVanilla[i * 3 + 1] = (byte)((g8 * 63) / 255);
-		_palVanilla[i * 3 + 2] = (byte)((b8 * 63) / 255);
+	_pal = Graphics::Palette(Graphics::PALETTE_COUNT);
+	_palVanilla = Graphics::Palette(Graphics::PALETTE_COUNT);
+	for (uint i = 0; i < colorCount && i < Graphics::PALETTE_COUNT; i++) {
+		byte r8, g8, b8;
+		paletteRgb.get(i, r8, g8, b8);
+		_palVanilla.set(i, PALETTE_8BIT_TO_6BIT(r8), PALETTE_8BIT_TO_6BIT(g8), PALETTE_8BIT_TO_6BIT(b8));
 	}
 
 	// Keep Info UI chrome colors in the high VGA indices used by panel drawing.
@@ -97,9 +95,7 @@ bool Macs2Engine::loadAmigaSceneBackground(uint32 sceneResourceId) {
 			const uint idx = 0xF0 + i;
 			if (idx >= 256)
 				break;
-			_palVanilla[idx * 3 + 0] = r6;
-			_palVanilla[idx * 3 + 1] = g6;
-			_palVanilla[idx * 3 + 2] = b6;
+			_palVanilla.set(idx, r6, g6, b6);
 		}
 	}
 	_amigaNativePlayfieldPalette = true;
@@ -411,9 +407,7 @@ void Macs2Engine::installAmigaPortraitPalette(bool copyFromPlayfield) {
 		byte r6, g6, b6;
 		amiga12ToVga6(highSrc[i], r6, g6, b6);
 		const uint idx = 17 + i;
-		_palVanilla[idx * 3 + 0] = r6;
-		_palVanilla[idx * 3 + 1] = g6;
-		_palVanilla[idx * 3 + 2] = b6;
+		_palVanilla.set(idx, r6, g6, b6);
 	}
 }
 
@@ -438,32 +432,23 @@ void Macs2Engine::applyAmigaUiPalette() {
 	// stay a visible ramp (overwritten per-line by scene copper).
 	byte r6, g6, b6;
 	amiga12ToVga6(info.uiPaletteAmiga[0], r6, g6, b6);
-	_palVanilla[0] = 0;
-	_palVanilla[1] = 0;
-	_palVanilla[2] = 0;
+	_palVanilla.set(0, 0, 0, 0);
 	for (uint i = 1; i < 16; i++) {
 		const byte v = (byte)((i * 63) / 15);
-		_palVanilla[i * 3 + 0] = v;
-		_palVanilla[i * 3 + 1] = (byte)((v * 3) / 4);
-		_palVanilla[i * 3 + 2] = (byte)(v / 2);
+		_palVanilla.set(i, v, (byte)((v * 3) / 4), (byte)(v / 2));
 	}
 	for (uint i = 0; i < 15; i++) {
 		amiga12ToVga6(info.uiPaletteAmiga[i + 1], r6, g6, b6);
-		const uint idx = 17 + i;
-		_palVanilla[idx * 3 + 0] = r6;
-		_palVanilla[idx * 3 + 1] = g6;
-		_palVanilla[idx * 3 + 2] = b6;
+		_palVanilla.set(17 + i, r6, g6, b6);
 	}
 
 	// Amiga UI colors also live in the high indices used by panel/chrome drawing.
 	for (uint i = 0; i < 16; i++) {
 		amiga12ToVga6(info.uiPaletteAmiga[i], r6, g6, b6);
 		const uint idx = 0xF0 + i;
-		if (idx >= 256)
+		if (idx >= Graphics::PALETTE_COUNT)
 			break;
-		_palVanilla[idx * 3 + 0] = r6;
-		_palVanilla[idx * 3 + 1] = g6;
-		_palVanilla[idx * 3 + 2] = b6;
+		_palVanilla.set(idx, r6, g6, b6);
 	}
 	_amigaNativePlayfieldPalette = false;
 	installAmigaPortraitPalette(false);
@@ -484,9 +469,8 @@ void Macs2Engine::buildAmigaPanelRemapTable() {
 	// MXIN UI[0..5] = wood ramp (BBA..741). UI[6] is 0x000 - never use it for fill.
 	static const byte kUiWood[8] = {0, 1, 2, 3, 4, 5, 2, 3};
 	for (uint i = 0; i < 0x100; i++) {
-		const byte r6 = _palVanilla[i * 3 + 0];
-		const byte g6 = _palVanilla[i * 3 + 1];
-		const byte b6 = _palVanilla[i * 3 + 2];
+		byte r6, g6, b6;
+		_palVanilla.get(i, r6, g6, b6);
 		const uint r4 = (r6 * 15) / 63;
 		const uint g4 = (g6 * 15) / 63;
 		const uint b4 = (b6 * 15) / 63;
diff --git a/engines/macs2/debugtools.cpp b/engines/macs2/debugtools.cpp
index 0e37a09cf7b..e000b31cd04 100644
--- a/engines/macs2/debugtools.cpp
+++ b/engines/macs2/debugtools.cpp
@@ -890,7 +890,7 @@ static void showAnimViewerWindow() {
 						}
 					}
 
-					ImTextureID texId = (ImTextureID)(intptr_t)g_system->getImGuiTexture(*animViewSurface.surfacePtr(), g_engine->_pal, 256);
+					ImTextureID texId = (ImTextureID)(intptr_t)g_system->getImGuiTexture(*animViewSurface.surfacePtr(), g_engine->_pal.data(), g_engine->_pal.size());
 					if (texId) {
 						float scale = MIN(128.0f / (float)fi.width, 128.0f / (float)fi.height);
 						if (scale > 3.0f)
@@ -1399,7 +1399,7 @@ static void showSceneMapsWindow() {
 		}
 
 		if (surface && surface->w > 0 && surface->h > 0) {
-			ImTextureID texId = (ImTextureID)(intptr_t)g_system->getImGuiTexture(*surface->surfacePtr(), g_engine->_pal, 256);
+			ImTextureID texId = (ImTextureID)(intptr_t)g_system->getImGuiTexture(*surface->surfacePtr(), g_engine->_pal.data(), g_engine->_pal.size());
 			if (texId) {
 				ImVec2 avail = ImGui::GetContentRegionAvail();
 				float scale = MIN(avail.x / (float)kScreenWidth, avail.y / (float)kGameHeight);
@@ -1646,7 +1646,7 @@ static void showImageResourcesWindow() {
 				x += f._width;
 			}
 
-			ImTextureID texId = (ImTextureID)(intptr_t)g_system->getImGuiTexture(*imgSurface.surfacePtr(), g_engine->_pal, 256);
+			ImTextureID texId = (ImTextureID)(intptr_t)g_system->getImGuiTexture(*imgSurface.surfacePtr(), g_engine->_pal.data(), g_engine->_pal.size());
 			if (texId) {
 				ImVec2 avail = ImGui::GetContentRegionAvail();
 				float scale = MIN(avail.x / (float)kScreenWidth, avail.y / (float)totalH);
diff --git a/engines/macs2/macs2.cpp b/engines/macs2/macs2.cpp
index 1f1f7988cef..ef068af1524 100644
--- a/engines/macs2/macs2.cpp
+++ b/engines/macs2/macs2.cpp
@@ -274,8 +274,8 @@ void Macs2Engine::loadResourceFileV2() {
 		return;
 
 	_fileStream->seek(_mcsDirectoryOffset + 0x3000, SEEK_SET);
-	_fileStream->read(_palVanilla, 0x300);
-	memcpy(_pal, _palVanilla, 0x300);
+	readPalette(_fileStream, _palVanilla);
+	_pal = _palVanilla;
 
 	for (int i = 0; i < ARRAYSIZE(_hudTextRecolor); i++) {
 		_hudTextRecolor[i] = _fileStream->readUint16LE();
@@ -963,20 +963,10 @@ bool Macs2Engine::loadSceneGraphicsV1(uint32 sceneIndex) {
 		}
 	}
 
-	// We load the palette right afterwards - 0x300 is exactly 3 * 256d
-	Common::Array<uint8> palette;
-	palette.resize(0x300);
-	_fileStream->read(palette.data(), 0x300);
-
-	// TODO: Copy-pasted code here
-	// Make a copy that will not be color corrected, for fading
-	memcpy(_palVanilla, palette.data(), 256 * 3);
-	memcpy(_pal, palette.data(), 0x300);
-
-	// Adjust the palette
-	for (int i = 0; i < 256 * 3; i++) {
-		_pal[i] = (_pal[i] * 259 + 33) >> 6;
-	}
+	// Palette is 0x300 bytes (256 RGB triples). Keep an uncorrected copy for fades.
+	readPalette(_fileStream, _palVanilla);
+	_pal = _palVanilla;
+	expandPalette6To8(_pal);
 
 	// changeScene @ 1008:2574: 0x100-byte panel remap table (scene+0x1006 area, NOT shading table)
 	if (_panelRemapTable.size() != 0x100)
@@ -1091,10 +1081,9 @@ bool Macs2Engine::loadSceneGraphicsV2(uint32 sceneIndex) {
 	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;
+	readPalette(stream, _palVanilla);
+	_pal = _palVanilla;
+	expandPalette6To8(_pal);
 
 	if (_panelRemapTable.size() != 0x100)
 		_panelRemapTable.resize(0x100);
@@ -1680,7 +1669,7 @@ bool Macs2Engine::loadDeltaAnimResource(uint8 resourceIndex, uint16 executingObj
 	Common::Array<DeltaSfxEvent> savedSfx = Common::move(_deltaAnim.sfxEvents);
 	clearDeltaAnim();
 	_deltaAnim.sfxEvents = Common::move(savedSfx);
-	_fileStream->read(_deltaAnim.palette, 0x300);
+	readPalette(_fileStream, _deltaAnim.palette);
 	_deltaAnim.frames.resize(numFrames);
 	_deltaAnim.frameCount = numFrames;
 	_deltaAnim.loaded = true;
@@ -1799,11 +1788,10 @@ bool Macs2Engine::startDeltaPlayback(uint16 startFrame, uint16 endFrame, uint16
 	_deltaAnim.playing = true;
 	_deltaAnim.applyPaletteOnStart = applyPalette;
 	if (applyPalette || _deltaAnim.currentFrame == 0) {
-		memcpy(_palVanilla, _deltaAnim.palette, 0x300);
-		memcpy(_pal, _deltaAnim.palette, 0x300);
-		for (int i = 0; i < 256 * 3; i++)
-			_pal[i] = (_pal[i] * 259 + 33) >> 6;
-		g_system->getPaletteManager()->setPalette(_pal, 0, 256);
+		_palVanilla = _deltaAnim.palette;
+		_pal = _deltaAnim.palette;
+		expandPalette6To8(_pal);
+		g_system->getPaletteManager()->setPalette(_pal);
 	}
 	const uint16 displayFrame = _deltaAnim.currentFrame;
 	playDeltaFrameSfx(displayFrame);
@@ -3646,6 +3634,24 @@ Audio::Timestamp MacsAudioStream::getLength() const {
 	return Audio::Timestamp(0, _data.size(), getRate());
 }
 
+void Macs2Engine::readPalette(Common::SeekableReadStream *stream, Graphics::Palette &dest) {
+	byte buf[Graphics::PALETTE_SIZE];
+	stream->read(buf, Graphics::PALETTE_SIZE);
+	if (dest.size() != Graphics::PALETTE_COUNT)
+		dest.resize(Graphics::PALETTE_COUNT, false);
+	dest.set(buf, 0, Graphics::PALETTE_COUNT);
+}
+
+void Macs2Engine::expandPalette6To8(Graphics::Palette &pal) {
+	for (uint i = 0; i < pal.size(); i++) {
+		byte r, g, b;
+		pal.get(i, r, g, b);
+		pal.set(i, (byte)((r * 259 + 33) >> 6),
+				(byte)((g * 259 + 33) >> 6),
+				(byte)((b * 259 + 33) >> 6));
+	}
+}
+
 void Macs2Engine::applyPaletteDarkening() {
 	// Binary: sceneData+0x5203 == 1 means copy source palette as-is to display;
 	// otherwise darken: display[i] = source[i] * (100 - darkenPercent) / 100.
@@ -3655,9 +3661,12 @@ void Macs2Engine::applyPaletteDarkening() {
 	if (darkenPercent > 100)
 		darkenPercent = 100;
 	uint16 brightnessFactor = 100 - darkenPercent;
-	for (int i = 0; i < 256 * 3; i++) {
-		uint8 darkened = (_palVanilla[i] * brightnessFactor) / 100;
-		_pal[i] = (darkened * 259 + 33) >> 6;
+	for (uint i = 0; i < Graphics::PALETTE_COUNT; i++) {
+		byte r, g, b;
+		_palVanilla.get(i, r, g, b);
+		_pal.set(i, (byte)((r * brightnessFactor / 100 * 259 + 33) >> 6),
+				 (byte)((g * brightnessFactor / 100 * 259 + 33) >> 6),
+				 (byte)((b * brightnessFactor / 100 * 259 + 33) >> 6));
 	}
 }
 
@@ -3685,37 +3694,41 @@ void Macs2Engine::applyScenePaletteEffect() {
 		selected[minIndex] = true;
 	}
 
-	byte refPalette[256 * 3];
-	memset(refPalette, 0, sizeof(refPalette));
+	Graphics::Palette refPalette(Graphics::PALETTE_COUNT);
 	int refSlot = 0x10;
 	for (int i = 0; i <= 0xBF; i++) {
 		if (selected[i]) {
-			refPalette[refSlot * 3 + 0] = _palVanilla[i * 3 + 0];
-			refPalette[refSlot * 3 + 1] = _palVanilla[i * 3 + 1];
-			refPalette[refSlot * 3 + 2] = _palVanilla[i * 3 + 2];
+			byte r, g, b;
+			_palVanilla.get(i, r, g, b);
+			refPalette.set(refSlot, r, g, b);
 			refSlot++;
 		}
 	}
 	for (int i = 0xC0; i <= 0xFF; i++) {
-		refPalette[i * 3 + 0] = _palVanilla[i * 3 + 0];
-		refPalette[i * 3 + 1] = _palVanilla[i * 3 + 1];
-		refPalette[i * 3 + 2] = _palVanilla[i * 3 + 2];
+		byte r, g, b;
+		_palVanilla.get(i, r, g, b);
+		refPalette.set(i, r, g, b);
 	}
 
 	uint8 remap[256];
 	for (int paletteIndex = 0; paletteIndex < 256; paletteIndex++) {
-		const byte *srcRgb = &_palVanilla[paletteIndex * 3];
+		byte srcR, srcG, srcB;
+		_palVanilla.get(paletteIndex, srcR, srcG, srcB);
 		uint32 bestDistance = 0x7FFF;
 		uint8 bestIndex = 0x10;
 		for (int candidate = 0x10; candidate <= 0xFF; candidate++) {
-			const byte *candidateRgb = &refPalette[candidate * 3];
-			uint32 distance = 0;
-			for (int channel = 0; channel < 3; channel++) {
-				int diff = (int)srcRgb[channel] - (int)candidateRgb[channel];
-				if (diff < 0)
-					diff = -diff;
-				distance += (uint32)diff;
-			}
+			byte candR, candG, candB;
+			refPalette.get(candidate, candR, candG, candB);
+			int dR = (int)srcR - (int)candR;
+			int dG = (int)srcG - (int)candG;
+			int dB = (int)srcB - (int)candB;
+			if (dR < 0)
+				dR = -dR;
+			if (dG < 0)
+				dG = -dG;
+			if (dB < 0)
+				dB = -dB;
+			const uint32 distance = (uint32)(dR + dG + dB);
 			if (distance < bestDistance) {
 				bestDistance = distance;
 				bestIndex = (uint8)candidate;
@@ -3752,14 +3765,13 @@ void Macs2Engine::applyScenePaletteEffect() {
 		}
 	}
 
-	byte remappedVanilla[256 * 3];
+	Graphics::Palette remappedVanilla(Graphics::PALETTE_COUNT);
 	for (int i = 0; i < 256; i++) {
-		const int src = remap[i];
-		remappedVanilla[i * 3 + 0] = refPalette[src * 3 + 0];
-		remappedVanilla[i * 3 + 1] = refPalette[src * 3 + 1];
-		remappedVanilla[i * 3 + 2] = refPalette[src * 3 + 2];
+		byte r, g, b;
+		refPalette.get(remap[i], r, g, b);
+		remappedVanilla.set(i, r, g, b);
 	}
-	memcpy(_palVanilla, remappedVanilla, 256 * 3);
+	_palVanilla = remappedVanilla;
 	applyPaletteDarkening();
 
 	View1 *view = (View1 *)findView("View1");
@@ -3798,9 +3810,9 @@ void Macs2Engine::updateBackgroundAnimationPalette() {
 
 	if (mapActive) {
 		// Preserve entries 0..15 (UI), update 16..255.
-		g_system->getPaletteManager()->setPalette(_pal + 16 * 3, 16, 240);
+		g_system->getPaletteManager()->setPalette(_pal.data() + 16 * 3, 16, 240);
 	} else {
-		g_system->getPaletteManager()->setPalette(_pal, 0, 256);
+		g_system->getPaletteManager()->setPalette(_pal);
 	}
 }
 
diff --git a/engines/macs2/macs2.h b/engines/macs2/macs2.h
index b6a983a4330..2cf882fe2d8 100644
--- a/engines/macs2/macs2.h
+++ b/engines/macs2/macs2.h
@@ -38,6 +38,7 @@
 #include "common/text-to-speech.h"
 #include "common/util.h"
 #include "engines/engine.h"
+#include "graphics/palette.h"
 #include "macs2/amiga_archive.h"
 #include "macs2/events.h"
 #include "macs2/macs2_constants.h"
@@ -371,9 +372,9 @@ public:
 	// Only scenes with shadow regions have non-zero data.
 	Graphics::ManagedSurface _shadowMap;
 
-	// TODO: use Graphics::Palette for this
-	byte _pal[256 * 3] = {0};
-	byte _palVanilla[256 * 3] = {0};
+	// _palVanilla: raw 6-bit VGA source. _pal: 8-bit display (darkened + expanded).
+	Graphics::Palette _pal{Graphics::PALETTE_COUNT};
+	Graphics::Palette _palVanilla{Graphics::PALETTE_COUNT};
 
 	Common::Array<Common::String> _debugOutput;
 	Common::Array<Common::String> _textLog;
@@ -476,7 +477,7 @@ public:
 		uint16 clipMiY = 0;
 		uint16 clipMaX = 0;
 		uint16 clipMaY = 0;
-		byte palette[0x300] = {};
+		Graphics::Palette palette{Graphics::PALETTE_COUNT};
 		bool applyPaletteOnStart = false;
 		Common::Array<DeltaFrame> frames;
 		Common::Array<DeltaSfxEvent> sfxEvents;
@@ -493,7 +494,7 @@ public:
 			clipMiX = clipMiY = 0;
 			clipMaX = (uint16)MAX(0, screenW - 1);
 			clipMaY = (uint16)MAX(0, screenH - 1);
-			memset(palette, 0, sizeof(palette));
+			palette = Graphics::Palette(Graphics::PALETTE_COUNT);
 		}
 	};
 	DeltaAnimState _deltaAnim;
@@ -598,6 +599,8 @@ public:
 	uint16 _paletteDarkenPercent;
 
 	void applyPaletteDarkening();
+	void readPalette(Common::SeekableReadStream *stream, Graphics::Palette &dest);
+	void expandPalette6To8(Graphics::Palette &pal);
 	// Palette quantization for g_wHelpButtonDisabled path (1000:103e).
 	// Histograms scene pixels, keeps 16 rarest colors (0..0xBF) plus UI range
 	// 0xC0..0xFF, remaps background + bg-anim blobs + palette via Manhattan RGB.
diff --git a/engines/macs2/scriptexecutor.cpp b/engines/macs2/scriptexecutor.cpp
index 8d39571a767..741b9afb92b 100644
--- a/engines/macs2/scriptexecutor.cpp
+++ b/engines/macs2/scriptexecutor.cpp
@@ -29,6 +29,7 @@
 #include "common/system.h"
 #include "common/util.h"
 #include "engines/enhancements.h"
+#include "graphics/palette.h"
 #include "macs2/amiga_archive.h"
 #include "macs2/amiga_decode.h"
 #include "macs2/debugtools.h"
@@ -1241,10 +1242,10 @@ OpcodeResult Script::ScriptExecutor::scriptChangeScene() {
 	_engine->setCursorMode(MouseMode::Disabled);
 
 	View1 *currentView = (View1 *)_engine->findView("View1");
-	byte savedPalette[768];
+	Graphics::Palette savedPalette(Graphics::PALETTE_COUNT);
 	if (currentView != nullptr && transitionMode == 0 && transitionSpeed != 0 &&
 		!currentView->isHelpButtonDisabled()) {
-		memcpy(savedPalette, g_engine->_palVanilla, sizeof(savedPalette));
+		savedPalette = g_engine->_palVanilla;
 	}
 	g_engine->changeScene(newSceneID, false);
 	// Binary (1008:ad6e): fade old palette to black after scene load (mode 0),
diff --git a/engines/macs2/view1.cpp b/engines/macs2/view1.cpp
index 7ea45adb7e5..38a0607f30e 100644
--- a/engines/macs2/view1.cpp
+++ b/engines/macs2/view1.cpp
@@ -28,6 +28,7 @@
 #include "engines/enhancements.h"
 #include "engines/util.h"
 #include "graphics/cursorman.h"
+#include "graphics/palette.h"
 #include "graphics/paletteman.h"
 #include "macs2/debugtools.h"
 #include "macs2/detection.h"
@@ -97,7 +98,7 @@ bool buildClippedEraseRect(int32 left, int32 top, uint16 width, uint16 height,
 	return out.isValidRect() && !out.isEmpty();
 }
 
-void buildFadedPalette(byte *colors, const byte *sourcePalette, int fadeValue) {
+void buildFadedPalette(Graphics::Palette &colors, const Graphics::Palette &sourcePalette, int fadeValue) {
 	// Original fadePaletteToBlack/FromBlack: subtracts fadeValue from raw 6-bit VGA
 	// palette values (0-63), clamping to 0. Then scales to 8-bit for ScummVM.
 	// Apply palette darkening if active (scenes with _scenePaletteMode != 1).
@@ -105,12 +106,21 @@ void buildFadedPalette(byte *colors, const byte *sourcePalette, int fadeValue) {
 	if (darkenPercent > 100)
 		darkenPercent = 100;
 	uint16 brightnessFactor = 100 - darkenPercent;
-	for (uint i = 0; i < 256 * 3; ++i) {
-		int raw = (sourcePalette[i] * brightnessFactor) / 100; // darken first
-		int faded = raw - fadeValue;
-		if (faded < 0)
-			faded = 0;
-		colors[i] = (faded * 259 + 33) >> 6; // 6-bit to 8-bit
+	for (uint i = 0; i < Graphics::PALETTE_COUNT; ++i) {
+		byte r, g, b;
+		sourcePalette.get(i, r, g, b);
+		int fadedR = (int)(r * brightnessFactor) / 100 - fadeValue;
+		int fadedG = (int)(g * brightnessFactor) / 100 - fadeValue;
+		int fadedB = (int)(b * brightnessFactor) / 100 - fadeValue;
+		if (fadedR < 0)
+			fadedR = 0;
+		if (fadedG < 0)
+			fadedG = 0;
+		if (fadedB < 0)
+			fadedB = 0;
+		colors.set(i, (byte)((fadedR * 259 + 33) >> 6),
+				   (byte)((fadedG * 259 + 33) >> 6),
+				   (byte)((fadedB * 259 + 33) >> 6));
 	}
 }
 
@@ -209,22 +219,22 @@ View1::~View1() {
 	}
 }
 
-void View1::applyPaletteWithFade(const byte *sourcePalette, int fadeValue) {
-	byte colors[256 * 3];
+void View1::applyPaletteWithFade(const Graphics::Palette &sourcePalette, int fadeValue) {
+	Graphics::Palette colors(Graphics::PALETTE_COUNT);
 	buildFadedPalette(colors, sourcePalette, fadeValue);
 	setViewPaletteSafely(colors);
 }
 
-void View1::setViewPaletteSafely(const byte *colors) {
+void View1::setViewPaletteSafely(const Graphics::Palette &colors) {
 	const bool shouldTouchCursor = _cursorSuppressedForFade;
 	const bool cursorWasVisible = shouldTouchCursor && CursorMan.isVisible();
 	if (cursorWasVisible)
 		CursorMan.showMouse(false);
 
 	if (shouldTouchCursor)
-		updateCursor(colors);
+		updateCursor(&colors);
 
-	g_system->getPaletteManager()->setPalette(colors, 0, 256);
+	g_system->getPaletteManager()->setPalette(colors);
 
 	if (cursorWasVisible)
 		CursorMan.showMouse(true);
@@ -233,7 +243,7 @@ void View1::setViewPaletteSafely(const byte *colors) {
 void View1::restoreUiPaletteEntries() {
 	// Binary setPaletteRange(0xF0, 0x10, palette+0x30) after applyScenePaletteEffect:
 	// push VGA indices 0xF0..0xFF from palette color slots 0x10..0x1F.
-	g_system->getPaletteManager()->setPalette(g_engine->_pal + 16 * 3, 0xF0, 16);
+	g_system->getPaletteManager()->setPalette(g_engine->_pal.data() + 16 * 3, 0xF0, 16);
 }
 
 void View1::openInventory(GameObject *newInventorySource) {
@@ -437,7 +447,7 @@ void View1::buildSortedObjectList(int low, int high) const {
 		buildSortedObjectList(i, high);
 }
 
-void View1::updateCursor(const byte *palette) {
+void View1::updateCursor(const Graphics::Palette *palette) {
 	CursorMan.showMouse(true);
 
 	// Original indexes cursor array as: base + mode * 16 - 16, i.e. 0-based index = mode - 1.
@@ -477,6 +487,7 @@ void View1::updateCursor(const byte *palette) {
 	Common::Array<uint32> rgbaCursor;
 	rgbaCursor.resize(width * height);
 
+	const Graphics::Palette &activePalette = palette ? *palette : g_engine->_pal;
 	for (uint i = 0; i < rgbaCursor.size(); ++i) {
 		const byte colorIndex = cursorData[i];
 		if (colorIndex == 0) {
@@ -484,9 +495,12 @@ void View1::updateCursor(const byte *palette) {
 			continue;
 		}
 
-		const byte *activePalette = palette ? palette : g_engine->_pal;
-		const byte *paletteEntry = &activePalette[colorIndex * 3];
-		rgbaCursor[i] = rgbaCursorFormat.RGBToColor(paletteEntry[0], paletteEntry[1], paletteEntry[2]);
+		byte r, g, b;
+		if (colorIndex < activePalette.size())
+			activePalette.get(colorIndex, r, g, b);
+		else
+			r = g = b = 0;
+		rgbaCursor[i] = rgbaCursorFormat.RGBToColor(r, g, b);
 	}
 
 	int hotX = width >> 1;
@@ -504,8 +518,8 @@ void View1::updateCursor(const byte *palette) {
 	// baked-in palette colors, so the cursor palette content is irrelevant -
 	// it just needs to exist to prevent the backend's setPalette() from
 	// triggering blitCursor() which can corrupt the RLE-accelerated surface.
-	byte dummyPalette[256 * 3] = {};
-	CursorMan.replaceCursorPalette(dummyPalette, 0, 256);
+	Graphics::Palette dummyPalette(Graphics::PALETTE_COUNT);
+	CursorMan.replaceCursorPalette(dummyPalette.data(), 0, dummyPalette.size());
 }
 
 AnimFrame *View1::getInventoryIcon(GameObject *gameObject) {
@@ -946,12 +960,12 @@ void View1::enterMapMode() {
 	if (helpOffset == 0 || helpOffset >= (uint32)g_engine->_fileStream->size()) {
 		return;
 	}
-	memcpy(_savedPalVanilla, g_engine->_palVanilla, 256 * 3);
+	_savedPalVanilla = g_engine->_palVanilla;
 	_savedDepthMap.copyFrom(g_engine->_depthMap);
 	startFadeToBlack(8);
 	Graphics::ManagedSurface mapBg = g_engine->readRLEImage(helpOffset, g_engine->_fileStream);
 	_backgroundSurface.copyFrom(mapBg);
-	g_engine->_fileStream->read(g_engine->_palVanilla, 0x300);
+	g_engine->readPalette(g_engine->_fileStream, g_engine->_palVanilla);
 	g_engine->applyPaletteDarkening();
 	Graphics::ManagedSurface mapDepth = g_engine->readRLEImage(g_engine->_fileStream->pos(), g_engine->_fileStream);
 	g_engine->_depthMap.blitFrom(mapDepth);
@@ -1149,12 +1163,10 @@ void View1::startFading(uint16 speed) {
 	startFadingWithSpeed(speed);
 }
 
-void View1::fadePaletteToBlack(uint16 speed, const byte *sourcePalette) {
+void View1::fadePaletteToBlack(uint16 speed, const Graphics::Palette &sourcePalette) {
 	// Blocking fade to black matching DOS fadePaletteToBlack (1010:00ba).
 	if (speed == 0)
 		speed = 4;
-	if (sourcePalette == nullptr)
-		sourcePalette = g_engine->_palVanilla;
 	beginFadeCursorSuppression();
 
 	// Ensure current frame is on screen before fading
@@ -1166,7 +1178,7 @@ void View1::fadePaletteToBlack(uint16 speed, const byte *sourcePalette) {
 	while (fadeValue <= 0x40 && !g_system->getEventManager()->shouldQuit()) {
 		uint32 frameStart = g_system->getMillis();
 
-		byte colors[256 * 3];
+		Graphics::Palette colors(Graphics::PALETTE_COUNT);
 		buildFadedPalette(colors, sourcePalette, fadeValue);
 		setViewPaletteSafely(colors);
 		g_system->copyRectToScreen((const byte *)g_events->getScreen()->getPixels(),
@@ -1188,8 +1200,7 @@ void View1::fadePaletteToBlack(uint16 speed, const byte *sourcePalette) {
 	}
 
 	// Final: set all black
-	byte colors[256 * 3];
-	memset(colors, 0, sizeof(colors));
+	Graphics::Palette colors(Graphics::PALETTE_COUNT);
 	setViewPaletteSafely(colors);
 	g_system->updateScreen();
 
@@ -1205,8 +1216,7 @@ void View1::startFadeToBlack(uint16 speed) {
 void View1::instantSceneCut() {
 	// Binary scriptChangeScene mode 1 (1008:ad6e): clearScreen + setPaletteRange(0x100, 0).
 	// applyScenePaletteEffect is only used on the help-disabled path, not here.
-	byte blackPal[256 * 3];
-	memset(blackPal, 0, sizeof(blackPal));
+	Graphics::Palette blackPal(Graphics::PALETTE_COUNT);
 	setViewPaletteSafely(blackPal);
 	Graphics::ManagedSurface s = getSurface();
 	s.fillRect(Common::Rect(s.w, s.h), 0);
@@ -1233,9 +1243,8 @@ void View1::startFadingWithSpeed(uint16 speed) {
 	beginFadeCursorSuppression();
 
 	// Set palette to black before blitting new scene pixels
-	byte blackPal[256 * 3];
-	memset(blackPal, 0, sizeof(blackPal));
-	g_system->getPaletteManager()->setPalette(blackPal, 0, 256);
+	Graphics::Palette blackPal(Graphics::PALETTE_COUNT);
+	g_system->getPaletteManager()->setPalette(blackPal);
 
 	// Draw the new scene to the screen surface (invisible because palette is black)
 	Graphics::ManagedSurface s = getSurface();
@@ -1304,13 +1313,13 @@ void View1::beginFadeCursorSuppression() {
 	_cursorSuppressedForFade = true;
 }
 
-void View1::endFadeCursorSuppression(const byte *palette) {
+void View1::endFadeCursorSuppression(const Graphics::Palette &palette) {
 	if (!_cursorSuppressedForFade) {
 		return;
 	}
 
 	_cursorSuppressedForFade = false;
-	updateCursor(palette);
+	updateCursor(&palette);
 	if (_cursorWasVisibleBeforeFade) {
 		CursorMan.showMouse(true);
 	}
@@ -1318,8 +1327,6 @@ void View1::endFadeCursorSuppression(const byte *palette) {
 }
 
 bool View1::msgFocus(const FocusMessage &msg) {
-	// Common::fill(&_pal[0], &_pal[256 * 3], 0);
-	//  _offset = 128;
 	return true;
 }
 
@@ -1669,7 +1676,7 @@ bool View1::handleHelpClick(const MouseDownMessage &msg) {
 				Graphics::ManagedSurface preview = g_engine->readRLEImage(subSceneOffset, g_engine->_fileStream);
 				_backgroundSurface.copyFrom(preview);
 				// Read sub-scene palette
-				g_engine->_fileStream->read(g_engine->_palVanilla, 0x300);
+				g_engine->readPalette(g_engine->_fileStream, g_engine->_palVanilla);
 				g_engine->applyPaletteDarkening();
 				// Read sub-scene depth map
 				Graphics::ManagedSurface subDepth = g_engine->readRLEImage(g_engine->_fileStream->pos(), g_engine->_fileStream);
@@ -1684,7 +1691,7 @@ bool View1::handleHelpClick(const MouseDownMessage &msg) {
 			updateCursor();
 			startFadeToBlack(8);
 			_backgroundSurface.copyFrom(g_engine->_sceneBackground);
-			memcpy(g_engine->_palVanilla, _savedPalVanilla, 256 * 3);
+			g_engine->_palVanilla = _savedPalVanilla;
 			g_engine->applyPaletteDarkening();
 			g_engine->_depthMap.copyFrom(_savedDepthMap);
 			startFading(8);
diff --git a/engines/macs2/view1.h b/engines/macs2/view1.h
index b1ee2f18784..b2aa1f49f68 100644
--- a/engines/macs2/view1.h
+++ b/engines/macs2/view1.h
@@ -204,7 +204,7 @@ private:
 	ActionBar *_actionBar = nullptr;
 
 	// Saved scene visuals for help screen restore (avoids changeScene on exit)
-	byte _savedPalVanilla[256 * 3] = {0};
+	Graphics::Palette _savedPalVanilla{Graphics::PALETTE_COUNT};
 	Graphics::ManagedSurface _savedDepthMap;
 	int _offset = 0; // TODO: palette cycling?
 
@@ -261,7 +261,7 @@ private:
 	void drawCurrentSpeaker(Graphics::ManagedSurface &s);
 
 	void beginFadeCursorSuppression();
-	void endFadeCursorSuppression(const byte *palette);
+	void endFadeCursorSuppression(const Graphics::Palette &palette);
 
 	bool handleInventoryClick(const MouseDownMessage &msg);
 	bool handleContainerInventoryClick(const MouseDownMessage &msg);
@@ -344,8 +344,8 @@ private:
 	void drawAllCharacters(Graphics::ManagedSurface *surface = nullptr, bool fullUpdate = true);
 
 	int findInventoryItem(const GameObject *item);
-	void setViewPaletteSafely(const byte *colors);
-	void applyPaletteWithFade(const byte *sourcePalette, int fadeValue);
+	void setViewPaletteSafely(const Graphics::Palette &colors);
+	void applyPaletteWithFade(const Graphics::Palette &sourcePalette, int fadeValue);
 
 public:
 	View1();
@@ -372,8 +372,6 @@ public:
 
 	AnimFrame *getInventoryIcon(GameObject *gameObject);
 
-	// TODO: use Graphics::Palette
-	byte _pal[256 * 3] = {0};
 	bool _paletteDirty = true;
 
 	// Background animation timing from gameTick (1008:e556).
@@ -502,7 +500,7 @@ public:
 
 	// Updates the cursor from the mode set in the engine - TODO: Clean up, this should not
 	// be so separated
-	void updateCursor(const byte *palette = nullptr);
+	void updateCursor(const Graphics::Palette *palette = nullptr);
 
 	bool msgFocus(const FocusMessage &msg) override;
 	bool msgKeypress(const KeypressMessage &msg) override;
@@ -534,7 +532,7 @@ public:
 	bool handleDialogueChoiceClick(int clickY, int clickX);
 
 	void startFading(uint16 speed = 4);
-	void fadePaletteToBlack(uint16 speed, const byte *sourcePalette);
+	void fadePaletteToBlack(uint16 speed, const Graphics::Palette &sourcePalette);
 	void startFadeToBlack(uint16 speed = 4);
 	void startFadingWithSpeed(uint16 speed);
 	// Mode-1 scene transition: clearScreen + full palette (1008:ad6e local_6==1).




More information about the Scummvm-git-logs mailing list