#include "ppsspp_config.h"
#include <algorithm>
#include <string>
#include <sstream>
#include <unordered_map>

#include "resource.h"

#include "Common/GPU/OpenGL/GLFeatures.h"

#include "Common/Data/Text/I18n.h"
#include "Common/Data/Encoding/Utf8.h"
#include "Common/System/System.h"
#include "Common/System/OSD.h"
#include "Common/System/Request.h"
#include "Common/System/Display.h"
#include "Common/File/FileUtil.h"
#include "Common/Log.h"
#include "Common/Log/LogManager.h"
#include "Common/Log/ConsoleListener.h"
#include "Common/StringUtils.h"
#if PPSSPP_API(ANY_GL)
#include "GPU/GLES/TextureCacheGLES.h"
#include "GPU/GLES/FramebufferManagerGLES.h"
#endif
#include "GPU/Common/PostShader.h"
#include "GPU/Common/TextureScalerCommon.h"

#include "Core/Config.h"
#include "Core/ConfigValues.h"
#include "Core/FileSystems/MetaFileSystem.h"
#include "Core/KeyMap.h"
#include "Core/Screenshot.h"
#include "Windows/MainWindowMenu.h"
#include "Windows/MainWindow.h"
#include "Windows/W32Util/DialogManager.h"
#include "Windows/W32Util/ShellUtil.h"
#include "Windows/W32Util/Misc.h"
#include "Windows/InputBox.h"
#include "Windows/main.h"
#include "Windows/W32Util/DarkMode.h"

#include "Core/HLE/sceUmd.h"
#include "Core/HLE/sceNet.h"
#include "Core/SaveState.h"
#include "Core/Core.h"
#include "Core/RetroAchievements.h"

#ifdef RC_CLIENT_SUPPORTS_RAINTEGRATION
#include "ext/rcheevos/include/rc_client_raintegration.h"
#endif

namespace MainWindow {
	extern bool noFocusPause;
	std::vector<HMENU> g_topLevelMenus;
	HMENU g_hMenuBackend;

	static std::unordered_map<int, std::string> initialMenuKeys;
	static std::vector<std::string> availableShaders;
	static std::string menuLanguageID = "";
	static int menuKeymapGeneration = -1;
	std::vector<ShaderInfo> menuShaderInfo;

	LRESULT CALLBACK AboutDlgProc(HWND, UINT, WPARAM, LPARAM);

	void SetIngameMenuItemStates(HMENU menu, const GlobalUIState state) {
		const bool menuEnableBool = state == UISTATE_INGAME || state == UISTATE_EXCEPTION;

		bool loadStateEnableBool = menuEnableBool;
		bool saveStateEnableBool = menuEnableBool;
		if (Achievements::HardcoreModeActive()) {
			loadStateEnableBool = false;
			if (!g_Config.bAchievementsSaveStateInHardcoreMode) {
				saveStateEnableBool = false;
			}
		}

		if (!NetworkAllowSaveState()) {
			loadStateEnableBool = false;
			saveStateEnableBool = false;
		}

		const UINT menuEnable = menuEnableBool ? MF_ENABLED : MF_GRAYED;
		const UINT loadStateEnable = loadStateEnableBool ? MF_ENABLED : MF_GRAYED;
		const UINT saveStateEnable = saveStateEnableBool ? MF_ENABLED : MF_GRAYED;
		const UINT menuInGameEnable = state == UISTATE_INGAME ? MF_ENABLED : MF_GRAYED;
		const UINT umdSwitchEnable = state == UISTATE_INGAME && getUMDReplacePermit() ? MF_ENABLED : MF_GRAYED;
		const UINT debugEnable = !Achievements::HardcoreModeActive() ? MF_ENABLED : MF_GRAYED;
		const UINT debugIngameEnable = (state == UISTATE_INGAME && !Achievements::HardcoreModeActive()) ? MF_ENABLED : MF_GRAYED;

		EnableMenuItem(menu, ID_FILE_SAVESTATE_SLOT_MENU, saveStateEnable);
		EnableMenuItem(menu, ID_FILE_SAVESTATEFILE, saveStateEnable);
		EnableMenuItem(menu, ID_FILE_LOADSTATEFILE, loadStateEnable);
		EnableMenuItem(menu, ID_FILE_QUICKSAVESTATE, saveStateEnable);
		EnableMenuItem(menu, ID_FILE_QUICKLOADSTATE, loadStateEnable);
		EnableMenuItem(menu, ID_EMULATION_PAUSE, menuEnable);
		EnableMenuItem(menu, ID_EMULATION_STOP, menuEnable);
		EnableMenuItem(menu, ID_EMULATION_RESET, menuEnable);
		EnableMenuItem(menu, ID_EMULATION_SWITCH_UMD, umdSwitchEnable);
		EnableMenuItem(menu, ID_EMULATION_CHAT, g_Config.bEnableNetworkChat ? menuInGameEnable : MF_GRAYED);
		EnableMenuItem(menu, ID_TOGGLE_BREAK, debugIngameEnable);
		EnableMenuItem(menu, ID_DEBUG_LOADMAPFILE, debugIngameEnable);
		EnableMenuItem(menu, ID_DEBUG_SAVEMAPFILE, debugIngameEnable);
		EnableMenuItem(menu, ID_DEBUG_LOADSYMFILE, debugIngameEnable);
		EnableMenuItem(menu, ID_DEBUG_SAVESYMFILE, debugIngameEnable);
		EnableMenuItem(menu, ID_DEBUG_RESETSYMBOLTABLE, debugIngameEnable);
		EnableMenuItem(menu, ID_DEBUG_SHOWDEBUGSTATISTICS, debugEnable);
		EnableMenuItem(menu, ID_DEBUG_EXTRACTFILE, menuEnable);
		EnableMenuItem(menu, ID_DEBUG_MEMORYBASE, menuInGameEnable);
		EnableMenuItem(menu, ID_DEBUG_DISASSEMBLY, debugEnable);
		EnableMenuItem(menu, ID_DEBUG_MEMORYVIEW, debugEnable);
		EnableMenuItem(menu, ID_DEBUG_GEDEBUGGER, debugEnable);

		// While playing, this pop up doesn't work - and probably doesn't make sense.
		EnableMenuItem(menu, ID_OPTIONS_LANGUAGE, state == UISTATE_INGAME ? MF_GRAYED : MF_ENABLED);
	}

	static HMENU GetSubmenuById(HMENU menu, int menuID) {
		MENUITEMINFO menuInfo{ sizeof(MENUITEMINFO), MIIM_SUBMENU };
		if (GetMenuItemInfo(menu, menuID, MF_BYCOMMAND, &menuInfo) != FALSE) {
			return menuInfo.hSubMenu;
		}
		return nullptr;
	}

	static void ClearSubMenu(HMENU menu) {
		const int count = GetMenuItemCount(menu);
		for (int i = count - 1; i >= 0; --i) {
			RemoveMenu(menu, i, MF_BYPOSITION);
		}
	}

	static std::string GetMenuItemText(HMENU menu, int menuID) {
		MENUITEMINFO menuInfo{ sizeof(menuInfo), MIIM_STRING };
		std::string retVal;
		if (GetMenuItemInfo(menu, menuID, MF_BYCOMMAND, &menuInfo) != FALSE) {
			menuInfo.cch++;  // account for null terminator
			std::vector<wchar_t> buffer(menuInfo.cch);
			menuInfo.dwTypeData = buffer.data();
			GetMenuItemInfo(menu, menuID, MF_BYCOMMAND, &menuInfo);
			retVal = ConvertWStringToUTF8(menuInfo.dwTypeData);  // note, this is buffer.
		}

		return retVal;
	}

	const std::string &GetMenuItemInitialText(HMENU menu, const int menuID) {
		if (initialMenuKeys.find(menuID) == initialMenuKeys.end()) {
			initialMenuKeys[menuID] = GetMenuItemText(menu, menuID);
		}
		return initialMenuKeys[menuID];
	}

	void MainMenuInit(HWND hwndMain, HMENU hMenu) {
		MENUINFO info;
		ZeroMemory(&info, sizeof(MENUINFO));
		info.cbSize = sizeof(MENUINFO);
		info.cyMax = 0;
		info.dwStyle = MNS_CHECKORBMP;
		info.fMask = MIM_STYLE;
		g_topLevelMenus.clear();
		for (int i = 0; i < GetMenuItemCount(hMenu); i++) {
			HMENU subMenu = GetSubMenu(hMenu, i);
			SetMenuInfo(subMenu, &info);
			g_topLevelMenus.push_back(subMenu);
		}

		// Always translate first: translating resets the menu.
		TranslateMenus(hwndMain, hMenu);
		// Don't need to update here, happens later.

		HMENU helpMenu = GetSubmenuById(hMenu, ID_HELP_MENU);
		if (System_GetPropertyBool(SYSPROP_APP_GOLD)) {
			RemoveMenu(helpMenu, ID_HELP_BUYGOLD, MF_BYCOMMAND);
		}

		HMENU hMenuOptions = GetSubmenuById(hMenu, ID_OPTIONS_MENU);
		g_hMenuBackend = GetSubmenuById(hMenuOptions, ID_OPTIONS_BACKEND_MENU);
	}

	static void TranslateMenuItem(const HMENU hMenu, const int menuID, const std::wstring& accelerator = L"", const char *key = nullptr) {
		auto des = GetI18NCategory(I18NCat::DESKTOPUI);

		std::wstring translated;
		if (key == nullptr || !strcmp(key, "")) {
			std::string_view initialText = GetMenuItemInitialText(hMenu, menuID);
			if (!initialText.empty()) {
				translated = ConvertUTF8ToWString(des->T(initialText));
			}
		} else {
			translated = ConvertUTF8ToWString(des->T(key));
		}
		translated.append(accelerator);

		ModifyMenu(hMenu, menuID, MF_STRING | MF_BYCOMMAND, menuID, translated.c_str());
	}

	void DoTranslateMenus(HWND hWnd, HMENU menu) {
		auto useDefHotkey = [](int virtKey) {
			return !KeyMap::PspButtonHasMappings(virtKey);
		};

		TranslateMenuItem(menu, ID_FILE_MENU);
		TranslateMenuItem(menu, ID_EMULATION_MENU);
		TranslateMenuItem(menu, ID_DEBUG_MENU);
		TranslateMenuItem(menu, ID_OPTIONS_MENU);
		TranslateMenuItem(menu, ID_HELP_MENU);

		// File menu
		TranslateMenuItem(menu, ID_FILE_LOAD);
		TranslateMenuItem(menu, ID_FILE_LOAD_DIR);
		TranslateMenuItem(menu, ID_FILE_LOAD_MEMSTICK);
		TranslateMenuItem(menu, ID_FILE_OPEN_NEW_INSTANCE);
		TranslateMenuItem(menu, ID_FILE_MEMSTICK);
		TranslateMenuItem(menu, ID_FILE_SAVESTATE_SLOT_MENU, useDefHotkey(VIRTKEY_NEXT_SLOT) ? L"\tF3" : L"");
		TranslateMenuItem(menu, ID_FILE_QUICKLOADSTATE, useDefHotkey(VIRTKEY_LOAD_STATE) ? L"\tF4" : L"");
		TranslateMenuItem(menu, ID_FILE_QUICKSAVESTATE, useDefHotkey(VIRTKEY_SAVE_STATE) ? L"\tF2" : L"");
		TranslateMenuItem(menu, ID_FILE_LOADSTATEFILE);
		TranslateMenuItem(menu, ID_FILE_SAVESTATEFILE);
		TranslateMenuItem(menu, ID_FILE_RECORD_MENU);
		TranslateMenuItem(menu, ID_FILE_EXIT, L"\tAlt+F4");

		// Emulation menu
		TranslateMenuItem(menu, ID_EMULATION_PAUSE);
		TranslateMenuItem(menu, ID_EMULATION_STOP, g_Config.bSystemControls ? L"\tCtrl+W" : L"");
		TranslateMenuItem(menu, ID_EMULATION_RESET, g_Config.bSystemControls ? L"\tCtrl+B" : L"");
		TranslateMenuItem(menu, ID_EMULATION_SWITCH_UMD, g_Config.bSystemControls ? L"\tCtrl+U" : L"");
		TranslateMenuItem(menu, ID_EMULATION_ROTATION_MENU);
		TranslateMenuItem(menu, ID_EMULATION_ROTATION_H);
		TranslateMenuItem(menu, ID_EMULATION_ROTATION_V);
		TranslateMenuItem(menu, ID_EMULATION_ROTATION_H_R);
		TranslateMenuItem(menu, ID_EMULATION_ROTATION_V_R);

		// Debug menu
		TranslateMenuItem(menu, ID_TOGGLE_BREAK, g_Config.bSystemControls ? L"\tF8" : L"", "Break");
		TranslateMenuItem(menu, ID_DEBUG_BREAKONLOAD);
		TranslateMenuItem(menu, ID_DEBUG_IGNOREILLEGALREADS);
		TranslateMenuItem(menu, ID_DEBUG_LOADMAPFILE);
		TranslateMenuItem(menu, ID_DEBUG_SAVEMAPFILE);
		TranslateMenuItem(menu, ID_DEBUG_LOADSYMFILE);
		TranslateMenuItem(menu, ID_DEBUG_SAVESYMFILE);
		TranslateMenuItem(menu, ID_DEBUG_RESETSYMBOLTABLE);
		TranslateMenuItem(menu, ID_DEBUG_TAKESCREENSHOT);  // TODO: Automatically show the bound key.
		TranslateMenuItem(menu, ID_DEBUG_SAVEFRAMEDUMP);
		TranslateMenuItem(menu, ID_DEBUG_SHOWDEBUGSTATISTICS);
		TranslateMenuItem(menu, ID_DEBUG_RESTARTGRAPHICS);
		TranslateMenuItem(menu, ID_DEBUG_DISASSEMBLY, g_Config.bSystemControls ? L"\tCtrl+D" : L"");
		TranslateMenuItem(menu, ID_DEBUG_GEDEBUGGER, g_Config.bSystemControls ? L"\tCtrl+G" : L"");
		TranslateMenuItem(menu, ID_DEBUG_EXTRACTFILE);
		TranslateMenuItem(menu, ID_DEBUG_LOG, g_Config.bSystemControls ? L"\tCtrl+L" : L"");
		TranslateMenuItem(menu, ID_DEBUG_MEMORYBASE);
		TranslateMenuItem(menu, ID_DEBUG_MEMORYVIEW, g_Config.bSystemControls ? L"\tCtrl+M" : L"");

		// Options menu
		TranslateMenuItem(menu, ID_OPTIONS_LANGUAGE);
		TranslateMenuItem(menu, ID_OPTIONS_TOPMOST);
		TranslateMenuItem(menu, ID_OPTIONS_PAUSE_FOCUS);
		TranslateMenuItem(menu, ID_OPTIONS_IGNOREWINKEY);
		TranslateMenuItem(menu, ID_OPTIONS_MORE_SETTINGS);
		TranslateMenuItem(menu, ID_OPTIONS_CONTROLS);
		TranslateMenuItem(menu, ID_OPTIONS_DISPLAY_LAYOUT);

		// Movie menu
		TranslateMenuItem(menu, ID_FILE_DUMPFRAMES);
		TranslateMenuItem(menu, ID_FILE_USEFFV1);
		TranslateMenuItem(menu, ID_FILE_DUMP_VIDEO_OUTPUT);
		TranslateMenuItem(menu, ID_FILE_DUMPAUDIO);

		// Skip display multipliers x1-x10
		TranslateMenuItem(menu, ID_OPTIONS_FULLSCREEN, g_Config.bSystemControls ? L"\tAlt+Return, F11" : L"");
		TranslateMenuItem(menu, ID_OPTIONS_VSYNC);
		TranslateMenuItem(menu, ID_OPTIONS_SCREEN_MENU, g_Config.bSystemControls ? L"\tCtrl+1" : L"");
		TranslateMenuItem(menu, ID_OPTIONS_SCREENAUTO);
		// Skip rendering resolution 2x-5x..
		TranslateMenuItem(menu, ID_OPTIONS_WINDOW_MENU);
		// Skip window size 1x-4x..
		TranslateMenuItem(menu, ID_OPTIONS_BACKEND_MENU);
		TranslateMenuItem(menu, ID_OPTIONS_DIRECT3D11);
		TranslateMenuItem(menu, ID_OPTIONS_OPENGL);
		TranslateMenuItem(menu, ID_OPTIONS_VULKAN);

		TranslateMenuItem(menu, ID_OPTIONS_RENDERMODE_MENU);
		TranslateMenuItem(menu, ID_OPTIONS_SKIP_BUFFER_EFFECTS);
		TranslateMenuItem(menu, ID_OPTIONS_FRAMESKIP_MENU, g_Config.bSystemControls ? L"\tF7" : L"");
		TranslateMenuItem(menu, ID_OPTIONS_FRAMESKIP_AUTO);
		TranslateMenuItem(menu, ID_OPTIONS_FRAMESKIP_0);
		// Skip frameskipping 1-8..
		TranslateMenuItem(menu, ID_OPTIONS_TEXTUREFILTERING_MENU);
		TranslateMenuItem(menu, ID_OPTIONS_TEXTUREFILTERING_AUTO);
		TranslateMenuItem(menu, ID_OPTIONS_NEARESTFILTERING);
		TranslateMenuItem(menu, ID_OPTIONS_LINEARFILTERING);
		TranslateMenuItem(menu, ID_OPTIONS_AUTOMAXQUALITYFILTERING);
		TranslateMenuItem(menu, ID_OPTIONS_SMART2DTEXTUREFILTERING);
		TranslateMenuItem(menu, ID_OPTIONS_SCREENFILTER_MENU);
		TranslateMenuItem(menu, ID_OPTIONS_BUFLINEARFILTER);
		TranslateMenuItem(menu, ID_OPTIONS_BUFNEARESTFILTER);
		TranslateMenuItem(menu, ID_OPTIONS_TEXTURESCALING_MENU);
		TranslateMenuItem(menu, ID_TEXTURESCALING_OFF);
		// Skip texture scaling 2x-5x...
		TranslateMenuItem(menu, ID_TEXTURESCALING_XBRZ);
		TranslateMenuItem(menu, ID_TEXTURESCALING_HYBRID);
		TranslateMenuItem(menu, ID_TEXTURESCALING_BICUBIC);
		TranslateMenuItem(menu, ID_TEXTURESCALING_HYBRID_BICUBIC);
		TranslateMenuItem(menu, ID_TEXTURESCALING_DEPOSTERIZE);
		TranslateMenuItem(menu, ID_OPTIONS_HARDWARETRANSFORM);
		TranslateMenuItem(menu, ID_EMULATION_SOUND);
		TranslateMenuItem(menu, ID_EMULATION_CHEATS, g_Config.bSystemControls ? L"\tCtrl+T" : L"");
		TranslateMenuItem(menu, ID_EMULATION_CHAT, g_Config.bSystemControls ? L"\tCtrl+C" : L"");

		// Help menu: it's translated in CreateHelpMenu.
		TranslateMenuItem(menu, ID_HELP_OPENWEBSITE);
		TranslateMenuItem(menu, ID_HELP_OPENFORUM);
		TranslateMenuItem(menu, ID_HELP_BUYGOLD);
		TranslateMenuItem(menu, ID_HELP_GITHUB);
		TranslateMenuItem(menu, ID_HELP_DISCORD);
		TranslateMenuItem(menu, ID_HELP_ABOUT);
	}

	void TranslateMenus(HWND hWnd, HMENU menu) {

		const std::string curLanguageID = g_i18nrepo.LanguageID();
		if (curLanguageID != menuLanguageID || KeyMap::HasChanged(menuKeymapGeneration)) {
			DoTranslateMenus(hWnd, menu);
			menuLanguageID = curLanguageID;
		}

		// Dynamically create the save state slot selector menu.
		// TODO: In the future, maybe change it to separate save and load submenus?
		HMENU fileMenu = GetSubmenuById(menu, ID_FILE_MENU);
		HMENU saveStateSlots = GetSubmenuById(fileMenu, ID_FILE_SAVESTATE_SLOT_MENU);
		while (GetMenuItemCount(saveStateSlots) > 0) {
			RemoveMenu(saveStateSlots, 0, MF_BYPOSITION);
		}

		auto di = GetI18NCategory(I18NCat::DIALOG);
		// Add new items
		for (int i = 0; i < g_Config.iSaveStateSlotCount; ++i) {
			std::string number = StringFromFormat("%d", i + 1);
			if (i < 10) {
				// Add an accelerator for the first 10 slots.
				number = "&" + number;
			}
			std::string label = ApplySafeSubstitutions(di->T("Slot %1"), number);
			AppendMenu(
				saveStateSlots,
				MF_STRING,
				ID_FILE_SAVESTATE_SLOT_BASE + i,
				ConvertUTF8ToWString(label).c_str()
			);
		}

		DrawMenuBar(hWnd);
	}

	void BrowseAndBootDone(std::string filename);

	void BrowseAndBoot(RequesterToken token, std::string defaultPath, bool browseDirectory) {
		bool browsePauseAfter = false;
		if (GetUIState() == UISTATE_INGAME) {
			browsePauseAfter = Core_IsStepping();
			if (!browsePauseAfter)
				Core_Break(BreakReason::BreakOnBoot, 0);
		}
		auto mm = GetI18NCategory(I18NCat::MAINMENU);

		W32Util::MakeTopMost(GetHWND(), false);

		if (browseDirectory) {
			System_BrowseForFolder(token, mm->T("Load"), Path(), [](const std::string &value, int) {
				BrowseAndBootDone(value);
			});
		} else {
			System_BrowseForFile(token, mm->T("Load"), BrowseFileType::BOOTABLE, [](const std::string &value, int) {
				BrowseAndBootDone(value);
			});
		}
	}

	void BrowseAndBootDone(std::string filename) {
		if (GetUIState() == UISTATE_INGAME || GetUIState() == UISTATE_EXCEPTION || GetUIState() == UISTATE_PAUSEMENU) {
			Core_Resume();
		}
		filename = ReplaceAll(filename, "\\", "/");
		System_PostUIMessage(UIMessage::REQUEST_GAME_BOOT, filename);
		W32Util::MakeTopMost(GetHWND(), g_Config.bTopMost);
	}

	static void UmdSwitchAction(RequesterToken token) {
		auto mm = GetI18NCategory(I18NCat::MAINMENU);
		System_BrowseForFile(token, mm->T("Switch UMD"), BrowseFileType::BOOTABLE, [](const std::string &value, int) {
			// This is safe because the callback runs on the emu thread.
			__UmdReplace(Path(value));
		});
	}

	static void SaveStateActionFinished(SaveState::Status status, std::string_view message) {
		if (!message.empty() && (!g_Config.bDumpFrames || !g_Config.bDumpVideoOutput)) {
			g_OSD.Show(status == SaveState::Status::SUCCESS ? OSDType::MESSAGE_SUCCESS : OSDType::MESSAGE_ERROR, message, status == SaveState::Status::SUCCESS ? 2.0 : 5.0);
		}
		PostMessage(MainWindow::GetHWND(), WM_USER_SAVESTATE_FINISH, 0, 0);
	}

	// not static
	void setTexScalingMultiplier(int level) {
		System_RunOnMainThread([level]() {
			g_Config.iTexScalingLevel = level;
		});
		System_PostUIMessage(UIMessage::GPU_CONFIG_CHANGED);
	}

	static void setTexScalingType(int type) {
		System_RunOnMainThread([type]() {
			g_Config.iTexScalingType = type;
		});
		System_PostUIMessage(UIMessage::GPU_CONFIG_CHANGED);
	}

	static void setSkipBufferEffects(bool skip) {
		System_RunOnMainThread([skip]() {
			g_Config.bSkipBufferEffects = skip;
		});
		System_PostUIMessage(UIMessage::GPU_RENDER_RESIZED);
		System_PostUIMessage(UIMessage::GPU_CONFIG_CHANGED);
	}

	static void setFrameSkipping(int framesToSkip = -1) {
		if (framesToSkip >= FRAMESKIP_OFF)
			g_Config.iFrameSkip = framesToSkip;
		else {
			if (++g_Config.iFrameSkip > FRAMESKIP_MAX)
				g_Config.iFrameSkip = FRAMESKIP_OFF;
		}

		auto gr = GetI18NCategory(I18NCat::GRAPHICS);

		std::ostringstream messageStream;
		messageStream << gr->T("Frame Skipping") << ":" << " ";

		if (g_Config.iFrameSkip == FRAMESKIP_OFF)
			messageStream << gr->T("Off");
		else
			messageStream << g_Config.iFrameSkip;

		g_OSD.Show(OSDType::MESSAGE_INFO, messageStream.str());
	}

	static void RestartApp() {
		if (System_GetPropertyBool(SYSPROP_DEBUGGER_PRESENT)) {
			PostMessage(MainWindow::GetHWND(), WM_USER_RESTART_EMUTHREAD, 0, 0);
		} else {
			g_Config.bRestartRequired = true;
			PostMessage(MainWindow::GetHWND(), WM_USER_DESTROY, 0, 0);
		}
	}

	void MainWindowMenu_Process(HWND hWnd, WPARAM wParam) {
		std::string fn;

		auto gr = GetI18NCategory(I18NCat::GRAPHICS);

		const int wmId = LOWORD(wParam);
		// Parse the menu selections:
		switch (wmId) {
		case ID_FILE_LOAD:
			BrowseAndBoot(NON_EPHEMERAL_TOKEN, "", false);
			break;

		case ID_FILE_LOAD_DIR:
			BrowseAndBoot(NON_EPHEMERAL_TOKEN, "", true);
			break;

		case ID_FILE_LOAD_MEMSTICK:
			BrowseAndBoot(NON_EPHEMERAL_TOKEN, GetSysDirectory(DIRECTORY_GAME).ToString());
			break;

		case ID_FILE_OPEN_NEW_INSTANCE:
			W32Util::SpawnNewInstance(false);
			break;

		case ID_FILE_MEMSTICK:
			System_LaunchUrl(LaunchUrlType::LOCAL_FILE, g_Config.memStickDirectory.ToString());
			break;

		case ID_TOGGLE_BREAK:
			if (GetUIState() == UISTATE_PAUSEMENU) {
				// Causes hang (outdated comment?)
				// System_PostUIMessage(UIMessage::REQUEST_GAME_RUN, "");

				if (disasmWindow)
					SendMessage(disasmWindow->GetDlgHandle(), WM_COMMAND, IDC_STOPGO, 0);
			} else if (Core_IsStepping()) { // It is paused, then continue to run.
				if (disasmWindow)
					SendMessage(disasmWindow->GetDlgHandle(), WM_COMMAND, IDC_STOPGO, 0);
				else
					Core_Resume();
			} else {
				if (disasmWindow)
					SendMessage(disasmWindow->GetDlgHandle(), WM_COMMAND, IDC_STOPGO, 0);
				else
					Core_Break(BreakReason::DebugBreak, 0);
			}
			noFocusPause = !noFocusPause;	// If we pause, override pause on lost focus
			break;

		case ID_EMULATION_PAUSE:
			System_PostUIMessage(UIMessage::REQUEST_GAME_PAUSE);
			break;

		case ID_EMULATION_STOP:
			System_PostUIMessage(UIMessage::REQUEST_GAME_STOP);
			break;

		case ID_EMULATION_RESET:
			if (MainWindow::ConfirmAction(hWnd, true)) {
				System_PostUIMessage(UIMessage::REQUEST_GAME_RESET);
			}
			break;

		case ID_EMULATION_SWITCH_UMD:
			UmdSwitchAction(NON_EPHEMERAL_TOKEN);
			break;

		case ID_EMULATION_ROTATION_H:
		{
			DisplayLayoutConfig &displayLayoutConfig = g_Config.GetDisplayLayoutConfig(g_display.GetDeviceOrientation());
			displayLayoutConfig.iInternalScreenRotation = ROTATION_LOCKED_HORIZONTAL;
			break;
		}
		case ID_EMULATION_ROTATION_V:
		{
			DisplayLayoutConfig &displayLayoutConfig = g_Config.GetDisplayLayoutConfig(g_display.GetDeviceOrientation());
			displayLayoutConfig.iInternalScreenRotation = ROTATION_LOCKED_VERTICAL;
			break;
		}
		case ID_EMULATION_ROTATION_H_R:
		{
			DisplayLayoutConfig &displayLayoutConfig = g_Config.GetDisplayLayoutConfig(g_display.GetDeviceOrientation());
			displayLayoutConfig.iInternalScreenRotation = ROTATION_LOCKED_HORIZONTAL180;
			break;
		}
		case ID_EMULATION_ROTATION_V_R:
		{
			DisplayLayoutConfig &displayLayoutConfig = g_Config.GetDisplayLayoutConfig(g_display.GetDeviceOrientation());
			displayLayoutConfig.iInternalScreenRotation = ROTATION_LOCKED_VERTICAL180;
			break;
		}

		case ID_EMULATION_CHEATS:
			g_Config.bEnableCheats = !g_Config.bEnableCheats;
			g_OSD.ShowOnOff(gr->T("Cheats"), g_Config.bEnableCheats);
			break;

		case ID_EMULATION_CHAT:
			if (GetUIState() == UISTATE_INGAME) {
				System_PostUIMessage(UIMessage::SHOW_CHAT_SCREEN);
			}
			break;

		case ID_FILE_LOADSTATEFILE:
			if (!Achievements::WarnUserIfHardcoreModeActive(false) && !NetworkWarnUserIfOnlineAndCantSavestate()) {
				if (W32Util::BrowseForFileName(true, hWnd, L"Load state", nullptr, nullptr, L"Save States (*.ppst)\0*.ppst\0All files\0*.*\0\0", L"ppst", fn)) {
					SetCursor(LoadCursor(0, IDC_WAIT));
					SaveState::Load(Path(fn), -1, SaveStateActionFinished);
				}
			}
			break;
		case ID_FILE_SAVESTATEFILE:
			if (!Achievements::WarnUserIfHardcoreModeActive(true) && !NetworkWarnUserIfOnlineAndCantSavestate()) {
				if (W32Util::BrowseForFileName(false, hWnd, L"Save state", nullptr, L"state.ppst", L"Save States (*.ppst)\0*.ppst\0All files\0*.*\0\0", L"ppst", fn)) {
					SetCursor(LoadCursor(0, IDC_WAIT));
					SaveState::Save(Path(fn), -1, SaveStateActionFinished);
				}
			}
			break;

		case ID_FILE_SAVESTATE_NEXT_SLOT:
		{
			if (!Achievements::WarnUserIfHardcoreModeActive(true) && !NetworkWarnUserIfOnlineAndCantSavestate()) {
				SaveState::NextSlot();
				System_PostUIMessage(UIMessage::SAVESTATE_DISPLAY_SLOT);
			}
			break;
		}

		case ID_FILE_SAVESTATE_NEXT_SLOT_HC:
		{
			if (!Achievements::WarnUserIfHardcoreModeActive(true) && !NetworkWarnUserIfOnlineAndCantSavestate()) {
				// We let F3 (search next) in the imdebugger take priority, if active.
				if (!KeyMap::PspButtonHasMappings(VIRTKEY_NEXT_SLOT) && !g_Config.bShowImDebugger) {
					SaveState::NextSlot();
					System_PostUIMessage(UIMessage::SAVESTATE_DISPLAY_SLOT);
				}
			}
			break;
		}

		case ID_FILE_QUICKLOADSTATE:
			if (!Achievements::WarnUserIfHardcoreModeActive(false) && !NetworkWarnUserIfOnlineAndCantSavestate()) {
				SetCursor(LoadCursor(0, IDC_WAIT));
				SaveState::LoadSlot(SaveState::GetGamePrefix(g_paramSFO), g_Config.iCurrentStateSlot, SaveStateActionFinished);
			}
			break;

		case ID_FILE_QUICKLOADSTATE_HC:
		{
			if (!Achievements::WarnUserIfHardcoreModeActive(false) && !NetworkWarnUserIfOnlineAndCantSavestate()) {
				if (!KeyMap::PspButtonHasMappings(VIRTKEY_LOAD_STATE)) {
					SetCursor(LoadCursor(0, IDC_WAIT));
					SaveState::LoadSlot(SaveState::GetGamePrefix(g_paramSFO), g_Config.iCurrentStateSlot, SaveStateActionFinished);
				}
			}
			break;
		}
		case ID_FILE_QUICKSAVESTATE:
		{
			if (!Achievements::WarnUserIfHardcoreModeActive(true) && !NetworkWarnUserIfOnlineAndCantSavestate()) {
				SetCursor(LoadCursor(0, IDC_WAIT));
				SaveState::SaveSlot(SaveState::GetGamePrefix(g_paramSFO), g_Config.iCurrentStateSlot, SaveStateActionFinished);
			}
			break;
		}

		case ID_FILE_QUICKSAVESTATE_HC:
		{
			if (!Achievements::WarnUserIfHardcoreModeActive(true) && !NetworkWarnUserIfOnlineAndCantSavestate()) {
				if (!KeyMap::PspButtonHasMappings(VIRTKEY_SAVE_STATE))
				{
					SetCursor(LoadCursor(0, IDC_WAIT));
					SaveState::SaveSlot(SaveState::GetGamePrefix(g_paramSFO), g_Config.iCurrentStateSlot, SaveStateActionFinished);
					break;
				}
			}
			break;
		}

		case ID_OPTIONS_LANGUAGE:
			System_PostUIMessage(UIMessage::SHOW_LANGUAGE_SCREEN);
			break;

		case ID_OPTIONS_IGNOREWINKEY:
			g_Config.bIgnoreWindowsKey = !g_Config.bIgnoreWindowsKey;
			break;

		case ID_OPTIONS_SCREENAUTO: SetInternalResolution(RESOLUTION_AUTO); break;
		case ID_OPTIONS_SCREEN1X:   SetInternalResolution(RESOLUTION_NATIVE); break;
		case ID_OPTIONS_SCREEN2X:   SetInternalResolution(RESOLUTION_2X); break;
		case ID_OPTIONS_SCREEN3X:   SetInternalResolution(RESOLUTION_3X); break;
		case ID_OPTIONS_SCREEN4X:   SetInternalResolution(RESOLUTION_4X); break;
		case ID_OPTIONS_SCREEN5X:   SetInternalResolution(RESOLUTION_5X); break;
		case ID_OPTIONS_SCREEN6X:   SetInternalResolution(RESOLUTION_6X); break;
		case ID_OPTIONS_SCREEN7X:   SetInternalResolution(RESOLUTION_7X); break;
		case ID_OPTIONS_SCREEN8X:   SetInternalResolution(RESOLUTION_8X); break;
		case ID_OPTIONS_SCREEN9X:   SetInternalResolution(RESOLUTION_9X); break;
		case ID_OPTIONS_SCREEN10X:   SetInternalResolution(RESOLUTION_MAX); break;

		case ID_OPTIONS_WINDOW1X:   SetWindowSize(1); break;
		case ID_OPTIONS_WINDOW2X:   SetWindowSize(2); break;
		case ID_OPTIONS_WINDOW3X:   SetWindowSize(3); break;
		case ID_OPTIONS_WINDOW4X:   SetWindowSize(4); break;
		case ID_OPTIONS_WINDOW5X:   SetWindowSize(5); break;
		case ID_OPTIONS_WINDOW6X:   SetWindowSize(6); break;
		case ID_OPTIONS_WINDOW7X:   SetWindowSize(7); break;
		case ID_OPTIONS_WINDOW8X:   SetWindowSize(8); break;
		case ID_OPTIONS_WINDOW9X:   SetWindowSize(9); break;
		case ID_OPTIONS_WINDOW10X:   SetWindowSize(10); break;

		case ID_OPTIONS_RESOLUTIONDUMMY:
		{
			SetInternalResolution();
			break;
		}

		case ID_OPTIONS_VSYNC:
			g_Config.bVSync = !g_Config.bVSync;
			System_PostUIMessage(UIMessage::GPU_CONFIG_CHANGED);
			break;

		case ID_OPTIONS_FRAMESKIP_AUTO:
			g_Config.bAutoFrameSkip = !g_Config.bAutoFrameSkip;
			if (g_Config.bAutoFrameSkip && g_Config.bSkipBufferEffects) {
				setSkipBufferEffects(false);
			}
			break;

		case ID_TEXTURESCALING_OFF:  setTexScalingMultiplier(TEXSCALING_OFF); break;
		case ID_TEXTURESCALING_2X:   setTexScalingMultiplier(TEXSCALING_2X); break;
		case ID_TEXTURESCALING_3X:   setTexScalingMultiplier(TEXSCALING_3X); break;
		case ID_TEXTURESCALING_4X:   setTexScalingMultiplier(TEXSCALING_4X); break;
		case ID_TEXTURESCALING_5X:   setTexScalingMultiplier(TEXSCALING_MAX); break;

		case ID_TEXTURESCALING_XBRZ:            setTexScalingType(TextureScalerCommon::XBRZ); break;
		case ID_TEXTURESCALING_HYBRID:          setTexScalingType(TextureScalerCommon::HYBRID); break;
		case ID_TEXTURESCALING_BICUBIC:         setTexScalingType(TextureScalerCommon::BICUBIC); break;
		case ID_TEXTURESCALING_HYBRID_BICUBIC:  setTexScalingType(TextureScalerCommon::HYBRID_BICUBIC); break;

		case ID_TEXTURESCALING_DEPOSTERIZE:
			g_Config.bTexDeposterize = !g_Config.bTexDeposterize;
			System_PostUIMessage(UIMessage::GPU_CONFIG_CHANGED);
			break;

		case ID_OPTIONS_DIRECT3D11:
			g_Config.iGPUBackend = (int)GPUBackend::DIRECT3D11;
			g_Config.Save("gpu_choice");
			RestartApp();
			break;

		case ID_OPTIONS_OPENGL:
			g_Config.iGPUBackend = (int)GPUBackend::OPENGL;
			g_Config.Save("gpu_choice");
			RestartApp();
			break;

		case ID_OPTIONS_VULKAN:
			g_Config.iGPUBackend = (int)GPUBackend::VULKAN;
			g_Config.Save("gpu_choice");
			RestartApp();
			break;

		case ID_OPTIONS_SKIP_BUFFER_EFFECTS:
			setSkipBufferEffects(!g_Config.bSkipBufferEffects);
			g_OSD.ShowOnOff(gr->T("Skip Buffer Effects"), g_Config.bSkipBufferEffects);
			break;

		case ID_DEBUG_SHOWDEBUGSTATISTICS:
			// This is still useful as a shortcut to tell users to use.
			// So let's fake the enum.
			if ((DebugOverlay)g_Config.iDebugOverlay == DebugOverlay::DEBUG_STATS) {
				g_Config.iDebugOverlay = (int)DebugOverlay::OFF;
			} else {
				g_Config.iDebugOverlay = (int)DebugOverlay::DEBUG_STATS;
			}
			System_PostUIMessage(UIMessage::REQUEST_CLEAR_JIT);
			break;

		case ID_OPTIONS_HARDWARETRANSFORM:
			System_RunOnMainThread([]() {
				auto gr = GetI18NCategory(I18NCat::GRAPHICS);
				g_Config.bHardwareTransform = !g_Config.bHardwareTransform;
				System_PostUIMessage(UIMessage::GPU_CONFIG_CHANGED);
				g_OSD.ShowOnOff(gr->T("Hardware Transform"), g_Config.bHardwareTransform);
			});
			break;

		case ID_OPTIONS_DISPLAY_LAYOUT:
			System_PostUIMessage(UIMessage::SHOW_DISPLAY_LAYOUT_EDITOR);
			break;


		case ID_OPTIONS_FRAMESKIP_0:    setFrameSkipping(FRAMESKIP_OFF); break;
		case ID_OPTIONS_FRAMESKIP_1:    setFrameSkipping(FRAMESKIP_1); break;
		case ID_OPTIONS_FRAMESKIP_2:    setFrameSkipping(FRAMESKIP_2); break;
		case ID_OPTIONS_FRAMESKIP_3:    setFrameSkipping(FRAMESKIP_3); break;
		case ID_OPTIONS_FRAMESKIP_4:    setFrameSkipping(FRAMESKIP_4); break;
		case ID_OPTIONS_FRAMESKIP_5:    setFrameSkipping(FRAMESKIP_5); break;
		case ID_OPTIONS_FRAMESKIP_6:    setFrameSkipping(FRAMESKIP_6); break;
		case ID_OPTIONS_FRAMESKIP_7:    setFrameSkipping(FRAMESKIP_7); break;
		case ID_OPTIONS_FRAMESKIP_8:    setFrameSkipping(FRAMESKIP_MAX); break;

		case ID_FILE_EXIT:
			if (MainWindow::ConfirmAction(hWnd, false)) {
				DestroyWindow(hWnd);
			}
			break;

		case ID_DEBUG_BREAKONLOAD:
			g_Config.bAutoRun = !g_Config.bAutoRun;
			break;

		case ID_DEBUG_SAVEFRAMEDUMP:
		{
			System_PostUIMessage(UIMessage::SAVE_FRAME_DUMP);
			break;
		}

		case ID_DEBUG_LOADMAPFILE:
			if (W32Util::BrowseForFileName(true, hWnd, L"Load .ppmap", nullptr, nullptr, L"Maps\0*.ppmap\0All files\0*.*\0\0", L"ppmap", fn)) {
				g_symbolMap->LoadSymbolMap(Path(fn));
				NotifyDebuggerMapLoaded();
			}
			break;

		case ID_DEBUG_SAVEMAPFILE:
			if (W32Util::BrowseForFileName(false, hWnd, L"Save .ppmap", nullptr, L"map.ppmap", L"Maps\0*.ppmap\0All files\0*.*\0\0", L"ppmap", fn)) {
				g_symbolMap->SaveSymbolMap(Path(fn));
			}
			break;

		case ID_DEBUG_LOADSYMFILE:
			if (W32Util::BrowseForFileName(true, hWnd, L"Load .sym", nullptr, nullptr, L"Symbols\0*.sym\0All files\0*.*\0\0", L"sym", fn)) {
				g_symbolMap->LoadNocashSym(Path(fn));
				NotifyDebuggerMapLoaded();
			}
			break;

		case ID_DEBUG_SAVESYMFILE:
			if (W32Util::BrowseForFileName(false, hWnd, L"Save .sym", nullptr, L"symbols.sym", L"Symbols\0*.sym\0All files\0*.*\0\0", L"sym", fn)) {
				g_symbolMap->SaveNocashSym(Path(fn));
			}
			break;

		case ID_DEBUG_RESETSYMBOLTABLE:
			g_symbolMap->Clear();
			NotifyDebuggerMapLoaded();
			break;

		case ID_DEBUG_DISASSEMBLY:
			CreateDisasmWindow();
			if (disasmWindow)
				disasmWindow->Show(true);
			break;

		case ID_DEBUG_GEDEBUGGER:
#if PPSSPP_API(ANY_GL)
			CreateGeDebuggerWindow();
			if (geDebuggerWindow)
				geDebuggerWindow->Show(true);
#endif
			break;

		case ID_DEBUG_MEMORYVIEW:
			CreateMemoryWindow();
			if (memoryWindow)
				memoryWindow->Show(true);
			break;

		case ID_DEBUG_MEMORYBASE:
		{
			System_CopyStringToClipboard(StringFromFormat("%016llx", (uint64_t)(uintptr_t)Memory::base));
			break;
		}

		case ID_DEBUG_EXTRACTFILE:
		{
			std::string filename;
			if (!InputBox_GetString(MainWindow::GetHInstance(), hWnd, L"Disc filename", filename, filename)) {
				break;
			}
			const char *lastSlash = strrchr(filename.c_str(), '/');
			if (lastSlash) {
				fn = lastSlash + 1;
			} else {
				fn.clear();
			}

			PSPFileInfo info = pspFileSystem.GetFileInfo(filename);
			if (!info.exists) {
				MessageBox(hWnd, L"File does not exist.", L"Sorry", 0);
			} else if (info.type == FILETYPE_DIRECTORY) {
				MessageBox(hWnd, L"Cannot extract directories.", L"Sorry", 0);
			} else if (W32Util::BrowseForFileName(false, hWnd, L"Save file as...", nullptr, nullptr, L"All files\0*.*\0\0", L"", fn)) {
				const u32 handle = pspFileSystem.OpenFile(filename, FILEACCESS_READ, "");
				// Note: len may be in blocks.
				size_t len = pspFileSystem.SeekFile(handle, 0, FILEMOVE_END);
				const bool isBlockMode = pspFileSystem.DevType(handle) & PSPDevType::BLOCK;

				FILE *fp = File::OpenCFile(Path(fn), "wb");
				pspFileSystem.SeekFile(handle, 0, FILEMOVE_BEGIN);
				u8 buffer[4096];
				size_t bufferSize = isBlockMode ? sizeof(buffer) / 2048 : sizeof(buffer);
				while (len > 0) {
					// This is all in blocks, not bytes, if isBlockMode.
					const size_t remain = std::min(len, bufferSize);
					const size_t readSize = pspFileSystem.ReadFile(handle, buffer, remain);
					if (readSize == 0)
						break;
					const size_t bytes = isBlockMode ? readSize * 2048 : readSize;
					fwrite(buffer, 1, bytes, fp);
					len -= readSize;
				}
				pspFileSystem.CloseFile(handle);
				fclose(fp);
			}
		}
		break;

		case ID_DEBUG_LOG:
			g_logManager.GetConsoleListener()->Show(g_logManager.GetConsoleListener()->Hidden());
			break;

		case ID_DEBUG_IGNOREILLEGALREADS:
			g_Config.bIgnoreBadMemAccess = !g_Config.bIgnoreBadMemAccess;
			break;

		case ID_OPTIONS_FULLSCREEN:
			if (!g_Config.bShowImDebugger) {
				g_Config.bFullScreen = !g_Config.bFullScreen;
				SendApplyFullscreenState();
			}
			break;

		case ID_OPTIONS_TEXTUREFILTERING_AUTO:   g_Config.iTexFiltering = TEX_FILTER_AUTO; break;
		case ID_OPTIONS_NEARESTFILTERING:        g_Config.iTexFiltering = TEX_FILTER_FORCE_NEAREST; break;
		case ID_OPTIONS_LINEARFILTERING:         g_Config.iTexFiltering = TEX_FILTER_FORCE_LINEAR; break;
		case ID_OPTIONS_AUTOMAXQUALITYFILTERING: g_Config.iTexFiltering = TEX_FILTER_AUTO_MAX_QUALITY; break;

		case ID_OPTIONS_SMART2DTEXTUREFILTERING: g_Config.bSmart2DTexFiltering = !g_Config.bSmart2DTexFiltering; break;

		case ID_OPTIONS_BUFLINEARFILTER:
		{
			DisplayLayoutConfig &displayLayoutConfig = g_Config.GetDisplayLayoutConfig(g_display.GetDeviceOrientation());
			displayLayoutConfig.iDisplayFilter = SCALE_LINEAR;
			break;
		}
		case ID_OPTIONS_BUFNEARESTFILTER:
		{
			DisplayLayoutConfig &displayLayoutConfig = g_Config.GetDisplayLayoutConfig(g_display.GetDeviceOrientation());
			displayLayoutConfig.iDisplayFilter = SCALE_NEAREST;
			break;
		}

		case ID_OPTIONS_TOPMOST:
			g_Config.bTopMost = !g_Config.bTopMost;
			W32Util::MakeTopMost(hWnd, g_Config.bTopMost);
			break;

		case ID_OPTIONS_PAUSE_FOCUS:
			g_Config.bPauseOnLostFocus = !g_Config.bPauseOnLostFocus;
			break;

		case ID_OPTIONS_CONTROLS:
			System_PostUIMessage(UIMessage::SHOW_CONTROL_MAPPING);
			break;

		case ID_OPTIONS_MORE_SETTINGS:
			System_PostUIMessage(UIMessage::SHOW_SETTINGS);
			break;

		case ID_EMULATION_SOUND:
			g_Config.bEnableSound = !g_Config.bEnableSound;
			break;

		case ID_HELP_OPENWEBSITE:
			System_LaunchUrl(LaunchUrlType::BROWSER_URL, "https://www.ppsspp.org/");
			break;

		case ID_HELP_BUYGOLD:
			System_LaunchUrl(LaunchUrlType::BROWSER_URL, "https://www.ppsspp.org/buygold");
			break;

		case ID_HELP_OPENFORUM:
			System_LaunchUrl(LaunchUrlType::BROWSER_URL, "https://forums.ppsspp.org/");
			break;

		case ID_HELP_GITHUB:
			System_LaunchUrl(LaunchUrlType::BROWSER_URL, "https://github.com/hrydgard/ppsspp/");
			break;

		case ID_HELP_DISCORD:
			System_LaunchUrl(LaunchUrlType::BROWSER_URL, "https://discord.gg/5NJB6dD");
			break;

		case ID_HELP_ABOUT:
			DialogManager::EnableAll(FALSE);
			DialogBox(MainWindow::GetHInstance(), (LPCTSTR)IDD_ABOUTBOX, hWnd, (DLGPROC)AboutDlgProc);
			DialogManager::EnableAll(TRUE);
			break;

		case ID_DEBUG_TAKESCREENSHOT:
			TakeUserScreenshot();
			break;

		case ID_DEBUG_RESTARTGRAPHICS:
			System_PostUIMessage(UIMessage::RESTART_GRAPHICS);
			break;

		case ID_FILE_DUMPFRAMES:
			g_Config.bDumpFrames = !g_Config.bDumpFrames;
			break;

		case ID_FILE_USEFFV1:
			g_Config.bUseFFV1 = !g_Config.bUseFFV1;
			break;

		case ID_FILE_DUMP_VIDEO_OUTPUT:
			g_Config.bDumpVideoOutput = !g_Config.bDumpVideoOutput;
			break;

		case ID_FILE_DUMPAUDIO:
			g_Config.bDumpAudio = !g_Config.bDumpAudio;
			break;

		default:
			if (wmId >= ID_FILE_SAVESTATE_SLOT_BASE && wmId < ID_FILE_SAVESTATE_SLOT_BASE + g_Config.iSaveStateSlotCount) {
				if (!Achievements::WarnUserIfHardcoreModeActive(true) && !NetworkWarnUserIfOnlineAndCantSavestate()) {
					g_Config.iCurrentStateSlot = wmId - ID_FILE_SAVESTATE_SLOT_BASE;
				}
				break;
			}

#ifdef RC_CLIENT_SUPPORTS_RAINTEGRATION
			if (rc_client_raintegration_activate_menu_item(Achievements::GetClient(), LOWORD(wParam))) {
				break;
			}
			MessageBox(hWnd, L"Unhandled menu item. Did you unload RAIntegration?", L"Sorry", 0);
#else
			MessageBox(hWnd, L"Unhandled menu item. Please report a bug.", L"Sorry", 0);
#endif
			break;
		}
	}

	void HideDebugWindows() {
		if (disasmWindow)
			disasmWindow->Show(false);
#if PPSSPP_API(ANY_GL)
		if (geDebuggerWindow)
			geDebuggerWindow->Show(false);
#endif
		if (memoryWindow)
			memoryWindow->Show(false);
		if (vfpudlg)
			vfpudlg->Show(false);
	}

	static void UpdateBackendSubMenu(HMENU menu);

	void UpdateMenus(HMENU menuSelected) {
		HMENU menu = GetMenu(GetHWND());

		if (menuSelected) {
			// Technically we only need to update the selected menu.
			if (menuSelected == g_hMenuBackend) {
				UpdateBackendSubMenu(menu);
				return;
			}
			bool found = false;
			for (auto topLevelMenu : g_topLevelMenus) {
				if (menuSelected == topLevelMenu) {
					found = true;
				}
			}

			if (!found) {
				// Don't do anything
				return;
			}
		}

#define CHECKITEM(item,value) 	CheckMenuItem(menu,item,MF_BYCOMMAND | ((value) ? MF_CHECKED : MF_UNCHECKED));
		CHECKITEM(ID_DEBUG_IGNOREILLEGALREADS, g_Config.bIgnoreBadMemAccess);
		CHECKITEM(ID_DEBUG_SHOWDEBUGSTATISTICS, (DebugOverlay)g_Config.iDebugOverlay == DebugOverlay::DEBUG_STATS);
		CHECKITEM(ID_OPTIONS_HARDWARETRANSFORM, g_Config.bHardwareTransform);
		CHECKITEM(ID_DEBUG_BREAKONLOAD, !g_Config.bAutoRun);
		CHECKITEM(ID_OPTIONS_FRAMESKIP_AUTO, g_Config.bAutoFrameSkip);
		CHECKITEM(ID_OPTIONS_FRAMESKIP, g_Config.iFrameSkip != FRAMESKIP_OFF);
		CHECKITEM(ID_OPTIONS_VSYNC, g_Config.bVSync);
		CHECKITEM(ID_OPTIONS_TOPMOST, g_Config.bTopMost);
		CHECKITEM(ID_OPTIONS_PAUSE_FOCUS, g_Config.bPauseOnLostFocus);
		CHECKITEM(ID_OPTIONS_SMART2DTEXTUREFILTERING, g_Config.bSmart2DTexFiltering);
		CHECKITEM(ID_EMULATION_SOUND, g_Config.bEnableSound);
		CHECKITEM(ID_TEXTURESCALING_DEPOSTERIZE, g_Config.bTexDeposterize);
		CHECKITEM(ID_EMULATION_CHEATS, g_Config.bEnableCheats);
		CHECKITEM(ID_OPTIONS_IGNOREWINKEY, g_Config.bIgnoreWindowsKey);
		CHECKITEM(ID_FILE_DUMPFRAMES, g_Config.bDumpFrames);
		CHECKITEM(ID_FILE_USEFFV1, g_Config.bUseFFV1);
		CHECKITEM(ID_FILE_DUMP_VIDEO_OUTPUT, g_Config.bDumpVideoOutput);
		CHECKITEM(ID_FILE_DUMPAUDIO, g_Config.bDumpAudio);
		CHECKITEM(ID_OPTIONS_SKIP_BUFFER_EFFECTS, g_Config.bSkipBufferEffects);

		static const int displayrotationitems[] = {
			ID_EMULATION_ROTATION_H,
			ID_EMULATION_ROTATION_V,
			ID_EMULATION_ROTATION_H_R,
			ID_EMULATION_ROTATION_V_R
		};

		DisplayLayoutConfig &displayLayoutConfig = g_Config.GetDisplayLayoutConfig(g_display.GetDeviceOrientation());

		if (displayLayoutConfig.iInternalScreenRotation < ROTATION_LOCKED_HORIZONTAL)
			displayLayoutConfig.iInternalScreenRotation = ROTATION_LOCKED_HORIZONTAL;

		else if (displayLayoutConfig.iInternalScreenRotation > ROTATION_LOCKED_VERTICAL180)
			displayLayoutConfig.iInternalScreenRotation = ROTATION_LOCKED_VERTICAL180;

		for (int i = 0; i < ARRAY_SIZE(displayrotationitems); i++) {
			CheckMenuItem(menu, displayrotationitems[i], MF_BYCOMMAND | ((i + 1) == displayLayoutConfig.iInternalScreenRotation ? MF_CHECKED : MF_UNCHECKED));
		}

		static const int zoomitems[11] = {
			ID_OPTIONS_SCREENAUTO,
			ID_OPTIONS_SCREEN1X,
			ID_OPTIONS_SCREEN2X,
			ID_OPTIONS_SCREEN3X,
			ID_OPTIONS_SCREEN4X,
			ID_OPTIONS_SCREEN5X,
			ID_OPTIONS_SCREEN6X,
			ID_OPTIONS_SCREEN7X,
			ID_OPTIONS_SCREEN8X,
			ID_OPTIONS_SCREEN9X,
			ID_OPTIONS_SCREEN10X,
		};
		if (g_Config.iInternalResolution < RESOLUTION_AUTO)
			g_Config.iInternalResolution = RESOLUTION_AUTO;

		else if (g_Config.iInternalResolution > RESOLUTION_MAX)
			g_Config.iInternalResolution = RESOLUTION_MAX;

		for (int i = 0; i < ARRAY_SIZE(zoomitems); i++) {
			CheckMenuItem(menu, zoomitems[i], MF_BYCOMMAND | ((i == g_Config.iInternalResolution) ? MF_CHECKED : MF_UNCHECKED));
		}

		static const int windowSizeItems[10] = {
			ID_OPTIONS_WINDOW1X,
			ID_OPTIONS_WINDOW2X,
			ID_OPTIONS_WINDOW3X,
			ID_OPTIONS_WINDOW4X,
			ID_OPTIONS_WINDOW5X,
			ID_OPTIONS_WINDOW6X,
			ID_OPTIONS_WINDOW7X,
			ID_OPTIONS_WINDOW8X,
			ID_OPTIONS_WINDOW9X,
			ID_OPTIONS_WINDOW10X,
		};

		RECT rc;
		GetClientRect(GetHWND(), &rc);

		int checkW = displayLayoutConfig.InternalRotationIsPortrait() ? 272 : 480;
		int checkH = displayLayoutConfig.InternalRotationIsPortrait() ? 480 : 272;

		for (int i = 0; i < ARRAY_SIZE(windowSizeItems); i++) {
			bool check = (i + 1) * checkW == rc.right - rc.left || (i + 1) * checkH == rc.bottom - rc.top;
			CheckMenuItem(menu, windowSizeItems[i], MF_BYCOMMAND | (check ? MF_CHECKED : MF_UNCHECKED));
		}

		static const int texscalingitems[] = {
			ID_TEXTURESCALING_OFF,
			ID_TEXTURESCALING_2X,
			ID_TEXTURESCALING_3X,
			ID_TEXTURESCALING_4X,
			ID_TEXTURESCALING_5X,
		};
		if (g_Config.iTexScalingLevel < TEXSCALING_OFF)
			g_Config.iTexScalingLevel = TEXSCALING_OFF;

		else if (g_Config.iTexScalingLevel > TEXSCALING_MAX)
			g_Config.iTexScalingLevel = TEXSCALING_MAX;

		for (int i = 0; i < ARRAY_SIZE(texscalingitems); i++) {
			// OFF is 1, skip 0.
			bool selected = i + 1 == g_Config.iTexScalingLevel;
			CheckMenuItem(menu, texscalingitems[i], MF_BYCOMMAND | (selected ? MF_CHECKED : MF_UNCHECKED));
		}

		if (g_Config.iGPUBackend == (int)GPUBackend::OPENGL && !gl_extensions.OES_texture_npot) {
			EnableMenuItem(menu, ID_TEXTURESCALING_3X, MF_GRAYED);
			EnableMenuItem(menu, ID_TEXTURESCALING_5X, MF_GRAYED);
		} else {
			EnableMenuItem(menu, ID_TEXTURESCALING_3X, MF_ENABLED);
			EnableMenuItem(menu, ID_TEXTURESCALING_5X, MF_ENABLED);
		}

		static const int texscalingtypeitems[] = {
			ID_TEXTURESCALING_XBRZ,
			ID_TEXTURESCALING_HYBRID,
			ID_TEXTURESCALING_BICUBIC,
			ID_TEXTURESCALING_HYBRID_BICUBIC,
		};
		if (g_Config.iTexScalingType < TextureScalerCommon::XBRZ)
			g_Config.iTexScalingType = TextureScalerCommon::XBRZ;

		else if (g_Config.iTexScalingType > TextureScalerCommon::HYBRID_BICUBIC)
			g_Config.iTexScalingType = TextureScalerCommon::HYBRID_BICUBIC;

		for (int i = 0; i < ARRAY_SIZE(texscalingtypeitems); i++) {
			CheckMenuItem(menu, texscalingtypeitems[i], MF_BYCOMMAND | ((i == g_Config.iTexScalingType) ? MF_CHECKED : MF_UNCHECKED));
		}

		static const int texfilteringitems[] = {
			ID_OPTIONS_TEXTUREFILTERING_AUTO,
			ID_OPTIONS_NEARESTFILTERING,
			ID_OPTIONS_LINEARFILTERING,
			ID_OPTIONS_AUTOMAXQUALITYFILTERING,
		};
		if (g_Config.iTexFiltering < TEX_FILTER_AUTO)
			g_Config.iTexFiltering = TEX_FILTER_AUTO;
		else if (g_Config.iTexFiltering > TEX_FILTER_AUTO_MAX_QUALITY)
			g_Config.iTexFiltering = TEX_FILTER_AUTO_MAX_QUALITY;

		for (int i = 0; i < ARRAY_SIZE(texfilteringitems); i++) {
			CheckMenuItem(menu, texfilteringitems[i], MF_BYCOMMAND | ((i + 1) == g_Config.iTexFiltering ? MF_CHECKED : MF_UNCHECKED));
		}

		static const int bufferfilteritems[] = {
			ID_OPTIONS_BUFLINEARFILTER,
			ID_OPTIONS_BUFNEARESTFILTER,
		};
		if (displayLayoutConfig.iDisplayFilter < SCALE_LINEAR)
			displayLayoutConfig.iDisplayFilter = SCALE_LINEAR;

		else if (displayLayoutConfig.iDisplayFilter > SCALE_NEAREST)
			displayLayoutConfig.iDisplayFilter = SCALE_NEAREST;

		for (int i = 0; i < ARRAY_SIZE(bufferfilteritems); i++) {
			CheckMenuItem(menu, bufferfilteritems[i], MF_BYCOMMAND | ((i + 1) == displayLayoutConfig.iDisplayFilter ? MF_CHECKED : MF_UNCHECKED));
		}

		static const int frameskipping[] = {
			ID_OPTIONS_FRAMESKIP_0,
			ID_OPTIONS_FRAMESKIP_1,
			ID_OPTIONS_FRAMESKIP_2,
			ID_OPTIONS_FRAMESKIP_3,
			ID_OPTIONS_FRAMESKIP_4,
			ID_OPTIONS_FRAMESKIP_5,
			ID_OPTIONS_FRAMESKIP_6,
			ID_OPTIONS_FRAMESKIP_7,
			ID_OPTIONS_FRAMESKIP_8,
		};

		if (g_Config.iFrameSkip < FRAMESKIP_OFF)
			g_Config.iFrameSkip = FRAMESKIP_OFF;

		else if (g_Config.iFrameSkip > FRAMESKIP_MAX)
			g_Config.iFrameSkip = FRAMESKIP_MAX;

		for (int i = 0; i < ARRAY_SIZE(frameskipping); i++) {
			CheckMenuItem(menu, frameskipping[i], MF_BYCOMMAND | ((i == g_Config.iFrameSkip) ? MF_CHECKED : MF_UNCHECKED));
		}

		if (g_Config.iCurrentStateSlot < 0)
			g_Config.iCurrentStateSlot = 0;

		else if (g_Config.iCurrentStateSlot >= g_Config.iSaveStateSlotCount)
			g_Config.iCurrentStateSlot = g_Config.iSaveStateSlotCount - 1;

		for (int i = 0; i < g_Config.iSaveStateSlotCount; i++) {
			CheckMenuItem(menu, ID_FILE_SAVESTATE_SLOT_BASE + i, MF_BYCOMMAND | ((i == g_Config.iCurrentStateSlot) ? MF_CHECKED : MF_UNCHECKED));
		}

#if !PPSSPP_API(ANY_GL)
		EnableMenuItem(menu, ID_DEBUG_GEDEBUGGER, MF_GRAYED);
#endif

		UpdateCommands();
	}

	// This one is pretty expensive so we handle it separately.
	static void UpdateBackendSubMenu(HMENU menu) {
		const bool allowD3D11 = g_Config.IsBackendEnabled(GPUBackend::DIRECT3D11);
		const bool allowOpenGL = g_Config.IsBackendEnabled(GPUBackend::OPENGL);
		const bool allowVulkan = g_Config.IsBackendEnabled(GPUBackend::VULKAN);

		switch (GetGPUBackend()) {
		case GPUBackend::OPENGL:
			EnableMenuItem(menu, ID_OPTIONS_DIRECT3D11, allowD3D11 ? MF_ENABLED : MF_GRAYED);
			EnableMenuItem(menu, ID_OPTIONS_OPENGL, MF_GRAYED);
			EnableMenuItem(menu, ID_OPTIONS_VULKAN, allowVulkan ? MF_ENABLED : MF_GRAYED);
			CheckMenuItem(menu, ID_OPTIONS_DIRECT3D11, MF_UNCHECKED);
			CheckMenuItem(menu, ID_OPTIONS_OPENGL, MF_CHECKED);
			CheckMenuItem(menu, ID_OPTIONS_VULKAN, MF_UNCHECKED);
			break;
		case GPUBackend::VULKAN:
			EnableMenuItem(menu, ID_OPTIONS_DIRECT3D11, allowD3D11 ? MF_ENABLED : MF_GRAYED);
			EnableMenuItem(menu, ID_OPTIONS_OPENGL, allowOpenGL ? MF_ENABLED : MF_GRAYED);
			EnableMenuItem(menu, ID_OPTIONS_VULKAN, MF_GRAYED);
			CheckMenuItem(menu, ID_OPTIONS_DIRECT3D11, MF_UNCHECKED);
			CheckMenuItem(menu, ID_OPTIONS_OPENGL, MF_UNCHECKED);
			CheckMenuItem(menu, ID_OPTIONS_VULKAN, MF_CHECKED);
			break;
		case GPUBackend::DIRECT3D11:
			EnableMenuItem(menu, ID_OPTIONS_DIRECT3D11, MF_GRAYED);
			EnableMenuItem(menu, ID_OPTIONS_OPENGL, allowOpenGL ? MF_ENABLED : MF_GRAYED);
			EnableMenuItem(menu, ID_OPTIONS_VULKAN, allowVulkan ? MF_ENABLED : MF_GRAYED);
			CheckMenuItem(menu, ID_OPTIONS_DIRECT3D11, MF_CHECKED);
			CheckMenuItem(menu, ID_OPTIONS_OPENGL, MF_UNCHECKED);
			CheckMenuItem(menu, ID_OPTIONS_VULKAN, MF_UNCHECKED);
			break;
		}
	}

	void UpdateCommands() {
		static GlobalUIState lastGlobalUIState = UISTATE_PAUSEMENU;
		static CoreState lastCoreState = CORE_POWERDOWN;

		HMENU menu = GetMenu(GetHWND());
		EnableMenuItem(menu, ID_DEBUG_LOG, g_Config.bEnableLogging ? MF_ENABLED : MF_GRAYED);
		SetIngameMenuItemStates(menu, GetUIState());

		if (lastGlobalUIState == GetUIState() && lastCoreState == coreState)
			return;

		lastCoreState = coreState;
		lastGlobalUIState = GetUIState();

		bool isPaused = Core_IsStepping() && GetUIState() == UISTATE_INGAME;
		TranslateMenuItem(menu, ID_TOGGLE_BREAK, L"\tF8", isPaused ? "Run" : "Break");
	}

	void UpdateSwitchUMD() {
		HMENU menu = GetMenu(GetHWND());
		GlobalUIState state = GetUIState();
		UINT umdSwitchEnable = state == UISTATE_INGAME && getUMDReplacePermit() ? MF_ENABLED : MF_GRAYED;
		EnableMenuItem(menu, ID_EMULATION_SWITCH_UMD, umdSwitchEnable);
	}

	// Message handler for about box.
	LRESULT CALLBACK AboutDlgProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam) {
		switch (message) {
			case WM_INITDIALOG:
			{
				W32Util::CenterWindow(hDlg);
				HWND versionBox = GetDlgItem(hDlg, IDC_VERSION);
				std::string windowText = System_GetPropertyBool(SYSPROP_APP_GOLD) ? "PPSSPP Gold " : "PPSSPP ";
				windowText.append(PPSSPP_GIT_VERSION);
				SetWindowText(versionBox, ConvertUTF8ToWString(windowText).c_str());
				DarkModeInitDialog(hDlg);
				return TRUE;
			}

			case WM_COMMAND:
			{
				if (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL) {
					EndDialog(hDlg, LOWORD(wParam));
					return TRUE;
				}
				break;
				return FALSE;
			}

			default:
				return DarkModeDlgProc(hDlg, message, wParam, lParam);
		}
		return FALSE;
	}
}
