Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion Source/Core/Core/PowerPC/Interpreter/Interpreter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
#include "Core/PowerPC/MMU.h"
#include "Core/PowerPC/PPCTables.h"
#include "Core/PowerPC/PowerPC.h"
#include "Core/PowerPC/StaticRecomp/StaticRecompCore.h"
#include "Core/System.h"

namespace
Expand Down Expand Up @@ -287,6 +288,7 @@ void Interpreter::Run()
void Interpreter::unknown_instruction(Interpreter& interpreter, UGeckoInstruction inst)
{
ASSERT(Core::IsCPUThread());
auto& ppc_state = interpreter.m_ppc_state;
auto& system = interpreter.m_system;
Core::CPUThreadGuard guard(system);

Expand Down Expand Up @@ -337,7 +339,6 @@ void Interpreter::unknown_instruction(Interpreter& interpreter, UGeckoInstructio
Dolphin_Debugger::PrintCallstack(guard, Common::Log::LogType::POWERPC,
Common::Log::LogLevel::LNOTICE);

const auto& ppc_state = interpreter.m_ppc_state;
NOTICE_LOG_FMT(
POWERPC,
"\nIntCPU: Unknown instruction {:08x} at PC = {:08x} last_PC = {:08x} LR = {:08x}\n",
Expand Down
260 changes: 258 additions & 2 deletions Source/Core/DolphinNoGUI/PlatformWin32.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,18 @@
#include "Core/Config/MainSettings.h"
#include "Core/Config/ConfigManager.h"
#include "Core/Core.h"
#include "Core/State.h"
#include "Core/System.h"

#include "Common/CommonPaths.h"
#include "Common/FileUtil.h"

#include <algorithm>
#include <chrono>
#include <ctime>
#include <filesystem>
#include <string>
#include <vector>
#include <windows.h>
#include <climits>
#include <dwmapi.h>
Expand All @@ -19,6 +28,19 @@

namespace
{
// Menu command ids. Load State entries are allocated a contiguous range,
// since the list is rebuilt from disk each time the menu opens.
constexpr UINT ID_SAVE_STATE = 41001;
constexpr UINT ID_PAUSE = 41002;
constexpr UINT ID_MUTE = 41003;
constexpr UINT ID_FULLSCREEN = 41004;
constexpr UINT ID_LOAD_STATE_FIRST = 41100;
constexpr UINT ID_LOAD_STATE_LAST = 41199;

// Hold-to-fast-forward target. 2x is fast enough to skip a cutscene without
// outrunning what most hosts can actually emulate.
constexpr float FAST_FORWARD_SPEED = 2.0f;

class PlatformWin32 final : public Platform
{
public:
Expand All @@ -37,10 +59,23 @@ class PlatformWin32 final : public Platform

static bool RegisterRenderWindowClass();
bool CreateRenderWindow();
bool CreateMenus();
void RefreshMenu(HMENU menu);
void SaveStateToStatesDirectory();
void ToggleFullscreen();
void UpdateWindowPosition();
void ProcessEvents();

HWND m_hwnd{};
HMENU m_menu{};
HMENU m_file_menu{};
HMENU m_load_menu{};
HMENU m_view_menu{};
// Parallel to the Load State menu entries, rebuilt whenever it opens.
std::vector<std::string> m_load_state_paths;
bool m_fullscreen = false;
LONG m_windowed_style = 0;
RECT m_windowed_rect{};

int m_window_x = Config::Get(Config::MAIN_RENDER_WINDOW_XPOS);
int m_window_y = Config::Get(Config::MAIN_RENDER_WINDOW_YPOS);
Expand Down Expand Up @@ -96,9 +131,147 @@ bool PlatformWin32::CreateRenderWindow()
return true;
}

bool PlatformWin32::CreateMenus()
{
m_menu = CreateMenu();
m_file_menu = CreatePopupMenu();
m_load_menu = CreatePopupMenu();
m_view_menu = CreatePopupMenu();
if (!m_menu || !m_file_menu || !m_load_menu || !m_view_menu)
return false;

AppendMenuW(m_file_menu, MF_STRING, ID_SAVE_STATE, L"&Save State\tF1");
AppendMenuW(m_file_menu, MF_POPUP, reinterpret_cast<UINT_PTR>(m_load_menu), L"&Load State");
AppendMenuW(m_file_menu, MF_SEPARATOR, 0, nullptr);
// Checked state is refreshed from the core when the menu opens, so it cannot
// drift out of step with an emulation that was paused some other way.
AppendMenuW(m_file_menu, MF_STRING, ID_PAUSE, L"&Pause");

AppendMenuW(m_view_menu, MF_STRING, ID_FULLSCREEN, L"&Fullscreen\tAlt+Enter");
AppendMenuW(m_view_menu, MF_STRING, ID_MUTE, L"&Mute Audio");

AppendMenuW(m_menu, MF_POPUP, reinterpret_cast<UINT_PTR>(m_file_menu), L"&File");
AppendMenuW(m_menu, MF_POPUP, reinterpret_cast<UINT_PTR>(m_view_menu), L"&View");
return SetMenu(m_hwnd, m_menu) != FALSE;
}

// Rebuilt on open rather than cached: states are written by this process while
// the menu is closed, and by the launcher between sessions.
void PlatformWin32::RefreshMenu(const HMENU menu)
{
if (menu == m_file_menu)
{
auto& system = Core::System::GetInstance();
const bool paused = Core::GetState(system) == Core::State::Paused;
CheckMenuItem(m_file_menu, ID_PAUSE, MF_BYCOMMAND | (paused ? MF_CHECKED : MF_UNCHECKED));
return;
}

if (menu == m_view_menu)
{
CheckMenuItem(m_view_menu, ID_MUTE,
MF_BYCOMMAND |
(Config::Get(Config::MAIN_AUDIO_MUTED) ? MF_CHECKED : MF_UNCHECKED));
CheckMenuItem(m_view_menu, ID_FULLSCREEN,
MF_BYCOMMAND | (m_fullscreen ? MF_CHECKED : MF_UNCHECKED));
return;
}

if (menu != m_load_menu)
return;

while (DeleteMenu(m_load_menu, 0, MF_BYPOSITION))
{
}
m_load_state_paths.clear();

const std::string directory = File::GetUserPath(D_STATESAVES_IDX);
std::vector<std::filesystem::path> states;
std::error_code ec;
for (const auto& entry : std::filesystem::directory_iterator(directory, ec))
{
if (entry.is_regular_file(ec) && entry.path().extension() == ".sav")
states.push_back(entry.path());
}
// Newest first: the state wanted next is nearly always the one just written.
// Filename breaks ties so the order does not shuffle between opens on a
// filesystem whose timestamps are coarse.
std::sort(states.begin(), states.end(),
[](const std::filesystem::path& left, const std::filesystem::path& right) {
std::error_code left_ec;
std::error_code right_ec;
const auto left_time = std::filesystem::last_write_time(left, left_ec);
const auto right_time = std::filesystem::last_write_time(right, right_ec);
if (!left_ec && !right_ec && left_time != right_time)
return left_time > right_time;
return left.filename().string() < right.filename().string();
});

if (states.empty())
{
AppendMenuW(m_load_menu, MF_STRING | MF_GRAYED, 0, L"(no savestates)");
return;
}

const std::size_t limit = std::min<std::size_t>(
states.size(), ID_LOAD_STATE_LAST - ID_LOAD_STATE_FIRST + 1);
for (std::size_t i = 0; i < limit; ++i)
{
AppendMenuW(m_load_menu, MF_STRING, ID_LOAD_STATE_FIRST + i,
states[i].filename().wstring().c_str());
m_load_state_paths.push_back(states[i].string());
}
}

void PlatformWin32::SaveStateToStatesDirectory()
{
// Named by wall-clock rather than by slot, so repeated saves accumulate
// instead of overwriting one another, and sort meaningfully by name.
const std::time_t now = std::time(nullptr);
std::tm local{};
localtime_s(&local, &now);
char stamp[32];
std::strftime(stamp, sizeof(stamp), "%Y%m%d-%H%M%S", &local);

const std::string directory = File::GetUserPath(D_STATESAVES_IDX);
File::CreateFullPath(directory);
State::SaveAs(Core::System::GetInstance(), directory + "state-" + stamp + ".sav");
}

void PlatformWin32::ToggleFullscreen()
{
if (!m_fullscreen)
{
GetWindowRect(m_hwnd, &m_windowed_rect);
m_windowed_style = GetWindowLong(m_hwnd, GWL_STYLE);

MONITORINFO monitor{};
monitor.cbSize = sizeof(monitor);
if (!GetMonitorInfo(MonitorFromWindow(m_hwnd, MONITOR_DEFAULTTONEAREST), &monitor))
return;

SetMenu(m_hwnd, nullptr);
SetWindowLong(m_hwnd, GWL_STYLE, m_windowed_style & ~WS_OVERLAPPEDWINDOW);
SetWindowPos(m_hwnd, HWND_TOP, monitor.rcMonitor.left, monitor.rcMonitor.top,
monitor.rcMonitor.right - monitor.rcMonitor.left,
monitor.rcMonitor.bottom - monitor.rcMonitor.top,
SWP_NOOWNERZORDER | SWP_FRAMECHANGED);
m_fullscreen = true;
return;
}

SetWindowLong(m_hwnd, GWL_STYLE, m_windowed_style);
SetMenu(m_hwnd, m_menu);
SetWindowPos(m_hwnd, nullptr, m_windowed_rect.left, m_windowed_rect.top,
m_windowed_rect.right - m_windowed_rect.left,
m_windowed_rect.bottom - m_windowed_rect.top,
SWP_NOOWNERZORDER | SWP_FRAMECHANGED | SWP_NOZORDER);
m_fullscreen = false;
}

bool PlatformWin32::Init()
{
if (!RegisterRenderWindowClass() || !CreateRenderWindow())
if (!RegisterRenderWindowClass() || !CreateRenderWindow() || !CreateMenus())
return false;

// TODO: Enter fullscreen if enabled.
Expand Down Expand Up @@ -200,10 +373,93 @@ LRESULT PlatformWin32::WndProc(const HWND hwnd, const UINT msg, const WPARAM wPa
break;

case WM_KEYDOWN:
if (wParam == VK_ESCAPE)
// Bit 30 of lParam is the previous key state: ignore auto-repeat, or holding
// F1 down would write a state every few milliseconds.
if (wParam == VK_F1 && (static_cast<ULONG_PTR>(lParam) & (1u << 30)) == 0)
{
platform->SaveStateToStatesDirectory();
return 0;
}
else if (wParam == VK_SPACE)
{
Config::SetCurrent(Config::MAIN_EMULATION_SPEED, FAST_FORWARD_SPEED);
return 0;
}
else if (wParam == VK_F11)
{
platform->ToggleFullscreen();
return 0;
}
else if (wParam == VK_ESCAPE && platform->m_fullscreen)
{
platform->ToggleFullscreen();
return 0;
}
else if (wParam == VK_ESCAPE)
{
platform->RequestShutdown();
}
break;

case WM_KEYUP:
if (wParam == VK_SPACE)
{
Config::SetCurrent(Config::MAIN_EMULATION_SPEED, 1.0f);
return 0;
}
break;

case WM_KILLFOCUS:
// Never leave emulation running fast because Space was released while
// another window had focus and the key-up went elsewhere.
Config::SetCurrent(Config::MAIN_EMULATION_SPEED, 1.0f);
break;

case WM_SYSKEYDOWN:
if (wParam == VK_RETURN && (GetKeyState(VK_MENU) & 0x8000) != 0)
{
platform->ToggleFullscreen();
return 0;
}
return DefWindowProc(hwnd, msg, wParam, lParam);

case WM_INITMENUPOPUP:
if (platform)
platform->RefreshMenu(reinterpret_cast<HMENU>(wParam));
break;

case WM_COMMAND:
{
if (!platform)
break;
const UINT command = LOWORD(wParam);
auto& system = Core::System::GetInstance();
if (command == ID_SAVE_STATE)
{
platform->SaveStateToStatesDirectory();
}
else if (command == ID_PAUSE)
{
const bool paused = Core::GetState(system) == Core::State::Paused;
Core::SetState(system, paused ? Core::State::Running : Core::State::Paused);
}
else if (command == ID_MUTE)
{
Config::SetCurrent(Config::MAIN_AUDIO_MUTED, !Config::Get(Config::MAIN_AUDIO_MUTED));
}
else if (command == ID_FULLSCREEN)
{
platform->ToggleFullscreen();
}
else if (command >= ID_LOAD_STATE_FIRST && command <= ID_LOAD_STATE_LAST)
{
const std::size_t index = command - ID_LOAD_STATE_FIRST;
if (index < platform->m_load_state_paths.size())
State::LoadAs(system, platform->m_load_state_paths[index]);
}
break;
}

case WM_CLOSE:
platform->RequestShutdown();
break;
Expand Down