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

fracturehill noreply at scummvm.org
Mon Aug 28 16:55:40 UTC 2023


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

Summary:
d98daea902 NANCY: Add export_image console command
7b11d63558 NANCY: Re-enable original main menu
f9413d1bda NANCY: Clear save_slot key after loading save
a006323d3d NANCY: Make scrollbars more usable
00c7f4d128 NANCY: Implement setup menu
17f9b48b2d NANCY: Pause all sound in GMM
53e548c818 NANCY: Do not leak chunk stream data
f3b5237a82 NANCY: Show partner logo on game start
c0264ed27f DEVTOOLS: Fix incorrect value in nancy.dat
b9c04f6c95 DEVTOOLS: Add new strings to nancy.dat
82494341ec NANCY: Read new string in nancy.dat
a75fd6dfcf NANCY: Add ReadRect16() utility functions
417669db9b NANCY: Reserve a special slot for Second Chance saves
f25b6e42a7 NANCY: Implement original load/save menu
99b358b056 NANCY: Fix Coverity warnings
1bb5d42a2e NANCY: Implement save prompt on quit
cb60680d37 NANCY: Use original menus by default
7e5f7030dd NANCY: Fix cursor transparency in save/load menu
efcd1757f5 NANCY: Add keymap for opening main menu


Commit: d98daea9023543ffac48d5496f503f0c69eadd6d
    https://github.com/scummvm/scummvm/commit/d98daea9023543ffac48d5496f503f0c69eadd6d
Author: Kaloyan Chehlarski (strahy at outlook.com)
Date: 2023-08-28T19:53:31+03:00

Commit Message:
NANCY: Add export_image console command

Changed paths:
    engines/nancy/console.cpp
    engines/nancy/console.h


diff --git a/engines/nancy/console.cpp b/engines/nancy/console.cpp
index 0c8c3aa9293..273c92ee699 100644
--- a/engines/nancy/console.cpp
+++ b/engines/nancy/console.cpp
@@ -24,6 +24,8 @@
 
 #include "audio/audiostream.h"
 
+#include "image/bmp.h"
+
 #include "engines/nancy/nancy.h"
 #include "engines/nancy/console.h"
 #include "engines/nancy/resource.h"
@@ -48,6 +50,7 @@ NancyConsole::NancyConsole() : GUI::Debugger() {
 	registerCmd("chunk_hexdump", WRAP_METHOD(NancyConsole, Cmd_chunkHexDump));
 	registerCmd("chunk_list", WRAP_METHOD(NancyConsole, Cmd_chunkList));
 	registerCmd("show_image", WRAP_METHOD(NancyConsole, Cmd_showImage));
+	registerCmd("export_image", WRAP_METHOD(NancyConsole, Cmd_exportImage));
 	registerCmd("play_video", WRAP_METHOD(NancyConsole, Cmd_playVideo));
 	registerCmd("play_sound", WRAP_METHOD(NancyConsole, Cmd_playSound));
 	registerCmd("load_scene", WRAP_METHOD(NancyConsole, Cmd_loadScene));
@@ -335,6 +338,29 @@ bool NancyConsole::Cmd_showImage(int argc, const char **argv) {
 	}
 }
 
+bool NancyConsole::Cmd_exportImage(int argc, const char **argv) {
+	if (argc != 2) {
+		debugPrintf("Exports an image to a file\n");
+		debugPrintf("Usage: %s <name>\n", argv[0]);
+		return true;
+	}
+
+	Graphics::ManagedSurface surf;
+	if (g_nancy->_resource->loadImage(argv[1], surf)) {
+		Common::DumpFile f;
+		if (!f.open(Common::String(argv[1]) + ".bmp")) {
+			debugPrintf("Couldn't open file for writing!");
+
+			return true;
+		}
+		Image::writeBMP(f, surf);
+	} else {
+		debugPrintf("File doesn't exist!\n");
+	}
+
+	return true;
+}
+
 bool NancyConsole::Cmd_playVideo(int argc, const char **argv) {
 	if (g_nancy->getGameType() == kGameTypeVampire) {
 		if (argc != 3) {
diff --git a/engines/nancy/console.h b/engines/nancy/console.h
index 99d1453b3ea..f52488fb40c 100644
--- a/engines/nancy/console.h
+++ b/engines/nancy/console.h
@@ -49,6 +49,7 @@ private:
 	bool Cmd_chunkHexDump(int argc, const char **argv);
 	bool Cmd_chunkList(int argc, const char **argv);
 	bool Cmd_showImage(int argc, const char **argv);
+	bool Cmd_exportImage(int argc, const char **argv);
 	bool Cmd_playVideo(int argc, const char **argv);
 	bool Cmd_playSound(int argc, const char **argv);
 	bool Cmd_loadScene(int argc, const char **argv);


Commit: 7b11d63558dc051e15b8e6c685fca4bbc0b00b2b
    https://github.com/scummvm/scummvm/commit/7b11d63558dc051e15b8e6c685fca4bbc0b00b2b
Author: Kaloyan Chehlarski (strahy at outlook.com)
Date: 2023-08-28T19:53:31+03:00

Commit Message:
NANCY: Re-enable original main menu

Fixed up all the old commented-out menu code and made
it useable again. Added support for at least up to nancy5's
main menu. For now, several buttons have been left disabled
and the menus are only accessible through a hidden
config option.

Changed paths:
    engines/nancy/enginedata.cpp
    engines/nancy/enginedata.h
    engines/nancy/nancy.cpp
    engines/nancy/nancy.h
    engines/nancy/state/credits.cpp
    engines/nancy/state/mainmenu.cpp
    engines/nancy/state/mainmenu.h
    engines/nancy/ui/button.cpp
    engines/nancy/ui/button.h


diff --git a/engines/nancy/enginedata.cpp b/engines/nancy/enginedata.cpp
index 31645cbaed3..bd171fe7cf8 100644
--- a/engines/nancy/enginedata.cpp
+++ b/engines/nancy/enginedata.cpp
@@ -354,6 +354,44 @@ CRED::CRED(Common::SeekableReadStream *chunkStream) {
 	delete chunkStream;
 }
 
+MENU::MENU(Common::SeekableReadStream *chunkStream) {
+	assert(chunkStream);
+
+	chunkStream->seek(0);
+	readFilename(*chunkStream, _imageName);
+	chunkStream->skip(22);
+
+	uint numOptions = 8;
+
+	_buttonDests.resize(numOptions);
+	_buttonDownSrcs.resize(numOptions);
+
+	if (g_nancy->getGameType() <= kGameTypeNancy1) {
+		for (uint i = 0; i < numOptions; ++i) {
+			Common::Rect &rect = _buttonDests[i];
+			rect.left = chunkStream->readSint16LE();
+			rect.top = chunkStream->readSint16LE();
+			rect.right = chunkStream->readSint16LE();
+			rect.bottom = chunkStream->readSint16LE();
+		}
+
+		for (uint i = 0; i < numOptions; ++i) {
+			Common::Rect &rect = _buttonDownSrcs[i];
+			rect.left = chunkStream->readSint16LE();
+			rect.top = chunkStream->readSint16LE();
+			rect.right = chunkStream->readSint16LE();
+			rect.bottom = chunkStream->readSint16LE();
+		}
+	} else {
+		_buttonHighlightSrcs.resize(numOptions);
+
+		readRectArray(*chunkStream, _buttonDests, numOptions);
+		readRectArray(*chunkStream, _buttonDownSrcs, numOptions);
+		readRectArray(*chunkStream, _buttonDisabledSrcs, numOptions);
+		readRectArray(*chunkStream, _buttonHighlightSrcs, numOptions);
+	}
+}
+
 HINT::HINT(Common::SeekableReadStream *chunkStream) {
 	assert(chunkStream);
 
diff --git a/engines/nancy/enginedata.h b/engines/nancy/enginedata.h
index 09cfe0d96bf..dab7344819c 100644
--- a/engines/nancy/enginedata.h
+++ b/engines/nancy/enginedata.h
@@ -177,6 +177,16 @@ struct CRED {
 	SoundDescription sound;
 };
 
+struct MENU {
+	MENU(Common::SeekableReadStream *chunkStream);
+
+	Common::String _imageName;
+	Common::Array<Common::Rect> _buttonDests;
+	Common::Array<Common::Rect> _buttonDownSrcs;
+	Common::Array<Common::Rect> _buttonHighlightSrcs;
+	Common::Array<Common::Rect> _buttonDisabledSrcs;
+};
+
 struct HINT {
 	HINT(Common::SeekableReadStream *chunkStream);
 
diff --git a/engines/nancy/nancy.cpp b/engines/nancy/nancy.cpp
index 333d45f11e3..eaaa36a6eb7 100644
--- a/engines/nancy/nancy.cpp
+++ b/engines/nancy/nancy.cpp
@@ -403,6 +403,7 @@ void NancyEngine::bootGameEngine() {
 	_textboxData = new TBOX(boot->getChunkStream("TBOX"));
 	_helpData = new HELP(boot->getChunkStream("HELP"));
 	_creditsData = new CRED(boot->getChunkStream("CRED"));
+	_menuData = new MENU(boot->getChunkStream("MENU"));
 
 	// For now we ignore the potential for more than one of each of these
 	_imageChunks.setVal("OB0", boot->getChunkStream("OB0"));
diff --git a/engines/nancy/nancy.h b/engines/nancy/nancy.h
index 62b1a25424b..75e058bab76 100644
--- a/engines/nancy/nancy.h
+++ b/engines/nancy/nancy.h
@@ -127,6 +127,7 @@ public:
 	MAP *_mapData;
 	HELP *_helpData;
 	CRED *_creditsData;
+	MENU *_menuData;
 	HINT *_hintData;
 	SPUZ *_sliderPuzzleData;
 	CLOK *_clockData;
diff --git a/engines/nancy/state/credits.cpp b/engines/nancy/state/credits.cpp
index b2c8ba5409e..8ded5a5147d 100644
--- a/engines/nancy/state/credits.cpp
+++ b/engines/nancy/state/credits.cpp
@@ -30,6 +30,7 @@
 #include "engines/nancy/state/credits.h"
 
 #include "common/events.h"
+#include "common/config-manager.h"
 
 namespace Common {
 DECLARE_SINGLETON(Nancy::State::Credits);
@@ -101,8 +102,10 @@ void Credits::run() {
 		g_nancy->setMouseEnabled(true);
 		_fullTextSurface.free();
 
-		// We don't yet support the original menus, so we close the game and go back to the launcher
-		// g_nancy->setState(NancyState::kMainMenu);
+		if (ConfMan.getBool("original_menus")) {
+			g_nancy->setState(NancyState::kMainMenu);
+			return;
+		}
 
 		Common::Event ev;
 		ev.type = Common::EVENT_RETURN_TO_LAUNCHER;
diff --git a/engines/nancy/state/mainmenu.cpp b/engines/nancy/state/mainmenu.cpp
index e4bfb015923..5654c07a31f 100644
--- a/engines/nancy/state/mainmenu.cpp
+++ b/engines/nancy/state/mainmenu.cpp
@@ -27,6 +27,9 @@
 
 #include "engines/nancy/state/mainmenu.h"
 #include "engines/nancy/state/scene.h"
+#include "engines/nancy/ui/button.h"
+
+#include "common/config-manager.h"
 
 namespace Common {
 DECLARE_SINGLETON(Nancy::State::MainMenu);
@@ -35,6 +38,12 @@ DECLARE_SINGLETON(Nancy::State::MainMenu);
 namespace Nancy {
 namespace State {
 
+MainMenu::~MainMenu() {
+	for (auto *button : _buttons) {
+		delete button;
+	}
+}
+
 void MainMenu::process() {
 	switch (_state) {
 	case kInit:
@@ -49,18 +58,33 @@ void MainMenu::process() {
 	}
 }
 
+void MainMenu::onStateEnter(const NancyState::NancyState prevState) {
+	registerGraphics();
+}
+
 bool MainMenu::onStateExit(const NancyState::NancyState nextState) {
 	return true;
 }
 
-void MainMenu::init() {
-	/*Common::SeekableReadStream *chunk = g_nancy->getBootChunkStream("MENU");
-	chunk->seek(0);
+void MainMenu::registerGraphics() {
+	_background.registerGraphics();
 
-	Common::String imageName;
-	readFilename(*chunk, imageName);
+	for (auto *button : _buttons) {
+		button->registerGraphics();
+	}
+}
 
-	_background.init(imageName);
+void MainMenu::clearButtonState() {
+	for (auto *button : _buttons) {
+		button->_isClicked = false;
+	}
+}
+
+void MainMenu::init() {
+	_menuData = g_nancy->_menuData;
+	assert(_menuData);
+
+	_background.init(_menuData->_imageName);
 	_background.registerGraphics();
 
 	g_nancy->_cursorManager->setCursorType(CursorManager::kNormalArrow);
@@ -70,28 +94,27 @@ void MainMenu::init() {
 		g_nancy->_sound->playSound("MSND");
 	}
 
-	chunk->seek(0x20);
-
-	// Unlike every other rect in the engine, these use int16 instead of int32
-	for (uint i = 0; i < 8; ++i) {
-		_destRects.push_back(Common::Rect());
-		Common::Rect &rect = _destRects.back();
-		rect.left = chunk->readSint16LE();
-		rect.top = chunk->readSint16LE();
-		rect.right = chunk->readSint16LE();
-		rect.bottom = chunk->readSint16LE();
+	for (uint i = 0; i < _menuData->_buttonDests.size(); ++i) {
+		_buttons.push_back(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);
 	}
 
-	for (uint i = 0; i < 8; ++i) {
-		_srcRects.push_back(Common::Rect());
-		Common::Rect &rect = _srcRects.back();
-		rect.left = chunk->readSint16LE();
-		rect.top = chunk->readSint16LE();
-		rect.right = chunk->readSint16LE();
-		rect.bottom = chunk->readSint16LE();
+	registerGraphics();
+
+	// Disable continue if game was just started
+	// Perhaps could be enabled always, and just load the latest save?
+	if (!Scene::hasInstance()) {
+		_buttons[3]->setDisabled(true);
 	}
 
-	_buttonDown.registerGraphics();*/
+	// Disable load/save & settings
+	_buttons[2]->setDisabled(true);
+	_buttons[5]->setDisabled(true);
 
 	_state = kRun;
 }
@@ -99,81 +122,77 @@ void MainMenu::init() {
 void MainMenu::run() {
 	NancyInput input = g_nancy->_input->getInput();
 
-	_buttonDown.setVisible(false);
-
-	if (input.input & NancyInput::kLeftMouseButtonUp) {
-		for (uint i = 0; i < 8; ++i) {
-			if (_destRects[i].contains(input.mousePos)) {
-				if (i == 3 && !Scene::hasInstance()) {
-					g_nancy->_sound->playSound("BUDE");
-					_playedOKSound = false;
-				} else {
-					g_nancy->_sound->playSound("BUOK");
-					_playedOKSound = true;
-				}
+	if (_selected != -1) {
+		input.input &= ~NancyInput::kLeftMouseButtonUp;
+	}
 
-				_selected = i;
-				_state = kStop;
+	for (uint i = 0; i < _buttons.size(); ++i) {
+		auto *button = _buttons[i];
+		button->handleInput(input);
+		if (_selected == -1 && button->_isClicked) {
+			if (button->_isDisabled) {
+				g_nancy->_sound->playSound("BUDE");
+			} else {
+				g_nancy->_sound->playSound("BUOK");
+			}
 
-				_buttonDown._drawSurface.create(_background._drawSurface, _srcRects[i]);
-				_buttonDown.moveTo(_destRects[i]);
-				_buttonDown.setVisible(true);
+			_selected = i;
+		}
+	}
 
-				return;
+	if (_selected != -1) {
+		if (!g_nancy->_sound->isSoundPlaying("BUOK") && !g_nancy->_sound->isSoundPlaying("BUDE")) {
+			if (_buttons[_selected]->_isDisabled) {
+				_selected = -1;
+				clearButtonState();
+			} else {
+				_state = kStop;
 			}
 		}
 	}
 }
 
 void MainMenu::stop() {
-	if (!g_nancy->_sound->isSoundPlaying(_playedOKSound ? "BUOK" : "BUDE")) {
-		switch (_selected) {
-		case 0:
-			// Credits
-			g_nancy->setState(NancyState::kCredits);
-			break;
-		case 1:
-			// New Game
-			if (Scene::hasInstance()) {
-				NancySceneState.destroy(); // Simply destroy the existing Scene and create a new one
-			}
-
-			g_nancy->setState(NancyState::kScene);
-			break;
-		case 2:
-			// Load and Save Game, TODO
-			_state = kRun;
-			break;
-		case 3:
-			// Continue
-			if (Scene::hasInstance()) {
-				g_nancy->setState(NancyState::kScene);
-			} else {
-				_state = kRun;
-			}
-			break;
-		case 4:
-			// Second Chance
-			if (!Scene::hasInstance()) {
-				NancySceneState.process(); // run once to init the state
-			}
+	switch (_selected) {
+	case 0:
+		// Credits
+		g_nancy->setState(NancyState::kCredits);
+		break;
+	case 1:
+		// New Game
+		if (Scene::hasInstance()) {
+			NancySceneState.destroy(); // Destroy the existing Scene and create a new one
+		}
 
-			g_nancy->loadGameState(g_nancy->getAutosaveSlot());
-			g_nancy->setState(NancyState::kScene);
-			break;
-		case 5:
-			// Game Setup, TODO
-			_state = kRun;
-			break;
-		case 6:
-			// Exit Game
-			g_nancy->quitGame();
-			break;
-		case 7:
-			// Help
-			g_nancy->setState(NancyState::kHelp);
-			break;
+		g_nancy->setState(NancyState::kScene);
+		break;
+	case 2:
+		// Load and Save Game, TODO
+		break;
+	case 3:
+		// Continue
+		g_nancy->setState(NancyState::kScene);
+		break;
+	case 4:
+		// Second Chance
+		if (Scene::hasInstance()) {
+			NancySceneState.destroy(); // Destroy the existing Scene and create a new one
 		}
+
+		ConfMan.setInt("save_slot", g_nancy->getMetaEngine()->getMaximumSaveSlot(), Common::ConfigManager::kTransientDomain);
+		g_nancy->setState(NancyState::kScene);
+		break;
+	case 5:
+		// Game Setup, TODO
+		break;
+	case 6:
+		// Exit Game
+		g_nancy->quitGame();
+		break;
+	case 7:
+		// Help
+		g_nancy->setState(NancyState::kHelp);
+		break;
 	}
 }
 
diff --git a/engines/nancy/state/mainmenu.h b/engines/nancy/state/mainmenu.h
index 753cb73f417..372cf8d5a7f 100644
--- a/engines/nancy/state/mainmenu.h
+++ b/engines/nancy/state/mainmenu.h
@@ -29,15 +29,22 @@
 #include "engines/nancy/ui/fullscreenimage.h"
 
 namespace Nancy {
+
+namespace UI {
+class Button;
+}
+
 namespace State {
 
 class MainMenu : public State, public Common::Singleton<MainMenu> {
 	friend class MainMenuButton;
 public:
-	MainMenu() : _state(kInit), _selected(-1), _playedOKSound(false), _buttonDown(5) {}
+	MainMenu() : _state(kInit), _selected(-1) {}
+	virtual ~MainMenu();
 
 	// State API
 	void process() override;
+	void onStateEnter(const NancyState::NancyState prevState) override;
 	bool onStateExit(const NancyState::NancyState nextState) override;
 
 private:
@@ -45,16 +52,18 @@ private:
 	void run();
 	void stop();
 
+	void registerGraphics();
+	void clearButtonState();
+
 	enum State { kInit, kRun, kStop };
 
 	UI::FullScreenImage _background;
-	RenderObject _buttonDown;
 	State _state;
 	int16 _selected;
-	bool _playedOKSound;
 
-	Common::Array<Common::Rect> _destRects;
-	Common::Array<Common::Rect> _srcRects;
+	Common::Array<UI::Button *> _buttons;
+
+	MENU *_menuData;
 };
 
 } // End of namespace State
diff --git a/engines/nancy/ui/button.cpp b/engines/nancy/ui/button.cpp
index 67fa6cd97fa..2bd320420fd 100644
--- a/engines/nancy/ui/button.cpp
+++ b/engines/nancy/ui/button.cpp
@@ -33,17 +33,23 @@
 namespace Nancy {
 namespace UI {
 
-Button::Button(uint16 zOrder, Graphics::ManagedSurface &surface, const Common::Rect &clickSrcBounds, const Common::Rect &destBounds, const Common::Rect &hoverSrcBounds) :
+Button::Button(uint16 zOrder, Graphics::ManagedSurface &surface, const Common::Rect &clickSrcBounds, const Common::Rect &destBounds, const Common::Rect &hoverSrcBounds, const Common::Rect &disabledSrcBounds) :
 		RenderObject(zOrder, surface, clickSrcBounds, destBounds),
 		surf(surface),
 		_clickSrc(clickSrcBounds),
 		_hoverSrc(hoverSrcBounds),
-		_isClicked(false) {
+		_disabledSrc(disabledSrcBounds),
+		_isClicked(false),
+		_isDisabled(false) {
 	setVisible(false);
 	setTransparent(true);
 }
 
 void Button::handleInput(NancyInput &input) {
+	if (_isDisabled && !_disabledSrc.isEmpty()) {
+		return;
+	}
+	
 	if (_screenPosition.contains(input.mousePos)) {
 		g_nancy->_cursorManager->setCursorType(CursorManager::kHotspotArrow);
 
@@ -54,7 +60,7 @@ void Button::handleInput(NancyInput &input) {
 
 		if (input.input & NancyInput::kLeftMouseButtonUp) {
 			_isClicked = true;
-			if (_hoverSrc.isEmpty()) {
+			if (_hoverSrc.isEmpty() && !_isDisabled) {
 				setVisible(true);
 			} else {
 				_drawSurface.create(surf, _clickSrc);
@@ -65,5 +71,19 @@ void Button::handleInput(NancyInput &input) {
 	}
 }
 
+void Button::setDisabled(bool disabled) {
+	if (disabled) {
+		_isDisabled = true;
+		if (!_disabledSrc.isEmpty()) {
+			_drawSurface.create(surf, _disabledSrc);
+			setVisible(true);
+		} else {
+			setVisible(false);
+		}
+	} else {
+		setVisible(false);
+	}
+}
+
 } // End of namespace UI
 } // End of namespace Nancy
diff --git a/engines/nancy/ui/button.h b/engines/nancy/ui/button.h
index f193bf521d6..3245b2ff793 100644
--- a/engines/nancy/ui/button.h
+++ b/engines/nancy/ui/button.h
@@ -35,15 +35,21 @@ public:
 	Button(uint16 zOrder, Graphics::ManagedSurface &surface,
 			const Common::Rect &clickSrcBounds,
 			const Common::Rect &destBounds,
-			const Common::Rect &hoverSrcBounds = Common::Rect());
+			const Common::Rect &hoverSrcBounds = Common::Rect(),
+			const Common::Rect &disabledSrcBounds = Common::Rect());
 	virtual ~Button() = default;
 
 	void handleInput(NancyInput &input);
 
+	void setDisabled(bool disabled);
+
 	Graphics::ManagedSurface &surf;
 	Common::Rect _clickSrc;
 	Common::Rect _hoverSrc;
+	Common::Rect _disabledSrc;
+	
 	bool _isClicked;
+	bool _isDisabled;
 };
 
 } // End of namespace UI


Commit: f9413d1bda1e36091074d7ead764eeff1676b9fb
    https://github.com/scummvm/scummvm/commit/f9413d1bda1e36091074d7ead764eeff1676b9fb
Author: Kaloyan Chehlarski (strahy at outlook.com)
Date: 2023-08-28T19:53:31+03:00

Commit Message:
NANCY: Clear save_slot key after loading save

This fixes the main menu just reloading the selected save
even when the user has picked "new game" instead.

Changed paths:
    engines/nancy/state/scene.cpp


diff --git a/engines/nancy/state/scene.cpp b/engines/nancy/state/scene.cpp
index 6ace33407a2..1685e3fd765 100644
--- a/engines/nancy/state/scene.cpp
+++ b/engines/nancy/state/scene.cpp
@@ -582,6 +582,9 @@ void Scene::init() {
 		if (saveSlot >= 0 && saveSlot <= g_nancy->getMetaEngine()->getMaximumSaveSlot()) {
 			g_nancy->loadGameState(saveSlot);
 		}
+
+		// Remove key so clicking on "New Game" in start menu doesn't just reload the save
+		ConfMan.removeKey("save_slot", Common::ConfigManager::kTransientDomain);
 	} else {
 		// Normal boot, load default first scene
 		_state = kLoad;


Commit: a006323d3dd1b885f39742e9bb807973d9eeacca
    https://github.com/scummvm/scummvm/commit/a006323d3dd1b885f39742e9bb807973d9eeacca
Author: Kaloyan Chehlarski (strahy at outlook.com)
Date: 2023-08-28T19:53:32+03:00

Commit Message:
NANCY: Make scrollbars more usable

Fixed the way scrollbars are handled, at the cost of some
accuracy. As a result, moving the mouse outside of the
scrollbar's hotspot no longer stops it from moving.

Changed paths:
    engines/nancy/state/scene.cpp
    engines/nancy/ui/scrollbar.cpp


diff --git a/engines/nancy/state/scene.cpp b/engines/nancy/state/scene.cpp
index 1685e3fd765..14edd68a176 100644
--- a/engines/nancy/state/scene.cpp
+++ b/engines/nancy/state/scene.cpp
@@ -792,8 +792,13 @@ void Scene::handleInput() {
 		}
 	}
 
+	// We handle the textbox and inventory box first because of their scrollbars, which
+	// need to take highest priority
+	_textbox.handleInput(input);
+	_inventoryBox.handleInput(input);
+
 	// Handle invisible map button
-	// We do this first since TVD's map button overlaps the viewport's right hotspot
+	// We do this before the viewport since TVD's map button overlaps the viewport's right hotspot
 	for (uint16 id : g_nancy->getStaticData().mapAccessSceneIDs) {
 		if ((int)_sceneState.currentScene.sceneID == id) {
 			if (_mapHotspot.contains(input.mousePos)) {
@@ -829,8 +834,6 @@ void Scene::handleInput() {
 	}
 
 	_actionManager.handleInput(input);
-	_textbox.handleInput(input);
-	_inventoryBox.handleInput(input);
 
 	if (_menuButton) {
 		_menuButton->handleInput(input);
diff --git a/engines/nancy/ui/scrollbar.cpp b/engines/nancy/ui/scrollbar.cpp
index 0a79e0d7c1e..07b343e7ad4 100644
--- a/engines/nancy/ui/scrollbar.cpp
+++ b/engines/nancy/ui/scrollbar.cpp
@@ -53,7 +53,10 @@ void Scrollbar::init() {
 }
 
 void Scrollbar::handleInput(NancyInput &input) {
-	if (_screenPosition.contains(input.mousePos)) {
+	// Note: the original engine's scrollbars only work if the cursor is inside
+	// the hotspot (happens if we remove the _isClicked check below). This doesn't make
+	// for great UX, however, so it has been fixed.
+	if (_screenPosition.contains(input.mousePos) || _isClicked) {
 		g_nancy->_cursorManager->setCursorType(CursorManager::kHotspotArrow);
 
 		if (input.input & NancyInput::kLeftMouseButtonDown && !_isClicked) {
@@ -90,9 +93,18 @@ void Scrollbar::handleInput(NancyInput &input) {
 		}
 	}
 
+	bool wasClicked = _isClicked;
 	if (input.input & NancyInput::kLeftMouseButtonUp) {
 		_isClicked = false;
 	}
+
+	// If the mouse is clicked and moves outside the scrollbar's hotspot, we don't want it
+	// to trigger other events. This only works if scrollbars are at the very top of the input priority.
+	// As a result, this effect won't be applied to the scrollbars in SoundEqualizerPuzzle
+	// In the future, this can be fixed by creating an input queue inside InputManager.
+	if (wasClicked) {
+		input.eatMouseInput();
+	}
 }
 
 void Scrollbar::setPosition(float pos) {


Commit: 00c7f4d128ec977507728e0154c7f24179ebb129
    https://github.com/scummvm/scummvm/commit/00c7f4d128ec977507728e0154c7f24179ebb129
Author: Kaloyan Chehlarski (strahy at outlook.com)
Date: 2023-08-28T19:53:32+03:00

Commit Message:
NANCY: Implement setup menu

Implemented the Setup menu, which handles game settings.
Also made some fixes to MainMenu.

Changed paths:
  A engines/nancy/state/setupmenu.cpp
  A engines/nancy/state/setupmenu.h
    engines/nancy/enginedata.cpp
    engines/nancy/enginedata.h
    engines/nancy/module.mk
    engines/nancy/nancy.cpp
    engines/nancy/nancy.h
    engines/nancy/state/mainmenu.cpp
    engines/nancy/state/mainmenu.h
    engines/nancy/state/scene.cpp
    engines/nancy/ui/button.cpp
    engines/nancy/ui/button.h
    engines/nancy/ui/scrollbar.cpp


diff --git a/engines/nancy/enginedata.cpp b/engines/nancy/enginedata.cpp
index bd171fe7cf8..6ad83f205ad 100644
--- a/engines/nancy/enginedata.cpp
+++ b/engines/nancy/enginedata.cpp
@@ -392,6 +392,41 @@ MENU::MENU(Common::SeekableReadStream *chunkStream) {
 	}
 }
 
+SET::SET(Common::SeekableReadStream *chunkStream) {
+	assert(chunkStream);
+
+	chunkStream->seek(0);
+	readFilename(*chunkStream, _imageName);
+	chunkStream->skip(20); // image info
+	chunkStream->skip(16); // bounds for all scrollbars
+
+	uint numButtons = g_nancy->getGameType() == kGameTypeVampire ? 5 : 4;
+
+	readRectArray(*chunkStream, _scrollbarBounds, 3);
+	readRectArray(*chunkStream, _buttonDests, numButtons);
+	readRectArray(*chunkStream, _buttonDownSrcs, numButtons);
+
+	if (g_nancy->getGameType() >= kGameTypeNancy2) {
+		readRect(*chunkStream, _doneButtonHighlightSrc);
+	}
+	
+	readRectArray(*chunkStream, _scrollbarSrcs, 3);
+
+	_scrollbarsCenterYPos.resize(3);
+	_scrollbarsCenterXPosL.resize(3);
+	_scrollbarsCenterXPosR.resize(3);
+	for (uint i = 0; i < 3; ++i) {
+		_scrollbarsCenterYPos[i] = chunkStream->readUint16LE();
+		_scrollbarsCenterXPosL[i] = chunkStream->readUint16LE();
+		_scrollbarsCenterXPosR[i] = chunkStream->readUint16LE();
+	}
+
+	_sounds.resize(3);
+	for (uint i = 0; i < 3; ++i) {
+		_sounds[i].readMenu(*chunkStream);
+	}
+}
+
 HINT::HINT(Common::SeekableReadStream *chunkStream) {
 	assert(chunkStream);
 
diff --git a/engines/nancy/enginedata.h b/engines/nancy/enginedata.h
index dab7344819c..4c309c124cf 100644
--- a/engines/nancy/enginedata.h
+++ b/engines/nancy/enginedata.h
@@ -187,6 +187,24 @@ struct MENU {
 	Common::Array<Common::Rect> _buttonDisabledSrcs;
 };
 
+struct SET {
+	SET(Common::SeekableReadStream *chunkStream);
+
+	Common::String _imageName;
+	// Common::Rect _scrollbarsBounds
+	Common::Array<Common::Rect> _scrollbarBounds;
+	Common::Array<Common::Rect> _buttonDests;
+	Common::Array<Common::Rect> _buttonDownSrcs;
+	Common::Rect _doneButtonHighlightSrc;
+	Common::Array<Common::Rect> _scrollbarSrcs;
+
+	Common::Array<uint16> _scrollbarsCenterYPos;
+	Common::Array<uint16> _scrollbarsCenterXPosL;
+	Common::Array<uint16> _scrollbarsCenterXPosR;
+
+	Common::Array<SoundDescription> _sounds;
+};
+
 struct HINT {
 	HINT(Common::SeekableReadStream *chunkStream);
 
diff --git a/engines/nancy/module.mk b/engines/nancy/module.mk
index ad6670c7463..d4d48d7dc4b 100644
--- a/engines/nancy/module.mk
+++ b/engines/nancy/module.mk
@@ -38,6 +38,7 @@ MODULE_OBJS = \
   state/mainmenu.o \
   state/map.o \
   state/scene.o \
+  state/setupmenu.o \
   misc/lightning.o \
   misc/specialeffect.o \
   commontypes.o \
diff --git a/engines/nancy/nancy.cpp b/engines/nancy/nancy.cpp
index eaaa36a6eb7..c405b7a17d3 100644
--- a/engines/nancy/nancy.cpp
+++ b/engines/nancy/nancy.cpp
@@ -44,6 +44,7 @@
 #include "engines/nancy/state/map.h"
 #include "engines/nancy/state/credits.h"
 #include "engines/nancy/state/mainmenu.h"
+#include "engines/nancy/state/setupmenu.h"
 
 namespace Nancy {
 
@@ -77,6 +78,8 @@ NancyEngine::NancyEngine(OSystem *syst, const NancyGameDescription *gd) :
 	_mapData = nullptr;
 	_helpData = nullptr;
 	_creditsData = nullptr;
+	_menuData = nullptr;
+	_setupData = nullptr;
 	_hintData = nullptr;
 	_sliderPuzzleData = nullptr;
 	_clockData = nullptr;
@@ -100,6 +103,8 @@ NancyEngine::~NancyEngine() {
 	delete _mapData;
 	delete _helpData;
 	delete _creditsData;
+	delete _menuData;
+	delete _setupData;
 	delete _hintData;
 	delete _sliderPuzzleData;
 	delete _clockData;
@@ -404,6 +409,7 @@ void NancyEngine::bootGameEngine() {
 	_helpData = new HELP(boot->getChunkStream("HELP"));
 	_creditsData = new CRED(boot->getChunkStream("CRED"));
 	_menuData = new MENU(boot->getChunkStream("MENU"));
+	_setupData = new SET(boot->getChunkStream("SET"));
 
 	// For now we ignore the potential for more than one of each of these
 	_imageChunks.setVal("OB0", boot->getChunkStream("OB0"));
@@ -464,6 +470,8 @@ State::State *NancyEngine::getStateObject(NancyState::NancyState state) const {
 		return &State::Credits::instance();
 	case NancyState::kMap:
 		return &State::Map::instance();
+	case NancyState::kSetup:
+		return &State::SetupMenu::instance();
 	case NancyState::kHelp:
 		return &State::Help::instance();
 	case NancyState::kScene:
@@ -507,6 +515,11 @@ void NancyEngine::destroyState(NancyState::NancyState state) const {
 			State::MainMenu::instance().destroy();
 		}
 		break;
+	case NancyState::kSetup:
+		if (State::SetupMenu::hasInstance()) {
+			State::SetupMenu::instance().destroy();
+		}
+		break;
 	default:
 		break;
 	}
diff --git a/engines/nancy/nancy.h b/engines/nancy/nancy.h
index 75e058bab76..99d97e83b31 100644
--- a/engines/nancy/nancy.h
+++ b/engines/nancy/nancy.h
@@ -128,6 +128,7 @@ public:
 	HELP *_helpData;
 	CRED *_creditsData;
 	MENU *_menuData;
+	SET *_setupData;
 	HINT *_hintData;
 	SPUZ *_sliderPuzzleData;
 	CLOK *_clockData;
diff --git a/engines/nancy/state/mainmenu.cpp b/engines/nancy/state/mainmenu.cpp
index 5654c07a31f..e711bff15ba 100644
--- a/engines/nancy/state/mainmenu.cpp
+++ b/engines/nancy/state/mainmenu.cpp
@@ -114,7 +114,6 @@ void MainMenu::init() {
 
 	// Disable load/save & settings
 	_buttons[2]->setDisabled(true);
-	_buttons[5]->setDisabled(true);
 
 	_state = kRun;
 }
@@ -150,6 +149,8 @@ void MainMenu::run() {
 			}
 		}
 	}
+
+	g_nancy->_cursorManager->setCursorType(CursorManager::kNormalArrow);
 }
 
 void MainMenu::stop() {
@@ -183,7 +184,8 @@ void MainMenu::stop() {
 		g_nancy->setState(NancyState::kScene);
 		break;
 	case 5:
-		// Game Setup, TODO
+		// Game Setup
+		g_nancy->setState(NancyState::kSetup);
 		break;
 	case 6:
 		// Exit Game
diff --git a/engines/nancy/state/mainmenu.h b/engines/nancy/state/mainmenu.h
index 372cf8d5a7f..d0492b32109 100644
--- a/engines/nancy/state/mainmenu.h
+++ b/engines/nancy/state/mainmenu.h
@@ -30,6 +30,8 @@
 
 namespace Nancy {
 
+struct MENU;
+
 namespace UI {
 class Button;
 }
@@ -37,9 +39,8 @@ class Button;
 namespace State {
 
 class MainMenu : public State, public Common::Singleton<MainMenu> {
-	friend class MainMenuButton;
 public:
-	MainMenu() : _state(kInit), _selected(-1) {}
+	MainMenu() : _state(kInit), _selected(-1), _menuData(nullptr) {}
 	virtual ~MainMenu();
 
 	// State API
diff --git a/engines/nancy/state/scene.cpp b/engines/nancy/state/scene.cpp
index 14edd68a176..23e14fb43ad 100644
--- a/engines/nancy/state/scene.cpp
+++ b/engines/nancy/state/scene.cpp
@@ -21,6 +21,7 @@
 
 #include "common/serializer.h"
 #include "common/config-manager.h"
+#include "common/func.h"
 
 #include "engines/nancy/nancy.h"
 #include "engines/nancy/iff.h"
diff --git a/engines/nancy/state/setupmenu.cpp b/engines/nancy/state/setupmenu.cpp
new file mode 100644
index 00000000000..2f487825cae
--- /dev/null
+++ b/engines/nancy/state/setupmenu.cpp
@@ -0,0 +1,246 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "engines/nancy/nancy.h"
+#include "engines/nancy/cursor.h"
+#include "engines/nancy/sound.h"
+#include "engines/nancy/input.h"
+#include "engines/nancy/util.h"
+
+#include "engines/nancy/state/setupmenu.h"
+#include "engines/nancy/state/scene.h"
+#include "engines/nancy/ui/button.h"
+#include "engines/nancy/ui/scrollbar.h"
+
+#include "common/config-manager.h"
+
+namespace Common {
+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:
+		init();
+		// fall through
+	case kRun:
+		run();
+		break;
+	case kStop:
+		stop();
+		break;
+	}
+}
+
+void SetupMenu::onStateEnter(const NancyState::NancyState prevState) {
+	registerGraphics();
+}
+
+bool SetupMenu::onStateExit(const NancyState::NancyState nextState) {
+	return true;
+}
+
+void SetupMenu::registerGraphics() {
+	_background.registerGraphics();
+
+	for (auto *tog : _toggles) {
+		tog->registerGraphics();
+	}
+
+	for (auto *scroll : _scrollbars) {
+		scroll->registerGraphics();
+	}
+
+	if (_exitButton) {
+		_exitButton->registerGraphics();
+	}
+}
+
+void SetupMenu::init() {
+	_setupData = g_nancy->_setupData;
+	assert(_setupData);
+
+	if (g_nancy->getGameType() == kGameTypeVampire) {
+		// There is a setup.bmp an the top directory of the first disk,
+		// which we need to avoid
+		_background.init("ART/" + _setupData->_imageName);
+	} else {
+		_background.init(_setupData->_imageName);
+	}
+
+	_background.registerGraphics();
+
+	g_nancy->_cursorManager->setCursorType(CursorManager::kNormalArrow);
+	g_nancy->setMouseEnabled(true);
+
+	g_nancy->_sound->stopSound("MSND");
+
+	for (uint i = 0; i < _setupData->_sounds.size(); ++i) {
+		if (!g_nancy->_sound->isSoundPlaying(_setupData->_sounds[i])) {
+			g_nancy->_sound->loadSound(_setupData->_sounds[i]);
+			g_nancy->_sound->playSound(_setupData->_sounds[i]);
+		}
+	}
+
+	for (uint i = 0; i < _setupData->_buttonDests.size() - 1; ++i) {
+		_toggles.push_back(new UI::Toggle(5, _background._drawSurface,
+			_setupData->_buttonDownSrcs[i], _setupData->_buttonDests[i]));
+		
+		_toggles.back()->init();
+	}
+
+	// Set toggle visibility
+	bool isVampire = g_nancy->getGameType() == kGameTypeVampire;
+	if (isVampire) {
+		// Interlaced video, currently useless
+		_toggles[1]->setState(false);
+	}
+	_toggles[0]->setState(ConfMan.getBool("subtitles"));
+	_toggles[isVampire ? 2 : 1]->setState(ConfMan.getBool("player_speech"));
+	_toggles[isVampire ? 3 : 2]->setState(ConfMan.getBool("character_speech"));
+
+	for (uint i = 0; i < _setupData->_scrollbarSrcs.size(); ++i) {
+		_scrollbars.push_back(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);
+	}
+
+	// Set scrollbar positions
+	_scrollbars[0]->setPosition(ConfMan.getInt("speech_volume") / 255.0);
+	_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,
+		_setupData->_buttonDownSrcs.back(), _setupData->_buttonDests.back());
+	_exitButton->init();
+	_exitButton->setVisible(false);
+
+	registerGraphics();
+
+	_state = kRun;
+}
+
+void SetupMenu::run() {
+	NancyInput input = g_nancy->_input->getInput();
+
+	for (uint i = 0; i < _scrollbars.size(); ++i) {
+		auto *scroll = _scrollbars[i];
+
+		float startPos = scroll->getPos();
+		scroll->handleInput(input);
+		float endPos = scroll->getPos();
+
+		if (endPos != startPos) {
+			Audio::Mixer::SoundType type;
+			switch (i) {
+			case 0 :
+				type = Audio::Mixer::SoundType::kSpeechSoundType;
+				ConfMan.setInt("speech_volume", endPos * 255, ConfMan.getActiveDomainName());
+				break;
+			case 1 :
+				type = Audio::Mixer::SoundType::kMusicSoundType;
+				ConfMan.setInt("music_volume", endPos * 255, ConfMan.getActiveDomainName());
+				break;
+			case 2 :
+				type = Audio::Mixer::SoundType::kSFXSoundType;
+				ConfMan.setInt("sfx_volume", endPos * 255, ConfMan.getActiveDomainName());
+				break;
+			}
+			
+			g_system->getMixer()->setVolumeForSoundType(type, endPos * 255);
+		}
+	}
+
+	for (uint i = 0; i < _toggles.size(); ++i) {
+		auto *tog = _toggles[i];
+		tog->handleInput(input);
+		if (tog->_stateChanged) {
+			bool isVampire = g_nancy->getGameType() == kGameTypeVampire;
+			uint toggleID = i;
+			// Make sure we ignore the interlaced video toggle
+			if (isVampire) {
+				if (i == 1) {
+					toggleID = 99;
+				} else if (i > 1) {
+					--toggleID;
+				}
+			}
+			switch (toggleID) {
+			case 0 :
+				ConfMan.setBool("subtitles", tog->_toggleState);
+				break;
+			case 1 :
+				ConfMan.setBool("player_speech", tog->_toggleState);
+				break;
+			case 2 :
+				ConfMan.setBool("character_speech", tog->_toggleState);
+				break;
+			default:
+				break;
+			}
+		}
+	}
+
+	if (_exitButton) {
+		_exitButton->handleInput(input);
+
+		if (_exitButton->_isClicked) {
+			g_nancy->_sound->playSound("BUOK");
+			_state = kStop;
+		}
+	}
+
+	g_nancy->_cursorManager->setCursorType(CursorManager::kNormalArrow);
+}
+
+void SetupMenu::stop() {
+	if (g_nancy->_sound->isSoundPlaying("BUOK")) {
+		return;
+	}
+
+	for (auto &sound : _setupData->_sounds) {
+		g_nancy->_sound->stopSound(sound);
+	}
+
+	ConfMan.flushToDisk();
+
+	g_nancy->setToPreviousState();
+}
+
+} // End of namespace State
+} // End of namespace Nancy
diff --git a/engines/nancy/state/setupmenu.h b/engines/nancy/state/setupmenu.h
new file mode 100644
index 00000000000..58b6d228054
--- /dev/null
+++ b/engines/nancy/state/setupmenu.h
@@ -0,0 +1,75 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef NANCY_STATE_SETUPMENU_H
+#define NANCY_STATE_SETUPMENU_H
+
+#include "common/singleton.h"
+
+#include "engines/nancy/state/state.h"
+
+#include "engines/nancy/ui/fullscreenimage.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;
+	bool onStateExit(const NancyState::NancyState nextState) override;
+
+private:
+	void init();
+	void run();
+	void stop();
+
+	void registerGraphics();
+
+	enum State { kInit, kRun, kStop };
+
+	UI::FullScreenImage _background;
+	State _state;
+
+	Common::Array<UI::Toggle *> _toggles;
+	Common::Array<UI::Scrollbar *> _scrollbars;
+	UI::Button *_exitButton;
+
+	SET *_setupData;
+};
+
+} // End of namespace State
+} // End of namespace Nancy
+
+#endif // NANCY_STATE_SETUPMENU_H
diff --git a/engines/nancy/ui/button.cpp b/engines/nancy/ui/button.cpp
index 2bd320420fd..5e59bfc4fd0 100644
--- a/engines/nancy/ui/button.cpp
+++ b/engines/nancy/ui/button.cpp
@@ -85,5 +85,42 @@ void Button::setDisabled(bool disabled) {
 	}
 }
 
+Toggle::Toggle(uint16 zOrder, Graphics::ManagedSurface &surface, Common::Rect &srcRect, Common::Rect &destRect) :
+		RenderObject(zOrder, surface, srcRect, destRect),
+		surf(surface),
+		_clickSrc(srcRect),
+		_toggleState(false),
+		_stateChanged(false) {
+	setVisible(false);
+	setTransparent(true);
+}
+
+void Toggle::handleInput(NancyInput &input) {
+	_stateChanged = false;
+
+	if (_screenPosition.contains(input.mousePos)) {
+		g_nancy->_cursorManager->setCursorType(CursorManager::kHotspotArrow);
+
+		if (input.input & NancyInput::kLeftMouseButtonUp) {
+			setState(!_toggleState);
+		}
+	}
+}
+
+void Toggle::setState(bool toggleState) {
+	if (_toggleState == toggleState) {
+		return;
+	}
+
+	_toggleState = toggleState;
+	_stateChanged = true;
+
+	if (toggleState) {
+		setVisible(true);
+	} else {
+		setVisible(false);
+	}
+}
+
 } // End of namespace UI
 } // End of namespace Nancy
diff --git a/engines/nancy/ui/button.h b/engines/nancy/ui/button.h
index 3245b2ff793..ee754eeb056 100644
--- a/engines/nancy/ui/button.h
+++ b/engines/nancy/ui/button.h
@@ -52,6 +52,21 @@ public:
 	bool _isDisabled;
 };
 
+class Toggle : public RenderObject {
+public:
+	Toggle(uint16 zOrder, Graphics::ManagedSurface &surface, Common::Rect &srcRect, Common::Rect &destRect);
+	virtual ~Toggle() = default;
+
+	void handleInput(NancyInput &input);
+	void setState(bool toggleState);
+
+	Graphics::ManagedSurface &surf;
+	Common::Rect _clickSrc;
+
+	bool _stateChanged;
+	bool _toggleState;
+};
+
 } // End of namespace UI
 } // End of namespace Nancy
 
diff --git a/engines/nancy/ui/scrollbar.cpp b/engines/nancy/ui/scrollbar.cpp
index 07b343e7ad4..54c7d6b22a3 100644
--- a/engines/nancy/ui/scrollbar.cpp
+++ b/engines/nancy/ui/scrollbar.cpp
@@ -43,6 +43,10 @@ Scrollbar::Scrollbar(uint16 zOrder, const Common::Rect &srcBounds, Graphics::Man
 	_startPosition = topPosition;
 	_startPosition.x -= srcBounds.width() / 2;
 
+	if (!isVertical) {
+		_startPosition.y -= srcBounds.height() / 2;
+	}
+
 	_screenPosition = srcBounds;
 	_screenPosition.moveTo(_startPosition);
 }
@@ -109,7 +113,11 @@ void Scrollbar::handleInput(NancyInput &input) {
 
 void Scrollbar::setPosition(float pos) {
     _currentPosition = pos;
-    moveTo(Common::Point(_screenPosition.left, _startPosition.y + (_maxDist * pos)));
+	if (_isVertical) {
+		moveTo(Common::Point(_screenPosition.left, _startPosition.y + (_maxDist * pos)));
+	} else {
+		moveTo(Common::Point(_startPosition.x + (_maxDist * pos), _screenPosition.top));
+	}
 }
 
 void Scrollbar::calculatePosition() {


Commit: 17f9b48b2df96ba6982faee5e8d2b8bf340260d5
    https://github.com/scummvm/scummvm/commit/17f9b48b2df96ba6982faee5e8d2b8bf340260d5
Author: Kaloyan Chehlarski (strahy at outlook.com)
Date: 2023-08-28T19:53:32+03:00

Commit Message:
NANCY: Pause all sound in GMM

Changed paths:
    engines/nancy/nancy.cpp


diff --git a/engines/nancy/nancy.cpp b/engines/nancy/nancy.cpp
index c405b7a17d3..5ecd764a49d 100644
--- a/engines/nancy/nancy.cpp
+++ b/engines/nancy/nancy.cpp
@@ -363,6 +363,8 @@ void NancyEngine::pauseEngineIntern(bool pause) {
 			s->onStateEnter(NancyState::kPause);
 		}
 	}
+
+	Engine::pauseEngineIntern(pause);
 }
 
 void NancyEngine::bootGameEngine() {


Commit: 53e548c818076cdbc7cb2461eaafd93eb0f7ef71
    https://github.com/scummvm/scummvm/commit/53e548c818076cdbc7cb2461eaafd93eb0f7ef71
Author: Kaloyan Chehlarski (strahy at outlook.com)
Date: 2023-08-28T19:53:32+03:00

Commit Message:
NANCY: Do not leak chunk stream data

Changed paths:
    engines/nancy/enginedata.cpp


diff --git a/engines/nancy/enginedata.cpp b/engines/nancy/enginedata.cpp
index 6ad83f205ad..1c19ba8c0ca 100644
--- a/engines/nancy/enginedata.cpp
+++ b/engines/nancy/enginedata.cpp
@@ -390,6 +390,8 @@ MENU::MENU(Common::SeekableReadStream *chunkStream) {
 		readRectArray(*chunkStream, _buttonDisabledSrcs, numOptions);
 		readRectArray(*chunkStream, _buttonHighlightSrcs, numOptions);
 	}
+
+	delete chunkStream;
 }
 
 SET::SET(Common::SeekableReadStream *chunkStream) {
@@ -425,6 +427,8 @@ SET::SET(Common::SeekableReadStream *chunkStream) {
 	for (uint i = 0; i < 3; ++i) {
 		_sounds[i].readMenu(*chunkStream);
 	}
+
+	delete chunkStream;
 }
 
 HINT::HINT(Common::SeekableReadStream *chunkStream) {


Commit: f3b5237a82a2e0bb03285fa661578533f8139739
    https://github.com/scummvm/scummvm/commit/f3b5237a82a2e0bb03285fa661578533f8139739
Author: Kaloyan Chehlarski (strahy at outlook.com)
Date: 2023-08-28T19:53:32+03:00

Commit Message:
NANCY: Show partner logo on game start

Games after nancy2 had a second logo that appeared on
startup, which is now also shown.

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


diff --git a/engines/nancy/nancy.cpp b/engines/nancy/nancy.cpp
index 5ecd764a49d..f531f079c24 100644
--- a/engines/nancy/nancy.cpp
+++ b/engines/nancy/nancy.cpp
@@ -418,12 +418,21 @@ void NancyEngine::bootGameEngine() {
 	_imageChunks.setVal("FR0", boot->getChunkStream("FR0"));
 	_imageChunks.setVal("LG0", boot->getChunkStream("LG0"));
 
+	auto *chunkStream = boot->getChunkStream("PLG0");
+	if (!chunkStream) {
+		chunkStream = boot->getChunkStream("PLGO"); // nancy4 and above use an O instead of a zero
+	}
+
+	if (chunkStream) {
+		_imageChunks.setVal("PLG0", chunkStream);
+	}
+
 	_cursorManager->init(boot->getChunkStream("CURS"));
 
 	_graphicsManager->init();
 	_graphicsManager->loadFonts(boot->getChunkStream("FONT"));
 
-	auto *chunkStream = boot->getChunkStream("MAP");
+	chunkStream = boot->getChunkStream("MAP");
 	if (chunkStream) {
 		_mapData = new MAP(chunkStream);
 	}
diff --git a/engines/nancy/state/logo.cpp b/engines/nancy/state/logo.cpp
index 21bbf1429fe..0ac900a64b6 100644
--- a/engines/nancy/state/logo.cpp
+++ b/engines/nancy/state/logo.cpp
@@ -84,6 +84,11 @@ void Logo::init() {
 	_logoImage.init(g_nancy->_imageChunks["LG0"].imageName);
 	_logoImage.registerGraphics();
 
+	if (g_nancy->_imageChunks.contains("PLG0")) {
+		_partnerLogoImage.init(g_nancy->_imageChunks["PLG0"].imageName);
+		_partnerLogoImage.registerGraphics();
+	}
+
 	if (g_nancy->getGameType() == kGameTypeVampire && _tvdVideoDecoder.loadFile("VAMPINTR.AVI")) {
 		_tvdVideoDecoder.start();
 		_videoObj.moveTo(Common::Rect(0, 0, 640, 480));
@@ -120,8 +125,18 @@ void Logo::startSound() {
 }
 
 void Logo::run() {
-	if ((g_nancy->getTotalPlayTime() - _startTicks >= g_nancy->getStaticData().logoEndAfter) ||
-		(g_nancy->_input->getInput().input & NancyInput::kLeftMouseButtonDown)) {
+	if (g_nancy->getTotalPlayTime() - _startTicks >= g_nancy->getStaticData().logoEndAfter) {
+		// Display game logo after partner logo
+		if (!_partnerLogoImage._drawSurface.empty() && _partnerLogoImage.isVisible()) {
+			_logoImage.setVisible(true);
+			_partnerLogoImage.setVisible(false);
+			_startTicks = g_nancy->getTotalPlayTime();
+		} else {
+			_state = kStop;
+		}
+	}
+
+	if (g_nancy->_input->getInput().input & NancyInput::kLeftMouseButtonDown) {
 		_state = kStop;
 	}
 }
diff --git a/engines/nancy/state/logo.h b/engines/nancy/state/logo.h
index 9e6d3a36b4a..cba948f85c7 100644
--- a/engines/nancy/state/logo.h
+++ b/engines/nancy/state/logo.h
@@ -60,6 +60,7 @@ private:
 	State _state;
 	uint _startTicks;
 	UI::FullScreenImage _logoImage;
+	UI::FullScreenImage _partnerLogoImage;
 	SoundDescription _msnd;
 	Video::AVIDecoder _tvdVideoDecoder;
 	RenderObject _videoObj;


Commit: c0264ed27f585542dfec1eef8a6712c4a8321d9d
    https://github.com/scummvm/scummvm/commit/c0264ed27f585542dfec1eef8a6712c4a8321d9d
Author: Kaloyan Chehlarski (strahy at outlook.com)
Date: 2023-08-28T19:53:32+03:00

Commit Message:
DEVTOOLS: Fix incorrect value in nancy.dat

Fixed the value for nancy5's logo timeout.

Changed paths:
    devtools/create_nancy/nancy5_data.h


diff --git a/devtools/create_nancy/nancy5_data.h b/devtools/create_nancy/nancy5_data.h
index b27ebadd0a0..31adf2ee886 100644
--- a/devtools/create_nancy/nancy5_data.h
+++ b/devtools/create_nancy/nancy5_data.h
@@ -33,7 +33,7 @@ const GameConstants _nancy5Constants ={
 		21, 22, 23, 24, 25, 26, 27, 28, 29, 30 },
 	36,
 	7,
-	7000
+	4000
 };
 
 const Common::Array<Common::Language> _nancy5LanguagesOrder = {


Commit: b9c04f6c95893b93180c1cabdd7cc10296ba3c5d
    https://github.com/scummvm/scummvm/commit/b9c04f6c95893b93180c1cabdd7cc10296ba3c5d
Author: Kaloyan Chehlarski (strahy at outlook.com)
Date: 2023-08-28T19:53:32+03:00

Commit Message:
DEVTOOLS: Add new strings to nancy.dat

Added the strings used in the save/load dialog to nancy.dat.
The file version doesn't need to be bumped.

Changed paths:
    devtools/create_nancy/create_nancy.cpp
    devtools/create_nancy/nancy1_data.h
    devtools/create_nancy/nancy2_data.h
    devtools/create_nancy/nancy3_data.h
    devtools/create_nancy/nancy4_data.h
    devtools/create_nancy/nancy5_data.h
    devtools/create_nancy/tvd_data.h


diff --git a/devtools/create_nancy/create_nancy.cpp b/devtools/create_nancy/create_nancy.cpp
index 4f5b9294a72..cafa856c508 100644
--- a/devtools/create_nancy/create_nancy.cpp
+++ b/devtools/create_nancy/create_nancy.cpp
@@ -129,6 +129,11 @@ void writeRingingTexts(File &output, const Common::Array<const char *> &ringingT
 	writeToFile(output, ringingTexts);
 }
 
+void writeEmptySaveTexts(File &output, const Common::Array<const char *> &emptySaveTexts) {
+	output.writeUint32(MKTAG('E', 'S', 'A', 'V'));
+	writeToFile(output, emptySaveTexts);
+}
+
 void writeEventFlagNames(File &output, const Common::Array<const char *> &eventFlagNames) {
 	output.writeUint32(MKTAG('E', 'F', 'L', 'G'));
 	writeToFile(output, eventFlagNames);
@@ -163,6 +168,7 @@ int main(int argc, char *argv[]) {
 	WRAPWITHOFFSET(writeLanguages(output, _tvdLanguagesOrder))
 	WRAPWITHOFFSET(writeConditionalDialogue(output, _tvdConditionalDialogue, _tvdConditionalDialogueTexts))
 	WRAPWITHOFFSET(writeGoodbyes(output, _tvdGoodbyes, _tvdGoodbyeTexts))
+	WRAPWITHOFFSET(writeEmptySaveTexts(output, _tvdEmptySaveStrings))
 	WRAPWITHOFFSET(writeEventFlagNames(output, _tvdEventFlagNames))
 	
 	// Nancy Drew: Secrets Can Kill data
@@ -174,6 +180,7 @@ int main(int argc, char *argv[]) {
 	WRAPWITHOFFSET(writeGoodbyes(output, _nancy1Goodbyes, _nancy1GoodbyeTexts))
 	WRAPWITHOFFSET(writeHints(output, _nancy1Hints, _nancy1HintSceneChange, _nancy1HintTexts))
 	WRAPWITHOFFSET(writeRingingTexts(output, _nancy1TelephoneRinging))
+	WRAPWITHOFFSET(writeEmptySaveTexts(output, _nancy1EmptySaveStrings))
 	WRAPWITHOFFSET(writeEventFlagNames(output, _nancy1EventFlagNames))
 	
 	// Nancy Drew: Stay Tuned for Danger data
@@ -184,6 +191,7 @@ int main(int argc, char *argv[]) {
 	WRAPWITHOFFSET(writeConditionalDialogue(output, _nancy2ConditionalDialogue, _nancy2ConditionalDialogueTexts))
 	WRAPWITHOFFSET(writeGoodbyes(output, _nancy2Goodbyes, _nancy2GoodbyeTexts))
 	WRAPWITHOFFSET(writeRingingTexts(output, _nancy2TelephoneRinging))
+	WRAPWITHOFFSET(writeEmptySaveTexts(output, _nancy2EmptySaveStrings))
 	WRAPWITHOFFSET(writeEventFlagNames(output, _nancy2EventFlagNames))
 	
 	// Nancy Drew: Message in a Haunted Mansion data
@@ -194,6 +202,7 @@ int main(int argc, char *argv[]) {
 	WRAPWITHOFFSET(writeConditionalDialogue(output, _nancy3ConditionalDialogue, _nancy3ConditionalDialogueTexts))
 	WRAPWITHOFFSET(writeGoodbyes(output, _nancy3Goodbyes, _nancy3GoodbyeTexts))
 	WRAPWITHOFFSET(writeRingingTexts(output, _nancy3TelephoneRinging))
+	WRAPWITHOFFSET(writeEmptySaveTexts(output, _nancy3EmptySaveStrings))
 	WRAPWITHOFFSET(writeEventFlagNames(output, _nancy3EventFlagNames))
 	
 	// Nancy Drew: Treasure in the Royal Tower data
@@ -204,6 +213,7 @@ int main(int argc, char *argv[]) {
 	WRAPWITHOFFSET(writeConditionalDialogue(output, _nancy4ConditionalDialogue, _nancy4ConditionalDialogueTexts))
 	WRAPWITHOFFSET(writeGoodbyes(output, _nancy4Goodbyes, _nancy4GoodbyeTexts))
 	WRAPWITHOFFSET(writeRingingTexts(output, _nancy4TelephoneRinging))
+	WRAPWITHOFFSET(writeEmptySaveTexts(output, _nancy4EmptySaveStrings))
 	WRAPWITHOFFSET(writeEventFlagNames(output, _nancy4EventFlagNames))
 
 	// Nancy Drew: The Final Scene data
@@ -214,6 +224,7 @@ int main(int argc, char *argv[]) {
 	WRAPWITHOFFSET(writeConditionalDialogue(output, _nancy5ConditionalDialogue, _nancy5ConditionalDialogueTexts))
 	WRAPWITHOFFSET(writeGoodbyes(output, _nancy5Goodbyes, _nancy5GoodbyeTexts))
 	WRAPWITHOFFSET(writeRingingTexts(output, _nancy5TelephoneRinging))
+	WRAPWITHOFFSET(writeEmptySaveTexts(output, _nancy5EmptySaveStrings))
 	WRAPWITHOFFSET(writeEventFlagNames(output, _nancy5EventFlagNames))
 
 	// Write the offsets for each game in the header
diff --git a/devtools/create_nancy/nancy1_data.h b/devtools/create_nancy/nancy1_data.h
index b8f9f187bfc..a119294debd 100644
--- a/devtools/create_nancy/nancy1_data.h
+++ b/devtools/create_nancy/nancy1_data.h
@@ -578,6 +578,11 @@ const Common::Array<const char *> _nancy1TelephoneRinging = {
 	"Hudki...  <n><e>"  // Russian
 };
 
+const Common::Array<const char *> _nancy1EmptySaveStrings = {
+	"-- Empty --",	// English
+	"- - - - -  "	// Russian
+};
+
 const Common::Array<const char *> _nancy1EventFlagNames = {
 	"Tried the locker",
 	"Locker open",
diff --git a/devtools/create_nancy/nancy2_data.h b/devtools/create_nancy/nancy2_data.h
index 75e4d3ffbc4..ecbc5dafff4 100644
--- a/devtools/create_nancy/nancy2_data.h
+++ b/devtools/create_nancy/nancy2_data.h
@@ -369,6 +369,11 @@ const Common::Array<const char *> _nancy2TelephoneRinging = {
 	"Hudki...  <n><e>"  // Russian
 };
 
+const Common::Array<const char *> _nancy2EmptySaveStrings = {
+	"Nothing Saved Here",	// English
+	"- - - - -"				// Russian
+};
+
 const Common::Array<const char *> _nancy2EventFlagNames = {
 	"Generic 0",
 	"Generic 1",
diff --git a/devtools/create_nancy/nancy3_data.h b/devtools/create_nancy/nancy3_data.h
index 2185df377b7..8c81cfc418d 100644
--- a/devtools/create_nancy/nancy3_data.h
+++ b/devtools/create_nancy/nancy3_data.h
@@ -509,6 +509,11 @@ const Common::Array<const char *> _nancy3TelephoneRinging = {
 	"Cjtlbytybt...<n><e>"  // Russian
 };
 
+const Common::Array<const char *> _nancy3EmptySaveStrings = {
+	"Nothing Saved Here",	// English
+	"- - - - -"				// Russian
+};
+
 const Common::Array<const char *> _nancy3EventFlagNames = {
 	"Generic 0",
 	"Generic 1",
diff --git a/devtools/create_nancy/nancy4_data.h b/devtools/create_nancy/nancy4_data.h
index 1da9f672260..debd446346c 100644
--- a/devtools/create_nancy/nancy4_data.h
+++ b/devtools/create_nancy/nancy4_data.h
@@ -428,6 +428,11 @@ const Common::Array<const char *> _nancy4TelephoneRinging = {
     "Hudki...  <n><e>"  // Russian
 };
 
+const Common::Array<const char *> _nancy4EmptySaveStrings = {
+	"Nothing Saved Here",	// English
+	"- - - - -"				// Russian
+};
+
 const Common::Array<const char *> _nancy4EventFlagNames = {
 	"EV_Generic0",
 	"EV_Generic1",
diff --git a/devtools/create_nancy/nancy5_data.h b/devtools/create_nancy/nancy5_data.h
index 31adf2ee886..88316c42aff 100644
--- a/devtools/create_nancy/nancy5_data.h
+++ b/devtools/create_nancy/nancy5_data.h
@@ -356,6 +356,11 @@ const Common::Array<const char *> _nancy5TelephoneRinging = {
 	"\xc3\xf3\xe4\xea\xe8...  <n><e>" // Russian
 };
 
+const Common::Array<const char *> _nancy5EmptySaveStrings = {
+	"Nothing Saved Here",	// English
+	"\xc3\xf3\xf1\xf2\xee"	// Russian
+};
+
 const Common::Array<const char *> _nancy5EventFlagNames = {
 	"EV_Generic0",
 	"EV_Generic1",
diff --git a/devtools/create_nancy/tvd_data.h b/devtools/create_nancy/tvd_data.h
index 7e63e7edfeb..4addce63c15 100644
--- a/devtools/create_nancy/tvd_data.h
+++ b/devtools/create_nancy/tvd_data.h
@@ -384,6 +384,10 @@ const Common::Array<Common::Array<const char *>> _tvdGoodbyeTexts = { {
 	"<c1>b<c0>ye.<h>", // EGBYE, again
 } };
 
+const Common::Array<const char *> _tvdEmptySaveStrings = {
+	"-- Empty --",	// English
+};
+
 const Common::Array<const char *> _tvdEventFlagNames = {
 	"Aristocrat Bonnie told about mikhails bgnd",
 	"AskedCandle asked about a black candle",


Commit: 82494341ec0aba756ea9c6aab274e7cd21b88bd6
    https://github.com/scummvm/scummvm/commit/82494341ec0aba756ea9c6aab274e7cd21b88bd6
Author: Kaloyan Chehlarski (strahy at outlook.com)
Date: 2023-08-28T19:53:32+03:00

Commit Message:
NANCY: Read new string in nancy.dat

Changed paths:
    engines/nancy/commontypes.cpp
    engines/nancy/commontypes.h


diff --git a/engines/nancy/commontypes.cpp b/engines/nancy/commontypes.cpp
index b63bf0b21ea..02388c97996 100644
--- a/engines/nancy/commontypes.cpp
+++ b/engines/nancy/commontypes.cpp
@@ -455,6 +455,17 @@ void StaticData::readData(Common::SeekableReadStream &stream, Common::Language l
 				}
 			}
 
+			break;
+		case MKTAG('E', 'S', 'A', 'V') :
+			num = stream.readUint16LE();
+			for (int i = 0; i < num; ++i) {
+				if (i == languageID) {
+					emptySaveText = stream.readString();
+				} else {
+					stream.readString();
+				}
+			}
+
 			break;
 		case MKTAG('E', 'F', 'L', 'G') :
 			// Event flag names
diff --git a/engines/nancy/commontypes.h b/engines/nancy/commontypes.h
index 56bc5d8a9e4..2e720b2c844 100644
--- a/engines/nancy/commontypes.h
+++ b/engines/nancy/commontypes.h
@@ -289,6 +289,7 @@ struct StaticData {
 	Common::Array<Common::String> goodbyeTexts;
 	Common::Array<Common::String> hintTexts;
 	Common::String ringingText;
+	Common::String emptySaveText;
 
 	// Debug strings
 	Common::Array<Common::String> eventFlagNames;


Commit: a75fd6dfcff3a48acedbc38021078bbb45392f26
    https://github.com/scummvm/scummvm/commit/a75fd6dfcff3a48acedbc38021078bbb45392f26
Author: Kaloyan Chehlarski (strahy at outlook.com)
Date: 2023-08-28T19:53:33+03:00

Commit Message:
NANCY: Add ReadRect16() utility functions

Also made all versions of ReadRect read signed integers.

Changed paths:
    engines/nancy/util.cpp
    engines/nancy/util.h


diff --git a/engines/nancy/util.cpp b/engines/nancy/util.cpp
index 39ec08ef8a8..04477a9bc4f 100644
--- a/engines/nancy/util.cpp
+++ b/engines/nancy/util.cpp
@@ -40,10 +40,10 @@ void readRect(Common::SeekableReadStream &stream, Common::Rect &inRect) {
 void readRect(Common::Serializer &stream, Common::Rect &inRect, Common::Serializer::Version minVersion, Common::Serializer::Version maxVersion) {
 	Common::Serializer::Version version = stream.getVersion();
 	if (version >= minVersion && version <= maxVersion) {
-		stream.syncAsUint32LE(inRect.left);
-		stream.syncAsUint32LE(inRect.top);
-		stream.syncAsUint32LE(inRect.right);
-		stream.syncAsUint32LE(inRect.bottom);
+		stream.syncAsSint32LE(inRect.left);
+		stream.syncAsSint32LE(inRect.top);
+		stream.syncAsSint32LE(inRect.right);
+		stream.syncAsSint32LE(inRect.bottom);
 
 		// TVD's rects are non-inclusive
 		if (version > kGameTypeVampire) {
@@ -65,10 +65,10 @@ void readRectArray(Common::Serializer &stream, Common::Array<Common::Rect> &inAr
 	if (version >= minVersion && version <= maxVersion) {
 		inArray.resize(num);
 		for (Common::Rect &rect : inArray) {
-			stream.syncAsUint32LE(rect.left);
-			stream.syncAsUint32LE(rect.top);
-			stream.syncAsUint32LE(rect.right);
-			stream.syncAsUint32LE(rect.bottom);
+			stream.syncAsSint32LE(rect.left);
+			stream.syncAsSint32LE(rect.top);
+			stream.syncAsSint32LE(rect.right);
+			stream.syncAsSint32LE(rect.bottom);
 
 			// TVD's rects are non-inclusive
 			if (version > kGameTypeVampire) {
@@ -79,6 +79,60 @@ void readRectArray(Common::Serializer &stream, Common::Array<Common::Rect> &inAr
 	}
 }
 
+void readRect16(Common::SeekableReadStream &stream, Common::Rect &inRect) {
+	inRect.left = stream.readSint16LE();
+	inRect.top = stream.readSint16LE();
+	inRect.right = stream.readSint16LE();
+	inRect.bottom = stream.readSint16LE();
+
+	// TVD's rects are non-inclusive
+	if (g_nancy->getGameType() > kGameTypeVampire) {
+		++inRect.right;
+		++inRect.bottom;
+	}
+}
+
+void readRect16(Common::Serializer &stream, Common::Rect &inRect, Common::Serializer::Version minVersion, Common::Serializer::Version maxVersion) {
+	Common::Serializer::Version version = stream.getVersion();
+	if (version >= minVersion && version <= maxVersion) {
+		stream.syncAsSint16LE(inRect.left);
+		stream.syncAsSint16LE(inRect.top);
+		stream.syncAsSint16LE(inRect.right);
+		stream.syncAsSint16LE(inRect.bottom);
+
+		// TVD's rects are non-inclusive
+		if (version > kGameTypeVampire) {
+			++inRect.right;
+			++inRect.bottom;
+		}
+	}
+}
+
+void readRectArray16(Common::SeekableReadStream &stream, Common::Array<Common::Rect> &inArray, uint num) {
+	inArray.resize(num);
+	for (Common::Rect &rect : inArray) {
+		readRect16(stream, rect);
+	}
+}
+
+void readRectArray16(Common::Serializer &stream, Common::Array<Common::Rect> &inArray, uint num, Common::Serializer::Version minVersion, Common::Serializer::Version maxVersion) {
+	Common::Serializer::Version version = stream.getVersion();
+	if (version >= minVersion && version <= maxVersion) {
+		inArray.resize(num);
+		for (Common::Rect &rect : inArray) {
+			stream.syncAsSint16LE(rect.left);
+			stream.syncAsSint16LE(rect.top);
+			stream.syncAsSint16LE(rect.right);
+			stream.syncAsSint16LE(rect.bottom);
+
+			// TVD's rects are non-inclusive
+			if (version > kGameTypeVampire) {
+				++rect.right;
+				++rect.bottom;
+			}
+		}
+	}
+}
 
 void readFilename(Common::SeekableReadStream &stream, Common::String &inString) {
 	char buf[33];
@@ -96,8 +150,6 @@ void readFilename(Common::SeekableReadStream &stream, Common::String &inString)
 	inString = buf;
 }
 
-
-// Reads an 8-character filename from a 10-character source
 void readFilename(Common::Serializer &stream, Common::String &inString, Common::Serializer::Version minVersion, Common::Serializer::Version maxVersion) {
 	Common::Serializer::Version version = stream.getVersion();
 	if (version >= minVersion && version <= maxVersion) {
diff --git a/engines/nancy/util.h b/engines/nancy/util.h
index a0eb19c5df9..61eabe711ba 100644
--- a/engines/nancy/util.h
+++ b/engines/nancy/util.h
@@ -31,6 +31,11 @@ void readRect(Common::Serializer &stream, Common::Rect &inRect, Common::Serializ
 void readRectArray(Common::SeekableReadStream &stream, Common::Array<Common::Rect> &inArray, uint num);
 void readRectArray(Common::Serializer &stream, Common::Array<Common::Rect> &inArray, uint num, Common::Serializer::Version minVersion = 0, Common::Serializer::Version maxVersion = Common::Serializer::kLastVersion);
 
+void readRect16(Common::SeekableReadStream &stream, Common::Rect &inRect);
+void readRect16(Common::Serializer &stream, Common::Rect &inRect, Common::Serializer::Version minVersion = 0, Common::Serializer::Version maxVersion = Common::Serializer::kLastVersion);
+void readRectArray16(Common::SeekableReadStream &stream, Common::Array<Common::Rect> &inArray, uint num);
+void readRectArray16(Common::Serializer &stream, Common::Array<Common::Rect> &inArray, uint num, Common::Serializer::Version minVersion = 0, Common::Serializer::Version maxVersion = Common::Serializer::kLastVersion);
+
 void readFilename(Common::SeekableReadStream &stream, Common::String &inString);
 void readFilename(Common::Serializer &stream, Common::String &inString, Common::Serializer::Version minVersion = 0, Common::Serializer::Version maxVersion = Common::Serializer::kLastVersion);
 void readFilenameArray(Common::SeekableReadStream &stream, Common::Array<Common::String> &inArray, uint num);


Commit: 417669db9bf24d5835def0266428366297ecf3a9
    https://github.com/scummvm/scummvm/commit/417669db9bf24d5835def0266428366297ecf3a9
Author: Kaloyan Chehlarski (strahy at outlook.com)
Date: 2023-08-28T19:53:33+03:00

Commit Message:
NANCY: Reserve a special slot for Second Chance saves

Second Chance saves have been moved to their own save
slot, which is now write-protected.

Changed paths:
    engines/nancy/metaengine.cpp
    engines/nancy/nancy.cpp
    engines/nancy/nancy.h


diff --git a/engines/nancy/metaengine.cpp b/engines/nancy/metaengine.cpp
index f9715a900b9..90c9c358416 100644
--- a/engines/nancy/metaengine.cpp
+++ b/engines/nancy/metaengine.cpp
@@ -64,6 +64,7 @@ public:
 	Common::Error createInstance(OSystem *syst, Engine **engine, const ADGameDescription *gd) const override;
 
 	int getMaximumSaveSlot() const override;
+	SaveStateDescriptor querySaveMetaInfos(const char *target, int slot) const override;
 
 	Common::KeymapArray initKeymaps(const char *target) const override;
 
@@ -100,6 +101,16 @@ Common::Error NancyMetaEngine::createInstance(OSystem *syst, Engine **engine, co
 
 int NancyMetaEngine::getMaximumSaveSlot() const { return 8; }
 
+SaveStateDescriptor NancyMetaEngine::querySaveMetaInfos(const char *target, int slot) const {
+	SaveStateDescriptor ret = AdvancedMetaEngine::querySaveMetaInfos(target, slot);
+	if (slot == getMaximumSaveSlot()) {
+		// We do not allow the second chance slot to be overwritten
+		ret.setWriteProtectedFlag(true);
+	}
+
+	return ret;
+}
+
 void NancyMetaEngine::getSavegameThumbnail(Graphics::Surface &thumb) {
 	// Second Chance autosaves trigger when a scene changes, but before
 	// it is drawn, so we need to refresh the screen before we take a screenshot
diff --git a/engines/nancy/nancy.cpp b/engines/nancy/nancy.cpp
index f531f079c24..68e62c5e024 100644
--- a/engines/nancy/nancy.cpp
+++ b/engines/nancy/nancy.cpp
@@ -143,28 +143,9 @@ bool NancyEngine::canSaveGameStateCurrently() {
 			NancySceneState.getActiveConversation() == nullptr;
 }
 
-bool NancyEngine::canSaveAutosaveCurrently() {
-	if (ConfMan.getBool("second_chance")) {
-		return false;
-	} else {
-		return Engine::canSaveAutosaveCurrently();
-	}
-}
-
 void NancyEngine::secondChance() {
-	SaveStateList saves = getMetaEngine()->listSaves(_targetName.c_str());
-	Common::String name = "SECOND CHANCE";
-
-	// Overwrite an existing second chance if possible
-	for (auto &save : saves) {
-		if (save.getDescription() == name) {
-			saveGameState(save.getSaveSlot(), name, true);
-			return;
-		}
-	}
-
-	// If no second chance slot exists, create a new one
-	saveGameState(saves.size(), name, true);
+	uint secondChanceSlot = getMetaEngine()->getMaximumSaveSlot();
+	saveGameState(secondChanceSlot, "SECOND CHANCE", true);
 }
 
 bool NancyEngine::hasFeature(EngineFeature f) const {
diff --git a/engines/nancy/nancy.h b/engines/nancy/nancy.h
index 99d97e83b31..28264c6c57e 100644
--- a/engines/nancy/nancy.h
+++ b/engines/nancy/nancy.h
@@ -85,7 +85,6 @@ public:
 	Common::Error saveGameStream(Common::WriteStream *stream, bool isAutosave = false) override;
 	bool canLoadGameStateCurrently() override;
 	bool canSaveGameStateCurrently() override;
-	bool canSaveAutosaveCurrently() override;
 
 	void secondChance();
 


Commit: f25b6e42a7ac0a64b39f89a065e3d8e7a9e7c038
    https://github.com/scummvm/scummvm/commit/f25b6e42a7ac0a64b39f89a065e3d8e7a9e7c038
Author: Kaloyan Chehlarski (strahy at outlook.com)
Date: 2023-08-28T19:53:33+03:00

Commit Message:
NANCY: Implement original load/save menu

Changed paths:
  A engines/nancy/state/loadsave.cpp
  A engines/nancy/state/loadsave.h
    engines/nancy/commontypes.h
    engines/nancy/enginedata.cpp
    engines/nancy/enginedata.h
    engines/nancy/font.cpp
    engines/nancy/font.h
    engines/nancy/graphics.cpp
    engines/nancy/graphics.h
    engines/nancy/metaengine.cpp
    engines/nancy/module.mk
    engines/nancy/nancy.cpp
    engines/nancy/nancy.h
    engines/nancy/state/mainmenu.cpp
    engines/nancy/state/scene.cpp
    engines/nancy/state/scene.h
    engines/nancy/ui/button.cpp


diff --git a/engines/nancy/commontypes.h b/engines/nancy/commontypes.h
index 2e720b2c844..7cca20aa1fb 100644
--- a/engines/nancy/commontypes.h
+++ b/engines/nancy/commontypes.h
@@ -80,7 +80,6 @@ enum MovementDirection : byte { kUp = 1, kDown = 2, kLeft = 4, kRight = 8, kMove
 namespace NancyState {
 enum NancyState {
 	kBoot,
-	kPartnerLogo,
 	kLogo,
 	kCredits,
 	kMap,
diff --git a/engines/nancy/enginedata.cpp b/engines/nancy/enginedata.cpp
index 1c19ba8c0ca..f6c60f2f4cf 100644
--- a/engines/nancy/enginedata.cpp
+++ b/engines/nancy/enginedata.cpp
@@ -431,6 +431,76 @@ SET::SET(Common::SeekableReadStream *chunkStream) {
 	delete chunkStream;
 }
 
+LOAD::LOAD(Common::SeekableReadStream *chunkStream) :
+		_highlightFontID(-1),
+		_disabledFontID(-1),
+		_blinkingTimeDelay(0) {
+	assert(chunkStream);
+
+	chunkStream->seek(0);
+	Common::Serializer s(chunkStream, nullptr);
+	s.setVersion(g_nancy->getGameType());
+
+	readFilename(s, _imageName);
+
+	s.skip(0x1F, kGameTypeVampire, kGameTypeVampire);
+	s.skip(0x23, kGameTypeNancy1);
+	s.skip(4);
+
+	s.syncAsSint16LE(_mainFontID);
+	s.syncAsSint16LE(_highlightFontID, kGameTypeNancy2);
+	s.syncAsSint16LE(_disabledFontID, kGameTypeNancy2);
+	s.syncAsSint16LE(_fontXOffset);
+	s.syncAsSint16LE(_fontYOffset);
+
+	s.skip(16);
+
+	if (s.getVersion() <= kGameTypeNancy1) {
+		readRectArray16(s, _saveButtonDests, 7);
+		readRectArray16(s, _loadButtonDests, 7);
+		readRectArray16(s, _textboxBounds, 7);
+		readRect16(s, _doneButtonDest);
+		readRectArray16(s, _saveButtonDownSrcs, 7);
+		readRectArray16(s, _loadButtonDownSrcs, 7);
+		s.skip(8 * 7);
+		readRect16(s, _doneButtonDownSrc);
+		readRect(s, _blinkingCursorSrc);
+		s.syncAsUint16LE(_blinkingTimeDelay, kGameTypeNancy1);
+		readRectArray(s, _cancelButtonSrcs, 7);
+		readRectArray(s, _cancelButtonDests, 7);
+		readRect(s, _cancelButtonDownSrc);
+	} else {
+		readRectArray(s, _saveButtonDests, 7);
+		readRectArray(s, _loadButtonDests, 7);
+		readRectArray(s, _textboxBounds, 7);
+		readRect(s, _doneButtonDest);
+		readRectArray(s, _saveButtonDownSrcs, 7);
+		readRectArray(s, _loadButtonDownSrcs, 7);
+		s.skip(16 * 7);
+		readRect(s, _doneButtonDownSrc);
+		readRectArray(s, _saveButtonHighlightSrcs, 7);
+		readRectArray(s, _loadButtonHighlightSrcs, 7);
+		s.skip(16 * 7);
+		readRect(s, _doneButtonHighlightSrc);
+		readRectArray(s, _saveButtonDisabledSrcs, 7);
+		readRectArray(s, _loadButtonDisabledSrcs, 7);
+		s.skip(16 * 7);
+		readRect(s, _doneButtonDisabledSrc);
+		readRect(s, _blinkingCursorSrc);
+		s.syncAsUint16LE(_blinkingTimeDelay);
+		readRectArray(s, _cancelButtonSrcs, 7);
+		readRectArray(s, _cancelButtonDests, 7);
+		readRect(s, _cancelButtonDownSrc);
+		readRect(s, _cancelButtonHighlightSrc);
+		readRect(s, _cancelButtonDisabledSrc);
+
+		readFilename(s, _gameSavedPopup, kGameTypeNancy3);
+		s.skip(16, kGameTypeNancy3);
+	}
+
+	delete chunkStream;
+}
+
 HINT::HINT(Common::SeekableReadStream *chunkStream) {
 	assert(chunkStream);
 
diff --git a/engines/nancy/enginedata.h b/engines/nancy/enginedata.h
index 4c309c124cf..dd22c3f9f37 100644
--- a/engines/nancy/enginedata.h
+++ b/engines/nancy/enginedata.h
@@ -205,6 +205,45 @@ struct SET {
 	Common::Array<SoundDescription> _sounds;
 };
 
+struct LOAD {
+	LOAD(Common::SeekableReadStream *chunkStream);
+
+	Common::String _imageName;
+
+	int16 _mainFontID;
+	int16 _highlightFontID;
+	int16 _disabledFontID;
+	int16 _fontXOffset;
+	int16 _fontYOffset;
+
+	Common::Array<Common::Rect> _saveButtonDests;
+	Common::Array<Common::Rect> _loadButtonDests;
+	Common::Array<Common::Rect> _textboxBounds;
+	Common::Rect _doneButtonDest;
+	Common::Array<Common::Rect> _saveButtonDownSrcs;
+	Common::Array<Common::Rect> _loadButtonDownSrcs;
+
+	Common::Rect _doneButtonDownSrc;
+	Common::Array<Common::Rect> _saveButtonHighlightSrcs;
+	Common::Array<Common::Rect> _loadButtonHighlightSrcs;
+
+	Common::Rect _doneButtonHighlightSrc;
+	Common::Array<Common::Rect> _saveButtonDisabledSrcs;
+	Common::Array<Common::Rect> _loadButtonDisabledSrcs;
+
+	Common::Rect _doneButtonDisabledSrc;
+	Common::Rect _blinkingCursorSrc;
+	uint16 _blinkingTimeDelay;
+	Common::Array<Common::Rect> _cancelButtonSrcs;
+	Common::Array<Common::Rect> _cancelButtonDests;
+	Common::Rect _cancelButtonDownSrc;
+	Common::Rect _cancelButtonHighlightSrc;
+	Common::Rect _cancelButtonDisabledSrc;
+
+	Common::String _gameSavedPopup;
+	// Common::Rect _gameSavedBounds
+};
+
 struct HINT {
 	HINT(Common::SeekableReadStream *chunkStream);
 
diff --git a/engines/nancy/font.cpp b/engines/nancy/font.cpp
index 4737185ac4b..b09325e2796 100644
--- a/engines/nancy/font.cpp
+++ b/engines/nancy/font.cpp
@@ -37,6 +37,11 @@ void Font::read(Common::SeekableReadStream &stream) {
 
 	g_nancy->_resource->loadImage(imageName, _image);
 
+	if (g_nancy->getGameType() == kGameTypeVampire) {
+		// Hacky fix for load/save menu
+		_image.setTransparentColor(g_nancy->_graphicsManager->getTransColor());
+	}
+
 	char desc[0x20];
 	stream.read(desc, 0x20);
 	desc[0x1F] = '\0';
@@ -224,7 +229,11 @@ Common::Rect Font::getCharacterSourceRect(char chr) const {
 			offset = _slashOffset;
 			break;
 		default:
-			error("Unsupported FONT character: %c", chr);
+			// Replace unknown characters with question marks. This shouldn't happen normally,
+			// but we need it to support displaying saves that were originally made in the GMM
+			// inside the in-game load/save menu.
+			offset = _questionMarkOffset;
+			break;
 		}
 	}
 	ret = _symbolRects[offset];
diff --git a/engines/nancy/font.h b/engines/nancy/font.h
index 9b84990144f..39ba8fa89a1 100644
--- a/engines/nancy/font.h
+++ b/engines/nancy/font.h
@@ -48,6 +48,8 @@ public:
 
 	void drawChar(Graphics::Surface *dst, uint32 chr, int x, int y, uint32 color) const override;
 
+	const Graphics::ManagedSurface &getImageSurface() const { return _image; }
+
 	// Custom word wrapping function to fix an edge case with overflowing whitespaces
 	void wordWrap(const Common::String &str, int maxWidth, Common::Array<Common::String> &lines, int initWidth = 0) const;
 
diff --git a/engines/nancy/graphics.cpp b/engines/nancy/graphics.cpp
index f9b58605ced..b393d37e21e 100644
--- a/engines/nancy/graphics.cpp
+++ b/engines/nancy/graphics.cpp
@@ -371,6 +371,12 @@ void GraphicsManager::screenshotViewport(Graphics::ManagedSurface &inSurf) {
 	inSurf.blitFrom(_screen, g_nancy->_viewportData->screenPosition, g_nancy->_viewportData->bounds);
 }
 
+void GraphicsManager::screenshotScreen(Graphics::ManagedSurface &inSurf) {
+	draw(false);
+	inSurf.free();
+	inSurf.copyFrom(_screen);
+}
+
 // Draw a given screen-space rectangle to the screen
 void GraphicsManager::blitToScreen(const RenderObject &src, Common::Rect screenRect) {
 	_screen.blitFrom(src._drawSurface, src._drawSurface.getBounds().findIntersectingRect(src.convertToLocal(screenRect)), screenRect);
diff --git a/engines/nancy/graphics.h b/engines/nancy/graphics.h
index ac5d7090f4e..bd52fb8a784 100644
--- a/engines/nancy/graphics.h
+++ b/engines/nancy/graphics.h
@@ -57,6 +57,7 @@ public:
 
 	void grabViewportObjects(Common::Array<RenderObject *> &inArray);
 	void screenshotViewport(Graphics::ManagedSurface &inSurf);
+	void screenshotScreen(Graphics::ManagedSurface &inSurf);
 
 	static void loadSurfacePalette(Graphics::ManagedSurface &inSurf, const Common::String paletteFilename, uint paletteStart = 0, uint paletteSize = 256);
 	static void copyToManaged(const Graphics::Surface &src, Graphics::ManagedSurface &dst, bool verticalFlip = false, bool doubleSize = false);
diff --git a/engines/nancy/metaengine.cpp b/engines/nancy/metaengine.cpp
index 90c9c358416..60b2f7dad39 100644
--- a/engines/nancy/metaengine.cpp
+++ b/engines/nancy/metaengine.cpp
@@ -24,10 +24,13 @@
 #include "engines/nancy/nancy.h"
 #include "engines/nancy/graphics.h"
 #include "engines/nancy/input.h"
+#include "engines/nancy/state/scene.h"
 
 #include "common/translation.h"
 #include "common/config-manager.h"
 
+#include "graphics/scaler.h"
+
 static const ADExtraGuiOptionsMap optionsList[] = {
 	{
 		GUIO_GAMEOPTIONS1,
@@ -112,6 +115,15 @@ SaveStateDescriptor NancyMetaEngine::querySaveMetaInfos(const char *target, int
 }
 
 void NancyMetaEngine::getSavegameThumbnail(Graphics::Surface &thumb) {
+	if (Nancy::g_nancy->getState() == Nancy::NancyState::kLoadSave) {
+		if (Nancy::State::Scene::hasInstance()) {
+			Graphics::ManagedSurface &screenshot = Nancy::State::Scene::instance().getLastScreenshot();
+			if (!screenshot.empty() && createThumbnail(&thumb, &screenshot)) {
+				return;
+			}
+		}
+	}
+
 	// Second Chance autosaves trigger when a scene changes, but before
 	// it is drawn, so we need to refresh the screen before we take a screenshot
 	Nancy::g_nancy->_graphicsManager->draw();
diff --git a/engines/nancy/module.mk b/engines/nancy/module.mk
index d4d48d7dc4b..fc994418fd0 100644
--- a/engines/nancy/module.mk
+++ b/engines/nancy/module.mk
@@ -34,6 +34,7 @@ MODULE_OBJS = \
   ui/viewport.o \
   state/credits.o \
   state/logo.o \
+  state/loadsave.o \
   state/help.o \
   state/mainmenu.o \
   state/map.o \
diff --git a/engines/nancy/nancy.cpp b/engines/nancy/nancy.cpp
index 68e62c5e024..e891a1691f0 100644
--- a/engines/nancy/nancy.cpp
+++ b/engines/nancy/nancy.cpp
@@ -45,6 +45,7 @@
 #include "engines/nancy/state/credits.h"
 #include "engines/nancy/state/mainmenu.h"
 #include "engines/nancy/state/setupmenu.h"
+#include "engines/nancy/state/loadsave.h"
 
 namespace Nancy {
 
@@ -80,6 +81,7 @@ NancyEngine::NancyEngine(OSystem *syst, const NancyGameDescription *gd) :
 	_creditsData = nullptr;
 	_menuData = nullptr;
 	_setupData = nullptr;
+	_loadSaveData = nullptr;
 	_hintData = nullptr;
 	_sliderPuzzleData = nullptr;
 	_clockData = nullptr;
@@ -89,6 +91,15 @@ NancyEngine::NancyEngine(OSystem *syst, const NancyGameDescription *gd) :
 }
 
 NancyEngine::~NancyEngine() {
+	destroyState(NancyState::kLogo);
+	destroyState(NancyState::kCredits);
+	destroyState(NancyState::kMap);
+	destroyState(NancyState::kHelp);
+	destroyState(NancyState::kScene);
+	destroyState(NancyState::kMainMenu);
+	destroyState(NancyState::kSetup);
+	destroyState(NancyState::kLoadSave);
+
 	delete _randomSource;
 
 	delete _graphicsManager;
@@ -105,6 +116,7 @@ NancyEngine::~NancyEngine() {
 	delete _creditsData;
 	delete _menuData;
 	delete _setupData;
+	delete _loadSaveData;
 	delete _hintData;
 	delete _sliderPuzzleData;
 	delete _clockData;
@@ -318,19 +330,6 @@ Common::Error NancyEngine::run() {
 		}
 	}
 
-	if (State::Logo::hasInstance())
-		State::Logo::instance().destroy();
-	if (State::Credits::hasInstance())
-		State::Credits::instance().destroy();
-	if (State::Map::hasInstance())
-		State::Map::instance().destroy();
-	if (State::Help::hasInstance())
-		State::Help::instance().destroy();
-	if (State::Scene::hasInstance())
-		State::Scene::instance().destroy();
-	if (State::MainMenu::hasInstance())
-		State::MainMenu::instance().destroy();
-
 	return Common::kNoError;
 }
 
@@ -393,6 +392,7 @@ void NancyEngine::bootGameEngine() {
 	_creditsData = new CRED(boot->getChunkStream("CRED"));
 	_menuData = new MENU(boot->getChunkStream("MENU"));
 	_setupData = new SET(boot->getChunkStream("SET"));
+	_loadSaveData = new LOAD(boot->getChunkStream("LOAD"));
 
 	// For now we ignore the potential for more than one of each of these
 	_imageChunks.setVal("OB0", boot->getChunkStream("OB0"));
@@ -470,6 +470,8 @@ State::State *NancyEngine::getStateObject(NancyState::NancyState state) const {
 		return &State::Scene::instance();
 	case NancyState::kMainMenu:
 		return &State::MainMenu::instance();
+	case NancyState::kLoadSave:
+		return &State::LoadSaveMenu::instance();
 	default:
 		return nullptr;
 	}
@@ -512,6 +514,11 @@ void NancyEngine::destroyState(NancyState::NancyState state) const {
 			State::SetupMenu::instance().destroy();
 		}
 		break;
+	case NancyState::kLoadSave:
+		if (State::LoadSaveMenu::hasInstance()) {
+			State::LoadSaveMenu::instance().destroy();
+		}
+		break;
 	default:
 		break;
 	}
diff --git a/engines/nancy/nancy.h b/engines/nancy/nancy.h
index 28264c6c57e..d1da5cfbcb4 100644
--- a/engines/nancy/nancy.h
+++ b/engines/nancy/nancy.h
@@ -128,6 +128,7 @@ public:
 	CRED *_creditsData;
 	MENU *_menuData;
 	SET *_setupData;
+	LOAD *_loadSaveData;
 	HINT *_hintData;
 	SPUZ *_sliderPuzzleData;
 	CLOK *_clockData;
diff --git a/engines/nancy/state/loadsave.cpp b/engines/nancy/state/loadsave.cpp
new file mode 100644
index 00000000000..d52045197f8
--- /dev/null
+++ b/engines/nancy/state/loadsave.cpp
@@ -0,0 +1,510 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "engines/nancy/nancy.h"
+#include "engines/nancy/cursor.h"
+#include "engines/nancy/sound.h"
+#include "engines/nancy/input.h"
+#include "engines/nancy/util.h"
+#include "engines/nancy/resource.h"
+#include "engines/nancy/graphics.h"
+
+#include "engines/nancy/state/loadsave.h"
+#include "engines/nancy/state/scene.h"
+#include "engines/nancy/ui/button.h"
+
+#include "common/config-manager.h"
+
+namespace Common {
+DECLARE_SINGLETON(Nancy::State::LoadSaveMenu);
+}
+
+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;
+}
+
+void LoadSaveMenu::process() {
+	if (g_nancy->_sound->isSoundPlaying("BUOK") || g_nancy->_sound->isSoundPlaying("BULS") || g_nancy->_sound->isSoundPlaying("BUDE")) {
+		return;
+	}
+
+	switch (_state) {
+	case kInit:
+		init();
+		// fall through
+	case kRun:
+		run();
+		break;
+	case kEnterFilename:
+		enterFilename();
+		break;
+	case kSave:
+		save();
+		break;
+	case kLoad:
+		load();
+		break;
+	case kSuccess:
+		success();
+		break;
+	case kStop:
+		stop();
+		break;
+	}
+
+	g_nancy->_cursorManager->setCursorType(CursorManager::kNormalArrow);
+}
+
+void LoadSaveMenu::onStateEnter(const NancyState::NancyState prevState) {
+	registerGraphics();
+}
+
+bool LoadSaveMenu::onStateExit(const NancyState::NancyState nextState) {
+	return true;
+}
+
+void LoadSaveMenu::registerGraphics() {
+	_background.registerGraphics();
+
+	for (auto *button : _loadButtons) {
+		button->registerGraphics();
+	}
+
+	for (auto *button : _saveButtons) {
+		button->registerGraphics();
+	}
+
+	for (auto *overlay : _cancelButtonOverlays) {
+		overlay->registerGraphics();
+	}
+
+	for (auto *tb : _textboxes) {
+		tb->registerGraphics();
+	}
+
+	if (_exitButton) {
+		_exitButton->registerGraphics();
+	}
+
+	if (_cancelButton) {
+		_cancelButton->registerGraphics();
+	}
+
+	_blinkingCursorOverlay.registerGraphics();
+	_successOverlay.registerGraphics();
+}
+
+void LoadSaveMenu::init() {
+	_loadSaveData = g_nancy->_loadSaveData;
+	assert(_loadSaveData);
+
+	_background.init(_loadSaveData->_imageName);
+
+	_baseFont = g_nancy->_graphicsManager->getFont(_loadSaveData->_mainFontID);
+
+	if (_loadSaveData->_highlightFontID != -1) {
+		_highlightFont = g_nancy->_graphicsManager->getFont(_loadSaveData->_highlightFontID);
+		_disabledFont = g_nancy->_graphicsManager->getFont(_loadSaveData->_disabledFontID);
+	} else {
+		_highlightFont = _disabledFont = _baseFont;
+	}
+
+	_filenameStrings.resize(_loadSaveData->_textboxBounds.size());
+	_saveExists.resize(_filenameStrings.size(), false);
+	_textboxes.resize(_filenameStrings.size());
+	for (uint i = 0; i < _textboxes.size(); ++i) {
+		// Load textbox objects
+		RenderObject *newTb = new RenderObject(5);
+		_textboxes[i] = newTb;
+		Common::Rect &bounds = _loadSaveData->_textboxBounds[i];
+		newTb->_drawSurface.create(bounds.width(), bounds.height(), g_nancy->_graphicsManager->getScreenPixelFormat());
+		newTb->_drawSurface.clear(g_nancy->_graphicsManager->getTransColor());
+		newTb->moveTo(bounds);
+		newTb->setTransparent(true);
+		newTb->setVisible(true);
+		newTb->init();
+
+		// Check for valid save and write its name in the textbox
+		SaveStateDescriptor desc = g_nancy->getMetaEngine()->querySaveMetaInfos(ConfMan.getActiveDomainName().c_str(), i + 1);
+		if (desc.isValid()) {
+			_saveExists[i] = true;
+			_filenameStrings[i] = desc.getDescription();
+		} else {
+			// If no valid save, copy over the empty save string
+			_filenameStrings[i] = g_nancy->getStaticData().emptySaveText;
+		}
+	}
+
+	bool hasHighlights = _loadSaveData->_loadButtonHighlightSrcs.size();
+
+	_loadButtons.resize(_textboxes.size());
+	_saveButtons.resize(_textboxes.size());
+	_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,
+			_loadSaveData->_loadButtonDownSrcs[i], _loadSaveData->_loadButtonDests[i],
+			hasHighlights ? _loadSaveData->_loadButtonHighlightSrcs[i] : Common::Rect(),
+			hasHighlights ? _loadSaveData->_loadButtonDisabledSrcs[i] : Common::Rect());
+
+		_saveButtons[i] = new UI::Button(1, _background._drawSurface,
+			_loadSaveData->_saveButtonDownSrcs[i], _loadSaveData->_saveButtonDests[i],
+			hasHighlights ? _loadSaveData->_saveButtonHighlightSrcs[i] : Common::Rect(),
+			hasHighlights ? _loadSaveData->_saveButtonDisabledSrcs[i] : Common::Rect());
+
+		_cancelButtonOverlays[i] = new RenderObject(2, _background._drawSurface,
+			_loadSaveData->_cancelButtonSrcs[i], _loadSaveData->_cancelButtonDests[i]);
+
+		_loadButtons[i]->init();
+		_saveButtons[i]->init();
+		_cancelButtonOverlays[i]->init();
+	}
+
+	// Load exit button
+	_exitButton = new UI::Button(3, _background._drawSurface,
+			_loadSaveData->_doneButtonDownSrc, _loadSaveData->_doneButtonDest,
+			hasHighlights ? _loadSaveData->_doneButtonHighlightSrc : 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;
+	// the graphics that replace the Save buttons with Cancel are their own RenderObject.
+	// 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,
+		_loadSaveData->_cancelButtonDownSrc, Common::Rect(),
+		_loadSaveData->_cancelButtonHighlightSrc, _loadSaveData->_cancelButtonDisabledSrc);
+	
+	// Load the blinking cursor graphic that appears while typing a filename
+	_blinkingCursorOverlay._drawSurface.create(_loadSaveData->_blinkingCursorSrc.width(),
+		_loadSaveData->_blinkingCursorSrc.height(),
+		g_nancy->_graphicsManager->getScreenPixelFormat());
+	_blinkingCursorOverlay.setTransparent(true);
+	_blinkingCursorOverlay._drawSurface.blitFrom(_highlightFont->getImageSurface(), _loadSaveData->_blinkingCursorSrc, Common::Point());
+	_blinkingCursorOverlay.setVisible(false);
+
+	// Load the "Your game has been saved" popup graphic
+	if (_loadSaveData->_gameSavedPopup.size()) {
+		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::run() {
+	if (_enteringNewState) {
+		// State has changed, revert all relevant objects to an appropriate state
+		for (uint i = 0; i < _textboxes.size(); ++i) {
+			writeToTextbox(i, _filenameStrings[i], Nancy::State::Scene::hasInstance() ? _baseFont : _disabledFont);
+
+			// Set load button state depending on whether there exists a save in the corresponding slot
+			// Save buttons are always active
+			_loadButtons[i]->setDisabled(_saveExists[i] == false);
+			_saveButtons[i]->setDisabled(!Nancy::State::Scene::hasInstance());
+			_cancelButtonOverlays[i]->setVisible(false);
+
+			_loadButtons[i]->_isClicked = false;
+			_saveButtons[i]->_isClicked = false;
+		}
+
+		if (_cancelButton) {
+			_cancelButton->_isClicked = false;
+			_cancelButton->setDisabled(true);
+			_cancelButton->moveTo(Common::Point(-500, 0));
+		}
+
+		_blinkingCursorOverlay.setVisible(false);
+		_exitButton->setDisabled(false);
+		_enteredString.clear();
+		_successOverlay.setVisible(false);
+		
+		_selectedSave = -1;
+		_enteringNewState = false;
+	}
+
+	// Handle input
+	NancyInput input = g_nancy->_input->getInput();
+
+	for (uint i = 0; i < _loadButtons.size(); ++i) {
+		_loadButtons[i]->handleInput(input);
+
+		if (_loadButtons[i]->_isClicked) {
+			if (_saveExists[i]) {
+				_state = kLoad;
+				_enteringNewState = true;
+				_selectedSave = i;
+				g_nancy->_sound->playSound("BULS");
+			} else if (!_loadSaveData->_saveButtonHighlightSrcs.size()) {
+				// TVD and nancy1 buttons get pushed and play error sound
+				_loadButtons[i]->setVisible(true);
+				g_nancy->_sound->playSound("BUDE");
+				_enteringNewState = true;
+			}
+			
+			return;
+		}
+	}
+
+	for (uint i = 0; i < _saveButtons.size(); ++i) {
+		_saveButtons[i]->handleInput(input);
+
+		if (_saveButtons[i]->_isClicked) {
+			if (Nancy::State::Scene::hasInstance()) {
+				_state = kSave;
+				_enteringNewState = true;
+				_selectedSave = i;
+				g_nancy->_sound->playSound("BULS");
+			} else if (!_loadSaveData->_saveButtonHighlightSrcs.size()) {
+				// TVD and nancy1 buttons get pushed and play error sound
+				_saveButtons[i]->setVisible(true);
+				g_nancy->_sound->playSound("BUDE");
+				_enteringNewState = true;
+			}
+			
+			return;
+		}
+	}
+
+	// Handle textbox hovering
+	bool hoversOverTextbox = false;
+	for (int i = 0; i < (int)_textboxes.size(); ++i) {
+		if (_textboxes[i]->getScreenPosition().contains(input.mousePos)) {
+			if (Nancy::State::Scene::hasInstance()) {
+				hoversOverTextbox = true;
+				if (_selectedSave != i) {
+					if (_selectedSave != -1) {
+						writeToTextbox(_selectedSave, _filenameStrings[_selectedSave], _baseFont);
+					}
+
+					_selectedSave = i;
+					writeToTextbox(_selectedSave, _filenameStrings[_selectedSave], _highlightFont);
+				}
+
+				if (input.input & NancyInput::kLeftMouseButtonUp) {
+					_state = kEnterFilename;
+					_enteringNewState = true;
+					g_nancy->_sound->playSound("BUOK");
+					_selectedSave = i;
+					return;
+				}
+
+				break;
+			} else if (!_loadSaveData->_saveButtonHighlightSrcs.size()) {
+				if (input.input & NancyInput::kLeftMouseButtonUp) {
+					// TVD and nancy1 textboxes play error sound when no Scene is active
+					g_nancy->_sound->playSound("BUDE");
+				}
+			}
+		}
+	}
+
+	if (!hoversOverTextbox && _selectedSave != -1) {
+		writeToTextbox(_selectedSave, _filenameStrings[_selectedSave], _baseFont);
+		_selectedSave = -1;
+	}
+
+	// Check Done button
+	if (_exitButton) {
+		_exitButton->handleInput(input);
+
+		if (_exitButton->_isClicked) {
+			_state = kStop;
+			g_nancy->_sound->playSound("BUOK");
+			return;
+		}
+	}
+}
+
+void LoadSaveMenu::enterFilename() {
+	if (_enteringNewState) {
+		// State has changed, revert all relevant objects to an appropriate state
+		if (_cancelButton) {
+			_cancelButton->setDisabled(false);
+			_cancelButton->moveTo(_loadSaveData->_cancelButtonDests[_selectedSave]);
+		}
+
+		for (int i = 0; i < (int)_textboxes.size(); ++i) {
+			bool sel = i == _selectedSave;
+			writeToTextbox(i, sel ? Common::String() : _filenameStrings[i], sel ? _highlightFont : _disabledFont);
+			_loadButtons[i]->setDisabled(true);
+
+			if (i != _selectedSave) {
+				_saveButtons[i]->setDisabled(true);
+			}
+		}
+
+		_exitButton->setDisabled(true);
+		_cancelButtonOverlays[_selectedSave]->setVisible(true);
+
+		// Set up blinking cursor (doesn't blink in TVD)
+		Common::Rect tbPosition = _textboxes[_selectedSave]->getScreenPosition();
+		Common::Rect cursorRect = _blinkingCursorOverlay._drawSurface.getBounds();
+		cursorRect.moveTo(tbPosition.left, tbPosition.bottom - _blinkingCursorOverlay._drawSurface.h + _loadSaveData->_fontYOffset);
+		_blinkingCursorOverlay.moveTo(cursorRect);
+		_blinkingCursorOverlay.setVisible(true);
+		_nextBlink = g_nancy->getTotalPlayTime() + _loadSaveData->_blinkingTimeDelay;
+		_enteringNewState = false;
+	}
+
+	// Perform cursor blinking
+	uint32 gameTime = g_nancy->getTotalPlayTime();
+	if (_loadSaveData->_blinkingTimeDelay != 0 && gameTime > _nextBlink) {
+		_blinkingCursorOverlay.setVisible(!_blinkingCursorOverlay.isVisible());
+		_nextBlink = gameTime + _loadSaveData->_blinkingTimeDelay;
+	}
+
+	// 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) {
+			enterKeyPressed = true;
+		} else if (Common::isAlnum(key.ascii) || Common::isSpace(key.ascii)) {
+			_enteredString += key.ascii;
+		}
+
+		uint16 textWidthInPixels = writeToTextbox(_selectedSave, _enteredString, _highlightFont);
+		Common::Rect tbPosition = _textboxes[_selectedSave]->getScreenPosition();
+		_blinkingCursorOverlay.moveTo(Common::Point(tbPosition.left + textWidthInPixels,
+			tbPosition.bottom - _blinkingCursorOverlay._drawSurface.h + _loadSaveData->_fontYOffset));
+	}
+	
+	_cancelButton->handleInput(input);
+	if (_cancelButton->_isClicked) {
+		_state = kRun;
+		_enteringNewState = true;
+		g_nancy->_sound->playSound("BULS");
+		return;
+	}
+
+	_saveButtons[_selectedSave]->handleInput(input);
+	if (_saveButtons[_selectedSave]->_isClicked || enterKeyPressed) {
+		_state = kSave;
+		_enteringNewState = true;
+		g_nancy->_sound->playSound("BULS");
+		return;
+	}
+}
+
+void LoadSaveMenu::save() {
+	// Improvement: not providing a name doesn't result in the
+	// savefile being named "--- Empty ---" or "Nothing Saved Here".
+	// Instead, we use ScummVM's built-in save name generator
+	g_nancy->saveGameState(_selectedSave + 1, _enteredString.size() ? _enteredString :
+		_filenameStrings[_selectedSave].equals(g_nancy->getStaticData().emptySaveText) ? Common::String() : _filenameStrings[_selectedSave], false);
+
+	// Feed the new name back into the list of saves
+	SaveStateDescriptor desc = g_nancy->getMetaEngine()->querySaveMetaInfos(ConfMan.getActiveDomainName().c_str(), _selectedSave + 1);
+	if (desc.isValid()) {
+		_filenameStrings[_selectedSave] = desc.getDescription();
+	}
+
+	if (_successOverlay._drawSurface.empty()) {
+		_state = kRun;
+		_enteringNewState = true;
+	} else {
+		_state = kSuccess;
+		_enteringNewState = true;
+	}
+}
+
+void LoadSaveMenu::load() {
+	if (Nancy::State::Scene::hasInstance()) {
+		Nancy::State::Scene::destroy();
+	}
+
+	ConfMan.setInt("save_slot", _selectedSave + 1, Common::ConfigManager::kTransientDomain);
+
+	_state = kStop;
+	_enteringNewState = true;
+}
+
+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 (g_nancy->getTotalPlayTime() > _nextBlink) {
+		_state = kRun;
+		_enteringNewState = true;
+	}
+}
+
+void LoadSaveMenu::stop() {
+	if (_selectedSave != -1) {
+		g_nancy->setState(NancyState::kScene);
+	} else {
+		g_nancy->setState(NancyState::kMainMenu);
+	}
+}
+
+uint16 LoadSaveMenu::writeToTextbox(uint textboxID, const Common::String &text, const Font *font) {
+	assert(font);
+
+	_textboxes[textboxID]->_drawSurface.clear(g_nancy->_graphicsManager->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);
+}
+
+} // End of namespace State
+} // End of namespace Nancy
diff --git a/engines/nancy/state/loadsave.h b/engines/nancy/state/loadsave.h
new file mode 100644
index 00000000000..57552c11c17
--- /dev/null
+++ b/engines/nancy/state/loadsave.h
@@ -0,0 +1,102 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef NANCY_STATE_LOADSAVE_H
+#define NANCY_STATE_LOADSAVE_H
+
+#include "common/singleton.h"
+
+#include "engines/nancy/state/state.h"
+#include "engines/nancy/ui/fullscreenimage.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();
+
+	// 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();
+
+	void registerGraphics();
+
+	uint16 writeToTextbox(uint textboxID, const Common::String &text, const Font *font);
+
+	enum State { kInit, kRun, kEnterFilename, kSave, kLoad, kSuccess, kStop };
+
+	State _state;
+
+	UI::FullScreenImage _background;
+
+	const Font *_baseFont;
+	const Font *_highlightFont;
+	const Font *_disabledFont;
+
+	Common::Array<Common::String> _filenameStrings;
+	Common::Array<bool> _saveExists;
+	Common::String _enteredString;
+
+	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;
+
+	int16 _selectedSave;
+	bool _enteringNewState;
+	uint32 _nextBlink;
+
+	LOAD *_loadSaveData;
+};
+
+} // End of namespace State
+} // End of namespace Nancy
+
+#endif // NANCY_STATE_LOADSAVE_H
diff --git a/engines/nancy/state/mainmenu.cpp b/engines/nancy/state/mainmenu.cpp
index e711bff15ba..2befd094aa0 100644
--- a/engines/nancy/state/mainmenu.cpp
+++ b/engines/nancy/state/mainmenu.cpp
@@ -112,9 +112,6 @@ void MainMenu::init() {
 		_buttons[3]->setDisabled(true);
 	}
 
-	// Disable load/save & settings
-	_buttons[2]->setDisabled(true);
-
 	_state = kRun;
 }
 
@@ -169,6 +166,7 @@ void MainMenu::stop() {
 		break;
 	case 2:
 		// Load and Save Game, TODO
+		g_nancy->setState(NancyState::kLoadSave);
 		break;
 	case 3:
 		// Continue
diff --git a/engines/nancy/state/scene.cpp b/engines/nancy/state/scene.cpp
index 23e14fb43ad..0ad0862acbe 100644
--- a/engines/nancy/state/scene.cpp
+++ b/engines/nancy/state/scene.cpp
@@ -184,6 +184,8 @@ void Scene::onStateEnter(const NancyState::NancyState prevState) {
 }
 
 bool Scene::onStateExit(const NancyState::NancyState nextState) {
+	g_nancy->_graphicsManager->screenshotScreen(_lastScreenshot);
+
 	if (nextState != NancyState::kPause) {
 		_timers.pushedPlayTime = g_nancy->getTotalPlayTime();
 	}
diff --git a/engines/nancy/state/scene.h b/engines/nancy/state/scene.h
index 026a8bf643c..251309e712c 100644
--- a/engines/nancy/state/scene.h
+++ b/engines/nancy/state/scene.h
@@ -179,6 +179,8 @@ public:
 	void setActiveConversation(Action::ConversationSound *activeConversation);
 	Action::ConversationSound *getActiveConversation();
 
+	Graphics::ManagedSurface &getLastScreenshot() { return _lastScreenshot; }
+
 	// The Vampire Diaries only;
 	void beginLightning(int16 distance, uint16 pulseTime, int16 rgbPercent);
 
@@ -274,6 +276,9 @@ private:
 	Action::ActionManager _actionManager;
 	Action::ConversationSound *_activeConversation;
 
+	// Contains a screenshot of the Scene state from the last time it was exited
+	Graphics::ManagedSurface _lastScreenshot;
+
 	State _state;
 };
 
diff --git a/engines/nancy/ui/button.cpp b/engines/nancy/ui/button.cpp
index 5e59bfc4fd0..85027ed1826 100644
--- a/engines/nancy/ui/button.cpp
+++ b/engines/nancy/ui/button.cpp
@@ -82,6 +82,7 @@ void Button::setDisabled(bool disabled) {
 		}
 	} else {
 		setVisible(false);
+		_isDisabled = false;
 	}
 }
 


Commit: 99b358b056fda20ae50245240792409cf7078db6
    https://github.com/scummvm/scummvm/commit/99b358b056fda20ae50245240792409cf7078db6
Author: Kaloyan Chehlarski (strahy at outlook.com)
Date: 2023-08-28T19:53:33+03:00

Commit Message:
NANCY: Fix Coverity warnings

Changed paths:
    engines/nancy/sound.cpp
    engines/nancy/video.cpp


diff --git a/engines/nancy/sound.cpp b/engines/nancy/sound.cpp
index d6a8d826bf9..b49209e514f 100644
--- a/engines/nancy/sound.cpp
+++ b/engines/nancy/sound.cpp
@@ -677,13 +677,13 @@ void SoundManager::soundEffectMaintenance(uint16 channelID) {
 				Math::Quaternion quat;
 				switch (chan.effectData->rotateMoveAxis) {
 				case kRotateAroundX:
-				   quat = Math::Quaternion::xAxis(360 / chan.effectData->numMoveSteps);
+				   quat = Math::Quaternion::xAxis(360.0 / chan.effectData->numMoveSteps);
 				   break;
 				case kRotateAroundY:
-				   quat = Math::Quaternion::yAxis(360 / chan.effectData->numMoveSteps);
+				   quat = Math::Quaternion::yAxis(360.0 / chan.effectData->numMoveSteps);
 				   break;
 				case kRotateAroundZ:
-				   quat = Math::Quaternion::zAxis(360 / chan.effectData->numMoveSteps);
+				   quat = Math::Quaternion::zAxis(360.0 / chan.effectData->numMoveSteps);
 				   break;
 				} 
 
diff --git a/engines/nancy/video.cpp b/engines/nancy/video.cpp
index 53429343d7a..d39714f4079 100644
--- a/engines/nancy/video.cpp
+++ b/engines/nancy/video.cpp
@@ -316,7 +316,11 @@ const Graphics::Surface *AVFDecoder::AVFVideoTrack::decodeFrame(uint frameNr)  {
 
 		if (!_dec->decompress(input, output)) {
 			warning("Failed to decompress frame %d", frameNr);
-			delete[] decompBuf;
+			// Make sure we don't delete data we don't own
+			if (info.type != 0) {
+				delete[] decompBuf;
+			}
+			
 			return nullptr;
 		}
 	} else {


Commit: 1bb5d42a2e8c8d3a40f5a60e1de88367ab35fd74
    https://github.com/scummvm/scummvm/commit/1bb5d42a2e8c8d3a40f5a60e1de88367ab35fd74
Author: Kaloyan Chehlarski (strahy at outlook.com)
Date: 2023-08-28T19:53:33+03:00

Commit Message:
NANCY: Implement save prompt on quit

Later games added a prompt to save before quitting, which is now implemented.

Changed paths:
  A engines/nancy/state/savedialog.cpp
  A engines/nancy/state/savedialog.h
    engines/nancy/commontypes.h
    engines/nancy/enginedata.cpp
    engines/nancy/enginedata.h
    engines/nancy/module.mk
    engines/nancy/nancy.cpp
    engines/nancy/nancy.h
    engines/nancy/state/loadsave.cpp
    engines/nancy/state/mainmenu.cpp
    engines/nancy/state/scene.cpp


diff --git a/engines/nancy/commontypes.h b/engines/nancy/commontypes.h
index 7cca20aa1fb..72712055fe7 100644
--- a/engines/nancy/commontypes.h
+++ b/engines/nancy/commontypes.h
@@ -94,6 +94,7 @@ enum NancyState {
 	kQuit,
 	// regain focus
 	kNone,
+	kSaveDialog,
 	kPause, // only used when the GMM is on screen
 	kReloadSave
 };
diff --git a/engines/nancy/enginedata.cpp b/engines/nancy/enginedata.cpp
index f6c60f2f4cf..f732964bb60 100644
--- a/engines/nancy/enginedata.cpp
+++ b/engines/nancy/enginedata.cpp
@@ -501,6 +501,30 @@ LOAD::LOAD(Common::SeekableReadStream *chunkStream) :
 	delete chunkStream;
 }
 
+SDLG::SDLG(Common::SeekableReadStream *chunkStream) {
+	assert(chunkStream);
+
+	chunkStream->seek(0);
+	readFilename(*chunkStream, _imageName);
+	chunkStream->skip(16);
+
+	readRect(*chunkStream, _yesDest);
+	readRect(*chunkStream, _noDest);
+	readRect(*chunkStream, _cancelDest);
+
+	chunkStream->skip(16);
+
+	readRect(*chunkStream, _yesHighlightSrc);
+	readRect(*chunkStream, _noHighlightSrc);
+	readRect(*chunkStream, _cancelHighlightSrc);
+
+	readRect(*chunkStream, _yesDownSrc);
+	readRect(*chunkStream, _noDownSrc);
+	readRect(*chunkStream, _cancelDownSrc);
+
+	delete chunkStream;
+}
+
 HINT::HINT(Common::SeekableReadStream *chunkStream) {
 	assert(chunkStream);
 
diff --git a/engines/nancy/enginedata.h b/engines/nancy/enginedata.h
index dd22c3f9f37..ca2c28c9f51 100644
--- a/engines/nancy/enginedata.h
+++ b/engines/nancy/enginedata.h
@@ -244,6 +244,24 @@ struct LOAD {
 	// Common::Rect _gameSavedBounds
 };
 
+struct SDLG {
+	SDLG(Common::SeekableReadStream *chunkStream);
+
+	Common::String _imageName;
+
+	Common::Rect _yesDest;
+	Common::Rect _noDest;
+	Common::Rect _cancelDest;
+
+	Common::Rect _yesHighlightSrc;
+	Common::Rect _noHighlightSrc;
+	Common::Rect _cancelHighlightSrc;
+
+	Common::Rect _yesDownSrc;
+	Common::Rect _noDownSrc;
+	Common::Rect _cancelDownSrc;
+};
+
 struct HINT {
 	HINT(Common::SeekableReadStream *chunkStream);
 
diff --git a/engines/nancy/module.mk b/engines/nancy/module.mk
index fc994418fd0..e0ccbad0d37 100644
--- a/engines/nancy/module.mk
+++ b/engines/nancy/module.mk
@@ -38,6 +38,7 @@ MODULE_OBJS = \
   state/help.o \
   state/mainmenu.o \
   state/map.o \
+  state/savedialog.o \
   state/scene.o \
   state/setupmenu.o \
   misc/lightning.o \
diff --git a/engines/nancy/nancy.cpp b/engines/nancy/nancy.cpp
index e891a1691f0..6adeea73633 100644
--- a/engines/nancy/nancy.cpp
+++ b/engines/nancy/nancy.cpp
@@ -46,6 +46,7 @@
 #include "engines/nancy/state/mainmenu.h"
 #include "engines/nancy/state/setupmenu.h"
 #include "engines/nancy/state/loadsave.h"
+#include "engines/nancy/state/savedialog.h"
 
 namespace Nancy {
 
@@ -82,12 +83,15 @@ NancyEngine::NancyEngine(OSystem *syst, const NancyGameDescription *gd) :
 	_menuData = nullptr;
 	_setupData = nullptr;
 	_loadSaveData = nullptr;
+	_saveDialogData = nullptr;
 	_hintData = nullptr;
 	_sliderPuzzleData = nullptr;
 	_clockData = nullptr;
 	_specialEffectData = nullptr;
 	_raycastPuzzleData = nullptr;
 	_raycastPuzzleLevelBuilderData = nullptr;
+
+	_hasJustSaved = false;
 }
 
 NancyEngine::~NancyEngine() {
@@ -99,6 +103,7 @@ NancyEngine::~NancyEngine() {
 	destroyState(NancyState::kMainMenu);
 	destroyState(NancyState::kSetup);
 	destroyState(NancyState::kLoadSave);
+	destroyState(NancyState::kSaveDialog);
 
 	delete _randomSource;
 
@@ -117,6 +122,7 @@ NancyEngine::~NancyEngine() {
 	delete _menuData;
 	delete _setupData;
 	delete _loadSaveData;
+	delete _saveDialogData;
 	delete _hintData;
 	delete _sliderPuzzleData;
 	delete _clockData;
@@ -394,15 +400,20 @@ void NancyEngine::bootGameEngine() {
 	_setupData = new SET(boot->getChunkStream("SET"));
 	_loadSaveData = new LOAD(boot->getChunkStream("LOAD"));
 
+	auto *chunkStream = boot->getChunkStream("SDLG");
+	if (chunkStream) {
+		_saveDialogData = new SDLG(chunkStream);
+	}
+
 	// For now we ignore the potential for more than one of each of these
 	_imageChunks.setVal("OB0", boot->getChunkStream("OB0"));
 	_imageChunks.setVal("FR0", boot->getChunkStream("FR0"));
 	_imageChunks.setVal("LG0", boot->getChunkStream("LG0"));
 
-	auto *chunkStream = boot->getChunkStream("PLG0");
+	chunkStream = boot->getChunkStream("PLG0");
 	if (!chunkStream) {
 		chunkStream = boot->getChunkStream("PLGO"); // nancy4 and above use an O instead of a zero
-	}
+	}	
 
 	if (chunkStream) {
 		_imageChunks.setVal("PLG0", chunkStream);
@@ -472,6 +483,8 @@ State::State *NancyEngine::getStateObject(NancyState::NancyState state) const {
 		return &State::MainMenu::instance();
 	case NancyState::kLoadSave:
 		return &State::LoadSaveMenu::instance();
+	case NancyState::kSaveDialog:
+		return &State::SaveDialog::instance();
 	default:
 		return nullptr;
 	}
@@ -519,6 +532,11 @@ void NancyEngine::destroyState(NancyState::NancyState state) const {
 			State::LoadSaveMenu::instance().destroy();
 		}
 		break;
+	case NancyState::kSaveDialog:
+		if (State::SaveDialog::hasInstance()) {
+			State::SaveDialog::instance().destroy();
+		}
+		break;
 	default:
 		break;
 	}
diff --git a/engines/nancy/nancy.h b/engines/nancy/nancy.h
index d1da5cfbcb4..9bd3b6e9b92 100644
--- a/engines/nancy/nancy.h
+++ b/engines/nancy/nancy.h
@@ -129,6 +129,7 @@ public:
 	MENU *_menuData;
 	SET *_setupData;
 	LOAD *_loadSaveData;
+	SDLG *_saveDialogData;
 	HINT *_hintData;
 	SPUZ *_sliderPuzzleData;
 	CLOK *_clockData;
@@ -138,6 +139,9 @@ public:
 
 	Common::HashMap<Common::String, ImageChunk> _imageChunks;
 
+	// Used to check whether we need to show the SaveDialog
+	bool _hasJustSaved;
+
 protected:
 	Common::Error run() override;
 	void pauseEngineIntern(bool pause) override;
diff --git a/engines/nancy/state/loadsave.cpp b/engines/nancy/state/loadsave.cpp
index d52045197f8..aefd2d35526 100644
--- a/engines/nancy/state/loadsave.cpp
+++ b/engines/nancy/state/loadsave.cpp
@@ -460,6 +460,8 @@ void LoadSaveMenu::save() {
 		_state = kSuccess;
 		_enteringNewState = true;
 	}
+
+	g_nancy->_hasJustSaved = true;
 }
 
 void LoadSaveMenu::load() {
diff --git a/engines/nancy/state/mainmenu.cpp b/engines/nancy/state/mainmenu.cpp
index 2befd094aa0..936206b8c9b 100644
--- a/engines/nancy/state/mainmenu.cpp
+++ b/engines/nancy/state/mainmenu.cpp
@@ -187,7 +187,12 @@ void MainMenu::stop() {
 		break;
 	case 6:
 		// Exit Game
-		g_nancy->quitGame();
+		if (g_nancy->_saveDialogData && Nancy::State::Scene::hasInstance() && !g_nancy->_hasJustSaved) {
+			g_nancy->setState(NancyState::kSaveDialog);
+		} else {
+			g_nancy->quitGame();
+		}
+		
 		break;
 	case 7:
 		// Help
diff --git a/engines/nancy/state/savedialog.cpp b/engines/nancy/state/savedialog.cpp
new file mode 100644
index 00000000000..58858d9dc70
--- /dev/null
+++ b/engines/nancy/state/savedialog.cpp
@@ -0,0 +1,147 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "engines/nancy/nancy.h"
+#include "engines/nancy/cursor.h"
+#include "engines/nancy/sound.h"
+#include "engines/nancy/input.h"
+#include "engines/nancy/util.h"
+
+#include "engines/nancy/state/savedialog.h"
+#include "engines/nancy/state/scene.h"
+#include "engines/nancy/ui/button.h"
+
+namespace Common {
+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;
+	}
+
+	switch (_state) {
+	case kInit:
+		init();
+		// fall through
+	case kRun:
+		run();
+		break;
+	case kStop:
+		stop();
+		break;
+	}
+
+	g_nancy->_cursorManager->setCursorType(CursorManager::kNormalArrow);
+}
+
+void SaveDialog::onStateEnter(const NancyState::NancyState prevState) {
+	registerGraphics();
+}
+
+bool SaveDialog::onStateExit(const NancyState::NancyState nextState) {
+	return true;
+}
+
+void SaveDialog::registerGraphics() {
+	_background.registerGraphics();
+
+	if (_yesButton) {
+		_yesButton->registerGraphics();
+	}
+
+	if (_noButton) {
+		_noButton->registerGraphics();
+	}
+
+	if (_cancelButton) {
+		_cancelButton->registerGraphics();
+	}
+}
+
+void SaveDialog::init() {
+	_dialogData = g_nancy->_saveDialogData;
+	assert(_dialogData);
+
+	_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);
+
+	registerGraphics();
+
+	_state = kRun;
+}
+
+void SaveDialog::run() {
+	NancyInput input = g_nancy->_input->getInput();
+
+	_yesButton->handleInput(input);
+	if (_yesButton->_isClicked) {
+		_selected = 0;
+		_state = kStop;
+		g_nancy->_sound->playSound("BUOK");
+	}
+
+	_noButton->handleInput(input);
+	if (_noButton->_isClicked) {
+		_selected = 1;
+		_state = kStop;
+		g_nancy->_sound->playSound("BUOK");
+	}
+
+	_cancelButton->handleInput(input);
+	if (_cancelButton->_isClicked) {
+		_selected = 2;
+		_state = kStop;
+		g_nancy->_sound->playSound("BUOK");
+	}
+}
+
+void SaveDialog::stop() {
+	switch (_selected) {
+	case 0 :
+		g_nancy->setState(NancyState::kLoadSave);
+		break;
+	case 1 :
+		g_nancy->quitGame();
+		break;
+	case 2 :
+		g_nancy->setToPreviousState();
+		break;
+	default:
+		break;
+	}
+}
+
+} // End of namespace State
+} // End of namespace Nancy
+
diff --git a/engines/nancy/state/savedialog.h b/engines/nancy/state/savedialog.h
new file mode 100644
index 00000000000..d2551141886
--- /dev/null
+++ b/engines/nancy/state/savedialog.h
@@ -0,0 +1,74 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef NANCY_STATE_SAVEDIALOG_H
+#define NANCY_STATE_SAVEDIALOG_H
+
+#include "common/singleton.h"
+
+#include "engines/nancy/state/state.h"
+
+#include "engines/nancy/ui/fullscreenimage.h"
+
+namespace Nancy {
+
+struct SDLG;
+
+namespace UI {
+class Button;
+}
+
+namespace State {
+
+class SaveDialog : public State, public Common::Singleton<SaveDialog> {
+public:
+	SaveDialog() : _state(kInit), _yesButton(nullptr), _noButton(nullptr), _cancelButton(nullptr), _selected(-1) {}
+	virtual ~SaveDialog();
+
+	// 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 stop();
+
+	void registerGraphics();
+
+	enum State { kInit, kRun, kStop };
+
+	UI::FullScreenImage _background;
+	State _state;
+	int _selected;
+
+	UI::Button *_yesButton;
+	UI::Button *_noButton;
+	UI::Button *_cancelButton;
+
+	SDLG *_dialogData;
+};
+
+} // End of namespace State
+} // End of namespace Nancy
+
+#endif // NANCY_STATE_MAINMENU_H
diff --git a/engines/nancy/state/scene.cpp b/engines/nancy/state/scene.cpp
index 0ad0862acbe..1d59e4a7157 100644
--- a/engines/nancy/state/scene.cpp
+++ b/engines/nancy/state/scene.cpp
@@ -181,6 +181,8 @@ void Scene::onStateEnter(const NancyState::NancyState prevState) {
 
 		g_nancy->_sound->stopSound("MSND");
 	}
+
+	g_nancy->_hasJustSaved = false;
 }
 
 bool Scene::onStateExit(const NancyState::NancyState nextState) {


Commit: cb60680d377185f1c08e582b81cea4a9e98fa550
    https://github.com/scummvm/scummvm/commit/cb60680d377185f1c08e582b81cea4a9e98fa550
Author: Kaloyan Chehlarski (strahy at outlook.com)
Date: 2023-08-28T19:53:33+03:00

Commit Message:
NANCY: Use original menus by default

Changed paths:
    engines/nancy/nancy.cpp
    engines/nancy/state/credits.cpp
    engines/nancy/state/logo.cpp


diff --git a/engines/nancy/nancy.cpp b/engines/nancy/nancy.cpp
index 6adeea73633..07cef66b43c 100644
--- a/engines/nancy/nancy.cpp
+++ b/engines/nancy/nancy.cpp
@@ -206,7 +206,7 @@ void NancyEngine::setState(NancyState::NancyState state, NancyState::NancyState
 		setState(NancyState::kLogo);
 		return;
 	case NancyState::kMainMenu: {
-		if (ConfMan.getBool("original_menus")) {
+		if (!ConfMan.hasKey("original_menus") || ConfMan.getBool("original_menus")) {
 			break;
 		}
 
diff --git a/engines/nancy/state/credits.cpp b/engines/nancy/state/credits.cpp
index 8ded5a5147d..2c4bea9bb0c 100644
--- a/engines/nancy/state/credits.cpp
+++ b/engines/nancy/state/credits.cpp
@@ -102,7 +102,7 @@ void Credits::run() {
 		g_nancy->setMouseEnabled(true);
 		_fullTextSurface.free();
 
-		if (ConfMan.getBool("original_menus")) {
+		if (!ConfMan.hasKey("original_menus") || ConfMan.getBool("original_menus")) {
 			g_nancy->setState(NancyState::kMainMenu);
 			return;
 		}
diff --git a/engines/nancy/state/logo.cpp b/engines/nancy/state/logo.cpp
index 0ac900a64b6..56e19bbb9d0 100644
--- a/engines/nancy/state/logo.cpp
+++ b/engines/nancy/state/logo.cpp
@@ -146,7 +146,7 @@ void Logo::stop() {
 	// For the N+C key combo it looks for some kind of cheat file
 	// to initialize the game state with.
 
-	if (ConfMan.getBool("original_menus")) {
+	if (!ConfMan.hasKey("original_menus") || ConfMan.getBool("original_menus")) {
 		g_nancy->setState(NancyState::kMainMenu);
 	} else {
 		g_nancy->setState(NancyState::kScene);


Commit: 7e5f7030dd7aa93715590c94a174f653db875a05
    https://github.com/scummvm/scummvm/commit/7e5f7030dd7aa93715590c94a174f653db875a05
Author: Kaloyan Chehlarski (strahy at outlook.com)
Date: 2023-08-28T19:53:33+03:00

Commit Message:
NANCY: Fix cursor transparency in save/load menu

Fixed an issue with the blinking cursor in LoadSaveMenu not
displaying properly.

Changed paths:
    engines/nancy/font.cpp
    engines/nancy/state/loadsave.cpp


diff --git a/engines/nancy/font.cpp b/engines/nancy/font.cpp
index b09325e2796..2ed3a9212cb 100644
--- a/engines/nancy/font.cpp
+++ b/engines/nancy/font.cpp
@@ -37,11 +37,6 @@ void Font::read(Common::SeekableReadStream &stream) {
 
 	g_nancy->_resource->loadImage(imageName, _image);
 
-	if (g_nancy->getGameType() == kGameTypeVampire) {
-		// Hacky fix for load/save menu
-		_image.setTransparentColor(g_nancy->_graphicsManager->getTransColor());
-	}
-
 	char desc[0x20];
 	stream.read(desc, 0x20);
 	desc[0x1F] = '\0';
diff --git a/engines/nancy/state/loadsave.cpp b/engines/nancy/state/loadsave.cpp
index aefd2d35526..dcba56ae02c 100644
--- a/engines/nancy/state/loadsave.cpp
+++ b/engines/nancy/state/loadsave.cpp
@@ -219,8 +219,10 @@ void LoadSaveMenu::init() {
 		_loadSaveData->_blinkingCursorSrc.height(),
 		g_nancy->_graphicsManager->getScreenPixelFormat());
 	_blinkingCursorOverlay.setTransparent(true);
-	_blinkingCursorOverlay._drawSurface.blitFrom(_highlightFont->getImageSurface(), _loadSaveData->_blinkingCursorSrc, Common::Point());
 	_blinkingCursorOverlay.setVisible(false);
+	_blinkingCursorOverlay._drawSurface.clear(_blinkingCursorOverlay._drawSurface.getTransparentColor());
+	_blinkingCursorOverlay._drawSurface.transBlitFrom(_highlightFont->getImageSurface(), _loadSaveData->_blinkingCursorSrc,
+		Common::Point(), g_nancy->_graphicsManager->getTransColor());
 
 	// Load the "Your game has been saved" popup graphic
 	if (_loadSaveData->_gameSavedPopup.size()) {


Commit: efcd1757f5db493f38964755c6d8f04dd2ac9e3e
    https://github.com/scummvm/scummvm/commit/efcd1757f5db493f38964755c6d8f04dd2ac9e3e
Author: Kaloyan Chehlarski (strahy at outlook.com)
Date: 2023-08-28T19:53:33+03:00

Commit Message:
NANCY: Add keymap for opening main menu

The original engine allowed players to go to the main menu
via the Esc button, but only when they were in the Scene state.
This is now implemented here as well.

Changed paths:
    engines/nancy/input.cpp
    engines/nancy/input.h
    engines/nancy/state/scene.cpp


diff --git a/engines/nancy/input.cpp b/engines/nancy/input.cpp
index 1c86078ed4b..9308d683f9c 100644
--- a/engines/nancy/input.cpp
+++ b/engines/nancy/input.cpp
@@ -71,6 +71,9 @@ void InputManager::processEvents() {
 			case kNancyActionMoveFast:
 				_inputs |= NancyInput::kMoveFastModifier;
 				break;
+			case kNancyActionOpenMainMenu:
+				_inputs |= NancyInput::kOpenMainMenu;
+				break;
 			default:
 				break;
 			}
@@ -101,6 +104,9 @@ void InputManager::processEvents() {
 			case kNancyActionMoveFast:
 				_inputs &= ~NancyInput::kMoveFastModifier;
 				break;
+			case kNancyActionOpenMainMenu:
+				_inputs &= ~NancyInput::kOpenMainMenu;
+				break;
 			default:
 				break;
 			}
@@ -194,6 +200,12 @@ void InputManager::initKeymaps(Common::KeymapArray &keymaps) {
 	act->addDefaultInputMapping("JOY_LEFT_SHOULDER");
 	mainKeymap->addAction(act);
 
+	act = new Action("MMENU", _("Open main menu"));
+	act->setCustomEngineActionEvent(kNancyActionOpenMainMenu);
+	act->addDefaultInputMapping("ESCAPE");
+	act->addDefaultInputMapping("JOY_START");
+	mainKeymap->addAction(act);
+
 	keymaps.push_back(mainKeymap);
 }
 
diff --git a/engines/nancy/input.h b/engines/nancy/input.h
index fbf090eee4b..92762b3089e 100644
--- a/engines/nancy/input.h
+++ b/engines/nancy/input.h
@@ -51,6 +51,7 @@ struct NancyInput {
 		kMoveLeft				= 1 << 8,
 		kMoveRight				= 1 << 9,
 		kMoveFastModifier		= 1 << 10,
+		kOpenMainMenu			= 1 << 11,
 
 		kLeftMouseButton		= kLeftMouseButtonDown | kLeftMouseButtonHeld | kLeftMouseButtonUp,
 		kRightMouseButton		= kRightMouseButtonDown | kRightMouseButtonHeld | kRightMouseButtonUp
@@ -75,7 +76,8 @@ enum NancyAction {
 	kNancyActionMoveRight,
 	kNancyActionMoveFast,
 	kNancyActionLeftClick,
-	kNancyActionRightClick
+	kNancyActionRightClick,
+	kNancyActionOpenMainMenu
 };
 
 public:
diff --git a/engines/nancy/state/scene.cpp b/engines/nancy/state/scene.cpp
index 1d59e4a7157..1938f45e913 100644
--- a/engines/nancy/state/scene.cpp
+++ b/engines/nancy/state/scene.cpp
@@ -768,6 +768,11 @@ void Scene::run() {
 
 	handleInput();
 
+	if (g_nancy->getState() == NancyState::kMainMenu) {
+		// Player pressed esc, do not process further
+		return;
+	}
+
 	_actionManager.processActionRecords();
 
 	if (_lightning) {
@@ -795,6 +800,12 @@ void Scene::handleInput() {
 			input.mousePos.y = inactiveZone.bottom + cursorHotspot.y;
 			g_system->warpMouse(input.mousePos.x, input.mousePos.y);
 		}
+	} else {
+		// Check if player has pressed esc
+		if (input.input & NancyInput::kOpenMainMenu) {
+			g_nancy->setState(NancyState::kMainMenu);
+			return;
+		}
 	}
 
 	// We handle the textbox and inventory box first because of their scrollbars, which




More information about the Scummvm-git-logs mailing list