diff --git a/CMakeModules/DuckStationBuildOptions.cmake b/CMakeModules/DuckStationBuildOptions.cmake
index 11add9e3b..5329ae848 100644
--- a/CMakeModules/DuckStationBuildOptions.cmake
+++ b/CMakeModules/DuckStationBuildOptions.cmake
@@ -1,8 +1,6 @@
# Renderer options.
option(ENABLE_OPENGL "Build with OpenGL renderer" ON)
option(ENABLE_VULKAN "Build with Vulkan renderer" ON)
-option(BUILD_QT_FRONTEND "Build the Qt frontend" ON)
-option(BUILD_MINI_FRONTEND "Build the Mini frontend" OFF)
option(BUILD_REGTEST "Build regression test runner" OFF)
option(BUILD_TESTS "Build unit tests" OFF)
option(DISABLE_SSE4 "Build with SSE4 instructions disabled, reduces performance" OFF)
diff --git a/CMakeModules/DuckStationBuildSummary.cmake b/CMakeModules/DuckStationBuildSummary.cmake
index f2f27d496..c66ed795f 100644
--- a/CMakeModules/DuckStationBuildSummary.cmake
+++ b/CMakeModules/DuckStationBuildSummary.cmake
@@ -16,17 +16,11 @@ if(ENABLE_WAYLAND)
message(STATUS "Building with Wayland support.")
endif()
-if(BUILD_QT_FRONTEND)
- message(STATUS "Building Qt frontend.")
-endif()
-if(BUILD_NOGUI_FRONTEND)
- message(STATUS "Building NoGUI frontend.")
-endif()
if(BUILD_REGTEST)
- message(STATUS "Building RegTest frontend.")
+ message(STATUS "[Extra] Building RegTest frontend.")
endif()
if(BUILD_TESTS)
- message(STATUS "Building unit tests.")
+ message(STATUS "[Extra] Building unit tests.")
endif()
# Refuse to build in hostile package environments. The code and build script licenses do not allow for
diff --git a/CMakeModules/DuckStationDependencies.cmake b/CMakeModules/DuckStationDependencies.cmake
index d1727c156..390de8217 100644
--- a/CMakeModules/DuckStationDependencies.cmake
+++ b/CMakeModules/DuckStationDependencies.cmake
@@ -95,25 +95,23 @@ foreach(dep zstd WebP PNG libjpeg-turbo freetype harfbuzz plutosvg cpuinfo
endif()
endforeach()
-if(BUILD_QT_FRONTEND)
- # All our builds include Qt, so this is not a problem.
- set(QT_NO_PRIVATE_MODULE_WARNING ON)
+# All our builds include Qt, so this is not a problem.
+set(QT_NO_PRIVATE_MODULE_WARNING ON)
- # Should be prebuilt.
- if(LINUX)
- find_package(Qt6 6.10.2 REQUIRED
- NO_DEFAULT_PATH PATHS "${DEPS_PATH}/lib/cmake/Qt6"
- COMPONENTS Core Gui GuiPrivate Widgets LinguistTools DBus)
- else()
- find_package(Qt6 6.10.2 REQUIRED
- NO_DEFAULT_PATH PATHS "${DEPS_PATH}/lib/cmake/Qt6"
- COMPONENTS Core Gui GuiPrivate Widgets LinguistTools)
- endif()
+# Should be prebuilt.
+if(LINUX)
+find_package(Qt6 6.10.2 REQUIRED
+ NO_DEFAULT_PATH PATHS "${DEPS_PATH}/lib/cmake/Qt6"
+ COMPONENTS Core Gui GuiPrivate Widgets LinguistTools DBus)
+else()
+find_package(Qt6 6.10.2 REQUIRED
+ NO_DEFAULT_PATH PATHS "${DEPS_PATH}/lib/cmake/Qt6"
+ COMPONENTS Core Gui GuiPrivate Widgets LinguistTools)
+endif()
- # Have to verify it down here, don't want users using unpatched Qt.
- if(NOT Qt6_DIR MATCHES "^${DEPS_PATH}")
- message(FATAL_ERROR "Using incorrect Qt library. Check your dependencies.")
- endif()
+# Have to verify it down here, don't want users using unpatched Qt.
+if(NOT Qt6_DIR MATCHES "^${DEPS_PATH}")
+message(FATAL_ERROR "Using incorrect Qt library. Check your dependencies.")
endif()
# Libraries that are pulled in from host.
diff --git a/duckstation.sln b/duckstation.sln
index 2c23e3be8..a6098aa2a 100644
--- a/duckstation.sln
+++ b/duckstation.sln
@@ -51,8 +51,6 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "reshadefx", "dep\reshadefx\
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "rapidyaml", "dep\rapidyaml\rapidyaml.vcxproj", "{1AD23A8A-4C20-434C-AE6B-0E07759EEB1E}"
EndProject
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "duckstation-mini", "src\duckstation-mini\duckstation-mini.vcxproj", "{FA259BC0-1007-4FD9-8A47-87CC0ECB8445}"
-EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "util-tests", "src\util-tests\util-tests.vcxproj", "{15538AD7-2201-45C2-B088-BBB7F37BD7F5}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "installer", "src\installer\installer.vcxproj", "{CD6D0C84-042E-4C9A-AAB5-D3BDC80273DA}"
@@ -895,28 +893,6 @@ Global
{1AD23A8A-4C20-434C-AE6B-0E07759EEB1E}.ReleaseLTCG-Clang-SSE2|ARM64.ActiveCfg = ReleaseLTCG-Clang|ARM64
{1AD23A8A-4C20-434C-AE6B-0E07759EEB1E}.ReleaseLTCG-Clang-SSE2|x64.ActiveCfg = ReleaseLTCG-Clang-SSE2|x64
{1AD23A8A-4C20-434C-AE6B-0E07759EEB1E}.ReleaseLTCG-Clang-SSE2|x64.Build.0 = ReleaseLTCG-Clang-SSE2|x64
- {FA259BC0-1007-4FD9-8A47-87CC0ECB8445}.Debug|ARM64.ActiveCfg = Debug-Clang|ARM64
- {FA259BC0-1007-4FD9-8A47-87CC0ECB8445}.Debug|x64.ActiveCfg = Debug|x64
- {FA259BC0-1007-4FD9-8A47-87CC0ECB8445}.Debug-Clang|ARM64.ActiveCfg = Debug-Clang|ARM64
- {FA259BC0-1007-4FD9-8A47-87CC0ECB8445}.Debug-Clang|x64.ActiveCfg = Debug-Clang|x64
- {FA259BC0-1007-4FD9-8A47-87CC0ECB8445}.Debug-Clang-SSE2|ARM64.ActiveCfg = Debug-Clang|ARM64
- {FA259BC0-1007-4FD9-8A47-87CC0ECB8445}.Debug-Clang-SSE2|x64.ActiveCfg = Debug-Clang-SSE2|x64
- {FA259BC0-1007-4FD9-8A47-87CC0ECB8445}.DebugFast|ARM64.ActiveCfg = DebugFast-Clang|ARM64
- {FA259BC0-1007-4FD9-8A47-87CC0ECB8445}.DebugFast|x64.ActiveCfg = DebugFast|x64
- {FA259BC0-1007-4FD9-8A47-87CC0ECB8445}.DebugFast-Clang|ARM64.ActiveCfg = DebugFast-Clang|ARM64
- {FA259BC0-1007-4FD9-8A47-87CC0ECB8445}.DebugFast-Clang|x64.ActiveCfg = DebugFast-Clang|x64
- {FA259BC0-1007-4FD9-8A47-87CC0ECB8445}.Devel-Clang|ARM64.ActiveCfg = Devel-Clang|ARM64
- {FA259BC0-1007-4FD9-8A47-87CC0ECB8445}.Devel-Clang|x64.ActiveCfg = Devel-Clang|x64
- {FA259BC0-1007-4FD9-8A47-87CC0ECB8445}.Release|ARM64.ActiveCfg = Release-Clang|ARM64
- {FA259BC0-1007-4FD9-8A47-87CC0ECB8445}.Release|x64.ActiveCfg = Release|x64
- {FA259BC0-1007-4FD9-8A47-87CC0ECB8445}.Release-Clang|ARM64.ActiveCfg = Release-Clang|ARM64
- {FA259BC0-1007-4FD9-8A47-87CC0ECB8445}.Release-Clang|x64.ActiveCfg = Release-Clang|x64
- {FA259BC0-1007-4FD9-8A47-87CC0ECB8445}.ReleaseLTCG|ARM64.ActiveCfg = ReleaseLTCG-Clang|ARM64
- {FA259BC0-1007-4FD9-8A47-87CC0ECB8445}.ReleaseLTCG|x64.ActiveCfg = ReleaseLTCG|x64
- {FA259BC0-1007-4FD9-8A47-87CC0ECB8445}.ReleaseLTCG-Clang|ARM64.ActiveCfg = ReleaseLTCG-Clang|ARM64
- {FA259BC0-1007-4FD9-8A47-87CC0ECB8445}.ReleaseLTCG-Clang|x64.ActiveCfg = ReleaseLTCG-Clang|x64
- {FA259BC0-1007-4FD9-8A47-87CC0ECB8445}.ReleaseLTCG-Clang-SSE2|ARM64.ActiveCfg = ReleaseLTCG-Clang|ARM64
- {FA259BC0-1007-4FD9-8A47-87CC0ECB8445}.ReleaseLTCG-Clang-SSE2|x64.ActiveCfg = ReleaseLTCG-Clang-SSE2|x64
{15538AD7-2201-45C2-B088-BBB7F37BD7F5}.Debug|ARM64.ActiveCfg = Debug-Clang|ARM64
{15538AD7-2201-45C2-B088-BBB7F37BD7F5}.Debug|x64.ActiveCfg = Debug|x64
{15538AD7-2201-45C2-B088-BBB7F37BD7F5}.Debug-Clang|ARM64.ActiveCfg = Debug-Clang|ARM64
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 12cccd7cc..7dc4ceb41 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -2,6 +2,7 @@ add_subdirectory(common)
add_subdirectory(util)
add_subdirectory(core)
add_subdirectory(scmversion)
+add_subdirectory(duckstation-qt)
if(WIN32 OR APPLE)
add_subdirectory(updater)
@@ -12,14 +13,6 @@ if(WIN32)
add_subdirectory(uninstaller)
endif()
-if(BUILD_QT_FRONTEND)
- add_subdirectory(duckstation-qt)
-endif()
-
-if(BUILD_MINI_FRONTEND)
- add_subdirectory(duckstation-mini)
-endif()
-
if(BUILD_REGTEST)
add_subdirectory(duckstation-regtest)
endif()
diff --git a/src/duckstation-mini/CMakeLists.txt b/src/duckstation-mini/CMakeLists.txt
deleted file mode 100644
index a405cf1f4..000000000
--- a/src/duckstation-mini/CMakeLists.txt
+++ /dev/null
@@ -1,20 +0,0 @@
-add_executable(duckstation-mini
- mini_host.cpp
-)
-
-target_include_directories(duckstation-mini PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/..")
-target_link_libraries(duckstation-mini PRIVATE core util common imgui scmversion SDL3::SDL3)
-
-add_core_resources(duckstation-mini)
-
-if(WIN32)
- target_sources(duckstation-mini PRIVATE
- duckstation-mini.manifest
- resource.h
- )
-
- # We want a Windows subsystem application not console.
- set_target_properties(duckstation-mini PROPERTIES
- WIN32_EXECUTABLE TRUE
- DEBUG_POSTFIX "-debug")
-endif()
diff --git a/src/duckstation-mini/duckstation-mini.aps b/src/duckstation-mini/duckstation-mini.aps
deleted file mode 100644
index 147191914..000000000
Binary files a/src/duckstation-mini/duckstation-mini.aps and /dev/null differ
diff --git a/src/duckstation-mini/duckstation-mini.ico b/src/duckstation-mini/duckstation-mini.ico
deleted file mode 100644
index f95b67991..000000000
Binary files a/src/duckstation-mini/duckstation-mini.ico and /dev/null differ
diff --git a/src/duckstation-mini/duckstation-mini.manifest b/src/duckstation-mini/duckstation-mini.manifest
deleted file mode 100644
index 6bbce6d9b..000000000
--- a/src/duckstation-mini/duckstation-mini.manifest
+++ /dev/null
@@ -1,17 +0,0 @@
-
-
-
-
- PerMonitorV2
- true
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/src/duckstation-mini/duckstation-mini.rc b/src/duckstation-mini/duckstation-mini.rc
deleted file mode 100644
index b4ad1577c..000000000
--- a/src/duckstation-mini/duckstation-mini.rc
+++ /dev/null
@@ -1,110 +0,0 @@
-// Microsoft Visual C++ generated resource script.
-//
-#include "resource.h"
-
-#define APSTUDIO_READONLY_SYMBOLS
-/////////////////////////////////////////////////////////////////////////////
-//
-// Generated from the TEXTINCLUDE 2 resource.
-//
-#include "winres.h"
-
-/////////////////////////////////////////////////////////////////////////////
-#undef APSTUDIO_READONLY_SYMBOLS
-
-/////////////////////////////////////////////////////////////////////////////
-// English (Australia) resources
-
-#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENA)
-LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_AUS
-#pragma code_page(1252)
-
-#ifdef APSTUDIO_INVOKED
-/////////////////////////////////////////////////////////////////////////////
-//
-// TEXTINCLUDE
-//
-
-1 TEXTINCLUDE
-BEGIN
- "resource.h\0"
-END
-
-2 TEXTINCLUDE
-BEGIN
- "#include ""winres.h""\r\n"
- "\0"
-END
-
-3 TEXTINCLUDE
-BEGIN
- "\r\n"
- "\0"
-END
-
-#endif // APSTUDIO_INVOKED
-
-
-/////////////////////////////////////////////////////////////////////////////
-//
-// Version
-//
-
-VS_VERSION_INFO VERSIONINFO
- FILEVERSION 1,0,0,1
- PRODUCTVERSION 1,0,0,1
- FILEFLAGSMASK 0x3fL
-#ifdef _DEBUG
- FILEFLAGS 0x1L
-#else
- FILEFLAGS 0x0L
-#endif
- FILEOS 0x40004L
- FILETYPE 0x1L
- FILESUBTYPE 0x0L
-BEGIN
- BLOCK "StringFileInfo"
- BEGIN
- BLOCK "0c0904b0"
- BEGIN
- VALUE "CompanyName", "https://github.com/stenzek/duckstation"
- VALUE "FileDescription", "DuckStation PS1 Emulator"
- VALUE "FileVersion", "1.0.0.1"
- VALUE "InternalName", "duckstation-mini.exe"
- VALUE "LegalCopyright", "Copyright (C) 2020-2025 Stenzek and collaborators"
- VALUE "OriginalFilename", "duckstation-mini.exe"
- VALUE "ProductName", "DuckStation"
- VALUE "ProductVersion", "1.0.0.1"
- END
- END
- BLOCK "VarFileInfo"
- BEGIN
- VALUE "Translation", 0xc09, 1200
- END
-END
-
-
-/////////////////////////////////////////////////////////////////////////////
-//
-// Icon
-//
-
-// Icon with lowest ID value placed first to ensure application icon
-// remains consistent on all systems.
-IDI_ICON1 ICON "duckstation-mini.ico"
-
-#endif // English (Australia) resources
-/////////////////////////////////////////////////////////////////////////////
-
-
-
-#ifndef APSTUDIO_INVOKED
-/////////////////////////////////////////////////////////////////////////////
-//
-// Generated from the TEXTINCLUDE 3 resource.
-//
-
-
-/////////////////////////////////////////////////////////////////////////////
-#endif // not APSTUDIO_INVOKED
-
diff --git a/src/duckstation-mini/duckstation-mini.vcxproj b/src/duckstation-mini/duckstation-mini.vcxproj
deleted file mode 100644
index 479f1d5bd..000000000
--- a/src/duckstation-mini/duckstation-mini.vcxproj
+++ /dev/null
@@ -1,37 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {ee054e08-3799-4a59-a422-18259c105ffd}
-
-
- {868b98c8-65a1-494b-8346-250a73a48c0a}
-
-
- {57f6206d-f264-4b07-baf8-11b9bbe1f455}
-
-
-
-
-
-
- {FA259BC0-1007-4FD9-8A47-87CC0ECB8445}
- duckstation-mini
-
-
-
-
-
\ No newline at end of file
diff --git a/src/duckstation-mini/duckstation-mini.vcxproj.filters b/src/duckstation-mini/duckstation-mini.vcxproj.filters
deleted file mode 100644
index bf40cc28a..000000000
--- a/src/duckstation-mini/duckstation-mini.vcxproj.filters
+++ /dev/null
@@ -1,18 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/src/duckstation-mini/mini_host.cpp b/src/duckstation-mini/mini_host.cpp
deleted file mode 100644
index 4e395519c..000000000
--- a/src/duckstation-mini/mini_host.cpp
+++ /dev/null
@@ -1,1824 +0,0 @@
-// SPDX-FileCopyrightText: 2019-2025 Connor McLaughlin
-// SPDX-License-Identifier: CC-BY-NC-ND-4.0
-
-#include "scmversion/scmversion.h"
-
-#include "core/achievements.h"
-#include "core/bus.h"
-#include "core/controller.h"
-#include "core/core_private.h"
-#include "core/cpu_core.h"
-#include "core/fullscreenui.h"
-#include "core/fullscreenui_widgets.h"
-#include "core/game_list.h"
-#include "core/gpu.h"
-#include "core/gpu_backend.h"
-#include "core/host.h"
-#include "core/imgui_overlays.h"
-#include "core/settings.h"
-#include "core/system.h"
-#include "core/system_private.h"
-#include "core/video_thread.h"
-
-#include "util/cd_image.h"
-#include "util/gpu_device.h"
-#include "util/imgui_manager.h"
-#include "util/input_manager.h"
-#include "util/sdl_input_source.h"
-#include "util/translation.h"
-
-#include "imgui.h"
-#include "imgui_internal.h"
-#include "imgui_stdlib.h"
-
-#include "common/assert.h"
-#include "common/crash_handler.h"
-#include "common/error.h"
-#include "common/file_system.h"
-#include "common/log.h"
-#include "common/path.h"
-#include "common/string_util.h"
-#include "common/task_queue.h"
-#include "common/threading.h"
-#include "common/time_helpers.h"
-
-#include "IconsEmoji.h"
-#include "fmt/format.h"
-
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-
-#ifdef _WIN32
-#include "common/windows_headers.h"
-#endif
-
-LOG_CHANNEL(Host);
-
-namespace MiniHost {
-
-/// Use two async worker threads, should be enough for most tasks.
-static constexpr u32 NUM_ASYNC_WORKER_THREADS = 2;
-
-static constexpr u32 DEFAULT_WINDOW_WIDTH = 1920;
-static constexpr u32 DEFAULT_WINDOW_HEIGHT = 1080;
-static constexpr float DEFAULT_WINDOW_REFRESH_RATE = 60.0f;
-
-static constexpr auto CORE_THREAD_POLL_INTERVAL =
- std::chrono::milliseconds(8); // how often we'll poll controllers when paused
-
-static bool ParseCommandLineParametersAndInitializeConfig(int argc, char* argv[],
- std::optional& autoboot);
-static void PrintCommandLineVersion();
-static void PrintCommandLineHelp(const char* progname);
-static bool InitializeFoldersAndConfig(Error* error);
-static void InitializeEarlyConsole();
-static void HookSignals();
-static std::string GetResourcePath(std::string_view name, bool allow_override);
-static bool PerformEarlyHardwareChecks();
-static bool EarlyProcessStartup();
-static void WarnAboutInterface();
-static void StartCoreThread();
-static void StopCoreThread();
-static void ProcessCoreThreadEvents(bool block);
-static void ProcessCoreThreadPlatformMessages();
-static void CoreThreadEntryPoint();
-static void CoreThreadMainLoop();
-static void VideoThreadEntryPoint();
-static void UIThreadMainLoop();
-static void ProcessSDLEvent(const SDL_Event* ev);
-static std::string GetWindowTitle(const std::string& game_title);
-static std::optional TranslateSDLWindowInfo(SDL_Window* win, Error* error);
-static bool GetSavedPlatformWindowGeometry(s32* x, s32* y, s32* width, s32* height);
-static void SavePlatformWindowGeometry(s32 x, s32 y, s32 width, s32 height);
-static void DestroySDLWindow();
-
-struct SDLHostState
-{
- // UI thread state
- ALIGN_TO_CACHE_LINE bool batch_mode = false;
- bool start_fullscreen_ui_fullscreen = false;
- bool was_paused_by_focus_loss = false;
- bool ui_thread_running = false;
-
- u32 func_event_id = 0;
-
- SDL_Window* sdl_window = nullptr;
- float sdl_window_scale = 0.0f;
- float sdl_window_refresh_rate = 0.0f;
- WindowInfoPrerotation force_prerotation = WindowInfoPrerotation::Identity;
- bool use_window_fullscreen_mode = false;
-
- Threading::Thread core_thread;
- Threading::Thread video_thread;
- Threading::KernelSemaphore platform_window_updated;
-
- std::mutex state_mutex;
- FullscreenUI::BackgroundProgressCallback* game_list_refresh_progress = nullptr;
-
- // CPU thread state.
- ALIGN_TO_CACHE_LINE std::atomic_bool core_thread_running{false};
- std::mutex core_thread_events_mutex;
- std::condition_variable core_thread_event_done;
- std::condition_variable core_thread_event_posted;
- std::deque, bool>> core_thread_events;
- u32 blocking_core_events_pending = 0;
-};
-
-static SDLHostState s_state;
-ALIGN_TO_CACHE_LINE static TaskQueue s_async_task_queue;
-
-} // namespace MiniHost
-
-//////////////////////////////////////////////////////////////////////////
-// Initialization/Shutdown
-//////////////////////////////////////////////////////////////////////////
-
-bool MiniHost::PerformEarlyHardwareChecks()
-{
- Error error;
- const bool okay = System::PerformEarlyHardwareChecks(&error);
- if (okay && !error.IsValid()) [[likely]]
- return true;
-
- if (okay)
- Host::ReportErrorAsync("Hardware Check Warning", error.GetDescription());
- else
- Host::ReportFatalError("Hardware Check Failed", error.GetDescription());
-
- return okay;
-}
-
-bool MiniHost::EarlyProcessStartup()
-{
- Error error;
- if (!System::ProcessStartup(&error)) [[unlikely]]
- {
- Host::ReportFatalError("Process Startup Failed", error.GetDescription());
- return false;
- }
-
- s_state.func_event_id = SDL_RegisterEvents(1);
- if (s_state.func_event_id == static_cast(-1))
- {
- Host::ReportFatalError("Error", TinyString::from_format("SDL_RegisterEvents() failed: {}", SDL_GetError()));
- return false;
- }
-
-#ifdef __linux__
- // If we're running without a windowing system, force fullscreen.
- if (const char* video_driver = SDL_GetCurrentVideoDriver(); video_driver && std::strcmp(video_driver, "kmsdrm") == 0)
- {
- fprintf(stderr, "kmsdrm video driver detected, using fullscreen display mode for windows.\n");
- s_state.use_window_fullscreen_mode = true;
- s_state.start_fullscreen_ui_fullscreen = true;
- }
-#endif
-
-#if !__has_include("scmversion/tag.h")
- //
- // To those distributing their own builds or packages of DuckStation, and seeing this message:
- //
- // DuckStation is licensed under the CC-BY-NC-ND-4.0 license.
- //
- // This means that you do NOT have permission to re-distribute your own modified builds of DuckStation.
- // Modifying DuckStation for personal use is fine, but you cannot distribute builds with your changes.
- // As per the CC-BY-NC-ND conditions, you can re-distribute the official builds from https://www.duckstation.org/ and
- // https://github.com/stenzek/duckstation, so long as they are left intact, without modification. I welcome and
- // appreciate any pull requests made to the official repository at https://github.com/stenzek/duckstation.
- //
- // I made the decision to switch to a no-derivatives license because of numerous "forks" that were created purely for
- // generating money for the person who knocked it off, and always died, leaving the community with multiple builds to
- // choose from, most of which were out of date and broken, and endless confusion. Other forks copy/pasted upstream
- // changes without attribution, violating copyright.
- //
- // Thanks, and I hope you understand.
- //
-
- const char* title = "WARNING! You are not using an official release!";
- const char* message = "DuckStation is licensed under the terms of CC-BY-NC-ND-4.0,\n"
- "which does not allow modified builds to be distributed.\n\n"
- "This build is NOT OFFICIAL and may be broken and/or malicious.\n\n"
- "You should download an official build from https://www.duckstation.org/.";
-
- Host::AddIconOSDMessage(OSDMessageType::Error, "OfficialReleaseWarning", ICON_EMOJI_WARNING, title, message);
-#endif
-
- return true;
-}
-
-bool MiniHost::InitializeFoldersAndConfig(Error* error)
-{
- // Path to the resources directory relative to the application binary.
-// On Windows/Linux, these are in the binary directory.
-// On macOS, this is in the bundle resources directory.
-#ifndef __APPLE__
- static constexpr const char* RESOURCES_RELATIVE_PATH = "resources";
-#else
- static constexpr const char* RESOURCES_RELATIVE_PATH = "../Resources";
-#endif
-
- if (!Core::SetCriticalFolders(RESOURCES_RELATIVE_PATH, error))
- return false;
-
- Error config_error;
- if (!Core::InitializeBaseSettingsLayer(Core::GetBaseSettingsPath(), &config_error))
- return false;
-
- return true;
-}
-
-void Host::SetDefaultSettings(SettingsInterface& si)
-{
-}
-
-void Host::OnSettingsResetToDefault(bool host, bool system, bool controller)
-{
- Host::RunOnCoreThread([]() { System::ApplySettings(false); });
-}
-
-void Host::ReportDebuggerEvent(CPU::DebuggerEvent event, std::string_view message)
-{
- ERROR_LOG("ReportDebuggerEvent(): {}", message);
-}
-
-std::span> Host::GetAvailableLanguageList()
-{
- return {};
-}
-
-const char* Host::GetLanguageName(std::string_view language_code)
-{
- return "";
-}
-
-bool Host::ChangeLanguage(const char* new_language)
-{
- return false;
-}
-
-void Host::AddFixedInputBindings(const SettingsInterface& si)
-{
-}
-
-void Host::OnInputDeviceConnected(InputBindingKey key, std::string_view identifier, std::string_view device_name)
-{
-}
-
-void Host::OnInputDeviceDisconnected(InputBindingKey key, std::string_view identifier)
-{
-}
-
-s32 Host::Internal::GetTranslatedStringImpl(std::string_view context, std::string_view msg,
- std::string_view disambiguation, char* tbuf, size_t tbuf_space)
-{
- if (msg.size() > tbuf_space)
- return -1;
- else if (msg.empty())
- return 0;
-
- std::memcpy(tbuf, msg.data(), msg.size());
- return static_cast(msg.size());
-}
-
-std::string Host::TranslatePluralToString(const char* context, const char* msg, const char* disambiguation, int count)
-{
- TinyString count_str = TinyString::from_format("{}", count);
-
- std::string ret(msg);
- for (;;)
- {
- std::string::size_type pos = ret.find("%n");
- if (pos == std::string::npos)
- break;
-
- ret.replace(pos, pos + 2, count_str.view());
- }
-
- return ret;
-}
-
-SmallString Host::TranslatePluralToSmallString(const char* context, const char* msg, const char* disambiguation,
- int count)
-{
- SmallString ret(msg);
- ret.replace("%n", TinyString::from_format("{}", count));
- return ret;
-}
-
-std::string MiniHost::GetResourcePath(std::string_view filename, bool allow_override)
-{
- return allow_override ? EmuFolders::GetOverridableResourcePath(filename) :
- Path::Combine(EmuFolders::Resources, filename);
-}
-
-bool Host::ResourceFileExists(std::string_view filename, bool allow_override)
-{
- const std::string path = MiniHost::GetResourcePath(filename, allow_override);
- return FileSystem::FileExists(path.c_str());
-}
-
-std::optional> Host::ReadResourceFile(std::string_view filename, bool allow_override, Error* error)
-{
- const std::string path = MiniHost::GetResourcePath(filename, allow_override);
- return FileSystem::ReadBinaryFile(path.c_str(), error);
-}
-
-std::optional Host::ReadResourceFileToString(std::string_view filename, bool allow_override, Error* error)
-{
- const std::string path = MiniHost::GetResourcePath(filename, allow_override);
- return FileSystem::ReadFileToString(path.c_str(), error);
-}
-
-std::optional Host::GetResourceFileTimestamp(std::string_view filename, bool allow_override)
-{
- const std::string path = MiniHost::GetResourcePath(filename, allow_override);
- FILESYSTEM_STAT_DATA sd;
- if (!FileSystem::StatFile(path.c_str(), &sd))
- {
- ERROR_LOG("Failed to stat resource file '{}'", filename);
- return std::nullopt;
- }
-
- return sd.ModificationTime;
-}
-
-void Host::LoadSettings(const SettingsInterface& si, std::unique_lock& lock)
-{
-}
-
-void Host::CheckForSettingsChanges(const Settings& old_settings)
-{
-}
-
-void Host::CommitBaseSettingChanges()
-{
- const auto lock = Core::GetSettingsLock();
- Error error;
- if (!Core::SaveBaseSettingsLayer(&error))
- ERROR_LOG("Failed to save settings: {}", error.GetDescription());
-}
-
-std::optional MiniHost::TranslateSDLWindowInfo(SDL_Window* win, Error* error)
-{
- if (!win)
- {
- Error::SetStringView(error, "Window handle is null.");
- return std::nullopt;
- }
-
- const SDL_WindowFlags window_flags = SDL_GetWindowFlags(win);
- int window_width = 1, window_height = 1;
- int window_px_width = 1, window_px_height = 1;
- SDL_GetWindowSize(win, &window_width, &window_height);
- SDL_GetWindowSizeInPixels(win, &window_px_width, &window_px_height);
-
- // For kmsdrm, assume 720p is 100% scale. Jank, but no other way to do it outside of querying the display size..
- if (s_state.use_window_fullscreen_mode)
- {
- s_state.sdl_window_scale = std::clamp(std::min(window_px_width / 1280.0f, window_px_height / 720.0f), 1.0f, 4.0f);
- INFO_LOG("Using window fullscreen display mode, setting scale to {}", s_state.sdl_window_scale);
- }
- else
- {
- s_state.sdl_window_scale = SDL_GetWindowDisplayScale(win);
- INFO_LOG("Using window scale of {} (window size {}x{}, pixel size {}x{})", s_state.sdl_window_scale, window_width,
- window_height, window_px_width, window_px_height);
- }
-
- const SDL_DisplayMode* dispmode = nullptr;
-
- if (window_flags & SDL_WINDOW_FULLSCREEN)
- {
- if (!(dispmode = SDL_GetWindowFullscreenMode(win)))
- ERROR_LOG("SDL_GetWindowFullscreenMode() failed: {}", SDL_GetError());
- }
-
- if (const SDL_DisplayID display_id = SDL_GetDisplayForWindow(win); display_id != 0)
- {
- if (!(window_flags & SDL_WINDOW_FULLSCREEN))
- {
- if (!(dispmode = SDL_GetDesktopDisplayMode(display_id)))
- ERROR_LOG("SDL_GetDesktopDisplayMode() failed: {}", SDL_GetError());
- }
- }
-
- WindowInfo wi;
- wi.surface_width = static_cast(window_px_width);
- wi.surface_height = static_cast(window_px_height);
- wi.surface_scale = s_state.sdl_window_scale;
- wi.surface_prerotation = s_state.force_prerotation;
-
- // set display refresh rate if available
- if (dispmode && dispmode->refresh_rate > 0.0f)
- {
- INFO_LOG("Display mode refresh rate: {} hz", dispmode->refresh_rate);
- wi.surface_refresh_rate = dispmode->refresh_rate;
- s_state.sdl_window_refresh_rate = dispmode->refresh_rate;
- }
- else
- {
- s_state.sdl_window_refresh_rate = 0.0f;
- }
-
- // SDL's opengl window flag tends to make a mess of pixel formats...
- if (!(SDL_GetWindowFlags(win) & (SDL_WINDOW_OPENGL | SDL_WINDOW_VULKAN)))
- {
- const SDL_PropertiesID props = SDL_GetWindowProperties(win);
- if (props == 0)
- {
- Error::SetStringFmt(error, "SDL_GetWindowProperties() failed: {}", SDL_GetError());
- return std::nullopt;
- }
-
-#if defined(SDL_PLATFORM_WINDOWS)
- wi.type = WindowInfoType::Win32;
- wi.window_handle = SDL_GetPointerProperty(props, SDL_PROP_WINDOW_WIN32_HWND_POINTER, nullptr);
- if (!wi.window_handle)
- {
- Error::SetStringView(error, "SDL_PROP_WINDOW_WIN32_HWND_POINTER not found.");
- return std::nullopt;
- }
-#elif defined(SDL_PLATFORM_MACOS)
- wi.type = WindowInfoType::MacOS;
- wi.window_handle = SDL_GetPointerProperty(props, SDL_PROP_WINDOW_COCOA_WINDOW_POINTER, nullptr);
- if (!wi.window_handle)
- {
- Error::SetStringView(error, "SDL_PROP_WINDOW_COCOA_WINDOW_POINTER not found.");
- return std::nullopt;
- }
-#elif defined(SDL_PLATFORM_LINUX) || defined(SDL_PLATFORM_FREEBSD)
- const std::string_view video_driver = SDL_GetCurrentVideoDriver();
- if (video_driver == "x11")
- {
- wi.display_connection = SDL_GetPointerProperty(props, SDL_PROP_WINDOW_X11_DISPLAY_POINTER, nullptr);
- wi.window_handle = reinterpret_cast(
- static_cast(SDL_GetNumberProperty(props, SDL_PROP_WINDOW_X11_WINDOW_NUMBER, 0)));
- if (!wi.display_connection)
- {
- Error::SetStringView(error, "SDL_PROP_WINDOW_X11_DISPLAY_POINTER not found.");
- return std::nullopt;
- }
- else if (!wi.window_handle)
- {
- Error::SetStringView(error, "SDL_PROP_WINDOW_X11_WINDOW_NUMBER not found.");
- return std::nullopt;
- }
- }
- else if (video_driver == "wayland")
- {
- wi.display_connection = SDL_GetPointerProperty(props, SDL_PROP_WINDOW_WAYLAND_DISPLAY_POINTER, nullptr);
- wi.window_handle = SDL_GetPointerProperty(props, SDL_PROP_WINDOW_WAYLAND_SURFACE_POINTER, nullptr);
- if (!wi.display_connection)
- {
- Error::SetStringView(error, "SDL_PROP_WINDOW_WAYLAND_DISPLAY_POINTER not found.");
- return std::nullopt;
- }
- else if (!wi.window_handle)
- {
- Error::SetStringView(error, "SDL_PROP_WINDOW_WAYLAND_SURFACE_POINTER not found.");
- return std::nullopt;
- }
- }
- else
- {
- Error::SetStringFmt(error, "Video driver {} not supported.", video_driver);
- return std::nullopt;
- }
-#else
-#error Unsupported platform.
-#endif
- }
- else
- {
- // nothing handled, fall back to SDL abstraction
- wi.type = WindowInfoType::SDL;
- wi.window_handle = win;
- }
-
- return wi;
-}
-
-std::optional Host::AcquireRenderWindow(RenderAPI render_api, bool fullscreen, bool exclusive_fullscreen,
- Error* error)
-{
- using namespace MiniHost;
-
- std::optional wi;
-
- Host::RunOnUIThread([render_api, fullscreen, error, &wi]() {
- // Don't try to create multiple windows, kmsdrm breaks.
- DestroySDLWindow();
-
- const std::string window_title = GetWindowTitle(System::GetGameTitle());
- const SDL_PropertiesID props = SDL_CreateProperties();
- SDL_SetStringProperty(props, SDL_PROP_WINDOW_CREATE_TITLE_STRING, window_title.c_str());
-
- SDL_SetBooleanProperty(props, SDL_PROP_WINDOW_CREATE_RESIZABLE_BOOLEAN, true);
- SDL_SetBooleanProperty(props, SDL_PROP_WINDOW_CREATE_FOCUSABLE_BOOLEAN, true);
- SDL_SetBooleanProperty(props, SDL_PROP_WINDOW_CREATE_HIGH_PIXEL_DENSITY_BOOLEAN, true);
-
- if (render_api == RenderAPI::OpenGL || render_api == RenderAPI::OpenGLES)
- SDL_SetBooleanProperty(props, SDL_PROP_WINDOW_CREATE_OPENGL_BOOLEAN, true);
- else if (render_api == RenderAPI::Vulkan)
- SDL_SetBooleanProperty(props, SDL_PROP_WINDOW_CREATE_VULKAN_BOOLEAN, true);
-
- std::optional window_fullscreen_mode;
-
- if (!s_state.use_window_fullscreen_mode)
- {
- if (fullscreen)
- {
- SDL_SetBooleanProperty(props, SDL_PROP_WINDOW_CREATE_BORDERLESS_BOOLEAN, true);
- SDL_SetBooleanProperty(props, SDL_PROP_WINDOW_CREATE_FULLSCREEN_BOOLEAN, true);
- }
-
- if (s32 window_x, window_y, window_width, window_height;
- MiniHost::GetSavedPlatformWindowGeometry(&window_x, &window_y, &window_width, &window_height))
- {
- SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_X_NUMBER, window_x);
- SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_Y_NUMBER, window_y);
- SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_WIDTH_NUMBER, window_width);
- SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_HEIGHT_NUMBER, window_height);
- }
- else
- {
- SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_WIDTH_NUMBER, DEFAULT_WINDOW_WIDTH);
- SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_HEIGHT_NUMBER, DEFAULT_WINDOW_HEIGHT);
- }
- }
- else
- {
- std::optional fullscreen_mode =
- GPUDevice::ExclusiveFullscreenMode::Parse(Core::GetTinyStringSettingValue("GPU", "FullscreenMode"));
- if (!fullscreen_mode.has_value())
- {
- INFO_LOG("Using default fullscreen mode ({}x{})", DEFAULT_WINDOW_WIDTH, DEFAULT_WINDOW_HEIGHT);
- fullscreen_mode =
- GPUDevice::ExclusiveFullscreenMode{DEFAULT_WINDOW_WIDTH, DEFAULT_WINDOW_HEIGHT, DEFAULT_WINDOW_REFRESH_RATE};
- }
-
- window_fullscreen_mode.emplace();
- if (SDL_GetClosestFullscreenDisplayMode(SDL_GetPrimaryDisplay(), fullscreen_mode->width, fullscreen_mode->height,
- fullscreen_mode->refresh_rate, true, &window_fullscreen_mode.value()))
- {
- INFO_LOG("Requesting window fullscreen display mode {}x{} @ {} hz (closest match: {}x{} @ {} hz)",
- fullscreen_mode->width, fullscreen_mode->height, fullscreen_mode->refresh_rate,
- window_fullscreen_mode->w, window_fullscreen_mode->h, window_fullscreen_mode->refresh_rate);
-
- // For GL we need to set the window size to the display mode. Annoyingly can't set the refresh rate...
- SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_WIDTH_NUMBER, window_fullscreen_mode->w);
- SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_HEIGHT_NUMBER, window_fullscreen_mode->h);
- }
- else
- {
- ERROR_LOG("SDL_GetClosestFullscreenDisplayMode() failed: {}", SDL_GetError());
- window_fullscreen_mode.reset();
- }
-
- // Force fullscreen.
- SDL_SetBooleanProperty(props, SDL_PROP_WINDOW_CREATE_FULLSCREEN_BOOLEAN, true);
- }
-
- s_state.sdl_window = SDL_CreateWindowWithProperties(props);
- SDL_DestroyProperties(props);
-
- if (s_state.sdl_window)
- {
- if (window_fullscreen_mode.has_value() &&
- !SDL_SetWindowFullscreenMode(s_state.sdl_window, &window_fullscreen_mode.value()))
- {
- ERROR_LOG("SDL_SetWindowFullscreenMode() failed: {}", SDL_GetError());
- }
-
- wi = TranslateSDLWindowInfo(s_state.sdl_window, error);
- if (!wi.has_value())
- {
- SDL_DestroyWindow(s_state.sdl_window);
- s_state.sdl_window = nullptr;
- }
- }
- else
- {
- Error::SetStringFmt(error, "SDL_CreateWindow() failed: {}", SDL_GetError());
- }
-
- s_state.platform_window_updated.Post();
- });
-
- s_state.platform_window_updated.Wait();
-
- // reload input sources, since it might use the window handle
- Host::RunOnCoreThread(&System::ReloadInputSources);
-
- return wi;
-}
-
-WindowInfoType Host::GetRenderWindowInfoType()
-{
- // Assume SDL for GL/Vulkan.
- return WindowInfoType::SDL;
-}
-
-void Host::ReleaseRenderWindow()
-{
- using namespace MiniHost;
-
- if (!s_state.sdl_window)
- return;
-
- Host::RunOnUIThread([]() {
- DestroySDLWindow();
- s_state.platform_window_updated.Post();
- });
-
- s_state.platform_window_updated.Wait();
-}
-
-void MiniHost::DestroySDLWindow()
-{
- if (!s_state.sdl_window)
- return;
-
- if (!(SDL_GetWindowFlags(s_state.sdl_window) & SDL_WINDOW_FULLSCREEN))
- {
- int window_x = SDL_WINDOWPOS_UNDEFINED, window_y = SDL_WINDOWPOS_UNDEFINED;
- int window_width = DEFAULT_WINDOW_WIDTH, window_height = DEFAULT_WINDOW_HEIGHT;
- SDL_GetWindowPosition(s_state.sdl_window, &window_x, &window_y);
- SDL_GetWindowSize(s_state.sdl_window, &window_width, &window_height);
- MiniHost::SavePlatformWindowGeometry(window_x, window_y, window_width, window_height);
- }
-
- SDL_DestroyWindow(s_state.sdl_window);
- s_state.sdl_window = nullptr;
-}
-
-bool Host::CanChangeFullscreenMode(bool new_fullscreen_state)
-{
- return !MiniHost::s_state.use_window_fullscreen_mode;
-}
-
-void Host::BeginTextInput()
-{
- using namespace MiniHost;
-
- SDL_StartTextInput(s_state.sdl_window);
-}
-
-void Host::EndTextInput()
-{
- // we want to keep getting text events, SDL_StopTextInput() apparently inhibits that
-}
-
-bool Host::CreateAuxiliaryRenderWindow(s32 x, s32 y, u32 width, u32 height, std::string_view title,
- std::string_view icon_name, AuxiliaryRenderWindowUserData userdata,
- AuxiliaryRenderWindowHandle* handle, WindowInfo* wi, Error* error)
-{
- // not here, but could be...
- Error::SetStringView(error, "Not supported.");
- return false;
-}
-
-void Host::DestroyAuxiliaryRenderWindow(AuxiliaryRenderWindowHandle handle, s32* pos_x /* = nullptr */,
- s32* pos_y /* = nullptr */, u32* width /* = nullptr */,
- u32* height /* = nullptr */)
-{
- // noop
-}
-
-bool MiniHost::GetSavedPlatformWindowGeometry(s32* x, s32* y, s32* width, s32* height)
-{
- const auto lock = Core::GetSettingsLock();
-
- SettingsInterface* si = Core::GetBaseSettingsLayer();
- bool result = si->FindIntValue("UI", "MainWindowX", x);
- result = result && si->FindIntValue("UI", "MainWindowY", y);
- result = result && si->FindIntValue("UI", "MainWindowWidth", width);
- result = result && si->FindIntValue("UI", "MainWindowHeight", height);
- return result;
-}
-
-void MiniHost::SavePlatformWindowGeometry(s32 x, s32 y, s32 width, s32 height)
-{
- const auto lock = Core::GetSettingsLock();
- SettingsInterface* si = Core::GetBaseSettingsLayer();
- si->SetIntValue("UI", "MainWindowX", x);
- si->SetIntValue("UI", "MainWindowY", y);
- si->SetIntValue("UI", "MainWindowWidth", width);
- si->SetIntValue("UI", "MainWindowHeight", height);
-}
-
-void MiniHost::UIThreadMainLoop()
-{
- while (s_state.ui_thread_running)
- {
- SDL_Event ev;
- if (!SDL_WaitEvent(&ev))
- continue;
-
- ProcessSDLEvent(&ev);
- }
-}
-
-void MiniHost::ProcessSDLEvent(const SDL_Event* ev)
-{
- switch (ev->type)
- {
- case SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED:
- {
- Host::RunOnCoreThread([window_width = ev->window.data1, window_height = ev->window.data2,
- window_scale = s_state.sdl_window_scale,
- window_refresh_rate = s_state.sdl_window_refresh_rate]() {
- VideoThread::ResizeRenderWindow(window_width, window_height, window_scale, window_refresh_rate);
- });
- }
- break;
-
- case SDL_EVENT_WINDOW_DISPLAY_CHANGED:
- case SDL_EVENT_WINDOW_DISPLAY_SCALE_CHANGED:
- {
- const float new_scale = SDL_GetWindowDisplayScale(s_state.sdl_window);
- if (new_scale != s_state.sdl_window_scale)
- {
- s_state.sdl_window_scale = new_scale;
-
- int window_width = 1, window_height = 1;
- SDL_GetWindowSizeInPixels(s_state.sdl_window, &window_width, &window_height);
- Host::RunOnCoreThread([window_width, window_height, window_scale = s_state.sdl_window_scale,
- window_refresh_rate = s_state.sdl_window_refresh_rate]() {
- VideoThread::ResizeRenderWindow(window_width, window_height, window_scale, window_refresh_rate);
- });
- }
- }
- break;
-
- case SDL_EVENT_WINDOW_CLOSE_REQUESTED:
- {
- Host::RunOnCoreThread([]() { Host::RequestExitApplication(false); });
- }
- break;
-
- case SDL_EVENT_WINDOW_FOCUS_GAINED:
- {
- Host::RunOnCoreThread([]() {
- if (!System::IsValid() || !s_state.was_paused_by_focus_loss)
- return;
-
- System::PauseSystem(false);
- s_state.was_paused_by_focus_loss = false;
- });
- }
- break;
-
- case SDL_EVENT_WINDOW_FOCUS_LOST:
- {
- Host::RunOnCoreThread([]() {
- if (!System::IsRunning() || !g_settings.pause_on_focus_loss)
- return;
-
- s_state.was_paused_by_focus_loss = true;
- System::PauseSystem(true);
- });
- }
- break;
-
- case SDL_EVENT_KEY_DOWN:
- case SDL_EVENT_KEY_UP:
- {
- if (const std::optional key = InputManager::ConvertHostNativeKeyCodeToKeyCode(ev->key.raw))
- {
- Host::RunOnCoreThread([key_code = key.value(), pressed = (ev->type == SDL_EVENT_KEY_DOWN)]() {
- InputManager::InvokeEvents(InputManager::MakeHostKeyboardKey(key_code), pressed ? 1.0f : 0.0f,
- GenericInputBinding::Unknown);
- });
- }
- }
- break;
-
- case SDL_EVENT_TEXT_INPUT:
- {
- if (ImGuiManager::WantsTextInput())
- Host::RunOnCoreThread([text = std::string(ev->text.text)]() { ImGuiManager::AddTextInput(std::move(text)); });
- }
- break;
-
- case SDL_EVENT_MOUSE_MOTION:
- {
- Host::RunOnCoreThread([x = static_cast(ev->motion.x), y = static_cast(ev->motion.y)]() {
- InputManager::UpdatePointerAbsolutePosition(0, x, y);
- ImGuiManager::UpdateMousePosition(x, y);
- });
- }
- break;
-
- case SDL_EVENT_MOUSE_BUTTON_DOWN:
- case SDL_EVENT_MOUSE_BUTTON_UP:
- {
- if (ev->button.button > 0)
- {
- // swap middle/right because sdl orders them differently
- const u8 button = (ev->button.button == 3) ? 1 : ((ev->button.button == 2) ? 2 : (ev->button.button - 1));
- Host::RunOnCoreThread([button, pressed = (ev->type == SDL_EVENT_MOUSE_BUTTON_DOWN)]() {
- InputManager::InvokeEvents(InputManager::MakePointerButtonKey(0, button), pressed ? 1.0f : 0.0f,
- GenericInputBinding::Unknown);
- });
- }
- }
- break;
-
- case SDL_EVENT_MOUSE_WHEEL:
- {
- Host::RunOnCoreThread([x = ev->wheel.x, y = ev->wheel.y]() {
- if (x != 0.0f)
- InputManager::UpdatePointerWheelRelativeDelta(0, InputPointerAxis::WheelX, x);
- if (y != 0.0f)
- InputManager::UpdatePointerWheelRelativeDelta(0, InputPointerAxis::WheelY, y);
- });
- }
- break;
-
- case SDL_EVENT_QUIT:
- {
- Host::RunOnCoreThread([]() { Host::RequestExitApplication(false); });
- }
- break;
-
- default:
- {
- if (ev->type == s_state.func_event_id)
- {
- std::function* pfunc = reinterpret_cast*>(ev->user.data1);
- if (pfunc)
- {
- (*pfunc)();
- delete pfunc;
- }
- }
- else if (SDLInputSource::IsHandledInputEvent(ev))
- {
- Host::RunOnCoreThread([event_copy = *ev]() {
- SDLInputSource* is =
- static_cast(InputManager::GetInputSourceInterface(InputSourceType::SDL));
- if (is)
- is->ProcessSDLEvent(&event_copy);
- });
- }
- }
- break;
- }
-}
-
-void MiniHost::ProcessCoreThreadPlatformMessages()
-{
- // This is lame. On Win32, we need to pump messages, even though *we* don't have any windows
- // on the CPU thread, because SDL creates a hidden window for raw input for some game controllers.
- // If we don't do this, we don't get any controller events.
-#ifdef _WIN32
- MSG msg;
- while (PeekMessageW(&msg, NULL, 0, 0, PM_REMOVE))
- {
- TranslateMessage(&msg);
- DispatchMessageW(&msg);
- }
-#endif
-}
-
-void MiniHost::ProcessCoreThreadEvents(bool block)
-{
- std::unique_lock lock(s_state.core_thread_events_mutex);
-
- for (;;)
- {
- if (s_state.core_thread_events.empty())
- {
- if (!block || !s_state.core_thread_running.load(std::memory_order_acquire))
- return;
-
- // we still need to keep polling the controllers when we're paused
- do
- {
- ProcessCoreThreadPlatformMessages();
- InputManager::PollSources();
- } while (!s_state.core_thread_event_posted.wait_for(lock, CORE_THREAD_POLL_INTERVAL,
- []() { return !s_state.core_thread_events.empty(); }));
- }
-
- // return after processing all events if we had one
- block = false;
-
- auto event = std::move(s_state.core_thread_events.front());
- s_state.core_thread_events.pop_front();
- lock.unlock();
- event.first();
- lock.lock();
-
- if (event.second)
- {
- s_state.blocking_core_events_pending--;
- s_state.core_thread_event_done.notify_one();
- }
- }
-}
-
-void MiniHost::StartCoreThread()
-{
- s_state.core_thread_running.store(true, std::memory_order_release);
- s_state.core_thread.Start(CoreThreadEntryPoint);
-}
-
-void MiniHost::StopCoreThread()
-{
- if (!s_state.core_thread.Joinable())
- return;
-
- {
- std::unique_lock lock(s_state.core_thread_events_mutex);
- s_state.core_thread_running.store(false, std::memory_order_release);
- s_state.core_thread_event_posted.notify_one();
- }
-
- s_state.core_thread.Join();
-}
-
-void MiniHost::CoreThreadEntryPoint()
-{
- Threading::SetNameOfCurrentThread("CPU Thread");
-
- // input source setup must happen on emu thread
- Error error;
- if (!System::CoreThreadInitialize(&error))
- {
- Host::ReportFatalError("CPU Thread Initialization Failed", error.GetDescription());
- return;
- }
-
- // startup worker threads
- s_async_task_queue.SetWorkerCount(NUM_ASYNC_WORKER_THREADS);
-
- // start up GPU thread
- s_state.video_thread.Start(&VideoThreadEntryPoint);
-
- // start the fullscreen UI and get it going
- if (VideoThread::StartFullscreenUI(s_state.start_fullscreen_ui_fullscreen, &error))
- {
- WarnAboutInterface();
-
- // kick a game list refresh if we're not in batch mode
- if (!s_state.batch_mode)
- Host::RefreshGameListAsync(false);
-
- CoreThreadMainLoop();
-
- Host::CancelGameListRefresh();
- }
- else
- {
- Host::ReportFatalError("Error", fmt::format("Failed to start fullscreen UI: {}", error.GetDescription()));
- }
-
- // finish any events off (e.g. shutdown system with save)
- ProcessCoreThreadEvents(false);
-
- if (System::IsValid())
- System::ShutdownSystem(false);
-
- VideoThread::StopFullscreenUI();
- VideoThread::Internal::RequestShutdown();
- s_state.video_thread.Join();
-
- // join worker threads
- s_async_task_queue.SetWorkerCount(0);
-
- System::CoreThreadShutdown();
-
- // Tell the UI thread to shut down.
- Host::RunOnUIThread([]() { s_state.ui_thread_running = false; });
-}
-
-void MiniHost::CoreThreadMainLoop()
-{
- while (s_state.core_thread_running.load(std::memory_order_acquire))
- {
- if (System::IsRunning())
- {
- System::Execute();
- continue;
- }
- else if (!VideoThread::IsUsingThread() && VideoThread::IsRunningIdle())
- {
- ProcessCoreThreadEvents(false);
- if (!VideoThread::IsUsingThread() && VideoThread::IsRunningIdle())
- VideoThread::Internal::DoRunIdle();
- }
-
- ProcessCoreThreadEvents(true);
- }
-}
-
-void MiniHost::VideoThreadEntryPoint()
-{
- Threading::SetNameOfCurrentThread("Video Thread");
- VideoThread::Internal::VideoThreadEntryPoint();
-}
-
-void Host::OnSystemStarting()
-{
- MiniHost::s_state.was_paused_by_focus_loss = false;
-}
-
-void Host::OnSystemStarted()
-{
-}
-
-void Host::OnSystemPaused()
-{
-}
-
-void Host::OnSystemResumed()
-{
-}
-
-void Host::OnSystemStopping()
-{
-}
-
-void Host::OnSystemDestroyed()
-{
-}
-
-void Host::OnSystemAbnormalShutdown(const std::string_view reason)
-{
- VideoThread::RunOnThread([reason = std::string(reason)]() {
- FullscreenUI::OpenInfoMessageDialog(
- ICON_EMOJI_NO_ENTRY_SIGN, "Abnormal System Shutdown",
- fmt::format("Unfortunately, the virtual machine has abnormally shut down and cannot "
- "be recovered. More information about the error is below:\n\n{}",
- reason));
- });
-}
-
-void Host::OnVideoThreadRunIdleChanged(bool is_active)
-{
-}
-
-bool Host::SetScreensaverInhibit(bool inhibit, Error* error)
-{
- if (inhibit)
- {
- if (SDL_DisableScreenSaver())
- {
- Error::SetStringFmt(error, "SDL_DisableScreenSaver() failed: {}", SDL_GetError());
- return false;
- }
-
- return true;
- }
- else
- {
- if (SDL_EnableScreenSaver())
- {
- Error::SetStringFmt(error, "SDL_EnableScreenSaver() failed: {}", SDL_GetError());
- return false;
- }
-
- return true;
- }
-}
-
-void Host::FrameDoneOnVideoThread(GPUBackend* gpu_backend, u32 frame_number)
-{
-}
-
-void Host::OnPerformanceCountersUpdated(const GPUBackend* gpu_backend)
-{
- // noop
-}
-
-void Host::OnAchievementsLoginRequested(Achievements::LoginRequestReason reason)
-{
- // noop
-}
-
-void Host::OnAchievementsLoginSuccess(const char* username, u32 points, u32 sc_points, u32 unread_messages)
-{
- // noop
-}
-
-void Host::OnAchievementsActiveChanged(bool active)
-{
- // noop
-}
-
-void Host::OnAchievementsHardcoreModeChanged(bool enabled)
-{
- // noop
-}
-
-#ifdef RC_CLIENT_SUPPORTS_RAINTEGRATION
-
-void Host::OnRAIntegrationMenuChanged()
-{
- // noop
-}
-
-#endif
-
-void Host::SetMouseMode(bool relative, bool hide_cursor)
-{
- // noop
-}
-
-void Host::OnMediaCaptureStarted()
-{
- // noop
-}
-
-void Host::OnMediaCaptureStopped()
-{
- // noop
-}
-
-void Host::PumpMessagesOnCoreThread()
-{
- MiniHost::ProcessCoreThreadEvents(false);
-}
-
-std::string MiniHost::GetWindowTitle(const std::string& game_title)
-{
-#if defined(_DEBUGFAST)
- static constexpr std::string_view suffix = " [DebugFast]";
-#elif defined(_DEBUG)
- static constexpr std::string_view suffix = " [Debug]";
-#else
- static constexpr std::string_view suffix = std::string_view();
-#endif
-
- if (System::IsShutdown() || game_title.empty())
- return fmt::format("DuckStation {}{}", g_scm_version_str, suffix);
- else
- return fmt::format("{}{}", game_title, suffix);
-}
-
-void MiniHost::WarnAboutInterface()
-{
- Host::AddIconOSDMessage(
- OSDMessageType::Warning, "MiniWarning", "images/duck.png",
- "This is the \"mini\" interface for DuckStation, and is missing many features.",
- "We recommend using the Qt interface instead, which you can download from https://www.duckstation.org/.");
-}
-
-void Host::OnSystemGameChanged(const std::string& disc_path, const std::string& game_serial,
- const std::string& game_name, GameHash game_hash)
-{
- using namespace MiniHost;
-
- VERBOSE_LOG("Host::OnGameChanged(\"{}\", \"{}\", \"{}\")", disc_path, game_serial, game_name);
- if (s_state.sdl_window)
- SDL_SetWindowTitle(s_state.sdl_window, GetWindowTitle(game_name).c_str());
-}
-
-void Host::OnSystemUndoStateAvailabilityChanged(bool available, u64 timestamp)
-{
- //
-}
-
-void Host::RunOnCoreThread(std::function function, bool block /* = false */)
-{
- using namespace MiniHost;
-
- std::unique_lock lock(s_state.core_thread_events_mutex);
- s_state.core_thread_events.emplace_back(std::move(function), block);
- s_state.blocking_core_events_pending += BoolToUInt32(block);
- s_state.core_thread_event_posted.notify_one();
- if (block)
- s_state.core_thread_event_done.wait(lock, []() { return s_state.blocking_core_events_pending == 0; });
-}
-
-void Host::RunOnUIThread(std::function function, bool block /* = false */)
-{
- using namespace MiniHost;
-
- std::function* pfunc = new std::function(std::move(function));
-
- SDL_Event ev;
- ev.user = {};
- ev.type = s_state.func_event_id;
- ev.user.data1 = pfunc;
- SDL_PushEvent(&ev);
-}
-
-void Host::QueueAsyncTask(std::function function)
-{
- MiniHost::s_async_task_queue.SubmitTask(std::move(function));
-}
-
-void Host::WaitForAllAsyncTasks()
-{
- MiniHost::s_async_task_queue.WaitForAll();
-}
-
-void Host::RefreshGameListAsync(bool invalidate_cache)
-{
- using namespace MiniHost;
-
- std::unique_lock lock(s_state.state_mutex);
-
- while (s_state.game_list_refresh_progress)
- {
- lock.unlock();
- CancelGameListRefresh();
- lock.lock();
- }
-
- s_state.game_list_refresh_progress = new FullscreenUI::BackgroundProgressCallback("glrefresh");
- Host::QueueAsyncTask([invalidate_cache]() {
- GameList::Refresh(invalidate_cache, false, s_state.game_list_refresh_progress);
-
- std::unique_lock lock(s_state.state_mutex);
- delete s_state.game_list_refresh_progress;
- s_state.game_list_refresh_progress = nullptr;
- });
-}
-
-void Host::CancelGameListRefresh()
-{
- using namespace MiniHost;
-
- {
- std::unique_lock lock(s_state.state_mutex);
- if (!s_state.game_list_refresh_progress)
- return;
-
- s_state.game_list_refresh_progress->SetCancelled();
- }
-
- Host::WaitForAllAsyncTasks();
-}
-
-void Host::OnGameListEntriesChanged(std::span changed_indices)
-{
- // constantly re-querying, don't need to do anything
-}
-
-std::optional Host::GetTopLevelWindowInfo()
-{
- return MiniHost::TranslateSDLWindowInfo(MiniHost::s_state.sdl_window, nullptr);
-}
-
-void Host::RequestExitApplication(bool allow_confirm)
-{
- Host::RunOnCoreThread([]() {
- System::ShutdownSystem(g_settings.save_state_on_exit);
-
- // clear the running flag, this'll break out of the main CPU loop once the VM is shutdown.
- MiniHost::s_state.core_thread_running.store(false, std::memory_order_release);
- });
-}
-
-void Host::RequestExitBigPicture()
-{
- // sorry dude
-}
-
-void Host::RequestSystemShutdown(bool allow_confirm, bool save_state, bool check_memcard_busy)
-{
- // TODO: Confirm
- if (System::IsValid())
- {
- Host::RunOnCoreThread([save_state]() { System::ShutdownSystem(save_state); });
- }
-}
-
-void Host::ReportFatalError(std::string_view title, std::string_view message)
-{
- // Depending on the platform, this may not be available.
- std::fputs(SmallString::from_format("Fatal error: {}: {}\n", title, message).c_str(), stderr);
- SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, TinyString(title).c_str(), SmallString(message).c_str(), nullptr);
- std::abort();
-}
-
-void Host::ReportErrorAsync(std::string_view title, std::string_view message)
-{
- std::fputs(SmallString::from_format("Error: {}: {}\n", title, message).c_str(), stderr);
- SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, TinyString(title).c_str(), SmallString(message).c_str(), nullptr);
-}
-
-void Host::ReportStatusMessage(std::string_view message)
-{
- Host::AddOSDMessage(OSDMessageType::Info, std::string(message));
-}
-
-void Host::RequestResizeHostDisplay(s32 width, s32 height)
-{
- using namespace MiniHost;
-
- if (!s_state.sdl_window || SDL_GetWindowFlags(s_state.sdl_window) & SDL_WINDOW_FULLSCREEN)
- return;
-
- SDL_SetWindowSize(s_state.sdl_window, width, height);
-}
-
-void Host::OpenURL(std::string_view url)
-{
- if (!SDL_OpenURL(SmallString(url).c_str()))
- ERROR_LOG("SDL_OpenURL({}) failed: {}", url, SDL_GetError());
-}
-
-std::string Host::GetClipboardText()
-{
- std::string ret;
-
- char* text = SDL_GetClipboardText();
- if (text)
- {
- ret = text;
- SDL_free(text);
- }
-
- return ret;
-}
-
-bool Host::CopyTextToClipboard(std::string_view text)
-{
- if (!SDL_SetClipboardText(SmallString(text).c_str()))
- {
- ERROR_LOG("SDL_SetClipboardText({}) failed: {}", text, SDL_GetError());
- return false;
- }
-
- return true;
-}
-
-std::string Host::FormatNumber(NumberFormatType type, s64 value)
-{
- std::string ret;
-
- if (type >= NumberFormatType::ShortDate && type <= NumberFormatType::LongDateTime)
- {
- const char* format;
- switch (type)
- {
- case NumberFormatType::ShortDate:
- format = "%x";
- break;
-
- case NumberFormatType::LongDate:
- format = "%A %B %e %Y";
- break;
-
- case NumberFormatType::ShortTime:
- case NumberFormatType::LongTime:
- format = "%X";
- break;
-
- case NumberFormatType::ShortDateTime:
- format = "%X %x";
- break;
-
- case NumberFormatType::LongDateTime:
- format = "%c";
- break;
-
- DefaultCaseIsUnreachable();
- }
-
- ret.resize(128);
-
- if (const std::optional ltime = Common::LocalTime(static_cast(value)))
- ret.resize(std::strftime(ret.data(), ret.size(), format, <ime.value()));
- else
- ret = "Invalid";
- }
- else
- {
- ret = fmt::format("{}", value);
- }
-
- return ret;
-}
-
-std::string Host::FormatNumber(NumberFormatType type, double value)
-{
- return fmt::format("{}", value);
-}
-
-void Host::ConfirmMessageAsync(std::string_view title, std::string_view message, ConfirmMessageAsyncCallback callback,
- std::string_view yes_text /* = std::string_view() */,
- std::string_view no_text /* = std::string_view() */)
-{
- Host::RunOnCoreThread([title = std::string(title), message = std::string(message), callback = std::move(callback),
- yes_text = std::string(yes_text), no_text = std::move(no_text)]() mutable {
- // in case we haven't started yet...
- if (!FullscreenUI::IsInitialized())
- {
- callback(false);
- return;
- }
-
- // Pause system while dialog is up.
- const bool needs_pause = System::IsValid() && !System::IsPaused();
- if (needs_pause)
- System::PauseSystem(true);
-
- VideoThread::RunOnThread([title = std::string(title), message = std::string(message),
- callback = std::move(callback), yes_text = std::string(yes_text),
- no_text = std::string(no_text), needs_pause]() mutable {
- // Need to reset run idle state _again_ after displaying.
- auto final_callback = [callback = std::move(callback), needs_pause](bool result) {
- FullscreenUI::UpdateRunIdleState();
-
- if (needs_pause)
- {
- Host::RunOnCoreThread([]() {
- if (System::IsValid())
- System::PauseSystem(false);
- });
- }
-
- callback(result);
- };
-
- FullscreenUI::Initialize();
- FullscreenUI::OpenConfirmMessageDialog(ICON_EMOJI_QUESTION_MARK, std::move(title), std::move(message),
- std::move(final_callback), fmt::format(ICON_FA_CHECK " {}", yes_text),
- fmt::format(ICON_FA_XMARK " {}", no_text));
- FullscreenUI::UpdateRunIdleState();
- });
- });
-}
-
-const char* Host::GetDefaultFullscreenUITheme()
-{
- return "";
-}
-
-static void SignalHandler(int signal)
-{
- // First try the normal (graceful) shutdown/exit.
- static bool graceful_shutdown_attempted = false;
- if (!graceful_shutdown_attempted)
- {
- std::fprintf(stderr, "Received CTRL+C, attempting graceful shutdown. Press CTRL+C again to force.\n");
- graceful_shutdown_attempted = true;
- Host::RequestExitApplication(false);
- return;
- }
-
- std::signal(signal, SIG_DFL);
-
- // MacOS is missing std::quick_exit() despite it being C++11...
-#ifndef __APPLE__
- std::quick_exit(1);
-#else
- _Exit(1);
-#endif
-}
-
-void MiniHost::HookSignals()
-{
- std::signal(SIGINT, SignalHandler);
- std::signal(SIGTERM, SignalHandler);
-
-#ifndef _WIN32
- // Ignore SIGCHLD by default on Linux, since we kick off aplay asynchronously.
- struct sigaction sa_chld = {};
- sigemptyset(&sa_chld.sa_mask);
- sa_chld.sa_handler = SIG_IGN;
- sa_chld.sa_flags = SA_RESTART | SA_NOCLDSTOP | SA_NOCLDWAIT;
- sigaction(SIGCHLD, &sa_chld, nullptr);
-#endif
-}
-
-void MiniHost::InitializeEarlyConsole()
-{
- const bool was_console_enabled = Log::IsConsoleOutputEnabled();
- if (!was_console_enabled)
- Log::SetConsoleOutputParams(true);
-}
-
-void MiniHost::PrintCommandLineVersion()
-{
- InitializeEarlyConsole();
-
- std::fprintf(stderr, "DuckStation Version %s (%s)\n", g_scm_tag_str, g_scm_branch_str);
- std::fprintf(stderr, "https://github.com/stenzek/duckstation\n");
- std::fprintf(stderr, "\n");
-}
-
-void MiniHost::PrintCommandLineHelp(const char* progname)
-{
- InitializeEarlyConsole();
-
- PrintCommandLineVersion();
- std::fprintf(stderr, "Usage: %s [parameters] [--] [boot filename]\n", progname);
- std::fprintf(stderr, "\n");
- std::fprintf(stderr, " -help: Displays this information and exits.\n");
- std::fprintf(stderr, " -version: Displays version information and exits.\n");
- std::fprintf(stderr, " -batch: Enables batch mode (exits after powering off).\n");
- std::fprintf(stderr, " -fastboot: Force fast boot for provided filename.\n");
- std::fprintf(stderr, " -slowboot: Force slow boot for provided filename.\n");
- std::fprintf(stderr, " -bios: Boot into the BIOS shell.\n");
- std::fprintf(stderr, " -resume: Load resume save state. If a boot filename is provided,\n"
- " that game's resume state will be loaded, otherwise the most\n"
- " recent resume save state will be loaded.\n");
- std::fprintf(stderr, " -state : Loads specified save state by index. If a boot\n"
- " filename is provided, a per-game state will be loaded, otherwise\n"
- " a global state will be loaded.\n");
- std::fprintf(stderr, " -statefile : Loads state from the specified filename.\n"
- " No boot filename is required with this option.\n");
- std::fprintf(stderr, " -exe : Boot the specified exe instead of loading from disc.\n");
- std::fprintf(stderr, " -fullscreen: Enters fullscreen mode immediately after starting.\n");
- std::fprintf(stderr, " -nofullscreen: Prevents fullscreen mode from triggering if enabled.\n");
- std::fprintf(stderr, " -earlyconsole: Creates console as early as possible, for logging.\n");
- std::fprintf(stderr, " -prerotation : Prerotates output by 90/180/270 degrees.\n");
- std::fprintf(stderr, " --: Signals that no more arguments will follow and the remaining\n"
- " parameters make up the filename. Use when the filename contains\n"
- " spaces or starts with a dash.\n");
- std::fprintf(stderr, "\n");
-}
-
-std::optional& AutoBoot(std::optional& autoboot)
-{
- if (!autoboot)
- autoboot.emplace();
-
- return autoboot;
-}
-
-bool MiniHost::ParseCommandLineParametersAndInitializeConfig(int argc, char* argv[],
- std::optional& autoboot)
-{
- std::optional state_index;
- bool starting_bios = false;
- bool no_more_args = false;
-
- for (int i = 1; i < argc; i++)
- {
- if (!no_more_args)
- {
-#define CHECK_ARG(str) (std::strcmp(argv[i], (str)) == 0)
-#define CHECK_ARG_PARAM(str) (std::strcmp(argv[i], (str)) == 0 && ((i + 1) < argc))
-
- if (CHECK_ARG("-help"))
- {
- PrintCommandLineHelp(argv[0]);
- return false;
- }
- else if (CHECK_ARG("-version"))
- {
- PrintCommandLineVersion();
- return false;
- }
- else if (CHECK_ARG("-batch"))
- {
- INFO_LOG("Command Line: Using batch mode.");
- s_state.batch_mode = true;
- continue;
- }
- else if (CHECK_ARG("-bios"))
- {
- INFO_LOG("Command Line: Starting BIOS.");
- AutoBoot(autoboot);
- starting_bios = true;
- continue;
- }
- else if (CHECK_ARG("-fastboot"))
- {
- INFO_LOG("Command Line: Forcing fast boot.");
- AutoBoot(autoboot)->override_fast_boot = true;
- continue;
- }
- else if (CHECK_ARG("-slowboot"))
- {
- INFO_LOG("Command Line: Forcing slow boot.");
- AutoBoot(autoboot)->override_fast_boot = false;
- continue;
- }
- else if (CHECK_ARG("-resume"))
- {
- state_index = -1;
- INFO_LOG("Command Line: Loading resume state.");
- continue;
- }
- else if (CHECK_ARG_PARAM("-state"))
- {
- state_index = StringUtil::FromChars(argv[++i]);
- if (!state_index.has_value())
- {
- ERROR_LOG("Invalid state index");
- return false;
- }
-
- INFO_LOG("Command Line: Loading state index: {}", state_index.value());
- continue;
- }
- else if (CHECK_ARG_PARAM("-statefile"))
- {
- AutoBoot(autoboot)->save_state = argv[++i];
- INFO_LOG("Command Line: Loading state file: '{}'", autoboot->save_state);
- continue;
- }
- else if (CHECK_ARG_PARAM("-exe"))
- {
- AutoBoot(autoboot)->override_exe = argv[++i];
- INFO_LOG("Command Line: Overriding EXE file: '{}'", autoboot->override_exe);
- continue;
- }
- else if (CHECK_ARG("-fullscreen"))
- {
- INFO_LOG("Command Line: Using fullscreen.");
- AutoBoot(autoboot)->override_fullscreen = true;
- s_state.start_fullscreen_ui_fullscreen = true;
- continue;
- }
- else if (CHECK_ARG("-nofullscreen"))
- {
- INFO_LOG("Command Line: Not using fullscreen.");
- AutoBoot(autoboot)->override_fullscreen = false;
- continue;
- }
- else if (CHECK_ARG("-earlyconsole"))
- {
- InitializeEarlyConsole();
- continue;
- }
- else if (CHECK_ARG_PARAM("-prerotation"))
- {
- const char* prerotation_str = argv[++i];
- if (std::strcmp(prerotation_str, "0") == 0 || StringUtil::EqualNoCase(prerotation_str, "identity"))
- {
- INFO_LOG("Command Line: Forcing surface pre-rotation to identity.");
- s_state.force_prerotation = WindowInfoPrerotation::Identity;
- }
- else if (std::strcmp(prerotation_str, "90") == 0)
- {
- INFO_LOG("Command Line: Forcing surface pre-rotation to 90 degrees clockwise.");
- s_state.force_prerotation = WindowInfoPrerotation::Rotate90Clockwise;
- }
- else if (std::strcmp(prerotation_str, "180") == 0)
- {
- INFO_LOG("Command Line: Forcing surface pre-rotation to 180 degrees clockwise.");
- s_state.force_prerotation = WindowInfoPrerotation::Rotate180Clockwise;
- }
- else if (std::strcmp(prerotation_str, "270") == 0)
- {
- INFO_LOG("Command Line: Forcing surface pre-rotation to 270 degrees clockwise.");
- s_state.force_prerotation = WindowInfoPrerotation::Rotate270Clockwise;
- }
- else
- {
- ERROR_LOG("Invalid prerotation value: {}", prerotation_str);
- return false;
- }
-
- continue;
- }
- else if (CHECK_ARG("--"))
- {
- no_more_args = true;
- continue;
- }
- else if (argv[i][0] == '-')
- {
- Host::ReportFatalError("Error", fmt::format("Unknown parameter: {}", argv[i]));
- return false;
- }
-
-#undef CHECK_ARG
-#undef CHECK_ARG_PARAM
- }
-
- if (autoboot && !autoboot->path.empty())
- autoboot->path += ' ';
- AutoBoot(autoboot)->path += argv[i];
- }
-
- // To do anything useful, we need the config initialized.
- Error error;
- if (!InitializeFoldersAndConfig(&error))
- {
- // NOTE: No point translating this, because no config means the language won't be loaded anyway.
- Host::ReportFatalError("DuckStation", error.GetDescription());
- return EXIT_FAILURE;
- }
-
- // Check the file we're starting actually exists.
-
- if (autoboot && !autoboot->path.empty() && !FileSystem::FileExists(autoboot->path.c_str()) &&
- !CDImage::IsDeviceName(autoboot->path.c_str()))
- {
- Host::ReportFatalError("DuckStation", fmt::format("File '{}' does not exist.", autoboot->path));
- return false;
- }
-
- if (state_index.has_value())
- {
- AutoBoot(autoboot);
-
- if (autoboot->path.empty())
- {
- // loading global state, -1 means resume the last game
- if (state_index.value() < 0)
- autoboot->save_state = System::GetMostRecentResumeSaveStatePath();
- else
- autoboot->save_state = System::GetGlobalSaveStatePath(state_index.value());
- }
- else
- {
- // loading game state
- const std::string game_serial(GameDatabase::GetSerialForPath(autoboot->path.c_str()));
- autoboot->save_state = System::GetGameSaveStatePath(game_serial, state_index.value());
- }
-
- if (autoboot->save_state.empty() || !FileSystem::FileExists(autoboot->save_state.c_str()))
- {
- Host::ReportFatalError("DuckStation", "The specified save state does not exist.");
- return false;
- }
- }
-
- // check autoboot parameters, if we set something like fullscreen without a bios
- // or disc, we don't want to actually start.
- if (autoboot && autoboot->path.empty() && autoboot->save_state.empty() && !starting_bios)
- autoboot.reset();
-
- // if we don't have autoboot, we definitely don't want batch mode (because that'll skip
- // scanning the game list).
- if (s_state.batch_mode)
- {
- if (!autoboot)
- {
- Host::ReportFatalError("DuckStation", "Cannot use batch mode, because no boot filename was specified.");
- return false;
- }
-
- // if using batch mode, immediately refresh the game list so the data is available
- GameList::Refresh(false, true);
- }
-
- return true;
-}
-
-#include
-
-int main(int argc, char* argv[])
-{
- using namespace MiniHost;
-
- CrashHandler::Install(&Bus::CleanupMemoryMap);
-
- if (!PerformEarlyHardwareChecks())
- return EXIT_FAILURE;
-
- if (!SDL_InitSubSystem(SDL_INIT_VIDEO | SDL_INIT_EVENTS))
- {
- Host::ReportFatalError("Error", TinyString::from_format("SDL_InitSubSystem() failed: {}", SDL_GetError()));
- return EXIT_FAILURE;
- }
-
- if (!EarlyProcessStartup())
- return EXIT_FAILURE;
-
- std::optional autoboot;
- if (!ParseCommandLineParametersAndInitializeConfig(argc, argv, autoboot))
- return EXIT_FAILURE;
-
- // the rest of initialization happens on the CPU thread.
- HookSignals();
-
- // prevent input source polling on CPU thread...
- SDLInputSource::ALLOW_EVENT_POLLING = false;
- s_state.ui_thread_running = true;
- StartCoreThread();
-
- // process autoboot early, that way we can set the fullscreen flag
- if (autoboot)
- {
- s_state.start_fullscreen_ui_fullscreen =
- s_state.start_fullscreen_ui_fullscreen || autoboot->override_fullscreen.value_or(false);
- Host::RunOnCoreThread([params = std::move(autoboot.value())]() mutable {
- Error error;
- if (!System::BootSystem(std::move(params), &error))
- Host::ReportErrorAsync("Failed to boot system", error.GetDescription());
- });
- }
-
- UIThreadMainLoop();
-
- StopCoreThread();
-
- System::ProcessShutdown();
-
- // Ensure log is flushed.
- Log::SetFileOutputParams(false, nullptr);
-
- Core::SaveBaseSettingsLayer(nullptr);
-
- SDL_QuitSubSystem(SDL_INIT_VIDEO | SDL_INIT_EVENTS);
-
- return EXIT_SUCCESS;
-}
diff --git a/src/duckstation-mini/resource.h b/src/duckstation-mini/resource.h
deleted file mode 100644
index 966189314..000000000
--- a/src/duckstation-mini/resource.h
+++ /dev/null
@@ -1,16 +0,0 @@
-//{{NO_DEPENDENCIES}}
-// Microsoft Visual C++ generated include file.
-// Used by duckstation-sdl.rc
-//
-#define IDI_ICON1 102
-
-// Next default values for new objects
-//
-#ifdef APSTUDIO_INVOKED
-#ifndef APSTUDIO_READONLY_SYMBOLS
-#define _APS_NEXT_RESOURCE_VALUE 103
-#define _APS_NEXT_COMMAND_VALUE 40001
-#define _APS_NEXT_CONTROL_VALUE 1001
-#define _APS_NEXT_SYMED_VALUE 101
-#endif
-#endif