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

dreammaster noreply at scummvm.org
Sat Feb 15 06:37:20 UTC 2025


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

Summary:
da713d15f8 M4: RIDDLE: In progress adding save load menu, common base class with Burger


Commit: da713d15f8fbdb734877ebc6a409241429a198d0
    https://github.com/scummvm/scummvm/commit/da713d15f8fbdb734877ebc6a409241429a198d0
Author: Paul Gilbert (dreammaster at scummvm.org)
Date: 2025-02-14T22:37:07-08:00

Commit Message:
M4: RIDDLE: In progress adding save load menu, common base class with Burger

Changed paths:
  A engines/m4/gui/game_menu.cpp
  A engines/m4/gui/game_menu.h
    engines/m4/burger/gui/game_menu.cpp
    engines/m4/burger/gui/game_menu.h
    engines/m4/gui/gui_menu_items.cpp
    engines/m4/gui/gui_menu_items.h
    engines/m4/module.mk
    engines/m4/riddle/gui/game_menu.cpp
    engines/m4/riddle/gui/game_menu.h
    engines/m4/riddle/riddle.cpp
    engines/m4/riddle/riddle.h


diff --git a/engines/m4/burger/gui/game_menu.cpp b/engines/m4/burger/gui/game_menu.cpp
index d1b02b017e2..8c9f43b7ece 100644
--- a/engines/m4/burger/gui/game_menu.cpp
+++ b/engines/m4/burger/gui/game_menu.cpp
@@ -45,192 +45,6 @@ namespace M4 {
 namespace Burger {
 namespace GUI {
 
-Sprite *menu_CreateThumbnail(int32 *spriteSize) {
-	Sprite *thumbNailSprite;
-	GrBuff *thumbNail;
-	Buffer *scrnBuff, *intrBuff, *destBuff, RLE8Buff;
-	uint8 *srcPtr, *srcPtr2, *srcPtr3, *srcRowPtr, *destPtr;
-	ScreenContext *gameScreen;
-	int32 i, status;
-	int32 currRow, beginRow, endRow;
-
-	// Create a Sprite for the thumbNail
-	if ((thumbNailSprite = (Sprite *)mem_alloc(sizeof(Sprite), "sprite")) == nullptr) {
-		return nullptr;
-	}
-
-	thumbNail = new GrBuff((MAX_VIDEO_X + 1) / 3, (MAX_VIDEO_Y + 1) / 3);
-	if (!thumbNail) {
-		return nullptr;
-	}
-
-	destBuff = thumbNail->get_buffer();
-	if (!destBuff) {
-		return nullptr;
-	}
-
-	gameScreen = vmng_screen_find(_G(gameDrawBuff), &status);
-	if ((!gameScreen) || (status != SCRN_ACTIVE)) {
-		return nullptr;
-	}
-
-	scrnBuff = _G(gameDrawBuff)->get_buffer();
-	if (!scrnBuff) {
-		return nullptr;
-	}
-
-	// Grab the interface buffer
-	intrBuff = _G(gameInterfaceBuff)->get_buffer();
-
-	if (gameScreen->y1 > 0) {
-		// Paint the top of the thumbnail black
-		beginRow = gameScreen->y1;
-		memset(destBuff->data, 21, (beginRow / 3) * destBuff->stride);
-		srcRowPtr = (uint8 *)(scrnBuff->data + (-gameScreen->x1));
-		destPtr = (uint8 *)(destBuff->data + ((beginRow / 3) * destBuff->stride));
-	} else {
-		srcRowPtr = (uint8 *)(scrnBuff->data + ((-gameScreen->y1) * scrnBuff->stride) + (-gameScreen->x1));
-		beginRow = 0;
-		destPtr = destBuff->data;
-	}
-	endRow = imath_min(MAX_VIDEO_Y, gameScreen->y2);
-
-	for (currRow = beginRow; currRow <= endRow; currRow += 3) {
-
-		// Set the src pointers
-		srcPtr = srcRowPtr;
-		srcPtr2 = srcRowPtr + scrnBuff->stride;
-		srcPtr3 = srcRowPtr + (scrnBuff->stride << 1);
-
-		for (i = 0; i < (MAX_VIDEO_X + 1) / 3; i++) {
-
-			// Calculate the average - make sure not to extend past the end of the buffer
-			if (endRow - currRow < 1) {
-				*destPtr = (uint8)((uint32)((*srcPtr + *(srcPtr + 1) + *(srcPtr + 2)) / 3));
-			} else if (endRow - currRow < 2) {
-				*destPtr = (uint8)((uint32)((*srcPtr + *(srcPtr + 1) + *(srcPtr + 2) +
-					*srcPtr2 + *(srcPtr2 + 1) + *(srcPtr2 + 2)) / 6));
-			} else {
-				*destPtr = (uint8)((uint32)((*srcPtr + *(srcPtr + 1) + *(srcPtr + 2) +
-					*srcPtr2 + *(srcPtr2 + 1) + *(srcPtr2 + 2) +
-					*srcPtr3 + *(srcPtr3 + 1) + *(srcPtr3 + 2)) / 9));
-			}
-
-			if (*destPtr == 0) {
-				*destPtr = 21;
-			}
-
-			// Increment the pointers
-			srcPtr += 3;
-			srcPtr2 += 3;
-			srcPtr3 += 3;
-			destPtr++;
-		}
-
-		// Update the row pointer
-		srcRowPtr += scrnBuff->stride * 3;
-	}
-
-	// Reset the currRow
-	beginRow = currRow;
-
-	// Paint the interface section of the thumbnail
-	if (currRow < MAX_VIDEO_Y) {
-		// If the interface is visible, grab it
-		if (intrBuff) {
-			srcRowPtr = intrBuff->data;
-			endRow = imath_min(MAX_VIDEO_Y, beginRow + intrBuff->h - 1);
-			for (currRow = beginRow; currRow <= endRow; currRow += 3) {
-				// Set the src pointers
-				srcPtr = srcRowPtr;
-				srcPtr2 = srcRowPtr + intrBuff->stride;
-				srcPtr3 = srcRowPtr + (intrBuff->stride << 1);
-
-				for (i = 0; i < (MAX_VIDEO_X + 1) / 3; i++) {
-					// If the pix is outside of the inventory objects in the interface, set to black
-					// If ((srcPtr - srcRowPtr < 180) || (srcPtr - srcRowPtr > 575)) {
-					if (true) {			// for now make everything in the interface black
-						*destPtr = 21;
-					}
-
-					// Else calculate the average - make sure not to extend past the end of the buffer
-					else {
-						if (endRow - currRow < 1) {
-							*destPtr = (uint8)((uint32)((*srcPtr + *(srcPtr + 1) + *(srcPtr + 2)) / 3));
-						} else if (endRow - currRow < 2) {
-							*destPtr = (uint8)((uint32)((*srcPtr + *(srcPtr + 1) + *(srcPtr + 2) +
-								*srcPtr2 + *(srcPtr2 + 1) + *(srcPtr2 + 2)) / 6));
-						} else {
-							*destPtr = (uint8)((uint32)((*srcPtr + *(srcPtr + 1) + *(srcPtr + 2) +
-								*srcPtr2 + *(srcPtr2 + 1) + *(srcPtr2 + 2) +
-								*srcPtr3 + *(srcPtr3 + 1) + *(srcPtr3 + 2)) / 9));
-						}
-						if (*destPtr == 0) {
-							*destPtr = 21;
-						}
-					}
-
-					// Increment the pointers
-					srcPtr += 3;
-					srcPtr2 += 3;
-					srcPtr3 += 3;
-					destPtr++;
-				}
-
-				// Update the row pointer
-				srcRowPtr += intrBuff->stride * 3;
-			}
-		} else {
-			// Else paint the bottom of the thumbnail black
-			destPtr = (uint8 *)(destBuff->data + ((currRow / 3) * destBuff->stride));
-			memset(destPtr, 21, (destBuff->h - (currRow / 3)) * destBuff->stride);
-		}
-	}
-
-	// Reset the currRow
-	beginRow = currRow;
-
-	if (currRow < MAX_VIDEO_Y) {
-		// Paint the bottom of the thumbnail black
-		destPtr = (uint8 *)(destBuff->data + ((currRow / 3) * destBuff->stride));
-		memset(destPtr, 21, (destBuff->h - (currRow / 3)) * destBuff->stride);
-	}
-
-	// Compress the thumbNail data into the RLE8Buff
-	if ((*spriteSize = (int32)gr_sprite_RLE8_encode(destBuff, &RLE8Buff)) <= 0) {
-		return nullptr;
-	}
-
-	// Fill in the Sprite structure
-	thumbNailSprite->w = destBuff->w;
-	thumbNailSprite->h = destBuff->h;
-	thumbNailSprite->encoding = RLE8;
-	thumbNailSprite->data = nullptr;
-	if ((thumbNailSprite->sourceHandle = NewHandle(*spriteSize, "thumbNail source")) == nullptr) {
-		return nullptr;
-	}
-	thumbNailSprite->sourceOffset = 0;
-
-	// Now copy the RLE8Buff into the thumbNail source handle
-	HLock(thumbNailSprite->sourceHandle);
-	thumbNailSprite->data = (uint8 *)(*(thumbNailSprite->sourceHandle));
-	memcpy(thumbNailSprite->data, RLE8Buff.data, *spriteSize);
-	HUnLock(thumbNailSprite->sourceHandle);
-
-	// Release all buffers
-	_G(gameDrawBuff)->release();
-	if (intrBuff) {
-		_G(gameInterfaceBuff)->release();
-	}
-	thumbNail->release();
-
-	// Free up both the thumbNail and the RLE8Buff
-	delete thumbNail;
-	mem_free((void *)RLE8Buff.data);
-
-	return thumbNailSprite;
-}
-
 //-------------------------------------   GAME MENU   -------------------------------------//
 
 
@@ -620,11 +434,188 @@ void CreateErrMenu(RGB8 *myPalette) {
 
 //--------------------------------   SAVE / LOAD MENU   -----------------------------------//
 
-void DestroySaveLoadMenu(bool saveMenu);
-void cb_SaveLoad_Slot(menuItemButton *theItem, guiMenu *myMenu);
-bool load_Handler(menuItemButton *theItem, int32 eventType, int32 event, int32 x, int32 y, void **currItem);
+#define SAVE_LOAD_MENU_X		145
+#define SAVE_LOAD_MENU_Y		 10
+#define SAVE_LOAD_MENU_W		344
+#define SAVE_LOAD_MENU_H		460
+
+#define SL_SAVE_X			214
+#define SL_SAVE_Y			384
+#define SL_SAVE_W			 74
+#define SL_SAVE_H		    43
+
+#define SL_LOAD_X			214
+#define SL_LOAD_Y			384
+#define SL_LOAD_W			 74
+#define SL_LOAD_H		    43
+
+#define SL_UP_X			292
+#define SL_UP_Y			255
+#define SL_UP_W			 20
+#define SL_UP_H		    17
+
+#define SL_DOWN_X			293
+#define SL_DOWN_Y			363
+#define SL_DOWN_W			 20
+#define SL_DOWN_H		    17
+
+#define SL_SLIDER_X			291
+#define SL_SLIDER_Y			255
+#define SL_SLIDER_W			 23
+#define SL_SLIDER_H		   127
+
+#define SL_CANCEL_X			139	
+#define SL_CANCEL_Y			384	
+#define SL_CANCEL_W			 74	
+#define SL_CANCEL_H		    43	
+
+#define SL_SAVE_LABEL_X			 50
+#define SL_SAVE_LABEL_Y			241
+#define SL_SAVE_LABEL_W			 70
+#define SL_SAVE_LABEL_H		    16
+
+#define SL_LOAD_LABEL_X			 50
+#define SL_LOAD_LABEL_Y			241
+#define SL_LOAD_LABEL_W			 70
+#define SL_LOAD_LABEL_H		    16
+
+#define SL_SCROLL_FIELD_X 		 50
+#define SL_SCROLL_FIELD_Y 		256
+#define SL_SCROLL_FIELD_W 		238
+#define SL_SCROLL_FIELD_H 		121
+
+#define SL_SCROLL_LINE_W 		238
+#define SL_SCROLL_LINE_H 		 15	//was 16
+
+#define SL_THUMBNAIL_X			 66
+#define SL_THUMBNAIL_Y			 28
+
+
+void SaveLoadMenu::show(RGB8 *myPalette, bool saveMenu) {
+	ItemHandlerFunction	i_handler;
+	bool buttonGreyed;
+
+	if (!_G(menuSystemInitialized)) {
+		guiMenu::initialize(myPalette);
+	}
+
+	// Keep the memory tidy
+	PurgeMem();
+	CompactMem();
+
+	// Load in the game menu sprites
+	if (!guiMenu::loadSprites("slmenu", SL_TOTAL_SPRITES)) {
+		return;
+	}
+
+	// Initialize some global vars
+	_GM(firstSlotIndex) = 0;
+	_GM(slotSelected) = -1;
+	_GM(saveLoadThumbNail) = nullptr;
+	_GM(thumbIndex) = 100;
+	_GM(currMenuIsSave) = saveMenu;
+
+	_GM(slMenu) = guiMenu::create(_GM(menuSprites)[SL_DIALOG_BOX], SAVE_LOAD_MENU_X, SAVE_LOAD_MENU_Y,
+		MENU_DEPTH | SF_GET_ALL | SF_BLOCK_ALL | SF_IMMOVABLE);
+	if (!_GM(slMenu)) {
+		return;
+	}
+
+	if (_GM(currMenuIsSave)) {
+		menuItemMsg::msgAdd(_GM(slMenu), SL_TAG_SAVE_LABEL, SL_SAVE_LABEL_X, SL_SAVE_LABEL_Y, SL_SAVE_LABEL_W, SL_SAVE_LABEL_H);
+		menuItemButton::add(_GM(slMenu), SL_TAG_SAVE, SL_SAVE_X, SL_SAVE_Y, SL_SAVE_W, SL_SAVE_H,
+			(CALLBACK)cb_SaveLoad_Save, menuItemButton::BTN_TYPE_SL_SAVE, true);
+	} else {
+		menuItemMsg::msgAdd(_GM(slMenu), SL_TAG_LOAD_LABEL, SL_LOAD_LABEL_X, SL_LOAD_LABEL_Y, SL_LOAD_LABEL_W, SL_LOAD_LABEL_H);
+		menuItemButton::add(_GM(slMenu), SL_TAG_LOAD, SL_LOAD_X, SL_LOAD_Y, SL_LOAD_W, SL_LOAD_H,
+			(CALLBACK)cb_SaveLoad_Load, menuItemButton::BTN_TYPE_SL_LOAD, true);
+	}
+
+	menuItemButton::add(_GM(slMenu), SL_TAG_CANCEL, SL_CANCEL_X, SL_CANCEL_Y, SL_CANCEL_W, SL_CANCEL_H,
+		(CALLBACK)cb_SaveLoad_Cancel, menuItemButton::BTN_TYPE_SL_CANCEL);
+
+	menuItemVSlider::add(_GM(slMenu), SL_TAG_VSLIDER, SL_SLIDER_X, SL_SLIDER_Y, SL_SLIDER_W, SL_SLIDER_H,
+		0, (CALLBACK)cb_SaveLoad_VSlider);
+
+	initializeSlotTables();
+
+	if (_GM(currMenuIsSave)) {
+		buttonGreyed = false;
+		i_handler = (ItemHandlerFunction)menuItemButton::handler;
+	} else {
+		buttonGreyed = true;
+		i_handler = (ItemHandlerFunction)load_Handler;
+	}
+
+	for (int32 i = 0; i < MAX_SLOTS_SHOWN; i++) {
+		menuItemButton::add(_GM(slMenu), 1001 + i,
+			SL_SCROLL_FIELD_X, SL_SCROLL_FIELD_Y + i * SL_SCROLL_LINE_H,
+			SL_SCROLL_LINE_W, SL_SCROLL_LINE_H,
+			(CALLBACK)cb_SaveLoad_Slot, menuItemButton::BTN_TYPE_SL_TEXT,
+			buttonGreyed && (!_GM(slotInUse)[i]), true, _GM(slotTitles)[i], i_handler);
+	}
 
-void cb_SaveLoad_VSlider(menuItemVSlider *myItem, guiMenu *myMenu) {
+	if (_GM(currMenuIsSave)) {
+		// Create thumbnails. One in the original game format for displaying,
+		// and the other in the ScummVM format for actually using in the save files
+		_GM(saveLoadThumbNail) = menu_CreateThumbnail(&_GM(sizeofThumbData));
+		_GM(_thumbnail).free();
+		Graphics::createThumbnail(_GM(_thumbnail));
+
+	} else {
+		updateThumbnails(0, _GM(slMenu));
+		_GM(saveLoadThumbNail) = _GM(menuSprites)[SL_EMPTY_THUMB];
+	}
+
+	menuItemMsg::msgAdd(_GM(slMenu), SL_TAG_THUMBNAIL, SL_THUMBNAIL_X, SL_THUMBNAIL_Y, SL_THUMBNAIL_W, SL_THUMBNAIL_H, false);
+
+	if (_GM(currMenuIsSave)) {
+		//<return> - if a slot has been selected, saves the game
+		//<esc> - cancels and returns to the game menu
+		guiMenu::configure(_GM(slMenu), (CALLBACK)cb_SaveLoad_Save, (CALLBACK)cb_SaveLoad_Cancel);
+	} else {
+		//<return> - if a slot has been selected, loads the selected game
+		//<esc> - cancels and returns to the game menu
+		guiMenu::configure(_GM(slMenu), (CALLBACK)cb_SaveLoad_Load, (CALLBACK)cb_SaveLoad_Cancel);
+	}
+
+	vmng_screen_show((void *)_GM(slMenu));
+	LockMouseSprite(0);
+}
+
+void SaveLoadMenu::destroyMenu(bool saveMenu) {
+	int32 i;
+
+	if (!_GM(slMenu)) {
+		return;
+	}
+
+	// Determine whether the screen was the SAVE or the LOAD menu
+	if (saveMenu) {
+
+		// If SAVE, there should be a thumbnail to unload
+		if (_GM(saveLoadThumbNail)) {
+			DisposeHandle(_GM(saveLoadThumbNail)->sourceHandle);
+			mem_free(_GM(saveLoadThumbNail));
+			_GM(saveLoadThumbNail) = nullptr;
+		}
+	} else {
+		// Else there may be up to 10 somewhere in the list to be unloaded
+		for (i = 0; i < MAX_SLOTS; i++) {
+			unloadThumbnail(i);
+		}
+		_GM(saveLoadThumbNail) = nullptr;
+	}
+
+	// Destroy the screen
+	vmng_screen_dispose(_GM(slMenu));
+	guiMenu::destroy(_GM(slMenu));
+
+	// Unload the save/load menu sprites
+	guiMenu::unloadSprites();
+}
+
+void SaveLoadMenu::cb_SaveLoad_VSlider(menuItemVSlider *myItem, guiMenu *myMenu) {
 	bool redraw;
 
 	if (!myMenu || !myItem)
@@ -664,7 +655,7 @@ void cb_SaveLoad_VSlider(menuItemVSlider *myItem, guiMenu *myMenu) {
 
 		// See if we were able to set a new first slot index
 		if (redraw) {
-			SetFirstSlot(_GM(firstSlotIndex), myMenu);
+			setFirstSlot(_GM(firstSlotIndex), myMenu);
 
 			// Calculate the new percent
 			myItem->percent = (_GM(firstSlotIndex) * 100) / 89;
@@ -681,12 +672,11 @@ void cb_SaveLoad_VSlider(menuItemVSlider *myItem, guiMenu *myMenu) {
 	// Else the callback came from the thumb - set the _GM(firstSlotIndex) based on the slider percent
 	else {
 		_GM(firstSlotIndex) = (myItem->percent * 89) / 100;
-		SetFirstSlot(_GM(firstSlotIndex), myMenu);
+		setFirstSlot(_GM(firstSlotIndex), myMenu);
 	}
 }
 
-
-void cb_SaveLoad_Save(void *, guiMenu *myMenu) {
+void SaveLoadMenu::cb_SaveLoad_Save(void *, guiMenu *myMenu) {
 	menuItemTextField *myText;
 	bool saveGameFailed;
 
@@ -713,7 +703,7 @@ void cb_SaveLoad_Save(void *, guiMenu *myMenu) {
 	// If the save game failed, bring up the err menu
 	if (saveGameFailed) {
 		// Kill the save menu
-		DestroySaveLoadMenu(true);
+		destroyMenu(true);
 
 		// Create the err menu
 		CreateErrMenu(nullptr);
@@ -723,14 +713,13 @@ void cb_SaveLoad_Save(void *, guiMenu *myMenu) {
 	}
 
 	// Kill the save menu
-	DestroySaveLoadMenu(true);
+	destroyMenu(true);
 
 	// Shutdown the menu system
 	guiMenu::shutdown(true);
 }
 
-
-void cb_SaveLoad_Load(menuItemButton *, guiMenu *) {
+void SaveLoadMenu::cb_SaveLoad_Load(menuItemButton *, guiMenu *) {
 	KernelTriggerType oldMode;
 
 	// If (slotSelected < 0) this callback is being executed by pressing return prematurely
@@ -739,7 +728,7 @@ void cb_SaveLoad_Load(menuItemButton *, guiMenu *) {
 	}
 
 	// Kill the menu
-	DestroySaveLoadMenu(false);
+	destroyMenu(false);
 
 	// Shutdown the menu system
 	guiMenu::shutdown(false);
@@ -760,8 +749,7 @@ void cb_SaveLoad_Load(menuItemButton *, guiMenu *) {
 	_G(kernel).trigger_mode = oldMode;
 }
 
-
-void cb_SaveLoad_Cancel(menuItemButton *, guiMenu *myMenu) {
+void SaveLoadMenu::cb_SaveLoad_Cancel(menuItemButton *, guiMenu *myMenu) {
 	menuItem *myItem;
 	int32 i, x, y, w, h;
 
@@ -796,11 +784,11 @@ void cb_SaveLoad_Cancel(menuItemButton *, guiMenu *myMenu) {
 
 			// Remove the thumbnail
 			if (_GM(saveLoadThumbNail)) {
-				_GM(saveLoadThumbNail) = _GM(menuSprites)[Burger::GUI::SL_EMPTY_THUMB];
+				_GM(saveLoadThumbNail) = _GM(menuSprites)[SL_EMPTY_THUMB];
 				guiMenu::itemRefresh(nullptr, SL_TAG_THUMBNAIL, myMenu);
 			}
 		}
-		SetFirstSlot(_GM(firstSlotIndex), myMenu);
+		setFirstSlot(_GM(firstSlotIndex), myMenu);
 
 		// Enable the slider
 		menuItemVSlider::enableVSlider(nullptr, SL_TAG_VSLIDER, myMenu);
@@ -822,7 +810,7 @@ void cb_SaveLoad_Cancel(menuItemButton *, guiMenu *myMenu) {
 		// Otherwise, back to the game menu
 
 		// Destroy the menu
-		DestroySaveLoadMenu(_GM(currMenuIsSave));
+		destroyMenu(_GM(currMenuIsSave));
 
 		if (_GM(saveLoadFromHotkey)) {
 			// Shutdown the menu system
@@ -836,8 +824,7 @@ void cb_SaveLoad_Cancel(menuItemButton *, guiMenu *myMenu) {
 	_GM(buttonClosesDialog) = true;
 }
 
-
-void cb_SaveLoad_Slot(menuItemButton *myButton, guiMenu *myMenu) {
+void SaveLoadMenu::cb_SaveLoad_Slot(menuItemButton *myButton, guiMenu *myMenu) {
 	int32 i, x, y, w, h;
 	char prompt[80];
 	int32 specialTag;
@@ -897,25 +884,7 @@ void cb_SaveLoad_Slot(menuItemButton *myButton, guiMenu *myMenu) {
 	}
 }
 
-void InitializeSlotTables(void) {
-	const SaveStateList saves = g_engine->listSaves();
-
-	// First reset all the slots to empty
-	for (int i = 0; i < MAX_SLOTS; ++i) {
-		Common::strcpy_s(_GM(slotTitles)[i], 80, "<empty>");
-		_GM(slotInUse)[i] = false;
-	}
-
-	for (const auto &save : saves) {
-		if (save.getSaveSlot() != 0) {
-			Common::String desc = save.getDescription();
-			Common::strcpy_s(_GM(slotTitles)[save.getSaveSlot() - 1], 80, desc.c_str());
-			_GM(slotInUse)[save.getSaveSlot() - 1] = true;
-		}
-	}
-}
-
-bool load_Handler(menuItemButton *myItem, int32 eventType, int32 event, int32 x, int32 y, void **currItem) {
+bool SaveLoadMenu::load_Handler(menuItemButton *myItem, int32 eventType, int32 event, int32 x, int32 y, void **currItem) {
 	bool handled;
 
 	// Handle the event just like any other button
@@ -956,7 +925,7 @@ bool load_Handler(menuItemButton *myItem, int32 eventType, int32 event, int32 x,
 
 				// Remove the thumbnail
 				if (_GM(saveLoadThumbNail)) {
-					_GM(saveLoadThumbNail) = _GM(menuSprites)[Burger::GUI::SL_EMPTY_THUMB];
+					_GM(saveLoadThumbNail) = _GM(menuSprites)[SL_EMPTY_THUMB];
 					guiMenu::itemRefresh(nullptr, SL_TAG_THUMBNAIL, (guiMenu *)myItem->myMenu);
 				}
 			}
@@ -967,131 +936,6 @@ bool load_Handler(menuItemButton *myItem, int32 eventType, int32 event, int32 x,
 }
 
 
-void DestroySaveLoadMenu(bool saveMenu) {
-	int32 i;
-
-	if (!_GM(slMenu)) {
-		return;
-	}
-
-	// Determine whether the screen was the SAVE or the LOAD menu
-	if (saveMenu) {
-
-		// If SAVE, there should be a thumbnail to unload
-		if (_GM(saveLoadThumbNail)) {
-			DisposeHandle(_GM(saveLoadThumbNail)->sourceHandle);
-			mem_free(_GM(saveLoadThumbNail));
-			_GM(saveLoadThumbNail) = nullptr;
-		}
-	} else {
-		// Else there may be up to 10 somewhere in the list to be unloaded
-		for (i = 0; i < MAX_SLOTS; i++) {
-			UnloadThumbNail(i);
-		}
-		_GM(saveLoadThumbNail) = nullptr;
-	}
-
-	// Destroy the screen
-	vmng_screen_dispose(_GM(slMenu));
-	guiMenu::destroy(_GM(slMenu));
-
-	// Unload the save/load menu sprites
-	guiMenu::unloadSprites();
-}
-
-
-void CreateSaveLoadMenu(RGB8 *myPalette, bool saveMenu) {
-	ItemHandlerFunction	i_handler;
-	bool buttonGreyed;
-
-	if (!_G(menuSystemInitialized)) {
-		guiMenu::initialize(myPalette);
-	}
-
-	// Keep the memory tidy
-	PurgeMem();
-	CompactMem();
-
-	// Load in the game menu sprites
-	if (!guiMenu::loadSprites("slmenu", Burger::GUI::SL_TOTAL_SPRITES)) {
-		return;
-	}
-
-	// Initialize some global vars
-	_GM(firstSlotIndex) = 0;
-	_GM(slotSelected) = -1;
-	_GM(saveLoadThumbNail) = nullptr;
-	_GM(thumbIndex) = 100;
-	_GM(currMenuIsSave) = saveMenu;
-
-	_GM(slMenu) = guiMenu::create(_GM(menuSprites)[Burger::GUI::SL_DIALOG_BOX], SAVE_LOAD_MENU_X, SAVE_LOAD_MENU_Y,
-		MENU_DEPTH | SF_GET_ALL | SF_BLOCK_ALL | SF_IMMOVABLE);
-	if (!_GM(slMenu)) {
-		return;
-	}
-
-	if (_GM(currMenuIsSave)) {
-		menuItemMsg::msgAdd(_GM(slMenu), SL_TAG_SAVE_LABEL, SL_SAVE_LABEL_X, SL_SAVE_LABEL_Y, SL_SAVE_LABEL_W, SL_SAVE_LABEL_H);
-		menuItemButton::add(_GM(slMenu), SL_TAG_SAVE, SL_SAVE_X, SL_SAVE_Y, SL_SAVE_W, SL_SAVE_H,
-			(CALLBACK)cb_SaveLoad_Save, menuItemButton::BTN_TYPE_SL_SAVE, true);
-	} else {
-		menuItemMsg::msgAdd(_GM(slMenu), SL_TAG_LOAD_LABEL, SL_LOAD_LABEL_X, SL_LOAD_LABEL_Y, SL_LOAD_LABEL_W, SL_LOAD_LABEL_H);
-		menuItemButton::add(_GM(slMenu), SL_TAG_LOAD, SL_LOAD_X, SL_LOAD_Y, SL_LOAD_W, SL_LOAD_H,
-			(CALLBACK)cb_SaveLoad_Load, menuItemButton::BTN_TYPE_SL_LOAD, true);
-	}
-
-	menuItemButton::add(_GM(slMenu), SL_TAG_CANCEL, SL_CANCEL_X, SL_CANCEL_Y, SL_CANCEL_W, SL_CANCEL_H,
-		(CALLBACK)cb_SaveLoad_Cancel, menuItemButton::BTN_TYPE_SL_CANCEL);
-
-	menuItemVSlider::add(_GM(slMenu), SL_TAG_VSLIDER, SL_SLIDER_X, SL_SLIDER_Y, SL_SLIDER_W, SL_SLIDER_H,
-		0, (CALLBACK)cb_SaveLoad_VSlider);
-
-	InitializeSlotTables();
-
-	if (_GM(currMenuIsSave)) {
-		buttonGreyed = false;
-		i_handler = (ItemHandlerFunction)menuItemButton::handler;
-	} else {
-		buttonGreyed = true;
-		i_handler = (ItemHandlerFunction)load_Handler;
-	}
-
-	for (int32 i = 0; i < MAX_SLOTS_SHOWN; i++) {
-		menuItemButton::add(_GM(slMenu), 1001 + i,
-			SL_SCROLL_FIELD_X, SL_SCROLL_FIELD_Y + i * SL_SCROLL_LINE_H,
-			SL_SCROLL_LINE_W, SL_SCROLL_LINE_H,
-			(CALLBACK)cb_SaveLoad_Slot, menuItemButton::BTN_TYPE_SL_TEXT,
-			buttonGreyed && (!_GM(slotInUse)[i]), true, _GM(slotTitles)[i], i_handler);
-	}
-
-	if (_GM(currMenuIsSave)) {
-		// Create thumbnails. One in the original game format for displaying,
-		// and the other in the ScummVM format for actually using in the save files
-		_GM(saveLoadThumbNail) = menu_CreateThumbnail(&_GM(sizeofThumbData));
-		_GM(_thumbnail).free();
-		Graphics::createThumbnail(_GM(_thumbnail));
-
-	} else {
-		UpdateThumbNails(0, _GM(slMenu));
-		_GM(saveLoadThumbNail) = _GM(menuSprites)[Burger::GUI::SL_EMPTY_THUMB];
-	}
-
-	menuItemMsg::msgAdd(_GM(slMenu), SL_TAG_THUMBNAIL, SL_THUMBNAIL_X, SL_THUMBNAIL_Y, SL_THUMBNAIL_W, SL_THUMBNAIL_H, false);
-
-	if (_GM(currMenuIsSave)) {
-		//<return> - if a slot has been selected, saves the game
-		//<esc> - cancels and returns to the game menu
-		guiMenu::configure(_GM(slMenu), (CALLBACK)cb_SaveLoad_Save, (CALLBACK)cb_SaveLoad_Cancel);
-	} else {
-		//<return> - if a slot has been selected, loads the selected game
-		//<esc> - cancels and returns to the game menu
-		guiMenu::configure(_GM(slMenu), (CALLBACK)cb_SaveLoad_Load, (CALLBACK)cb_SaveLoad_Cancel);
-	}
-
-	vmng_screen_show((void *)_GM(slMenu));
-	LockMouseSprite(0);
-}
-
 void CreateGameMenu(RGB8 *myPalette) {
 	if ((!player_commands_allowed()) || (!INTERFACE_VISIBLE) ||
 		_G(pal_fade_in_progress) || _G(menuSystemInitialized)) {
@@ -1113,7 +957,7 @@ void CreateGameMenuFromMain(RGB8 *myPalette) {
 
 void CreateSaveMenu(RGB8 *myPalette) {
 	_GM(saveLoadFromHotkey) = false;
-	CreateSaveLoadMenu(myPalette, true);
+	SaveLoadMenu::show(myPalette, true);
 }
 
 void CreateF2SaveMenu(RGB8 *myPalette) {
@@ -1124,12 +968,12 @@ void CreateF2SaveMenu(RGB8 *myPalette) {
 
 	_GM(saveLoadFromHotkey) = true;
 	_GM(gameMenuFromMain) = false;
-	CreateSaveLoadMenu(myPalette, true);
+	SaveLoadMenu::show(myPalette, true);
 }
 
 void CreateLoadMenu(RGB8 *myPalette) {
 	_GM(saveLoadFromHotkey) = false;
-	CreateSaveLoadMenu(myPalette, false);
+	SaveLoadMenu::show(myPalette, false);
 }
 
 void CreateF3LoadMenu(RGB8 *myPalette) {
@@ -1140,7 +984,7 @@ void CreateF3LoadMenu(RGB8 *myPalette) {
 
 	_GM(saveLoadFromHotkey) = true;
 	_GM(gameMenuFromMain) = false;
-	CreateSaveLoadMenu(myPalette, false);
+	SaveLoadMenu::show(myPalette, false);
 }
 
 void CreateLoadMenuFromMain(RGB8 *myPalette) {
@@ -1150,7 +994,7 @@ void CreateLoadMenuFromMain(RGB8 *myPalette) {
 
 	_GM(saveLoadFromHotkey) = true;
 	_GM(gameMenuFromMain) = true;
-	CreateSaveLoadMenu(myPalette, false);
+	SaveLoadMenu::show(myPalette, false);
 }
 
 } // namespace GUI
diff --git a/engines/m4/burger/gui/game_menu.h b/engines/m4/burger/gui/game_menu.h
index b228eee5835..be5b781a56a 100644
--- a/engines/m4/burger/gui/game_menu.h
+++ b/engines/m4/burger/gui/game_menu.h
@@ -27,6 +27,7 @@
 #include "m4/m4_types.h"
 #include "m4/graphics/gr_buff.h"
 #include "m4/gui/gui_menu_items.h"
+#include "m4/gui/game_menu.h"
 #include "m4/gui/gui_univ.h"
 
 namespace M4 {
@@ -43,6 +44,21 @@ using M4::GUI::Sprite;
 using M4::GUI::CALLBACK;
 using M4::GUI::ItemHandlerFunction;
 
+class SaveLoadMenu : public M4::GUI::SaveLoadMenuBase {
+private:
+	static void destroyMenu(bool saveMenu);
+	static bool load_Handler(menuItemButton *theItem, int32 eventType, int32 event, int32 x, int32 y, void **currItem);
+
+	static void cb_SaveLoad_Save(void *, guiMenu *myMenu);
+	static void cb_SaveLoad_Load(menuItemButton *, guiMenu *);
+	static void cb_SaveLoad_Cancel(menuItemButton *, guiMenu *myMenu);
+	static void cb_SaveLoad_Slot(menuItemButton *myButton, guiMenu *myMenu);
+	static void cb_SaveLoad_VSlider(menuItemVSlider *myItem, guiMenu *myMenu);
+
+public:
+	static void show(RGB8 *myPalette, bool saveMenu);
+};
+
 //GAME MENU FUNCTIONS
 extern void CreateGameMenu(RGB8 *myPalette);
 extern void CreateOptionsMenu(RGB8 *myPalette);
@@ -68,66 +84,6 @@ enum game_menu_button_tags {
 	GM_TAG_MAIN = 6
 };
 
-//======================================
-//
-//		Save/Load menu enums and defines
-//
-#define SAVE_LOAD_MENU_X		145
-#define SAVE_LOAD_MENU_Y		 10
-#define SAVE_LOAD_MENU_W		344
-#define SAVE_LOAD_MENU_H		460
-
-#define SL_SAVE_X			214
-#define SL_SAVE_Y			384
-#define SL_SAVE_W			 74
-#define SL_SAVE_H		    43
-
-#define SL_LOAD_X			214
-#define SL_LOAD_Y			384
-#define SL_LOAD_W			 74
-#define SL_LOAD_H		    43
-
-#define SL_UP_X			292
-#define SL_UP_Y			255
-#define SL_UP_W			 20
-#define SL_UP_H		    17
-
-#define SL_DOWN_X			293
-#define SL_DOWN_Y			363
-#define SL_DOWN_W			 20
-#define SL_DOWN_H		    17
-
-#define SL_SLIDER_X			291
-#define SL_SLIDER_Y			255
-#define SL_SLIDER_W			 23
-#define SL_SLIDER_H		   127
-
-#define SL_CANCEL_X			139	
-#define SL_CANCEL_Y			384	
-#define SL_CANCEL_W			 74	
-#define SL_CANCEL_H		    43	
-
-#define SL_SAVE_LABEL_X			 50
-#define SL_SAVE_LABEL_Y			241
-#define SL_SAVE_LABEL_W			 70
-#define SL_SAVE_LABEL_H		    16
-
-#define SL_LOAD_LABEL_X			 50
-#define SL_LOAD_LABEL_Y			241
-#define SL_LOAD_LABEL_W			 70
-#define SL_LOAD_LABEL_H		    16
-
-#define SL_SCROLL_FIELD_X 		 50
-#define SL_SCROLL_FIELD_Y 		256
-#define SL_SCROLL_FIELD_W 		238
-#define SL_SCROLL_FIELD_H 		121
-
-#define SL_SCROLL_LINE_W 		238
-#define SL_SCROLL_LINE_H 		 15	//was 16
-
-#define SL_THUMBNAIL_X			 66
-#define SL_THUMBNAIL_Y			 28
-
 /**
  * Error menu enums and defines
  */
diff --git a/engines/m4/gui/game_menu.cpp b/engines/m4/gui/game_menu.cpp
new file mode 100644
index 00000000000..15caeff3b3f
--- /dev/null
+++ b/engines/m4/gui/game_menu.cpp
@@ -0,0 +1,334 @@
+/* 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 "m4/core/imath.h"
+#include "m4/graphics/gr_sprite.h"
+#include "m4/gui/game_menu.h"
+#include "m4/gui/gui_vmng.h"
+#include "m4/m4.h"
+
+namespace M4 {
+namespace GUI {
+
+void SaveLoadMenuBase::initializeSlotTables(void) {
+	const SaveStateList saves = g_engine->listSaves();
+
+	// First reset all the slots to empty
+	for (int i = 0; i < MAX_SLOTS; ++i) {
+		Common::strcpy_s(_GM(slotTitles)[i], 80, "<empty>");
+		_GM(slotInUse)[i] = false;
+	}
+
+	for (const auto &save : saves) {
+		if (save.getSaveSlot() != 0) {
+			Common::String desc = save.getDescription();
+			Common::strcpy_s(_GM(slotTitles)[save.getSaveSlot() - 1], 80, desc.c_str());
+			_GM(slotInUse)[save.getSaveSlot() - 1] = true;
+		}
+	}
+}
+
+Sprite *SaveLoadMenuBase::menu_CreateThumbnail(int32 *spriteSize) {
+	Sprite *thumbNailSprite;
+	GrBuff *thumbNail;
+	Buffer *scrnBuff, *intrBuff, *destBuff, RLE8Buff;
+	uint8 *srcPtr, *srcPtr2, *srcPtr3, *srcRowPtr, *destPtr;
+	ScreenContext *gameScreen;
+	int32 i, status;
+	int32 currRow, beginRow, endRow;
+
+	// Create a Sprite for the thumbNail
+	if ((thumbNailSprite = (Sprite *)mem_alloc(sizeof(Sprite), "sprite")) == nullptr) {
+		return nullptr;
+	}
+
+	thumbNail = new GrBuff((MAX_VIDEO_X + 1) / 3, (MAX_VIDEO_Y + 1) / 3);
+	if (!thumbNail) {
+		return nullptr;
+	}
+
+	destBuff = thumbNail->get_buffer();
+	if (!destBuff) {
+		return nullptr;
+	}
+
+	gameScreen = vmng_screen_find(_G(gameDrawBuff), &status);
+	if ((!gameScreen) || (status != SCRN_ACTIVE)) {
+		return nullptr;
+	}
+
+	scrnBuff = _G(gameDrawBuff)->get_buffer();
+	if (!scrnBuff) {
+		return nullptr;
+	}
+
+	// Grab the interface buffer
+	intrBuff = _G(gameInterfaceBuff)->get_buffer();
+
+	if (gameScreen->y1 > 0) {
+		// Paint the top of the thumbnail black
+		beginRow = gameScreen->y1;
+		memset(destBuff->data, 21, (beginRow / 3) * destBuff->stride);
+		srcRowPtr = (uint8 *)(scrnBuff->data + (-gameScreen->x1));
+		destPtr = (uint8 *)(destBuff->data + ((beginRow / 3) * destBuff->stride));
+	} else {
+		srcRowPtr = (uint8 *)(scrnBuff->data + ((-gameScreen->y1) * scrnBuff->stride) + (-gameScreen->x1));
+		beginRow = 0;
+		destPtr = destBuff->data;
+	}
+	endRow = imath_min(MAX_VIDEO_Y, gameScreen->y2);
+
+	for (currRow = beginRow; currRow <= endRow; currRow += 3) {
+
+		// Set the src pointers
+		srcPtr = srcRowPtr;
+		srcPtr2 = srcRowPtr + scrnBuff->stride;
+		srcPtr3 = srcRowPtr + (scrnBuff->stride << 1);
+
+		for (i = 0; i < (MAX_VIDEO_X + 1) / 3; i++) {
+
+			// Calculate the average - make sure not to extend past the end of the buffer
+			if (endRow - currRow < 1) {
+				*destPtr = (uint8)((uint32)((*srcPtr + *(srcPtr + 1) + *(srcPtr + 2)) / 3));
+			} else if (endRow - currRow < 2) {
+				*destPtr = (uint8)((uint32)((*srcPtr + *(srcPtr + 1) + *(srcPtr + 2) +
+					*srcPtr2 + *(srcPtr2 + 1) + *(srcPtr2 + 2)) / 6));
+			} else {
+				*destPtr = (uint8)((uint32)((*srcPtr + *(srcPtr + 1) + *(srcPtr + 2) +
+					*srcPtr2 + *(srcPtr2 + 1) + *(srcPtr2 + 2) +
+					*srcPtr3 + *(srcPtr3 + 1) + *(srcPtr3 + 2)) / 9));
+			}
+
+			if (*destPtr == 0) {
+				*destPtr = 21;
+			}
+
+			// Increment the pointers
+			srcPtr += 3;
+			srcPtr2 += 3;
+			srcPtr3 += 3;
+			destPtr++;
+		}
+
+		// Update the row pointer
+		srcRowPtr += scrnBuff->stride * 3;
+	}
+
+	// Reset the currRow
+	beginRow = currRow;
+
+	// Paint the interface section of the thumbnail
+	if (currRow < MAX_VIDEO_Y) {
+		// If the interface is visible, grab it
+		if (intrBuff) {
+			srcRowPtr = intrBuff->data;
+			endRow = imath_min(MAX_VIDEO_Y, beginRow + intrBuff->h - 1);
+			for (currRow = beginRow; currRow <= endRow; currRow += 3) {
+				// Set the src pointers
+				srcPtr = srcRowPtr;
+				srcPtr2 = srcRowPtr + intrBuff->stride;
+				srcPtr3 = srcRowPtr + (intrBuff->stride << 1);
+
+				for (i = 0; i < (MAX_VIDEO_X + 1) / 3; i++) {
+					// If the pix is outside of the inventory objects in the interface, set to black
+					// If ((srcPtr - srcRowPtr < 180) || (srcPtr - srcRowPtr > 575)) {
+					if (true) {			// for now make everything in the interface black
+						*destPtr = 21;
+					}
+
+					// Else calculate the average - make sure not to extend past the end of the buffer
+					else {
+						if (endRow - currRow < 1) {
+							*destPtr = (uint8)((uint32)((*srcPtr + *(srcPtr + 1) + *(srcPtr + 2)) / 3));
+						} else if (endRow - currRow < 2) {
+							*destPtr = (uint8)((uint32)((*srcPtr + *(srcPtr + 1) + *(srcPtr + 2) +
+								*srcPtr2 + *(srcPtr2 + 1) + *(srcPtr2 + 2)) / 6));
+						} else {
+							*destPtr = (uint8)((uint32)((*srcPtr + *(srcPtr + 1) + *(srcPtr + 2) +
+								*srcPtr2 + *(srcPtr2 + 1) + *(srcPtr2 + 2) +
+								*srcPtr3 + *(srcPtr3 + 1) + *(srcPtr3 + 2)) / 9));
+						}
+						if (*destPtr == 0) {
+							*destPtr = 21;
+						}
+					}
+
+					// Increment the pointers
+					srcPtr += 3;
+					srcPtr2 += 3;
+					srcPtr3 += 3;
+					destPtr++;
+				}
+
+				// Update the row pointer
+				srcRowPtr += intrBuff->stride * 3;
+			}
+		} else {
+			// Else paint the bottom of the thumbnail black
+			destPtr = (uint8 *)(destBuff->data + ((currRow / 3) * destBuff->stride));
+			memset(destPtr, 21, (destBuff->h - (currRow / 3)) * destBuff->stride);
+		}
+	}
+
+	// Reset the currRow
+	beginRow = currRow;
+
+	if (currRow < MAX_VIDEO_Y) {
+		// Paint the bottom of the thumbnail black
+		destPtr = (uint8 *)(destBuff->data + ((currRow / 3) * destBuff->stride));
+		memset(destPtr, 21, (destBuff->h - (currRow / 3)) * destBuff->stride);
+	}
+
+	// Compress the thumbNail data into the RLE8Buff
+	if ((*spriteSize = (int32)gr_sprite_RLE8_encode(destBuff, &RLE8Buff)) <= 0) {
+		return nullptr;
+	}
+
+	// Fill in the Sprite structure
+	thumbNailSprite->w = destBuff->w;
+	thumbNailSprite->h = destBuff->h;
+	thumbNailSprite->encoding = RLE8;
+	thumbNailSprite->data = nullptr;
+	if ((thumbNailSprite->sourceHandle = NewHandle(*spriteSize, "thumbNail source")) == nullptr) {
+		return nullptr;
+	}
+	thumbNailSprite->sourceOffset = 0;
+
+	// Now copy the RLE8Buff into the thumbNail source handle
+	HLock(thumbNailSprite->sourceHandle);
+	thumbNailSprite->data = (uint8 *)(*(thumbNailSprite->sourceHandle));
+	memcpy(thumbNailSprite->data, RLE8Buff.data, *spriteSize);
+	HUnLock(thumbNailSprite->sourceHandle);
+
+	// Release all buffers
+	_G(gameDrawBuff)->release();
+	if (intrBuff) {
+		_G(gameInterfaceBuff)->release();
+	}
+	thumbNail->release();
+
+	// Free up both the thumbNail and the RLE8Buff
+	delete thumbNail;
+	mem_free((void *)RLE8Buff.data);
+
+	return thumbNailSprite;
+}
+
+bool SaveLoadMenuBase::loadThumbnail(int32 slotNum) {
+	Sprite *&thumbNailSprite = _GM(thumbNails)[slotNum];
+	return g_engine->loadSaveThumbnail(slotNum + 1, thumbNailSprite);
+}
+
+void SaveLoadMenuBase::unloadThumbnail(int32 slotNum) {
+	if (_GM(thumbNails)[slotNum]->sourceHandle) {
+		HUnLock(_GM(thumbNails)[slotNum]->sourceHandle);
+		DisposeHandle(_GM(thumbNails)[slotNum]->sourceHandle);
+		_GM(thumbNails)[slotNum]->sourceHandle = nullptr;
+	}
+}
+
+void SaveLoadMenuBase::updateThumbnails(int32 firstSlot, guiMenu *myMenu) {
+	int32 i, startIndex, endIndex;
+
+	// Make sure there is something to update
+	if (firstSlot == _GM(thumbIndex)) {
+		return;
+	}
+
+	// Ensure firstSlot is in a valid range
+	firstSlot = imath_max(imath_min(firstSlot, 89), 0);
+
+	if (firstSlot > _GM(thumbIndex)) {
+		// Dump Out all thumbnails in slots which don't overlap
+		startIndex = _GM(thumbIndex);
+		endIndex = imath_min(_GM(thumbIndex) + 9, firstSlot - 1);
+		for (i = startIndex; i <= endIndex; i++) {
+			unloadThumbnail(i);
+		}
+
+		// Load in all thumbnails missing thumbnails
+		startIndex = imath_max(_GM(thumbIndex) + 10, firstSlot);
+		endIndex = imath_min(firstSlot + 9, 98);
+		for (i = startIndex; i <= endIndex; i++) {
+			if (_GM(slotInUse)[i]) {
+				if (!loadThumbnail(i)) {
+					_GM(slotInUse)[i] = false;
+					menuItemButton::disableButton(nullptr, 1001 + i - firstSlot, myMenu);
+					guiMenu::itemRefresh(nullptr, 1001 + i - firstSlot, myMenu);
+				}
+			}
+		}
+	} else {
+		// Else firstSlot < _GM(thumbIndex)
+		// Dump Out all thumbnails in slots which don't overlap
+		startIndex = imath_max(firstSlot + 10, _GM(thumbIndex));
+		endIndex = imath_min(_GM(thumbIndex) + 9, 98);
+		for (i = startIndex; i <= endIndex; i++) {
+			unloadThumbnail(i);
+		}
+
+		// Load in all thumbnails missing thumbnails
+		startIndex = firstSlot;
+		endIndex = imath_min(firstSlot + 9, _GM(thumbIndex) - 1);
+		for (i = startIndex; i <= endIndex; i++) {
+			if (_GM(slotInUse)[i]) {
+				if (!loadThumbnail(i)) {
+					_GM(slotInUse)[i] = false;
+					menuItemButton::disableButton(nullptr, 1001 + i - firstSlot, myMenu);
+					guiMenu::itemRefresh(nullptr, 1001 + i - firstSlot, myMenu);
+				}
+			}
+		}
+	}
+
+	// Set the var
+	_GM(thumbIndex) = firstSlot;
+}
+
+void SaveLoadMenuBase::setFirstSlot(int32 firstSlot, guiMenu *myMenu) {
+	menuItemButton *myButton;
+	int32 i;
+
+	if (!myMenu) {
+		return;
+	}
+
+	// Ensure firstSlot is in a valid range
+	firstSlot = imath_max(imath_min(firstSlot, 89), 0);
+
+	// Change the prompt and special tag of each of the slot buttons
+	for (i = 0; i < MAX_SLOTS_SHOWN; i++) {
+		myButton = (menuItemButton *)guiMenu::getItem(i + 1001, myMenu);
+
+		myButton->prompt = _GM(slotTitles)[firstSlot + i];
+		if (_GM(currMenuIsSave) || _GM(slotInUse)[firstSlot + i]) {
+			myButton->itemFlags = menuItemButton::BTN_STATE_NORM;
+		} else {
+			myButton->itemFlags = menuItemButton::BTN_STATE_GREY;
+		}
+
+		myButton->specialTag = firstSlot + i + 1;
+		guiMenu::itemRefresh(myButton, i + 1001, myMenu);
+	}
+}
+
+} // namespace GUI
+} // namespace M4
diff --git a/engines/m4/gui/game_menu.h b/engines/m4/gui/game_menu.h
new file mode 100644
index 00000000000..e33b1251d3c
--- /dev/null
+++ b/engines/m4/gui/game_menu.h
@@ -0,0 +1,47 @@
+
+/* 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 M4_GUI_GUI_GAME_MENU_H
+#define M4_GUI_GUI_GAME_MENU_H
+
+#include "m4/m4_types.h"
+#include "m4/gui/gui_menu_items.h"
+
+namespace M4 {
+namespace GUI {
+
+class SaveLoadMenuBase {
+protected:
+	static void initializeSlotTables();
+	static Sprite *menu_CreateThumbnail(int32 *spriteSize);
+	static bool loadThumbnail(int32 slotNum);
+	static void unloadThumbnail(int32 slotNum);
+	static void setFirstSlot(int32 firstSlot, guiMenu *myMenu);
+
+public:
+	static void updateThumbnails(int32 firstSlot, guiMenu *myMenu);
+};
+
+} // namespace GUI
+} // namespace M4
+
+#endif
diff --git a/engines/m4/gui/gui_menu_items.cpp b/engines/m4/gui/gui_menu_items.cpp
index 49c0afe744c..ac737f02dca 100644
--- a/engines/m4/gui/gui_menu_items.cpp
+++ b/engines/m4/gui/gui_menu_items.cpp
@@ -27,6 +27,7 @@
 #include "m4/adv_r/adv_player.h"
 #include "m4/core/errors.h"
 #include "m4/core/imath.h"
+#include "m4/gui/game_menu.h"
 #include "m4/gui/gui_event.h"
 #include "m4/gui/hotkeys.h"
 #include "m4/graphics/gr_line.h"
@@ -78,104 +79,6 @@ void gui_DrawSprite(Sprite *mySprite, Buffer *myBuff, int32 x, int32 y) {
 	}
 }
 
-bool LoadThumbNail(int32 slotNum) {
-	Sprite *&thumbNailSprite = _GM(thumbNails)[slotNum];
-	return g_engine->loadSaveThumbnail(slotNum + 1, thumbNailSprite);
-}
-
-void UnloadThumbNail(int32 slotNum) {
-	if (_GM(thumbNails)[slotNum]->sourceHandle) {
-		HUnLock(_GM(thumbNails)[slotNum]->sourceHandle);
-		DisposeHandle(_GM(thumbNails)[slotNum]->sourceHandle);
-		_GM(thumbNails)[slotNum]->sourceHandle = nullptr;
-	}
-}
-
-void UpdateThumbNails(int32 firstSlot, guiMenu *myMenu) {
-	int32 i, startIndex, endIndex;
-
-	// Make sure there is something to update
-	if (firstSlot == _GM(thumbIndex)) {
-		return;
-	}
-
-	// Ensure firstSlot is in a valid range
-	firstSlot = imath_max(imath_min(firstSlot, 89), 0);
-
-	if (firstSlot > _GM(thumbIndex)) {
-		// Dump Out all thumbnails in slots which don't overlap
-		startIndex = _GM(thumbIndex);
-		endIndex = imath_min(_GM(thumbIndex) + 9, firstSlot - 1);
-		for (i = startIndex; i <= endIndex; i++) {
-			UnloadThumbNail(i);
-		}
-
-		// Load in all thumbnails missing thumbnails
-		startIndex = imath_max(_GM(thumbIndex) + 10, firstSlot);
-		endIndex = imath_min(firstSlot + 9, 98);
-		for (i = startIndex; i <= endIndex; i++) {
-			if (_GM(slotInUse)[i]) {
-				if (!LoadThumbNail(i)) {
-					_GM(slotInUse)[i] = false;
-					menuItemButton::disableButton(nullptr, 1001 + i - firstSlot, myMenu);
-					guiMenu::itemRefresh(nullptr, 1001 + i - firstSlot, myMenu);
-				}
-			}
-		}
-	} else {
-		// Else firstSlot < _GM(thumbIndex)
-		// Dump Out all thumbnails in slots which don't overlap
-		startIndex = imath_max(firstSlot + 10, _GM(thumbIndex));
-		endIndex = imath_min(_GM(thumbIndex) + 9, 98);
-		for (i = startIndex; i <= endIndex; i++) {
-			UnloadThumbNail(i);
-		}
-
-		// Load in all thumbnails missing thumbnails
-		startIndex = firstSlot;
-		endIndex = imath_min(firstSlot + 9, _GM(thumbIndex) - 1);
-		for (i = startIndex; i <= endIndex; i++) {
-			if (_GM(slotInUse)[i]) {
-				if (!LoadThumbNail(i)) {
-					_GM(slotInUse)[i] = false;
-					menuItemButton::disableButton(nullptr, 1001 + i - firstSlot, myMenu);
-					guiMenu::itemRefresh(nullptr, 1001 + i - firstSlot, myMenu);
-				}
-			}
-		}
-	}
-
-	// Set the var
-	_GM(thumbIndex) = firstSlot;
-}
-
-void SetFirstSlot(int32 firstSlot, guiMenu *myMenu) {
-	menuItemButton *myButton;
-	int32 i;
-
-	if (!myMenu) {
-		return;
-	}
-
-	// Ensure firstSlot is in a valid range
-	firstSlot = imath_max(imath_min(firstSlot, 89), 0);
-
-	// Change the prompt and special tag of each of the slot buttons
-	for (i = 0; i < MAX_SLOTS_SHOWN; i++) {
-		myButton = (menuItemButton *)guiMenu::getItem(i + 1001, myMenu);
-
-		myButton->prompt = _GM(slotTitles)[firstSlot + i];
-		if (_GM(currMenuIsSave) || _GM(slotInUse)[firstSlot + i]) {
-			myButton->itemFlags = menuItemButton::BTN_STATE_NORM;
-		} else {
-			myButton->itemFlags = menuItemButton::BTN_STATE_GREY;
-		}
-
-		myButton->specialTag = firstSlot + i + 1;
-		guiMenu::itemRefresh(myButton, i + 1001, myMenu);
-	}
-}
-
 //-----------------------------  MENU DIALOG FUNCTIONS    ---------------------------------//
 
 bool guiMenu::initialize(RGB8 *myPalette) {
@@ -1891,7 +1794,7 @@ bool menuItemVSlider::handler(menuItemVSlider *myItem, int32 eventType, int32 ev
 		}
 		redrawItem = true;
 		if (!_GM(currMenuIsSave)) {
-			UpdateThumbNails(_GM(firstSlotIndex), (guiMenu *)myItem->myMenu);
+			SaveLoadMenuBase::updateThumbnails(_GM(firstSlotIndex), (guiMenu *)myItem->myMenu);
 		}
 		break;
 
diff --git a/engines/m4/gui/gui_menu_items.h b/engines/m4/gui/gui_menu_items.h
index 1c7fc33bed4..8d2917ca2f4 100644
--- a/engines/m4/gui/gui_menu_items.h
+++ b/engines/m4/gui/gui_menu_items.h
@@ -131,23 +131,54 @@ enum options_menu_sprites {
 	OM_SCROLLING_OFF_BTN_NORM = 11,
 	OM_SCROLLING_OFF_BTN_OVER = 12,
 	OM_SCROLLING_OFF_BTN_PRESS = 10,
-#if 0
-	OM_SLIDER_BTN_NORM,
-	OM_SLIDER_BTN_OVER,
-	OM_SLIDER_BTN_PRESS,
 
-	OM_SLIDER_BAR,
+	OM_TOTAL_SPRITES = 14
+};
 
-	OM_DONE_BTN_GREY,
-	OM_DONE_BTN_NORM,
-	OM_DONE_BTN_OVER,
-	OM_DONE_BTN_PRESS,
+enum save_load_menu_sprites {
+	SL_DIALOG_BOX,
 
-	OM_CANCEL_BTN_NORM,
-	OM_CANCEL_BTN_OVER,
-	OM_CANCEL_BTN_PRESS,
+#ifdef TODO
+	SL_SAVE_BTN_GREY,
+	SL_SAVE_BTN_NORM,
+	SL_SAVE_BTN_OVER,
+	SL_SAVE_BTN_PRESS,
+
+	SL_LOAD_BTN_GREY,
+	SL_LOAD_BTN_NORM,
+	SL_LOAD_BTN_OVER,
+	SL_LOAD_BTN_PRESS,
+
+	SL_CANCEL_BTN_NORM,
+	SL_CANCEL_BTN_OVER,
+	SL_CANCEL_BTN_PRESS,
+
+	SL_UP_BTN_GREY,
+	SL_UP_BTN_NORM,
+	SL_UP_BTN_OVER,
+	SL_UP_BTN_PRESS,
+
+	SL_DOWN_BTN_GREY,
+	SL_DOWN_BTN_NORM,
+	SL_DOWN_BTN_OVER,
+	SL_DOWN_BTN_PRESS,
+
+	SL_SAVE_LABEL,
+	SL_LOAD_LABEL,
+
+	SL_SLIDER_BTN_NORM,
+	SL_SLIDER_BTN_OVER,
+	SL_SLIDER_BTN_PRESS,
+
+	SL_LINE_NORM,
+	SL_LINE_OVER,
+	SL_LINE_PRESS,
+
+	SL_SCROLL_BAR,
 #endif
-	OM_TOTAL_SPRITES = 14
+	SL_EMPTY_THUMB = 25,
+
+	SL_TOTAL_SPRITES = 26
 };
 
 } // namespace GUI
@@ -439,10 +470,6 @@ struct MenuGlobals {
 };
 
 extern void gui_DrawSprite(Sprite *mySprite, Buffer *myBuff, int32 x, int32 y);
-extern bool LoadThumbNail(int32 slotNum);
-extern void UnloadThumbNail(int32 slotNum);
-extern void UpdateThumbNails(int32 firstSlot, guiMenu *myMenu);
-extern void SetFirstSlot(int32 firstSlot, guiMenu *myMenu);
 
 //======================================
 //
diff --git a/engines/m4/module.mk b/engines/m4/module.mk
index 1a2e482cd78..8a79b3545c5 100644
--- a/engines/m4/module.mk
+++ b/engines/m4/module.mk
@@ -48,6 +48,7 @@ MODULE_OBJS = \
 	graphics/gr_surface.o \
 	graphics/krn_pal.o \
 	graphics/rend.o \
+	gui/game_menu.o \
 	gui/gui_buffer.o \
 	gui/gui_cheapo.o \
 	gui/gui_dialog.o \
diff --git a/engines/m4/riddle/gui/game_menu.cpp b/engines/m4/riddle/gui/game_menu.cpp
index 54519378947..b369a474965 100644
--- a/engines/m4/riddle/gui/game_menu.cpp
+++ b/engines/m4/riddle/gui/game_menu.cpp
@@ -197,11 +197,21 @@ void GameMenu::cbOptions(void *, void *) {
 }
 
 void GameMenu::cbSave(void *, void *) {
-	// TODO
+	destroyGameMenu();
+	guiMenu::shutdown(true);
+	_GM(buttonClosesDialog) = true;
+
+	// Create the save game menu
+	g_engine->showSaveScreen();
 }
 
 void GameMenu::cbLoad(void *, void *) {
-	// TODO
+	destroyGameMenu();
+	guiMenu::shutdown(true);
+	_GM(buttonClosesDialog) = true;
+
+	// Create the save game menu
+	g_engine->showLoadScreen(M4Engine::kLoadFromGameDialog);
 }
 
 /*-------------------- OPTIONS MENU --------------------*/
@@ -294,6 +304,248 @@ void OptionsMenu::cbSetMidi(M4::GUI::menuItemHSlider *myItem, M4::GUI::guiMenu *
 	midi_set_overall_volume(myItem->percent);
 }
 
+/*------------------- SAVE/LOAD METHODS ------------------*/
+
+#define SAVE_LOAD_MENU_X 42
+#define SAVE_LOAD_MENU_Y 155
+
+#define SL_TAG_SAVE_LABEL	1
+#define SL_TAG_LOAD_LABEL	2
+#define SL_LABEL_X		111
+#define SL_LABEL_Y		2
+#define SL_LABEL_W		110
+#define SL_LABEL_H		17
+
+#define SL_TAG_THUMBNAIL	5
+#define SL_THUMBNAIL_X		333
+#define SL_THUMBNAIL_Y		5
+#define SL_THUMBNAIL_W		213
+#define SL_THUMBNAIL_H		160
+
+#define SL_TAG_SAVE		100
+#define SL_TAG_LOAD		101
+#define SL_SAVELOAD_X	10
+#define SL_SAVELOAD_Y	74
+#define SL_SAVELOAD_W	26
+#define SL_SAVELOAD_H	26
+
+#define SL_TAG_CANCEL	102
+#define SL_CANCEL_X		10
+#define SL_CANCEL_Y		122
+#define SL_CANCEL_W		26
+#define SL_CANCEL_H		26
+
+#define SL_TAG_VSLIDER	103
+#define SL_SLIDER_X		305
+#define SL_SLIDER_Y		21
+#define SL_SLIDER_W		20
+#define SL_SLIDER_H		140
+
+#define SL_SCROLL_FIELD_X	46
+#define SL_SCROLL_FIELD_Y	21
+#define SL_SCROLL_LINE_W	258
+#define SL_SCROLL_LINE_H	14
+#define SL_SCROLL_FIELD_W	257
+#define SL_SCROLL_FIELD_H	139
+
+void SaveLoadMenu::show(RGB8 *myPalette, bool saveMenu) {
+	ItemHandlerFunction	i_handler;
+	bool buttonGreyed;
+
+	if (!_G(menuSystemInitialized))
+		guiMenu::initialize(myPalette);
+
+	// Keep the memory tidy
+	PurgeMem();
+	CompactMem();
+
+	// Load in the game menu sprites
+	if (!guiMenu::loadSprites("slmenu", SL_TOTAL_SPRITES)) {
+		return;
+	}
+
+	// Initialize some global vars
+	_GM(firstSlotIndex) = 0;
+	_GM(slotSelected) = -1;
+	_GM(saveLoadThumbNail) = nullptr;
+	_GM(thumbIndex) = 100;
+	_GM(currMenuIsSave) = saveMenu;
+
+	_GM(slMenu) = guiMenu::create(_GM(menuSprites)[SL_DIALOG_BOX], SAVE_LOAD_MENU_X, SAVE_LOAD_MENU_Y,
+		MENU_DEPTH | SF_GET_ALL | SF_BLOCK_ALL | SF_IMMOVABLE);
+	if (!_GM(slMenu)) {
+		return;
+	}
+
+	if (_GM(currMenuIsSave)) {
+		menuItemMsg::msgAdd(_GM(slMenu), SL_TAG_SAVE_LABEL, SL_LABEL_X, SL_LABEL_Y,
+			SL_LABEL_W, SL_LABEL_H);
+		menuItemButton::add(_GM(slMenu), SL_TAG_SAVE, SL_SAVELOAD_X, SL_SAVELOAD_Y,
+			SL_SAVELOAD_W, SL_SAVELOAD_H, (CALLBACK)cbSave,
+			menuItemButton::BTN_TYPE_SL_SAVE, true);
+	} else {
+		menuItemMsg::msgAdd(_GM(slMenu), SL_TAG_LOAD_LABEL, SL_LABEL_X, SL_LABEL_Y,
+			SL_LABEL_W, SL_LABEL_H);
+		menuItemButton::add(_GM(slMenu), SL_TAG_LOAD, SL_SAVELOAD_X, SL_SAVELOAD_Y,
+			SL_SAVELOAD_W, SL_SAVELOAD_H, (CALLBACK)cbSave,
+			menuItemButton::BTN_TYPE_SL_SAVE, true);
+	}
+
+	menuItemButton::add(_GM(slMenu), SL_TAG_CANCEL, SL_CANCEL_X, SL_CANCEL_Y, SL_CANCEL_W, SL_CANCEL_H,
+		(CALLBACK)cbCancel, menuItemButton::BTN_TYPE_SL_CANCEL);
+
+	menuItemVSlider::add(_GM(slMenu), SL_TAG_VSLIDER, SL_SLIDER_X, SL_SLIDER_Y, SL_SLIDER_W, SL_SLIDER_H,
+		0, (CALLBACK)cbVSlider);
+
+	initializeSlotTables();
+
+	if (_GM(currMenuIsSave)) {
+		buttonGreyed = false;
+		i_handler = (ItemHandlerFunction)menuItemButton::handler;
+	} else {
+		buttonGreyed = true;
+		i_handler = (ItemHandlerFunction)load_Handler;
+	}
+
+	for (int32 i = 0; i < MAX_SLOTS_SHOWN; i++) {
+		menuItemButton::add(_GM(slMenu), 1001 + i,
+			SL_SCROLL_FIELD_X, SL_SCROLL_FIELD_Y + i * SL_SCROLL_LINE_H,
+			SL_SCROLL_LINE_W, SL_SCROLL_LINE_H,
+			(CALLBACK)cbSlot, menuItemButton::BTN_TYPE_SL_TEXT,
+			buttonGreyed && (!_GM(slotInUse)[i]), true,
+			_GM(slotTitles)[i], i_handler);
+	}
+
+	if (_GM(currMenuIsSave)) {
+		// Create thumbnails. One in the original game format for displaying,
+		// and the other in the ScummVM format for actually using in the save files
+		_GM(saveLoadThumbNail) = menu_CreateThumbnail(&_GM(sizeofThumbData));
+		_GM(_thumbnail).free();
+		Graphics::createThumbnail(_GM(_thumbnail));
+
+	} else {
+		updateThumbnails(0, _GM(slMenu));
+		_GM(saveLoadThumbNail) = _GM(menuSprites)[SL_EMPTY_THUMB];
+	}
+
+	menuItemMsg::msgAdd(_GM(slMenu), SL_TAG_THUMBNAIL, SL_THUMBNAIL_X, SL_THUMBNAIL_Y, SL_THUMBNAIL_W, SL_THUMBNAIL_H, false);
+
+	if (_GM(currMenuIsSave)) {
+		//<return> - if a slot has been selected, saves the game
+		//<esc> - cancels and returns to the game menu
+		guiMenu::configure(_GM(slMenu), (CALLBACK)cbSave, (CALLBACK)cbCancel);
+	} else {
+		//<return> - if a slot has been selected, loads the selected game
+		//<esc> - cancels and returns to the game menu
+		guiMenu::configure(_GM(slMenu), (CALLBACK)cbLoad, (CALLBACK)cbCancel);
+	}
+
+	vmng_screen_show((void *)_GM(slMenu));
+	LockMouseSprite(0);
+}
+
+void SaveLoadMenu::destroyMenu(bool saveMenu) {
+	int32 i;
+
+	if (!_GM(slMenu)) {
+		return;
+	}
+
+	// Determine whether the screen was the SAVE or the LOAD menu
+	if (saveMenu) {
+		// If SAVE, there should be a thumbnail to unload
+		if (_GM(saveLoadThumbNail)) {
+			DisposeHandle(_GM(saveLoadThumbNail)->sourceHandle);
+			mem_free(_GM(saveLoadThumbNail));
+			_GM(saveLoadThumbNail) = nullptr;
+		}
+	} else {
+		// Else there may be up to 10 somewhere in the list to be unloaded
+		for (i = 0; i < MAX_SLOTS; i++) {
+			unloadThumbnail(i);
+		}
+		_GM(saveLoadThumbNail) = nullptr;
+	}
+
+	// Destroy the screen
+	vmng_screen_dispose(_GM(slMenu));
+	guiMenu::destroy(_GM(slMenu));
+
+	// Unload the save/load menu sprites
+	guiMenu::unloadSprites();
+}
+
+bool SaveLoadMenu::load_Handler(M4::GUI::menuItemButton *myItem, int32 eventType, int32 event, int32 x, int32 y, void **currItem) {
+	bool handled;
+
+	// Handle the event just like any other button
+	handled = menuItemButton::handler(myItem, eventType, event, x, y, currItem);
+
+	// If we've selected a slot, we want the thumbNail to remain on the menu permanently
+	if (_GM(slotSelected) >= 0) {
+		return handled;
+	}
+
+	// But if the event moved the mouse, we want to display the correct thumbNail;
+	if ((eventType == EVENT_MOUSE) && ((event == _ME_move) || (event == _ME_L_drag) || (event == _ME_L_release) ||
+		(event == _ME_doubleclick_drag) || (event == _ME_doubleclick_release))) {
+
+		// Get the button
+		if (!myItem)
+			return handled;
+
+		// This determines that we are over the button
+		if ((myItem->itemFlags == menuItemButton::BTN_STATE_OVER) || (myItem->itemFlags == menuItemButton::BTN_STATE_PRESS)) {
+			// See if the current _GM(saveLoadThumbNail) is pointing to the correct sprite
+			if (_GM(saveLoadThumbNail) != _GM(thumbNails)[myItem->specialTag - 1]) {
+				_GM(saveLoadThumbNail) = _GM(thumbNails)[myItem->specialTag - 1];
+				guiMenu::itemRefresh(nullptr, SL_TAG_THUMBNAIL, (guiMenu *)myItem->myMenu);
+			}
+		}
+
+		// Else we must determine whether the thumbnail needs to be replaced with the empty thumbnail.
+		else {
+
+			// If the mouse has moved outside of the entire range of all 10 buttons,
+			//or it is over a button which is not hilited it is to be removed.
+			if (menuItem::cursorInsideItem(myItem, x, y)
+				|| (x < SL_SCROLL_FIELD_X)
+				|| (x > SL_SCROLL_FIELD_X + SL_SCROLL_FIELD_W)
+				|| (y < SL_SCROLL_FIELD_Y)
+				|| (y > SL_SCROLL_FIELD_Y + SL_SCROLL_FIELD_H)) {
+
+				// Remove the thumbnail
+				if (_GM(saveLoadThumbNail)) {
+					_GM(saveLoadThumbNail) = _GM(menuSprites)[SL_EMPTY_THUMB];
+					guiMenu::itemRefresh(nullptr, SL_TAG_THUMBNAIL, (guiMenu *)myItem->myMenu);
+				}
+			}
+		}
+	}
+
+	return handled;
+}
+
+void SaveLoadMenu::cbCancel(void *, void *) {
+
+}
+
+void SaveLoadMenu::cbSave(void *, void *) {
+
+}
+
+void SaveLoadMenu::cbLoad(void *, void *) {
+
+}
+
+void SaveLoadMenu::cbSlot(void *, void *) {
+
+}
+
+void SaveLoadMenu::cbVSlider(void *, void *) {
+
+}
+
 /*-------------------- ACCESS METHODS --------------------*/
 
 void CreateGameMenu(RGB8 *myPalette) {
@@ -305,6 +557,43 @@ void CreateGameMenu(RGB8 *myPalette) {
 	GameMenu::show(myPalette);
 }
 
+
+void CreateF2SaveMenu(RGB8 *myPalette) {
+	if ((!player_commands_allowed()) || (!INTERFACE_VISIBLE) ||
+		_G(pal_fade_in_progress) || _G(menuSystemInitialized)) {
+		return;
+	}
+
+	_GM(saveLoadFromHotkey) = true;
+	_GM(gameMenuFromMain) = false;
+	SaveLoadMenu::show(myPalette, true);
+}
+
+void CreateLoadMenu(RGB8 *myPalette) {
+	_GM(saveLoadFromHotkey) = false;
+	SaveLoadMenu::show(myPalette, false);
+}
+
+void CreateLoadMenuFromMain(RGB8 *myPalette) {
+	if (_G(pal_fade_in_progress) || _G(menuSystemInitialized)) {
+		return;
+	}
+
+	_GM(saveLoadFromHotkey) = true;
+	_GM(gameMenuFromMain) = true;
+	SaveLoadMenu::show(myPalette, false);
+}
+
+void CreateF3LoadMenu(RGB8 *myPalette) {
+	if ((!player_commands_allowed()) || (!INTERFACE_VISIBLE) ||
+		_G(pal_fade_in_progress) || _G(menuSystemInitialized)) {
+		return;
+	}
+
+	_GM(saveLoadFromHotkey) = true;
+	_GM(gameMenuFromMain) = false;
+	SaveLoadMenu::show(myPalette, false);
+}
 } // namespace GUI
 } // namespace Riddle
 } // namespace M4
diff --git a/engines/m4/riddle/gui/game_menu.h b/engines/m4/riddle/gui/game_menu.h
index 64d1af006d9..72c88f0aaa2 100644
--- a/engines/m4/riddle/gui/game_menu.h
+++ b/engines/m4/riddle/gui/game_menu.h
@@ -24,6 +24,7 @@
 #define M4_RIDDLE_GUI_GAME_MENU_H
 
 #include "m4/gui/gui_menu_items.h"
+#include "m4/gui/game_menu.h"
 
 namespace M4 {
 namespace Riddle {
@@ -55,7 +56,27 @@ public:
 	static void show();
 };
 
+class SaveLoadMenu : public M4::GUI::SaveLoadMenuBase {
+private:
+	static void destroyMenu(bool saveMenu);
+	static bool load_Handler(M4::GUI::menuItemButton *myItem, int32 eventType,
+		int32 event, int32 x, int32 y, void **currItem);
+	static void cbCancel(void *, void *);
+	static void cbSave(void *, void *);
+	static void cbLoad(void *, void *);
+	static void cbSlot(void *, void *);
+	static void cbVSlider(void *, void *);
+public:
+	static void show(RGB8 *myPalette, bool saveMenu);
+};
+
 extern void CreateGameMenu(RGB8 *myPalette);
+extern void CreateF2SaveMenu(RGB8 *myPalette);
+extern void CreateLoadMenu(RGB8 *myPalette);
+extern void CreateF3LoadMenu(RGB8 *myPalette);
+// Routines used by the main menu
+void CreateLoadMenuFromMain(RGB8 *myPalette);
+void CreateGameMenuFromMain(RGB8 *myPalette);
 
 } // namespace GUI
 } // namespace Riddle
diff --git a/engines/m4/riddle/riddle.cpp b/engines/m4/riddle/riddle.cpp
index 779fa6e8fa2..a948f50d9ca 100644
--- a/engines/m4/riddle/riddle.cpp
+++ b/engines/m4/riddle/riddle.cpp
@@ -20,6 +20,7 @@
  */
 
 #include "common/debug.h"
+#include "m4/riddle/gui/game_menu.h"
 #include "m4/riddle/riddle.h"
 #include "m4/riddle/triggers.h"
 #include "m4/riddle/console.h"
@@ -64,6 +65,32 @@ void RiddleEngine::syncFlags(Common::Serializer &s) {
 	g_vars->_flags.sync(s);
 }
 
+void RiddleEngine::showSaveScreen() {
+	if (_useOriginalSaveLoad) {
+		GUI::CreateF2SaveMenu(_G(master_palette));
+	} else {
+		M4Engine::showSaveScreen();
+	}
+}
+
+void RiddleEngine::showLoadScreen(LoadDialogSource source) {
+	if (_useOriginalSaveLoad) {
+		switch (source) {
+		case kLoadFromMainMenu:
+			GUI::CreateLoadMenuFromMain(_G(master_palette));
+			break;
+		case kLoadFromGameDialog:
+			GUI::CreateLoadMenu(_G(master_palette));
+			break;
+		case kLoadFromHotkey:
+			GUI::CreateF3LoadMenu(_G(master_palette));
+			break;
+		}
+	} else {
+		M4Engine::showLoadScreen(source);
+	}
+}
+
 void RiddleEngine::global_daemon() {
 	_G(i_just_hyperwalked) = false;
 
diff --git a/engines/m4/riddle/riddle.h b/engines/m4/riddle/riddle.h
index 7d39d8b7a4b..70b6e23c2b4 100644
--- a/engines/m4/riddle/riddle.h
+++ b/engines/m4/riddle/riddle.h
@@ -88,6 +88,8 @@ public:
 	void showEngineInfo() override;
 
 	void syncFlags(Common::Serializer &s) override;
+	void showSaveScreen() override;
+	void showLoadScreen(LoadDialogSource source) override;
 
 	void global_daemon() override;
 	void global_parser() override;




More information about the Scummvm-git-logs mailing list