[Scummvm-git-logs] scummvm master -> 223a437ce3afd99495314e403f463513f0cc9991

dreammaster noreply at scummvm.org
Sat Aug 8 06:27:37 UTC 2026


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

Summary:
f1f1d208ee MADS: NEBULAR: Fix Macintosh GMM save/load
72a927f1f2 MADS: Hook Macintosh original menus gameoption
dcf9766f39 MADS: NEBULAR: Add original Macintosh menu framework
ef6f275a44 MADS: NEBULAR: Present original Macintosh composition
223a437ce3 MADS: NEBULAR: Implement Macintosh menu actions


Commit: f1f1d208ee5065ba4e490813fdaed0fd810fdefe
    https://github.com/scummvm/scummvm/commit/f1f1d208ee5065ba4e490813fdaed0fd810fdefe
Author: fusefib (fibofuse at gmail.com)
Date: 2026-08-08T16:27:31+10:00

Commit Message:
MADS: NEBULAR: Fix Macintosh GMM save/load

Changed paths:
    engines/mads/nebular/mac_menus.cpp


diff --git a/engines/mads/nebular/mac_menus.cpp b/engines/mads/nebular/mac_menus.cpp
index 14b6138c90e..a697d8e5351 100644
--- a/engines/mads/nebular/mac_menus.cpp
+++ b/engines/mads/nebular/mac_menus.cpp
@@ -65,13 +65,13 @@ void selectMacintoshDifficulty() {
 
 void macintoshGameMenu() {
 	g_engine->flushKeys();
+	const int requestedMenu = kernel.activate_menu;
+	kernel.activate_menu = GAME_NO_MENU;
 
-	if (kernel.activate_menu == GAME_DIFFICULTY_MENU)
+	if (requestedMenu == GAME_DIFFICULTY_MENU)
 		selectMacintoshDifficulty();
-	else if (kernel.activate_menu != GAME_NO_MENU)
+	else if (requestedMenu != GAME_NO_MENU)
 		g_engine->openMainMenuDialog();
-
-	kernel.activate_menu = GAME_NO_MENU;
 }
 
 } // namespace RexNebular


Commit: 72a927f1f297e63f414e7fece5292ccdd9173898
    https://github.com/scummvm/scummvm/commit/72a927f1f297e63f414e7fece5292ccdd9173898
Author: fusefib (fibofuse at gmail.com)
Date: 2026-08-08T16:27:31+10:00

Commit Message:
MADS: Hook Macintosh original menus gameoption

Changed paths:
    engines/mads/detection.h
    engines/mads/detection_tables.h
    engines/mads/metaengine.cpp


diff --git a/engines/mads/detection.h b/engines/mads/detection.h
index fe2b406b3a6..20b72658739 100644
--- a/engines/mads/detection.h
+++ b/engines/mads/detection.h
@@ -60,6 +60,8 @@ struct MADSGameDescription {
 
 #define GAMEOPTION_ORIGINAL_SAVELOAD   GUIO_GAMEOPTIONS7
 
+#define GAMEOPTION_ORIGINAL_MAC_MENUS  GUIO_GAMEOPTIONS8
+
 } // namespace MADS
 
 #endif
diff --git a/engines/mads/detection_tables.h b/engines/mads/detection_tables.h
index f801b6cb1f5..24260a79829 100644
--- a/engines/mads/detection_tables.h
+++ b/engines/mads/detection_tables.h
@@ -160,9 +160,9 @@ static const MADSGameDescription gameDescriptions[] = {
 			Common::kPlatformMacintosh,
 			ADGF_UNSTABLE,
 #ifdef USE_TTS
-			GUIO10(GUIO_NOMIDI, GUIO_NOSPEECH, GUIO_NOASPECT, GAMEOPTION_EASY_MOUSE, GAMEOPTION_ANIMATED_INVENTORY, GAMEOPTION_ANIMATED_INTERFACE, GAMEOPTION_NAUGHTY_MODE, GAMEOPTION_TTS_NARRATOR, GAMEOPTION_COPY_PROTECTION, GAMEOPTION_ORIGINAL_SAVELOAD)
+			GUIO10(GUIO_NOMIDI, GUIO_NOSPEECH, GUIO_NOASPECT, GAMEOPTION_EASY_MOUSE, GAMEOPTION_ANIMATED_INVENTORY, GAMEOPTION_ANIMATED_INTERFACE, GAMEOPTION_NAUGHTY_MODE, GAMEOPTION_TTS_NARRATOR, GAMEOPTION_COPY_PROTECTION, GAMEOPTION_ORIGINAL_MAC_MENUS)
 #else
-			GUIO9(GUIO_NOMIDI, GUIO_NOSPEECH, GUIO_NOASPECT, GAMEOPTION_EASY_MOUSE, GAMEOPTION_ANIMATED_INVENTORY, GAMEOPTION_ANIMATED_INTERFACE, GAMEOPTION_NAUGHTY_MODE, GAMEOPTION_COPY_PROTECTION, GAMEOPTION_ORIGINAL_SAVELOAD)
+			GUIO9(GUIO_NOMIDI, GUIO_NOSPEECH, GUIO_NOASPECT, GAMEOPTION_EASY_MOUSE, GAMEOPTION_ANIMATED_INVENTORY, GAMEOPTION_ANIMATED_INTERFACE, GAMEOPTION_NAUGHTY_MODE, GAMEOPTION_COPY_PROTECTION, GAMEOPTION_ORIGINAL_MAC_MENUS)
 #endif
 		},
 		GType_RexNebular,
diff --git a/engines/mads/metaengine.cpp b/engines/mads/metaengine.cpp
index f9427f7bc3b..e35413c5035 100644
--- a/engines/mads/metaengine.cpp
+++ b/engines/mads/metaengine.cpp
@@ -115,6 +115,18 @@ static const ADExtraGuiOptionsMap optionsList[] = {
 		}
 	},
 
+	{
+		GAMEOPTION_ORIGINAL_MAC_MENUS,
+		{
+			_s("Use original Macintosh menus (experimental)"),
+			_s("Use the Macintosh menu bar and original desktop framing"),
+			"original_mac_menus",
+			false,
+			0,
+			0
+		}
+	},
+
 #ifdef USE_TTS
 	{
 		GAMEOPTION_TTS_NARRATOR,


Commit: dcf9766f39d307b061f4c79e870a12d8d923799e
    https://github.com/scummvm/scummvm/commit/dcf9766f39d307b061f4c79e870a12d8d923799e
Author: fusefib (fibofuse at gmail.com)
Date: 2026-08-08T16:27:31+10:00

Commit Message:
MADS: NEBULAR: Add original Macintosh menu framework

Assisted-by: Codex:GPT-5.4

Changed paths:
    engines/mads/mads.cpp
    engines/mads/mads.h
    engines/mads/nebular/mac_menus.cpp
    engines/mads/nebular/mac_menus.h
    engines/mads/nebular/mac_nebular.cpp
    engines/mads/nebular/mac_nebular.h
    engines/mads/nebular/mac_resources.cpp
    engines/mads/nebular/mac_resources.h
    engines/mads/nebular/nebular.h


diff --git a/engines/mads/mads.cpp b/engines/mads/mads.cpp
index 559ec692450..63eb7f5c0bc 100644
--- a/engines/mads/mads.cpp
+++ b/engines/mads/mads.cpp
@@ -258,6 +258,9 @@ void MADSEngine::pollEvents() {
 	// Poll for events
 	Common::Event e;
 	while (g_system->getEventManager()->pollEvent(e)) {
+		if (handleMacEvent(e))
+			continue;
+
 		bool isMouse = false;
 		switch (e.type) {
 		case Common::EVENT_LBUTTONDOWN:
diff --git a/engines/mads/mads.h b/engines/mads/mads.h
index 19526e5f4b4..e98aee825c6 100644
--- a/engines/mads/mads.h
+++ b/engines/mads/mads.h
@@ -92,6 +92,8 @@ protected:
 	virtual Common::Point gameToScreen(const Common::Point &point) const;
 	virtual void presentScreen(int shakeOffset);
 
+	virtual bool handleMacEvent(Common::Event &event) { return false; }
+
 	bool hasFeature(EngineFeature f) const override;
 
 	void pollEvents();
diff --git a/engines/mads/nebular/mac_menus.cpp b/engines/mads/nebular/mac_menus.cpp
index a697d8e5351..4f8907866ff 100644
--- a/engines/mads/nebular/mac_menus.cpp
+++ b/engines/mads/nebular/mac_menus.cpp
@@ -20,17 +20,188 @@
  */
 
 #include "common/config-manager.h"
+#include "common/debug.h"
+#include "common/events.h"
+#include "common/stream.h"
+#include "common/system.h"
 #include "common/translation.h"
+#include "graphics/macgui/macmenu.h"
+#include "graphics/macgui/macwindowmanager.h"
+#include "graphics/managed_surface.h"
+#include "graphics/paletteman.h"
 #include "gui/chooser.h"
 #include "mads/core/config.h"
 #include "mads/core/game.h"
 #include "mads/core/kernel.h"
 #include "mads/mads.h"
 #include "mads/nebular/mac_menus.h"
+#include "mads/nebular/mac_resources.h"
+#include "mads/nebular/nebular.h"
 
 namespace MADS {
 namespace RexNebular {
 
+namespace {
+
+struct MenuResourceItem {
+	Common::String text;
+	byte key = 0;
+	byte mark = 0;
+	byte style = 0;
+	bool enabled = false;
+};
+
+struct MenuResource {
+	uint16 id = 0;
+	Common::String title;
+	Common::Array<MenuResourceItem> items;
+};
+
+bool readMenuResource(Common::SeekableReadStream &stream, MenuResource &resource) {
+	if (stream.size() < 15)
+		return false;
+
+	resource.id = stream.readUint16BE();
+	stream.skip(8); // Width, height, resource ID, and placeholder.
+	const uint32 enabledItems = stream.readUint32BE();
+	resource.title = stream.readPascalString();
+
+	for (uint itemNumber = 1; stream.pos() < stream.size(); ++itemNumber) {
+		MenuResourceItem item;
+		item.text = stream.readPascalString();
+		if (item.text.empty())
+			return !stream.err();
+		if (stream.pos() + 4 > stream.size())
+			return false;
+
+		/* byte icon = */ stream.readByte();
+		item.key = stream.readByte();
+		item.mark = stream.readByte();
+		item.style = stream.readByte();
+		item.enabled = itemNumber < 32 &&
+			(enabledItems & (1U << itemNumber)) != 0;
+		resource.items.push_back(item);
+	}
+
+	return false;
+}
+
+} // namespace
+
+MacNebularMenu::MacNebularMenu(RexNebularEngine &engine,
+		MacResourceProvider &resources, Graphics::ManagedSurface &screen) :
+		_engine(engine), _resources(resources), _screen(screen) {
+}
+
+MacNebularMenu::~MacNebularMenu() {
+	delete _windowManager;
+}
+
+bool MacNebularMenu::initialize() {
+	// MacWindowManager installs its default palette during construction. Save
+	// and restore the active MADS palette before it can reach the backend.
+	byte palette[256 * 3];
+	g_system->getPaletteManager()->grabPalette(palette, 0, 256);
+
+	const uint32 mode = Graphics::kWMModeNoDesktop |
+		Graphics::kWMModalMenuMode | Graphics::kWMModeNoCursorOverride |
+		Graphics::kWMModeForceMacFonts | Graphics::kWMModeNoSystemRedraw |
+		Graphics::kWMNoScummVMWallpaper;
+	_windowManager = new Graphics::MacWindowManager(mode);
+	g_system->getPaletteManager()->setPalette(palette, 0, 256);
+	_windowManager->setEngine(&_engine);
+	_windowManager->setScreen(&_screen);
+	_windowManager->passPalette(palette, 256);
+	memcpy(_palette, palette, sizeof(_palette));
+	_paletteValid = true;
+	_menu = _windowManager->addMenu();
+	_menu->setCommandsCallback(menuCallback, this);
+
+	if (!loadMenuResource(1000) || !loadMenuResource(1001) ||
+			!loadMenuResource(1002) || !loadMenuResource(2001) ||
+			!loadMenuResource(2101, _menu->getSubmenu(nullptr, 3), 2) ||
+			!loadMenuResource(2102, _menu->getSubmenu(nullptr, 3), 3) ||
+			!loadMenuResource(2000)) {
+		warning("Could not construct the Macintosh Rex menu bar");
+		return false;
+	}
+
+	_menu->calcDimensions();
+	disableActions();
+	return true;
+}
+
+bool MacNebularMenu::loadMenuResource(uint16 resourceID,
+		Graphics::MacMenuSubMenu *parent, int parentItem) {
+	Common::SeekableReadStream *stream = _resources.openResource(
+		MacResourceProvider::kApplicationContainer, MKTAG('M', 'E', 'N', 'U'),
+		resourceID);
+	if (!stream)
+		return false;
+
+	MenuResource resource;
+	const bool valid = readMenuResource(*stream, resource);
+	delete stream;
+	if (!valid)
+		return false;
+
+	Graphics::MacMenuSubMenu *submenu = nullptr;
+	if (parent) {
+		if (parentItem < 0 || (uint)parentItem >= parent->items.size())
+			return false;
+		submenu = _menu->addSubMenu(parent, parentItem);
+	} else {
+		const int menuIndex = _menu->addMenuItem(nullptr, resource.title);
+		submenu = _menu->addSubMenu(nullptr, menuIndex);
+	}
+
+	for (uint index = 0; index < resource.items.size(); ++index) {
+		const MenuResourceItem &item = resource.items[index];
+		const char shortcut = item.key == 0x1b ? 0 : item.key;
+		_menu->addMenuItem(submenu, item.text,
+			(resource.id << 16) | index, item.style, shortcut, item.enabled);
+	}
+
+	return true;
+}
+
+void MacNebularMenu::disableActions() {
+	for (int menuIndex = 0; menuIndex < _menu->numberOfMenus(); ++menuIndex) {
+		Graphics::MacMenuItem *topLevel = _menu->getMenuItem(menuIndex);
+		for (int itemIndex = 0;
+				itemIndex < _menu->numberOfMenuItems(topLevel); ++itemIndex)
+			_menu->setEnabled(_menu->getSubMenuItem(topLevel, itemIndex), false);
+	}
+}
+
+void MacNebularMenu::syncPalette() {
+	byte palette[256 * 3];
+	g_system->getPaletteManager()->grabPalette(palette, 0, 256);
+	if (_paletteValid && memcmp(_palette, palette, sizeof(_palette)) == 0)
+		return;
+
+	memcpy(_palette, palette, sizeof(_palette));
+	_paletteValid = true;
+	_windowManager->passPalette(_palette, 256);
+}
+
+bool MacNebularMenu::processEvent(Common::Event &event) {
+	return _windowManager && _windowManager->processEvent(event);
+}
+
+void MacNebularMenu::draw() {
+	if (!_menu)
+		return;
+
+	syncPalette();
+	_menu->draw(&_screen, true);
+}
+
+void MacNebularMenu::menuCallback(int, Common::String &, void *data) {
+	MacNebularMenu *menus = (MacNebularMenu *)data;
+	menus->_menu->closeMenu();
+}
+
 void selectMacintoshDifficulty() {
 	const int configuredDifficulty = ConfMan.getInt("difficulty");
 	if (configuredDifficulty >= DIFFICULTY_HARD && configuredDifficulty <= DIFFICULTY_EASY) {
diff --git a/engines/mads/nebular/mac_menus.h b/engines/mads/nebular/mac_menus.h
index c40fc913136..26d4ebe4ad7 100644
--- a/engines/mads/nebular/mac_menus.h
+++ b/engines/mads/nebular/mac_menus.h
@@ -22,9 +22,52 @@
 #ifndef MADS_NEBULAR_MAC_MENUS_H
 #define MADS_NEBULAR_MAC_MENUS_H
 
+#include "common/scummsys.h"
+
+namespace Common {
+struct Event;
+class String;
+}
+
+namespace Graphics {
+class MacMenu;
+class MacMenuSubMenu;
+class MacWindowManager;
+class ManagedSurface;
+}
+
 namespace MADS {
 namespace RexNebular {
 
+class MacResourceProvider;
+class RexNebularEngine;
+
+class MacNebularMenu {
+private:
+	RexNebularEngine &_engine;
+	MacResourceProvider &_resources;
+	Graphics::ManagedSurface &_screen;
+	Graphics::MacWindowManager *_windowManager = nullptr;
+	Graphics::MacMenu *_menu = nullptr;
+	byte _palette[256 * 3] = {};
+	bool _paletteValid = false;
+
+	static void menuCallback(int command, Common::String &name, void *data);
+	bool loadMenuResource(uint16 resourceID,
+		Graphics::MacMenuSubMenu *parent = nullptr, int parentItem = -1);
+	void disableActions();
+	void syncPalette();
+
+public:
+	MacNebularMenu(RexNebularEngine &engine, MacResourceProvider &resources,
+		Graphics::ManagedSurface &screen);
+	~MacNebularMenu();
+
+	bool initialize();
+	bool processEvent(Common::Event &event);
+	void draw();
+};
+
 void selectMacintoshDifficulty();
 void macintoshGameMenu();
 
diff --git a/engines/mads/nebular/mac_nebular.cpp b/engines/mads/nebular/mac_nebular.cpp
index 20969eed2ef..55f730d5ce2 100644
--- a/engines/mads/nebular/mac_nebular.cpp
+++ b/engines/mads/nebular/mac_nebular.cpp
@@ -20,7 +20,9 @@
  */
 
 #include "engines/util.h"
+#include "common/config-manager.h"
 #include "common/debug.h"
+#include "common/events.h"
 #include "common/system.h"
 #include "graphics/font.h"
 #include "graphics/managed_surface.h"
@@ -30,6 +32,7 @@
 #include "mads/core/object.h"
 #include "mads/core/pal.h"
 #include "mads/core/screen.h"
+#include "mads/nebular/mac_menus.h"
 #include "mads/nebular/mac_nebular.h"
 #include "mads/nebular/mac_resources.h"
 #include "mads/nebular/nebular.h"
@@ -422,6 +425,8 @@ MacNebular::MacNebular(RexNebularEngine &engine) : _engine(engine) {
 }
 
 MacNebular::~MacNebular() {
+	delete _menus;
+
 	if (!_resources)
 		return;
 
@@ -433,6 +438,8 @@ MacNebular::~MacNebular() {
 
 void MacNebular::initGraphics() {
 	::initGraphics(kMacScreenWidth, kMacScreenHeight);
+	_output.create(kMacScreenWidth, kMacScreenHeight,
+		Graphics::PixelFormat::createFormatCLUT8());
 }
 
 bool MacNebular::initResources() {
@@ -446,6 +453,14 @@ bool MacNebular::initResources() {
 	env_set_resource_provider(_resources);
 	_engine._soundManager = new Sound::MacSoundManager(
 		_engine._mixer, _engine._soundFlag, _resources);
+
+	if (ConfMan.getBool("original_mac_menus")) {
+		_menus = new MacNebularMenu(_engine, *_resources, _output);
+		if (!_menus->initialize()) {
+			delete _menus;
+			_menus = nullptr;
+		}
+	}
 	return true;
 }
 
@@ -487,14 +502,13 @@ Common::Point MacNebular::gameToScreen(const Common::Point &point) const {
 }
 
 void MacNebular::presentScreen(int shakeOffset) {
-	_output.resize(kMacScreenWidth * kMacScreenHeight);
-	memset(_output.data(), kMacBlackColor, _output.size());
+	_output.fillRect(_output.getBounds(), kMacBlackColor);
 
 	// Native large-window mode doubles the 320x156 scene in both axes.
 	for (int y = 0; y < 156; ++y) {
 		const byte *source = (const byte *)_engine._screen->getBasePtr(0, y);
-		byte *line1 = _output.data() + (y * 2) * kMacScreenWidth;
-		byte *line2 = line1 + kMacScreenWidth;
+		byte *line1 = (byte *)_output.getBasePtr(0, y * 2);
+		byte *line2 = (byte *)_output.getBasePtr(0, y * 2 + 1);
 		for (int x = 0; x < 320; ++x) {
 			const byte color = source[(x + shakeOffset) % 320];
 			line1[x * 2] = color;
@@ -560,8 +574,7 @@ void MacNebular::presentScreen(int shakeOffset) {
 			drawMacInterfaceState(panel, *interfaceFont);
 
 		for (int y = 0; y < kMacInterfaceHeight; ++y) {
-			memcpy(_output.data() +
-				(kMacSceneHeight + y) * kMacScreenWidth + kMacInterfaceX,
+			memcpy(_output.getBasePtr(kMacInterfaceX, kMacSceneHeight + y),
 				panel.getBasePtr(0, y), kMacInterfaceWidth);
 		}
 	} else {
@@ -569,8 +582,8 @@ void MacNebular::presentScreen(int shakeOffset) {
 		// fallback by scaling the shared 320x44 interface into its Mac bounds.
 		for (int y = 0; y < kMacInterfaceHeight; ++y) {
 			const byte *source = (const byte *)_engine._screen->getBasePtr(0, 156 + y / 2);
-			byte *target = _output.data() +
-				(kMacSceneHeight + y) * kMacScreenWidth + kMacInterfaceX;
+			byte *target = (byte *)_output.getBasePtr(
+				kMacInterfaceX, kMacSceneHeight + y);
 			for (int x = 0; x < kMacInterfaceWidth; ++x)
 				target[x] = source[x * 320 / kMacInterfaceWidth];
 		}
@@ -587,7 +600,7 @@ void MacNebular::presentScreen(int shakeOffset) {
 			const int width = MIN<int>(_popup.w - sourceX,
 				kMacScreenWidth - targetX);
 			if (width > 0)
-				memcpy(_output.data() + targetY * kMacScreenWidth + targetX,
+				memcpy(_output.getBasePtr(targetX, targetY),
 					_popup.getBasePtr(sourceX, y), width);
 		}
 	}
@@ -597,12 +610,19 @@ void MacNebular::presentScreen(int shakeOffset) {
 		_layoutLogged = true;
 	}
 
-	g_system->copyRectToScreen(_output.data(), kMacScreenWidth,
+	if (_menus)
+		_menus->draw();
+
+	g_system->copyRectToScreen(_output.getPixels(), _output.pitch,
 		0, 0, kMacScreenWidth, kMacScreenHeight);
 	g_system->updateScreen();
 	_engine._screen->clearDirtyRects();
 }
 
+bool MacNebular::handleMacEvent(Common::Event &event) {
+	return _menus && _menus->processEvent(event);
+}
+
 void MacNebular::showPopup() {
 	if (!_resources || !box)
 		return;
@@ -727,6 +747,10 @@ void RexNebularEngine::presentScreen(int shakeOffset) {
 		MADSEngine::presentScreen(shakeOffset);
 }
 
+bool RexNebularEngine::handleMacEvent(Common::Event &event) {
+	return _macNebular && _macNebular->handleMacEvent(event);
+}
+
 bool RexNebularEngine::drawPopup() {
 	if (!_macNebular)
 		return false;
diff --git a/engines/mads/nebular/mac_nebular.h b/engines/mads/nebular/mac_nebular.h
index f4560c50d78..65b883d405d 100644
--- a/engines/mads/nebular/mac_nebular.h
+++ b/engines/mads/nebular/mac_nebular.h
@@ -22,21 +22,26 @@
 #ifndef MADS_NEBULAR_MAC_NEBULAR_H
 #define MADS_NEBULAR_MAC_NEBULAR_H
 
-#include "common/array.h"
 #include "common/rect.h"
 #include "graphics/managed_surface.h"
 
+namespace Common {
+struct Event;
+}
+
 namespace MADS {
 namespace RexNebular {
 
 class MacResourceProvider;
+class MacNebularMenu;
 class RexNebularEngine;
 
 class MacNebular {
 private:
 	RexNebularEngine &_engine;
 	MacResourceProvider *_resources = nullptr;
-	Common::Array<byte> _output;
+	MacNebularMenu *_menus = nullptr;
+	Graphics::ManagedSurface _output;
 	Graphics::ManagedSurface _popup;
 	Common::Rect _popupRect;
 	bool _popupActive = false;
@@ -51,6 +56,7 @@ public:
 	void applyGameSettings();
 	Common::Point screenToGame(const Common::Point &point) const;
 	Common::Point gameToScreen(const Common::Point &point) const;
+	bool handleMacEvent(Common::Event &event);
 	void presentScreen(int shakeOffset);
 	void showPopup();
 	void hidePopup();
diff --git a/engines/mads/nebular/mac_resources.cpp b/engines/mads/nebular/mac_resources.cpp
index fc9ccd1fd6d..3cff373d887 100644
--- a/engines/mads/nebular/mac_resources.cpp
+++ b/engines/mads/nebular/mac_resources.cpp
@@ -188,6 +188,12 @@ Common::MacResManager *MacResourceProvider::getContainer(Container container) co
 	return _containers[container];
 }
 
+Common::SeekableReadStream *MacResourceProvider::openResource(
+		Container container, uint32 type, uint16 id) const {
+	Common::MacResManager *resourceContainer = getContainer(container);
+	return resourceContainer ? resourceContainer->getResource(type, id) : nullptr;
+}
+
 const Graphics::Font *MacResourceProvider::getDialogFont() {
 	if (!_fontManager)
 		return nullptr;
diff --git a/engines/mads/nebular/mac_resources.h b/engines/mads/nebular/mac_resources.h
index 6ef0b66521d..15730e034b9 100644
--- a/engines/mads/nebular/mac_resources.h
+++ b/engines/mads/nebular/mac_resources.h
@@ -29,6 +29,7 @@
 
 namespace Common {
 class MacResManager;
+class SeekableReadStream;
 }
 
 namespace Graphics {
@@ -65,6 +66,8 @@ public:
 	~MacResourceProvider() override;
 
 	bool load();
+	Common::SeekableReadStream *openResource(Container container, uint32 type,
+		uint16 id) const;
 	Common::SeekableReadStream *open(const char *filename) override;
 	bool exists(const char *filename) override;
 	bool allowsFallback(const char *filename) const override;
diff --git a/engines/mads/nebular/nebular.h b/engines/mads/nebular/nebular.h
index 199908f0b32..f3232243075 100644
--- a/engines/mads/nebular/nebular.h
+++ b/engines/mads/nebular/nebular.h
@@ -53,6 +53,7 @@ protected:
 	Common::Point screenToGame(const Common::Point &point) const override;
 	Common::Point gameToScreen(const Common::Point &point) const override;
 	void presentScreen(int shakeOffset) override;
+	bool handleMacEvent(Common::Event &event) override;
 
 public:
 	RexNebularEngine(OSystem *syst, const MADSGameDescription *gameDesc);


Commit: ef6f275a4479154823ae75c8979b3f68be4af62d
    https://github.com/scummvm/scummvm/commit/ef6f275a4479154823ae75c8979b3f68be4af62d
Author: fusefib (fibofuse at gmail.com)
Date: 2026-08-08T16:27:31+10:00

Commit Message:
MADS: NEBULAR: Present original Macintosh composition

Assisted-by: Codex:GPT-5.4

Changed paths:
    engines/mads/nebular/mac_nebular.cpp
    engines/mads/nebular/mac_nebular.h


diff --git a/engines/mads/nebular/mac_nebular.cpp b/engines/mads/nebular/mac_nebular.cpp
index 55f730d5ce2..55a89c982e8 100644
--- a/engines/mads/nebular/mac_nebular.cpp
+++ b/engines/mads/nebular/mac_nebular.cpp
@@ -49,7 +49,12 @@ enum {
 	kMacInterfaceWidth = 512,
 	kMacInterfaceHeight = 88,
 	kMacInterfaceX = (kMacScreenWidth - kMacInterfaceWidth) / 2,
-	kMacScreenHeight = kMacSceneHeight + kMacInterfaceHeight,
+	kMacLegacyScreenHeight = kMacSceneHeight + kMacInterfaceHeight,
+	kMacDesktopHeight = 480,
+	kMacMenuBarHeight = 20,
+	kMacDesktopSceneY = kMacMenuBarHeight + 20,
+	kMacDesktopSeparatorY = kMacDesktopSceneY + kMacSceneHeight,
+	kMacDesktopInterfaceY = kMacDesktopSeparatorY + 1,
 	kMacBlackColor = 8,
 	kMacNormalTextColor = 15,
 	kMacLeftSelectColor = 13,
@@ -421,7 +426,8 @@ static bool isMacInterfaceSemanticPixel(int x, int y) {
 	return false;
 }
 
-MacNebular::MacNebular(RexNebularEngine &engine) : _engine(engine) {
+MacNebular::MacNebular(RexNebularEngine &engine) :
+		_engine(engine), _useOriginalMenus(ConfMan.getBool("original_mac_menus")) {
 }
 
 MacNebular::~MacNebular() {
@@ -437,8 +443,9 @@ MacNebular::~MacNebular() {
 }
 
 void MacNebular::initGraphics() {
-	::initGraphics(kMacScreenWidth, kMacScreenHeight);
-	_output.create(kMacScreenWidth, kMacScreenHeight,
+	const int height = _useOriginalMenus ? kMacDesktopHeight : kMacLegacyScreenHeight;
+	::initGraphics(kMacScreenWidth, height);
+	_output.create(kMacScreenWidth, height,
 		Graphics::PixelFormat::createFormatCLUT8());
 }
 
@@ -454,7 +461,7 @@ bool MacNebular::initResources() {
 	_engine._soundManager = new Sound::MacSoundManager(
 		_engine._mixer, _engine._soundFlag, _resources);
 
-	if (ConfMan.getBool("original_mac_menus")) {
+	if (_useOriginalMenus) {
 		_menus = new MacNebularMenu(_engine, *_resources, _output);
 		if (!_menus->initialize()) {
 			delete _menus;
@@ -465,7 +472,7 @@ bool MacNebular::initResources() {
 }
 
 void MacNebular::applyGameSettings() {
-	// The Macintosh port's 640x400 large-window mode uses square pixels.
+	// The Macintosh port's large-window presentation uses square pixels.
 	// DOS-style 320x200 aspect correction would stretch its scene and native
 	// interface vertically.
 	if (g_system->hasFeature(OSystem::kFeatureAspectRatioCorrection) &&
@@ -477,15 +484,19 @@ void MacNebular::applyGameSettings() {
 }
 
 Common::Point MacNebular::screenToGame(const Common::Point &point) const {
-	if (point.y >= 0 && point.y < kMacSceneHeight)
-		return Common::Point(CLIP<int>(point.x / 2, 0, 319), point.y / 2);
-
-	if (point.y >= kMacSceneHeight && point.y < kMacScreenHeight &&
+	const int sceneY = _useOriginalMenus ? kMacDesktopSceneY : 0;
+	const int interfaceY = _useOriginalMenus ?
+		kMacDesktopInterfaceY : kMacSceneHeight;
+	if (point.y >= sceneY && point.y < sceneY + kMacSceneHeight)
+		return Common::Point(CLIP<int>(point.x / 2, 0, 319),
+			(point.y - sceneY) / 2);
+
+	if (point.y >= interfaceY && point.y < interfaceY + kMacInterfaceHeight &&
 			point.x >= kMacInterfaceX &&
 			point.x < kMacInterfaceX + kMacInterfaceWidth) {
 		return Common::Point(
 			(point.x - kMacInterfaceX) * 320 / kMacInterfaceWidth,
-			156 + (point.y - kMacSceneHeight) / 2);
+			156 + (point.y - interfaceY) / 2);
 	}
 
 	// The native interface is narrower than the scene. Its side gutters are
@@ -494,21 +505,27 @@ Common::Point MacNebular::screenToGame(const Common::Point &point) const {
 }
 
 Common::Point MacNebular::gameToScreen(const Common::Point &point) const {
+	const int sceneY = _useOriginalMenus ? kMacDesktopSceneY : 0;
+	const int interfaceY = _useOriginalMenus ?
+		kMacDesktopInterfaceY : kMacSceneHeight;
 	if (point.y < 156)
-		return Common::Point(point.x * 2, point.y * 2);
+		return Common::Point(point.x * 2, sceneY + point.y * 2);
 
 	return Common::Point(kMacInterfaceX + point.x * kMacInterfaceWidth / 320,
-		kMacSceneHeight + (point.y - 156) * 2);
+		interfaceY + (point.y - 156) * 2);
 }
 
 void MacNebular::presentScreen(int shakeOffset) {
+	const int sceneY = _useOriginalMenus ? kMacDesktopSceneY : 0;
+	const int interfaceY = _useOriginalMenus ?
+		kMacDesktopInterfaceY : kMacSceneHeight;
 	_output.fillRect(_output.getBounds(), kMacBlackColor);
 
 	// Native large-window mode doubles the 320x156 scene in both axes.
 	for (int y = 0; y < 156; ++y) {
 		const byte *source = (const byte *)_engine._screen->getBasePtr(0, y);
-		byte *line1 = (byte *)_output.getBasePtr(0, y * 2);
-		byte *line2 = (byte *)_output.getBasePtr(0, y * 2 + 1);
+		byte *line1 = (byte *)_output.getBasePtr(0, sceneY + y * 2);
+		byte *line2 = (byte *)_output.getBasePtr(0, sceneY + y * 2 + 1);
 		for (int x = 0; x < 320; ++x) {
 			const byte color = source[(x + shakeOffset) % 320];
 			line1[x * 2] = color;
@@ -574,7 +591,7 @@ void MacNebular::presentScreen(int shakeOffset) {
 			drawMacInterfaceState(panel, *interfaceFont);
 
 		for (int y = 0; y < kMacInterfaceHeight; ++y) {
-			memcpy(_output.getBasePtr(kMacInterfaceX, kMacSceneHeight + y),
+			memcpy(_output.getBasePtr(kMacInterfaceX, interfaceY + y),
 				panel.getBasePtr(0, y), kMacInterfaceWidth);
 		}
 	} else {
@@ -583,7 +600,7 @@ void MacNebular::presentScreen(int shakeOffset) {
 		for (int y = 0; y < kMacInterfaceHeight; ++y) {
 			const byte *source = (const byte *)_engine._screen->getBasePtr(0, 156 + y / 2);
 			byte *target = (byte *)_output.getBasePtr(
-				kMacInterfaceX, kMacSceneHeight + y);
+				kMacInterfaceX, interfaceY + y);
 			for (int x = 0; x < kMacInterfaceWidth; ++x)
 				target[x] = source[x * 320 / kMacInterfaceWidth];
 		}
@@ -592,7 +609,7 @@ void MacNebular::presentScreen(int shakeOffset) {
 	if (_popupActive && !_popup.empty()) {
 		for (int y = 0; y < _popup.h; ++y) {
 			const int targetY = _popupRect.top + y;
-			if (targetY < 0 || targetY >= kMacScreenHeight)
+			if (targetY < 0 || targetY >= _output.h)
 				continue;
 
 			const int targetX = MAX<int>(0, _popupRect.left);
@@ -606,7 +623,11 @@ void MacNebular::presentScreen(int shakeOffset) {
 	}
 
 	if (!_layoutLogged) {
-		debug(2, "Presenting Macintosh Rex as 640x312 scene plus centered 512x88 interface");
+		if (_useOriginalMenus) {
+			debug(2, "Presenting Macintosh Rex in original 640x480 composition");
+		} else {
+			debug(2, "Presenting Macintosh Rex as 640x312 scene plus centered 512x88 interface");
+		}
 		_layoutLogged = true;
 	}
 
@@ -614,7 +635,7 @@ void MacNebular::presentScreen(int shakeOffset) {
 		_menus->draw();
 
 	g_system->copyRectToScreen(_output.getPixels(), _output.pitch,
-		0, 0, kMacScreenWidth, kMacScreenHeight);
+		0, 0, kMacScreenWidth, _output.h);
 	g_system->updateScreen();
 	_engine._screen->clearDirtyRects();
 }
@@ -668,8 +689,11 @@ void MacNebular::showPopup() {
 		appendWrappedMacPopupText(*font, paragraph, paragraphTab,
 			width - 20, lines);
 
+	const int popupAreaY = _useOriginalMenus ? kMacDesktopSceneY : 0;
+	const int popupAreaHeight = _useOriginalMenus ?
+		kMacSceneHeight : kMacLegacyScreenHeight;
 	const int height = CLIP<int>((int)lines.size() * 12 + 20, 20,
-		kMacScreenHeight - 2);
+		popupAreaHeight - 2);
 	_popup.create(width, height, Graphics::PixelFormat::createFormatCLUT8());
 	_popup.fillRect(Common::Rect(width, height), kMacPopupColor);
 	_popup.frameRect(Common::Rect(width, height), kMacBlackColor);
@@ -704,9 +728,9 @@ void MacNebular::showPopup() {
 
 	_popupRect = Common::Rect(
 		(kMacScreenWidth - width) / 2,
-		(kMacScreenHeight - height) / 2,
+		popupAreaY + (popupAreaHeight - height) / 2,
 		(kMacScreenWidth + width) / 2,
-		(kMacScreenHeight + height) / 2);
+		popupAreaY + (popupAreaHeight + height) / 2);
 	_popupActive = true;
 	presentScreen(0);
 }
diff --git a/engines/mads/nebular/mac_nebular.h b/engines/mads/nebular/mac_nebular.h
index 65b883d405d..79efd0ce051 100644
--- a/engines/mads/nebular/mac_nebular.h
+++ b/engines/mads/nebular/mac_nebular.h
@@ -44,6 +44,7 @@ private:
 	Graphics::ManagedSurface _output;
 	Graphics::ManagedSurface _popup;
 	Common::Rect _popupRect;
+	bool _useOriginalMenus;
 	bool _popupActive = false;
 	bool _layoutLogged = false;
 


Commit: 223a437ce3afd99495314e403f463513f0cc9991
    https://github.com/scummvm/scummvm/commit/223a437ce3afd99495314e403f463513f0cc9991
Author: fusefib (fibofuse at gmail.com)
Date: 2026-08-08T16:27:31+10:00

Commit Message:
MADS: NEBULAR: Implement Macintosh menu actions

Assisted-by: Codex:GPT-5.4

Changed paths:
    engines/mads/nebular/mac_menus.cpp
    engines/mads/nebular/mac_menus.h


diff --git a/engines/mads/nebular/mac_menus.cpp b/engines/mads/nebular/mac_menus.cpp
index 4f8907866ff..9a0bbdb6ed5 100644
--- a/engines/mads/nebular/mac_menus.cpp
+++ b/engines/mads/nebular/mac_menus.cpp
@@ -33,6 +33,7 @@
 #include "mads/core/config.h"
 #include "mads/core/game.h"
 #include "mads/core/kernel.h"
+#include "mads/core/sound_manager.h"
 #include "mads/mads.h"
 #include "mads/nebular/mac_menus.h"
 #include "mads/nebular/mac_resources.h"
@@ -43,6 +44,24 @@ namespace RexNebular {
 
 namespace {
 
+enum {
+	kFileMenu = 1,
+	kOptionsMenu = 3,
+	kWindowMenu = 4,
+
+	kFileOpen = (1001 << 16) | 2,
+	kFileSave = (1001 << 16) | 4,
+	kFileSaveAs = (1001 << 16) | 5,
+	kFileQuit = (1001 << 16) | 9,
+	kOptionNoSound = (2001 << 16),
+	kOptionEasyInterface = (2001 << 16) | 1,
+	kFadeSmooth = (101 << 16),
+	kFadeMedium = (101 << 16) | 1,
+	kFadeFast = (101 << 16) | 2,
+	kStoryNaughty = (102 << 16),
+	kStoryNice = (102 << 16) | 1
+};
+
 struct MenuResourceItem {
 	Common::String text;
 	byte key = 0;
@@ -127,7 +146,7 @@ bool MacNebularMenu::initialize() {
 	}
 
 	_menu->calcDimensions();
-	disableActions();
+	updateState();
 	return true;
 }
 
@@ -149,6 +168,10 @@ bool MacNebularMenu::loadMenuResource(uint16 resourceID,
 	if (parent) {
 		if (parentItem < 0 || (uint)parentItem >= parent->items.size())
 			return false;
+		// In a classic MENU resource, the parent item's mark byte carries
+		// the hierarchical submenu ID. Refuse to attach a mismatched resource.
+		if (parent->items[parentItem]->checkSymbol != resource.id)
+			return false;
 		submenu = _menu->addSubMenu(parent, parentItem);
 	} else {
 		const int menuIndex = _menu->addMenuItem(nullptr, resource.title);
@@ -158,20 +181,135 @@ bool MacNebularMenu::loadMenuResource(uint16 resourceID,
 	for (uint index = 0; index < resource.items.size(); ++index) {
 		const MenuResourceItem &item = resource.items[index];
 		const char shortcut = item.key == 0x1b ? 0 : item.key;
-		_menu->addMenuItem(submenu, item.text,
+		const int itemIndex = _menu->addMenuItem(submenu, item.text,
 			(resource.id << 16) | index, item.style, shortcut, item.enabled);
+		submenu->items[itemIndex]->checkSymbol = item.mark;
 	}
 
 	return true;
 }
 
-void MacNebularMenu::disableActions() {
+Graphics::MacMenuItem *MacNebularMenu::getMenuItem(int menu, int item) const {
+	Graphics::MacMenuItem *topLevel = _menu->getMenuItem(menu);
+	if (!topLevel || !topLevel->submenu || item < 0 ||
+			(uint)item >= topLevel->submenu->items.size())
+		return nullptr;
+	return topLevel->submenu->items[item];
+}
+
+Graphics::MacMenuItem *MacNebularMenu::getSubMenuItem(int menu,
+		int parentItem, int item) const {
+	Graphics::MacMenuItem *parent = getMenuItem(menu, parentItem);
+	if (!parent || !parent->submenu || item < 0 ||
+			(uint)item >= parent->submenu->items.size())
+		return nullptr;
+	return parent->submenu->items[item];
+}
+
+void MacNebularMenu::setItemState(Graphics::MacMenuItem *item,
+		bool enabled, bool checked) {
+	if (!item)
+		return;
+	if (item->enabled != enabled)
+		_menu->setEnabled(item, enabled);
+	if (item->checked != checked)
+		_menu->setCheckMark(item, checked);
+}
+
+void MacNebularMenu::updateState() {
+	// Start with every direct action disabled. This preserves separators and
+	// keeps native actions without a safe ScummVM equivalent visible.
 	for (int menuIndex = 0; menuIndex < _menu->numberOfMenus(); ++menuIndex) {
 		Graphics::MacMenuItem *topLevel = _menu->getMenuItem(menuIndex);
 		for (int itemIndex = 0;
 				itemIndex < _menu->numberOfMenuItems(topLevel); ++itemIndex)
-			_menu->setEnabled(_menu->getSubMenuItem(topLevel, itemIndex), false);
+			setItemState(_menu->getSubMenuItem(topLevel, itemIndex), false, false);
+	}
+
+	setItemState(getMenuItem(kFileMenu, 2),
+		_engine.canLoadGameStateCurrently(nullptr), false);
+	const bool canSave = _engine.canSaveGameStateCurrently(nullptr);
+	setItemState(getMenuItem(kFileMenu, 4), canSave, false);
+	setItemState(getMenuItem(kFileMenu, 5), canSave, false);
+	setItemState(getMenuItem(kFileMenu, 9), true, false);
+
+	const bool noSound = !_engine._musicFlag && !_engine._soundFlag;
+	setItemState(getMenuItem(kOptionsMenu, 0), true, noSound);
+	setItemState(getMenuItem(kOptionsMenu, 1), true, inter_report_hotspots);
+	setItemState(getMenuItem(kOptionsMenu, 2), true, false);
+	setItemState(getMenuItem(kOptionsMenu, 3), true, false);
+
+	for (int fade = SCREEN_FADE_SMOOTH; fade <= SCREEN_FADE_FAST; ++fade)
+		setItemState(getSubMenuItem(kOptionsMenu, 2, fade), true,
+			kernel_screen_fade == fade);
+
+	setItemState(getSubMenuItem(kOptionsMenu, 3, 0),
+		config_file.naughtiness != NAUGHTY,
+		config_file.naughtiness == NAUGHTY);
+	setItemState(getSubMenuItem(kOptionsMenu, 3, 1),
+		config_file.naughtiness != NICE,
+		config_file.naughtiness == NICE);
+	setItemState(getSubMenuItem(kOptionsMenu, 3, 2), false,
+		config_file.naughtiness != NAUGHTY && config_file.naughtiness != NICE);
+
+	setItemState(getMenuItem(kWindowMenu, 0), false, false);
+	setItemState(getMenuItem(kWindowMenu, 1), false, false);
+	setItemState(getMenuItem(kWindowMenu, 2), true, true);
+}
+
+void MacNebularMenu::dispatchCommand(int command) {
+	switch (command) {
+	case kFileOpen:
+		if (_engine.canLoadGameStateCurrently(nullptr))
+			_engine.loadGameDialog();
+		break;
+	case kFileSave:
+	case kFileSaveAs:
+		if (_engine.canSaveGameStateCurrently(nullptr))
+			_engine.saveGameDialog();
+		break;
+	case kFileQuit:
+		_engine.quitGame();
+		break;
+	case kOptionNoSound: {
+		const bool enableSound = !_engine._musicFlag && !_engine._soundFlag;
+		_engine._musicFlag = enableSound;
+		_engine._soundFlag = enableSound;
+		config_file.music_flag = enableSound;
+		config_file.sound_flag = enableSound;
+		if (!enableSound && _engine._soundManager)
+			_engine._soundManager->stop();
+		ConfMan.setBool("music_mute", !enableSound);
+		ConfMan.setBool("sfx_mute", !enableSound);
+		ConfMan.flushToDisk();
+		break;
+	}
+	case kOptionEasyInterface:
+		inter_report_hotspots = !inter_report_hotspots;
+		config_file.interface_hotspots = inter_report_hotspots ?
+			INTERFACE_BRAINDEAD : INTERFACE_MACINTOSH;
+		ConfMan.setBool("interface_hotspots", inter_report_hotspots);
+		ConfMan.flushToDisk();
+		break;
+	case kFadeSmooth:
+	case kFadeMedium:
+	case kFadeFast:
+		kernel_screen_fade = command - kFadeSmooth;
+		config_file.screen_fade = kernel_screen_fade;
+		ConfMan.setInt("screen_fade", kernel_screen_fade);
+		ConfMan.flushToDisk();
+		break;
+	case kStoryNaughty:
+	case kStoryNice:
+		config_file.naughtiness = command == kStoryNaughty ? NAUGHTY : NICE;
+		ConfMan.setBool("naughtiness", config_file.naughtiness == NAUGHTY);
+		ConfMan.flushToDisk();
+		break;
+	default:
+		break;
 	}
+
+	updateState();
 }
 
 void MacNebularMenu::syncPalette() {
@@ -186,7 +324,17 @@ void MacNebularMenu::syncPalette() {
 }
 
 bool MacNebularMenu::processEvent(Common::Event &event) {
-	return _windowManager && _windowManager->processEvent(event);
+	if (!_windowManager)
+		return false;
+
+	updateState();
+	const bool handled = _windowManager->processEvent(event);
+	if (_pendingCommand != -1) {
+		const int command = _pendingCommand;
+		_pendingCommand = -1;
+		dispatchCommand(command);
+	}
+	return handled;
 }
 
 void MacNebularMenu::draw() {
@@ -194,11 +342,13 @@ void MacNebularMenu::draw() {
 		return;
 
 	syncPalette();
+	updateState();
 	_menu->draw(&_screen, true);
 }
 
-void MacNebularMenu::menuCallback(int, Common::String &, void *data) {
+void MacNebularMenu::menuCallback(int command, Common::String &, void *data) {
 	MacNebularMenu *menus = (MacNebularMenu *)data;
+	menus->_pendingCommand = command;
 	menus->_menu->closeMenu();
 }
 
diff --git a/engines/mads/nebular/mac_menus.h b/engines/mads/nebular/mac_menus.h
index 26d4ebe4ad7..ed52e7b9e38 100644
--- a/engines/mads/nebular/mac_menus.h
+++ b/engines/mads/nebular/mac_menus.h
@@ -31,6 +31,7 @@ class String;
 
 namespace Graphics {
 class MacMenu;
+struct MacMenuItem;
 class MacMenuSubMenu;
 class MacWindowManager;
 class ManagedSurface;
@@ -51,11 +52,16 @@ private:
 	Graphics::MacMenu *_menu = nullptr;
 	byte _palette[256 * 3] = {};
 	bool _paletteValid = false;
+	int _pendingCommand = -1;
 
 	static void menuCallback(int command, Common::String &name, void *data);
 	bool loadMenuResource(uint16 resourceID,
 		Graphics::MacMenuSubMenu *parent = nullptr, int parentItem = -1);
-	void disableActions();
+	Graphics::MacMenuItem *getMenuItem(int menu, int item) const;
+	Graphics::MacMenuItem *getSubMenuItem(int menu, int parentItem, int item) const;
+	void setItemState(Graphics::MacMenuItem *item, bool enabled, bool checked);
+	void updateState();
+	void dispatchCommand(int command);
 	void syncPalette();
 
 public:




More information about the Scummvm-git-logs mailing list