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

fracturehill noreply at scummvm.org
Tue Nov 11 10:58:30 UTC 2025


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

Summary:
4afb30b272 NANCY: Add extra graphic state to Button
e5b2b5d84d NANCY: Implement save/load menu for nancy8 and up
0a1ab6cbe8 NANCY: Fix image loading in The Vampire Diaries
fa613690e6 NANCY: Use ScopedPtr in SetupMenu
b7b64dd4ab NANCY: Use ScopedPtr in SaveDialog
9f3140df4c NANCY: Use ScopedPtr in Map
a0809aa698 NANCY: Use ScopedPtr in MainMenu
7a85b93834 NANCY: Use ScopedPtr in Help
5078e6c1a9 NANCY: Use ScopedPtr in InventoryBox
b782932303 NANCY: Use ScopedPtr in SecondaryMovie


Commit: 4afb30b272f067c17346ee9bdb43cf5bfd32ce1e
    https://github.com/scummvm/scummvm/commit/4afb30b272f067c17346ee9bdb43cf5bfd32ce1e
Author: Kaloyan Chehlarski (strahy at outlook.com)
Date: 2025-11-11T11:22:47+01:00

Commit Message:
NANCY: Add extra graphic state to Button

Adds a "base" graphic, used when the button is not being
interacted with. This was missing before, because in most
places, that graphic would just be baked into the background.

Changed paths:
    engines/nancy/ui/button.cpp
    engines/nancy/ui/button.h


diff --git a/engines/nancy/ui/button.cpp b/engines/nancy/ui/button.cpp
index 5b1e5af4b22..deb2a3c74c7 100644
--- a/engines/nancy/ui/button.cpp
+++ b/engines/nancy/ui/button.cpp
@@ -33,15 +33,28 @@
 namespace Nancy {
 namespace UI {
 
-Button::Button(uint16 zOrder, Graphics::ManagedSurface &surface, const Common::Rect &clickSrcBounds, const Common::Rect &destBounds, const Common::Rect &hoverSrcBounds, const Common::Rect &disabledSrcBounds) :
+Button::Button(uint16 zOrder,
+			Graphics::ManagedSurface &surface,
+			const Common::Rect &clickSrcBounds,
+			const Common::Rect &destBounds,
+			const Common::Rect &hoverSrcBounds,
+			const Common::Rect &disabledSrcBounds,
+			const Common::Rect &baseSrcBounds) :
 		RenderObject(zOrder, surface, clickSrcBounds, destBounds),
 		surf(surface),
 		_clickSrc(clickSrcBounds),
 		_hoverSrc(hoverSrcBounds),
 		_disabledSrc(disabledSrcBounds),
+		_baseSrc(baseSrcBounds),
 		_isClicked(false),
 		_isDisabled(false) {
-	setVisible(false);
+	if (!_baseSrc.isEmpty()) {
+		_drawSurface.create(surf, _baseSrc);
+		setVisible(true);
+	} else {
+		setVisible(false);
+	}
+
 	setTransparent(true);
 }
 
@@ -67,7 +80,12 @@ void Button::handleInput(NancyInput &input) {
 			}
 		}
 	} else if (!_isClicked && _isVisible) {
-		setVisible(false);
+		if (!_baseSrc.isEmpty()) {
+			_drawSurface.create(surf, _baseSrc);
+			setVisible(true);
+		} else {
+			setVisible(false);
+		}
 	}
 }
 
@@ -78,10 +96,20 @@ void Button::setDisabled(bool disabled) {
 			_drawSurface.create(surf, _disabledSrc);
 			setVisible(true);
 		} else {
-			setVisible(false);
+			if (!_baseSrc.isEmpty()) {
+				_drawSurface.create(surf, _baseSrc);
+				setVisible(true);
+			} else {
+				setVisible(false);
+			}
 		}
 	} else {
-		setVisible(false);
+		if (!_baseSrc.isEmpty()) {
+			_drawSurface.create(surf, _baseSrc);
+			setVisible(true);
+		} else {
+			setVisible(false);
+		}
 		_isDisabled = false;
 	}
 }
diff --git a/engines/nancy/ui/button.h b/engines/nancy/ui/button.h
index eef9c464c73..d2d7fcf8ac4 100644
--- a/engines/nancy/ui/button.h
+++ b/engines/nancy/ui/button.h
@@ -36,7 +36,8 @@ public:
 			const Common::Rect &clickSrcBounds,
 			const Common::Rect &destBounds,
 			const Common::Rect &hoverSrcBounds = Common::Rect(),
-			const Common::Rect &disabledSrcBounds = Common::Rect());
+			const Common::Rect &disabledSrcBounds = Common::Rect(),
+			const Common::Rect &baseSrcBounds = Common::Rect());
 	virtual ~Button() = default;
 
 	void handleInput(NancyInput &input);
@@ -47,6 +48,7 @@ public:
 	Common::Rect _clickSrc;
 	Common::Rect _hoverSrc;
 	Common::Rect _disabledSrc;
+	Common::Rect _baseSrc;
 
 	bool _isClicked;
 	bool _isDisabled;


Commit: e5b2b5d84d12202b5f4879a3726dbc1edf5c1104
    https://github.com/scummvm/scummvm/commit/e5b2b5d84d12202b5f4879a3726dbc1edf5c1104
Author: Kaloyan Chehlarski (strahy at outlook.com)
Date: 2025-11-11T11:50:18+01:00

Commit Message:
NANCY: Implement save/load menu for nancy8 and up

The newer menu has support for infinite saves, with
multiple pages. Thus, the implementation has
to be in a separate class from the existing one, though
common code is mostly not duplicated.

As a drive-by, all owned pointers are made ScopedPtr.

Changed paths:
    engines/nancy/detection.cpp
    engines/nancy/enginedata.cpp
    engines/nancy/enginedata.h
    engines/nancy/nancy.cpp
    engines/nancy/nancy.h
    engines/nancy/state/loadsave.cpp
    engines/nancy/state/loadsave.h
    engines/nancy/state/scene.cpp
    engines/nancy/ui/fullscreenimage.cpp


diff --git a/engines/nancy/detection.cpp b/engines/nancy/detection.cpp
index 50ead210a23..048bbaf496f 100644
--- a/engines/nancy/detection.cpp
+++ b/engines/nancy/detection.cpp
@@ -64,9 +64,7 @@ static const PlainGameDescriptor nancyGames[] = {
 #define NANCY2_GUIOPTIONS GUIO5(GAMEOPTION_PLAYER_SPEECH, GAMEOPTION_CHARACTER_SPEECH, GAMEOPTION_FIX_SOFTLOCKS, GAMEOPTION_NANCY2_TIMER, GAMEOPTION_ORIGINAL_SAVELOAD)
 #define NANCY5_GUIOPTIONS GUIO4(GAMEOPTION_PLAYER_SPEECH, GAMEOPTION_CHARACTER_SPEECH, GAMEOPTION_FIX_SOFTLOCKS, GAMEOPTION_ORIGINAL_SAVELOAD)
 #define NANCY6_7_GUIOPTIONS GUIO3(GAMEOPTION_AUTO_MOVE, GAMEOPTION_FIX_SOFTLOCKS, GAMEOPTION_ORIGINAL_SAVELOAD)
-// TODO: Since the original save/load menus aren't implemented for
-// Nancy 8 and newer games, we always use the ScummVM ones there
-#define NANCY8_GUIOPTIONS GUIO1(GAMEOPTION_AUTO_MOVE)
+#define NANCY8_GUIOPTIONS GUIO2(GAMEOPTION_AUTO_MOVE, GAMEOPTION_ORIGINAL_SAVELOAD)
 
 static const Nancy::NancyGameDescription gameDescriptions[] = {
 
diff --git a/engines/nancy/enginedata.cpp b/engines/nancy/enginedata.cpp
index 8aa89b4420c..4cad392d97e 100644
--- a/engines/nancy/enginedata.cpp
+++ b/engines/nancy/enginedata.cpp
@@ -516,7 +516,8 @@ LOAD::LOAD(Common::SeekableReadStream *chunkStream) :
 		readRectArray(*chunkStream, _disabledButtonSrcs, 5);
 
 		readRectArray(*chunkStream, _buttonDests, 5);
-		readRectArray(*chunkStream, _textboxBounds, 10);
+		readRectArray(*chunkStream, _textboxBounds, 9);
+		readRect(*chunkStream, _inputTextboxBounds);
 
 		chunkStream->skip(25); // prefixes and suffixes for filenames
 
diff --git a/engines/nancy/enginedata.h b/engines/nancy/enginedata.h
index cc6bfcc733f..f88540b8064 100644
--- a/engines/nancy/enginedata.h
+++ b/engines/nancy/enginedata.h
@@ -275,6 +275,7 @@ struct LOAD : public EngineData {
 	Common::Array<Common::Rect> _saveButtonDests;
 	Common::Array<Common::Rect> _loadButtonDests;
 	Common::Array<Common::Rect> _textboxBounds;
+	Common::Rect _inputTextboxBounds;
 	Common::Rect _doneButtonDest;
 	Common::Array<Common::Rect> _saveButtonDownSrcs;
 	Common::Array<Common::Rect> _loadButtonDownSrcs;
diff --git a/engines/nancy/nancy.cpp b/engines/nancy/nancy.cpp
index 43de27d0df1..f4ab8e2f4e7 100644
--- a/engines/nancy/nancy.cpp
+++ b/engines/nancy/nancy.cpp
@@ -108,6 +108,16 @@ NancyEngine *NancyEngine::create(GameType type, OSystem *syst, const NancyGameDe
 	error("Unknown GameType");
 }
 
+Common::Error NancyEngine::loadGameState(int slot) {
+	auto save = g_nancy->getMetaEngine()->querySaveMetaInfos(ConfMan.getActiveDomainName().c_str(), slot);
+	if (save.isValid() && save.getDescription() != "SECOND CHANCE") {
+		// Ensure the nancy8+ save screen will display the last non-autosave name
+		ConfMan.setInt("display_slot", slot, Common::ConfigManager::kTransientDomain);
+	}
+
+	return Engine::loadGameState(slot);
+}
+
 Common::Error NancyEngine::loadGameStream(Common::SeekableReadStream *stream) {
 	Common::Serializer ser(stream, nullptr);
 	return synchronize(ser);
@@ -262,12 +272,6 @@ Common::Error NancyEngine::run() {
 		}
 	}
 
-	// TODO: Since the original save/load menus aren't implemented for
-	// Nancy 8 and newer games, we always use the ScummVM ones there
-	if (getGameType() >= kGameTypeNancy8) {
-		ConfMan.setBool("originalsaveload", false, ConfMan.getActiveDomainName());
-	}
-
 	if (!ConfMan.getBool("originalsaveload")) {
 		ConfMan.setInt("nancy_max_saves", 999, ConfMan.getActiveDomainName());
 	}
diff --git a/engines/nancy/nancy.h b/engines/nancy/nancy.h
index 7f21c5c5406..5dd080cf913 100644
--- a/engines/nancy/nancy.h
+++ b/engines/nancy/nancy.h
@@ -83,6 +83,7 @@ public:
 	void errorString(const char *buf_input, char *buf_output, int buf_output_size) override;
 	bool hasFeature(EngineFeature f) const override;
 
+	Common::Error loadGameState(int slot) override;
 	Common::Error loadGameStream(Common::SeekableReadStream *stream) override;
 	Common::Error saveGameStream(Common::WriteStream *stream, bool isAutosave = false) override;
 	bool canLoadGameStateCurrently(Common::U32String *msg = nullptr) override;
diff --git a/engines/nancy/state/loadsave.cpp b/engines/nancy/state/loadsave.cpp
index 3556b5e2002..cd68299e3b9 100644
--- a/engines/nancy/state/loadsave.cpp
+++ b/engines/nancy/state/loadsave.cpp
@@ -39,31 +39,22 @@
 
 namespace Common {
 DECLARE_SINGLETON(Nancy::State::LoadSaveMenu);
+
+template<>
+Nancy::State::LoadSaveMenu *Singleton<Nancy::State::LoadSaveMenu>::makeInstance() {
+	if (Nancy::g_nancy->getGameType() <= Nancy::kGameTypeNancy7) {
+		return new Nancy::State::LoadSaveMenu_V1();
+	} else {
+		return new Nancy::State::LoadSaveMenu_V2();
+	}
+}
+
 }
 
 namespace Nancy {
 namespace State {
 
 LoadSaveMenu::~LoadSaveMenu() {
-	for (auto *tb : _textboxes) {
-		delete tb;
-	}
-
-	for (auto *button : _loadButtons) {
-		delete button;
-	}
-
-	for (auto *button : _saveButtons) {
-		delete button;
-	}
-
-	for (auto *overlay : _cancelButtonOverlays) {
-		delete overlay;
-	}
-
-	delete _exitButton;
-	delete _cancelButton;
-
 	g_nancy->_input->setVKEnabled(false);
 }
 
@@ -103,6 +94,44 @@ void LoadSaveMenu::process() {
 	g_nancy->_cursor->setCursorType(CursorManager::kNormalArrow);
 }
 
+void LoadSaveMenu::onStateEnter(const NancyState::NancyState prevState) {
+	if (!ConfMan.getBool("originalsaveload")) {
+		bool saveAndQuit = false;
+		if (ConfMan.hasKey("sdlg_save_and_quit", Common::ConfigManager::kTransientDomain)) {
+			saveAndQuit = ConfMan.getBool("sdlg_save_and_quit", Common::ConfigManager::kTransientDomain);
+			ConfMan.removeKey("sdlg_save_and_quit", Common::ConfigManager::kTransientDomain);
+		}
+
+		// Display the question dialog if we are in a scene, and if we are not
+		// in the middle of quitting the game, and a save has been requested
+		if (Nancy::State::Scene::hasInstance() && !saveAndQuit) {
+			GUI::MessageDialog saveOrLoad(_("Would you like to load or save a game?"), _("Load"), _("Save"));
+
+			int choice = saveOrLoad.runModal();
+			if (choice == GUI::kMessageOK)
+				scummVMLoad();
+			else
+				scummVMSave();
+		} else if (saveAndQuit) {
+			scummVMSave();
+		} else {
+			scummVMLoad();
+		}
+
+		return;
+	}
+
+	if (_state == kEnterFilename) {
+		g_nancy->_input->setVKEnabled(true);
+	}
+	registerGraphics();
+}
+
+bool LoadSaveMenu::onStateExit(const NancyState::NancyState nextState) {
+	g_nancy->_input->setVKEnabled(false);
+	return _destroyOnExit;
+}
+
 void LoadSaveMenu::scummVMSave() {
 	GUI::SaveLoadChooser *dialog = new GUI::SaveLoadChooser(_("Save game:"), _("Save"), true);
 	int slot = dialog->runModalWithCurrentTarget();
@@ -140,67 +169,143 @@ void LoadSaveMenu::scummVMLoad() {
 	}
 }
 
-void LoadSaveMenu::onStateEnter(const NancyState::NancyState prevState) {
-	if (!ConfMan.getBool("originalsaveload")) {
-		bool saveAndQuit = false;
-		if (ConfMan.hasKey("sdlg_save_and_quit", Common::ConfigManager::kTransientDomain)) {
-			saveAndQuit = ConfMan.getBool("sdlg_save_and_quit", Common::ConfigManager::kTransientDomain);
-			ConfMan.removeKey("sdlg_save_and_quit", Common::ConfigManager::kTransientDomain);
+void LoadSaveMenu::enterFilename() {
+	// Handle keyboard input and cursor blinking
+	uint32 gameTime = g_nancy->getTotalPlayTime();
+	if (_loadSaveData->_blinkingTimeDelay != 0 && gameTime > _nextBlink) {
+		_blinkingCursorOverlay.setVisible(!_blinkingCursorOverlay.isVisible());
+		_nextBlink = gameTime + _loadSaveData->_blinkingTimeDelay;
+	}
+
+	NancyInput input = g_nancy->_input->getInput();
+
+	for (uint i = 0; i < input.otherKbdInput.size(); ++i) {
+		Common::KeyState &key = input.otherKbdInput[i];
+		if (key.keycode == Common::KEYCODE_BACKSPACE) {
+			if (_enteredString.size()) {
+				_enteredString.deleteLastChar();
+			}
+		} else if (Common::isAlnum(key.ascii) || Common::isSpace(key.ascii)) {
+			_enteredString += key.ascii;
 		}
+	}
 
-		// Display the question dialog if we are in a scene, and if we are not
-		// in the middle of quitting the game, and a save has been requested
-		if (Nancy::State::Scene::hasInstance() && !saveAndQuit) {
-			GUI::MessageDialog saveOrLoad(_("Would you like to load or save a game?"), _("Load"), _("Save"));
+	if (_exitButton && !_exitButton->_isDisabled) {
+		_exitButton->handleInput(input);
+		if (_exitButton->_isClicked) {
+			_state = kStop;
+			g_nancy->_sound->playSound("BUOK");
+			g_nancy->_input->setVKEnabled(false);
+			return;
+		}
+	}
+}
 
-			int choice = saveOrLoad.runModal();
-			if (choice == GUI::kMessageOK)
-				scummVMLoad();
-			else
-				scummVMSave();
-		} else if (saveAndQuit) {
-			scummVMSave();
+void LoadSaveMenu::load() {
+	auto *sdlg = GetEngineData(SDLG);
+
+	if (sdlg && sdlg->dialogs.size() > 1 && Nancy::State::Scene::hasInstance() && !g_nancy->_hasJustSaved) {
+		// nancy6 added a "Do you want load without saving" dialog.
+		if (!ConfMan.hasKey("sdlg_return", Common::ConfigManager::kTransientDomain)) {
+			// Request the dialog
+			ConfMan.setInt("sdlg_id", 2, Common::ConfigManager::kTransientDomain);
+			_destroyOnExit = false;
+			g_nancy->setState(NancyState::kSaveDialog);
+			return;
 		} else {
-			scummVMLoad();
+			// Dialog has returned
+			_destroyOnExit = true;
+			g_nancy->_graphics->suppressNextDraw();
+			uint ret = ConfMan.getInt("sdlg_return", Common::ConfigManager::kTransientDomain);
+			ConfMan.removeKey("sdlg_return", Common::ConfigManager::kTransientDomain);
+			switch (ret) {
+			case 1 :
+				// "No" keeps us in the LoadSave state but doesn't load
+				_state = kRun;
+				return;
+			case 2 :
+				// "Cancel" returns to the main menu
+				g_nancy->setState(NancyState::kMainMenu);
+				return;
+			default:
+				// "Yes" actually loads
+				break;
+			}
 		}
+	}
 
-		return;
+	if (Nancy::State::Scene::hasInstance()) {
+		Nancy::State::Scene::destroy();
 	}
 
-	if (_state == kEnterFilename) {
-		g_nancy->_input->setVKEnabled(true);
+	ConfMan.setInt("save_slot", scummVMSaveSlotToLoad(), Common::ConfigManager::kTransientDomain);
+	ConfMan.setInt("display_slot", scummVMSaveSlotToLoad(), Common::ConfigManager::kTransientDomain); // Used to load the save name
+
+	_state = kStop;
+	_enteringNewState = true;
+}
+
+void LoadSaveMenu::success() {
+	if (_enteringNewState) {
+		_nextBlink = g_nancy->getTotalPlayTime() + 2000; // Hardcoded
+		_successOverlay.setVisible(true);
+		_enteringNewState = false;
+	}
+
+	if (g_nancy->getTotalPlayTime() > _nextBlink) {
+		_state = kStop;
+		_selectedSave = 0;
+
+		_enteringNewState = true;
 	}
-	registerGraphics();
 }
 
-bool LoadSaveMenu::onStateExit(const NancyState::NancyState nextState) {
-	g_nancy->_input->setVKEnabled(false);
-	return _destroyOnExit;
+void LoadSaveMenu::stop() {
+	if (_selectedSave != -1) {
+		g_nancy->setState(NancyState::kScene);
+	} else {
+		g_nancy->setState(NancyState::kMainMenu);
+	}
 }
 
 void LoadSaveMenu::registerGraphics() {
+	for (auto &tb : _textboxes) {
+		tb->registerGraphics();
+	}
+
+	if (_exitButton) {
+		_exitButton->registerGraphics();
+	}
+}
+
+uint16 LoadSaveMenu::writeToTextbox(int textboxID, const Common::String &text, const Font *font) {
+	assert(font);
+
+	_textboxes[textboxID]->_drawSurface.clear(g_nancy->_graphics->getTransColor());
+	Common::Point destPoint(_loadSaveData->_fontXOffset, _loadSaveData->_fontYOffset + _textboxes[textboxID]->_drawSurface.h - font->getFontHeight());
+	font->drawString(&_textboxes[textboxID]->_drawSurface, text, destPoint.x, destPoint.y, _textboxes[textboxID]->_drawSurface.w, 0);
+	_textboxes[textboxID]->setVisible(true);
+
+	return font->getStringWidth(text);
+}
+
+void LoadSaveMenu_V1::registerGraphics() {
+	LoadSaveMenu::registerGraphics();
+
 	_background.registerGraphics();
 
-	for (auto *button : _loadButtons) {
+	for (auto &button : _loadButtons) {
 		button->registerGraphics();
 	}
 
-	for (auto *button : _saveButtons) {
+	for (auto &button : _saveButtons) {
 		button->registerGraphics();
 	}
 
-	for (auto *overlay : _cancelButtonOverlays) {
+	for (auto &overlay : _cancelButtonOverlays) {
 		overlay->registerGraphics();
 	}
 
-	for (auto *tb : _textboxes) {
-		tb->registerGraphics();
-	}
-
-	if (_exitButton) {
-		_exitButton->registerGraphics();
-	}
-
 	if (_cancelButton) {
 		_cancelButton->registerGraphics();
 	}
@@ -211,7 +316,7 @@ void LoadSaveMenu::registerGraphics() {
 	g_nancy->_graphics->redrawAll();
 }
 
-void LoadSaveMenu::init() {
+void LoadSaveMenu_V1::init() {
 	_loadSaveData = GetEngineData(LOAD);
 	assert(_loadSaveData);
 
@@ -232,7 +337,7 @@ void LoadSaveMenu::init() {
 	for (uint i = 0; i < _textboxes.size(); ++i) {
 		// Load textbox objects
 		RenderObject *newTb = new RenderObject(5);
-		_textboxes[i] = newTb;
+		_textboxes[i].reset(newTb);
 		const Common::Rect &bounds = _loadSaveData->_textboxBounds[i];
 		newTb->_drawSurface.create(bounds.width(), bounds.height(), g_nancy->_graphics->getScreenPixelFormat());
 		newTb->_drawSurface.clear(g_nancy->_graphics->getTransColor());
@@ -263,18 +368,18 @@ void LoadSaveMenu::init() {
 	_cancelButtonOverlays.resize(_textboxes.size());
 	for (uint i = 0; i < _loadButtons.size(); ++i) {
 		// Load Save and Load buttons, and Cancel overlays
-		_loadButtons[i] = new UI::Button(1, _background._drawSurface,
+		_loadButtons[i].reset(new UI::Button(1, _background._drawSurface,
 			_loadSaveData->_loadButtonDownSrcs[i], _loadSaveData->_loadButtonDests[i],
 			hasHighlights ? _loadSaveData->_loadButtonHighlightSrcs[i] : Common::Rect(),
-			hasHighlights ? _loadSaveData->_loadButtonDisabledSrcs[i] : Common::Rect());
+			hasHighlights ? _loadSaveData->_loadButtonDisabledSrcs[i] : Common::Rect()));
 
-		_saveButtons[i] = new UI::Button(1, _background._drawSurface,
+		_saveButtons[i].reset(new UI::Button(1, _background._drawSurface,
 			_loadSaveData->_saveButtonDownSrcs[i], _loadSaveData->_saveButtonDests[i],
 			hasHighlights ? _loadSaveData->_saveButtonHighlightSrcs[i] : Common::Rect(),
-			hasHighlights ? _loadSaveData->_saveButtonDisabledSrcs[i] : Common::Rect());
+			hasHighlights ? _loadSaveData->_saveButtonDisabledSrcs[i] : Common::Rect()));
 
-		_cancelButtonOverlays[i] = new RenderObject(2, _background._drawSurface,
-			_loadSaveData->_cancelButtonSrcs[i], _loadSaveData->_cancelButtonDests[i]);
+		_cancelButtonOverlays[i].reset(new RenderObject(2, _background._drawSurface,
+			_loadSaveData->_cancelButtonSrcs[i], _loadSaveData->_cancelButtonDests[i]));
 
 		_loadButtons[i]->init();
 		_saveButtons[i]->init();
@@ -282,10 +387,10 @@ void LoadSaveMenu::init() {
 	}
 
 	// Load exit button
-	_exitButton = new UI::Button(3, _background._drawSurface,
+	_exitButton.reset(new UI::Button(3, _background._drawSurface,
 			_loadSaveData->_doneButtonDownSrc, _loadSaveData->_doneButtonDest,
 			hasHighlights ? _loadSaveData->_doneButtonHighlightSrc : Common::Rect(),
-			hasHighlights ? _loadSaveData->_doneButtonDisabledSrc : Common::Rect());
+			hasHighlights ? _loadSaveData->_doneButtonDisabledSrc : Common::Rect()));
 
 	// Load Cancel button that activates when typing a filename
 	// Note: this is only responsible for the hover/mouse down/disabled graphic;
@@ -293,9 +398,9 @@ void LoadSaveMenu::init() {
 	// We also make sure this has an invalid position until we need it.
 	Common::Rect pos = _loadSaveData->_cancelButtonDests[0];
 	pos.moveTo(-500, 0);
-	_cancelButton = new UI::Button(3, _background._drawSurface,
+	_cancelButton.reset(new UI::Button(3, _background._drawSurface,
 		_loadSaveData->_cancelButtonDownSrc, Common::Rect(),
-		_loadSaveData->_cancelButtonHighlightSrc, _loadSaveData->_cancelButtonDisabledSrc);
+		_loadSaveData->_cancelButtonHighlightSrc, _loadSaveData->_cancelButtonDisabledSrc));
 
 	// Load the blinking cursor graphic that appears while typing a filename
 	_blinkingCursorOverlay._drawSurface.create(_loadSaveData->_blinkingCursorSrc.width(),
@@ -323,7 +428,7 @@ void LoadSaveMenu::init() {
 	_enteringNewState = true;
 }
 
-void LoadSaveMenu::run() {
+void LoadSaveMenu_V1::run() {
 	if (_enteringNewState) {
 		// State has changed, revert all relevant objects to an appropriate state
 		for (uint i = 0; i < _textboxes.size(); ++i) {
@@ -447,7 +552,7 @@ void LoadSaveMenu::run() {
 	}
 }
 
-void LoadSaveMenu::enterFilename() {
+void LoadSaveMenu_V1::enterFilename() {
 	if (_enteringNewState) {
 		// State has changed, revert all relevant objects to an appropriate state
 		if (_cancelButton) {
@@ -479,34 +584,27 @@ void LoadSaveMenu::enterFilename() {
 		g_nancy->_input->setVKEnabled(true);
 	}
 
-	// Perform cursor blinking
-	uint32 gameTime = g_nancy->getTotalPlayTime();
-	if (_loadSaveData->_blinkingTimeDelay != 0 && gameTime > _nextBlink) {
-		_blinkingCursorOverlay.setVisible(!_blinkingCursorOverlay.isVisible());
-		_nextBlink = gameTime + _loadSaveData->_blinkingTimeDelay;
-	}
+	LoadSaveMenu::enterFilename();
 
 	// Handle input
 	NancyInput input = g_nancy->_input->getInput();
 
-	// Improvement: we allow the enter key to sumbit
 	bool enterKeyPressed = false;
-	for (uint i = 0; i < input.otherKbdInput.size(); ++i) {
-		Common::KeyState &key = input.otherKbdInput[i];
-		if (key.keycode == Common::KEYCODE_BACKSPACE) {
-			if (_enteredString.size()) {
-				_enteredString.deleteLastChar();
-			}
-		} else if (key.keycode == Common::KEYCODE_RETURN || key.keycode == Common::KEYCODE_KP_ENTER) {
-			enterKeyPressed = true;
-		} else if (Common::isAlnum(key.ascii) || Common::isSpace(key.ascii)) {
-			_enteredString += key.ascii;
-		}
-
+	if (input.otherKbdInput.size()) {
 		uint16 textWidthInPixels = writeToTextbox(_selectedSave, _enteredString, _highlightFont);
 		Common::Rect tbPosition = _textboxes[_selectedSave]->getScreenPosition();
+		Common::Rect lastCursorPosition = _blinkingCursorOverlay.getScreenPosition();
 		_blinkingCursorOverlay.moveTo(Common::Point(tbPosition.left + textWidthInPixels,
-			tbPosition.bottom - _blinkingCursorOverlay._drawSurface.h + _loadSaveData->_fontYOffset));
+			lastCursorPosition.top));
+
+		if (	input.otherKbdInput.back().keycode == Common::KEYCODE_RETURN ||
+				input.otherKbdInput.back().keycode == Common::KEYCODE_KP_ENTER) {
+			enterKeyPressed = true;
+		}
+	}
+
+	if (_state != kEnterFilename) {
+		return;
 	}
 
 	_cancelButton->handleInput(input);
@@ -528,7 +626,7 @@ void LoadSaveMenu::enterFilename() {
 	}
 }
 
-void LoadSaveMenu::save() {
+void LoadSaveMenu_V1::save() {
 	auto *sdlg = GetEngineData(SDLG);
 
 	if (sdlg && sdlg->dialogs.size() > 1) {
@@ -623,80 +721,673 @@ void LoadSaveMenu::save() {
 	g_nancy->_hasJustSaved = true;
 }
 
-void LoadSaveMenu::load() {
-	auto *sdlg = GetEngineData(SDLG);
+int LoadSaveMenu_V1::scummVMSaveSlotToLoad() const {
+	return _selectedSave + 1;
+}
 
-	if (sdlg && sdlg->dialogs.size() > 1 && Nancy::State::Scene::hasInstance() && !g_nancy->_hasJustSaved) {
-		// nancy6 added a "Do you want load without saving" dialog.
-		if (!ConfMan.hasKey("sdlg_return", Common::ConfigManager::kTransientDomain)) {
-			// Request the dialog
-			ConfMan.setInt("sdlg_id", 2, Common::ConfigManager::kTransientDomain);
-			_destroyOnExit = false;
-			g_nancy->setState(NancyState::kSaveDialog);
-			return;
-		} else {
-			// Dialog has returned
-			_destroyOnExit = true;
-			g_nancy->_graphics->suppressNextDraw();
-			uint ret = ConfMan.getInt("sdlg_return", Common::ConfigManager::kTransientDomain);
-			ConfMan.removeKey("sdlg_return", Common::ConfigManager::kTransientDomain);
-			switch (ret) {
-			case 1 :
-				// "No" keeps us in the LoadSave state but doesn't load
-				_state = kRun;
-				return;
-			case 2 :
-				// "Cancel" returns to the main menu
-				g_nancy->setState(NancyState::kMainMenu);
-				return;
-			default:
-				// "Yes" actually loads
-				break;
-			}
-		}
+enum { kInputTextboxIndex = -2 };
+
+void LoadSaveMenu_V2::registerGraphics() {
+	LoadSaveMenu::registerGraphics();
+
+	_background1.registerGraphics();
+	_background2.registerGraphics();
+
+	if (_loadButton) {
+		_loadButton->registerGraphics();
 	}
 
-	if (Nancy::State::Scene::hasInstance()) {
-		Nancy::State::Scene::destroy();
+	if (_saveButton) {
+		_saveButton->registerGraphics();
 	}
 
-	ConfMan.setInt("save_slot", _selectedSave + 1, Common::ConfigManager::kTransientDomain);
+	if (_exitButton) {
+		_exitButton->registerGraphics();
+	}
 
-	_state = kStop;
-	_enteringNewState = true;
-}
+	if (_pageUpButton) {
+		_pageUpButton->registerGraphics();
+	}
 
-void LoadSaveMenu::success() {
-	// The original engine still lets the cursor blink in the background, but implementing that is completely unnecessary
-	if (_enteringNewState) {
-		_nextBlink = g_nancy->getTotalPlayTime() + 2000; // Hardcoded
-		_successOverlay.setVisible(true);
-		_enteringNewState = false;
+	if (_pageDownButton) {
+		_pageDownButton->registerGraphics();
 	}
 
-	if (g_nancy->getTotalPlayTime() > _nextBlink) {
-		_state = kRun;
-		_enteringNewState = true;
+	if (_inputTextbox) {
+		_inputTextbox->registerGraphics();
 	}
+
+	_blinkingCursorOverlay.registerGraphics();
+	_successOverlay.registerGraphics();
+
+	g_nancy->_graphics->redrawAll();
 }
 
-void LoadSaveMenu::stop() {
-	if (_selectedSave != -1) {
-		g_nancy->setState(NancyState::kScene);
+void LoadSaveMenu_V2::init() {
+	_loadSaveData = GetEngineData(LOAD);
+	assert(_loadSaveData);
+
+	_background1.init(_loadSaveData->_image1Name);
+	_background2.init(_loadSaveData->_image2Name);
+
+	_baseFont = g_nancy->_graphics->getFont(_loadSaveData->_mainFontID);
+
+	if (_loadSaveData->_highlightFontID != -1) {
+		_highlightFont = g_nancy->_graphics->getFont(_loadSaveData->_highlightFontID);
 	} else {
-		g_nancy->setState(NancyState::kMainMenu);
+		_highlightFont = _baseFont;
+	}
+
+	if (_loadSaveData->_disabledFontID != -1) {
+		_disabledFont = g_nancy->_graphics->getFont(_loadSaveData->_disabledFontID);
+	} else {
+		_disabledFont = _baseFont;
+	}
+
+	_sortedSavesList = g_nancy->getMetaEngine()->listSaves(ConfMan.getActiveDomainName().c_str());
+	filterAndSortSaveStates();
+
+	_textboxes.resize(_loadSaveData->_textboxBounds.size());
+	for (uint i = 0; i < _textboxes.size(); ++i) {
+		// Load textbox objects
+		RenderObject *newTb = new RenderObject(5);
+		_textboxes[i].reset(newTb);
+		const Common::Rect &bounds = _loadSaveData->_textboxBounds[i];
+		newTb->_drawSurface.create(bounds.width(), bounds.height(), g_nancy->_graphics->getScreenPixelFormat());
+		newTb->_drawSurface.clear(g_nancy->_graphics->getTransColor());
+		newTb->moveTo(bounds);
+		newTb->setTransparent(true);
+		newTb->setVisible(true);
+		newTb->init();
+	}
+
+	if (!_loadSaveData->_inputTextboxBounds.isEmpty()) {
+		_inputTextbox.reset(new RenderObject(5));
+		const Common::Rect &bounds = _loadSaveData->_inputTextboxBounds;
+		_inputTextbox->_drawSurface.create(bounds.width(), bounds.height(), g_nancy->_graphics->getScreenPixelFormat());
+		_inputTextbox->_drawSurface.clear(g_nancy->_graphics->getTransColor());
+		_inputTextbox->moveTo(bounds);
+		_inputTextbox->setTransparent(true);
+		_inputTextbox->setVisible(true);
+		_inputTextbox->init();
+	}
+
+	_filenameStrings.resize(_loadSaveData->_textboxBounds.size());
+	_saveExists.resize(_filenameStrings.size(), false);
+
+	// Five buttons total
+	g_nancy->_resource->loadImage(_loadSaveData->_imageButtonsName, _buttonsImage);
+
+	_saveButton.reset(new UI::Button(1, _buttonsImage,
+			_loadSaveData->_pressedButtonSrcs[0],
+			_loadSaveData->_buttonDests[0],
+			_loadSaveData->_highlightedButtonSrcs[0],
+			_loadSaveData->_disabledButtonSrcs[0],
+			_loadSaveData->_unpressedButtonSrcs[0]));
+	_pageUpButton.reset(new UI::Button(1, _buttonsImage,
+			_loadSaveData->_pressedButtonSrcs[1],
+			_loadSaveData->_buttonDests[1],
+			_loadSaveData->_highlightedButtonSrcs[1],
+			_loadSaveData->_disabledButtonSrcs[1],
+			_loadSaveData->_unpressedButtonSrcs[1]));
+	_pageDownButton.reset(new UI::Button(1, _buttonsImage,
+			_loadSaveData->_pressedButtonSrcs[2],
+			_loadSaveData->_buttonDests[2],
+			_loadSaveData->_highlightedButtonSrcs[2],
+			_loadSaveData->_disabledButtonSrcs[2],
+			_loadSaveData->_unpressedButtonSrcs[2]));
+	_loadButton.reset(new UI::Button(1, _buttonsImage,
+			_loadSaveData->_pressedButtonSrcs[3],
+			_loadSaveData->_buttonDests[3],
+			_loadSaveData->_highlightedButtonSrcs[3],
+			_loadSaveData->_disabledButtonSrcs[3],
+			_loadSaveData->_unpressedButtonSrcs[3]));
+	_exitButton.reset(new UI::Button(1, _buttonsImage,
+			_loadSaveData->_pressedButtonSrcs[4],
+			_loadSaveData->_buttonDests[4],
+			_loadSaveData->_highlightedButtonSrcs[4],
+			_loadSaveData->_disabledButtonSrcs[4],
+			_loadSaveData->_unpressedButtonSrcs[4]));
+
+	// Load the blinking cursor graphic that appears while typing a filename
+	if (!_loadSaveData->_blinkingCursorSrc.isEmpty()) {
+		_blinkingCursorOverlay._drawSurface.create(_loadSaveData->_blinkingCursorSrc.width(),
+			_loadSaveData->_blinkingCursorSrc.height(),
+			g_nancy->_graphics->getScreenPixelFormat());
+		_blinkingCursorOverlay.setTransparent(true);
+		_blinkingCursorOverlay.setVisible(false);
+		_blinkingCursorOverlay._drawSurface.clear(_blinkingCursorOverlay._drawSurface.getTransparentColor());
+		_blinkingCursorOverlay._drawSurface.transBlitFrom(_highlightFont->getImageSurface(), _loadSaveData->_blinkingCursorSrc,
+			Common::Point(), g_nancy->_graphics->getTransColor());
+	} else {
+		Common::Rect bounds = _highlightFont->getBoundingBox('-');
+		_blinkingCursorOverlay._drawSurface.create(bounds.width() + 2, bounds.height(),
+			g_nancy->_graphics->getScreenPixelFormat());
+		_blinkingCursorOverlay.setTransparent(true);
+		_blinkingCursorOverlay.setVisible(false);
+		_blinkingCursorOverlay._drawSurface.clear(_blinkingCursorOverlay._drawSurface.getTransparentColor());
+		_highlightFont->drawChar(_blinkingCursorOverlay._drawSurface.surfacePtr(), '-', 2, 0, 0);
+	}
+
+	// Load the "Your game has been saved" popup graphic
+	if (!_loadSaveData->_gameSavedPopup.empty()) {
+		g_nancy->_resource->loadImage(_loadSaveData->_gameSavedPopup, _successOverlay._drawSurface);
+		Common::Rect destBounds = Common::Rect(0,0, _successOverlay._drawSurface.w, _successOverlay._drawSurface.h);
+		destBounds.moveTo(640 / 2 - destBounds.width() / 2,
+			480 / 2 - destBounds.height() / 2);
+		_successOverlay.moveTo(destBounds);
+		_successOverlay.setVisible(false);
+	}
+
+	registerGraphics();
+
+	_state = kRun;
+	_enteringNewState = true;
+}
+
+void LoadSaveMenu_V2::run() {
+	if (_enteringNewState) {
+		// State has changed, revert all relevant objects to an appropriate state
+		goToPage(_currentPage);
+		for (uint i = 0; i < _textboxes.size(); ++i) {
+			writeToTextbox(i, _filenameStrings[i], Nancy::State::Scene::hasInstance() ? _baseFont : _disabledFont);
+		}
+
+		if (_currentPage == 0) {
+			_saveButton->setDisabled(!Nancy::State::Scene::hasInstance());
+			_saveButton->_isClicked = false;
+			_inputTextbox->setVisible(true);
+			_textboxes[0]->setVisible(false);
+		} else {
+			_saveButton->setDisabled(true);
+			_saveButton->setVisible(false);
+			_inputTextbox->setVisible(false);
+			_textboxes[0]->setVisible(true);
+		}
+
+		_loadButton->setDisabled(_selectedSave == -1);
+		_loadButton->_isClicked = false;
+
+		_blinkingCursorOverlay.setVisible(false);
+		_exitButton->setDisabled(false);
+		_successOverlay.setVisible(false);
+
+		_hoveredSave = -1;
+		_enteringNewState = false;
+	}
+
+	// Handle input
+	NancyInput input = g_nancy->_input->getInput();
+
+	_loadButton->handleInput(input);
+
+	if (_loadButton->_isClicked) {
+		uint index = _selectedSave;
+		if (_saveExists[index]) {
+			_state = kLoad;
+			_enteringNewState = true;
+			_selectedSave = index;
+			g_nancy->_sound->playSound("BULS");
+		}
+
+		return;
+	}
+
+	_saveButton->handleInput(input);
+
+	if (_saveButton->_isClicked) {
+		if (Nancy::State::Scene::hasInstance()) {
+			_state = kSave;
+			_enteringNewState = true;
+			g_nancy->_sound->playSound("BULS");
+		}
+
+		return;
+	}
+
+	if (_pageUpButton) {
+		_pageUpButton->handleInput(input);
+		if (_pageUpButton->_isClicked) {
+			--_currentPage;
+			g_nancy->_sound->playSound("BUOK");
+			_enteringNewState = true;
+			_pageUpButton->_isClicked = false;
+			return;
+		}
+	}
+
+	if (_pageDownButton) {
+		_pageDownButton->handleInput(input);
+		if (_pageDownButton->_isClicked) {
+			++_currentPage;
+			g_nancy->_sound->playSound("BUOK");
+			_enteringNewState = true;
+			_pageDownButton->_isClicked = false;
+			return;
+		}
+	}
+
+	// Handle textbox hovering
+	bool hoversOverTextbox = false;
+	for (int i = 0; i < (int)_textboxes.size(); ++i) {
+		uint i2 = (_currentPage == 0 ? i + 1 : i);
+		if (i2 >= _textboxes.size()) {
+			break;
+		}
+
+		if (_textboxes[i2]->getScreenPosition().contains(input.mousePos)) {
+			hoversOverTextbox = true;
+			if (_hoveredSave != i) {
+				if (_hoveredSave != -1 && _hoveredSave != _selectedSave) {
+					if (_hoveredSave == kInputTextboxIndex) {
+						writeToInputTextbox(_baseFont);
+					} else {
+						writeToTextbox(_hoveredSave, _filenameStrings[_hoveredSave], _baseFont);
+					}
+				}
+
+				_hoveredSave = i;
+				writeToTextbox(_hoveredSave, _filenameStrings[_hoveredSave], _highlightFont);
+			}
+
+			if (input.input & NancyInput::kLeftMouseButtonUp && (_filenameStrings[_hoveredSave].size())) {
+				if (_selectedSave != -1) {
+					writeToTextbox(_selectedSave, _filenameStrings[_selectedSave], _baseFont);
+				}
+
+				_loadButton->setDisabled(false);
+
+				_hoveredSave = -1;
+				_selectedSave = i;
+
+				return;
+			}
+
+			break;
+		}
+	}
+
+	if (Nancy::State::Scene::hasInstance() && _currentPage == 0 &&
+			_inputTextbox && _inputTextbox->getScreenPosition().contains(input.mousePos)) {
+		hoversOverTextbox = true;
+
+		if (_hoveredSave != kInputTextboxIndex && _hoveredSave != -1) {
+			writeToTextbox(_hoveredSave, _filenameStrings[_hoveredSave], _baseFont);
+		}
+
+		if (_selectedSave != -1) {
+			writeToTextbox(_selectedSave, _filenameStrings[_selectedSave], _baseFont);
+		}
+
+		_hoveredSave = kInputTextboxIndex;
+		writeToInputTextbox(_highlightFont);
+
+		if (input.input & NancyInput::kLeftMouseButtonUp) {
+			_state = kEnterFilename;
+			_enteringNewState = true;
+
+			return;
+		}
+	}
+
+	if (!hoversOverTextbox && _hoveredSave != -1 && _hoveredSave != _selectedSave) {
+		if (_hoveredSave == kInputTextboxIndex) {
+			writeToInputTextbox(_baseFont);
+		} else {
+			writeToTextbox(_hoveredSave, _filenameStrings[_hoveredSave], _baseFont);
+		}
+		_hoveredSave = -1;
+	}
+
+	// Check Done button
+	if (_exitButton) {
+		_exitButton->handleInput(input);
+
+		if (_exitButton->_isClicked) {
+			_state = kStop;
+			g_nancy->_sound->playSound("BUOK");
+			return;
+		}
+	}
+}
+
+void LoadSaveMenu_V2::enterFilename() {
+	if (_enteringNewState) {
+		// State has changed, revert all relevant objects to an appropriate state
+		_hoveredSave = -1;
+		_loadButton->setDisabled(true);
+
+		Common::Rect tbPosition = _inputTextbox->getScreenPosition();
+		Common::Rect cursorRect = _blinkingCursorOverlay._drawSurface.getBounds();
+		cursorRect.moveTo(tbPosition.left + _highlightFont->getStringWidth(_enteredString),
+			tbPosition.bottom - _blinkingCursorOverlay._drawSurface.h + _loadSaveData->_fontYOffset);
+		cursorRect.translate(0, cursorRect.height() / 2 - 1);
+		_blinkingCursorOverlay.moveTo(cursorRect);
+		_blinkingCursorOverlay.setVisible(true);
+		_nextBlink = g_nancy->getTotalPlayTime() + _loadSaveData->_blinkingTimeDelay;
+		_enteringNewState = false;
+		g_nancy->_input->setVKEnabled(true);
+	}
+
+	LoadSaveMenu::enterFilename();
+
+	// Handle input
+	NancyInput input = g_nancy->_input->getInput();
+
+	bool enterKeyPressed = false;
+	if (input.otherKbdInput.size()) {
+		uint16 textWidthInPixels = writeToInputTextbox(_highlightFont);
+		Common::Rect tbPosition = _inputTextbox->getScreenPosition();
+		Common::Rect lastCursorPosition = _blinkingCursorOverlay.getScreenPosition();
+		_blinkingCursorOverlay.moveTo(Common::Point(tbPosition.left + textWidthInPixels,
+			lastCursorPosition.top));
+
+		if (	input.otherKbdInput.back().keycode == Common::KEYCODE_RETURN ||
+				input.otherKbdInput.back().keycode == Common::KEYCODE_KP_ENTER) {
+			enterKeyPressed = true;
+		}
+	}
+
+	if (_state != kEnterFilename) {
+		return;
+	}
+
+	if (_pageUpButton) {
+		_pageUpButton->handleInput(input);
+		if (_pageUpButton->_isClicked) {
+			--_currentPage;
+			_state = kRun;
+			_enteringNewState = true;
+			_pageUpButton->_isClicked = false;
+			return;
+		}
+	}
+
+	if (_pageDownButton) {
+		_pageDownButton->handleInput(input);
+		if (_pageDownButton->_isClicked) {
+			// Redraw input textbox so it's not highlighted when user goes back up
+			writeToInputTextbox(_baseFont);
+
+			++_currentPage;
+			_state = kRun;
+			_enteringNewState = true;
+			_pageDownButton->_isClicked = false;
+			return;
+		}
+	}
+
+	_saveButton->handleInput(input);
+	if (_saveButton->_isClicked || enterKeyPressed) {
+		_state = kSave;
+		_enteringNewState = true;
+		g_nancy->_sound->playSound("BULS");
+		g_nancy->_input->setVKEnabled(false);
+		return;
+	}
+
+	// Handle hovering over other saves
+	bool hoversOverTextbox = false;
+	for (int i = 0; i < (int)_textboxes.size(); ++i) {
+		uint i2 = i + 1;
+		if (i2 >= _textboxes.size()) {
+			break;
+		}
+
+		if (_textboxes[i2]->getScreenPosition().contains(input.mousePos)) {
+			hoversOverTextbox = true;
+			if (_hoveredSave != i) {
+				if (_hoveredSave != -1) {
+					writeToTextbox(_hoveredSave, _filenameStrings[_hoveredSave], _baseFont);
+				}
+
+				_hoveredSave = i;
+				writeToTextbox(_hoveredSave, _filenameStrings[_hoveredSave], _highlightFont);
+			}
+
+			if (input.input & NancyInput::kLeftMouseButtonUp) {
+				writeToInputTextbox(_baseFont);
+				_state = kRun;
+				_enteringNewState = true;
+
+				_hoveredSave = -1;
+				_selectedSave = i;
+
+				return;
+			}
+
+			break;
+		}
+	}
+
+	if (!hoversOverTextbox && _hoveredSave != -1 && _hoveredSave != _selectedSave) {
+		writeToTextbox(_hoveredSave, _filenameStrings[_hoveredSave], _baseFont);
+		_hoveredSave = -1;
 	}
 }
 
-uint16 LoadSaveMenu::writeToTextbox(uint textboxID, const Common::String &text, const Font *font) {
+void LoadSaveMenu_V2::save() {
+	Common::String finalDesc = _enteredString;
+
+	// Look for a state with a matching name and overwrite it
+	bool foundMatch = false;
+	for (auto &save : _sortedSavesList) {
+		if (save.getDescription() == finalDesc) {
+			foundMatch = true;
+			_selectedSave = save.getSaveSlot();
+			break;
+		}
+	}
+
+	if (!foundMatch) {
+		// No match, place in the lowest free slot
+		_selectedSave = 1;
+		bool shouldContinue = false;
+		do {
+			shouldContinue = false;
+			for (auto &save : _sortedSavesList) {
+				if (save.getSaveSlot() == _selectedSave) {
+					++_selectedSave;
+					shouldContinue = true;
+					break;
+				}
+			}
+		} while (shouldContinue);
+	}
+
+	g_nancy->saveGameState(_selectedSave, finalDesc, false);
+
+	ConfMan.setInt("display_slot", _selectedSave, Common::ConfigManager::kTransientDomain); // Used to load the save name
+
+	if (_successOverlay._drawSurface.empty()) {
+		_state = kRun;
+		_enteringNewState = true;
+	} else {
+		_state = kSuccess;
+		_enteringNewState = true;
+	}
+
+	if ((int)_saveExists.size() < _selectedSave) {
+		_saveExists.resize(_selectedSave + 1, false);
+	}
+
+	_saveExists[_selectedSave] = true;
+	g_nancy->_hasJustSaved = true;
+}
+
+void LoadSaveMenu_V2::success() {
+	LoadSaveMenu::success();
+
+	if (g_nancy->getTotalPlayTime() > _nextBlink) {
+		_selectedSave = 0;
+	}
+}
+
+int LoadSaveMenu_V2::scummVMSaveSlotToLoad() const {
+	uint orderedSaveID = _currentPage * _filenameStrings.size() + _selectedSave;
+	if (_currentPage != 0) {
+		// First page has one save less
+		orderedSaveID -= 1;
+	}
+
+	return _sortedSavesList[orderedSaveID].getSaveSlot();
+}
+
+uint16 LoadSaveMenu_V2::writeToTextbox(int textboxID, const Common::String &text, const Font *font) {
 	assert(font);
 
-	_textboxes[textboxID]->_drawSurface.clear(g_nancy->_graphics->getTransColor());
-	Common::Point destPoint(_loadSaveData->_fontXOffset, _loadSaveData->_fontYOffset + _textboxes[textboxID]->_drawSurface.h - font->getFontHeight());
-	font->drawString(&_textboxes[textboxID]->_drawSurface, text, destPoint.x, destPoint.y, _textboxes[textboxID]->_drawSurface.w, 0);
-	_textboxes[textboxID]->setVisible(true);
+	if (_currentPage == 0) {
+		// Page one has one textbox less, right at the top.
+		// We simply disable it and adjust the indexing here and in the hovering code
+		++textboxID;
+	}
 
-	return font->getStringWidth(text);
+	if (textboxID >= (int)_textboxes.size()) {
+		return 0;
+	}
+
+	return LoadSaveMenu::writeToTextbox(textboxID, text, font);
+}
+
+uint16 LoadSaveMenu_V2::writeToInputTextbox(const Font *font) {
+	assert(font);
+
+	_inputTextbox->_drawSurface.clear(g_nancy->_graphics->getTransColor());
+	Common::Point destPoint(_loadSaveData->_fontXOffset, _loadSaveData->_fontYOffset + _inputTextbox->_drawSurface.h - font->getFontHeight());
+	font->drawString(&_inputTextbox->_drawSurface, _enteredString,
+			destPoint.x, destPoint.y, _inputTextbox->_drawSurface.w, 0);
+	_inputTextbox->setVisible(true);
+
+	return font->getStringWidth(_enteredString);
+}
+
+struct SaveStateDescriptorSaveTimeComparator {
+	bool operator()(const SaveStateDescriptor &x, const SaveStateDescriptor &y) const {
+		// Compare the date/time strings. This is valid since they only
+		// contain digits and the - and : characters. The comparison
+		// makes sure that saves are listed from newest to oldest.
+		int dateCompare = x.getSaveDate().compareToIgnoreCase(y.getSaveDate());
+		if (dateCompare) {
+			return dateCompare > 0;
+		} else {
+			return x.getSaveTime().compareToIgnoreCase(y.getSaveTime()) > 0;
+		}
+	}
+};
+
+void LoadSaveMenu_V2::filterAndSortSaveStates() {
+	if (!_sortedSavesList.size()) {
+		return;
+	}
+
+	// Assumes the autosave slot is 0
+	if (_sortedSavesList[0].isAutosave() && _sortedSavesList[0].isValid()) {
+		_sortedSavesList.erase(_sortedSavesList.begin());
+	}
+
+	// Clear second chance saves
+	for (auto *save = _sortedSavesList.begin(); save != _sortedSavesList.end(); ++save) {
+		if (save->getDescription() == "SECOND CHANCE") {
+			save = _sortedSavesList.erase(save) - 1;
+		}
+	}
+
+	Common::sort(_sortedSavesList.begin(), _sortedSavesList.end(), SaveStateDescriptorSaveTimeComparator());
+}
+
+void LoadSaveMenu_V2::extractSaveNames(uint pageID) {
+	if (!_sortedSavesList.size()) {
+		// No saves yet, just load the default name into the input box
+		_enteredString = _loadSaveData->_emptySaveText;
+		writeToInputTextbox(_baseFont);
+
+		return;
+	}
+
+	// First, empty all save names
+	for (uint i = 0; i < _filenameStrings.size(); ++i) {
+		_filenameStrings[i].clear();
+		_saveExists[i] = false;
+	}
+
+	uint firstSaveID, lastSaveID;
+
+	// The first page has one textbox less
+	uint numTextboxes = (_currentPage == 0 ? _filenameStrings.size() - 1 : _filenameStrings.size());
+	firstSaveID = pageID == 0 ? 0 : (_filenameStrings.size() - 1 + (pageID - 1) * _filenameStrings.size());
+	lastSaveID = MIN<uint>(_sortedSavesList.size() - 1, firstSaveID + numTextboxes - 1);
+
+	for (uint i = firstSaveID; i <= lastSaveID; ++i) {
+		int onScreenSaveID = i - firstSaveID;
+		_saveExists[onScreenSaveID] = true;
+		_filenameStrings[onScreenSaveID] = _sortedSavesList[i].getDescription();
+	}
+
+	// Load the top textbox showing the name of the last save made
+	if (_enteredString.empty() && Nancy::State::Scene::hasInstance()) {
+		bool textboxSet = false;
+
+		if (ConfMan.hasKey("display_slot", Common::ConfigManager::kTransientDomain)) {
+			int slot = ConfMan.getInt("display_slot", Common::ConfigManager::kTransientDomain);
+
+			for (uint i = 0; i < _sortedSavesList.size(); ++i) {
+				if (_sortedSavesList[i].getSaveSlot() == slot) {
+					_enteredString = _sortedSavesList[i].getDescription();
+					writeToInputTextbox(_baseFont);
+					textboxSet = true;
+					break;
+				}
+			}
+		}
+
+		if (!textboxSet) {
+			_enteredString = _loadSaveData->_emptySaveText;
+			writeToInputTextbox(_baseFont);
+		}
+	}
+}
+
+void LoadSaveMenu_V2::goToPage(uint pageID) {
+	if (pageID == 0) {
+		_background1.setVisible(true);
+		_background2.setVisible(false);
+	} else {
+		_background1.setVisible(false);
+		_background2.setVisible(true);
+	}
+
+	extractSaveNames(pageID);
+	_currentPage = pageID;
+	_selectedSave = -1;
+
+	int numSaves = _sortedSavesList.size();
+
+	if (!_pageUpButton || !_pageDownButton)
+		return;
+
+	if (numSaves > (int)((_filenameStrings.size() - 1) + pageID * _filenameStrings.size())) {
+		_pageDownButton->setDisabled(false);
+		_pageDownButton->setVisible(true);
+	} else {
+		_pageDownButton->setDisabled(true);
+		_pageDownButton->setVisible(false);
+	}
+
+	if (pageID == 0) {
+		_pageUpButton->setDisabled(true);
+		_pageUpButton->setVisible(false);
+	} else {
+		_pageUpButton->setDisabled(false);
+		_pageUpButton->setVisible(true);
+	}
+
+	_loadButton->setDisabled(true);
+}
+
+void LoadSaveMenu_V2::reloadSaves() {
+	_sortedSavesList = g_nancy->getMetaEngine()->listSaves(ConfMan.getActiveDomainName().c_str());
+	filterAndSortSaveStates();
+	extractSaveNames(0);
 }
 
 } // End of namespace State
diff --git a/engines/nancy/state/loadsave.h b/engines/nancy/state/loadsave.h
index 6a46176f1fc..8eaf602c9ff 100644
--- a/engines/nancy/state/loadsave.h
+++ b/engines/nancy/state/loadsave.h
@@ -23,85 +23,144 @@
 #define NANCY_STATE_LOADSAVE_H
 
 #include "common/singleton.h"
+#include "common/ptr.h"
 
 #include "engines/nancy/state/state.h"
 #include "engines/nancy/ui/fullscreenimage.h"
+#include "engines/nancy/ui/button.h"
 #include "engines/nancy/font.h"
 
 namespace Nancy {
 
 struct LOAD;
 
-namespace UI {
-class Button;
-}
-
 namespace State {
 
 class LoadSaveMenu : public State, public Common::Singleton<LoadSaveMenu> {
 public:
-	LoadSaveMenu() :
-		_state(kInit), _selectedSave(-1), _enteringNewState(false), _nextBlink(0),
-		_baseFont(nullptr), _highlightFont(nullptr),
-		_disabledFont(nullptr), _loadSaveData(nullptr),
-		_cancelButton(nullptr), _exitButton(nullptr),
-		_blinkingCursorOverlay(6), _successOverlay(8) {}
 	virtual ~LoadSaveMenu();
 
+protected:
+	enum State { kInit, kRun, kEnterFilename, kSave, kLoad, kSuccess, kStop };
+
+	LoadSaveMenu() :
+		_blinkingCursorOverlay(6),
+		_successOverlay(8) {};
+
+	virtual void init() = 0;
+	virtual void run() = 0;
+	virtual void enterFilename();
+	virtual void save() = 0;
+	virtual void load();
+	virtual void success();
+	virtual void stop();
+
+	virtual int scummVMSaveSlotToLoad() const = 0;
+
+	virtual void registerGraphics();
+
+	virtual uint16 writeToTextbox(int textboxID, const Common::String &text, const Font *font);
+
+	void scummVMSave();
+	void scummVMLoad();
+
 	// State API
 	void process() override;
 	void onStateEnter(const NancyState::NancyState prevState) override;
 	bool onStateExit(const NancyState::NancyState nextState) override;
 
-private:
-	void init();
-	void run();
-	void enterFilename();
-	void save();
-	void load();
-	void success();
-	void stop();
+	const Font *_baseFont = nullptr;
+	const Font *_highlightFont = nullptr;
+	const Font *_disabledFont = nullptr;
 
-	void registerGraphics();
+	State _state = kInit;
+	bool _enteringNewState = true;
+	bool _destroyOnExit = true;
+	const LOAD *_loadSaveData = nullptr;
 
-	void scummVMSave();
-	void scummVMLoad();
+	int16 _selectedSave = -1;
+	RenderObject *_textboxReceivingInput = nullptr;
 
-	uint16 writeToTextbox(uint textboxID, const Common::String &text, const Font *font);
+	// UI elements common to both menus
+	Common::Array<Common::ScopedPtr<RenderObject>> _textboxes;
+	Common::ScopedPtr<UI::Button> _exitButton;
 
-	enum State { kInit, kRun, kEnterFilename, kSave, kLoad, kSuccess, kStop };
+	RenderObject _blinkingCursorOverlay;
+	RenderObject _successOverlay;
+
+	uint32 _nextBlink = 0;
+	Common::String _enteredString;
+};
 
-	State _state;
+class LoadSaveMenu_V1 : public LoadSaveMenu {
+private:
+	void init() override;
+	void run() override;
+	void enterFilename() override;
+	void save() override;
 
-	UI::FullScreenImage _background;
+	virtual int scummVMSaveSlotToLoad() const override;
 
-	const Font *_baseFont;
-	const Font *_highlightFont;
-	const Font *_disabledFont;
+	void registerGraphics() override;
+
+	UI::FullScreenImage _background;
 
 	Common::Array<Common::String> _filenameStrings;
 	Common::Array<bool> _saveExists;
-	Common::String _enteredString;
+	Common::Array<Common::ScopedPtr<UI::Button>> _loadButtons;
+	Common::Array<Common::ScopedPtr<UI::Button>> _saveButtons;
+	Common::Array<Common::ScopedPtr<RenderObject>> _cancelButtonOverlays;
+	Common::ScopedPtr<UI::Button> _cancelButton;
+};
 
-	Common::Array<RenderObject *> _textboxes;
-	Common::Array<UI::Button *> _loadButtons;
-	Common::Array<UI::Button *> _saveButtons;
-	Common::Array<RenderObject *> _cancelButtonOverlays;
-	UI::Button *_exitButton;
-	UI::Button *_cancelButton;
-	RenderObject _blinkingCursorOverlay;
-	RenderObject _successOverlay;
+class LoadSaveMenu_V2 : public LoadSaveMenu {
+private:
+	void init() override;
+	void run() override;
+	void enterFilename() override;
+	void save() override;
+	void success() override;
 
-	int16 _selectedSave;
-	bool _enteringNewState;
-	uint32 _nextBlink;
+	virtual int scummVMSaveSlotToLoad() const override;
 
-	bool _destroyOnExit = true;
+	void registerGraphics() override;
 
-	const LOAD *_loadSaveData;
+	uint16 writeToTextbox(int textboxID, const Common::String &text, const Font *font) override;
+	uint16 writeToInputTextbox(const Font *font);
+
+	void filterAndSortSaveStates();
+	void extractSaveNames(uint pageID);
+	void goToPage(uint pageID);
+	void reloadSaves();
+	void setConfig();
+
+	UI::FullScreenImage _background1;
+	UI::FullScreenImage _background2;
+	Graphics::ManagedSurface _buttonsImage;
+
+	Common::Array<Common::String> _filenameStrings;
+	Common::Array<bool> _saveExists;
+
+	Common::ScopedPtr<RenderObject> _inputTextbox;
+	Common::ScopedPtr<UI::Button> _loadButton;
+	Common::ScopedPtr<UI::Button> _saveButton;
+	Common::ScopedPtr<UI::Button> _pageUpButton;
+	Common::ScopedPtr<UI::Button> _pageDownButton;
+
+	int16 _hoveredSave = -1;
+	uint _currentPage = 0;
+
+	SaveStateList _sortedSavesList;
 };
 
 } // End of namespace State
 } // End of namespace Nancy
 
+namespace Common {
+
+template<>
+Nancy::State::LoadSaveMenu *Singleton<Nancy::State::LoadSaveMenu>::makeInstance();
+
+} // End of namespace Common
+
 #endif // NANCY_STATE_LOADSAVE_H
diff --git a/engines/nancy/state/scene.cpp b/engines/nancy/state/scene.cpp
index 0a17e7fb671..58af4576895 100644
--- a/engines/nancy/state/scene.cpp
+++ b/engines/nancy/state/scene.cpp
@@ -835,9 +835,13 @@ void Scene::init() {
 
 		// Remove key so clicking on "New Game" in start menu doesn't just reload the save
 		ConfMan.removeKey("save_slot", Common::ConfigManager::kTransientDomain);
+		// Retain the last slot used so the nancy8+ save menu shows its name on top
+		ConfMan.setInt("display_slot", saveSlot, Common::ConfigManager::kTransientDomain);
 	} else {
 		// Normal boot, load default first scene
 		_state = kLoad;
+		// Make sure the nancy8+ save menu doesn't display a save name on new game
+		ConfMan.removeKey("display_slot", Common::ConfigManager::kTransientDomain);
 	}
 
 	// Set relevant event flag when player has won the game at least once
diff --git a/engines/nancy/ui/fullscreenimage.cpp b/engines/nancy/ui/fullscreenimage.cpp
index c6ab4bffdb7..9f11e8323c6 100644
--- a/engines/nancy/ui/fullscreenimage.cpp
+++ b/engines/nancy/ui/fullscreenimage.cpp
@@ -28,7 +28,9 @@ namespace Nancy {
 namespace UI {
 
 void FullScreenImage::init(const Common::Path &imageName) {
-	g_nancy->_resource->loadImage(imageName, _drawSurface);
+	if (!g_nancy->_resource->loadImage(imageName, _drawSurface)) {
+		return;
+	}
 
 	Common::Rect srcBounds = Common::Rect(0,0, _drawSurface.w, _drawSurface.h);
 	_screenPosition = srcBounds;


Commit: 0a1ab6cbe81c435e6bdec78f4d12efc6bc904421
    https://github.com/scummvm/scummvm/commit/0a1ab6cbe81c435e6bdec78f4d12efc6bc904421
Author: Kaloyan Chehlarski (strahy at outlook.com)
Date: 2025-11-11T11:50:18+01:00

Commit Message:
NANCY: Fix image loading in The Vampire Diaries

loadImage() was erroneously returning false in
TVD, even when the image was, in fact, found and loaded.
As a result, most graphics wouldn't be displayed
on screen at all.

Changed paths:
    engines/nancy/resource.cpp


diff --git a/engines/nancy/resource.cpp b/engines/nancy/resource.cpp
index d83c992da78..b99461d7555 100644
--- a/engines/nancy/resource.cpp
+++ b/engines/nancy/resource.cpp
@@ -78,6 +78,7 @@ bool ResourceManager::loadImage(const Common::Path &name, Graphics::ManagedSurfa
 			bmpDec.loadStream(*stream);
 			surf.copyFrom(*bmpDec.getSurface());
 			surf.setPalette(bmpDec.getPalette().data(), 0, MIN<uint>(256, bmpDec.getPalette().size())); // LOGO.BMP reports 257 colors
+			return true;
 		}
 	}
 


Commit: fa613690e691a78078fa2a292b913ee664904a10
    https://github.com/scummvm/scummvm/commit/fa613690e691a78078fa2a292b913ee664904a10
Author: Kaloyan Chehlarski (strahy at outlook.com)
Date: 2025-11-11T11:50:18+01:00

Commit Message:
NANCY: Use ScopedPtr in SetupMenu

Changed paths:
    engines/nancy/state/setupmenu.cpp
    engines/nancy/state/setupmenu.h


diff --git a/engines/nancy/state/setupmenu.cpp b/engines/nancy/state/setupmenu.cpp
index 73d670a0248..a08788e2f6b 100644
--- a/engines/nancy/state/setupmenu.cpp
+++ b/engines/nancy/state/setupmenu.cpp
@@ -39,18 +39,6 @@ DECLARE_SINGLETON(Nancy::State::SetupMenu);
 namespace Nancy {
 namespace State {
 
-SetupMenu::~SetupMenu() {
-	for (auto *tog : _toggles) {
-		delete tog;
-	}
-
-	for (auto *scroll : _scrollbars) {
-		delete scroll;
-	}
-
-	delete _exitButton;
-}
-
 void SetupMenu::process() {
 	switch (_state) {
 	case kInit:
@@ -76,11 +64,11 @@ bool SetupMenu::onStateExit(const NancyState::NancyState nextState) {
 void SetupMenu::registerGraphics() {
 	_background.registerGraphics();
 
-	for (auto *tog : _toggles) {
+	for (auto &tog : _toggles) {
 		tog->registerGraphics();
 	}
 
-	for (auto *scroll : _scrollbars) {
+	for (auto &scroll : _scrollbars) {
 		scroll->registerGraphics();
 	}
 
@@ -153,11 +141,12 @@ void SetupMenu::init() {
 		}
 	}
 
+	_toggles.resize(_setupData->_buttonDests.size() - 1);
 	for (uint i = 0; i < _setupData->_buttonDests.size() - 1; ++i) {
-		_toggles.push_back(new UI::Toggle(5, _background._drawSurface,
+		_toggles[i].reset(new UI::Toggle(5, _background._drawSurface,
 			_setupData->_buttonDownSrcs[i], _setupData->_buttonDests[i]));
 
-		_toggles.back()->init();
+		_toggles[i]->init();
 	}
 
 	// Set toggle visibility
@@ -165,12 +154,13 @@ void SetupMenu::init() {
 		_toggles[i]->setState(ConfMan.getBool(getToggleConfManKey(i), ConfMan.getActiveDomainName()));
 	}
 
+	_scrollbars.resize(_setupData->_scrollbarSrcs.size());
 	for (uint i = 0; i < _setupData->_scrollbarSrcs.size(); ++i) {
-		_scrollbars.push_back(new UI::Scrollbar(7, _setupData->_scrollbarSrcs[i],
+		_scrollbars[i].reset(new UI::Scrollbar(7, _setupData->_scrollbarSrcs[i],
 			_background._drawSurface, Common::Point(_setupData->_scrollbarsCenterXPosL[i] + 1, _setupData->_scrollbarsCenterYPos[i]),
 			_setupData->_scrollbarsCenterXPosR[i] + 1 - _setupData->_scrollbarsCenterXPosL[i] - 1, false));
-		_scrollbars.back()->init();
-		_scrollbars.back()->setVisible(true);
+		_scrollbars[i]->init();
+		_scrollbars[i]->setVisible(true);
 	}
 
 	// Set scrollbar positions
@@ -178,9 +168,9 @@ void SetupMenu::init() {
 	_scrollbars[1]->setPosition(ConfMan.getInt("music_volume") / 255.0);
 	_scrollbars[2]->setPosition(ConfMan.getInt("sfx_volume") / 255.0);
 
-	_exitButton = new UI::Button(5, _background._drawSurface,
+	_exitButton.reset(new UI::Button(5, _background._drawSurface,
 		_setupData->_buttonDownSrcs.back(), _setupData->_buttonDests.back(),
-		_setupData->_doneButtonHighlightSrc);
+		_setupData->_doneButtonHighlightSrc));
 	_exitButton->init();
 	_exitButton->setVisible(false);
 
@@ -193,7 +183,7 @@ void SetupMenu::run() {
 	NancyInput input = g_nancy->_input->getInput();
 
 	for (uint i = 0; i < _scrollbars.size(); ++i) {
-		auto *scroll = _scrollbars[i];
+		auto &scroll = _scrollbars[i];
 
 		float startPos = scroll->getPos();
 		scroll->handleInput(input);
@@ -223,7 +213,7 @@ void SetupMenu::run() {
 	}
 
 	for (uint i = 0; i < _toggles.size(); ++i) {
-		auto *tog = _toggles[i];
+		auto &tog = _toggles[i];
 		tog->handleInput(input);
 		if (tog->_stateChanged) {
 			g_nancy->_sound->playSound("BUOK");
diff --git a/engines/nancy/state/setupmenu.h b/engines/nancy/state/setupmenu.h
index cedc0f147e7..bd45c38f270 100644
--- a/engines/nancy/state/setupmenu.h
+++ b/engines/nancy/state/setupmenu.h
@@ -22,29 +22,22 @@
 #ifndef NANCY_STATE_SETUPMENU_H
 #define NANCY_STATE_SETUPMENU_H
 
+#include "common/ptr.h"
 #include "common/singleton.h"
 
 #include "engines/nancy/state/state.h"
-
 #include "engines/nancy/ui/fullscreenimage.h"
+#include "engines/nancy/ui/scrollbar.h"
+#include "engines/nancy/ui/button.h"
 
 namespace Nancy {
 
 struct SET;
 
-namespace UI {
-class Button;
-class Toggle;
-class Scrollbar;
-}
-
 namespace State {
 
 class SetupMenu : public State, public Common::Singleton<SetupMenu> {
 public:
-	SetupMenu() : _state(kInit), _exitButton(nullptr), _setupData(nullptr) {}
-	virtual ~SetupMenu();
-
 	// State API
 	void process() override;
 	void onStateEnter(const NancyState::NancyState prevState) override;
@@ -62,11 +55,11 @@ private:
 	enum State { kInit, kRun, kStop };
 
 	UI::FullScreenImage _background;
-	State _state;
+	State _state = kInit;
 
-	Common::Array<UI::Toggle *> _toggles;
-	Common::Array<UI::Scrollbar *> _scrollbars;
-	UI::Button *_exitButton;
+	Common::Array<Common::ScopedPtr<UI::Toggle>> _toggles;
+	Common::Array<Common::ScopedPtr<UI::Scrollbar>> _scrollbars;
+	Common::ScopedPtr<UI::Button> _exitButton;
 
 	const SET *_setupData;
 };


Commit: b7b64dd4abef7aef3eb02dcec8b2e0fa93214023
    https://github.com/scummvm/scummvm/commit/b7b64dd4abef7aef3eb02dcec8b2e0fa93214023
Author: Kaloyan Chehlarski (strahy at outlook.com)
Date: 2025-11-11T11:50:18+01:00

Commit Message:
NANCY: Use ScopedPtr in SaveDialog

Changed paths:
    engines/nancy/state/savedialog.cpp
    engines/nancy/state/savedialog.h


diff --git a/engines/nancy/state/savedialog.cpp b/engines/nancy/state/savedialog.cpp
index bb9b41cb508..f58eea0049b 100644
--- a/engines/nancy/state/savedialog.cpp
+++ b/engines/nancy/state/savedialog.cpp
@@ -38,12 +38,6 @@ DECLARE_SINGLETON(Nancy::State::SaveDialog);
 namespace Nancy {
 namespace State {
 
-SaveDialog::~SaveDialog() {
-	delete _yesButton;
-	delete _noButton;
-	delete _cancelButton;
-}
-
 void SaveDialog::process() {
 	if (g_nancy->_sound->isSoundPlaying("BUOK")) {
 		return;
@@ -97,9 +91,9 @@ void SaveDialog::init() {
 
 	_background.init(_dialogData->imageName);
 
-	_yesButton = new UI::Button(1, _background._drawSurface, _dialogData->yesDownSrc, _dialogData->yesDest, _dialogData->yesHighlightSrc);
-	_noButton = new UI::Button(1, _background._drawSurface, _dialogData->noDownSrc, _dialogData->noDest, _dialogData->noHighlightSrc);
-	_cancelButton = new UI::Button(1, _background._drawSurface, _dialogData->cancelDownSrc, _dialogData->cancelDest, _dialogData->cancelHighlightSrc);
+	_yesButton.reset(new UI::Button(1, _background._drawSurface, _dialogData->yesDownSrc, _dialogData->yesDest, _dialogData->yesHighlightSrc));
+	_noButton.reset(new UI::Button(1, _background._drawSurface, _dialogData->noDownSrc, _dialogData->noDest, _dialogData->noHighlightSrc));
+	_cancelButton.reset(new UI::Button(1, _background._drawSurface, _dialogData->cancelDownSrc, _dialogData->cancelDest, _dialogData->cancelHighlightSrc));
 
 	registerGraphics();
 
@@ -138,4 +132,3 @@ void SaveDialog::stop() {
 
 } // End of namespace State
 } // End of namespace Nancy
-
diff --git a/engines/nancy/state/savedialog.h b/engines/nancy/state/savedialog.h
index 0a31ccbe082..d4d93918f0b 100644
--- a/engines/nancy/state/savedialog.h
+++ b/engines/nancy/state/savedialog.h
@@ -22,6 +22,7 @@
 #ifndef NANCY_STATE_SAVEDIALOG_H
 #define NANCY_STATE_SAVEDIALOG_H
 
+#include "common/ptr.h"
 #include "common/singleton.h"
 
 #include "engines/nancy/state/state.h"
@@ -40,8 +41,7 @@ namespace State {
 
 class SaveDialog : public State, public Common::Singleton<SaveDialog> {
 public:
-	SaveDialog() : _state(kInit), _yesButton(nullptr), _noButton(nullptr), _cancelButton(nullptr), _selected(-1), _dialogData(nullptr) {}
-	virtual ~SaveDialog();
+	SaveDialog() : _state(kInit), _selected(-1), _dialogData(nullptr) {}
 
 	// State API
 	void process() override;
@@ -61,9 +61,9 @@ private:
 	State _state;
 	int _selected;
 
-	UI::Button *_yesButton;
-	UI::Button *_noButton;
-	UI::Button *_cancelButton;
+	Common::ScopedPtr<UI::Button> _yesButton;
+	Common::ScopedPtr<UI::Button> _noButton;
+	Common::ScopedPtr<UI::Button> _cancelButton;
 
 	const SDLG::Dialog *_dialogData;
 };


Commit: 9f3140df4c55f7087a6fc4406fe857343d489d3b
    https://github.com/scummvm/scummvm/commit/9f3140df4c55f7087a6fc4406fe857343d489d3b
Author: Kaloyan Chehlarski (strahy at outlook.com)
Date: 2025-11-11T11:50:18+01:00

Commit Message:
NANCY: Use ScopedPtr in Map

Changed paths:
    engines/nancy/state/map.cpp
    engines/nancy/state/map.h


diff --git a/engines/nancy/state/map.cpp b/engines/nancy/state/map.cpp
index ea58daa7f3d..230cd5f219e 100644
--- a/engines/nancy/state/map.cpp
+++ b/engines/nancy/state/map.cpp
@@ -351,12 +351,6 @@ void TVDMap::MapGlobe::onTrigger() {
 	}
 }
 
-Nancy1Map::Nancy1Map() : _button(nullptr)/*, _mapButtonClicked(false)*/ {}
-
-Nancy1Map::~Nancy1Map() {
-	delete _button;
-}
-
 void Nancy1Map::init() {
 	_viewport.init();
 	_label.init();
@@ -382,7 +376,7 @@ void Nancy1Map::init() {
 		_locationLabelDests[i].top = _locationLabelDests[i].bottom - _mapData->locations[i].labelSrc.height() + 1;
 	}
 
-	_button = new UI::Button(9, g_nancy->_graphics->_object0, _mapData->buttonSrc, _mapData->buttonDest);
+	_button.reset(new UI::Button(9, g_nancy->_graphics->_object0, _mapData->buttonSrc, _mapData->buttonDest));
 	_button->init();
 	_button->setVisible(true);
 
diff --git a/engines/nancy/state/map.h b/engines/nancy/state/map.h
index 62a433eeabd..1a58924fee0 100644
--- a/engines/nancy/state/map.h
+++ b/engines/nancy/state/map.h
@@ -22,6 +22,7 @@
 #ifndef NANCY_STATE_MAP_H
 #define NANCY_STATE_MAP_H
 
+#include "common/ptr.h"
 #include "common/singleton.h"
 
 #include "engines/nancy/sound.h"
@@ -127,10 +128,6 @@ private:
 };
 
 class Nancy1Map : public Map {
-public:
-	Nancy1Map();
-	virtual ~Nancy1Map();
-
 private:
 	void init() override;
 	void load() override;
@@ -139,7 +136,7 @@ private:
 
 	bool onStateExit(const NancyState::NancyState next) override;
 
-	UI::Button *_button;
+	Common::ScopedPtr<UI::Button> _button;
 };
 
 #define NancyMapState Nancy::State::Map::instance()


Commit: a0809aa698b343d7b5d52f2f330e2b90be277735
    https://github.com/scummvm/scummvm/commit/a0809aa698b343d7b5d52f2f330e2b90be277735
Author: Kaloyan Chehlarski (strahy at outlook.com)
Date: 2025-11-11T11:50:19+01:00

Commit Message:
NANCY: Use ScopedPtr in MainMenu

Changed paths:
    engines/nancy/state/mainmenu.cpp
    engines/nancy/state/mainmenu.h


diff --git a/engines/nancy/state/mainmenu.cpp b/engines/nancy/state/mainmenu.cpp
index 0e58e166fac..048cdcb3d64 100644
--- a/engines/nancy/state/mainmenu.cpp
+++ b/engines/nancy/state/mainmenu.cpp
@@ -39,12 +39,6 @@ DECLARE_SINGLETON(Nancy::State::MainMenu);
 namespace Nancy {
 namespace State {
 
-MainMenu::~MainMenu() {
-	for (auto *button : _buttons) {
-		delete button;
-	}
-}
-
 void MainMenu::process() {
 	switch (_state) {
 	case kInit:
@@ -70,7 +64,7 @@ bool MainMenu::onStateExit(const NancyState::NancyState nextState) {
 void MainMenu::registerGraphics() {
 	_background.registerGraphics();
 
-	for (auto *button : _buttons) {
+	for (auto &button : _buttons) {
 		button->registerGraphics();
 	}
 
@@ -78,7 +72,7 @@ void MainMenu::registerGraphics() {
 }
 
 void MainMenu::clearButtonState() {
-	for (auto *button : _buttons) {
+	for (auto &button : _buttons) {
 		button->_isClicked = false;
 	}
 }
@@ -97,14 +91,15 @@ void MainMenu::init() {
 		g_nancy->_sound->playSound("MSND");
 	}
 
+	_buttons.resize(_menuData->_buttonDests.size());
 	for (uint i = 0; i < _menuData->_buttonDests.size(); ++i) {
-		_buttons.push_back(new UI::Button(5, _background._drawSurface,
+		_buttons[i].reset(new UI::Button(5, _background._drawSurface,
 			_menuData->_buttonDownSrcs[i], _menuData->_buttonDests[i],
 			_menuData->_buttonHighlightSrcs.size() ? _menuData->_buttonHighlightSrcs[i] : Common::Rect(),
 			_menuData->_buttonDisabledSrcs.size() ? _menuData->_buttonDisabledSrcs[i] : Common::Rect()));
 
-		_buttons.back()->init();
-		_buttons.back()->setVisible(false);
+		_buttons[i]->init();
+		_buttons[i]->setVisible(false);
 	}
 
 	registerGraphics();
@@ -139,7 +134,7 @@ void MainMenu::run() {
 	}
 
 	for (uint i = 0; i < _buttons.size(); ++i) {
-		auto *button = _buttons[i];
+		auto &button = _buttons[i];
 		button->handleInput(input);
 		if (_selected == -1 && button->_isClicked) {
 			if (button->_isDisabled) {
diff --git a/engines/nancy/state/mainmenu.h b/engines/nancy/state/mainmenu.h
index f92e9b88bf5..48f19eaefd1 100644
--- a/engines/nancy/state/mainmenu.h
+++ b/engines/nancy/state/mainmenu.h
@@ -22,6 +22,7 @@
 #ifndef NANCY_STATE_MAINMENU_H
 #define NANCY_STATE_MAINMENU_H
 
+#include "common/ptr.h"
 #include "common/singleton.h"
 
 #include "engines/nancy/state/state.h"
@@ -41,7 +42,6 @@ namespace State {
 class MainMenu : public State, public Common::Singleton<MainMenu> {
 public:
 	MainMenu() : _state(kInit), _selected(-1), _menuData(nullptr) {}
-	virtual ~MainMenu();
 
 	// State API
 	void process() override;
@@ -62,7 +62,7 @@ private:
 	State _state;
 	int16 _selected;
 
-	Common::Array<UI::Button *> _buttons;
+	Common::Array<Common::ScopedPtr<UI::Button>> _buttons;
 
 	bool _destroyOnExit = true;
 


Commit: 7a85b93834d702c9af7ca1958a8f951a204afeee
    https://github.com/scummvm/scummvm/commit/7a85b93834d702c9af7ca1958a8f951a204afeee
Author: Kaloyan Chehlarski (strahy at outlook.com)
Date: 2025-11-11T11:50:19+01:00

Commit Message:
NANCY: Use ScopedPtr in Help

Changed paths:
    engines/nancy/state/help.cpp
    engines/nancy/state/help.h


diff --git a/engines/nancy/state/help.cpp b/engines/nancy/state/help.cpp
index 22f46b1f66f..c88bc370ee3 100644
--- a/engines/nancy/state/help.cpp
+++ b/engines/nancy/state/help.cpp
@@ -36,15 +36,6 @@ DECLARE_SINGLETON(Nancy::State::Help);
 namespace Nancy {
 namespace State {
 
-Help::Help() :
-		_state(kInit),
-		_image(),
-		_button(nullptr) {}
-
-Help::~Help() {
-	delete _button;
-}
-
 void Help::process() {
 	switch (_state) {
 	case kInit:
@@ -85,7 +76,7 @@ void Help::init() {
 
 	_image.init(helpData->imageName);
 
-	_button = new UI::Button(5, _image._drawSurface, helpData->buttonSrc, helpData->buttonDest, helpData->buttonHoverSrc);
+	_button.reset(new UI::Button(5, _image._drawSurface, helpData->buttonSrc, helpData->buttonDest, helpData->buttonHoverSrc));
 	_button->init();
 
 	_state = kBegin;
diff --git a/engines/nancy/state/help.h b/engines/nancy/state/help.h
index b0ad4480c52..a136dd9d0f7 100644
--- a/engines/nancy/state/help.h
+++ b/engines/nancy/state/help.h
@@ -22,6 +22,7 @@
 #ifndef NANCY_STATE_HELP_H
 #define NANCY_STATE_HELP_H
 
+#include "common/ptr.h"
 #include "common/singleton.h"
 
 #include "engines/nancy/commontypes.h"
@@ -40,8 +41,6 @@ namespace State {
 class Help : public State, public Common::Singleton<Help> {
 public:
 	enum State { kInit, kBegin, kRun, kWait };
-	Help();
-	virtual ~Help();
 
 	// State API
 	void process() override;
@@ -54,9 +53,9 @@ private:
 	void run();
 	void wait();
 
-	State _state;
+	State _state = kInit;
 	UI::FullScreenImage _image;
-	UI::Button *_button;
+	Common::ScopedPtr<UI::Button> _button;
 	Time _buttonPressActivationTime;
 };
 


Commit: 5078e6c1a9d42e1ad156fa436689dbfb21016399
    https://github.com/scummvm/scummvm/commit/5078e6c1a9d42e1ad156fa436689dbfb21016399
Author: Kaloyan Chehlarski (strahy at outlook.com)
Date: 2025-11-11T11:50:19+01:00

Commit Message:
NANCY: Use ScopedPtr in InventoryBox

Changed paths:
    engines/nancy/ui/inventorybox.cpp
    engines/nancy/ui/inventorybox.h


diff --git a/engines/nancy/ui/inventorybox.cpp b/engines/nancy/ui/inventorybox.cpp
index 1ef5e823164..28b8661679b 100644
--- a/engines/nancy/ui/inventorybox.cpp
+++ b/engines/nancy/ui/inventorybox.cpp
@@ -38,16 +38,10 @@ namespace UI {
 
 InventoryBox::InventoryBox() :
 		RenderObject(6),
-		_scrollbar(nullptr),
 		_scrollbarPos(0),
 		_highlightedHotspot(-1),
 		_inventoryData(nullptr) {}
 
-InventoryBox::~InventoryBox() {
-	_fullInventorySurface.free();
-	_iconsSurface.free(); delete _scrollbar;
-}
-
 void InventoryBox::init() {
 	auto *bootSummary = GetEngineData(BSUM);
 	assert(bootSummary);
@@ -75,10 +69,10 @@ void InventoryBox::init() {
 
 	RenderObject::init();
 
-	_scrollbar = new Scrollbar(	9,
+	_scrollbar.reset(new Scrollbar(	9,
 								_inventoryData->scrollbarSrcBounds,
 								_inventoryData->scrollbarDefaultPos,
-								_inventoryData->scrollbarMaxScroll - _inventoryData->scrollbarDefaultPos.y);
+								_inventoryData->scrollbarMaxScroll - _inventoryData->scrollbarDefaultPos.y));
 	_scrollbar->init();
 	_curtains.init();
 }
diff --git a/engines/nancy/ui/inventorybox.h b/engines/nancy/ui/inventorybox.h
index 43107b2fdbc..9e420d12e5c 100644
--- a/engines/nancy/ui/inventorybox.h
+++ b/engines/nancy/ui/inventorybox.h
@@ -22,9 +22,11 @@
 #ifndef NANCY_UI_INVENTORYBOX_H
 #define NANCY_UI_INVENTORYBOX_H
 
-#include "engines/nancy/time.h"
+#include "common/ptr.h"
 
+#include "engines/nancy/time.h"
 #include "engines/nancy/renderobject.h"
+#include "engines/nancy/ui/scrollbar.h"
 
 namespace Nancy {
 
@@ -37,8 +39,6 @@ class Scene;
 
 namespace UI {
 
-class Scrollbar;
-
 class InventoryBox : public RenderObject {
 	friend class Nancy::State::Scene;
 
@@ -50,7 +50,6 @@ public:
 	};
 
 	InventoryBox();
-	virtual ~InventoryBox();
 
 	void init() override;
 	void updateGraphics() override;
@@ -96,7 +95,7 @@ private:
 	Graphics::ManagedSurface _iconsSurface;
 	Graphics::ManagedSurface _fullInventorySurface;
 
-	Scrollbar *_scrollbar;
+	Common::ScopedPtr<Scrollbar> _scrollbar;
 	Curtains _curtains;
 
 	float _scrollbarPos;


Commit: b7829323038bdd90e7ecadf1cf52ba490afc3f7d
    https://github.com/scummvm/scummvm/commit/b7829323038bdd90e7ecadf1cf52ba490afc3f7d
Author: Kaloyan Chehlarski (strahy at outlook.com)
Date: 2025-11-11T11:50:19+01:00

Commit Message:
NANCY: Use ScopedPtr in SecondaryMovie

Changed paths:
    engines/nancy/action/secondarymovie.cpp
    engines/nancy/action/secondarymovie.h


diff --git a/engines/nancy/action/secondarymovie.cpp b/engines/nancy/action/secondarymovie.cpp
index 90a1c43bfc2..19273fd2431 100644
--- a/engines/nancy/action/secondarymovie.cpp
+++ b/engines/nancy/action/secondarymovie.cpp
@@ -36,8 +36,6 @@ namespace Nancy {
 namespace Action {
 
 PlaySecondaryMovie::~PlaySecondaryMovie() {
-	delete _decoder;
-
 	if (NancySceneState.getActiveMovie() == this) {
 		NancySceneState.setActiveMovie(nullptr);
 	}
@@ -97,9 +95,9 @@ void PlaySecondaryMovie::readData(Common::SeekableReadStream &stream) {
 void PlaySecondaryMovie::init() {
 	if (!_decoder) {
 		if (_videoType == kVideoPlaytypeAVF) {
-			_decoder = new AVFDecoder();
+			_decoder.reset(new AVFDecoder());
 		} else {
-			_decoder = new Video::BinkDecoder();
+			_decoder.reset(new Video::BinkDecoder());
 		}
 	}
 
@@ -145,7 +143,7 @@ void PlaySecondaryMovie::execute() {
 			// Sync audio and video. This is mostly relevant for some nancy2 scenes, as the
 			// devs stopped using the built-in movie sound around nancy4. The 12 ms
 			// difference is roughly how long it takes for a single execution of the main game loop
-			((AVFDecoder *)_decoder)->addFrameTime(12);
+			((AVFDecoder *)_decoder.get())->addFrameTime(12);
 		}
 
 		if (_playerCursorAllowed == kNoPlayerCursorAllowed) {
diff --git a/engines/nancy/action/secondarymovie.h b/engines/nancy/action/secondarymovie.h
index 351bcdde1a3..93520580805 100644
--- a/engines/nancy/action/secondarymovie.h
+++ b/engines/nancy/action/secondarymovie.h
@@ -22,6 +22,8 @@
 #ifndef NANCY_ACTION_SECONDARYMOVIE_H
 #define NANCY_ACTION_SECONDARYMOVIE_H
 
+#include "common/ptr.h"
+
 #include "engines/nancy/action/actionrecord.h"
 
 namespace Video {
@@ -86,7 +88,7 @@ public:
 	SceneChangeDescription _sceneChange;
 	Common::Array<SecondaryVideoDescription> _videoDescs;
 
-	Video::VideoDecoder *_decoder = nullptr;
+	Common::ScopedPtr<Video::VideoDecoder> _decoder;
 
 protected:
 	Common::String getRecordTypeName() const override { return "PlaySecondaryMovie"; }




More information about the Scummvm-git-logs mailing list