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