diff --git a/.github/workflows/rolling-release.yml b/.github/workflows/rolling-release.yml index 698d1bebd..46f815090 100644 --- a/.github/workflows/rolling-release.yml +++ b/.github/workflows/rolling-release.yml @@ -144,6 +144,12 @@ jobs: with: fetch-depth: 0 + - name: Install packages + shell: bash + run: | + sudo apt-get update + sudo apt-get -y install gcc-aarch64-linux-gnu g++-aarch64-linux-gnu + - name: Compile and zip Linux x64 libretro core shell: bash run: | @@ -151,13 +157,28 @@ jobs: cd build-libretro-linux-x64 cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_LIBRETRO_CORE=ON .. cmake --build . --parallel 2 - zip -j duckstation_libretro.so.zip duckstation_libretro.so + zip -j duckstation_libretro_x64.so.zip duckstation_libretro.so - name: Upload Linux x64 libretro core uses: actions/upload-artifact@v1 with: name: "linux-libretro" - path: "build-libretro-linux-x64/duckstation_libretro.so.zip" + path: "build-libretro-linux-x64/duckstation_libretro_x64.so.zip" + + - name: Compile and zip Linux AArch64 libretro core + shell: bash + run: | + mkdir build-libretro-linux-aarch64 + cd build-libretro-linux-aarch64 + cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_LIBRETRO_CORE=ON -DCMAKE_TOOLCHAIN_FILE=../CMakeModules/aarch64-cross-toolchain.cmake .. + cmake --build . --parallel 2 + zip -j duckstation_libretro_linux_aarch64.so.zip duckstation_libretro.so + + - name: Upload Linux AArch64 libretro core + uses: actions/upload-artifact@v1 + with: + name: "linux-libretro" + path: "build-libretro-linux-aarch64/duckstation_libretro_linux_aarch64.so.zip" - name: Compile and zip Android AArch64 libretro core shell: bash @@ -271,7 +292,8 @@ jobs: linux-x64-appimage-sdl-zsync/duckstation-sdl-x64.AppImage.zsync linux-x64-appimage-qt/duckstation-qt-x64.AppImage linux-x64-appimage-qt-zsync/duckstation-qt-x64.AppImage.zsync - linux-libretro/duckstation_libretro.so.zip + linux-libretro/duckstation_libretro_x64.so.zip + linux-libretro/duckstation_libretro_linux_aarch64.so.zip linux-libretro/duckstation_libretro_android_aarch64.so.zip android/duckstation-android-aarch64.apk diff --git a/CMakeModules/aarch64-cross-toolchain.cmake b/CMakeModules/aarch64-cross-toolchain.cmake new file mode 100644 index 000000000..1391797b7 --- /dev/null +++ b/CMakeModules/aarch64-cross-toolchain.cmake @@ -0,0 +1,14 @@ +# Source: https://github.com/stenzek/duckstation/issues/626#issuecomment-660718306 + +# Target system +SET(CMAKE_SYSTEM_NAME Linux) +SET(CMAKE_SYSTEM_PROCESSOR aarch64) +SET(CMAKE_SYSTEM_VERSION 1) +set(CMAKE_CROSSCOMPILING TRUE) + +# Cross compiler +SET(CMAKE_C_COMPILER aarch64-linux-gnu-gcc) +SET(CMAKE_CXX_COMPILER aarch64-linux-gnu-g++) +set(CMAKE_LIBRARY_ARCHITECTURE aarch64-linux-gnu) + +set(THREADS_PTHREAD_ARG "0" CACHE STRING "Result from TRY_RUN" FORCE) diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 6d3c71df8..b7fb79e4f 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -12,6 +12,7 @@ The following people have contributed to the project in some way, and are credit - @phoe-nix - Chinese (Simplified) - Sorer - @MojoJojoDojo - Hebrew - Hipnosis183 - Spanish +- @RaydenX93 - Italian ## Game Compatibility Database - @Zet-sensei diff --git a/README.md b/README.md index 99ecff446..5b78ebf41 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,8 @@ A "BIOS" ROM image is required to to start the emulator and to play games. You c ## Latest News +- 2020/08/20: Per-game setting overrides added. Mostly for compatibility, but some options are customizable. +- 2020/08/19: CPU PGXP mode added. It is very slow and incompatible with the recompiler, only use for games which need it. - 2020/08/15: Playlist support/single memcard for multi-disc games in Qt frontend added. - 2020/08/07: Automatic updater for standalone Windows builds. - 2020/08/01: Initial PGXP (geometry/perspective correction) support. @@ -36,6 +38,7 @@ Other features include: - CPU Recompiler/JIT (x86-64 and AArch64) - Hardware (D3D11, OpenGL, Vulkan) and software rendering - Upscaling and true colour (24-bit) in hardware renderers + - PGXP for geometry precision and texture correction - "Fast boot" for skipping BIOS splash/intro - Save state support - Windows, Linux, **highly experimental** macOS support @@ -228,7 +231,8 @@ DuckStation is available as a libretro core, which can be loaded into a frontend Prebuilt binaries for 64-bit Windows, Linux and Android can be found on the releases page. Direct links: - 64-bit Windows: https://github.com/stenzek/duckstation/releases/download/latest/duckstation_libretro.dll.zip -- 64-bit Linux: https://github.com/stenzek/duckstation/releases/download/latest/duckstation_libretro.so.zip +- 64-bit Linux: https://github.com/stenzek/duckstation/releases/download/latest/duckstation_libretro_x64.so.zip +- AArch64 Linux: https://github.com/stenzek/duckstation/releases/download/latest/duckstation_libretro_linux_aarch64.so.zip - AArch64 Android: https://github.com/stenzek/duckstation/releases/download/latest/duckstation_libretro_android_aarch64.so.zip To use, download and extract, and install the core file in RetroArch or your preferred frontend. diff --git a/android/app/src/cpp/android_host_interface.cpp b/android/app/src/cpp/android_host_interface.cpp index 7235e2dc4..d13d6c205 100644 --- a/android/app/src/cpp/android_host_interface.cpp +++ b/android/app/src/cpp/android_host_interface.cpp @@ -132,6 +132,7 @@ void AndroidHostInterface::SetUserDirectory() void AndroidHostInterface::LoadSettings() { CommonHostInterface::LoadSettings(m_settings_interface); + CommonHostInterface::FixIncompatibleSettings(false); CommonHostInterface::UpdateInputMap(m_settings_interface); } diff --git a/data/database/compatibility.xml b/data/database/compatibility.xml index 2b5e6fb90..110bba0e5 100644 --- a/data/database/compatibility.xml +++ b/data/database/compatibility.xml @@ -583,6 +583,10 @@ No Issues 0.1-1308-g622e50fa + + No Issues + 0.1-1529-ga895c027 + No Issues 0.1-1308-g622e50fa @@ -591,6 +595,14 @@ No Issues 0.1-1115-g0b261e8 + + No Issues + 0.1-1529-ga895c027 + + + No Issues + 0.1-1529-ga895c027 + No Issues When upscale the game 4x or above the green boxes (where the commands are showned) seems to be cutted there is a black line, 4 lines to more specific dividing the gren box. @@ -1314,11 +1326,40 @@ Tetris with Card Captor Sakura (Japan) No Issues 0.1-986-gfc911de1 + + Graphical/Audio Issues + 0.1-1530-g6d75f42e + Various graphical errors in menu (Issue #772). + + + No Issues + 0.1-1530-g6d75f42e + + + No Issues + 0.1-1530-g6d75f42e + + + No Issues + 0.1-1530-g6d75f42e + Crashes In-Game 0.1-1490-g76978986 Hangs when choosing Armored core (Issue #751). + + No Issues + 0.1-1530-g6d75f42e + + + No Issues + 0.1-1530-g6d75f42e + + + No Issues + 0.1-1530-g6d75f42e + No Issues 0.1-1490-g76978986 @@ -1623,6 +1664,10 @@ Tetris with Card Captor Sakura (Japan) No Issues + + No Issues + 0.1-1529-ga895c027 + No Issues 0.1-826-g712168c @@ -2006,6 +2051,30 @@ Tetris with Card Captor Sakura (Japan) No Issues 0.1-1445-ge5c46a54 + + No Issues + 0.1-1530-g6d75f42e + + + No Issues + 0.1-1530-g6d75f42e + + + No Issues + 0.1-1530-g6d75f42e + + + No Issues + 0.1-1530-g6d75f42e + + + No Issues + 0.1-1530-g6d75f42e + + + No Issues + 0.1-1530-g6d75f42e + No Issues 0.1-884-g096ed21 @@ -2035,6 +2104,10 @@ Tetris with Card Captor Sakura (Japan) No Issues 0.1-774-g5a1b008 + + No Issues + 0.1-1529-ga895c027 + No Issues 2D elements on title screen @@ -2369,6 +2442,14 @@ Tetris with Card Captor Sakura (Japan) No Issues + + No Issues + 0.1-1529-ga895c027 + + + No Issues + 0.1-1529-ga895c027 + No Issues 0.1-1308-g622e50fa diff --git a/data/database/gamesettings.ini b/data/database/gamesettings.ini new file mode 100644 index 000000000..22b2b8ac1 --- /dev/null +++ b/data/database/gamesettings.ini @@ -0,0 +1,75 @@ +# DuckStation Game Settings + + +# Croc - Legend of the Gobbos (USA) (SLUS-00530) +[SLUS-00530] +EnablePGXPCPUMode = true + + +# Croc 2 (USA) (SLUS-00634) +[SLUS-00634] +EnablePGXPCPUMode = true + + +# Doom (USA) (Rev 1) (SLUS-00077) +[SLUS-00077] +DisableUpscaling = true + + +# Pop'n Music 6 (Japan) (SLPM-87089) +[SLPM-87089] +EnableInterlacing = true + + +# Mr. Driller G (Japan) (SLPS-03336) +[SLPS-03336] +EnableInterlacing = true + + +# Pro Pinball - Big Race USA (USA) (SLUS-01260) +[SLUS-01260] +ForceSoftwareRenderer = true +EnableInterlacing = true + + +# Pro Pinball - Fantastic Journey (USA) (SLUS-01261) +[SLUS-01261] +ForceSoftwareRenderer = true +EnableInterlacing = true + + +# True Pinball (USA) (SLUS-00337) +[SLUS-00337] +EnableInterlacing = true + + +# Dead or Alive (USA) (SLUS-00606) +[SLUS-00606] +EnableInterlacing = true + + +# Shinobi no Sato no Jintori Gassen (Japan) (SLPS-03553) +[SLPS-03553] +EnableInterlacing = true + + +# Time Bokan Series: Bokan desu yo (SLPS-01211) +[SLPS-01211] +EnableInterlacing = true + + +# Rat Attack! (USA) (SLUS-00656) +[SLUS-00656] +EnableInterlacing = true + + +# Arcade Party Pak (USA) (SLUS-00952) +[SLUS-00952] +EnableInterlacing = true + + +# SLUS-01222 (Colin McRae Rally 2.0 (USA) (En,Fr,Es)) +[SLUS-01222] +DisplayActiveStartOffset = 64 +DisplayActiveEndOffset = 68 + diff --git a/src/common/byte_stream.cpp b/src/common/byte_stream.cpp index eb2fb3c70..31f85d524 100644 --- a/src/common/byte_stream.cpp +++ b/src/common/byte_stream.cpp @@ -267,10 +267,12 @@ public: { #if WIN32 // delete the temporary file - if (!DeleteFileA(m_temporaryFileName.c_str())) + if (!DeleteFileW(StringUtil::UTF8StringToWideString(m_temporaryFileName).c_str())) + { Log_WarningPrintf( "AtomicUpdatedFileByteStream::~AtomicUpdatedFileByteStream(): Failed to delete temporary file '%s'", m_temporaryFileName.c_str()); + } #else // delete the temporary file if (remove(m_temporaryFileName.c_str()) < 0) @@ -308,7 +310,8 @@ public: #ifdef WIN32 // move the atomic file name to the original file name - if (!MoveFileExA(m_temporaryFileName.c_str(), m_originalFileName.c_str(), MOVEFILE_REPLACE_EXISTING)) + if (!MoveFileExW(StringUtil::UTF8StringToWideString(m_temporaryFileName).c_str(), + StringUtil::UTF8StringToWideString(m_originalFileName).c_str(), MOVEFILE_REPLACE_EXISTING)) { Log_WarningPrintf("AtomicUpdatedFileByteStream::Commit(): Failed to rename temporary file '%s' to '%s'", m_temporaryFileName.c_str(), m_originalFileName.c_str()); diff --git a/src/common/types.h b/src/common/types.h index 02657135d..6b94fd87f 100644 --- a/src/common/types.h +++ b/src/common/types.h @@ -15,6 +15,13 @@ #endif #endif +// Force inline in non-debug helper +#ifdef _DEBUG +#define ALWAYS_INLINE_RELEASE +#else +#define ALWAYS_INLINE_RELEASE ALWAYS_INLINE +#endif + // unreferenced parameter macro #ifndef UNREFERENCED_VARIABLE #if defined(_MSC_VER) diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 6b68305eb..4d2cbad5f 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -26,6 +26,8 @@ add_library(core dma.h game_list.cpp game_list.h + game_settings.cpp + game_settings.h gpu.cpp gpu.h gpu_commands.cpp @@ -96,7 +98,7 @@ set(RECOMPILER_SRCS target_include_directories(core PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/..") target_include_directories(core PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/..") -target_link_libraries(core PUBLIC Threads::Threads common imgui tinyxml2 zlib vulkan-loader) +target_link_libraries(core PUBLIC Threads::Threads common imgui tinyxml2 zlib vulkan-loader simpleini) target_link_libraries(core PRIVATE glad stb) if(WIN32) diff --git a/src/core/analog_controller.cpp b/src/core/analog_controller.cpp index 41165cec6..b745386e3 100644 --- a/src/core/analog_controller.cpp +++ b/src/core/analog_controller.cpp @@ -53,8 +53,12 @@ bool AnalogController::DoState(StateWrapper& sw) if (old_analog_mode != m_analog_mode) { - g_host_interface->AddFormattedOSDMessage(5.0f, "Controller %u switched to %s mode.", m_index + 1u, - m_analog_mode ? "analog" : "digital"); + g_host_interface->AddFormattedOSDMessage( + 5.0f, + m_analog_mode ? + g_host_interface->TranslateString("AnalogController", "Controller %u switched to analog mode.") : + g_host_interface->TranslateString("AnalogController", "Controller %u switched to digital mode."), + m_index + 1u); } } return true; @@ -95,8 +99,13 @@ void AnalogController::SetButtonState(Button button, bool pressed) { if (m_analog_locked) { - g_host_interface->AddFormattedOSDMessage(5.0f, "Controller %u is locked to %s mode by the game.", m_index + 1u, - m_analog_mode ? "analog" : "digital"); + g_host_interface->AddFormattedOSDMessage( + 5.0f, + m_analog_mode ? g_host_interface->TranslateString("AnalogController", + "Controller %u is locked to analog mode by the game.") : + g_host_interface->TranslateString("AnalogController", + "Controller %u is locked to digital mode by the game."), + m_index + 1u); } else { @@ -155,8 +164,11 @@ void AnalogController::SetAnalogMode(bool enabled) return; Log_InfoPrintf("Controller %u switched to %s mode.", m_index + 1u, enabled ? "analog" : "digital"); - g_host_interface->AddFormattedOSDMessage(5.0f, "Controller %u switched to %s mode.", m_index + 1u, - enabled ? "analog" : "digital"); + g_host_interface->AddFormattedOSDMessage( + 5.0f, + enabled ? g_host_interface->TranslateString("AnalogController", "Controller %u switched to analog mode.") : + g_host_interface->TranslateString("AnalogController", "Controller %u switched to digital mode."), + m_index + 1u); m_analog_mode = enabled; } diff --git a/src/core/cdrom.cpp b/src/core/cdrom.cpp index b9e16f73c..42bb87b9a 100644 --- a/src/core/cdrom.cpp +++ b/src/core/cdrom.cpp @@ -1917,6 +1917,17 @@ void CDROM::DoSectorRead() // TODO: Error handling const CDImage::SubChannelQ& subq = m_reader.GetSectorSubQ(); + if (subq.IsCRCValid()) + { + m_last_subq = subq; + } + else + { + const CDImage::Position pos(CDImage::Position::FromLBA(m_current_lba)); + Log_DevPrintf("Sector %u [%02u:%02u:%02u] has invalid subchannel Q", m_current_lba, pos.minute, pos.second, + pos.frame); + } + if (subq.track_number_bcd == CDImage::LEAD_OUT_TRACK_NUMBER) { Log_DevPrintf("Read reached lead-out area of disc at LBA %u, pausing", m_reader.GetLastReadSector()); @@ -1948,17 +1959,6 @@ void CDROM::DoSectorRead() ProcessDataSectorHeader(m_reader.GetSectorBuffer().data()); } - if (subq.IsCRCValid()) - { - m_last_subq = subq; - } - else - { - const CDImage::Position pos(CDImage::Position::FromLBA(m_current_lba)); - Log_DevPrintf("Sector %u [%02u:%02u:%02u] has invalid subchannel Q", m_current_lba, pos.minute, pos.second, - pos.frame); - } - if (is_data_sector && m_drive_state == DriveState::Reading) { ProcessDataSector(m_reader.GetSectorBuffer().data(), subq); diff --git a/src/core/core.vcxproj b/src/core/core.vcxproj index 07b9b2198..bc5aeadf1 100644 --- a/src/core/core.vcxproj +++ b/src/core/core.vcxproj @@ -60,6 +60,7 @@ + @@ -107,6 +108,7 @@ + @@ -142,6 +144,12 @@ + + {bb08260f-6fbc-46af-8924-090ee71360c6} + + + {3773f4cc-614e-4028-8595-22e08ca649e3} + {ed601289-ac1a-46b8-a8ed-17db9eb73423} @@ -299,7 +307,7 @@ WITH_RECOMPILER=1;_CRT_SECURE_NO_WARNINGS;WIN32;_DEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions) true ProgramDatabase - $(SolutionDir)dep\msvc\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\stb\include;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\xbyak\xbyak;$(SolutionDir)dep\tinyxml2\include;$(SolutionDir)dep\zlib\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)src;%(AdditionalIncludeDirectories) + $(SolutionDir)dep\msvc\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\stb\include;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\xbyak\xbyak;$(SolutionDir)dep\tinyxml2\include;$(SolutionDir)dep\simpleini\include;$(SolutionDir)dep\zlib\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)src;%(AdditionalIncludeDirectories) true false stdcpp17 @@ -325,7 +333,7 @@ WITH_RECOMPILER=1;_CRT_SECURE_NO_WARNINGS;WIN32;_DEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions) true ProgramDatabase - $(SolutionDir)dep\msvc\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\stb\include;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\xbyak\xbyak;$(SolutionDir)dep\tinyxml2\include;$(SolutionDir)dep\zlib\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)src;%(AdditionalIncludeDirectories) + $(SolutionDir)dep\msvc\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\stb\include;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\xbyak\xbyak;$(SolutionDir)dep\tinyxml2\include;$(SolutionDir)dep\simpleini\include;$(SolutionDir)dep\zlib\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)src;%(AdditionalIncludeDirectories) true false stdcpp17 @@ -351,7 +359,7 @@ WITH_RECOMPILER=1;_ITERATOR_DEBUG_LEVEL=1;_CRT_SECURE_NO_WARNINGS;WIN32;_DEBUGFAST;_DEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions) true ProgramDatabase - $(SolutionDir)dep\msvc\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\stb\include;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\xbyak\xbyak;$(SolutionDir)dep\tinyxml2\include;$(SolutionDir)dep\zlib\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)src;%(AdditionalIncludeDirectories) + $(SolutionDir)dep\msvc\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\stb\include;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\xbyak\xbyak;$(SolutionDir)dep\tinyxml2\include;$(SolutionDir)dep\simpleini\include;$(SolutionDir)dep\zlib\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)src;%(AdditionalIncludeDirectories) Default true false @@ -380,7 +388,7 @@ WITH_RECOMPILER=1;_ITERATOR_DEBUG_LEVEL=1;_CRT_SECURE_NO_WARNINGS;WIN32;_DEBUGFAST;_DEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions) true ProgramDatabase - $(SolutionDir)dep\msvc\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\stb\include;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\xbyak\xbyak;$(SolutionDir)dep\tinyxml2\include;$(SolutionDir)dep\zlib\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)src;%(AdditionalIncludeDirectories) + $(SolutionDir)dep\msvc\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\stb\include;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\xbyak\xbyak;$(SolutionDir)dep\tinyxml2\include;$(SolutionDir)dep\simpleini\include;$(SolutionDir)dep\zlib\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)src;%(AdditionalIncludeDirectories) Default true false @@ -408,7 +416,7 @@ MaxSpeed true WITH_RECOMPILER=1;_CRT_SECURE_NO_WARNINGS;WIN32;NDEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions) - $(SolutionDir)dep\msvc\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\stb\include;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\xbyak\xbyak;$(SolutionDir)dep\tinyxml2\include;$(SolutionDir)dep\zlib\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)src;%(AdditionalIncludeDirectories) + $(SolutionDir)dep\msvc\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\stb\include;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\xbyak\xbyak;$(SolutionDir)dep\tinyxml2\include;$(SolutionDir)dep\simpleini\include;$(SolutionDir)dep\zlib\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)src;%(AdditionalIncludeDirectories) true false stdcpp17 @@ -435,7 +443,7 @@ MaxSpeed true WITH_RECOMPILER=1;_CRT_SECURE_NO_WARNINGS;WIN32;NDEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions) - $(SolutionDir)dep\msvc\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\stb\include;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\xbyak\xbyak;$(SolutionDir)dep\tinyxml2\include;$(SolutionDir)dep\zlib\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)src;%(AdditionalIncludeDirectories) + $(SolutionDir)dep\msvc\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\stb\include;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\xbyak\xbyak;$(SolutionDir)dep\tinyxml2\include;$(SolutionDir)dep\simpleini\include;$(SolutionDir)dep\zlib\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)src;%(AdditionalIncludeDirectories) true true stdcpp17 @@ -463,7 +471,7 @@ MaxSpeed true WITH_RECOMPILER=1;_CRT_SECURE_NO_WARNINGS;WIN32;NDEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions) - $(SolutionDir)dep\msvc\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\stb\include;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\xbyak\xbyak;$(SolutionDir)dep\tinyxml2\include;$(SolutionDir)dep\zlib\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)src;%(AdditionalIncludeDirectories) + $(SolutionDir)dep\msvc\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\stb\include;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\xbyak\xbyak;$(SolutionDir)dep\tinyxml2\include;$(SolutionDir)dep\simpleini\include;$(SolutionDir)dep\zlib\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)src;%(AdditionalIncludeDirectories) true false stdcpp17 @@ -490,7 +498,7 @@ MaxSpeed true WITH_RECOMPILER=1;_CRT_SECURE_NO_WARNINGS;WIN32;NDEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions) - $(SolutionDir)dep\msvc\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\stb\include;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\xbyak\xbyak;$(SolutionDir)dep\tinyxml2\include;$(SolutionDir)dep\zlib\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)src;%(AdditionalIncludeDirectories) + $(SolutionDir)dep\msvc\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\stb\include;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\xbyak\xbyak;$(SolutionDir)dep\tinyxml2\include;$(SolutionDir)dep\simpleini\include;$(SolutionDir)dep\zlib\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)src;%(AdditionalIncludeDirectories) true true stdcpp17 diff --git a/src/core/core.vcxproj.filters b/src/core/core.vcxproj.filters index 9acf9dc28..0d86f8170 100644 --- a/src/core/core.vcxproj.filters +++ b/src/core/core.vcxproj.filters @@ -47,6 +47,7 @@ + @@ -97,5 +98,6 @@ + \ No newline at end of file diff --git a/src/core/cpu_code_cache.cpp b/src/core/cpu_code_cache.cpp index 1bd8bda9c..eb21533f4 100644 --- a/src/core/cpu_code_cache.cpp +++ b/src/core/cpu_code_cache.cpp @@ -110,7 +110,8 @@ void Shutdown() #endif } -void Execute() +template +static void ExecuteImpl() { CodeBlockKey next_block_key; @@ -157,7 +158,7 @@ void Execute() } else { - InterpretCachedBlock(*block); + InterpretCachedBlock(*block); } if (g_state.pending_ticks >= g_state.downcount) @@ -212,6 +213,21 @@ void Execute() g_state.regs.npc = g_state.regs.pc; } +void Execute() +{ + if (g_settings.gpu_pgxp_enable) + { + if (g_settings.gpu_pgxp_cpu) + ExecuteImpl(); + else + ExecuteImpl(); + } + else + { + ExecuteImpl(); + } +} + #ifdef WITH_RECOMPILER void ExecuteRecompiler() diff --git a/src/core/cpu_code_cache.h b/src/core/cpu_code_cache.h index 28401bea6..eec01ac3b 100644 --- a/src/core/cpu_code_cache.h +++ b/src/core/cpu_code_cache.h @@ -96,6 +96,7 @@ void SetUseRecompiler(bool enable); /// Invalidates all blocks which are in the range of the specified code page. void InvalidateBlocksWithPageIndex(u32 page_index); +template void InterpretCachedBlock(const CodeBlock& block); void InterpretUncachedBlock(); diff --git a/src/core/cpu_core.cpp b/src/core/cpu_core.cpp index 6ad58d962..16c4c9e04 100644 --- a/src/core/cpu_core.cpp +++ b/src/core/cpu_core.cpp @@ -15,19 +15,9 @@ Log_SetChannel(CPU::Core); namespace CPU { -/// Sets the PC and flushes the pipeline. static void SetPC(u32 new_pc); - -// Updates load delays - call after each instruction static void UpdateLoadDelay(); - -// Fetches the instruction at m_regs.npc -static void ExecuteInstruction(); -static void ExecuteCop0Instruction(); -static void ExecuteCop2Instruction(); static void Branch(u32 target); - -// clears pipeline of load/branch delays static void FlushPipeline(); State g_state; @@ -139,14 +129,14 @@ bool DoState(StateWrapper& sw) return !sw.HasError(); } -void SetPC(u32 new_pc) +ALWAYS_INLINE_RELEASE void SetPC(u32 new_pc) { DebugAssert(Common::IsAlignedPow2(new_pc, 4)); g_state.regs.npc = new_pc; FlushPipeline(); } -void Branch(u32 target) +ALWAYS_INLINE_RELEASE void Branch(u32 target) { if (!Common::IsAlignedPow2(target, 4)) { @@ -240,7 +230,7 @@ void ClearExternalInterrupt(u8 bit) g_state.cop0_regs.cause.Ip &= static_cast(~(1u << bit)); } -void UpdateLoadDelay() +ALWAYS_INLINE_RELEASE static void UpdateLoadDelay() { // the old value is needed in case the delay slot instruction overwrites the same register if (g_state.load_delay_reg != Reg::count) @@ -251,7 +241,7 @@ void UpdateLoadDelay() g_state.next_load_delay_reg = Reg::count; } -void FlushPipeline() +ALWAYS_INLINE_RELEASE static void FlushPipeline() { // loads are flushed g_state.next_load_delay_reg = Reg::count; @@ -275,12 +265,12 @@ void FlushPipeline() g_state.current_instruction_was_branch_taken = false; } -ALWAYS_INLINE u32 ReadReg(Reg rs) +ALWAYS_INLINE static u32 ReadReg(Reg rs) { return g_state.regs.r[static_cast(rs)]; } -ALWAYS_INLINE void WriteReg(Reg rd, u32 value) +ALWAYS_INLINE static void WriteReg(Reg rd, u32 value) { g_state.regs.r[static_cast(rd)] = value; g_state.load_delay_reg = (rd == g_state.load_delay_reg) ? Reg::count : g_state.load_delay_reg; @@ -289,7 +279,7 @@ ALWAYS_INLINE void WriteReg(Reg rd, u32 value) g_state.regs.zero = 0; } -static void WriteRegDelayed(Reg rd, u32 value) +ALWAYS_INLINE_RELEASE static void WriteRegDelayed(Reg rd, u32 value) { Assert(g_state.next_load_delay_reg == Reg::count); if (rd == Reg::zero) @@ -304,7 +294,7 @@ static void WriteRegDelayed(Reg rd, u32 value) g_state.next_load_delay_value = value; } -static std::optional ReadCop0Reg(Cop0Reg reg) +ALWAYS_INLINE_RELEASE static std::optional ReadCop0Reg(Cop0Reg reg) { switch (reg) { @@ -347,7 +337,7 @@ static std::optional ReadCop0Reg(Cop0Reg reg) } } -static void WriteCop0Reg(Cop0Reg reg, u32 value) +ALWAYS_INLINE_RELEASE static void WriteCop0Reg(Cop0Reg reg, u32 value) { switch (reg) { @@ -431,12 +421,12 @@ static void LogInstruction(u32 bits, u32 pc, Registers* regs) WriteToExecutionLog("%08x: %08x %s\n", pc, bits, instr.GetCharArray()); } -static constexpr bool AddOverflow(u32 old_value, u32 add_value, u32 new_value) +ALWAYS_INLINE static constexpr bool AddOverflow(u32 old_value, u32 add_value, u32 new_value) { return (((new_value ^ old_value) & (new_value ^ add_value)) & UINT32_C(0x80000000)) != 0; } -static constexpr bool SubOverflow(u32 old_value, u32 sub_value, u32 new_value) +ALWAYS_INLINE static constexpr bool SubOverflow(u32 old_value, u32 sub_value, u32 new_value) { return (((new_value ^ old_value) & (old_value ^ sub_value)) & UINT32_C(0x80000000)) != 0; } @@ -467,53 +457,8 @@ void DisassembleAndPrint(u32 addr, u32 instructions_before /* = 0 */, u32 instru } } -void Execute() -{ - g_state.frame_done = false; - while (!g_state.frame_done) - { - TimingEvents::UpdateCPUDowncount(); - - while (g_state.pending_ticks <= g_state.downcount) - { - if (HasPendingInterrupt()) - DispatchInterrupt(); - - g_state.pending_ticks++; - - // now executing the instruction we previously fetched - g_state.current_instruction.bits = g_state.next_instruction.bits; - g_state.current_instruction_pc = g_state.regs.pc; - g_state.current_instruction_in_branch_delay_slot = g_state.next_instruction_is_branch_delay_slot; - g_state.current_instruction_was_branch_taken = g_state.branch_was_taken; - g_state.next_instruction_is_branch_delay_slot = false; - g_state.branch_was_taken = false; - g_state.exception_raised = false; - - // fetch the next instruction - if (!FetchInstruction()) - continue; - -#if 0 // GTE flag test debugging - if (g_state.m_current_instruction_pc == 0x8002cdf4) - { - if (g_state.m_regs.v1 != g_state.m_regs.v0) - printf("Got %08X Expected? %08X\n", g_state.m_regs.v1, g_state.m_regs.v0); - } -#endif - - // execute the instruction we previously fetched - ExecuteInstruction(); - - // next load delay - UpdateLoadDelay(); - } - - TimingEvents::RunEvents(); - } -} - -void ExecuteInstruction() +template +ALWAYS_INLINE_RELEASE static void ExecuteInstruction() { const Instruction inst = g_state.current_instruction; @@ -525,14 +470,6 @@ void ExecuteInstruction() } #endif -#if 0 - if (g_state.m_current_instruction_pc == 0x8002bf50) - { - TRACE_EXECUTION = true; - __debugbreak(); - } -#endif - #ifdef _DEBUG if (TRACE_EXECUTION) PrintInstruction(inst.bits, g_state.current_instruction_pc, &g_state.regs); @@ -540,6 +477,10 @@ void ExecuteInstruction() LogInstruction(inst.bits, g_state.current_instruction_pc, &g_state.regs); #endif + // Skip nops. Makes PGXP-CPU quicker, but also the regular interpreter. + if (inst.bits == 0) + return; + switch (inst.op) { case InstructionOp::funct: @@ -549,6 +490,9 @@ void ExecuteInstruction() case InstructionFunct::sll: { const u32 new_value = ReadReg(inst.r.rt) << inst.r.shamt; + if constexpr (pgxp_mode >= PGXPMode::CPU) + PGXP::CPU_SLL(inst.bits, new_value, ReadReg(inst.r.rt)); + WriteReg(inst.r.rd, new_value); } break; @@ -556,6 +500,9 @@ void ExecuteInstruction() case InstructionFunct::srl: { const u32 new_value = ReadReg(inst.r.rt) >> inst.r.shamt; + if constexpr (pgxp_mode >= PGXPMode::CPU) + PGXP::CPU_SRL(inst.bits, new_value, ReadReg(inst.r.rt)); + WriteReg(inst.r.rd, new_value); } break; @@ -563,6 +510,9 @@ void ExecuteInstruction() case InstructionFunct::sra: { const u32 new_value = static_cast(static_cast(ReadReg(inst.r.rt)) >> inst.r.shamt); + if constexpr (pgxp_mode >= PGXPMode::CPU) + PGXP::CPU_SRA(inst.bits, new_value, ReadReg(inst.r.rt)); + WriteReg(inst.r.rd, new_value); } break; @@ -571,6 +521,9 @@ void ExecuteInstruction() { const u32 shift_amount = ReadReg(inst.r.rs) & UINT32_C(0x1F); const u32 new_value = ReadReg(inst.r.rt) << shift_amount; + if constexpr (pgxp_mode >= PGXPMode::CPU) + PGXP::CPU_SLLV(inst.bits, new_value, ReadReg(inst.r.rt), shift_amount); + WriteReg(inst.r.rd, new_value); } break; @@ -579,6 +532,9 @@ void ExecuteInstruction() { const u32 shift_amount = ReadReg(inst.r.rs) & UINT32_C(0x1F); const u32 new_value = ReadReg(inst.r.rt) >> shift_amount; + if constexpr (pgxp_mode >= PGXPMode::CPU) + PGXP::CPU_SRLV(inst.bits, new_value, ReadReg(inst.r.rt), shift_amount); + WriteReg(inst.r.rd, new_value); } break; @@ -587,6 +543,9 @@ void ExecuteInstruction() { const u32 shift_amount = ReadReg(inst.r.rs) & UINT32_C(0x1F); const u32 new_value = static_cast(static_cast(ReadReg(inst.r.rt)) >> shift_amount); + if constexpr (pgxp_mode >= PGXPMode::CPU) + PGXP::CPU_SRAV(inst.bits, new_value, ReadReg(inst.r.rt), shift_amount); + WriteReg(inst.r.rd, new_value); } break; @@ -594,6 +553,9 @@ void ExecuteInstruction() case InstructionFunct::and_: { const u32 new_value = ReadReg(inst.r.rs) & ReadReg(inst.r.rt); + if constexpr (pgxp_mode >= PGXPMode::CPU) + PGXP::CPU_AND_(inst.bits, new_value, ReadReg(inst.r.rs), ReadReg(inst.r.rt)); + WriteReg(inst.r.rd, new_value); } break; @@ -601,6 +563,9 @@ void ExecuteInstruction() case InstructionFunct::or_: { const u32 new_value = ReadReg(inst.r.rs) | ReadReg(inst.r.rt); + if constexpr (pgxp_mode >= PGXPMode::CPU) + PGXP::CPU_OR_(inst.bits, new_value, ReadReg(inst.r.rs), ReadReg(inst.r.rt)); + WriteReg(inst.r.rd, new_value); } break; @@ -608,6 +573,9 @@ void ExecuteInstruction() case InstructionFunct::xor_: { const u32 new_value = ReadReg(inst.r.rs) ^ ReadReg(inst.r.rt); + if constexpr (pgxp_mode >= PGXPMode::CPU) + PGXP::CPU_XOR_(inst.bits, new_value, ReadReg(inst.r.rs), ReadReg(inst.r.rt)); + WriteReg(inst.r.rd, new_value); } break; @@ -615,6 +583,9 @@ void ExecuteInstruction() case InstructionFunct::nor: { const u32 new_value = ~(ReadReg(inst.r.rs) | ReadReg(inst.r.rt)); + if constexpr (pgxp_mode >= PGXPMode::CPU) + PGXP::CPU_NOR(inst.bits, new_value, ReadReg(inst.r.rs), ReadReg(inst.r.rt)); + WriteReg(inst.r.rd, new_value); } break; @@ -630,6 +601,9 @@ void ExecuteInstruction() return; } + if constexpr (pgxp_mode == PGXPMode::CPU) + PGXP::CPU_ADD(inst.bits, new_value, ReadReg(inst.r.rs), ReadReg(inst.r.rt)); + WriteReg(inst.r.rd, new_value); } break; @@ -637,6 +611,9 @@ void ExecuteInstruction() case InstructionFunct::addu: { const u32 new_value = ReadReg(inst.r.rs) + ReadReg(inst.r.rt); + if constexpr (pgxp_mode >= PGXPMode::CPU) + PGXP::CPU_ADDU(inst.bits, new_value, ReadReg(inst.r.rs), ReadReg(inst.r.rt)); + WriteReg(inst.r.rd, new_value); } break; @@ -652,6 +629,9 @@ void ExecuteInstruction() return; } + if constexpr (pgxp_mode >= PGXPMode::CPU) + PGXP::CPU_SUB(inst.bits, new_value, ReadReg(inst.r.rs), ReadReg(inst.r.rt)); + WriteReg(inst.r.rd, new_value); } break; @@ -659,6 +639,9 @@ void ExecuteInstruction() case InstructionFunct::subu: { const u32 new_value = ReadReg(inst.r.rs) - ReadReg(inst.r.rt); + if constexpr (pgxp_mode >= PGXPMode::CPU) + PGXP::CPU_SUBU(inst.bits, new_value, ReadReg(inst.r.rs), ReadReg(inst.r.rt)); + WriteReg(inst.r.rd, new_value); } break; @@ -666,6 +649,9 @@ void ExecuteInstruction() case InstructionFunct::slt: { const u32 result = BoolToUInt32(static_cast(ReadReg(inst.r.rs)) < static_cast(ReadReg(inst.r.rt))); + if constexpr (pgxp_mode >= PGXPMode::CPU) + PGXP::CPU_SLT(inst.bits, result, ReadReg(inst.r.rs), ReadReg(inst.r.rt)); + WriteReg(inst.r.rd, result); } break; @@ -673,12 +659,18 @@ void ExecuteInstruction() case InstructionFunct::sltu: { const u32 result = BoolToUInt32(ReadReg(inst.r.rs) < ReadReg(inst.r.rt)); + if constexpr (pgxp_mode >= PGXPMode::CPU) + PGXP::CPU_SLTU(inst.bits, result, ReadReg(inst.r.rs), ReadReg(inst.r.rt)); + WriteReg(inst.r.rd, result); } break; case InstructionFunct::mfhi: { + if constexpr (pgxp_mode >= PGXPMode::CPU) + PGXP::CPU_MFHI(inst.bits, ReadReg(inst.r.rd), g_state.regs.hi); + WriteReg(inst.r.rd, g_state.regs.hi); } break; @@ -686,12 +678,18 @@ void ExecuteInstruction() case InstructionFunct::mthi: { const u32 value = ReadReg(inst.r.rs); + if constexpr (pgxp_mode >= PGXPMode::CPU) + PGXP::CPU_MTHI(inst.bits, g_state.regs.hi, value); + g_state.regs.hi = value; } break; case InstructionFunct::mflo: { + if constexpr (pgxp_mode >= PGXPMode::CPU) + PGXP::CPU_MFLO(inst.bits, ReadReg(inst.r.rd), g_state.regs.lo); + WriteReg(inst.r.rd, g_state.regs.lo); } break; @@ -699,6 +697,9 @@ void ExecuteInstruction() case InstructionFunct::mtlo: { const u32 value = ReadReg(inst.r.rs); + if constexpr (pgxp_mode == PGXPMode::CPU) + PGXP::CPU_MTLO(inst.bits, g_state.regs.lo, value); + g_state.regs.lo = value; } break; @@ -709,8 +710,12 @@ void ExecuteInstruction() const u32 rhs = ReadReg(inst.r.rt); const u64 result = static_cast(static_cast(SignExtend64(lhs)) * static_cast(SignExtend64(rhs))); + g_state.regs.hi = Truncate32(result >> 32); g_state.regs.lo = Truncate32(result); + + if constexpr (pgxp_mode >= PGXPMode::CPU) + PGXP::CPU_MULT(inst.bits, g_state.regs.hi, g_state.regs.lo, lhs, rhs); } break; @@ -719,6 +724,10 @@ void ExecuteInstruction() const u32 lhs = ReadReg(inst.r.rs); const u32 rhs = ReadReg(inst.r.rt); const u64 result = ZeroExtend64(lhs) * ZeroExtend64(rhs); + + if constexpr (pgxp_mode >= PGXPMode::CPU) + PGXP::CPU_MULTU(inst.bits, g_state.regs.hi, g_state.regs.lo, lhs, rhs); + g_state.regs.hi = Truncate32(result >> 32); g_state.regs.lo = Truncate32(result); } @@ -746,6 +755,9 @@ void ExecuteInstruction() g_state.regs.lo = static_cast(num / denom); g_state.regs.hi = static_cast(num % denom); } + + if constexpr (pgxp_mode >= PGXPMode::CPU) + PGXP::CPU_DIV(inst.bits, g_state.regs.hi, g_state.regs.lo, num, denom); } break; @@ -765,6 +777,9 @@ void ExecuteInstruction() g_state.regs.lo = num / denom; g_state.regs.hi = num % denom; } + + if constexpr (pgxp_mode >= PGXPMode::CPU) + PGXP::CPU_DIVU(inst.bits, g_state.regs.hi, g_state.regs.lo, num, denom); } break; @@ -808,25 +823,44 @@ void ExecuteInstruction() case InstructionOp::lui: { - WriteReg(inst.i.rt, inst.i.imm_zext32() << 16); + const u32 value = inst.i.imm_zext32() << 16; + WriteReg(inst.i.rt, value); + + if constexpr (pgxp_mode >= PGXPMode::CPU) + PGXP::CPU_LUI(inst.bits, value); } break; case InstructionOp::andi: { - WriteReg(inst.i.rt, ReadReg(inst.i.rs) & inst.i.imm_zext32()); + const u32 new_value = ReadReg(inst.i.rs) & inst.i.imm_zext32(); + + if constexpr (pgxp_mode >= PGXPMode::CPU) + PGXP::CPU_ANDI(inst.bits, new_value, ReadReg(inst.i.rs)); + + WriteReg(inst.i.rt, new_value); } break; case InstructionOp::ori: { - WriteReg(inst.i.rt, ReadReg(inst.i.rs) | inst.i.imm_zext32()); + const u32 new_value = ReadReg(inst.i.rs) | inst.i.imm_zext32(); + + if constexpr (pgxp_mode >= PGXPMode::CPU) + PGXP::CPU_ORI(inst.bits, new_value, ReadReg(inst.i.rs)); + + WriteReg(inst.i.rt, new_value); } break; case InstructionOp::xori: { - WriteReg(inst.i.rt, ReadReg(inst.i.rs) ^ inst.i.imm_zext32()); + const u32 new_value = ReadReg(inst.i.rs) ^ inst.i.imm_zext32(); + + if constexpr (pgxp_mode >= PGXPMode::CPU) + PGXP::CPU_XORI(inst.bits, new_value, ReadReg(inst.i.rs)); + + WriteReg(inst.i.rt, new_value); } break; @@ -841,19 +875,31 @@ void ExecuteInstruction() return; } + if constexpr (pgxp_mode >= PGXPMode::CPU) + PGXP::CPU_ANDI(inst.bits, new_value, ReadReg(inst.i.rs)); + WriteReg(inst.i.rt, new_value); } break; case InstructionOp::addiu: { - WriteReg(inst.i.rt, ReadReg(inst.i.rs) + inst.i.imm_sext32()); + const u32 new_value = ReadReg(inst.i.rs) + inst.i.imm_sext32(); + + if constexpr (pgxp_mode >= PGXPMode::CPU) + PGXP::CPU_ADDIU(inst.bits, new_value, ReadReg(inst.i.rs)); + + WriteReg(inst.i.rt, new_value); } break; case InstructionOp::slti: { const u32 result = BoolToUInt32(static_cast(ReadReg(inst.i.rs)) < static_cast(inst.i.imm_sext32())); + + if constexpr (pgxp_mode >= PGXPMode::CPU) + PGXP::CPU_SLTI(inst.bits, result, ReadReg(inst.i.rs)); + WriteReg(inst.i.rt, result); } break; @@ -861,6 +907,10 @@ void ExecuteInstruction() case InstructionOp::sltiu: { const u32 result = BoolToUInt32(ReadReg(inst.i.rs) < inst.i.imm_sext32()); + + if constexpr (pgxp_mode >= PGXPMode::CPU) + PGXP::CPU_SLTIU(inst.bits, result, ReadReg(inst.i.rs)); + WriteReg(inst.i.rt, result); } break; @@ -876,7 +926,7 @@ void ExecuteInstruction() WriteRegDelayed(inst.i.rt, sxvalue); - if (g_settings.gpu_pgxp_enable) + if constexpr (pgxp_mode >= PGXPMode::Memory) PGXP::CPU_LBx(inst.bits, sxvalue, addr); } break; @@ -891,7 +941,7 @@ void ExecuteInstruction() const u32 sxvalue = SignExtend32(value); WriteRegDelayed(inst.i.rt, sxvalue); - if (g_settings.gpu_pgxp_enable) + if constexpr (pgxp_mode >= PGXPMode::Memory) PGXP::CPU_LHx(inst.bits, sxvalue, addr); } break; @@ -905,7 +955,7 @@ void ExecuteInstruction() WriteRegDelayed(inst.i.rt, value); - if (g_settings.gpu_pgxp_enable) + if constexpr (pgxp_mode >= PGXPMode::Memory) PGXP::CPU_LW(inst.bits, value, addr); } break; @@ -920,7 +970,7 @@ void ExecuteInstruction() const u32 zxvalue = ZeroExtend32(value); WriteRegDelayed(inst.i.rt, zxvalue); - if (g_settings.gpu_pgxp_enable) + if constexpr (pgxp_mode >= PGXPMode::Memory) PGXP::CPU_LBx(inst.bits, zxvalue, addr); } break; @@ -935,7 +985,7 @@ void ExecuteInstruction() const u32 zxvalue = ZeroExtend32(value); WriteRegDelayed(inst.i.rt, zxvalue); - if (g_settings.gpu_pgxp_enable) + if constexpr (pgxp_mode >= PGXPMode::Memory) PGXP::CPU_LHx(inst.bits, zxvalue, addr); } break; @@ -966,7 +1016,7 @@ void ExecuteInstruction() WriteRegDelayed(inst.i.rt, new_value); - if (g_settings.gpu_pgxp_enable) + if constexpr (pgxp_mode >= PGXPMode::Memory) PGXP::CPU_LW(inst.bits, new_value, addr); } break; @@ -977,7 +1027,7 @@ void ExecuteInstruction() const u8 value = Truncate8(ReadReg(inst.i.rt)); WriteMemoryByte(addr, value); - if (g_settings.gpu_pgxp_enable) + if constexpr (pgxp_mode >= PGXPMode::Memory) PGXP::CPU_SB(inst.bits, value, addr); } break; @@ -988,7 +1038,7 @@ void ExecuteInstruction() const u16 value = Truncate16(ReadReg(inst.i.rt)); WriteMemoryHalfWord(addr, value); - if (g_settings.gpu_pgxp_enable) + if constexpr (pgxp_mode >= PGXPMode::Memory) PGXP::CPU_SH(inst.bits, value, addr); } break; @@ -999,7 +1049,7 @@ void ExecuteInstruction() const u32 value = ReadReg(inst.i.rt); WriteMemoryWord(addr, value); - if (g_settings.gpu_pgxp_enable) + if constexpr (pgxp_mode >= PGXPMode::Memory) PGXP::CPU_SW(inst.bits, value, addr); } break; @@ -1029,7 +1079,7 @@ void ExecuteInstruction() WriteMemoryWord(aligned_addr, new_value); - if (g_settings.gpu_pgxp_enable) + if constexpr (pgxp_mode >= PGXPMode::Memory) PGXP::CPU_SW(inst.bits, new_value, addr); } break; @@ -1114,7 +1164,58 @@ void ExecuteInstruction() return; } - ExecuteCop0Instruction(); + if (inst.cop.IsCommonInstruction()) + { + switch (inst.cop.CommonOp()) + { + case CopCommonInstruction::mfcn: + { + const std::optional value = ReadCop0Reg(static_cast(inst.r.rd.GetValue())); + + if constexpr (pgxp_mode == PGXPMode::CPU) + PGXP::CPU_MFC0(inst.bits, value.value_or(0), ReadReg(inst.i.rs)); + + if (value) + WriteRegDelayed(inst.r.rt, value.value()); + else + RaiseException(Exception::RI); + } + break; + + case CopCommonInstruction::mtcn: + { + WriteCop0Reg(static_cast(inst.r.rd.GetValue()), ReadReg(inst.r.rt)); + + if constexpr (pgxp_mode == PGXPMode::CPU) + { + PGXP::CPU_MTC0(inst.bits, ReadCop0Reg(static_cast(inst.r.rd.GetValue())).value_or(0), + ReadReg(inst.i.rs)); + } + } + break; + + default: + Panic("Missing implementation"); + break; + } + } + else + { + switch (inst.cop.Cop0Op()) + { + case Cop0Instruction::rfe: + { + // restore mode + g_state.cop0_regs.sr.mode_bits = + (g_state.cop0_regs.sr.mode_bits & UINT32_C(0b110000)) | (g_state.cop0_regs.sr.mode_bits >> 2); + } + break; + + default: + Panic("Missing implementation"); + break; + } + } } break; @@ -1127,7 +1228,61 @@ void ExecuteInstruction() return; } - ExecuteCop2Instruction(); + if (inst.cop.IsCommonInstruction()) + { + // TODO: Combine with cop0. + switch (inst.cop.CommonOp()) + { + case CopCommonInstruction::cfcn: + { + const u32 value = GTE::ReadRegister(static_cast(inst.r.rd.GetValue()) + 32); + WriteRegDelayed(inst.r.rt, value); + + if constexpr (pgxp_mode >= PGXPMode::Memory) + PGXP::CPU_CFC2(inst.bits, value, value); + } + break; + + case CopCommonInstruction::ctcn: + { + const u32 value = ReadReg(inst.r.rt); + GTE::WriteRegister(static_cast(inst.r.rd.GetValue()) + 32, value); + + if constexpr (pgxp_mode >= PGXPMode::Memory) + PGXP::CPU_CTC2(inst.bits, value, value); + } + break; + + case CopCommonInstruction::mfcn: + { + const u32 value = GTE::ReadRegister(static_cast(inst.r.rd.GetValue())); + WriteRegDelayed(inst.r.rt, value); + + if constexpr (pgxp_mode >= PGXPMode::Memory) + PGXP::CPU_MFC2(inst.bits, value, value); + } + break; + + case CopCommonInstruction::mtcn: + { + const u32 value = ReadReg(inst.r.rt); + GTE::WriteRegister(static_cast(inst.r.rd.GetValue()), value); + + if constexpr (pgxp_mode >= PGXPMode::Memory) + PGXP::CPU_MTC2(inst.bits, value, value); + } + break; + + case CopCommonInstruction::bcnc: + default: + Panic("Missing implementation"); + break; + } + } + else + { + GTE::ExecuteInstruction(inst.bits); + } } break; @@ -1147,7 +1302,7 @@ void ExecuteInstruction() GTE::WriteRegister(ZeroExtend32(static_cast(inst.i.rt.GetValue())), value); - if (g_settings.gpu_pgxp_enable) + if constexpr (pgxp_mode >= PGXPMode::Memory) PGXP::CPU_LWC2(inst.bits, value, addr); } break; @@ -1165,12 +1320,12 @@ void ExecuteInstruction() const u32 value = GTE::ReadRegister(ZeroExtend32(static_cast(inst.i.rt.GetValue()))); WriteMemoryWord(addr, value); - if (g_settings.gpu_pgxp_enable) + if constexpr (pgxp_mode >= PGXPMode::Memory) PGXP::CPU_SWC2(inst.bits, value, addr); } break; - // swc0/lwc0/cop1/cop3 are essentially no-ops + // swc0/lwc0/cop1/cop3 are essentially no-ops case InstructionOp::cop1: case InstructionOp::cop3: case InstructionOp::lwc0: @@ -1183,7 +1338,7 @@ void ExecuteInstruction() } break; - // everything else is reserved/invalid + // everything else is reserved/invalid default: { RaiseException(Exception::RI); @@ -1192,117 +1347,71 @@ void ExecuteInstruction() } } -void ExecuteCop0Instruction() +template +static void ExecuteImpl() { - const Instruction inst = g_state.current_instruction; - - if (inst.cop.IsCommonInstruction()) + g_state.frame_done = false; + while (!g_state.frame_done) { - switch (inst.cop.CommonOp()) + TimingEvents::UpdateCPUDowncount(); + + while (g_state.pending_ticks <= g_state.downcount) { - case CopCommonInstruction::mfcn: - { - const std::optional value = ReadCop0Reg(static_cast(inst.r.rd.GetValue())); - if (value) - WriteRegDelayed(inst.r.rt, value.value()); - else - RaiseException(Exception::RI); - } - break; + if (HasPendingInterrupt()) + DispatchInterrupt(); - case CopCommonInstruction::mtcn: - { - WriteCop0Reg(static_cast(inst.r.rd.GetValue()), ReadReg(inst.r.rt)); - } - break; + g_state.pending_ticks++; - default: - Panic("Missing implementation"); - break; + // now executing the instruction we previously fetched + g_state.current_instruction.bits = g_state.next_instruction.bits; + g_state.current_instruction_pc = g_state.regs.pc; + g_state.current_instruction_in_branch_delay_slot = g_state.next_instruction_is_branch_delay_slot; + g_state.current_instruction_was_branch_taken = g_state.branch_was_taken; + g_state.next_instruction_is_branch_delay_slot = false; + g_state.branch_was_taken = false; + g_state.exception_raised = false; + + // fetch the next instruction + if (!FetchInstruction()) + continue; + +#if 0 // GTE flag test debugging + if (g_state.m_current_instruction_pc == 0x8002cdf4) + { + if (g_state.m_regs.v1 != g_state.m_regs.v0) + printf("Got %08X Expected? %08X\n", g_state.m_regs.v1, g_state.m_regs.v0); + } +#endif + + // execute the instruction we previously fetched + ExecuteInstruction(); + + // next load delay + UpdateLoadDelay(); } - } - else - { - switch (inst.cop.Cop0Op()) - { - case Cop0Instruction::rfe: - { - // restore mode - g_state.cop0_regs.sr.mode_bits = - (g_state.cop0_regs.sr.mode_bits & UINT32_C(0b110000)) | (g_state.cop0_regs.sr.mode_bits >> 2); - } - break; - default: - Panic("Missing implementation"); - break; - } + TimingEvents::RunEvents(); } } -void ExecuteCop2Instruction() +void Execute() { - const Instruction inst = g_state.current_instruction; - - if (inst.cop.IsCommonInstruction()) + if (g_settings.gpu_pgxp_enable) { - // TODO: Combine with cop0. - switch (inst.cop.CommonOp()) - { - case CopCommonInstruction::cfcn: - { - const u32 value = GTE::ReadRegister(static_cast(inst.r.rd.GetValue()) + 32); - WriteRegDelayed(inst.r.rt, value); - - if (g_settings.gpu_pgxp_enable) - PGXP::CPU_CFC2(inst.bits, value, value); - } - break; - - case CopCommonInstruction::ctcn: - { - const u32 value = ReadReg(inst.r.rt); - GTE::WriteRegister(static_cast(inst.r.rd.GetValue()) + 32, value); - - if (g_settings.gpu_pgxp_enable) - PGXP::CPU_CTC2(inst.bits, value, value); - } - break; - - case CopCommonInstruction::mfcn: - { - const u32 value = GTE::ReadRegister(static_cast(inst.r.rd.GetValue())); - WriteRegDelayed(inst.r.rt, value); - - if (g_settings.gpu_pgxp_enable) - PGXP::CPU_MFC2(inst.bits, value, value); - } - break; - - case CopCommonInstruction::mtcn: - { - const u32 value = ReadReg(inst.r.rt); - GTE::WriteRegister(static_cast(inst.r.rd.GetValue()), value); - - if (g_settings.gpu_pgxp_enable) - PGXP::CPU_MTC2(inst.bits, value, value); - } - break; - - case CopCommonInstruction::bcnc: - default: - Panic("Missing implementation"); - break; - } + if (g_settings.gpu_pgxp_cpu) + ExecuteImpl(); + else + ExecuteImpl(); } else { - GTE::ExecuteInstruction(inst.bits); + ExecuteImpl(); } } namespace CodeCache { +template void InterpretCachedBlock(const CodeBlock& block) { // set up the state so we've already fetched the instruction @@ -1327,7 +1436,7 @@ void InterpretCachedBlock(const CodeBlock& block) g_state.regs.npc += 4; // execute the instruction we previously fetched - ExecuteInstruction(); + ExecuteInstruction(); // next load delay UpdateLoadDelay(); @@ -1340,6 +1449,10 @@ void InterpretCachedBlock(const CodeBlock& block) g_state.next_instruction_is_branch_delay_slot = false; } +template void InterpretCachedBlock(const CodeBlock& block); +template void InterpretCachedBlock(const CodeBlock& block); +template void InterpretCachedBlock(const CodeBlock& block); + void InterpretUncachedBlock() { Panic("Fixme with regards to re-fetching PC"); @@ -1365,7 +1478,7 @@ void InterpretUncachedBlock() break; // execute the instruction we previously fetched - ExecuteInstruction(); + ExecuteInstruction(); // next load delay UpdateLoadDelay(); @@ -1387,7 +1500,13 @@ namespace Recompiler::Thunks { bool InterpretInstruction() { - ExecuteInstruction(); + ExecuteInstruction(); + return g_state.exception_raised; +} + +bool InterpretInstructionPGXP() +{ + ExecuteInstruction(); return g_state.exception_raised; } diff --git a/src/core/cpu_recompiler_code_generator.cpp b/src/core/cpu_recompiler_code_generator.cpp index 2e8802c17..1c7ae0db4 100644 --- a/src/core/cpu_recompiler_code_generator.cpp +++ b/src/core/cpu_recompiler_code_generator.cpp @@ -1014,12 +1014,14 @@ bool CodeGenerator::Compile_Fallback(const CodeBlockInstruction& cbi) { // TODO: Use carry flag or something here too Value return_value = m_register_cache.AllocateScratch(RegSize_8); - EmitFunctionCall(&return_value, &Thunks::InterpretInstruction); + EmitFunctionCall(&return_value, + g_settings.gpu_pgxp_enable ? &Thunks::InterpretInstructionPGXP : &Thunks::InterpretInstruction); EmitExceptionExitOnBool(return_value); } else { - EmitFunctionCall(nullptr, &Thunks::InterpretInstruction); + EmitFunctionCall(nullptr, + g_settings.gpu_pgxp_enable ? &Thunks::InterpretInstructionPGXP : &Thunks::InterpretInstruction); } m_current_instruction_in_branch_delay_slot_dirty = cbi.is_branch_instruction; diff --git a/src/core/cpu_recompiler_thunks.h b/src/core/cpu_recompiler_thunks.h index 9b9316dde..602f522af 100644 --- a/src/core/cpu_recompiler_thunks.h +++ b/src/core/cpu_recompiler_thunks.h @@ -13,6 +13,7 @@ namespace Recompiler::Thunks { // TODO: Abuse carry flag or something else for exception ////////////////////////////////////////////////////////////////////////// bool InterpretInstruction(); +bool InterpretInstructionPGXP(); // Memory access functions for the JIT - MSB is set on exception. u64 ReadMemoryByte(u32 address); diff --git a/src/core/cpu_types.h b/src/core/cpu_types.h index 48021af6d..785c746fb 100644 --- a/src/core/cpu_types.h +++ b/src/core/cpu_types.h @@ -135,7 +135,6 @@ enum class InstructionFunct : u8 or_ = 37, xor_ = 38, nor = 39, - sh = 41, slt = 42, sltu = 43 }; diff --git a/src/core/game_list.cpp b/src/core/game_list.cpp index fff5e7305..fef61d38b 100644 --- a/src/core/game_list.cpp +++ b/src/core/game_list.cpp @@ -8,6 +8,7 @@ #include "common/log.h" #include "common/progress_callback.h" #include "common/string_util.h" +#include "host_interface.h" #include "settings.h" #include #include @@ -270,7 +271,12 @@ std::vector GameList::ParseM3UFile(const char* path) const char* GameList::GetGameListCompatibilityRatingString(GameListCompatibilityRating rating) { static constexpr std::array(GameListCompatibilityRating::Count)> names = { - {"Unknown", "Doesn't Boot", "Crashes In Intro", "Crashes In-Game", "Graphical/Audio Issues", "No Issues"}}; + {TRANSLATABLE("GameListCompatibilityRating", "Unknown"), + TRANSLATABLE("GameListCompatibilityRating", "Doesn't Boot"), + TRANSLATABLE("GameListCompatibilityRating", "Crashes In Intro"), + TRANSLATABLE("GameListCompatibilityRating", "Crashes In-Game"), + TRANSLATABLE("GameListCompatibilityRating", "Graphical/Audio Issues"), + TRANSLATABLE("GameListCompatibilityRating", "No Issues")}}; return (rating >= GameListCompatibilityRating::Unknown && rating < GameListCompatibilityRating::Count) ? names[static_cast(rating)] : ""; @@ -446,6 +452,12 @@ bool GameList::GetGameListEntry(const std::string& path, GameListEntry* entry) entry->compatibility_rating = compatibility_entry->compatibility_rating; else Log_WarningPrintf("'%s' (%s) not found in compatibility list", entry->code.c_str(), entry->title.c_str()); + + if (!m_game_settings_load_tried) + LoadGameSettings(); + const GameSettings::Entry* settings = m_game_settings.GetEntry(entry->code); + if (settings) + entry->settings = *settings; } FILESYSTEM_STAT_DATA ffd; @@ -577,6 +589,12 @@ bool GameList::LoadEntriesFromCache(ByteStream* stream) ge.type = static_cast(type); ge.compatibility_rating = static_cast(compatibility_rating); + if (!ge.settings.LoadFromStream(stream)) + { + Log_WarningPrintf("Game list cache entry is corrupted (settings)"); + return false; + } + auto iter = m_cache_map.find(ge.path); if (iter != m_cache_map.end()) iter->second = std::move(ge); @@ -625,6 +643,7 @@ bool GameList::WriteEntryToCache(const GameListEntry* entry, ByteStream* stream) result &= WriteU8(stream, static_cast(entry->region)); result &= WriteU8(stream, static_cast(entry->type)); result &= WriteU8(stream, static_cast(entry->compatibility_rating)); + result &= entry->settings.SaveToStream(stream); return result; } @@ -853,6 +872,18 @@ const GameListEntry* GameList::GetEntryForPath(const char* path) const return nullptr; } +GameListEntry* GameList::GetMutableEntryForPath(const char* path) +{ + const size_t path_length = std::strlen(path); + for (GameListEntry& entry : m_entries) + { + if (entry.path.size() == path_length && StringUtil::Strcasecmp(entry.path.c_str(), path) == 0) + return &entry; + } + + return nullptr; +} + const GameListDatabaseEntry* GameList::GetDatabaseEntryForCode(const std::string& code) const { if (!m_database_load_tried) @@ -1272,3 +1303,46 @@ std::string GameList::ExportCompatibilityEntry(const GameListCompatibilityEntry* entry_elem->Accept(&printer); return std::string(printer.CStr(), printer.CStrSize()); } + +void GameList::LoadGameSettings() +{ + if (m_game_settings_load_tried) + return; + + m_game_settings_load_tried = true; + + if (!m_game_settings_filename.empty() && FileSystem::FileExists(m_user_game_settings_filename.c_str())) + m_game_settings.Load(m_game_settings_filename.c_str()); + if (!m_user_game_settings_filename.empty() && FileSystem::FileExists(m_user_game_settings_filename.c_str())) + m_game_settings.Load(m_user_game_settings_filename.c_str()); +} + +const GameSettings::Entry* GameList::GetGameSettings(const std::string& filename, const std::string& game_code) +{ + const GameListEntry* entry = GetMutableEntryForPath(filename.c_str()); + if (entry) + return &entry->settings; + + if (!m_game_settings_load_tried) + LoadGameSettings(); + + return m_game_settings.GetEntry(game_code); +} + +void GameList::UpdateGameSettings(const std::string& filename, const std::string& game_code, + const std::string& game_title, const GameSettings::Entry& new_entry, + bool save_to_list /* = true */, bool save_to_user /* = true */) +{ + GameListEntry* entry = GetMutableEntryForPath(filename.c_str()); + if (entry) + { + entry->settings = new_entry; + RewriteCacheFile(); + } + + if (save_to_list) + { + m_game_settings.SetEntry(game_code, game_title, new_entry, + save_to_user ? m_user_game_settings_filename.c_str() : m_game_settings_filename.c_str()); + } +} diff --git a/src/core/game_list.h b/src/core/game_list.h index 9bbad1656..7b4f3fc80 100644 --- a/src/core/game_list.h +++ b/src/core/game_list.h @@ -1,4 +1,5 @@ #pragma once +#include "game_settings.h" #include "types.h" #include #include @@ -48,6 +49,7 @@ struct GameListEntry DiscRegion region; GameListEntryType type; GameListCompatibilityRating compatibility_rating; + GameSettings::Entry settings; }; struct GameListCompatibilityEntry @@ -109,6 +111,8 @@ public: void SetCacheFilename(std::string filename) { m_cache_filename = std::move(filename); } void SetDatabaseFilename(std::string filename) { m_database_filename = std::move(filename); } void SetCompatibilityFilename(std::string filename) { m_compatibility_list_filename = std::move(filename); } + void SetGameSettingsFilename(std::string filename) { m_game_settings_filename = std::move(filename); } + void SetUserGameSettingsFilename(std::string filename) { m_user_game_settings_filename = std::move(filename); } void SetSearchDirectoriesFromSettings(SettingsInterface& si); bool IsDatabasePresent() const; @@ -120,11 +124,15 @@ public: static std::string ExportCompatibilityEntry(const GameListCompatibilityEntry* entry); + const GameSettings::Entry* GetGameSettings(const std::string& filename, const std::string& game_code); + void UpdateGameSettings(const std::string& filename, const std::string& game_code, const std::string& game_title, + const GameSettings::Entry& new_entry, bool save_to_list = true, bool save_to_user = true); + private: enum : u32 { GAME_LIST_CACHE_SIGNATURE = 0x45434C47, - GAME_LIST_CACHE_VERSION = 5 + GAME_LIST_CACHE_VERSION = 6 }; using DatabaseMap = std::unordered_map; @@ -140,6 +148,8 @@ private: class RedumpDatVisitor; class CompatibilityListVisitor; + GameListEntry* GetMutableEntryForPath(const char* path); + static bool GetExeListEntry(const char* path, GameListEntry* entry); bool GetM3UListEntry(const char* path, GameListEntry* entry); @@ -163,16 +173,22 @@ private: bool SaveCompatibilityDatabase(); bool SaveCompatibilityDatabaseForEntry(const GameListCompatibilityEntry* entry); + void LoadGameSettings(); + DatabaseMap m_database; EntryList m_entries; CacheMap m_cache_map; CompatibilityMap m_compatibility_list; + GameSettings::Database m_game_settings; std::unique_ptr m_cache_write_stream; std::vector m_search_directories; std::string m_cache_filename; std::string m_database_filename; std::string m_compatibility_list_filename; + std::string m_game_settings_filename; + std::string m_user_game_settings_filename; bool m_database_load_tried = false; bool m_compatibility_list_load_tried = false; + bool m_game_settings_load_tried = false; }; diff --git a/src/core/game_settings.cpp b/src/core/game_settings.cpp new file mode 100644 index 000000000..0db275879 --- /dev/null +++ b/src/core/game_settings.cpp @@ -0,0 +1,429 @@ +#include "game_settings.h" +#include "common/assert.h" +#include "common/byte_stream.h" +#include "common/file_system.h" +#include "common/log.h" +#include "common/string.h" +#include "common/string_util.h" +#include "host_interface.h" +#include "settings.h" +#include +#include +Log_SetChannel(GameSettings); + +#ifdef WIN32 +#include "common/windows_headers.h" +#endif +#include "SimpleIni.h" + +namespace GameSettings { + +std::array, static_cast(Trait::Count)> s_trait_names = {{ + {"ForceInterpreter", TRANSLATABLE("GameSettingsTrait", "Force Interpreter")}, + {"ForceSoftwareRenderer", TRANSLATABLE("GameSettingsTrait", "Force Software Renderer")}, + {"EnableInterlacing", TRANSLATABLE("GameSettingsTrait", "Enable Interlacing")}, + {"DisableTrueColor", TRANSLATABLE("GameSettingsTrait", "Disable True Color")}, + {"DisableUpscaling", TRANSLATABLE("GameSettingsTrait", "Disable Upscaling")}, + {"DisableScaledDithering", TRANSLATABLE("GameSettingsTrait", "Disable Scaled Dithering")}, + {"DisableWidescreen", TRANSLATABLE("GameSettingsTrait", "Disable Widescreen")}, + {"DisablePGXP", TRANSLATABLE("GameSettingsTrait", "Disable PGXP")}, + {"DisablePGXPCulling", TRANSLATABLE("GameSettingsTrait", "Disable PGXP Culling")}, + {"EnablePGXPVertexCache", TRANSLATABLE("GameSettingsTrait", "Enable PGXP Vertex Cache")}, + {"EnablePGXPCPUMode", TRANSLATABLE("GameSettingsTrait", "Enable PGXP CPU Mode")}, + {"ForceDigitalController", TRANSLATABLE("GameSettingsTrait", "Force Digital Controller")}, + {"EnableRecompilerMemoryExceptions", TRANSLATABLE("GameSettingsTrait", "Enable Recompiler Memory Exceptions")}, +}}; + +const char* GetTraitName(Trait trait) +{ + DebugAssert(trait < Trait::Count); + return s_trait_names[static_cast(trait)].first; +} + +const char* GetTraitDisplayName(Trait trait) +{ + DebugAssert(trait < Trait::Count); + return s_trait_names[static_cast(trait)].second; +} + +bool Entry::HasAnySettings() const +{ + return traits.any() || display_active_start_offset >= 0 || display_active_end_offset > 0; +} + +template +bool ReadOptionalFromStream(ByteStream* stream, std::optional* dest) +{ + bool has_value; + if (!stream->Read2(&has_value, sizeof(has_value))) + return false; + + if (!has_value) + return true; + + T value; + if (!stream->Read2(&value, sizeof(T))) + return false; + + *dest = value; + return true; +} + +template +bool WriteOptionalToStream(ByteStream* stream, const std::optional& src) +{ + const bool has_value = src.has_value(); + if (!stream->Write2(&has_value, sizeof(has_value))) + return false; + + if (!has_value) + return true; + + return stream->Write2(&src.value(), sizeof(T)); +} + +bool Entry::LoadFromStream(ByteStream* stream) +{ + constexpr u32 num_bytes = (static_cast(Trait::Count) + 7) / 8; + std::array bits; + + if (!stream->Read2(bits.data(), num_bytes) || !ReadOptionalFromStream(stream, &display_active_start_offset) || + !ReadOptionalFromStream(stream, &display_active_end_offset) || + !ReadOptionalFromStream(stream, &display_crop_mode) || !ReadOptionalFromStream(stream, &display_aspect_ratio) || + !ReadOptionalFromStream(stream, &controller_1_type) || !ReadOptionalFromStream(stream, &controller_2_type)) + { + return false; + } + + traits.reset(); + for (u32 i = 0; i < static_cast(Trait::Count); i++) + { + if ((bits[i / 8] & (1u << (i % 8))) != 0) + AddTrait(static_cast(i)); + } + + return true; +} + +bool Entry::SaveToStream(ByteStream* stream) const +{ + constexpr u32 num_bytes = (static_cast(Trait::Count) + 7) / 8; + std::array bits; + bits.fill(0); + for (u32 i = 0; i < static_cast(Trait::Count); i++) + { + if (HasTrait(static_cast(i))) + bits[i / 8] |= (1u << (i % 8)); + } + + return stream->Write2(bits.data(), num_bytes) && WriteOptionalToStream(stream, display_active_start_offset) && + WriteOptionalToStream(stream, display_active_end_offset) && WriteOptionalToStream(stream, display_crop_mode) && + WriteOptionalToStream(stream, display_aspect_ratio) && WriteOptionalToStream(stream, controller_1_type) && + WriteOptionalToStream(stream, controller_2_type); +} + +static void ParseIniSection(Entry* entry, const char* section, const CSimpleIniA& ini) +{ + for (u32 trait = 0; trait < static_cast(Trait::Count); trait++) + { + if (ini.GetBoolValue(section, s_trait_names[trait].first, false)) + entry->AddTrait(static_cast(trait)); + } + + long lvalue = ini.GetLongValue(section, "DisplayActiveStartOffset", 0); + if (lvalue != 0) + entry->display_active_start_offset = static_cast(lvalue); + lvalue = ini.GetLongValue(section, "DisplayActiveEndOffset", 0); + if (lvalue != 0) + entry->display_active_end_offset = static_cast(lvalue); + + const char* cvalue = ini.GetValue(section, "DisplayCropMode", nullptr); + if (cvalue) + entry->display_crop_mode = Settings::ParseDisplayCropMode(cvalue); + cvalue = ini.GetValue(section, "DisplayAspectRatio", nullptr); + if (cvalue) + entry->display_aspect_ratio = Settings::ParseDisplayAspectRatio(cvalue); + + cvalue = ini.GetValue(section, "Controller1Type", nullptr); + if (cvalue) + entry->controller_1_type = Settings::ParseControllerTypeName(cvalue); + cvalue = ini.GetValue(section, "Controller2Type", nullptr); + if (cvalue) + entry->controller_2_type = Settings::ParseControllerTypeName(cvalue); + + cvalue = ini.GetValue(section, "GPUWidescreenHack", nullptr); + if (cvalue) + entry->gpu_widescreen_hack = StringUtil::FromChars(cvalue); +} + +static void StoreIniSection(const Entry& entry, const char* section, CSimpleIniA& ini) +{ + for (u32 trait = 0; trait < static_cast(Trait::Count); trait++) + { + if (entry.HasTrait(static_cast(trait))) + ini.SetBoolValue(section, s_trait_names[trait].first, true); + } + + if (entry.display_active_start_offset.has_value()) + ini.SetLongValue(section, "DisplayActiveStartOffset", entry.display_active_start_offset.value()); + + if (entry.display_active_end_offset.has_value()) + ini.SetLongValue(section, "DisplayActiveEndOffset", entry.display_active_end_offset.value()); + + if (entry.display_crop_mode.has_value()) + ini.SetValue(section, "DisplayCropMode", Settings::GetDisplayCropModeName(entry.display_crop_mode.value())); + if (entry.display_aspect_ratio.has_value()) + { + ini.SetValue(section, "DisplayAspectRatio", + Settings::GetDisplayAspectRatioName(entry.display_aspect_ratio.value())); + } + + if (entry.controller_1_type.has_value()) + ini.SetValue(section, "Controller1Type", Settings::GetControllerTypeName(entry.controller_1_type.value())); + if (entry.controller_2_type.has_value()) + ini.SetValue(section, "Controller2Type", Settings::GetControllerTypeName(entry.controller_2_type.value())); + + if (entry.gpu_widescreen_hack.has_value()) + ini.SetValue(section, "GPUWidescreenHack", entry.gpu_widescreen_hack.value() ? "true" : "false"); +} + +Database::Database() = default; + +Database::~Database() = default; + +const GameSettings::Entry* Database::GetEntry(const std::string& code) const +{ + auto it = m_entries.find(code); + return (it != m_entries.end()) ? &it->second : nullptr; +} + +bool Database::Load(const char* path) +{ + auto fp = FileSystem::OpenManagedCFile(path, "rb"); + if (!fp) + return false; + + CSimpleIniA ini; + SI_Error err = ini.LoadFile(fp.get()); + if (err != SI_OK) + { + Log_ErrorPrintf("Failed to parse game settings ini: %d", static_cast(err)); + return false; + } + + std::list sections; + ini.GetAllSections(sections); + for (const CSimpleIniA::Entry& section_entry : sections) + { + std::string code(section_entry.pItem); + auto it = m_entries.find(code); + if (it != m_entries.end()) + { + ParseIniSection(&it->second, code.c_str(), ini); + continue; + } + + Entry entry; + ParseIniSection(&entry, code.c_str(), ini); + m_entries.emplace(std::move(code), std::move(entry)); + } + + Log_InfoPrintf("Loaded settings for %zu games from '%s'", sections.size(), path); + return true; +} + +void Database::SetEntry(const std::string& code, const std::string& name, const Entry& entry, const char* save_path) +{ + if (save_path) + { + CSimpleIniA ini; + if (FileSystem::FileExists(save_path)) + { + auto fp = FileSystem::OpenManagedCFile(save_path, "rb"); + if (fp) + { + SI_Error err = ini.LoadFile(fp.get()); + if (err != SI_OK) + Log_ErrorPrintf("Failed to parse game settings ini: %d. Contents will be lost.", static_cast(err)); + } + else + { + Log_ErrorPrintf("Failed to open existing settings ini: '%s'", save_path); + } + } + + ini.Delete(code.c_str(), nullptr, false); + ini.SetValue(code.c_str(), nullptr, nullptr, SmallString::FromFormat("# %s (%s)", code.c_str(), name.c_str()), + false); + StoreIniSection(entry, code.c_str(), ini); + + const bool did_exist = FileSystem::FileExists(save_path); + auto fp = FileSystem::OpenManagedCFile(save_path, "wb"); + if (fp) + { + // write file comment so simpleini doesn't get confused + if (!did_exist) + std::fputs("# DuckStation Game Settings\n\n", fp.get()); + + SI_Error err = ini.SaveFile(fp.get()); + if (err != SI_OK) + Log_ErrorPrintf("Failed to save game settings ini: %d", static_cast(err)); + } + else + { + Log_ErrorPrintf("Failed to open settings ini for saving: '%s'", save_path); + } + } + + auto it = m_entries.find(code); + if (it != m_entries.end()) + it->second = entry; + else + m_entries.emplace(code, entry); +} + +void Entry::ApplySettings(bool display_osd_messages) const +{ + constexpr float osd_duration = 10.0f; + + if (display_active_start_offset.has_value()) + g_settings.display_active_start_offset = display_active_start_offset.value(); + if (display_active_end_offset.has_value()) + g_settings.display_active_end_offset = display_active_end_offset.value(); + + if (display_crop_mode.has_value()) + g_settings.display_crop_mode = display_crop_mode.value(); + if (display_aspect_ratio.has_value()) + g_settings.display_aspect_ratio = display_aspect_ratio.value(); + if (controller_1_type.has_value()) + g_settings.controller_types[0] = controller_1_type.value(); + if (controller_2_type.has_value()) + g_settings.controller_types[1] = controller_2_type.value(); + if (gpu_widescreen_hack.has_value()) + g_settings.gpu_widescreen_hack = gpu_widescreen_hack.value(); + + if (HasTrait(Trait::ForceInterpreter)) + { + if (display_osd_messages && g_settings.cpu_execution_mode != CPUExecutionMode::Interpreter) + g_host_interface->AddOSDMessage("CPU execution mode forced to interpreter by game settings.", osd_duration); + + g_settings.cpu_execution_mode = CPUExecutionMode::Interpreter; + } + + if (HasTrait(Trait::ForceSoftwareRenderer)) + { + if (display_osd_messages && g_settings.gpu_renderer != GPURenderer::Software) + g_host_interface->AddOSDMessage("GPU renderer forced to software by game settings.", osd_duration); + + g_settings.gpu_renderer = GPURenderer::Software; + } + + if (HasTrait(Trait::EnableInterlacing)) + { + if (display_osd_messages && g_settings.gpu_disable_interlacing) + g_host_interface->AddOSDMessage("Interlacing enabled by game settings.", osd_duration); + + g_settings.gpu_disable_interlacing = false; + } + + if (HasTrait(Trait::DisableTrueColor)) + { + if (display_osd_messages && g_settings.gpu_true_color) + g_host_interface->AddOSDMessage("True color disabled by game settings.", osd_duration); + + g_settings.gpu_true_color = false; + } + + if (HasTrait(Trait::DisableUpscaling)) + { + if (display_osd_messages && g_settings.gpu_resolution_scale > 1) + g_host_interface->AddOSDMessage("Upscaling disabled by game settings.", osd_duration); + + g_settings.gpu_resolution_scale = 1; + } + + if (HasTrait(Trait::DisableScaledDithering)) + { + if (display_osd_messages && g_settings.gpu_scaled_dithering) + g_host_interface->AddOSDMessage("Scaled dithering disabled by game settings.", osd_duration); + + g_settings.gpu_scaled_dithering = false; + } + + if (HasTrait(Trait::DisableWidescreen)) + { + if (display_osd_messages && + (g_settings.display_aspect_ratio == DisplayAspectRatio::R16_9 || g_settings.gpu_widescreen_hack)) + { + g_host_interface->AddOSDMessage("Widescreen disabled by game settings.", osd_duration); + } + + g_settings.display_aspect_ratio = DisplayAspectRatio::R4_3; + g_settings.gpu_widescreen_hack = false; + } + + if (HasTrait(Trait::DisablePGXP)) + { + if (display_osd_messages && g_settings.gpu_pgxp_enable) + g_host_interface->AddOSDMessage("PGXP geometry correction disabled by game settings.", osd_duration); + + g_settings.gpu_pgxp_enable = false; + } + + if (HasTrait(Trait::DisablePGXPCulling)) + { + if (display_osd_messages && g_settings.gpu_pgxp_culling) + g_host_interface->AddOSDMessage("PGXP culling disabled by game settings.", osd_duration); + + g_settings.gpu_pgxp_culling = false; + } + + if (HasTrait(Trait::EnablePGXPVertexCache)) + { + if (display_osd_messages && g_settings.gpu_pgxp_enable && !g_settings.gpu_pgxp_vertex_cache) + g_host_interface->AddOSDMessage("PGXP vertex cache enabled by game settings.", osd_duration); + + g_settings.gpu_pgxp_vertex_cache = true; + } + + if (HasTrait(Trait::EnablePGXPCPUMode)) + { + if (display_osd_messages && g_settings.gpu_pgxp_enable && !g_settings.gpu_pgxp_cpu) + g_host_interface->AddOSDMessage("PGXP CPU mode enabled by game settings.", osd_duration); + + g_settings.gpu_pgxp_cpu = true; + } + + if (HasTrait(Trait::ForceDigitalController)) + { + for (u32 i = 0; i < NUM_CONTROLLER_AND_CARD_PORTS; i++) + { + if (g_settings.controller_types[i] != ControllerType::None && + g_settings.controller_types[i] != ControllerType::DigitalController) + { + if (display_osd_messages) + { + g_host_interface->AddFormattedOSDMessage(osd_duration, "Controller %u changed to digital by game settings.", + i + 1u); + } + + g_settings.controller_types[i] = ControllerType::DigitalController; + } + } + } + + if (HasTrait(Trait::EnableRecompilerMemoryExceptions)) + { + if (display_osd_messages && g_settings.cpu_execution_mode == CPUExecutionMode::Recompiler && + !g_settings.cpu_recompiler_memory_exceptions) + { + g_host_interface->AddOSDMessage("Recompiler memory exceptions enabled by game settings.", osd_duration); + } + + g_settings.cpu_recompiler_memory_exceptions = true; + } + + // TODO: Overscan settings. +} + +} // namespace GameSettings \ No newline at end of file diff --git a/src/core/game_settings.h b/src/core/game_settings.h new file mode 100644 index 000000000..09907dd79 --- /dev/null +++ b/src/core/game_settings.h @@ -0,0 +1,74 @@ +#pragma once +#include "types.h" +#include +#include +#include +#include + +class ByteStream; + +namespace GameSettings { +enum class Trait : u32 +{ + ForceInterpreter, + ForceSoftwareRenderer, + EnableInterlacing, + DisableTrueColor, + DisableUpscaling, + DisableScaledDithering, + DisableWidescreen, + DisablePGXP, + DisablePGXPCulling, + EnablePGXPVertexCache, + EnablePGXPCPUMode, + ForceDigitalController, + EnableRecompilerMemoryExceptions, + + Count +}; + +const char* GetTraitName(Trait trait); +const char* GetTraitDisplayName(Trait trait); + +struct Entry +{ + std::bitset(Trait::Count)> traits{}; + std::optional display_active_start_offset; + std::optional display_active_end_offset; + + // user settings + std::optional display_crop_mode; + std::optional display_aspect_ratio; + std::optional controller_1_type; + std::optional controller_2_type; + std::optional gpu_widescreen_hack; + + ALWAYS_INLINE bool HasTrait(Trait trait) const { return traits[static_cast(trait)]; } + ALWAYS_INLINE void AddTrait(Trait trait) { traits[static_cast(trait)] = true; } + ALWAYS_INLINE void RemoveTrait(Trait trait) { traits[static_cast(trait)] = false; } + ALWAYS_INLINE void SetTrait(Trait trait, bool enabled) { traits[static_cast(trait)] = enabled; } + + bool HasAnySettings() const; + + bool LoadFromStream(ByteStream* stream); + bool SaveToStream(ByteStream* stream) const; + + void ApplySettings(bool display_osd_messages) const; +}; + +class Database +{ +public: + Database(); + ~Database(); + + const Entry* GetEntry(const std::string& code) const; + void SetEntry(const std::string& code, const std::string& name, const Entry& entry, const char* save_path); + + bool Load(const char* path); + +private: + std::unordered_map m_entries; +}; + +}; // namespace GameSettings \ No newline at end of file diff --git a/src/core/gpu.cpp b/src/core/gpu.cpp index de8b77f24..c56657c45 100644 --- a/src/core/gpu.cpp +++ b/src/core/gpu.cpp @@ -513,15 +513,15 @@ void GPU::UpdateCRTCDisplayParameters() switch (crop_mode) { case DisplayCropMode::None: - cs.horizontal_active_start = 487; - cs.horizontal_active_end = 3282; + cs.horizontal_active_start = static_cast(std::max(0, 487 + g_settings.display_active_start_offset)); + cs.horizontal_active_end = static_cast(std::max(0, 3282 + g_settings.display_active_end_offset)); cs.vertical_active_start = 20; cs.vertical_active_end = 308; break; case DisplayCropMode::Overscan: - cs.horizontal_active_start = 628; - cs.horizontal_active_end = 3188; + cs.horizontal_active_start = static_cast(std::max(0, 628 + g_settings.display_active_start_offset)); + cs.horizontal_active_end = static_cast(std::max(0, 3188 + g_settings.display_active_end_offset)); cs.vertical_active_start = 30; cs.vertical_active_end = 298; break; @@ -540,15 +540,15 @@ void GPU::UpdateCRTCDisplayParameters() switch (crop_mode) { case DisplayCropMode::None: - cs.horizontal_active_start = 488; - cs.horizontal_active_end = 3288; + cs.horizontal_active_start = static_cast(std::max(0, 488 + g_settings.display_active_start_offset)); + cs.horizontal_active_end = static_cast(std::max(0, 3288 + g_settings.display_active_end_offset)); cs.vertical_active_start = 16; cs.vertical_active_end = 256; break; case DisplayCropMode::Overscan: - cs.horizontal_active_start = 608; - cs.horizontal_active_end = 3168; + cs.horizontal_active_start = static_cast(std::max(0, 608 + g_settings.display_active_start_offset)); + cs.horizontal_active_end = static_cast(std::max(0, 3168 + g_settings.display_active_end_offset)); cs.vertical_active_start = 24; cs.vertical_active_end = 248; break; @@ -759,9 +759,15 @@ void GPU::CRTCTickEvent(TickCount ticks) // start the new frame m_crtc_state.current_scanline = 0; if (m_GPUSTAT.vertical_interlace) + { m_crtc_state.interlaced_field ^= 1u; + m_GPUSTAT.interlaced_field = m_crtc_state.interlaced_field; + } else + { m_crtc_state.interlaced_field = 0; + m_GPUSTAT.interlaced_field = 0u; // new GPU = 1, old GPU = 0 + } } } diff --git a/src/core/gpu.h b/src/core/gpu.h index 3ebe49898..b91981071 100644 --- a/src/core/gpu.h +++ b/src/core/gpu.h @@ -489,7 +489,7 @@ protected: BitField draw_to_displayed_field; BitField set_mask_while_drawing; BitField check_mask_before_draw; - BitField interlaced_field; + BitField interlaced_field; BitField reverse_flag; BitField texture_disable; BitField horizontal_resolution_2; diff --git a/src/core/gpu_hw_opengl.cpp b/src/core/gpu_hw_opengl.cpp index 6d8844389..97aca0306 100644 --- a/src/core/gpu_hw_opengl.cpp +++ b/src/core/gpu_hw_opengl.cpp @@ -588,7 +588,7 @@ void GPU_HW_OpenGL::ClearDisplay() glClearColor(0.0f, 0.0f, 0.0f, 1.0f); glClear(GL_COLOR_BUFFER_BIT); glEnable(GL_SCISSOR_TEST); - m_vram_texture.BindFramebuffer(GL_DRAW_FRAMEBUFFER); + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, m_vram_fbo_id); } void GPU_HW_OpenGL::UpdateDisplay() diff --git a/src/core/gte.cpp b/src/core/gte.cpp index 12cda54ad..54694587a 100644 --- a/src/core/gte.cpp +++ b/src/core/gte.cpp @@ -625,7 +625,7 @@ static void RTPS(const s16 V[3], u8 shift, bool lm, bool last) if (g_settings.gpu_pgxp_enable) { // this can potentially use increased precision on Z - const float precise_z = std::max((float)REGS.H / 2.f, (float)REGS.SZ3); + const float precise_z = std::max((float)REGS.H / 2.f, (float)z / 4096.0f); const float precise_h_div_sz = (float)REGS.H / precise_z; const float fofx = ((float)REGS.OFX / (float)(1 << 16)); const float fofy = ((float)REGS.OFY / (float)(1 << 16)); diff --git a/src/core/host_interface.cpp b/src/core/host_interface.cpp index e72cb1fa1..db8efc625 100644 --- a/src/core/host_interface.cpp +++ b/src/core/host_interface.cpp @@ -114,7 +114,7 @@ void HostInterface::ResetSystem() { System::Reset(); System::ResetPerformanceCounters(); - AddOSDMessage("System reset."); + AddOSDMessage(TranslateStdString("OSDMessage", "System reset.")); } void HostInterface::PowerOffSystem() @@ -284,13 +284,13 @@ bool HostInterface::LoadState(const char* filename) if (!stream) return false; - AddFormattedOSDMessage(5.0f, "Loading state from '%s'...", filename); + AddFormattedOSDMessage(5.0f, TranslateString("OSDMessage", "Loading state from '%s'..."), filename); if (!System::IsShutdown()) { if (!System::LoadState(stream.get())) { - ReportFormattedError("Loading state from '%s' failed. Resetting.", filename); + ReportFormattedError(TranslateString("OSDMessage", "Loading state from '%s' failed. Resetting."), filename); ResetSystem(); return false; } @@ -318,12 +318,12 @@ bool HostInterface::SaveState(const char* filename) const bool result = System::SaveState(stream.get()); if (!result) { - ReportFormattedError("Saving state to '%s' failed.", filename); + ReportFormattedError(TranslateString("OSDMessage", "Saving state to '%s' failed."), filename); stream->Discard(); } else { - AddFormattedOSDMessage(5.0f, "State saved to '%s'.", filename); + AddFormattedOSDMessage(5.0f, TranslateString("OSDMessage", "State saved to '%s'."), filename); stream->Commit(); } @@ -358,6 +358,7 @@ void HostInterface::SetDefaultSettings(SettingsInterface& si) si.SetBoolValue("Main", "SaveStateOnExit", true); si.SetBoolValue("Main", "ConfirmPowerOff", true); si.SetBoolValue("Main", "LoadDevicesFromSaveStates", false); + si.SetBoolValue("Main", "ApplyGameSettings", true); si.SetStringValue("CPU", "ExecutionMode", Settings::GetCPUExecutionModeName(Settings::DEFAULT_CPU_EXECUTION_MODE)); si.SetBoolValue("CPU", "RecompilerMemoryExceptions", false); @@ -375,8 +376,11 @@ void HostInterface::SetDefaultSettings(SettingsInterface& si) si.SetBoolValue("GPU", "PGXPCulling", true); si.SetBoolValue("GPU", "PGXPTextureCorrection", true); si.SetBoolValue("GPU", "PGXPVertexCache", false); + si.SetBoolValue("GPU", "PGXPCPU", false); si.SetStringValue("Display", "CropMode", Settings::GetDisplayCropModeName(Settings::DEFAULT_DISPLAY_CROP_MODE)); + si.SetIntValue("Display", "OverscanActiveStartOffset", 0); + si.SetIntValue("Display", "OverscanActiveEndOffset", 0); si.SetStringValue("Display", "AspectRatio", Settings::GetDisplayAspectRatioName(Settings::DEFAULT_DISPLAY_ASPECT_RATIO)); si.SetBoolValue("Display", "LinearFiltering", true); @@ -440,6 +444,32 @@ void HostInterface::LoadSettings(SettingsInterface& si) g_settings.Load(si); } +void HostInterface::FixIncompatibleSettings(bool display_osd_messages) +{ + if (g_settings.gpu_pgxp_enable) + { + if (g_settings.gpu_renderer == GPURenderer::Software) + { + if (display_osd_messages) + { + AddOSDMessage(TranslateStdString("OSDMessage", "PGXP is incompatible with the software renderer, disabling PGXP."), 10.0f); + } + g_settings.gpu_pgxp_enable = false; + } + else if (g_settings.gpu_pgxp_cpu && g_settings.cpu_execution_mode == CPUExecutionMode::Recompiler) + { + if (display_osd_messages) + { + AddOSDMessage( + TranslateStdString("OSDMessage", + "PGXP CPU mode is incompatible with the recompiler, using Cached Interpreter instead."), + 10.0f); + } + g_settings.cpu_execution_mode = CPUExecutionMode::CachedInterpreter; + } + } +} + void HostInterface::SaveSettings(SettingsInterface& si) { g_settings.Save(si); @@ -447,7 +477,7 @@ void HostInterface::SaveSettings(SettingsInterface& si) void HostInterface::CheckForSettingsChanges(const Settings& old_settings) { - if (!System::IsShutdown()) + if (System::IsValid()) { if (g_settings.gpu_renderer != old_settings.gpu_renderer || g_settings.gpu_use_debug_device != old_settings.gpu_use_debug_device) @@ -502,7 +532,9 @@ void HostInterface::CheckForSettingsChanges(const Settings& old_settings) g_settings.gpu_force_ntsc_timings != old_settings.gpu_force_ntsc_timings || g_settings.display_crop_mode != old_settings.display_crop_mode || g_settings.display_aspect_ratio != old_settings.display_aspect_ratio || - g_settings.gpu_pgxp_enable != old_settings.gpu_pgxp_enable) + g_settings.gpu_pgxp_enable != old_settings.gpu_pgxp_enable || + g_settings.display_active_start_offset != old_settings.display_active_start_offset || + g_settings.display_active_end_offset != old_settings.display_active_end_offset) { g_gpu->UpdateSettings(); } @@ -658,6 +690,16 @@ float HostInterface::GetFloatSettingValue(const char* section, const char* key, return float_value.value_or(default_value); } +TinyString HostInterface::TranslateString(const char* context, const char* str) const +{ + return str; +} + +std::string HostInterface::TranslateStdString(const char* context, const char* str) const +{ + return str; +} + void HostInterface::ToggleSoftwareRendering() { if (System::IsShutdown() || g_settings.gpu_renderer == GPURenderer::Software) diff --git a/src/core/host_interface.h b/src/core/host_interface.h index 2f92313a2..596ffaf75 100644 --- a/src/core/host_interface.h +++ b/src/core/host_interface.h @@ -109,6 +109,10 @@ public: /// Returns a float setting from the configuration. virtual float GetFloatSettingValue(const char* section, const char* key, float default_value = 0.0f); + /// Translates a string to the current language. + virtual TinyString TranslateString(const char* context, const char* str) const; + virtual std::string TranslateStdString(const char* context, const char* str) const; + /// Loads the BIOS image for the specified region. std::optional> GetBIOSImage(ConsoleRegion region); @@ -134,6 +138,9 @@ protected: /// Saves current settings variables to ini. virtual void SaveSettings(SettingsInterface& si); + /// Checks and fixes up any incompatible settings. + virtual void FixIncompatibleSettings(bool display_osd_messages); + /// Checks for settings changes, std::move() the old settings away for comparing beforehand. virtual void CheckForSettingsChanges(const Settings& old_settings); @@ -161,4 +168,6 @@ protected: std::string m_user_directory; }; +#define TRANSLATABLE(context, str) str + extern HostInterface* g_host_interface; diff --git a/src/core/pgxp.cpp b/src/core/pgxp.cpp index 7ca7c4f94..04ae11219 100644 --- a/src/core/pgxp.cpp +++ b/src/core/pgxp.cpp @@ -21,6 +21,7 @@ #include "pgxp.h" #include "settings.h" #include +#include namespace PGXP { // pgxp_types.h @@ -77,6 +78,11 @@ typedef enum INVALID_8BIT_STORE = 6 } PGXP_error_states; +typedef enum +{ + VALID_HALF = (1 << 0) +} PGXP_half_flags; + #define NONE 0 #define ALL 0xFFFFFFFF #define VALID 1 @@ -92,9 +98,15 @@ typedef enum static const PGXP_value PGXP_value_invalid_address = {0.f, 0.f, 0.f, {0}, 0, 0, INVALID_ADDRESS, 0, 0}; static const PGXP_value PGXP_value_zero = {0.f, 0.f, 0.f, {0}, 0, VALID_ALL, 0, 0, 0}; +static void MakeValid(PGXP_value* pV, u32 psxV); static void Validate(PGXP_value* pV, u32 psxV); static void MaskValidate(PGXP_value* pV, u32 psxV, u32 mask, u32 validMask); +static double f16Sign(double in); +static double f16Unsign(double in); +static double fu16Trunc(double in); +static double f16Overflow(double in); + typedef union { struct @@ -138,6 +150,20 @@ static PGXP_value* CPU_reg = CPU_reg_mem; static PGXP_value* CP0_reg = CP0_reg_mem; // pgxp_value.c +void MakeValid(PGXP_value* pV, u32 psxV) +{ + psx_value psx; + psx.d = psxV; + if (VALID_01 != (pV->flags & VALID_01)) + { + pV->x = psx.sw.l; + pV->y = psx.sw.h; + pV->z = 0.f; + pV->flags |= VALID_01; + pV->value = psx.d; + } +} + void Validate(PGXP_value* pV, u32 psxV) { // assume pV is not NULL @@ -150,6 +176,23 @@ void MaskValidate(PGXP_value* pV, u32 psxV, u32 mask, u32 validMask) pV->flags &= ((pV->value & mask) == (psxV & mask)) ? ALL : (ALL ^ (validMask)); } +double f16Sign(double in) +{ + u32 s = (u32)(in * (double)((u32)1 << 16)); + return ((double)*((s32*)&s)) / (double)((s32)1 << 16); +} +double f16Unsign(double in) +{ + return (in >= 0) ? in : ((double)in + (double)USHRT_MAX + 1); +} +double f16Overflow(double in) +{ + double out = 0; + s64 v = ((s64)in) >> 16; + out = (double)v; + return out; +} + // pgxp_mem.c static void PGXP_InitMem(); static PGXP_value Mem[3 * 2048 * 1024 / 4]; // mirror 2MB in 32-bit words * 3 @@ -797,4 +840,1001 @@ void CPU_SW(u32 instr, u32 rtVal, u32 addr) WriteMem(&CPU_reg[rt(instr)], addr); } -} // namespace PGXP \ No newline at end of file +void CPU_ADDI(u32 instr, u32 rtVal, u32 rsVal) +{ + // Rt = Rs + Imm (signed) + psx_value tempImm; + PGXP_value ret; + + Validate(&CPU_reg[rs(instr)], rsVal); + ret = CPU_reg[rs(instr)]; + tempImm.d = imm(instr); + tempImm.sd = (tempImm.sd << 16) >> 16; // sign extend + + ret.x = (float)f16Unsign(ret.x); + ret.x += (float)tempImm.w.l; + + // carry on over/underflow + float of = (ret.x > USHRT_MAX) ? 1.f : (ret.x < 0) ? -1.f : 0.f; + ret.x = (float)f16Sign(ret.x); + // ret.x -= of * (USHRT_MAX + 1); + ret.y += tempImm.sw.h + of; + + // truncate on overflow/underflow + ret.y += (ret.y > SHRT_MAX) ? -(USHRT_MAX + 1) : (ret.y < SHRT_MIN) ? USHRT_MAX + 1 : 0.f; + + CPU_reg[rt(instr)] = ret; + CPU_reg[rt(instr)].value = rtVal; +} + +void CPU_ADDIU(u32 instr, u32 rtVal, u32 rsVal) +{ + // Rt = Rs + Imm (signed) (unsafe?) + CPU_ADDI(instr, rtVal, rsVal); +} + +void CPU_ANDI(u32 instr, u32 rtVal, u32 rsVal) +{ + // Rt = Rs & Imm + psx_value vRt; + PGXP_value ret; + + Validate(&CPU_reg[rs(instr)], rsVal); + ret = CPU_reg[rs(instr)]; + + vRt.d = rtVal; + + ret.y = 0.f; // remove upper 16-bits + + switch (imm(instr)) + { + case 0: + // if 0 then x == 0 + ret.x = 0.f; + break; + case 0xFFFF: + // if saturated then x == x + break; + default: + // otherwise x is low precision value + ret.x = vRt.sw.l; + ret.flags |= VALID_0; + } + + ret.flags |= VALID_1; + + CPU_reg[rt(instr)] = ret; + CPU_reg[rt(instr)].value = rtVal; +} + +void CPU_ORI(u32 instr, u32 rtVal, u32 rsVal) +{ + // Rt = Rs | Imm + psx_value vRt; + PGXP_value ret; + + Validate(&CPU_reg[rs(instr)], rsVal); + ret = CPU_reg[rs(instr)]; + + vRt.d = rtVal; + + switch (imm(instr)) + { + case 0: + // if 0 then x == x + break; + default: + // otherwise x is low precision value + ret.x = vRt.sw.l; + ret.flags |= VALID_0; + } + + ret.value = rtVal; + CPU_reg[rt(instr)] = ret; +} + +void CPU_XORI(u32 instr, u32 rtVal, u32 rsVal) +{ + // Rt = Rs ^ Imm + psx_value vRt; + PGXP_value ret; + + Validate(&CPU_reg[rs(instr)], rsVal); + ret = CPU_reg[rs(instr)]; + + vRt.d = rtVal; + + switch (imm(instr)) + { + case 0: + // if 0 then x == x + break; + default: + // otherwise x is low precision value + ret.x = vRt.sw.l; + ret.flags |= VALID_0; + } + + ret.value = rtVal; + CPU_reg[rt(instr)] = ret; +} + +void CPU_SLTI(u32 instr, u32 rtVal, u32 rsVal) +{ + // Rt = Rs < Imm (signed) + psx_value tempImm; + PGXP_value ret; + + Validate(&CPU_reg[rs(instr)], rsVal); + ret = CPU_reg[rs(instr)]; + + tempImm.w.h = imm(instr); + ret.y = 0.f; + ret.x = (CPU_reg[rs(instr)].x < tempImm.sw.h) ? 1.f : 0.f; + ret.flags |= VALID_1; + ret.value = rtVal; + + CPU_reg[rt(instr)] = ret; +} + +void CPU_SLTIU(u32 instr, u32 rtVal, u32 rsVal) +{ + // Rt = Rs < Imm (Unsigned) + psx_value tempImm; + PGXP_value ret; + + Validate(&CPU_reg[rs(instr)], rsVal); + ret = CPU_reg[rs(instr)]; + + tempImm.w.h = imm(instr); + ret.y = 0.f; + ret.x = (f16Unsign(CPU_reg[rs(instr)].x) < tempImm.w.h) ? 1.f : 0.f; + ret.flags |= VALID_1; + ret.value = rtVal; + + CPU_reg[rt(instr)] = ret; +} + +//////////////////////////////////// +// Load Upper +//////////////////////////////////// +void CPU_LUI(u32 instr, u32 rtVal) +{ + // Rt = Imm << 16 + CPU_reg[rt(instr)] = PGXP_value_zero; + CPU_reg[rt(instr)].y = (float)(s16)imm(instr); + CPU_reg[rt(instr)].hFlags = VALID_HALF; + CPU_reg[rt(instr)].value = rtVal; + CPU_reg[rt(instr)].flags = VALID_01; +} + +//////////////////////////////////// +// Register Arithmetic +//////////////////////////////////// + +void CPU_ADD(u32 instr, u32 rdVal, u32 rsVal, u32 rtVal) +{ + // Rd = Rs + Rt (signed) + PGXP_value ret; + Validate(&CPU_reg[rs(instr)], rsVal); + Validate(&CPU_reg[rt(instr)], rtVal); + + // iCB: Only require one valid input + if (((CPU_reg[rt(instr)].flags & VALID_01) != VALID_01) != ((CPU_reg[rs(instr)].flags & VALID_01) != VALID_01)) + { + MakeValid(&CPU_reg[rs(instr)], rsVal); + MakeValid(&CPU_reg[rt(instr)], rtVal); + } + + ret = CPU_reg[rs(instr)]; + + ret.x = (float)f16Unsign(ret.x); + ret.x += (float)f16Unsign(CPU_reg[rt(instr)].x); + + // carry on over/underflow + float of = (ret.x > USHRT_MAX) ? 1.f : (ret.x < 0) ? -1.f : 0.f; + ret.x = (float)f16Sign(ret.x); + // ret.x -= of * (USHRT_MAX + 1); + ret.y += CPU_reg[rt(instr)].y + of; + + // truncate on overflow/underflow + ret.y += (ret.y > SHRT_MAX) ? -(USHRT_MAX + 1) : (ret.y < SHRT_MIN) ? USHRT_MAX + 1 : 0.f; + + // TODO: decide which "z/w" component to use + + ret.halfFlags[0] &= CPU_reg[rt(instr)].halfFlags[0]; + ret.gFlags |= CPU_reg[rt(instr)].gFlags; + ret.lFlags |= CPU_reg[rt(instr)].lFlags; + ret.hFlags |= CPU_reg[rt(instr)].hFlags; + + ret.value = rdVal; + + CPU_reg[rd(instr)] = ret; +} + +void CPU_ADDU(u32 instr, u32 rdVal, u32 rsVal, u32 rtVal) +{ + // Rd = Rs + Rt (signed) (unsafe?) + CPU_ADD(instr, rdVal, rsVal, rtVal); +} + +void CPU_SUB(u32 instr, u32 rdVal, u32 rsVal, u32 rtVal) +{ + // Rd = Rs - Rt (signed) + PGXP_value ret; + Validate(&CPU_reg[rs(instr)], rsVal); + Validate(&CPU_reg[rt(instr)], rtVal); + + // iCB: Only require one valid input + if (((CPU_reg[rt(instr)].flags & VALID_01) != VALID_01) != ((CPU_reg[rs(instr)].flags & VALID_01) != VALID_01)) + { + MakeValid(&CPU_reg[rs(instr)], rsVal); + MakeValid(&CPU_reg[rt(instr)], rtVal); + } + + ret = CPU_reg[rs(instr)]; + + ret.x = (float)f16Unsign(ret.x); + ret.x -= (float)f16Unsign(CPU_reg[rt(instr)].x); + + // carry on over/underflow + float of = (ret.x > USHRT_MAX) ? 1.f : (ret.x < 0) ? -1.f : 0.f; + ret.x = (float)f16Sign(ret.x); + // ret.x -= of * (USHRT_MAX + 1); + ret.y -= CPU_reg[rt(instr)].y - of; + + // truncate on overflow/underflow + ret.y += (ret.y > SHRT_MAX) ? -(USHRT_MAX + 1) : (ret.y < SHRT_MIN) ? USHRT_MAX + 1 : 0.f; + + ret.halfFlags[0] &= CPU_reg[rt(instr)].halfFlags[0]; + ret.gFlags |= CPU_reg[rt(instr)].gFlags; + ret.lFlags |= CPU_reg[rt(instr)].lFlags; + ret.hFlags |= CPU_reg[rt(instr)].hFlags; + + ret.value = rdVal; + + CPU_reg[rd(instr)] = ret; +} + +void CPU_SUBU(u32 instr, u32 rdVal, u32 rsVal, u32 rtVal) +{ + // Rd = Rs - Rt (signed) (unsafe?) + CPU_SUB(instr, rdVal, rsVal, rtVal); +} + +void CPU_AND_(u32 instr, u32 rdVal, u32 rsVal, u32 rtVal) +{ + // Rd = Rs & Rt + psx_value vald, vals, valt; + PGXP_value ret; + + Validate(&CPU_reg[rs(instr)], rsVal); + Validate(&CPU_reg[rt(instr)], rtVal); + + // iCB: Only require one valid input + if (((CPU_reg[rt(instr)].flags & VALID_01) != VALID_01) != ((CPU_reg[rs(instr)].flags & VALID_01) != VALID_01)) + { + MakeValid(&CPU_reg[rs(instr)], rsVal); + MakeValid(&CPU_reg[rt(instr)], rtVal); + } + + vald.d = rdVal; + vals.d = rsVal; + valt.d = rtVal; + + // CPU_reg[rd(instr)].valid = CPU_reg[rs(instr)].valid && CPU_reg[rt(instr)].valid; + ret.flags = VALID_01; + + if (vald.w.l == 0) + { + ret.x = 0.f; + ret.lFlags = VALID_HALF; + } + else if (vald.w.l == vals.w.l) + { + ret.x = CPU_reg[rs(instr)].x; + ret.lFlags = CPU_reg[rs(instr)].lFlags; + ret.compFlags[0] = CPU_reg[rs(instr)].compFlags[0]; + } + else if (vald.w.l == valt.w.l) + { + ret.x = CPU_reg[rt(instr)].x; + ret.lFlags = CPU_reg[rt(instr)].lFlags; + ret.compFlags[0] = CPU_reg[rt(instr)].compFlags[0]; + } + else + { + ret.x = (float)vald.sw.l; + ret.compFlags[0] = VALID; + ret.lFlags = 0; + } + + if (vald.w.h == 0) + { + ret.y = 0.f; + ret.hFlags = VALID_HALF; + } + else if (vald.w.h == vals.w.h) + { + ret.y = CPU_reg[rs(instr)].y; + ret.hFlags = CPU_reg[rs(instr)].hFlags; + ret.compFlags[1] &= CPU_reg[rs(instr)].compFlags[1]; + } + else if (vald.w.h == valt.w.h) + { + ret.y = CPU_reg[rt(instr)].y; + ret.hFlags = CPU_reg[rt(instr)].hFlags; + ret.compFlags[1] &= CPU_reg[rt(instr)].compFlags[1]; + } + else + { + ret.y = (float)vald.sw.h; + ret.compFlags[1] = VALID; + ret.hFlags = 0; + } + + // iCB Hack: Force validity if even one half is valid + // if ((ret.hFlags & VALID_HALF) || (ret.lFlags & VALID_HALF)) + // ret.valid = 1; + // /iCB Hack + + // Get a valid W + if ((CPU_reg[rs(instr)].flags & VALID_2) == VALID_2) + { + ret.z = CPU_reg[rs(instr)].z; + ret.compFlags[2] = CPU_reg[rs(instr)].compFlags[2]; + } + else if ((CPU_reg[rt(instr)].flags & VALID_2) == VALID_2) + { + ret.z = CPU_reg[rt(instr)].z; + ret.compFlags[2] = CPU_reg[rt(instr)].compFlags[2]; + } + + ret.value = rdVal; + CPU_reg[rd(instr)] = ret; +} + +void CPU_OR_(u32 instr, u32 rdVal, u32 rsVal, u32 rtVal) +{ + // Rd = Rs | Rt + CPU_AND_(instr, rdVal, rsVal, rtVal); +} + +void CPU_XOR_(u32 instr, u32 rdVal, u32 rsVal, u32 rtVal) +{ + // Rd = Rs ^ Rt + CPU_AND_(instr, rdVal, rsVal, rtVal); +} + +void CPU_NOR(u32 instr, u32 rdVal, u32 rsVal, u32 rtVal) +{ + // Rd = Rs NOR Rt + CPU_AND_(instr, rdVal, rsVal, rtVal); +} + +void CPU_SLT(u32 instr, u32 rdVal, u32 rsVal, u32 rtVal) +{ + // Rd = Rs < Rt (signed) + PGXP_value ret; + Validate(&CPU_reg[rs(instr)], rsVal); + Validate(&CPU_reg[rt(instr)], rtVal); + + // iCB: Only require one valid input + if (((CPU_reg[rt(instr)].flags & VALID_01) != VALID_01) != ((CPU_reg[rs(instr)].flags & VALID_01) != VALID_01)) + { + MakeValid(&CPU_reg[rs(instr)], rsVal); + MakeValid(&CPU_reg[rt(instr)], rtVal); + } + + ret = CPU_reg[rs(instr)]; + ret.y = 0.f; + ret.compFlags[1] = VALID; + + ret.x = (CPU_reg[rs(instr)].y < CPU_reg[rt(instr)].y) ? + 1.f : + (f16Unsign(CPU_reg[rs(instr)].x) < f16Unsign(CPU_reg[rt(instr)].x)) ? 1.f : 0.f; + + ret.value = rdVal; + CPU_reg[rd(instr)] = ret; +} + +void CPU_SLTU(u32 instr, u32 rdVal, u32 rsVal, u32 rtVal) +{ + // Rd = Rs < Rt (unsigned) + PGXP_value ret; + Validate(&CPU_reg[rs(instr)], rsVal); + Validate(&CPU_reg[rt(instr)], rtVal); + + // iCB: Only require one valid input + if (((CPU_reg[rt(instr)].flags & VALID_01) != VALID_01) != ((CPU_reg[rs(instr)].flags & VALID_01) != VALID_01)) + { + MakeValid(&CPU_reg[rs(instr)], rsVal); + MakeValid(&CPU_reg[rt(instr)], rtVal); + } + + ret = CPU_reg[rs(instr)]; + ret.y = 0.f; + ret.compFlags[1] = VALID; + + ret.x = (f16Unsign(CPU_reg[rs(instr)].y) < f16Unsign(CPU_reg[rt(instr)].y)) ? + 1.f : + (f16Unsign(CPU_reg[rs(instr)].x) < f16Unsign(CPU_reg[rt(instr)].x)) ? 1.f : 0.f; + + ret.value = rdVal; + CPU_reg[rd(instr)] = ret; +} + +//////////////////////////////////// +// Register mult/div +//////////////////////////////////// + +void CPU_MULT(u32 instr, u32 hiVal, u32 loVal, u32 rsVal, u32 rtVal) +{ + // Hi/Lo = Rs * Rt (signed) + Validate(&CPU_reg[rs(instr)], rsVal); + Validate(&CPU_reg[rt(instr)], rtVal); + + // iCB: Only require one valid input + if (((CPU_reg[rt(instr)].flags & VALID_01) != VALID_01) != ((CPU_reg[rs(instr)].flags & VALID_01) != VALID_01)) + { + MakeValid(&CPU_reg[rs(instr)], rsVal); + MakeValid(&CPU_reg[rt(instr)], rtVal); + } + + CPU_Lo = CPU_Hi = CPU_reg[rs(instr)]; + + CPU_Lo.halfFlags[0] = CPU_Hi.halfFlags[0] = (CPU_reg[rs(instr)].halfFlags[0] & CPU_reg[rt(instr)].halfFlags[0]); + + double xx, xy, yx, yy; + double lx = 0, ly = 0, hx = 0, hy = 0; + + // Multiply out components + xx = f16Unsign(CPU_reg[rs(instr)].x) * f16Unsign(CPU_reg[rt(instr)].x); + xy = f16Unsign(CPU_reg[rs(instr)].x) * (CPU_reg[rt(instr)].y); + yx = (CPU_reg[rs(instr)].y) * f16Unsign(CPU_reg[rt(instr)].x); + yy = (CPU_reg[rs(instr)].y) * (CPU_reg[rt(instr)].y); + + // Split values into outputs + lx = xx; + + ly = f16Overflow(xx); + ly += xy + yx; + + hx = f16Overflow(ly); + hx += yy; + + hy = f16Overflow(hx); + + CPU_Lo.x = (float)f16Sign(lx); + CPU_Lo.y = (float)f16Sign(ly); + CPU_Hi.x = (float)f16Sign(hx); + CPU_Hi.y = (float)f16Sign(hy); + + CPU_Lo.value = loVal; + CPU_Hi.value = hiVal; +} + +void CPU_MULTU(u32 instr, u32 hiVal, u32 loVal, u32 rsVal, u32 rtVal) +{ + // Hi/Lo = Rs * Rt (unsigned) + Validate(&CPU_reg[rs(instr)], rsVal); + Validate(&CPU_reg[rt(instr)], rtVal); + + // iCB: Only require one valid input + if (((CPU_reg[rt(instr)].flags & VALID_01) != VALID_01) != ((CPU_reg[rs(instr)].flags & VALID_01) != VALID_01)) + { + MakeValid(&CPU_reg[rs(instr)], rsVal); + MakeValid(&CPU_reg[rt(instr)], rtVal); + } + + CPU_Lo = CPU_Hi = CPU_reg[rs(instr)]; + + CPU_Lo.halfFlags[0] = CPU_Hi.halfFlags[0] = (CPU_reg[rs(instr)].halfFlags[0] & CPU_reg[rt(instr)].halfFlags[0]); + + double xx, xy, yx, yy; + double lx = 0, ly = 0, hx = 0, hy = 0; + + // Multiply out components + xx = f16Unsign(CPU_reg[rs(instr)].x) * f16Unsign(CPU_reg[rt(instr)].x); + xy = f16Unsign(CPU_reg[rs(instr)].x) * f16Unsign(CPU_reg[rt(instr)].y); + yx = f16Unsign(CPU_reg[rs(instr)].y) * f16Unsign(CPU_reg[rt(instr)].x); + yy = f16Unsign(CPU_reg[rs(instr)].y) * f16Unsign(CPU_reg[rt(instr)].y); + + // Split values into outputs + lx = xx; + + ly = f16Overflow(xx); + ly += xy + yx; + + hx = f16Overflow(ly); + hx += yy; + + hy = f16Overflow(hx); + + CPU_Lo.x = (float)f16Sign(lx); + CPU_Lo.y = (float)f16Sign(ly); + CPU_Hi.x = (float)f16Sign(hx); + CPU_Hi.y = (float)f16Sign(hy); + + CPU_Lo.value = loVal; + CPU_Hi.value = hiVal; +} + +void CPU_DIV(u32 instr, u32 hiVal, u32 loVal, u32 rsVal, u32 rtVal) +{ + // Lo = Rs / Rt (signed) + // Hi = Rs % Rt (signed) + Validate(&CPU_reg[rs(instr)], rsVal); + Validate(&CPU_reg[rt(instr)], rtVal); + + //// iCB: Only require one valid input + if (((CPU_reg[rt(instr)].flags & VALID_01) != VALID_01) != ((CPU_reg[rs(instr)].flags & VALID_01) != VALID_01)) + { + MakeValid(&CPU_reg[rs(instr)], rsVal); + MakeValid(&CPU_reg[rt(instr)], rtVal); + } + + CPU_Lo = CPU_Hi = CPU_reg[rs(instr)]; + + CPU_Lo.halfFlags[0] = CPU_Hi.halfFlags[0] = (CPU_reg[rs(instr)].halfFlags[0] & CPU_reg[rt(instr)].halfFlags[0]); + + double vs = f16Unsign(CPU_reg[rs(instr)].x) + (CPU_reg[rs(instr)].y) * (double)(1 << 16); + double vt = f16Unsign(CPU_reg[rt(instr)].x) + (CPU_reg[rt(instr)].y) * (double)(1 << 16); + + double lo = vs / vt; + CPU_Lo.y = (float)f16Sign(f16Overflow(lo)); + CPU_Lo.x = (float)f16Sign(lo); + + double hi = fmod(vs, vt); + CPU_Hi.y = (float)f16Sign(f16Overflow(hi)); + CPU_Hi.x = (float)f16Sign(hi); + + CPU_Lo.value = loVal; + CPU_Hi.value = hiVal; +} + +void CPU_DIVU(u32 instr, u32 hiVal, u32 loVal, u32 rsVal, u32 rtVal) +{ + // Lo = Rs / Rt (unsigned) + // Hi = Rs % Rt (unsigned) + Validate(&CPU_reg[rs(instr)], rsVal); + Validate(&CPU_reg[rt(instr)], rtVal); + + //// iCB: Only require one valid input + if (((CPU_reg[rt(instr)].flags & VALID_01) != VALID_01) != ((CPU_reg[rs(instr)].flags & VALID_01) != VALID_01)) + { + MakeValid(&CPU_reg[rs(instr)], rsVal); + MakeValid(&CPU_reg[rt(instr)], rtVal); + } + + CPU_Lo = CPU_Hi = CPU_reg[rs(instr)]; + + CPU_Lo.halfFlags[0] = CPU_Hi.halfFlags[0] = (CPU_reg[rs(instr)].halfFlags[0] & CPU_reg[rt(instr)].halfFlags[0]); + + double vs = f16Unsign(CPU_reg[rs(instr)].x) + f16Unsign(CPU_reg[rs(instr)].y) * (double)(1 << 16); + double vt = f16Unsign(CPU_reg[rt(instr)].x) + f16Unsign(CPU_reg[rt(instr)].y) * (double)(1 << 16); + + double lo = vs / vt; + CPU_Lo.y = (float)f16Sign(f16Overflow(lo)); + CPU_Lo.x = (float)f16Sign(lo); + + double hi = fmod(vs, vt); + CPU_Hi.y = (float)f16Sign(f16Overflow(hi)); + CPU_Hi.x = (float)f16Sign(hi); + + CPU_Lo.value = loVal; + CPU_Hi.value = hiVal; +} + +//////////////////////////////////// +// Shift operations (sa) +//////////////////////////////////// +void CPU_SLL(u32 instr, u32 rdVal, u32 rtVal) +{ + // Rd = Rt << Sa + PGXP_value ret; + u32 sh = sa(instr); + Validate(&CPU_reg[rt(instr)], rtVal); + + ret = CPU_reg[rt(instr)]; + + // TODO: Shift flags + double x = f16Unsign(CPU_reg[rt(instr)].x); + double y = f16Unsign(CPU_reg[rt(instr)].y); + if (sh >= 32) + { + x = 0.f; + y = 0.f; + } + else if (sh == 16) + { + y = f16Sign(x); + x = 0.f; + } + else if (sh >= 16) + { + y = x * (1 << (sh - 16)); + y = f16Sign(y); + x = 0.f; + } + else + { + x = x * (1 << sh); + y = y * (1 << sh); + y += f16Overflow(x); + x = f16Sign(x); + y = f16Sign(y); + } + + ret.x = (float)x; + ret.y = (float)y; + + ret.value = rdVal; + CPU_reg[rd(instr)] = ret; +} + +void CPU_SRL(u32 instr, u32 rdVal, u32 rtVal) +{ + // Rd = Rt >> Sa + PGXP_value ret; + u32 sh = sa(instr); + Validate(&CPU_reg[rt(instr)], rtVal); + + ret = CPU_reg[rt(instr)]; + + double x = CPU_reg[rt(instr)].x, y = f16Unsign(CPU_reg[rt(instr)].y); + + psx_value iX; + iX.d = rtVal; + psx_value iY; + iY.d = rtVal; + + iX.sd = (iX.sd << 16) >> 16; // remove Y + iY.sw.l = iX.sw.h; // overwrite x with sign(x) + + // Shift test values + psx_value dX; + dX.sd = iX.sd >> sh; + psx_value dY; + dY.d = iY.d >> sh; + + if (dX.sw.l != iX.sw.h) + x = x / (1 << sh); + else + x = dX.sw.l; // only sign bits left + + if (dY.sw.l != iX.sw.h) + { + if (sh == 16) + { + x = y; + } + else if (sh < 16) + { + x += y * (1 << (16 - sh)); + if (CPU_reg[rt(instr)].x < 0) + x += 1 << (16 - sh); + } + else + { + x += y / (1 << (sh - 16)); + } + } + + if ((dY.sw.h == 0) || (dY.sw.h == -1)) + y = dY.sw.h; + else + y = y / (1 << sh); + + x = f16Sign(x); + y = f16Sign(y); + + ret.x = (float)x; + ret.y = (float)y; + + ret.value = rdVal; + CPU_reg[rd(instr)] = ret; +} + +void CPU_SRA(u32 instr, u32 rdVal, u32 rtVal) +{ + // Rd = Rt >> Sa + PGXP_value ret; + u32 sh = sa(instr); + Validate(&CPU_reg[rt(instr)], rtVal); + ret = CPU_reg[rt(instr)]; + + double x = CPU_reg[rt(instr)].x, y = CPU_reg[rt(instr)].y; + + psx_value iX; + iX.d = rtVal; + psx_value iY; + iY.d = rtVal; + + iX.sd = (iX.sd << 16) >> 16; // remove Y + iY.sw.l = iX.sw.h; // overwrite x with sign(x) + + // Shift test values + psx_value dX; + dX.sd = iX.sd >> sh; + psx_value dY; + dY.sd = iY.sd >> sh; + + if (dX.sw.l != iX.sw.h) + x = x / (1 << sh); + else + x = dX.sw.l; // only sign bits left + + if (dY.sw.l != iX.sw.h) + { + if (sh == 16) + { + x = y; + } + else if (sh < 16) + { + x += y * (1 << (16 - sh)); + if (CPU_reg[rt(instr)].x < 0) + x += 1 << (16 - sh); + } + else + { + x += y / (1 << (sh - 16)); + } + } + + if ((dY.sw.h == 0) || (dY.sw.h == -1)) + y = dY.sw.h; + else + y = y / (1 << sh); + + x = f16Sign(x); + y = f16Sign(y); + + ret.x = (float)x; + ret.y = (float)y; + + ret.value = rdVal; + CPU_reg[rd(instr)] = ret; +} + +//////////////////////////////////// +// Shift operations variable +//////////////////////////////////// +void CPU_SLLV(u32 instr, u32 rdVal, u32 rtVal, u32 rsVal) +{ + // Rd = Rt << Rs + PGXP_value ret; + u32 sh = rsVal & 0x1F; + Validate(&CPU_reg[rt(instr)], rtVal); + Validate(&CPU_reg[rs(instr)], rsVal); + + ret = CPU_reg[rt(instr)]; + + double x = f16Unsign(CPU_reg[rt(instr)].x); + double y = f16Unsign(CPU_reg[rt(instr)].y); + if (sh >= 32) + { + x = 0.f; + y = 0.f; + } + else if (sh == 16) + { + y = f16Sign(x); + x = 0.f; + } + else if (sh >= 16) + { + y = x * (1 << (sh - 16)); + y = f16Sign(y); + x = 0.f; + } + else + { + x = x * (1 << sh); + y = y * (1 << sh); + y += f16Overflow(x); + x = f16Sign(x); + y = f16Sign(y); + } + + ret.x = (float)x; + ret.y = (float)y; + + ret.value = rdVal; + CPU_reg[rd(instr)] = ret; +} + +void CPU_SRLV(u32 instr, u32 rdVal, u32 rtVal, u32 rsVal) +{ + // Rd = Rt >> Sa + PGXP_value ret; + u32 sh = rsVal & 0x1F; + Validate(&CPU_reg[rt(instr)], rtVal); + Validate(&CPU_reg[rs(instr)], rsVal); + + ret = CPU_reg[rt(instr)]; + + double x = CPU_reg[rt(instr)].x, y = f16Unsign(CPU_reg[rt(instr)].y); + + psx_value iX; + iX.d = rtVal; + psx_value iY; + iY.d = rtVal; + + iX.sd = (iX.sd << 16) >> 16; // remove Y + iY.sw.l = iX.sw.h; // overwrite x with sign(x) + + // Shift test values + psx_value dX; + dX.sd = iX.sd >> sh; + psx_value dY; + dY.d = iY.d >> sh; + + if (dX.sw.l != iX.sw.h) + x = x / (1 << sh); + else + x = dX.sw.l; // only sign bits left + + if (dY.sw.l != iX.sw.h) + { + if (sh == 16) + { + x = y; + } + else if (sh < 16) + { + x += y * (1 << (16 - sh)); + if (CPU_reg[rt(instr)].x < 0) + x += 1 << (16 - sh); + } + else + { + x += y / (1 << (sh - 16)); + } + } + + if ((dY.sw.h == 0) || (dY.sw.h == -1)) + y = dY.sw.h; + else + y = y / (1 << sh); + + x = f16Sign(x); + y = f16Sign(y); + + ret.x = (float)x; + ret.y = (float)y; + + ret.value = rdVal; + CPU_reg[rd(instr)] = ret; +} + +void CPU_SRAV(u32 instr, u32 rdVal, u32 rtVal, u32 rsVal) +{ + // Rd = Rt >> Sa + PGXP_value ret; + u32 sh = rsVal & 0x1F; + Validate(&CPU_reg[rt(instr)], rtVal); + Validate(&CPU_reg[rs(instr)], rsVal); + + ret = CPU_reg[rt(instr)]; + + double x = CPU_reg[rt(instr)].x, y = CPU_reg[rt(instr)].y; + + psx_value iX; + iX.d = rtVal; + psx_value iY; + iY.d = rtVal; + + iX.sd = (iX.sd << 16) >> 16; // remove Y + iY.sw.l = iX.sw.h; // overwrite x with sign(x) + + // Shift test values + psx_value dX; + dX.sd = iX.sd >> sh; + psx_value dY; + dY.sd = iY.sd >> sh; + + if (dX.sw.l != iX.sw.h) + x = x / (1 << sh); + else + x = dX.sw.l; // only sign bits left + + if (dY.sw.l != iX.sw.h) + { + if (sh == 16) + { + x = y; + } + else if (sh < 16) + { + x += y * (1 << (16 - sh)); + if (CPU_reg[rt(instr)].x < 0) + x += 1 << (16 - sh); + } + else + { + x += y / (1 << (sh - 16)); + } + } + + if ((dY.sw.h == 0) || (dY.sw.h == -1)) + y = dY.sw.h; + else + y = y / (1 << sh); + + x = f16Sign(x); + y = f16Sign(y); + + ret.x = (float)x; + ret.y = (float)y; + + ret.value = rdVal; + CPU_reg[rd(instr)] = ret; +} + +void CPU_MFHI(u32 instr, u32 rdVal, u32 hiVal) +{ + // Rd = Hi + Validate(&CPU_Hi, hiVal); + + CPU_reg[rd(instr)] = CPU_Hi; +} + +void CPU_MTHI(u32 instr, u32 hiVal, u32 rdVal) +{ + // Hi = Rd + Validate(&CPU_reg[rd(instr)], rdVal); + + CPU_Hi = CPU_reg[rd(instr)]; +} + +void CPU_MFLO(u32 instr, u32 rdVal, u32 loVal) +{ + // Rd = Lo + Validate(&CPU_Lo, loVal); + + CPU_reg[rd(instr)] = CPU_Lo; +} + +void CPU_MTLO(u32 instr, u32 loVal, u32 rdVal) +{ + // Lo = Rd + Validate(&CPU_reg[rd(instr)], rdVal); + + CPU_Lo = CPU_reg[rd(instr)]; +} + +void CPU_MFC0(u32 instr, u32 rtVal, u32 rdVal) +{ + // CPU[Rt] = CP0[Rd] + Validate(&CP0_reg[rd(instr)], rdVal); + CPU_reg[rt(instr)] = CP0_reg[rd(instr)]; + CPU_reg[rt(instr)].value = rtVal; +} + +void CPU_MTC0(u32 instr, u32 rdVal, u32 rtVal) +{ + // CP0[Rd] = CPU[Rt] + Validate(&CPU_reg[rt(instr)], rtVal); + CP0_reg[rd(instr)] = CPU_reg[rt(instr)]; + CP0_reg[rd(instr)].value = rdVal; +} + +void CPU_CFC0(u32 instr, u32 rtVal, u32 rdVal) +{ + // CPU[Rt] = CP0[Rd] + Validate(&CP0_reg[rd(instr)], rdVal); + CPU_reg[rt(instr)] = CP0_reg[rd(instr)]; + CPU_reg[rt(instr)].value = rtVal; +} + +void CPU_CTC0(u32 instr, u32 rdVal, u32 rtVal) +{ + // CP0[Rd] = CPU[Rt] + Validate(&CPU_reg[rt(instr)], rtVal); + CP0_reg[rd(instr)] = CPU_reg[rt(instr)]; + CP0_reg[rd(instr)].value = rdVal; +} + +} // namespace PGXP diff --git a/src/core/pgxp.h b/src/core/pgxp.h index 02b996615..db3192cd4 100644 --- a/src/core/pgxp.h +++ b/src/core/pgxp.h @@ -41,7 +41,8 @@ void CPU_CTC2(u32 instr, u32 rdVal, u32 rtVal); // copy GPR reg to GTE ctrl reg void CPU_LWC2(u32 instr, u32 rtVal, u32 addr); // copy memory to GTE reg void CPU_SWC2(u32 instr, u32 rtVal, u32 addr); // copy GTE reg to memory -bool GetPreciseVertex(u32 addr, u32 value, int x, int y, int xOffs, int yOffs, float* out_x, float* out_y, float* out_w); +bool GetPreciseVertex(u32 addr, u32 value, int x, int y, int xOffs, int yOffs, float* out_x, float* out_y, + float* out_w); // -- CPU functions void CPU_LW(u32 instr, u32 rtVal, u32 addr); @@ -51,4 +52,56 @@ void CPU_SB(u32 instr, u8 rtVal, u32 addr); void CPU_SH(u32 instr, u16 rtVal, u32 addr); void CPU_SW(u32 instr, u32 rtVal, u32 addr); +// Arithmetic with immediate value +void CPU_ADDI(u32 instr, u32 rtVal, u32 rsVal); +void CPU_ADDIU(u32 instr, u32 rtVal, u32 rsVal); +void CPU_ANDI(u32 instr, u32 rtVal, u32 rsVal); +void CPU_ORI(u32 instr, u32 rtVal, u32 rsVal); +void CPU_XORI(u32 instr, u32 rtVal, u32 rsVal); +void CPU_SLTI(u32 instr, u32 rtVal, u32 rsVal); +void CPU_SLTIU(u32 instr, u32 rtVal, u32 rsVal); + +// Load Upper +void CPU_LUI(u32 instr, u32 rtVal); + +// Register Arithmetic +void CPU_ADD(u32 instr, u32 rdVal, u32 rsVal, u32 rtVal); +void CPU_ADDU(u32 instr, u32 rdVal, u32 rsVal, u32 rtVal); +void CPU_SUB(u32 instr, u32 rdVal, u32 rsVal, u32 rtVal); +void CPU_SUBU(u32 instr, u32 rdVal, u32 rsVal, u32 rtVal); +void CPU_AND_(u32 instr, u32 rdVal, u32 rsVal, u32 rtVal); +void CPU_OR_(u32 instr, u32 rdVal, u32 rsVal, u32 rtVal); +void CPU_XOR_(u32 instr, u32 rdVal, u32 rsVal, u32 rtVal); +void CPU_NOR(u32 instr, u32 rdVal, u32 rsVal, u32 rtVal); +void CPU_SLT(u32 instr, u32 rdVal, u32 rsVal, u32 rtVal); +void CPU_SLTU(u32 instr, u32 rdVal, u32 rsVal, u32 rtVal); + +// Register mult/div +void CPU_MULT(u32 instr, u32 hiVal, u32 loVal, u32 rsVal, u32 rtVal); +void CPU_MULTU(u32 instr, u32 hiVal, u32 loVal, u32 rsVal, u32 rtVal); +void CPU_DIV(u32 instr, u32 hiVal, u32 loVal, u32 rsVal, u32 rtVal); +void CPU_DIVU(u32 instr, u32 hiVal, u32 loVal, u32 rsVal, u32 rtVal); + +// Shift operations (sa) +void CPU_SLL(u32 instr, u32 rdVal, u32 rtVal); +void CPU_SRL(u32 instr, u32 rdVal, u32 rtVal); +void CPU_SRA(u32 instr, u32 rdVal, u32 rtVal); + +// Shift operations variable +void CPU_SLLV(u32 instr, u32 rdVal, u32 rtVal, u32 rsVal); +void CPU_SRLV(u32 instr, u32 rdVal, u32 rtVal, u32 rsVal); +void CPU_SRAV(u32 instr, u32 rdVal, u32 rtVal, u32 rsVal); + +// Move registers +void CPU_MFHI(u32 instr, u32 rdVal, u32 hiVal); +void CPU_MTHI(u32 instr, u32 hiVal, u32 rdVal); +void CPU_MFLO(u32 instr, u32 rdVal, u32 loVal); +void CPU_MTLO(u32 instr, u32 loVal, u32 rdVal); + +// CP0 Data transfer tracking +void CPU_MFC0(u32 instr, u32 rtVal, u32 rdVal); +void CPU_MTC0(u32 instr, u32 rdVal, u32 rtVal); +void CPU_CFC0(u32 instr, u32 rtVal, u32 rdVal); +void CPU_CTC0(u32 instr, u32 rdVal, u32 rtVal); + } // namespace PGXP \ No newline at end of file diff --git a/src/core/settings.cpp b/src/core/settings.cpp index 9458112fb..d63d0fbf2 100644 --- a/src/core/settings.cpp +++ b/src/core/settings.cpp @@ -84,6 +84,7 @@ void Settings::Load(SettingsInterface& si) save_state_on_exit = si.GetBoolValue("Main", "SaveStateOnExit", true); confim_power_off = si.GetBoolValue("Main", "ConfirmPowerOff", true); load_devices_from_save_states = si.GetBoolValue("Main", "LoadDevicesFromSaveStates", false); + apply_game_settings = si.GetBoolValue("Main", "ApplyGameSettings", true); cpu_execution_mode = ParseCPUExecutionMode( @@ -106,6 +107,7 @@ void Settings::Load(SettingsInterface& si) gpu_pgxp_culling = si.GetBoolValue("GPU", "PGXPCulling", true); gpu_pgxp_texture_correction = si.GetBoolValue("GPU", "PGXPTextureCorrection", true); gpu_pgxp_vertex_cache = si.GetBoolValue("GPU", "PGXPVertexCache", false); + gpu_pgxp_cpu = si.GetBoolValue("GPU", "PGXPCPU", false); display_crop_mode = ParseDisplayCropMode( @@ -115,6 +117,8 @@ void Settings::Load(SettingsInterface& si) ParseDisplayAspectRatio( si.GetStringValue("Display", "AspectRatio", GetDisplayAspectRatioName(DEFAULT_DISPLAY_ASPECT_RATIO)).c_str()) .value_or(DEFAULT_DISPLAY_ASPECT_RATIO); + display_active_start_offset = static_cast(si.GetIntValue("Display", "ActiveStartOffset", 0)); + display_active_end_offset = static_cast(si.GetIntValue("Display", "ActiveEndOffset", 0)); display_linear_filtering = si.GetBoolValue("Display", "LinearFiltering", true); display_integer_scaling = si.GetBoolValue("Display", "IntegerScaling", false); display_show_osd_messages = si.GetBoolValue("Display", "ShowOSDMessages", true); @@ -197,6 +201,7 @@ void Settings::Save(SettingsInterface& si) const si.SetBoolValue("Main", "SaveStateOnExit", save_state_on_exit); si.SetBoolValue("Main", "ConfirmPowerOff", confim_power_off); si.SetBoolValue("Main", "LoadDevicesFromSaveStates", load_devices_from_save_states); + si.SetBoolValue("Main", "ApplyGameSettings", apply_game_settings); si.SetStringValue("CPU", "ExecutionMode", GetCPUExecutionModeName(cpu_execution_mode)); si.SetBoolValue("CPU", "RecompilerMemoryExceptions", cpu_recompiler_memory_exceptions); @@ -215,8 +220,11 @@ void Settings::Save(SettingsInterface& si) const si.SetBoolValue("GPU", "PGXPCulling", gpu_pgxp_culling); si.SetBoolValue("GPU", "PGXPTextureCorrection", gpu_pgxp_texture_correction); si.SetBoolValue("GPU", "PGXPVertexCache", gpu_pgxp_vertex_cache); + si.SetBoolValue("GPU", "PGXPCPU", gpu_pgxp_cpu); si.SetStringValue("Display", "CropMode", GetDisplayCropModeName(display_crop_mode)); + si.SetIntValue("Display", "ActiveStartOffset", display_active_start_offset); + si.SetIntValue("Display", "ActiveEndOffset", display_active_end_offset); si.SetStringValue("Display", "AspectRatio", GetDisplayAspectRatioName(display_aspect_ratio)); si.SetBoolValue("Display", "LinearFiltering", display_linear_filtering); si.SetBoolValue("Display", "IntegerScaling", display_integer_scaling); @@ -283,7 +291,10 @@ void Settings::Save(SettingsInterface& si) const static std::array s_log_level_names = { {"None", "Error", "Warning", "Perf", "Success", "Info", "Dev", "Profile", "Debug", "Trace"}}; static std::array s_log_level_display_names = { - {"None", "Error", "Warning", "Performance", "Success", "Information", "Developer", "Profile", "Debug", "Trace"}}; + {TRANSLATABLE("LogLevel", "None"), TRANSLATABLE("LogLevel", "Error"), TRANSLATABLE("LogLevel", "Warning"), + TRANSLATABLE("LogLevel", "Performance"), TRANSLATABLE("LogLevel", "Success"), + TRANSLATABLE("LogLevel", "Information"), TRANSLATABLE("LogLevel", "Developer"), TRANSLATABLE("LogLevel", "Profile"), + TRANSLATABLE("LogLevel", "Debug"), TRANSLATABLE("LogLevel", "Trace")}}; std::optional Settings::ParseLogLevelName(const char* str) { @@ -311,7 +322,8 @@ const char* Settings::GetLogLevelDisplayName(LOGLEVEL level) static std::array s_console_region_names = {{"Auto", "NTSC-J", "NTSC-U", "PAL"}}; static std::array s_console_region_display_names = { - {"Auto-Detect", "NTSC-J (Japan)", "NTSC-U (US)", "PAL (Europe, Australia)"}}; + {TRANSLATABLE("ConsoleRegion", "Auto-Detect"), TRANSLATABLE("ConsoleRegion", "NTSC-J (Japan)"), + TRANSLATABLE("ConsoleRegion", "NTSC-U (US)"), TRANSLATABLE("ConsoleRegion", "PAL (Europe, Australia)")}}; std::optional Settings::ParseConsoleRegionName(const char* str) { @@ -339,7 +351,8 @@ const char* Settings::GetConsoleRegionDisplayName(ConsoleRegion region) static std::array s_disc_region_names = {{"NTSC-J", "NTSC-U", "PAL", "Other"}}; static std::array s_disc_region_display_names = { - {"NTSC-J (Japan)", "NTSC-U (US)", "PAL (Europe, Australia)", "Other"}}; + {TRANSLATABLE("DiscRegion", "NTSC-J (Japan)"), TRANSLATABLE("DiscRegion", "NTSC-U (US)"), + TRANSLATABLE("DiscRegion", "PAL (Europe, Australia)"), TRANSLATABLE("DiscRegion", "Other")}}; std::optional Settings::ParseDiscRegionName(const char* str) { @@ -367,7 +380,8 @@ const char* Settings::GetDiscRegionDisplayName(DiscRegion region) static std::array s_cpu_execution_mode_names = {{"Interpreter", "CachedInterpreter", "Recompiler"}}; static std::array s_cpu_execution_mode_display_names = { - {"Intepreter (Slowest)", "Cached Interpreter (Faster)", "Recompiler (Fastest)"}}; + {TRANSLATABLE("CPUExecutionMode", "Intepreter (Slowest)"), TRANSLATABLE("CPUExecutionMode", "Cached Interpreter (Faster)"), + TRANSLATABLE("CPUExecutionMode", "Recompiler (Fastest)")}}; std::optional Settings::ParseCPUExecutionMode(const char* str) { @@ -400,9 +414,10 @@ static std::array s_gpu_renderer_names = {{ "Vulkan", "OpenGL", "Software"}}; static std::array s_gpu_renderer_display_names = {{ #ifdef WIN32 - "Hardware (D3D11)", + TRANSLATABLE("GPURenderer", "Hardware (D3D11)"), #endif - "Hardware (Vulkan)", "Hardware (OpenGL)", "Software"}}; + TRANSLATABLE("GPURenderer", "Hardware (Vulkan)"), TRANSLATABLE("GPURenderer", "Hardware (OpenGL)"), + TRANSLATABLE("GPURenderer", "Software")}}; std::optional Settings::ParseRendererName(const char* str) { @@ -429,7 +444,9 @@ const char* Settings::GetRendererDisplayName(GPURenderer renderer) } static std::array s_display_crop_mode_names = {{"None", "Overscan", "Borders"}}; -static std::array s_display_crop_mode_display_names = {{"None", "Only Overscan Area", "All Borders"}}; +static std::array s_display_crop_mode_display_names = {{TRANSLATABLE("DisplayCropMode", "None"), + TRANSLATABLE("DisplayCropMode", "Only Overscan Area"), + TRANSLATABLE("DisplayCropMode", "All Borders")}}; std::optional Settings::ParseDisplayCropMode(const char* str) { @@ -485,7 +502,8 @@ float Settings::GetDisplayAspectRatioValue(DisplayAspectRatio ar) } static std::array s_audio_backend_names = {{"Null", "Cubeb", "SDL"}}; -static std::array s_audio_backend_display_names = {{"Null (No Output)", "Cubeb", "SDL"}}; +static std::array s_audio_backend_display_names = { + {TRANSLATABLE("AudioBackend", "Null (No Output)"), TRANSLATABLE("AudioBackend", "Cubeb"), TRANSLATABLE("AudioBackend", "SDL")}}; std::optional Settings::ParseAudioBackend(const char* str) { @@ -514,7 +532,9 @@ const char* Settings::GetAudioBackendDisplayName(AudioBackend backend) static std::array s_controller_type_names = { {"None", "DigitalController", "AnalogController", "NamcoGunCon", "PlayStationMouse", "NeGcon"}}; static std::array s_controller_display_names = { - {"None", "Digital Controller", "Analog Controller (DualShock)", "Namco GunCon", "PlayStation Mouse", "NeGcon"}}; + {TRANSLATABLE("ControllerType", "None"), TRANSLATABLE("ControllerType", "Digital Controller"), + TRANSLATABLE("ControllerType", "Analog Controller (DualShock)"), TRANSLATABLE("ControllerType", "Namco GunCon"), + TRANSLATABLE("ControllerType", "PlayStation Mouse"), TRANSLATABLE("ControllerType", "NeGcon")}}; std::optional Settings::ParseControllerTypeName(const char* str) { @@ -541,9 +561,10 @@ const char* Settings::GetControllerTypeDisplayName(ControllerType type) } static std::array s_memory_card_type_names = {{"None", "Shared", "PerGame", "PerGameTitle"}}; -static std::array s_memory_card_type_display_names = {{"No Memory Card", "Shared Between All Games", - "Separate Card Per Game (Game Code)", - "Separate Card Per Game (Game Title)"}}; +static std::array s_memory_card_type_display_names = { + {TRANSLATABLE("MemoryCardType", "No Memory Card"), TRANSLATABLE("MemoryCardType", "Shared Between All Games"), + TRANSLATABLE("MemoryCardType", "Separate Card Per Game (Game Code)"), + TRANSLATABLE("MemoryCardType", "Separate Card Per Game (Game Title)")}}; std::optional Settings::ParseMemoryCardTypeName(const char* str) { diff --git a/src/core/settings.h b/src/core/settings.h index ba8ead79a..91e26d5b4 100644 --- a/src/core/settings.h +++ b/src/core/settings.h @@ -78,6 +78,7 @@ struct Settings bool save_state_on_exit = true; bool confim_power_off = true; bool load_devices_from_save_states = false; + bool apply_game_settings = true; GPURenderer gpu_renderer = GPURenderer::Software; std::string gpu_adapter; @@ -93,7 +94,10 @@ struct Settings bool gpu_pgxp_culling = true; bool gpu_pgxp_texture_correction = true; bool gpu_pgxp_vertex_cache = false; + bool gpu_pgxp_cpu = false; DisplayCropMode display_crop_mode = DisplayCropMode::None; + s16 display_active_start_offset = 0; + s16 display_active_end_offset = 0; DisplayAspectRatio display_aspect_ratio = DisplayAspectRatio::R4_3; bool display_linear_filtering = true; bool display_integer_scaling = false; @@ -157,6 +161,11 @@ struct Settings ALWAYS_INLINE bool IsUsingRecompiler() const { return (cpu_execution_mode == CPUExecutionMode::Recompiler); } ALWAYS_INLINE bool IsUsingSoftwareRenderer() const { return (gpu_renderer == GPURenderer::Software); } + ALWAYS_INLINE PGXPMode GetPGXPMode() + { + return gpu_pgxp_enable ? (gpu_pgxp_cpu ? PGXPMode::CPU : PGXPMode::Memory) : PGXPMode::Disabled; + } + bool HasAnyPerGameMemoryCards() const; enum : u32 diff --git a/src/core/system.cpp b/src/core/system.cpp index 0420a482a..67a316f64 100644 --- a/src/core/system.cpp +++ b/src/core/system.cpp @@ -131,7 +131,7 @@ bool IsShutdown() bool IsValid() { - return s_state != State::Shutdown; + return s_state != State::Shutdown && s_state != State::Starting; } ConsoleRegion GetRegion() @@ -384,6 +384,9 @@ bool Boot(const SystemBootParameters& params) return false; } + // Notify change of disc. + UpdateRunningGame(media ? media->GetFileName().c_str() : params.filename.c_str(), media.get()); + // Component setup. if (!Initialize(params.force_software_renderer)) { @@ -391,8 +394,6 @@ bool Boot(const SystemBootParameters& params) return false; } - // Notify change of disc. - UpdateRunningGame(params.filename.c_str(), media.get()); UpdateControllers(); UpdateMemoryCards(); Reset(); @@ -649,8 +650,10 @@ bool DoLoadState(ByteStream* state, bool force_software_renderer) if (header.version != SAVE_STATE_VERSION) { - g_host_interface->ReportFormattedError("Save state is incompatible: expecting version %u but state is version %u.", - SAVE_STATE_VERSION, header.version); + g_host_interface->ReportFormattedError( + g_host_interface->TranslateString("System", + "Save state is incompatible: expecting version %u but state is version %u."), + SAVE_STATE_VERSION, header.version); return false; } @@ -671,8 +674,9 @@ bool DoLoadState(ByteStream* state, bool force_software_renderer) media = OpenCDImage(media_filename.c_str(), false); if (!media) { - g_host_interface->ReportFormattedError("Failed to open CD image from save state: '%s'.", - media_filename.c_str()); + g_host_interface->ReportFormattedError( + g_host_interface->TranslateString("System", "Failed to open CD image from save state: '%s'."), + media_filename.c_str()); return false; } } @@ -1172,10 +1176,12 @@ void UpdateMemoryCards() { if (s_running_game_code.empty()) { - g_host_interface->AddFormattedOSDMessage(5.0f, - "Per-game memory card cannot be used for slot %u as the running " - "game has no code. Using shared card instead.", - i + 1u); + g_host_interface->AddFormattedOSDMessage( + 5.0f, + g_host_interface->TranslateString("System", + "Per-game memory card cannot be used for slot %u as the running " + "game has no code. Using shared card instead."), + i + 1u); card = MemoryCard::Open(g_host_interface->GetSharedMemoryCardPath(i)); } else @@ -1194,10 +1200,12 @@ void UpdateMemoryCards() } else if (s_running_game_title.empty()) { - g_host_interface->AddFormattedOSDMessage(5.0f, - "Per-game memory card cannot be used for slot %u as the running " - "game has no title. Using shared card instead.", - i + 1u); + g_host_interface->AddFormattedOSDMessage( + 5.0f, + g_host_interface->TranslateString("System", + "Per-game memory card cannot be used for slot %u as the running " + "game has no title. Using shared card instead."), + i + 1u); card = MemoryCard::Open(g_host_interface->GetSharedMemoryCardPath(i)); } else @@ -1211,8 +1219,10 @@ void UpdateMemoryCards() { if (g_settings.memory_card_paths[i].empty()) { - g_host_interface->AddFormattedOSDMessage(10.0f, "Memory card path for slot %u is missing, using default.", - i + 1u); + g_host_interface->AddFormattedOSDMessage( + 10.0f, + g_host_interface->TranslateString("System", "Memory card path for slot %u is missing, using default."), + i + 1u); card = MemoryCard::Open(g_host_interface->GetSharedMemoryCardPath(i)); } else @@ -1255,7 +1265,8 @@ bool InsertMedia(const char* path) if (g_settings.HasAnyPerGameMemoryCards()) { - g_host_interface->AddOSDMessage("Game changed, reloading memory cards.", 2.0f); + g_host_interface->AddOSDMessage( + g_host_interface->TranslateStdString("System", "Game changed, reloading memory cards."), 10.0f); UpdateMemoryCards(); } @@ -1269,6 +1280,9 @@ void RemoveMedia() void UpdateRunningGame(const char* path, CDImage* image) { + if (s_running_game_path == path) + return; + s_running_game_path.clear(); s_running_game_code.clear(); s_running_game_title.clear(); diff --git a/src/core/types.h b/src/core/types.h index 3c234cd90..46482d951 100644 --- a/src/core/types.h +++ b/src/core/types.h @@ -48,6 +48,13 @@ enum class CPUExecutionMode : u8 Count }; +enum class PGXPMode : u8 +{ + Disabled, + Memory, + CPU +}; + enum class GPURenderer : u8 { #ifdef WIN32 diff --git a/src/duckstation-libretro/libretro_host_interface.cpp b/src/duckstation-libretro/libretro_host_interface.cpp index a15ae4655..52ec60a3d 100644 --- a/src/duckstation-libretro/libretro_host_interface.cpp +++ b/src/duckstation-libretro/libretro_host_interface.cpp @@ -63,6 +63,23 @@ LibretroHostInterface::~LibretroHostInterface() } } +void LibretroHostInterface::InitInterfaces() +{ + SetCoreOptions(); + InitDiskControlInterface(); + + if (!m_interfaces_initialized) + { + InitLogging(); + InitRumbleInterface(); + + unsigned dummy = 0; + m_supports_input_bitmasks = g_retro_environment_callback(RETRO_ENVIRONMENT_GET_INPUT_BITMASKS, &dummy); + + m_interfaces_initialized = true; + } +} + void LibretroHostInterface::InitLogging() { if (s_libretro_log_callback_registered) @@ -84,6 +101,7 @@ bool LibretroHostInterface::Initialize() return false; LoadSettings(); + FixIncompatibleSettings(true); UpdateLogging(); return true; } @@ -352,7 +370,7 @@ void LibretroHostInterface::OnSystemDestroyed() m_using_hardware_renderer = false; } -static std::array s_option_definitions = {{ +static std::array s_option_definitions = {{ {"duckstation_Console.Region", "Console Region", "Determines which region/hardware to emulate. Auto-Detect will use the region of the disc inserted.", @@ -483,6 +501,12 @@ static std::array s_option_definitions = {{ "Uses screen coordinates as a fallback when tracking vertices through memory fails. May improve PGXP compatibility.", {{"true", "Enabled"}, {"false", "Disabled"}}, "false"}, + {"duckstation_GPU.PGXPCPU", + "PGXP CPU Mode", + "Tries to track vertex manipulation through the CPU. Some games require this option for PGXP to be effective. " + "Very slow, and incompatible with the recompiler.", + {{"true", "Enabled"}, {"false", "Disabled"}}, + "false"}, {"duckstation_Display.CropMode", "Crop Mode", "Changes how much of the image is cropped. Some games display garbage in the overscan area which is typically " @@ -494,9 +518,9 @@ static std::array s_option_definitions = {{ "Sets the core-provided aspect ratio.", {{"4:3", "4:3"}, {"16:9", "16:9"}, {"2:1", "2:1 (VRAM 1:1)"}, {"1:1", "1:1"}}, "4:3"}, - {"duckstation_MemoryCards.LoadFromSaveStates", - "Load Memory Cards From Save States", - "Sets whether the contents of memory cards will be loaded when a save state is loaded.", + {"duckstation_Main.LoadDevicesFromSaveStates", + "Load Devices From Save States", + "Sets whether the contents of devices and memory cards will be loaded when a save state is loaded.", {{"true", "Enabled"}, {"false", "Disabled"}}, "false"}, {"duckstation_MemoryCards.Card1Type", @@ -520,7 +544,7 @@ static std::array s_option_definitions = {{ "When using a playlist (m3u) and per-game (title) memory cards, a single memory card " "will be used for all discs. If unchecked, a separate card will be used for each disc.", {{"true", "Enabled"}, {"false", "Disabled"}}, - "false"}, + "true"}, {"duckstation_Controller1.Type", "Controller 1 Type", "Sets the type of controller for Slot 1.", @@ -590,7 +614,7 @@ bool LibretroHostInterface::HasCoreVariablesChanged() void LibretroHostInterface::LoadSettings() { LibretroSettingsInterface si; - g_settings.Load(si); + HostInterface::LoadSettings(si); // Assume BIOS files are located in system directory. const char* system_directory = nullptr; @@ -608,6 +632,7 @@ void LibretroHostInterface::UpdateSettings() { Settings old_settings(std::move(g_settings)); LoadSettings(); + FixIncompatibleSettings(false); if (g_settings.gpu_resolution_scale != old_settings.gpu_resolution_scale && g_settings.gpu_renderer != GPURenderer::Software) @@ -650,6 +675,11 @@ void LibretroHostInterface::CheckForSettingsChanges(const Settings& old_settings UpdateLogging(); } +void LibretroHostInterface::InitRumbleInterface() +{ + m_rumble_interface_valid = g_retro_environment_callback(RETRO_ENVIRONMENT_GET_RUMBLE_INTERFACE, &m_rumble_interface); +} + void LibretroHostInterface::UpdateControllers() { g_retro_input_poll_callback(); @@ -698,10 +728,19 @@ void LibretroHostInterface::UpdateControllersDigitalController(u32 index) {DigitalController::Button::R1, RETRO_DEVICE_ID_JOYPAD_R}, {DigitalController::Button::R2, RETRO_DEVICE_ID_JOYPAD_R2}}}; - for (const auto& it : mapping) + if (m_supports_input_bitmasks) { - const int16_t state = g_retro_input_state_callback(index, RETRO_DEVICE_JOYPAD, 0, it.second); - controller->SetButtonState(it.first, state != 0); + const u16 active = g_retro_input_state_callback(index, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_MASK); + for (const auto& it : mapping) + controller->SetButtonState(it.first, (active & (static_cast(1u) << it.second)) != 0u); + } + else + { + for (const auto& it : mapping) + { + const int16_t state = g_retro_input_state_callback(index, RETRO_DEVICE_JOYPAD, 0, it.second); + controller->SetButtonState(it.first, state != 0); + } } } @@ -734,10 +773,19 @@ void LibretroHostInterface::UpdateControllersAnalogController(u32 index) {AnalogController::Axis::RightX, {RETRO_DEVICE_INDEX_ANALOG_RIGHT, RETRO_DEVICE_ID_ANALOG_X}}, {AnalogController::Axis::RightY, {RETRO_DEVICE_INDEX_ANALOG_RIGHT, RETRO_DEVICE_ID_ANALOG_Y}}}}; - for (const auto& it : button_mapping) + if (m_supports_input_bitmasks) { - const int16_t state = g_retro_input_state_callback(index, RETRO_DEVICE_JOYPAD, 0, it.second); - controller->SetButtonState(it.first, state != 0); + const u16 active = g_retro_input_state_callback(index, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_MASK); + for (const auto& it : button_mapping) + controller->SetButtonState(it.first, (active & (static_cast(1u) << it.second)) != 0u); + } + else + { + for (const auto& it : button_mapping) + { + const int16_t state = g_retro_input_state_callback(index, RETRO_DEVICE_JOYPAD, 0, it.second); + controller->SetButtonState(it.first, state != 0); + } } for (const auto& it : axis_mapping) @@ -745,6 +793,14 @@ void LibretroHostInterface::UpdateControllersAnalogController(u32 index) const int16_t state = g_retro_input_state_callback(index, RETRO_DEVICE_ANALOG, it.second.first, it.second.second); controller->SetAxisState(static_cast(it.first), std::clamp(static_cast(state) / 32767.0f, -1.0f, 1.0f)); } + + if (m_rumble_interface_valid) + { + const u16 strong = static_cast(static_cast(controller->GetVibrationMotorStrength(0) * 65565.0f)); + const u16 weak = static_cast(static_cast(controller->GetVibrationMotorStrength(1) * 65565.0f)); + m_rumble_interface.set_rumble_state(index, RETRO_RUMBLE_STRONG, strong); + m_rumble_interface.set_rumble_state(index, RETRO_RUMBLE_WEAK, weak); + } } static std::optional RetroHwContextToRenderer(retro_hw_context_type type) diff --git a/src/duckstation-libretro/libretro_host_interface.h b/src/duckstation-libretro/libretro_host_interface.h index a1d97a519..07e0e23fb 100644 --- a/src/duckstation-libretro/libretro_host_interface.h +++ b/src/duckstation-libretro/libretro_host_interface.h @@ -11,10 +11,7 @@ public: LibretroHostInterface(); ~LibretroHostInterface() override; - static void InitLogging(); - static bool SetCoreOptions(); - static bool HasCoreVariablesChanged(); - static void InitDiskControlInterface(); + void InitInterfaces(); ALWAYS_INLINE u32 GetResolutionScale() const { return g_settings.gpu_resolution_scale; } @@ -51,6 +48,12 @@ protected: void CheckForSettingsChanges(const Settings& old_settings) override; private: + bool SetCoreOptions(); + bool HasCoreVariablesChanged(); + void InitLogging(); + void InitDiskControlInterface(); + void InitRumbleInterface(); + void LoadSettings(); void UpdateSettings(); void UpdateControllers(); @@ -86,6 +89,11 @@ private: bool m_hw_render_callback_valid = false; bool m_using_hardware_renderer = false; std::optional m_next_disc_index; + + retro_rumble_interface m_rumble_interface = {}; + bool m_rumble_interface_valid = false; + bool m_supports_input_bitmasks = false; + bool m_interfaces_initialized = false; }; extern LibretroHostInterface g_libretro_host_interface; diff --git a/src/duckstation-libretro/main.cpp b/src/duckstation-libretro/main.cpp index 773a54a60..848fd53b3 100644 --- a/src/duckstation-libretro/main.cpp +++ b/src/duckstation-libretro/main.cpp @@ -122,12 +122,7 @@ RETRO_API size_t retro_get_memory_size(unsigned id) RETRO_API void retro_set_environment(retro_environment_t f) { g_retro_environment_callback = f; - - if (!g_libretro_host_interface.SetCoreOptions()) - Log_WarningPrintf("Failed to set core options, settings will not be changeable."); - - g_libretro_host_interface.InitLogging(); - g_libretro_host_interface.InitDiskControlInterface(); + g_libretro_host_interface.InitInterfaces(); } RETRO_API void retro_set_video_refresh(retro_video_refresh_t f) diff --git a/src/duckstation-qt/CMakeLists.txt b/src/duckstation-qt/CMakeLists.txt index 25c4956ae..3efd34db4 100644 --- a/src/duckstation-qt/CMakeLists.txt +++ b/src/duckstation-qt/CMakeLists.txt @@ -68,6 +68,7 @@ set(TS_FILES translations/duckstation-qt_de.ts translations/duckstation-qt_es.ts translations/duckstation-qt_he.ts + translations/duckstation-qt_it.ts translations/duckstation-qt_pt-br.ts translations/duckstation-qt_pt-pt.ts translations/duckstation-qt_zh-cn.ts diff --git a/src/duckstation-qt/advancedsettingswidget.cpp b/src/duckstation-qt/advancedsettingswidget.cpp index 4862b2fe1..2b4d93a32 100644 --- a/src/duckstation-qt/advancedsettingswidget.cpp +++ b/src/duckstation-qt/advancedsettingswidget.cpp @@ -1,4 +1,5 @@ #include "advancedsettingswidget.h" +#include "mainwindow.h" #include "settingsdialog.h" #include "settingwidgetbinder.h" @@ -8,7 +9,7 @@ AdvancedSettingsWidget::AdvancedSettingsWidget(QtHostInterface* host_interface, m_ui.setupUi(this); for (u32 i = 0; i < static_cast(LOGLEVEL_COUNT); i++) - m_ui.logLevel->addItem(tr(Settings::GetLogLevelDisplayName(static_cast(i)))); + m_ui.logLevel->addItem(qApp->translate("LogLevel", Settings::GetLogLevelDisplayName(static_cast(i)))); SettingWidgetBinder::BindWidgetToEnumSetting(m_host_interface, m_ui.logLevel, "Logging", "LogLevel", &Settings::ParseLogLevelName, &Settings::GetLogLevelName, @@ -27,9 +28,12 @@ AdvancedSettingsWidget::AdvancedSettingsWidget(QtHostInterface* host_interface, SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.cpuRecompilerMemoryExceptions, "CPU", "RecompilerMemoryExceptions", false); + SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.showDebugMenu, "Main", "ShowDebugMenu"); SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.gpuUseDebugDevice, "GPU", "UseDebugDevice"); connect(m_ui.resetToDefaultButton, &QPushButton::clicked, this, &AdvancedSettingsWidget::onResetToDefaultClicked); + connect(m_ui.showDebugMenu, &QCheckBox::toggled, m_host_interface->getMainWindow(), + &MainWindow::updateDebugMenuVisibility, Qt::QueuedConnection); dialog->registerWidgetHelp(m_ui.gpuUseDebugDevice, tr("Use Debug Host GPU Device"), tr("Unchecked"), tr("Enables the usage of debug devices and shaders for rendering APIs which support them. " diff --git a/src/duckstation-qt/advancedsettingswidget.ui b/src/duckstation-qt/advancedsettingswidget.ui index 897aedfc2..d1ba75000 100644 --- a/src/duckstation-qt/advancedsettingswidget.ui +++ b/src/duckstation-qt/advancedsettingswidget.ui @@ -207,7 +207,14 @@ System Settings - + + + + Show Debug Menu + + + + Use Debug Host GPU Device diff --git a/src/duckstation-qt/audiosettingswidget.cpp b/src/duckstation-qt/audiosettingswidget.cpp index 583decfc5..194251e6d 100644 --- a/src/duckstation-qt/audiosettingswidget.cpp +++ b/src/duckstation-qt/audiosettingswidget.cpp @@ -11,7 +11,10 @@ AudioSettingsWidget::AudioSettingsWidget(QtHostInterface* host_interface, QWidge m_ui.setupUi(this); for (u32 i = 0; i < static_cast(AudioBackend::Count); i++) - m_ui.audioBackend->addItem(tr(Settings::GetAudioBackendDisplayName(static_cast(i)))); + { + m_ui.audioBackend->addItem( + qApp->translate("AudioBackend", Settings::GetAudioBackendDisplayName(static_cast(i)))); + } SettingWidgetBinder::BindWidgetToEnumSetting(m_host_interface, m_ui.audioBackend, "Audio", "Backend", &Settings::ParseAudioBackend, &Settings::GetAudioBackendName, diff --git a/src/duckstation-qt/autoupdaterdialog.cpp b/src/duckstation-qt/autoupdaterdialog.cpp index a91433e1f..7140b1e32 100644 --- a/src/duckstation-qt/autoupdaterdialog.cpp +++ b/src/duckstation-qt/autoupdaterdialog.cpp @@ -39,6 +39,7 @@ Log_SetChannel(AutoUpdaterDialog); static constexpr char LATEST_TAG_URL[] = "https://api.github.com/repos/stenzek/duckstation/tags"; static constexpr char LATEST_RELEASE_URL[] = "https://api.github.com/repos/stenzek/duckstation/releases/tags/" SCM_RELEASE_TAG; +static constexpr char CHANGES_URL[] = "https://api.github.com/repos/stenzek/duckstation/compare/%s..." SCM_RELEASE_TAG; static constexpr char UPDATE_ASSET_FILENAME[] = SCM_RELEASE_ASSET; #endif @@ -196,10 +197,11 @@ void AutoUpdaterDialog::getLatestReleaseComplete(QNetworkReply* reply) m_download_url = asset_obj["browser_download_url"].toString(); if (!m_download_url.isEmpty()) { - m_ui.currentVersion->setText(tr("Current Version: %1 (%2)").arg(g_scm_hash_str).arg(__TIMESTAMP__)); + m_ui.currentVersion->setText(tr("Current Version: %1 (%2)").arg(g_scm_hash_str).arg(g_scm_date_str)); m_ui.newVersion->setText( tr("New Version: %1 (%2)").arg(m_latest_sha).arg(doc_object["published_at"].toString())); - m_ui.updateNotes->setText(doc_object["body"].toString()); + m_ui.updateNotes->setText(tr("Loading...")); + queueGetChanges(); exec(); emit updateCheckCompleted(); return; @@ -223,6 +225,68 @@ void AutoUpdaterDialog::getLatestReleaseComplete(QNetworkReply* reply) #endif } +void AutoUpdaterDialog::queueGetChanges() +{ +#ifdef AUTO_UPDATER_SUPPORTED + connect(m_network_access_mgr, &QNetworkAccessManager::finished, this, &AutoUpdaterDialog::getChangesComplete); + + const std::string url_string(StringUtil::StdStringFromFormat(CHANGES_URL, g_scm_hash_str)); + QUrl url(QUrl::fromEncoded(QByteArray(url_string.c_str(), static_cast(url_string.size())))); + QNetworkRequest request(url); + request.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true); + m_network_access_mgr->get(request); +#endif +} + +void AutoUpdaterDialog::getChangesComplete(QNetworkReply* reply) +{ +#ifdef AUTO_UPDATER_SUPPORTED + m_network_access_mgr->disconnect(this); + reply->deleteLater(); + + if (reply->error() == QNetworkReply::NoError) + { + const QByteArray reply_json(reply->readAll()); + QJsonParseError parse_error; + QJsonDocument doc(QJsonDocument::fromJson(reply_json, &parse_error)); + if (doc.isObject()) + { + const QJsonObject doc_object(doc.object()); + + QString changes_html = QStringLiteral("
    "); + + const QJsonArray commits(doc_object["commits"].toArray()); + for (const QJsonValue& commit : commits) + { + const QJsonObject commit_obj(commit["commit"].toObject()); + + QString message = commit_obj["message"].toString(); + QString author = commit_obj["author"].toObject()["name"].toString(); + const int first_line_terminator = message.indexOf('\n'); + if (first_line_terminator >= 0) + message.remove(first_line_terminator, message.size() - first_line_terminator); + if (!message.isEmpty()) + changes_html += + QStringLiteral("
  • %1 (%2)
  • ").arg(message.toHtmlEscaped()).arg(author.toHtmlEscaped()); + } + + changes_html += "
"; + m_ui.updateNotes->setText(changes_html); + } + else + { + reportError("Change list JSON is not an object"); + } + } + else + { + reportError("Failed to download change list: %d", static_cast(reply->error())); + } +#endif + + m_ui.downloadAndInstall->setEnabled(true); +} + void AutoUpdaterDialog::downloadUpdateClicked() { QUrl url(m_download_url); diff --git a/src/duckstation-qt/autoupdaterdialog.h b/src/duckstation-qt/autoupdaterdialog.h index dcd3b4195..475dd9403 100644 --- a/src/duckstation-qt/autoupdaterdialog.h +++ b/src/duckstation-qt/autoupdaterdialog.h @@ -28,6 +28,9 @@ private Q_SLOTS: void getLatestTagComplete(QNetworkReply* reply); void getLatestReleaseComplete(QNetworkReply* reply); + void queueGetChanges(); + void getChangesComplete(QNetworkReply* reply); + void downloadUpdateClicked(); void skipThisUpdateClicked(); void remindMeLaterClicked(); diff --git a/src/duckstation-qt/autoupdaterdialog.ui b/src/duckstation-qt/autoupdaterdialog.ui index a5b456956..50dcd238c 100644 --- a/src/duckstation-qt/autoupdaterdialog.ui +++ b/src/duckstation-qt/autoupdaterdialog.ui @@ -82,6 +82,9 @@ Download and Install... + + false +
diff --git a/src/duckstation-qt/consolesettingswidget.cpp b/src/duckstation-qt/consolesettingswidget.cpp index 739cc3dbc..cab79ade6 100644 --- a/src/duckstation-qt/consolesettingswidget.cpp +++ b/src/duckstation-qt/consolesettingswidget.cpp @@ -11,10 +11,16 @@ ConsoleSettingsWidget::ConsoleSettingsWidget(QtHostInterface* host_interface, QW m_ui.setupUi(this); for (u32 i = 0; i < static_cast(ConsoleRegion::Count); i++) - m_ui.region->addItem(tr(Settings::GetConsoleRegionDisplayName(static_cast(i)))); + { + m_ui.region->addItem( + qApp->translate("ConsoleRegion", Settings::GetConsoleRegionDisplayName(static_cast(i)))); + } for (u32 i = 0; i < static_cast(CPUExecutionMode::Count); i++) - m_ui.cpuExecutionMode->addItem(tr(Settings::GetCPUExecutionModeDisplayName(static_cast(i)))); + { + m_ui.cpuExecutionMode->addItem( + qApp->translate("CPUExecutionMode", Settings::GetCPUExecutionModeDisplayName(static_cast(i)))); + } SettingWidgetBinder::BindWidgetToEnumSetting(m_host_interface, m_ui.region, "Console", "Region", &Settings::ParseConsoleRegionName, &Settings::GetConsoleRegionName, diff --git a/src/duckstation-qt/controllersettingswidget.cpp b/src/duckstation-qt/controllersettingswidget.cpp index 45653ed4f..c7c5d7914 100644 --- a/src/duckstation-qt/controllersettingswidget.cpp +++ b/src/duckstation-qt/controllersettingswidget.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -48,7 +49,7 @@ void ControllerSettingsWidget::onProfileLoaded() ControllerType ctype = Settings::ParseControllerTypeName( m_host_interface ->GetStringSettingValue(QStringLiteral("Controller%1").arg(i + 1).toStdString().c_str(), - QStringLiteral("Type").toStdString().c_str()) + QStringLiteral("Type").toStdString().c_str()) .c_str()) .value_or(ControllerType::None); @@ -86,7 +87,7 @@ void ControllerSettingsWidget::createPortSettingsUi(int index, PortSettingsUI* u for (int i = 0; i < static_cast(ControllerType::Count); i++) { ui->controller_type->addItem( - QString::fromUtf8(Settings::GetControllerTypeDisplayName(static_cast(i)))); + qApp->translate("ControllerType", Settings::GetControllerTypeDisplayName(static_cast(i)))); } ControllerType ctype = Settings::ParseControllerTypeName( diff --git a/src/duckstation-qt/duckstation-qt.vcxproj b/src/duckstation-qt/duckstation-qt.vcxproj index 410f3aab5..7f75cbbad 100644 --- a/src/duckstation-qt/duckstation-qt.vcxproj +++ b/src/duckstation-qt/duckstation-qt.vcxproj @@ -212,6 +212,7 @@ + diff --git a/src/duckstation-qt/duckstation-qt.vcxproj.filters b/src/duckstation-qt/duckstation-qt.vcxproj.filters index 3e6b9fc98..74a1763f0 100644 --- a/src/duckstation-qt/duckstation-qt.vcxproj.filters +++ b/src/duckstation-qt/duckstation-qt.vcxproj.filters @@ -113,15 +113,28 @@ - - - - - + + translations + + + translations + + + translations + + + translations + + + translations + translations + + translations + \ No newline at end of file diff --git a/src/duckstation-qt/gamepropertiesdialog.cpp b/src/duckstation-qt/gamepropertiesdialog.cpp index 05bcfd92e..f72fe4bed 100644 --- a/src/duckstation-qt/gamepropertiesdialog.cpp +++ b/src/duckstation-qt/gamepropertiesdialog.cpp @@ -74,6 +74,11 @@ void GamePropertiesDialog::populate(const GameListEntry* ge) } populateTracksInfo(ge->path); + + m_game_code = ge->code; + m_game_title = ge->title; + m_game_settings = ge->settings; + populateGameSettings(); } void GamePropertiesDialog::populateCompatibilityInfo(const std::string& game_code) @@ -99,12 +104,50 @@ void GamePropertiesDialog::populateCompatibilityInfo(const std::string& game_cod void GamePropertiesDialog::setupAdditionalUi() { for (u8 i = 0; i < static_cast(DiscRegion::Count); i++) - m_ui.region->addItem(tr(Settings::GetDiscRegionDisplayName(static_cast(i)))); + m_ui.region->addItem(qApp->translate("DiscRegion", Settings::GetDiscRegionDisplayName(static_cast(i)))); for (int i = 0; i < static_cast(GameListCompatibilityRating::Count); i++) { m_ui.compatibility->addItem( - tr(GameList::GetGameListCompatibilityRatingString(static_cast(i)))); + qApp->translate("GameListCompatibilityRating", + GameList::GetGameListCompatibilityRatingString(static_cast(i)))); + } + + m_ui.userAspectRatio->addItem(tr("(unchanged)")); + for (u32 i = 0; i < static_cast(DisplayAspectRatio::Count); i++) + { + m_ui.userAspectRatio->addItem( + QString::fromUtf8(Settings::GetDisplayAspectRatioName(static_cast(i)))); + } + + m_ui.userCropMode->addItem(tr("(unchanged)")); + for (u32 i = 0; i < static_cast(DisplayCropMode::Count); i++) + { + m_ui.userCropMode->addItem( + qApp->translate("DisplayCropMode", Settings::GetDisplayCropModeDisplayName(static_cast(i)))); + } + + m_ui.userControllerType1->addItem(tr("(unchanged)")); + for (u32 i = 0; i < static_cast(ControllerType::Count); i++) + { + m_ui.userControllerType1->addItem( + qApp->translate("ControllerType", Settings::GetControllerTypeDisplayName(static_cast(i)))); + } + + m_ui.userControllerType2->addItem(tr("(unchanged)")); + for (u32 i = 0; i < static_cast(ControllerType::Count); i++) + { + m_ui.userControllerType2->addItem( + qApp->translate("ControllerType", Settings::GetControllerTypeDisplayName(static_cast(i)))); + } + + QGridLayout* traits_layout = new QGridLayout(m_ui.compatibilityTraits); + for (u32 i = 0; i < static_cast(GameSettings::Trait::Count); i++) + { + m_trait_checkboxes[i] = new QCheckBox( + qApp->translate("GameSettingsTrait", GameSettings::GetTraitDisplayName(static_cast(i))), + m_ui.compatibilityTraits); + traits_layout->addWidget(m_trait_checkboxes[i], i / 2, i % 2); } setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); @@ -133,7 +176,7 @@ void GamePropertiesDialog::populateTracksInfo(const std::string& image_path) {"Audio", "Mode 1", "Mode 1/Raw", "Mode 2", "Mode 2/Form 1", "Mode 2/Form 2", "Mode 2/Mix", "Mode 2/Raw"}}; m_ui.tracks->clearContents(); - m_image_path = image_path; + m_path = image_path; std::unique_ptr image = CDImage::Open(image_path.c_str()); if (!image) @@ -147,14 +190,74 @@ void GamePropertiesDialog::populateTracksInfo(const std::string& image_path) const CDImage::TrackMode mode = image->GetTrackMode(static_cast(track)); const int row = static_cast(track - 1u); m_ui.tracks->insertRow(row); - m_ui.tracks->setItem(row, 0, new QTableWidgetItem(tr("%1").arg(track))); - m_ui.tracks->setItem(row, 1, new QTableWidgetItem(tr(track_mode_strings[static_cast(mode)]))); + m_ui.tracks->setItem(row, 0, new QTableWidgetItem(QStringLiteral("%1").arg(track))); + m_ui.tracks->setItem(row, 1, new QTableWidgetItem(track_mode_strings[static_cast(mode)])); m_ui.tracks->setItem(row, 2, new QTableWidgetItem(MSFTotString(position))); m_ui.tracks->setItem(row, 3, new QTableWidgetItem(MSFTotString(length))); m_ui.tracks->setItem(row, 4, new QTableWidgetItem(tr(""))); } } +void GamePropertiesDialog::populateGameSettings() +{ + const GameSettings::Entry& gs = m_game_settings; + + for (u32 i = 0; i < static_cast(GameSettings::Trait::Count); i++) + { + QSignalBlocker sb(m_trait_checkboxes[i]); + m_trait_checkboxes[i]->setChecked(gs.HasTrait(static_cast(i))); + } + + if (gs.display_active_start_offset.has_value()) + { + QSignalBlocker sb(m_ui.displayActiveStartOffset); + m_ui.displayActiveStartOffset->setValue(static_cast(gs.display_active_start_offset.value())); + } + if (gs.display_active_end_offset.has_value()) + { + QSignalBlocker sb(m_ui.displayActiveEndOffset); + m_ui.displayActiveEndOffset->setValue(static_cast(gs.display_active_end_offset.value())); + } + + if (gs.display_crop_mode.has_value()) + { + QSignalBlocker sb(m_ui.userCropMode); + m_ui.userCropMode->setCurrentIndex(static_cast(gs.display_crop_mode.value()) + 1); + } + if (gs.display_aspect_ratio.has_value()) + { + QSignalBlocker sb(m_ui.userAspectRatio); + m_ui.userAspectRatio->setCurrentIndex(static_cast(gs.display_aspect_ratio.value()) + 1); + } + + if (gs.controller_1_type.has_value()) + { + QSignalBlocker sb(m_ui.userControllerType1); + m_ui.userControllerType1->setCurrentIndex(static_cast(gs.controller_1_type.value()) + 1); + } + if (gs.controller_2_type.has_value()) + { + QSignalBlocker sb(m_ui.userControllerType2); + m_ui.userControllerType2->setCurrentIndex(static_cast(gs.controller_2_type.value()) + 1); + } + if (gs.gpu_widescreen_hack.has_value()) + { + QSignalBlocker sb(m_ui.userControllerType2); + m_ui.userWidescreenHack->setCheckState(Qt::Checked); + } + else + { + QSignalBlocker sb(m_ui.userControllerType2); + m_ui.userWidescreenHack->setCheckState(Qt::PartiallyChecked); + } +} + +void GamePropertiesDialog::saveGameSettings() +{ + m_host_interface->getGameList()->UpdateGameSettings(m_path, m_game_code, m_game_title, m_game_settings, true, true); + m_host_interface->applySettings(true); +} + void GamePropertiesDialog::closeEvent(QCloseEvent* ev) { deleteLater(); @@ -186,6 +289,69 @@ void GamePropertiesDialog::connectUi() connect(m_ui.exportCompatibilityInfo, &QPushButton::clicked, this, &GamePropertiesDialog::onExportCompatibilityInfoClicked); connect(m_ui.close, &QPushButton::clicked, this, &QDialog::close); + + connect(m_ui.userAspectRatio, QOverload::of(&QComboBox::currentIndexChanged), [this](int index) { + if (index <= 0) + m_game_settings.display_aspect_ratio.reset(); + else + m_game_settings.display_aspect_ratio = static_cast(index - 1); + saveGameSettings(); + }); + + connect(m_ui.userCropMode, QOverload::of(&QComboBox::currentIndexChanged), [this](int index) { + if (index <= 0) + m_game_settings.display_crop_mode.reset(); + else + m_game_settings.display_crop_mode = static_cast(index - 1); + saveGameSettings(); + }); + + connect(m_ui.userControllerType1, QOverload::of(&QComboBox::currentIndexChanged), [this](int index) { + if (index <= 0) + m_game_settings.controller_1_type.reset(); + else + m_game_settings.controller_1_type = static_cast(index - 1); + saveGameSettings(); + }); + + connect(m_ui.userControllerType2, QOverload::of(&QComboBox::currentIndexChanged), [this](int index) { + if (index <= 0) + m_game_settings.controller_2_type.reset(); + else + m_game_settings.controller_2_type = static_cast(index - 1); + saveGameSettings(); + }); + + connect(m_ui.userWidescreenHack, &QCheckBox::stateChanged, [this](int state) { + if (state == Qt::PartiallyChecked) + m_game_settings.gpu_widescreen_hack.reset(); + else + m_game_settings.gpu_widescreen_hack = (state == Qt::Checked); + saveGameSettings(); + }); + + for (u32 i = 0; i < static_cast(GameSettings::Trait::Count); i++) + { + connect(m_trait_checkboxes[i], &QCheckBox::toggled, [this, i](bool checked) { + m_game_settings.SetTrait(static_cast(i), checked); + saveGameSettings(); + }); + } + + connect(m_ui.displayActiveStartOffset, QOverload::of(&QSpinBox::valueChanged), [this](int value) { + if (value == 0) + m_game_settings.display_active_start_offset.reset(); + else + m_game_settings.display_active_start_offset = static_cast(value); + saveGameSettings(); + }); + connect(m_ui.displayActiveEndOffset, QOverload::of(&QSpinBox::valueChanged), [this](int value) { + if (value == 0) + m_game_settings.display_active_end_offset.reset(); + else + m_game_settings.display_active_end_offset = static_cast(value); + saveGameSettings(); + }); } void GamePropertiesDialog::fillEntryFromUi(GameListCompatibilityEntry* entry) @@ -263,10 +429,10 @@ void GamePropertiesDialog::onExportCompatibilityInfoClicked() void GamePropertiesDialog::computeTrackHashes() { - if (m_image_path.empty()) + if (m_path.empty()) return; - std::unique_ptr image = CDImage::Open(m_image_path.c_str()); + std::unique_ptr image = CDImage::Open(m_path.c_str()); if (!image) return; diff --git a/src/duckstation-qt/gamepropertiesdialog.h b/src/duckstation-qt/gamepropertiesdialog.h index 724fd785f..fb2929bec 100644 --- a/src/duckstation-qt/gamepropertiesdialog.h +++ b/src/duckstation-qt/gamepropertiesdialog.h @@ -1,6 +1,8 @@ #pragma once +#include "core/game_settings.h" #include "ui_gamepropertiesdialog.h" #include +#include struct GameListEntry; struct GameListCompatibilityEntry; @@ -40,15 +42,22 @@ private: void connectUi(); void populateCompatibilityInfo(const std::string& game_code); void populateTracksInfo(const std::string& image_path); + void populateGameSettings(); + void saveGameSettings(); void fillEntryFromUi(GameListCompatibilityEntry* entry); void computeTrackHashes(); void onResize(); Ui::GamePropertiesDialog m_ui; + std::array(GameSettings::Trait::Count)> m_trait_checkboxes{}; QtHostInterface* m_host_interface; - std::string m_image_path; + std::string m_path; + std::string m_game_code; + std::string m_game_title; + + GameSettings::Entry m_game_settings; bool m_compatibility_info_changed = false; bool m_tracks_hashed = false; diff --git a/src/duckstation-qt/gamepropertiesdialog.ui b/src/duckstation-qt/gamepropertiesdialog.ui index 167e7af70..e6a800ede 100644 --- a/src/duckstation-qt/gamepropertiesdialog.ui +++ b/src/duckstation-qt/gamepropertiesdialog.ui @@ -6,171 +6,346 @@ 0 0 - 722 - 466 + 793 + 647 Dialog - + :/icons/duck.png:/icons/duck.png - - - - - Image Path: + + + + + 0 + + + Properties + + + + + + Image Path: + + + + + + + true + + + + + + + Game Code: + + + + + + + true + + + + + + + Title: + + + + + + + true + + + + + + + Region: + + + + + + + false + + + + + + + Compatibility: + + + + + + + + + + Upscaling Issues: + + + + + + + + + + Comments: + + + + + + + + + + Version Tested: + + + + + + + + + + + + Set to Current + + + + + + + + + Tracks: + + + + + + + QAbstractItemView::NoEditTriggers + + + false + + + false + + + + # + + + + + Mode + + + + + Start + + + + + Length + + + + + Hash + + + + + + + + + User Settings + + + + + + GPU Settings + + + + + + Crop Mode: + + + + + + + + + + Aspect Ratio: + + + + + + + + + + Widescreen Hack + + + true + + + + + + + + + + Controller Settings + + + + + + Controller 1 Type: + + + + + + + + + + Controller 2 Type: + + + + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + Compatibility Settings + + + + + + Traits + + + + + + + Overrides + + + + + + Display Active Offset: + + + + + + + + + -5000 + + + 5000 + + + 0 + + + + + + + -5000 + + + 5000 + + + 0 + + + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + - - - - true - - - - - - - Game Code: - - - - - - - true - - - - - - - Title: - - - - - - - true - - - - - - - Region: - - - - - - - false - - - - - - - Compatibility: - - - - - - - - - - Upscaling Issues: - - - - - - - - - - Comments: - - - - - - - - - - Version Tested: - - - - - - - - - - - - Set to Current - - - - - - - - - Tracks: - - - - - - - QAbstractItemView::NoEditTriggers - - - false - - - false - - - - # - - - - - Mode - - - - - Start - - - - - Length - - - - - Hash - - - - - + diff --git a/src/duckstation-qt/generalsettingswidget.cpp b/src/duckstation-qt/generalsettingswidget.cpp index ec44232d6..6ed0929a7 100644 --- a/src/duckstation-qt/generalsettingswidget.cpp +++ b/src/duckstation-qt/generalsettingswidget.cpp @@ -16,12 +16,15 @@ GeneralSettingsWidget::GeneralSettingsWidget(QtHostInterface* host_interface, QW SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.confirmPowerOff, "Main", "ConfirmPowerOff", true); SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.loadDevicesFromSaveStates, "Main", "LoadDevicesFromSaveStates", false); + SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.applyGameSettings, "Main", "ApplyGameSettings", + true); SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.showOSDMessages, "Display", "ShowOSDMessages", true); SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.showFPS, "Display", "ShowFPS", false); SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.showVPS, "Display", "ShowVPS", false); SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.showSpeed, "Display", "ShowSpeed", false); - SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.showResolution, "Display", "ShowResolution", false); + SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.showResolution, "Display", "ShowResolution", + false); SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.enableSpeedLimiter, "Main", "SpeedLimiterEnabled", true); @@ -57,6 +60,10 @@ GeneralSettingsWidget::GeneralSettingsWidget(QtHostInterface* host_interface, QW tr("When enabled, memory cards and controllers will be overwritten when save states are loaded. This can " "result in lost saves, and controller type mismatches. For deterministic save states, enable this option, " "otherwise leave disabled.")); + dialog->registerWidgetHelp( + m_ui.applyGameSettings, tr("Apply Per-Game Settings"), tr("Checked"), + tr("When enabled, per-game settings will be applied, and incompatible enhancements will be disabled. You should " + "leave this option enabled except when testing enhancements with incompatible games.")); dialog->registerWidgetHelp( m_ui.enableSpeedLimiter, tr("Enable Speed Limiter"), tr("Checked"), tr("Throttles the emulation speed to the chosen speed above. If unchecked, the emulator will " @@ -82,26 +89,33 @@ GeneralSettingsWidget::GeneralSettingsWidget(QtHostInterface* host_interface, QW tr("Shows the current emulation speed of the system in the top-right corner of the display as a percentage.")); // Since this one is compile-time selected, we don't put it in the .ui file. - const int last_row_count = m_ui.formLayout_4->rowCount(); + int current_col = 1; + int current_row = m_ui.formLayout_4->rowCount() - current_col; #ifdef WITH_DISCORD_PRESENCE { QCheckBox* enableDiscordPresence = new QCheckBox(tr("Enable Discord Presence"), m_ui.groupBox_4); SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, enableDiscordPresence, "Main", "EnableDiscordPresence"); - m_ui.formLayout_4->addWidget(enableDiscordPresence, last_row_count, 0); + m_ui.formLayout_4->addWidget(enableDiscordPresence, current_row, current_col); dialog->registerWidgetHelp(enableDiscordPresence, tr("Enable Discord Presence"), tr("Unchecked"), tr("Shows the game you are currently playing as part of your profile in Discord.")); + current_col++; + current_row += (current_col / 2); + current_col %= 2; } #endif if (AutoUpdaterDialog::isSupported()) { QCheckBox* enableDiscordPresence = new QCheckBox(tr("Enable Automatic Update Check"), m_ui.groupBox_4); SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, enableDiscordPresence, "AutoUpdater", - "CheckAtStartup"); - m_ui.formLayout_4->addWidget(enableDiscordPresence, last_row_count, 1); + "CheckAtStartup", true); + m_ui.formLayout_4->addWidget(enableDiscordPresence, current_row, current_col); dialog->registerWidgetHelp(enableDiscordPresence, tr("Enable Automatic Update Check"), tr("Checked"), tr("Automatically checks for updates to the program on startup. Updates can be deferred " "until later or skipped entirely.")); + current_col++; + current_row += (current_col / 2); + current_col %= 2; } } diff --git a/src/duckstation-qt/generalsettingswidget.ui b/src/duckstation-qt/generalsettingswidget.ui index 09b29791c..40489f03d 100644 --- a/src/duckstation-qt/generalsettingswidget.ui +++ b/src/duckstation-qt/generalsettingswidget.ui @@ -32,6 +32,20 @@ Behaviour + + + + Confirm Power Off + + + + + + + Render To Main Window + + + @@ -39,10 +53,10 @@ - - + + - Confirm Power Off + Start Fullscreen @@ -60,17 +74,10 @@ - - + + - Start Fullscreen - - - - - - - Render To Main Window + Apply Per-Game Settings diff --git a/src/duckstation-qt/gpusettingswidget.cpp b/src/duckstation-qt/gpusettingswidget.cpp index 15707b271..21a4eb503 100644 --- a/src/duckstation-qt/gpusettingswidget.cpp +++ b/src/duckstation-qt/gpusettingswidget.cpp @@ -44,6 +44,7 @@ GPUSettingsWidget::GPUSettingsWidget(QtHostInterface* host_interface, QWidget* p SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.pgxpTextureCorrection, "GPU", "PGXPTextureCorrection", true); SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.pgxpVertexCache, "GPU", "PGXPVertexCache", false); + SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.pgxpCPUMode, "GPU", "PGXPCPUMode", false); connect(m_ui.resolutionScale, QOverload::of(&QComboBox::currentIndexChanged), this, &GPUSettingsWidget::updateScaledDitheringEnabled); @@ -128,7 +129,8 @@ GPUSettingsWidget::GPUSettingsWidget(QtHostInterface* host_interface, QWidget* p m_ui.widescreenHack, tr("Widescreen Hack"), tr("Unchecked"), tr("Scales vertex positions in screen-space to a widescreen aspect ratio, essentially " "increasing the field of view from 4:3 to 16:9 in 3D games.
For 2D games, or games which " - "use pre-rendered backgrounds, this enhancement will not work as expected. May not be compatible with all games.")); + "use pre-rendered backgrounds, this enhancement will not work as expected. May not be compatible with all " + "games.")); dialog->registerWidgetHelp( m_ui.pgxpEnable, tr("Geometry Correction"), tr("Unchecked"), tr("Reduces \"wobbly\" polygons and \"warping\" textures that are common in PS1 games.
Only " @@ -142,6 +144,10 @@ GPUSettingsWidget::GPUSettingsWidget(QtHostInterface* host_interface, QWidget* p dialog->registerWidgetHelp(m_ui.pgxpVertexCache, tr("Vertex Cache"), tr("Unchecked"), tr("Uses screen coordinates as a fallback when tracking vertices through memory fails. " "May improve PGXP compatibility.")); + dialog->registerWidgetHelp( + m_ui.pgxpCPUMode, tr("CPU Mode"), tr("Unchecked"), + tr("Tries to track vertex manipulation through the CPU. Some games require this option for PGXP to be effective. " + "Very slow, and incompatible with the recompiler.")); } GPUSettingsWidget::~GPUSettingsWidget() = default; @@ -157,7 +163,10 @@ void GPUSettingsWidget::updateScaledDitheringEnabled() void GPUSettingsWidget::setupAdditionalUi() { for (u32 i = 0; i < static_cast(GPURenderer::Count); i++) - m_ui.renderer->addItem(QString::fromUtf8(Settings::GetRendererDisplayName(static_cast(i)))); + { + m_ui.renderer->addItem( + qApp->translate("GPURenderer", Settings::GetRendererDisplayName(static_cast(i)))); + } for (u32 i = 0; i < static_cast(DisplayAspectRatio::Count); i++) { @@ -168,7 +177,7 @@ void GPUSettingsWidget::setupAdditionalUi() for (u32 i = 0; i < static_cast(DisplayCropMode::Count); i++) { m_ui.displayCropMode->addItem( - QString::fromUtf8(Settings::GetDisplayCropModeDisplayName(static_cast(i)))); + qApp->translate("DisplayCropMode", Settings::GetDisplayCropModeDisplayName(static_cast(i)))); } std::array resolution_suffixes = {{ @@ -255,4 +264,5 @@ void GPUSettingsWidget::updatePGXPSettingsEnabled() m_ui.pgxpCulling->setEnabled(enabled); m_ui.pgxpTextureCorrection->setEnabled(enabled); m_ui.pgxpVertexCache->setEnabled(enabled); + m_ui.pgxpCPUMode->setEnabled(enabled); } diff --git a/src/duckstation-qt/gpusettingswidget.ui b/src/duckstation-qt/gpusettingswidget.ui index 33dcae1c2..0d858dde2 100644 --- a/src/duckstation-qt/gpusettingswidget.ui +++ b/src/duckstation-qt/gpusettingswidget.ui @@ -215,6 +215,13 @@
+ + + + CPU Mode + + +
diff --git a/src/duckstation-qt/hotkeysettingswidget.cpp b/src/duckstation-qt/hotkeysettingswidget.cpp index 7bbfac751..0473f415c 100644 --- a/src/duckstation-qt/hotkeysettingswidget.cpp +++ b/src/duckstation-qt/hotkeysettingswidget.cpp @@ -4,6 +4,7 @@ #include "inputbindingwidgets.h" #include "qthostinterface.h" #include "qtutils.h" +#include #include #include #include @@ -63,7 +64,7 @@ void HotkeySettingsWidget::createButtons() std::string section_name("Hotkeys"); std::string key_name(hi.name.GetCharArray()); - layout->addWidget(new QLabel(QString::fromUtf8(hi.display_name), container), target_row, 0); + layout->addWidget(new QLabel(qApp->translate("Hotkeys", hi.display_name), container), target_row, 0); layout->addWidget( new InputButtonBindingWidget(m_host_interface, std::move(section_name), std::move(key_name), container), target_row, 1); diff --git a/src/duckstation-qt/mainwindow.cpp b/src/duckstation-qt/mainwindow.cpp index f32f55cc8..97e67ef50 100644 --- a/src/duckstation-qt/mainwindow.cpp +++ b/src/duckstation-qt/mainwindow.cpp @@ -310,6 +310,34 @@ void MainWindow::onRemoveDiscActionTriggered() m_host_interface->changeDisc(QString()); } +void MainWindow::onViewToolbarActionToggled(bool checked) +{ + m_host_interface->SetBoolSettingValue("UI", "ShowToolbar", checked); + m_ui.toolBar->setVisible(checked); +} + +void MainWindow::onViewStatusBarActionToggled(bool checked) +{ + m_host_interface->SetBoolSettingValue("UI", "ShowStatusBar", checked); + m_ui.statusBar->setVisible(checked); +} + +void MainWindow::onViewGameListActionTriggered() +{ + if (m_emulation_running) + m_host_interface->pauseSystem(true); + switchToGameListView(); +} + +void MainWindow::onViewSystemDisplayTriggered() +{ + if (m_emulation_running) + { + switchToEmulationView(); + m_host_interface->pauseSystem(false); + } +} + void MainWindow::onGitHubRepositoryActionTriggered() { QtUtils::OpenURL(this, "https://github.com/stenzek/duckstation/"); @@ -431,6 +459,14 @@ void MainWindow::setupAdditionalUi() { setWindowTitle(getWindowTitle()); + const bool toolbar_visible = m_host_interface->GetBoolSettingValue("UI", "ShowToolbar", true); + m_ui.actionViewToolbar->setChecked(toolbar_visible); + m_ui.toolBar->setVisible(toolbar_visible); + + const bool status_bar_visible = m_host_interface->GetBoolSettingValue("UI", "ShowStatusBar", true); + m_ui.actionViewStatusBar->setChecked(status_bar_visible); + m_ui.statusBar->setVisible(status_bar_visible); + m_game_list_widget = new GameListWidget(m_ui.mainContainer); m_game_list_widget->initialize(m_host_interface); m_ui.mainContainer->insertWidget(0, m_game_list_widget); @@ -451,6 +487,8 @@ void MainWindow::setupAdditionalUi() m_status_frame_time_widget->setFixedSize(190, 16); m_status_frame_time_widget->hide(); + updateDebugMenuVisibility(); + for (u32 i = 0; i < static_cast(CPUExecutionMode::Count); i++) { const CPUExecutionMode mode = static_cast(i); @@ -507,6 +545,7 @@ void MainWindow::updateEmulationActions(bool starting, bool running) m_ui.actionPause->setDisabled(starting || !running); m_ui.actionChangeDisc->setDisabled(starting || !running); m_ui.actionScreenshot->setDisabled(starting || !running); + m_ui.actionViewSystemDisplay->setEnabled(starting || running); m_ui.menuChangeDisc->setDisabled(starting || !running); m_ui.actionSaveState->setDisabled(starting || !running); @@ -615,6 +654,10 @@ void MainWindow::connectSignals() [this]() { doSettings(SettingsDialog::Category::AudioSettings); }); connect(m_ui.actionAdvancedSettings, &QAction::triggered, [this]() { doSettings(SettingsDialog::Category::AdvancedSettings); }); + connect(m_ui.actionViewToolbar, &QAction::toggled, this, &MainWindow::onViewToolbarActionToggled); + connect(m_ui.actionViewStatusBar, &QAction::toggled, this, &MainWindow::onViewStatusBarActionToggled); + connect(m_ui.actionViewGameList, &QAction::triggered, this, &MainWindow::onViewGameListActionTriggered); + connect(m_ui.actionViewSystemDisplay, &QAction::triggered, this, &MainWindow::onViewSystemDisplayTriggered); connect(m_ui.actionGitHubRepository, &QAction::triggered, this, &MainWindow::onGitHubRepositoryActionTriggered); connect(m_ui.actionIssueTracker, &QAction::triggered, this, &MainWindow::onIssueTrackerActionTriggered); connect(m_ui.actionDiscordServer, &QAction::triggered, this, &MainWindow::onDiscordServerActionTriggered); @@ -847,6 +890,12 @@ void MainWindow::startupUpdateCheck() checkForUpdates(false); } +void MainWindow::updateDebugMenuVisibility() +{ + const bool visible = m_host_interface->GetBoolSettingValue("Main", "ShowDebugMenu", false); + m_ui.menuDebug->menuAction()->setVisible(visible); +} + void MainWindow::checkForUpdates(bool display_message) { if (!AutoUpdaterDialog::isSupported()) diff --git a/src/duckstation-qt/mainwindow.h b/src/duckstation-qt/mainwindow.h index 0e1d0c2d7..e53053866 100644 --- a/src/duckstation-qt/mainwindow.h +++ b/src/duckstation-qt/mainwindow.h @@ -29,6 +29,10 @@ public: /// Performs update check if enabled in settings. void startupUpdateCheck(); +public Q_SLOTS: + /// Updates debug menu visibility (hides if disabled). + void updateDebugMenuVisibility(); + private Q_SLOTS: void reportError(const QString& message); void reportMessage(const QString& message); @@ -58,6 +62,10 @@ private Q_SLOTS: void onChangeDiscFromPlaylistMenuAboutToShow(); void onChangeDiscFromPlaylistMenuAboutToHide(); void onRemoveDiscActionTriggered(); + void onViewToolbarActionToggled(bool checked); + void onViewStatusBarActionToggled(bool checked); + void onViewGameListActionTriggered(); + void onViewSystemDisplayTriggered(); void onGitHubRepositoryActionTriggered(); void onIssueTrackerActionTriggered(); void onDiscordServerActionTriggered(); diff --git a/src/duckstation-qt/mainwindow.ui b/src/duckstation-qt/mainwindow.ui index 03dafc216..14e80ffee 100644 --- a/src/duckstation-qt/mainwindow.ui +++ b/src/duckstation-qt/mainwindow.ui @@ -30,7 +30,7 @@ 0 0 754 - 22 + 30 @@ -161,8 +161,19 @@ + + + &View + + + + + + + + @@ -560,6 +571,41 @@ Resumes the last save state created. + + + true + + + true + + + &Toolbar + + + + + true + + + true + + + &Status Bar + + + + + &Game List + + + + + false + + + System &Display + + diff --git a/src/duckstation-qt/memorycardsettingswidget.cpp b/src/duckstation-qt/memorycardsettingswidget.cpp index fc7730424..63ee1eaae 100644 --- a/src/duckstation-qt/memorycardsettingswidget.cpp +++ b/src/duckstation-qt/memorycardsettingswidget.cpp @@ -76,7 +76,7 @@ void MemoryCardSettingsWidget::createPortSettingsUi(SettingsDialog* dialog, int for (int i = 0; i < static_cast(MemoryCardType::Count); i++) { ui->memory_card_type->addItem( - QString::fromUtf8(Settings::GetMemoryCardTypeDisplayName(static_cast(i)))); + qApp->translate("MemoryCardType", Settings::GetMemoryCardTypeDisplayName(static_cast(i)))); } const MemoryCardType default_value = (index == 0) ? MemoryCardType::PerGameTitle : MemoryCardType::None; diff --git a/src/duckstation-qt/qthostinterface.cpp b/src/duckstation-qt/qthostinterface.cpp index 39b6f6fa1..6c2b9fb95 100644 --- a/src/duckstation-qt/qthostinterface.cpp +++ b/src/duckstation-qt/qthostinterface.cpp @@ -34,7 +34,10 @@ Log_SetChannel(QtHostInterface); #ifdef WIN32 +#include "common/windows_headers.h" #include "frontend-common/d3d11_host_display.h" +#include +#include #endif QtHostInterface::QtHostInterface(QObject* parent) : QObject(parent), CommonHostInterface() @@ -58,6 +61,7 @@ std::vector> QtHostInterface::getAvailableLanguageLi {QStringLiteral("Deutsch"), QStringLiteral("de")}, {QStringLiteral("Español"), QStringLiteral("es")}, {QStringLiteral("עברית"), QStringLiteral("he")}, + {QStringLiteral("Italiano"), QStringLiteral("it")}, {QStringLiteral("Português (Pt)"), QStringLiteral("pt-pt")}, {QStringLiteral("Português (Br)"), QStringLiteral("pt-br")}, {QStringLiteral("简体中文"), QStringLiteral("zh-cn")}}; @@ -279,16 +283,18 @@ void QtHostInterface::setDefaultSettings() m_settings_interface->Save(); CommonHostInterface::LoadSettings(*m_settings_interface.get()); + CommonHostInterface::ApplyGameSettings(false); + CommonHostInterface::FixIncompatibleSettings(false); } CheckForSettingsChanges(old_settings); } -void QtHostInterface::applySettings() +void QtHostInterface::applySettings(bool display_osd_messages /* = false */) { if (!isOnWorkerThread()) { - QMetaObject::invokeMethod(this, "applySettings", Qt::QueuedConnection); + QMetaObject::invokeMethod(this, "applySettings", Qt::QueuedConnection, Q_ARG(bool, display_osd_messages)); return; } @@ -296,6 +302,8 @@ void QtHostInterface::applySettings() { std::lock_guard guard(m_settings_mutex); CommonHostInterface::LoadSettings(*m_settings_interface.get()); + CommonHostInterface::ApplyGameSettings(display_osd_messages); + CommonHostInterface::FixIncompatibleSettings(display_osd_messages); } CheckForSettingsChanges(old_settings); @@ -648,6 +656,7 @@ void QtHostInterface::OnSystemPerformanceCountersUpdated() void QtHostInterface::OnRunningGameChanged() { CommonHostInterface::OnRunningGameChanged(); + applySettings(true); if (!System::IsShutdown()) { @@ -672,6 +681,7 @@ void QtHostInterface::LoadSettings() CommonHostInterface::CheckSettings(*m_settings_interface.get()); CommonHostInterface::LoadSettings(*m_settings_interface.get()); + CommonHostInterface::FixIncompatibleSettings(false); } void QtHostInterface::SetDefaultSettings(SettingsInterface& si) @@ -1179,6 +1189,49 @@ void QtHostInterface::wakeThread() QMetaObject::invokeMethod(m_worker_thread_event_loop, "quit", Qt::QueuedConnection); } +static std::string GetFontPath(const char* name) +{ +#ifdef WIN32 + PWSTR folder_path; + if (FAILED(SHGetKnownFolderPath(FOLDERID_Fonts, 0, nullptr, &folder_path))) + return StringUtil::StdStringFromFormat("C:\\Windows\\Fonts\\%s", name); + + std::string font_path(StringUtil::WideStringToUTF8String(folder_path)); + CoTaskMemFree(folder_path); + font_path += "\\"; + font_path += name; + return font_path; +#else + return name; +#endif +} + +static bool AddImGuiFont(const std::string& language, float size, float framebuffer_scale) +{ + std::string path; + const ImWchar* range = nullptr; +#ifdef WIN32 + if (language == "jp") + { + path = GetFontPath("msgothic.ttc"); + range = ImGui::GetIO().Fonts->GetGlyphRangesJapanese(); + } + else if (language == "zh-cn") + { + path = GetFontPath("msyh.ttc"); + range = ImGui::GetIO().Fonts->GetGlyphRangesChineseSimplifiedCommon(); + } +#endif + + if (!path.empty()) + { + return (ImGui::GetIO().Fonts->AddFontFromFileTTF(path.c_str(), size * framebuffer_scale, nullptr, range) != + nullptr); + } + + return false; +} + void QtHostInterface::createImGuiContext(float framebuffer_scale) { ImGui::CreateContext(); @@ -1190,7 +1243,10 @@ void QtHostInterface::createImGuiContext(float framebuffer_scale) ImGui::GetStyle().ScaleAllSizes(framebuffer_scale); ImGui::StyleColorsDarker(); - ImGui::AddRobotoRegularFont(15.0f * framebuffer_scale); + + std::string language = GetStringSettingValue("Main", "Language", ""); + if (!AddImGuiFont(language, 15.0f, framebuffer_scale)) + ImGui::AddRobotoRegularFont(15.0f * framebuffer_scale); } void QtHostInterface::destroyImGuiContext() @@ -1198,6 +1254,24 @@ void QtHostInterface::destroyImGuiContext() ImGui::DestroyContext(); } +TinyString QtHostInterface::TranslateString(const char* context, const char* str) const +{ + const QString translated(m_translator->translate(context, str)); + if (translated.isEmpty()) + return TinyString(str); + + return TinyString(translated.toUtf8().constData()); +} + +std::string QtHostInterface::TranslateStdString(const char* context, const char* str) const +{ + const QString translated(m_translator->translate(context, str)); + if (translated.isEmpty()) + return std::string(str); + + return translated.toStdString(); +} + QtHostInterface::Thread::Thread(QtHostInterface* parent) : QThread(parent), m_parent(parent) {} QtHostInterface::Thread::~Thread() = default; diff --git a/src/duckstation-qt/qthostinterface.h b/src/duckstation-qt/qthostinterface.h index 5f843364c..663bb26eb 100644 --- a/src/duckstation-qt/qthostinterface.h +++ b/src/duckstation-qt/qthostinterface.h @@ -64,6 +64,9 @@ public: void SetStringListSettingValue(const char* section, const char* key, const std::vector& values); void RemoveSettingValue(const char* section, const char* key); + TinyString TranslateString(const char* context, const char* str) const; + std::string TranslateStdString(const char* context, const char* str) const; + ALWAYS_INLINE const GameList* getGameList() const { return m_game_list.get(); } ALWAYS_INLINE GameList* getGameList() { return m_game_list.get(); } void refreshGameList(bool invalidate_cache = false, bool invalidate_database = false); @@ -129,7 +132,7 @@ Q_SIGNALS: public Q_SLOTS: void setDefaultSettings(); - void applySettings(); + void applySettings(bool display_osd_messages = false); void updateInputMap(); void applyInputProfile(const QString& profile_path); void onDisplayWindowKeyEvent(int key, bool pressed); diff --git a/src/duckstation-qt/translations/duckstation-qt_it.ts b/src/duckstation-qt/translations/duckstation-qt_it.ts new file mode 100644 index 000000000..5cc57e878 --- /dev/null +++ b/src/duckstation-qt/translations/duckstation-qt_it.ts @@ -0,0 +1,2110 @@ + + + + + AboutDialog + + + About DuckStation + Infor su Duckstation + + + + DuckStation + + + + + %1 (%2) + + + + + DuckStation is a free and open-source simulator/emulator of the Sony PlayStation<span style="vertical-align:super;">TM</span> console, focusing on playability, speed, and long-term maintainability. + playability does not mean anything in italian. I'm going with "compatibility" + DuckStation è un simulatore/emulatore gratuito ed open-source della console Sony Playstation<span style="vertical-align:super;">TM</span>, che si concentra su compatibilità, velocità e facilità di manutenzione. + + + + Authors + Autori + + + + Icon by + Icona di + + + + License + Licenza + + + + AdvancedSettingsWidget + + + Form + Finestra + + + + Logging + No way to translate in Italian + Logging + + + + Log Level: + Livello di log: + + + + Log Filters: + Filtri per log: + + + + Log To System Console + Log in Console di Sistema + + + + Log To Window + Log in Finestra + + + + Log To Debug Console + Log in Console di Debug + + + + Log To File + Log in File + + + + Tweaks/Hacks + Not translatable + Tweaks/Hacks + + + + These options are tweakable to improve performance/game compatibility. Use at your own risk, modified values will not be supported. + Queste opzioni sono modificabili per migliorare performance/compatibilità. Usale a tuo rischio. Non riceverai supporto se userai valori modificati. + + + + DMA Max Slice Ticks: + Not translatable + + + + + DMA Halt Ticks: + Not translatable + + + + + GPU FIFO Size: + Not translatable + + + + + GPU Max Run-Ahead: + Not translatable + + + + + Reset To Default + Reimposta Default + + + + Enable Recompiler Memory Exceptions + Abilità Eccezioni di Memoria del Recompiler + + + + System Settings + Impostazioni di Sistema + + + + + Use Debug Host GPU Device + Usa Dispositivo GPU Host di Debug + + + + Unchecked + Deselezionato + + + + Enables the usage of debug devices and shaders for rendering APIs which support them. Should only be used when debugging the emulator. + Abilità l'uso di dispositivi di debug e shaders per API di rendering che li supportano. Dovrebbe essere attivo solo in fase di debug dell'emulatore. + + + + AudioSettingsWidget + + + Form + FInestra + + + + Configuration + Configurazione + + + + Backend: + Not translatable + + + + + Buffer Size: + Dimensione Buffer: + + + + Maximum latency: 0 frames (0.00ms) + Massima latenza: 0 fotogrammi (0.00ms) + + + + Sync To Output + Sincronizza con Output + + + + Start Dumping On Boot + Avvia dumping all'avvio + + + + Controls + Controlli + + + + Volume: + Volume: + + + + + Mute + Muto + + + + 100% + 100% + + + + Audio Backend + Backend Audio + + + + The audio backend determines how frames produced by the emulator are submitted to the host. Cubeb provides the lowest latency, if you encounter issues, try the SDL backend. The null backend disables all host audio output. + Il backend audio determina come i fotogrammi prodotti dall'emulatore vengono mandati all'host. Cubeb fornisce la latenza più bassa, se incontri problemi, prova il backend SDL. il Backend null disabilita l'audio verso l'host. + + + + Buffer Size + Dimensione buffer + + + + The buffer size determines the size of the chunks of audio which will be pulled by the host. Smaller values reduce the output latency, but may cause hitches if the emulation speed is inconsistent. Note that the Cubeb backend uses smaller chunks regardless of this value, so using a low value here may not significantly change latency. + La dimensione del buffer determina la dimensione delle parti di audio che saranno richieste dall'host. Valori piccoli riducono la latenza dell'output, ma potrebbero causare rallentamenti se la velocità di emulazione non è costante. Nota che il backend Cubeb usa parti piccole a prescindere da questo valore, quindi usare un valore basso qui potrebbe non impattare significativamente la latenza. + + + + Checked + Selezionato + + + + Throttles the emulation speed based on the audio backend pulling audio frames. Sync will automatically be disabled if not running at 100% speed. + Modifica la velocità di emulazione in base alla velocità alla quale il backend audio recupera i frame dell'audio. La sincronia verrà automaticamente disabilitata se l'emulatore non raggiunge il 100% di velocità. + + + + + Unchecked + Deselezionato + + + + Start dumping audio to file as soon as the emulator is started. Mainly useful as a debug option. + Avvia il dumping dell'audio in un file non appena l'emulatore parte. Utile principalmente come opzione di debug. + + + + Volume + + + + + Controls the volume of the audio played on the host. Values are in percentage. + Controlla il volume dell'audio riprodotto sull'host. I valori sono percentuali. + + + + Prevents the emulator from producing any audible sound. + Impedisce all'emulatore di produrre qualsiasi suono udibile. + + + + Maximum latency: %1 frames (%2ms) + Massima latenza: %1 fotogrammi (%2ms) + + + + %1% + + + + + AutoUpdaterDialog + + + + + Automatic Updater + Aggiornamento Automatico + + + + Update Available + Aggiornamento Disponibile + + + + Current Version: + Versione Attuale: + + + + New Version: + Nuova Versione: + + + + Update Notes: + Note di aggiornamento: + + + + Download and Install... + Scarica ed Installa... + + + + Skip This Update + Salta Questo Aggiornamento + + + + Remind Me Later + Ricordami Dopo + + + + Updater Error + Errore di Aggiornamento + + + + No updates are currently available. Please try again later. + Non ci sono aggiornamenti disponibili al momento. Riprova più tardi. + + + + Current Version: %1 (%2) + Versione Attuale: %1 (%2) + + + + New Version: %1 (%2) + Nuova Versione: %1 (%2) + + + + Downloading %1... + Scaricando %1... + + + + Cancel + Cancella + + + + ConsoleSettingsWidget + + + Form + Finestra + + + + Console + Console + + + + Region: + Regione: + + + + BIOS Image Path: + Percorso Immagine BIOS: + + + + + Fast Boot + Avvio Veloce + + + + Enable TTY Output + Abilita TTY Output + + + + ... + ... + + + + CPU Emulation + Emulazione CPU + + + + Execution Mode: + Modalità di Esecuzione: + + + + CDROM Emulation + Emulaione CDROM + + + + Use Read Thread (Asynchronous) + Usa Thread di Lettura (Asincrono) + + + + Enable Region Check + Abilità Controllo Regione + + + + Preload Image To RAM + Precarica Imagine nella RAM + + + + + Unchecked + Deselezionato + + + + Patches the BIOS to skip the console's boot animation. Does not work with all games, but usually safe to enabled. + Applica patch al BIOS per saltare l'animazione di avvio della console. Non funziona con tutti i giochi, ma solitamente si può abilitare senza problemi. + + + + Preload Image to RAM + Precarica Immagine nella RAM + + + + Loads the game image into RAM. Useful for network paths that may become unreliable during gameplay. + Carica l'immagine del gioco nella RAM. Utile per percorsi di rete che potrebbero non essere affidabili durante il gameplay. + + + + Select BIOS Image + Selezione Immagine BIOS + + + + ControllerSettingsWidget + + + Controller Type: + Tipo Controller: + + + + Load Profile + Carica Profilo + + + + Save Profile + Salva Profilo + + + + Clear All + Cancella Tutto + + + + Clear Bindings + Cancella Associazioni + + + + Are you sure you want to clear all bound controls? This can not be reversed. + Sei sicuro di voler cancellare tutte le associazioni dei controlli? Quest'azione non può essere annullata. + + + + + Rebind All + Riassocia Tutti + + + + Are you sure you want to rebind all controls? All currently-bound controls will be irreversibly cleared. Rebinding will begin after confirmation. + Sei sicuro di voler riassociare tutti i controlli? Tutte le associazioni attuali verranno irreversibilmente cancellate. La riassociazione comincerà dopo la conferma. + + + + Port %1 + Porta %1 + + + + Button Bindings: + Assocazioni Pulsante: + + + + Axis Bindings: + Assocazioni Asse: + + + + Rumble + Vibrazione + + + + + + Browse... + Sfoglia... + + + + Select File + Seleziona File + + + + + Select path to input profile ini + Selezione percorso per ini del profilo di input + + + + New... + Nuovo... + + + + + Enter Input Profile Name + Inserisci il Nome del Profilo di Input + + + + + Error + Errore + + + + No name entered, input profile was not saved. + Nessun nome inserito, il profilo di input non è stato salvato. + + + + No path selected, input profile was not saved. + Nessun percorso selezionato, il profilo di input non è stato salvato. + + + + GPUSettingsWidget + + + Form + Finestra + + + + Basic + Base + + + + Renderer: + Renderer: + + + + Adapter: + Dispositivo: + + + + Screen Display + Schermo + + + + Aspect Ratio: + Rapporto di Aspetto: + + + + Crop: + Ritaglio: + + + + + Linear Upscaling + Upscaling Lineare + + + + + Integer Upscaling + Upscaling Intero + + + + + VSync + + + + + Enhancements + Miglioramenti + + + + Resolution Scale: + Scala di Risoluzione: + + + + + True Color Rendering (24-bit, disables dithering) + Rendering True Color (24-bit, disabilita retinatura) + + + + + Scaled Dithering (scale dither pattern to resolution) + Retinatura Scalata (scala il pattern di retinatura con la risoluzione) + + + + + Disable Interlacing (force progressive render/scan) + Disabilita Interlacciamento (forza rendering/scan progressivo) + + + + + Force NTSC Timings (60hz-on-PAL) + Forza Timing NTSC (60hz-su-PAL) + + + + + Bilinear Texture Filtering + Filtraggio Texture Bilineare + + + + + Widescreen Hack + + + + + PGXP + + + + + + Geometry Correction + Correzione Geometria + + + + + Culling Correction + Correzione Culling + + + + + Texture Correction + Correzione Texture + + + + + Vertex Cache + Cache Vertice + + + + Renderer + + + + + Chooses the backend to use for rendering tasks for the the console GPU. Depending on your system and hardware, Direct3D 11 and OpenGL hardware backends may be available. The software renderer offers the best compatibility, but is the slowest and does not offer any enhancements. + Sceglie il backend da usare per il rendering della GPU della console. In base al tuo sistema e hardware, potrebbero essere disponibili i backend hardware Direct3D 11 e OpenGL. Il renderer software offre la miglior compatibilità, ma è il più lento e non permette di applicare alcun miglioramento. + + + + Adapter + Dispositivo + + + + + (Default) + + + + + If your system contains multiple GPUs or adapters, you can select which GPU you wish to use for the hardware renderers. This option is only supported in Direct3D and Vulkan, OpenGL will always use the default device. + Se il tuo sistema dispone di diverse GPU o dispositivi, puoi selezionare quale GPU usare per il renderer hardware. Questa opzione è supportata soltanto da Direct3D e Vulkan, OpenGL userà sempre il dispositivo di default. + + + + Aspect Ratio + Rapporto di Aspetto + + + + Changes the aspect ratio used to display the console's output to the screen. The default is 4:3 which matches a typical TV of the era. + Cambia il rapporto di aspetto (Aspect Ratio) usato per mostrare l'output della console sullo schermo. Il valore di default di 4:3 è quello tipico delle TV dell'epoca. + + + + Crop Mode + Modalità di Ritaglio + + + + Only Overscan Area + + + + + Determines how much of the area typically not visible on a consumer TV set to crop/hide. Some games display content in the overscan area, or use it for screen effects and may not display correctly with the All Borders setting. Only Overscan offers a good compromise between stability and hiding black borders. + Determina quanto tagliare/nascondere dell'area tipicamente non visibile su una comune TV. Alcuni giochi mostrano contenuto nella zona di overscan, o la usano per effetti sullo schermo e potrebbero non essere correttamente mostrati con l'impostazione "All Borders"."Only Overscan Area" offre un buon compromesso fra stabilità e il nascondere i bordi neri. + + + + + + + + + + + Unchecked + Deselezionato + + + + Forces the rendering and display of frames to progressive mode. This removes the "combing" effect seen in 480i games by rendering them in 480p. Not all games are compatible with this option, some require interlaced rendering or render interlaced internally. Usually safe to enable. + Forza il rendering e la presentazione dei fotogrammi in modalità progressiva.Questo rimuove l'effetto "rastrello" visibile quando avviene il rendering di giochi 480i in 480p. Non tutti i giochi sono disponibili con questa opzione, alcuni richiedono il rendering interlacciato oppure vengono renderizzati internamente in modo interlacciato. Di solito, si può attivare senza problemi. + + + + + + + + Checked + Selezionato + + + + Uses bilinear texture filtering when displaying the console's framebuffer to the screen. Disabling filtering will producer a sharper, blockier/pixelated image. Enabling will smooth out the image. The option will be less noticable the higher the resolution scale. + Applica un filtraggio texture bilineare mentre viene mostrato il framebuffer della console sullo schermo. Disabilitare il filtraggio produrrà una immagine quadrettosa/pixelata ma più definita. Abilitandolo, l'immagine risulterà smussata. L'opzione si noterà meno all'aumentare del fattore di scala della risoluzione. + + + + Adds padding to the display area to ensure that the ratio between pixels on the host to pixels in the console is an integer number. May result in a sharper image in some 2D games. + Aggiunge del riempimento all'area del display per fare in modo che il rapporto fra i pixel dell'host e i pixel della console sia un numero intero. Potrebbe fornire una immagine più definita in alcuni giochi 2D. + + + + Enables synchronization with the host display when possible. Enabling this option will provide better frame pacing and smoother motion with fewer duplicated frames. VSync is automatically disabled when it is not possible (e.g. running at non-100% speed). + Abilita la sincronizzazione del display dell'host quando possibile. Abilitare l'opzione fornirà un andamento dei frame più continuo con un numero minore di fotogrammi duplicati. VSync è automaticamente disabilitato quando non possibile (es. l'emulatore non va al 100% della velocità). + + + + Resolution Scale + Scala di Risoluzione + + + + Enables the upscaling of 3D objects rendered to the console's framebuffer. Only applies to the hardware backends. This option is usually safe, with most games looking fine at higher resolutions. Higher resolutions require a more powerful GPU. + Abilita l'upscaling degli oggetti 3D renderizzati sul framebuffer della console. Si applica soltanto ai backend hardware. Questa opzione è generalmente sicura e la maggior parte dei giochi non dovrebbero presentre difetti a risoluzioni più alte. Risoluzioni più alte richiedono GPU più potenti. + + + + Forces the precision of colours output to the console's framebuffer to use the full 8 bits of precision per channel. This produces nicer looking gradients at the cost of making some colours look slightly different. Disabling the option also enables dithering, which makes the transition between colours less sharp by applying a pattern around those pixels. Most games are compatible with this option, but there is a number which aren't and will have broken effects with it enabled. Only applies to the hardware renderers. + Forza il framebuffer della console ad usare l'intera precisione di 8 bit per canale per l'output dei colori. Questo produce gradienti più belli da vedere al costo di modificare leggermente i colori. Disabilitare questa opzione abilita anche la retinatura (dithering), che rende la transizione fra colori meno netta applicando un pattern intorno a quei pixel. La maggior parte dei giochi è compatibile con questa opzione, ma ce n'è un numero che non lo sono e mostreranno male alcuni effetti quando abilitata. Si applica solo ai renderer hardware. + + + + Scales the dither pattern to the resolution scale of the emulated GPU. This makes the dither pattern much less obvious at higher resolutions. Usually safe to enable, and only supported by the hardware renderers. + Scala il pattern di retinatura (dithering) al fattore di scala della risoluzione della GPU emulata. Questo rende il pattern di retinatura molto meno ovvio a risoluzioni più alte. Si può generalmente abilitare ed è supportata soltanto dai renderer hardware. + + + + Uses NTSC frame timings when the console is in PAL mode, forcing PAL games to run at 60hz. For most games which have a speed tied to the framerate, this will result in the game running approximately 17% faster. For variable frame rate games, it may not affect the speed. + Usa il timing NTSC per i fotogrammi quando la console è in modalità PAL, forzando i giochi PAL a funzionare a 60hz. Per la maggior parte dei giochi che hanno la velocità legata al framerate, questa opzione li farà andare più veloci del 17%. Per i giochi con framerate variabile, questa opzione non influenzerà la velocità. + + + + Smooths out the blockyness of magnified textures on 3D object by using bilinear filtering. Will have a greater effect on higher resolution scales. Only applies to the hardware renderers. + Smussa la spigolatura delle texture ingrandite su oggetti 3D applicando un filtraggio bilineare. Avrà un effetto maggiore con fatto di scala della risoluzione più alti. Si applica soltanto ai renderer hardware. + + + + Scales vertex positions in screen-space to a widescreen aspect ratio, essentially increasing the field of view from 4:3 to 16:9 in 3D games. <br>For 2D games, or games which use pre-rendered backgrounds, this enhancement will not work as expected. <b><u>May not be compatible with all games.</u></b> + Scala le posizioni dei vertici sullo spazio dello schermo a un rapporto di aspetto widescreen, aumentando essenzialmente il campo visivo (FOV) da 4:3 a 16:9 nei giochi 3D. <br>Per giochi 2D, o giochi che usano sfondi pre-renderizzati, questo miglioramento non funzionerà in modo prevedibile. <br><u>Potrebbe non essere compatibile con tutti i giochi. </u></b> + + + + Reduces "wobbly" polygons and "warping" textures that are common in PS1 games. <br>Only works with the hardware renderers. <b><u>May not be compatible with all games.</u></b> + Riduce i poligoni "ondeggianti" e le texture "deformate" che sono comuni in giochi PS1. <br>Funziona solo con i renderer hardware. <b><u>Potrebbe non essere compatibile con tutti i giochi. </u></b> + + + + Increases the precision of polygon culling, reducing the number of holes in geometry. Requires geometry correction enabled. + Aumenta la precisione del culling dei poligoni, riducendo il numero di buchi nella geometria. Richiede che la correzione della geometria sia attiva. + + + + Uses perspective-correct interpolation for texture coordinates and colors, straightening out warped textures. Requires geometry correction enabled. + Usa un'interpolazione corretta da un punto di vista prospettico delle coordinate delle texture e dei colori, riaddrizzando le texture deformate. Richiede che la correzione della geometria sia attiva. + + + + Uses screen coordinates as a fallback when tracking vertices through memory fails. May improve PGXP compatibility. + Usa le coordinate dello schermo come ripiego durante il tracciamento dei vertici durante i fallimenti della memoria. Potrebbe migliorare la compatibilità del PGXP. + + + + (for 720p) + (per 720p) + + + + (for 1080p) + (per 1080p) + + + + (for 1440p) + (per 1440p) + + + + (for 4K) + (per 4K) + + + + Automatic based on window size + Automatico in base alla dimensione della finestra + + + + %1x%2 + %1x%2 + + + + GameListModel + + + Type + Tipo + + + + Code + Codice + + + + Title + Titolo + + + + File Title + Titolo del File + + + + Size + Dimensione + + + + Region + Regione + + + + Compatibility + Compatibilità + + + + GameListSearchDirectoriesModel + + + Path + Percorso + + + + Recursive + Ricorsivo + + + + GameListSettingsWidget + + + Form + Finestra + + + + Search Directories + Percorsi di Ricerca + + + + Add + Aggiungi + + + + + Remove + Rimuovi + + + + Scan New + Cerca Nuovi + + + + Rescan All + Cerca Tutti + + + + Update Redump Database + Aggiorna DB Redump + + + + Open Directory... + Apri Percorso... + + + + Select Search Directory + Seleziona Percorso di Ricerca + + + + Scan Recursively? + Cercare Ricorsivamente? + + + + Would you like to scan the directory "%1" recursively? + +Scanning recursively takes more time, but will identify files in subdirectories. + Vuoi cercarere nel percorso "%1" ricorsivamente? + +Scansionare ricorsivamente richiede più tempo, ma identificherà file anche nelle sottodirectory. + + + + Download database from redump.org? + Scaricare il database da redump.org? + + + + Do you wish to download the disc database from redump.org? + +This will download approximately 4 megabytes over your current internet connection. + Desideri scaricare il database dei dischi da redump.org? + +Questo scaricherà circa 4 MB attraverso la tua connessione internet attuale. + + + + Downloading %1... + Scaricando %1... + + + + Cancel + Cancella + + + + Download failed + Download fallito + + + + Extracting... + Estrazione... + + + + Extract failed + Estrazione fallita + + + + Extracting game database failed. + Estrazione del database dei giochi fallita. + + + + GamePropertiesDialog + + + Dialog + Finestra + + + + Image Path: + Percorso dell'Immagine: + + + + Game Code: + Codice del Gioco: + + + + Title: + Titolo: + + + + Region: + Regione: + + + + Compatibility: + + + + + Upscaling Issues: + Compatibilità: + + + + Comments: + Commenti: + + + + Version Tested: + Versione Testata: + + + + Set to Current + Imposta Attuale + + + + Tracks: + Tracce: + + + + # + + + + + Mode + Modalità + + + + Start + Inizio + + + + Length + Lunghezza + + + + Hash + + + + + Compute Hashes + Calcola Hash + + + + Verify Dump + Verifica Dump + + + + Export Compatibility Info + Esporta Info Compatibilità + + + + Close + Chiudi + + + + Game Properties - %1 + Proprietà del Gioco - %1 + + + + %1 + + + + + <not computed> + <non calcolato> + + + + Not yet implemented + Non ancora implementato + + + + Compatibility Info Export + Esporta Info Compatibilità + + + + Press OK to copy to clipboard. + Premi OK per copiare negli appunti. + + + + GeneralSettingsWidget + + + Form + FInestra + + + + Behaviour + Comportamento + + + + + Pause On Start + Pausa all'Avvio + + + + + Confirm Power Off + Conferma Spegnimento + + + + + Save State On Exit + Salva Stato all'Uscita + + + + + Load Devices From Save States + Carica Dispositivi da Salva Stato + + + + + Start Fullscreen + Avvia a Schermo Intero + + + + + Render To Main Window + Rendering nella Finestra Principale + + + + + Emulation Speed + Velocità di Emulazione + + + + 100% + 100% + + + + + Enable Speed Limiter + Abilita Limitatore Velocità + + + + + Increase Timer Resolution + Aumenta Risoluzione Timer + + + + On-Screen Display + On-Screen Display (OSD) + + + + Show Messages + Mostra Messaggi + + + + + Show FPS + Mostra FPS + + + + Show Emulation Speed + Mostra Velocità Emulazione + + + + + Show VPS + Mostra VPS + + + + Show Resolution + Mostra Risoluzione + + + + + + + + + + Checked + Selezionato + + + + Determines whether a prompt will be displayed to confirm shutting down the emulator/game when the hotkey is pressed. + Determina se una verrà mostrata una finestra di conferma alla chiusura dell'emulatore/gioco quando viene premuto il tasto di scelta rapida. + + + + Automatically saves the emulator state when powering down or exiting. You can then resume directly from where you left off next time. + Salva automaticamente lo stato dell'emulatore allo spegnimento o chiusura. Potrai riprendere direttamente da dove hai lasciato la prossima volta. + + + + + + + + + + Unchecked + Deselezionato + + + + Automatically switches to fullscreen mode when a game is started. + Passa automaticamente alla modalità Schermo Intero quando un gioco viene avviato. + + + + Renders the display of the simulated console to the main window of the application, over the game list. If unchecked, the display will render in a separate window. + Fa il rendering del display della console simulata nella finestra principale dell'applicazione, sopra la lista dei giochi. Se deselezionato, il rendering verrà fatto in una finestra separata. + + + + Pauses the emulator when a game is started. + Mette in pausa l'emulatore quando viene avviato un gioco. + + + + When enabled, memory cards and controllers will be overwritten when save states are loaded. This can result in lost saves, and controller type mismatches. For deterministic save states, enable this option, otherwise leave disabled. + Quando abilitato, le memory card e i controller saranno sovrascritti quando viene caricato un salvataggio di stato. Questo può causare perdite di salvataggi ed errori negli abbinamenti dei tipi di controller. Per salvataggi di stato deterministici, attiva questa opzione, altrimenti lasciala disabilitata. + + + + Throttles the emulation speed to the chosen speed above. If unchecked, the emulator will run as fast as possible, which may not be playable. + Imposta la velocità di emulazione al valore scelto sopra. Se deselezionato, l'emulatore andrà alla massima velocità possibile, che potrebbe non essere giocabile. + + + + Increases the system timer resolution when emulation is started to provide more accurate frame pacing. May increase battery usage on laptops. + Aumenta la risoluzione del timer del sistema quando l'emulatore è avviato per fornire un andamento dei frame più accurato. Può aumentare il consumo di batteria su laptop. + + + + Sets the target emulation speed. It is not guaranteed that this speed will be reached, and if not, the emulator will run as fast as it can manage. + Imposta la velocità di emulazione desiderata. Non è garantito che questa velocità venga raggiunta. In questo caso, l'emulatore andrà alla massima velocità che potrà raggiungere. + + + + Show OSD Messages + Mostra Messaggi OSD + + + + Shows on-screen-display messages when events occur such as save states being created/loaded, screenshots being taken, etc. + Mostra mesaggi sull'on-screen display (OSD) quando accadono eventi come creazione/caricamento di salvataggi di stato, di screenshot, ecc. + + + + Shows the internal frame rate of the game in the top-right corner of the display. + Mostra il frame rate interno del gioco nell'angolo in alto a destra del display. + + + + Shows the number of frames (or v-syncs) displayed per second by the system in the top-right corner of the display. + Mostra il numero di fotogrammi (o v-sync) mostrati al secondo dal sistema nell'angolo in alto a destra del display. + + + + Show Speed + Mostra velocità + + + + Shows the current emulation speed of the system in the top-right corner of the display as a percentage. + Mostra la velocità di emulazione attuale del sistema nell'angolo in alto a destra, in percentuale. + + + + + Enable Discord Presence + Abilita Presenza Discord + + + + Shows the game you are currently playing as part of your profile in Discord. + Mostra il gioco a cui stai attualmente giocando come parte del tuo profilo su Discord. + + + + + Enable Automatic Update Check + Abilita Controllo Automatico Aggiornamenti + + + + Automatically checks for updates to the program on startup. Updates can be deferred until later or skipped entirely. + Controlla aumetcamente la presenza di aggiornamenti all'avvio del programma. Gli aggiornamenti possono essere posticipati o saltati completamente. + + + + %1% + + + + + InputBindingDialog + + + Edit Bindings + Modifica Associazioni + + + + Bindings for Controller0/ButtonCircle + Associazioni per Controller0/PulsanteCerchio + + + + Add Binding + Aggiungi Associazione + + + + Remove Binding + Rimuovi Associazione + + + + Clear Bindings + Cancella Associazioni + + + + Bindings for %1 %2 + Associazioni per %1 %2 + + + + + Push Button/Axis... [%1] + Premi Bottone/Asse... [%1] + + + + InputBindingWidget + + + %1 bindings + Associazioni %1 + + + + + Push Button/Axis... [%1] + Premi Bottone/Asse... [%1] + + + + MainWindow + + + + + + DuckStation + + + + + System + Sistema + + + + + Change Disc + Cambia Disco + + + + From Playlist... + Da Playlist... + + + + Load State + Carica Stato + + + + Save State + Salva Stato + + + + S&ettings + &Impostazioni + + + + Theme + Tema + + + + Language + Lingua + + + + &Help + &Aiuto + + + + &Debug + + + + + Switch GPU Renderer + Cambia Renderer GPU + + + + Switch CPU Emulation Mode + Cambia Modalità Emulazione CPU + + + + toolBar + + + + + Start &Disc... + Avvia &Disco... + + + + Start &BIOS + Avvia &BIOS + + + + &Scan For New Games + Loosely translated + &Ricerca Nuovi Giochi + + + + &Rescan All Games + Loosely translated + &Aggiorna Intera Libreria + + + + Power &Off + &Spegni + + + + &Reset + &Reset + + + + &Pause + &Pausa + + + + &Load State + &Carica Stato + + + + &Save State + &Salva Stato + + + + E&xit + &Esci + + + + C&onsole Settings... + Impostazioni &Console... + + + + &Controller Settings... + Impostazioni C&ontroller... + + + + &Hotkey Settings... + Impostazioni &Scorciatoie... + + + + &GPU Settings... + Impostazioni &GPU... + + + + Fullscreen + Schermo Intero + + + + Resolution Scale + Scala di Risoluzione + + + + &GitHub Repository... + + + + + &Issue Tracker... + + + + + &Discord Server... + + + + + Check for &Updates... + Cerca &Aggiornamenti... + + + + &About... + &Informazioni su... + + + + Change Disc... + Cambia Disco... + + + + Audio Settings... + Impostazioni Audio... + + + + Game List Settings... + Impostazioni Lista Giochi... + + + + General Settings... + Impostazioni Generali... + + + + Advanced Settings... + Impostazioni Avanzate... + + + + Add Game Directory... + Aggiungi Percorso Giochi... + + + + &Settings... + &Impostazioni... + + + + From File... + Da File... + + + + From Game List... + Da Lista Giochi... + + + + Remove Disc + RImuovi Disco + + + + Resume State + Riprendi Stato + + + + Global State + Stato Globale + + + + Show VRAM + Mostra VRAM + + + + Dump CPU to VRAM Copies + not sure... + Dump CPU verso Copie VRAM + + + + Dump VRAM to CPU Copies + not sure... + Dump VRAM verso Copie CPU + + + + Dump Audio + + + + + Show GPU State + Mostra Stato GPU + + + + Show CDROM State + Mostra Stato CDROM + + + + Show SPU State + Mostra Stato SPU + + + + Show Timers State + Mostra Stato Timers + + + + Show MDEC State + Mostra Stato MDEC + + + + &Screenshot + + + + + &Memory Card Settings... + Impostazioni &Memory Card... + + + + Resume + Riprendi + + + + Resumes the last save state created. + Riprendo l'ultimo salvataggio di stato creato. + + + + Failed to create host display device context. + Errore di creazione del contesto del dispositivo display. + + + + + Select Disc Image + Seleziona Immagine Disco + + + + Properties... + Proprietà... + + + + Open Containing Directory... + Apri Directory... + + + + Default Boot + Boot Predefinito + + + + Fast Boot + Boot Veloce + + + + Full Boot + Boot Completo + + + + Add Search Directory... + Aggiungi Directory di Ricerca... + + + + Language changed. Please restart the application to apply. + Lingua cambiata. Riavvia l'applicazione per applicare il cambiamento. + + + + Default + Predefinito + + + + DarkFusion + + + + + QDarkStyle + + + + + Updater Error + Errore Aggiornamento + + + + <p>Sorry, you are trying to update a DuckStation version which is not an official GitHub release. To prevent incompatibilities, the auto-updater is only enabled on official builds.</p><p>To obtain an official build, please follow the instructions under "Downloading and Running" at the link below:</p><p><a href="https://github.com/stenzek/duckstation/">https://github.com/stenzek/duckstation/</a></p> + <p>Mi dispiace, stai cercando di aggiornare DuckStation ad una versione che non corrisponde ad una release ufficiale di GitHub. Per impedire incompatibiità, l'aggiornamento automatico è abilitato soltanto soltanto per build ufficiali. </p><p> Per ottenere una build ufficiale, segui le istruzioni sotto "Download and Running" al link di seguito:</p><p><a href="https://github.com/stenzek/duckstation/">https://github.com/stenzek/duckstation/</a></p> + + + + Automatic updating is not supported on the current platform. + L'aggiornamento automatico non è supportato sulla tua piattaforma corrente. + + + + MemoryCardSettingsWidget + + + Shared Settings + Impostazioni Condivise + + + + + Use Single Card For Playlist + Usa Memory Card singola per Playlist + + + + Checked + Selezionato + + + + When using a playlist (m3u) and per-game (title) memory cards, a single memory card will be used for all discs. If unchecked, a separate card will be used for each disc. + Quando usi una playlist (m3u) e "per-game (title) memory cards", una singola memory card sarà usata per tutti i dischi. Se deselezionato, sarà usata una memory card separata per ogni disco. + + + + If one of the "separate card per game" memory card modes is chosen, these memory cards will be saved to the memcards directory. + Se una delle modalità "separate card per game" è selezionata, le memory card saranno salvate nella directory memcards. + + + + Open... + Apri... + + + + Memory Card %1 + + + + + Memory Card Type: + Tipo Memory Card: + + + + Browse... + Sfoglia... + + + + Shared Memory Card Path: + Percorso Memory Card Condivisa: + + + + Select path to memory card image + Seleziona percorso per l'immagine memory card + + + + QObject + + + DuckStation Error + Errore DuckStation + + + + Failed to initialize host interface. Cannot continue. + Errore nell'inizializzazione dell'interfaccia host. Impossibile continuare. + + + + Failed to open URL + Impossibile aprire URL + + + + Failed to open URL. + +The URL was: %1 + Improssibile aprire URL. + +L'URL era: %1 + + + + QtHostInterface + + + Game Save %1 (%2) + Salvataggio Gioco %1 (%2) + + + + Game Save %1 (Empty) + Salvataggio Gioco %1 (Vuoto) + + + + Global Save %1 (%2) + Salvataggio Globale %1 (%2) + + + + Global Save %1 (Empty) + Salvataggio Globale %1 (Vuoto) + + + + Resume + Riprendi + + + + Load State + Carica Stato + + + + Resume (%1) + Riprendi (%1) + + + + %1 Save %2 (%3) + to be confirmed + %1 Salva %2 (%3) + + + + Game + Gioco + + + + Delete Save States... + Cancella Salvataggi Stato... + + + + Confirm Save State Deletion + Conferma Cancellazione Salvataggio Stato + + + + Are you sure you want to delete all save states for %1? + +The saves will not be recoverable. + Sei sicuro di voler cancellare tutti i salvataggi di stato di %1? + +I salvataggi non sono recuperabili. + + + + QtProgressCallback + + + DuckStation + + + + + Cancel + Cancella + + + + Error + Errore + + + + Question + Domanda + + + + Information + Informazione + + + + SettingsDialog + + + DuckStation Settings + Impostazioni DuckStation + + + + General Settings + Impostazioni Generali + + + + Console Settings + Impostazioni Console + + + + Game List Settings + Impostazioni Lista Giochi + + + + Hotkey Settings + Impostazioni Scorciatoie + + + + Controller Settings + Impostazioni Controller + + + + Memory Card Settings + Impostazioni Memory Card + + + + GPU Settings + Impostazioni GPU + + + + Audio Settings + Impostazioni Audio + + + + Advanced Settings + Impostazioni Avanzate + + + + <strong>General Settings</strong><hr>These options control how the emulator looks and behaves.<br><br>Mouse over an option for additional information. + <strong>Impostazioni Generali</strong><hr>Queste opzioni controllano l'apparenza e il comportamento dell'emulatore.<br><br>Passa il mouse sopra un'opzione per informazioni aggiuntive. + + + + <strong>Console Settings</strong><hr>These options determine the configuration of the simulated console.<br><br>Mouse over an option for additional information. + <strong>Impostazioni Console</strong><hr>Queste opzioni determinano la configurazione della console simulata.<br><br>Passa il mouse sopra un'opzione per informazioni aggiuntive. + + + + <strong>Game List Settings</strong><hr>The list above shows the directories which will be searched by DuckStation to populate the game list. Search directories can be added, removed, and switched to recursive/non-recursive. Additionally, the redump.org database can be downloaded or updated to provide titles for discs, as the discs themselves do not provide title information. + <strong>Impostazioni Lista Giochi</strong><hr>La lista sopra mostra i percorsi che DuckStation controllerà per popolare la lista giochi. I percorsi di ricerca possono essere aggiunti, rimossi e modificati in ricorsivi/non-ricorsivi. Inoltre, si può scaricare o aggiornare il database redump.org per ottenere i titoli dei giochi, in quanto i dischi non contentono informazioni sui titoli. + + + + <strong>Hotkey Settings</strong><hr>Binding a hotkey allows you to trigger events such as a resetting or taking screenshots at the press of a key/controller button. Hotkey titles are self-explanatory. Clicking a binding will start a countdown, in which case you should press the key or controller button/axis you wish to bind. If no button is pressed and the timer lapses, the binding will be unchanged. To clear a binding, right-click the button. To bind multiple buttons, hold Shift and click the button. + <strong>Impostazioni Scorciatoie</strong><hr>Definire una scorciatoia permette di attivare eventi come riavviare o catturare screenshots alla pressione di un tasto/bottone del controller. I nomi delle scorciatoie sono esplicativi. Cliccando un'associazione si avvierà un conto alla rovescia durante il quale puoi premere un tasto, bottone o asse del controller da associare. Se non viene premuto alcun tasto, l'associazione non verrà modificata. Per cancellare un'associazione, fai click destro sul pulsante. Per associare tasti multipli, tieni premuto Shift mentre premi il tasto. + + + + <strong>Controller Settings</strong><hr>This page lets you choose the type of controller you wish to simulate for the console, and rebind the keys or host game controller buttons to your choosing. Clicking a binding will start a countdown, in which case you should press the key or controller button/axis you wish to bind. (For rumble, press any button/axis on the controller you wish to send rumble to.) If no button is pressed and the timer lapses, the binding will be unchanged. To clear a binding, right-click the button. To bind multiple buttons, hold Shift and click the button. + <strong>Impostazioni Controller</strong><hr>Questa pagina ti permette di scegliere il tipo di controller da utilizzare e modificare le associazioni dei tasti come preferisci. Cliccando un'associazione si avvierà un conto alla rovescia durante il quale puoi premere un tasto, bottone o asse del controller da associare. (Per la vibrazione, premi qualsiasi tasto/asse del controller sul quale vuoi inviare la vibrazione) Se non viene premuto alcun tasto, l'associazione non verrà modificata. Per cancellare un'associazione, fai click destro sul pulsante. Per associare tasti multipli, tieni premuto Shift mentre premi il tasto. + + + + <strong>Memory Card Settings</strong><hr>This page lets you control what mode the memory card emulation will function in, and where the images for these cards will be stored on disk. + <strong>Impostazioni Memory Card</strong><hr>Questa pagina ti permette di cambiare la modalità di emulazione della memory card e il percorso in cui salvare le immagini sul disco. + + + + <strong>GPU Settings</strong><hr>These options control the simulation of the GPU in the console. Various enhancements are available, mouse over each for additional information. + <strong>Impostazioni GPU</strong><hr>Queste opzioni controllano la simulazione della GPU nella console. Vari miglioramenti sono disponibili. Passa il mouse sopra un'opzione per informazioni aggiuntive. + + + + <strong>Audio Settings</strong><hr>These options control the audio output of the console. Mouse over an option for additional information. + <strong>Impostazioni Audio</strong><hr>Queste opzioni controllano l'output audio della console. Passa il mouse sopra un'opzione per informazioni aggiuntive. + + + + <strong>Advanced Settings</strong><hr>These options control logging and internal behavior of the emulator. Mouse over an option for additional information. + <strong>Impostazioni Avanzate</strong><hr>Queste opzioni controllano i log e il comportamento interno dell'emulatore. Passa il mouse sopra un'opzione per informazioni aggiuntive. + + + + Recommended Value + Valore Raccomandato + + + diff --git a/src/duckstation-qt/translations/duckstation-qt_pt-br.ts b/src/duckstation-qt/translations/duckstation-qt_pt-br.ts index 8eaa71ed1..451a3f85a 100644 --- a/src/duckstation-qt/translations/duckstation-qt_pt-br.ts +++ b/src/duckstation-qt/translations/duckstation-qt_pt-br.ts @@ -128,17 +128,22 @@
- + Show Debug Menu + Mostrar Menu de Depuração + + + + Use Debug Host GPU Device Usar GPU para depuração - + Unchecked Desmarcado - + Enables the usage of debug devices and shaders for rendering APIs which support them. Should only be used when debugging the emulator. Permite o uso de dispositivos de depuração e shaders para renderizar APIs que os suportam. Só deve ser usado ao depurar o emulador. @@ -224,7 +229,7 @@ Checked - + Marcado @@ -272,8 +277,8 @@ AutoUpdaterDialog - - + + Automatic Updater Atualizador Automático @@ -303,42 +308,47 @@ Baixar e Instalar... - + Skip This Update Pular esta Atualizção - + Remind Me Later Deixar Para Depois - + Updater Error Erro na Atualização - + No updates are currently available. Please try again later. Não há novas atualizações no momento. - + Current Version: %1 (%2) Versão Atual: %1 (%2) - + New Version: %1 (%2) Nova Versão: %1 (%2) - + + Loading... + Carregando... + + + Downloading %1... Baixando %1... - + Cancel Cancelar @@ -413,6 +423,7 @@ + Unchecked Desmarcado @@ -422,7 +433,17 @@ Pula a animação de inicio do console. Não funciona com todos os jogos, mas é seguro deixar marcado. - + + Preload Image to RAM + Pré-carregar Jogo para RAM + + + + Loads the game image into RAM. Useful for network paths that may become unreliable during gameplay. + Carrega o jogo na memória RAM. Útil para evitar certas instabilidades durante o jogo. + + + Select BIOS Image Escolha o Arquivo de BIOS @@ -579,19 +600,19 @@ - + Linear Upscaling Escalonamento Linear - + Integer Upscaling Escalonamento Integro - + VSync Sincronização Vertical (V-Sync) @@ -607,37 +628,37 @@ - + True Color Rendering (24-bit, disables dithering) Renderização em (24 Cores, desativa o efeito dithering) - + Scaled Dithering (scale dither pattern to resolution) Dithering Escalonado, (Escalona o padrão do dithering para a resolução) - + Disable Interlacing (force progressive render/scan) Desativa o entrelaçamento (Força Rederização Progressiva) - + Force NTSC Timings (60hz-on-PAL) Força o temporizador rodar em NTSC (60hz em jogos EU) - + Bilinear Texture Filtering Filtragem de Textura Bilinear - + Widescreen Hack Hack para Telas Widescreen @@ -648,57 +669,64 @@ - + Geometry Correction Correção Geométrica - + Culling Correction Correção de Curvas - + Texture Correction Correção de Textura - + Vertex Cache Vertice Armazenado - + + + CPU Mode + Modo CPU + + + Renderer Rederizador - + Chooses the backend to use for rendering tasks for the the console GPU. Depending on your system and hardware, Direct3D 11 and OpenGL hardware backends may be available. The software renderer offers the best compatibility, but is the slowest and does not offer any enhancements. Escolhe a opção a ser usada para emular a GPU. Dependendo do seu sistema e hardware, As opções DX11 e OpenGL podem aparecer.O renderizador de software oferece a melhor compatibilidade, mas é o mais lento e não oferece nenhum aprimoramento. - + Adapter Adaptador - + If your system contains multiple GPUs or adapters, you can select which GPU you wish to use for the hardware renderers. This option is only supported in Direct3D and Vulkan, OpenGL will always use the default device. Se você tem várias GPUs ,você poderá selecionar qual delas deseja usar para os renderizadores de hardware. Esta opção é suportada apenas no Direct3D e no Vulkan, OpenGL sempre usará o dispositivo padrão. - - - - - - - - + + + + + + + + + Unchecked Desmarcado @@ -707,37 +735,37 @@ Permite o uso de dispositivos de depuração e shaders para renderizar APIs que os suportam. Só deve ser usado ao depurar o emulador. - + Aspect Ratio Razão de Aspecto - + Changes the aspect ratio used to display the console's output to the screen. The default is 4:3 which matches a typical TV of the era. Altera a proporção usada para exibir o jogo na tela. O padrão é 4:3, que corresponde a uma TV típica da época.CRT mais conhecida como Tubão. - + Crop Mode Modo de Corte - + Only Overscan Area Somente Área Renderizada - + Determines how much of the area typically not visible on a consumer TV set to crop/hide. Some games display content in the overscan area, or use it for screen effects and may not display correctly with the All Borders setting. Only Overscan offers a good compromise between stability and hiding black borders. Determina quanto da area normalmente não visivel em uma TV o usuário pode ver ou não.Alguns jogos mostram conteúdo fora desta area pré-determinada.Somente esta opção "overscan" (fora da área visivel) pode oferecer um boa estabilidade na hora de ocultar as tarjas (bordas)pretas quando ocorrem. - + Forces the rendering and display of frames to progressive mode. This removes the "combing" effect seen in 480i games by rendering them in 480p. Not all games are compatible with this option, some require interlaced rendering or render interlaced internally. Usually safe to enable. Força a renderização e a exibição de quadros para o modo progressivo. Isso remove efeitos de "trepidação" Visto nos jogos 480i renderizando-os em 480p.Nem todos os jogos são compatíveis com esta opção, alguns requerem renderização entrelaçada internamente. Normalmente é seguro ativar. - + Uses NTSC frame timings when the console is in PAL mode, forcing PAL games to run at 60hz. For most games which have a speed tied to the framerate, this will result in the game running approximately 17% faster. For variable frame rate games, it may not affect the speed. Quando o console está no modo PAL - Geralmente jogos Europeus rodam a 50hz. força estes jogos a rodar em até 60hz sendo assim, resulta em um jogo mais rápido até 15%.Em jogos com taxas de quadro (fps) variável pode isto não afetará a velocidade na hora da jogatina. @@ -746,46 +774,46 @@ Força o modo de quadros por segundo em modo progressivo. Se o jogo já tem essa opção nativamente ele não irá ter nenhum beneficio podendo assim deixar a mesma ligada. - - - - - + + + + + Checked Marcado - + Uses bilinear texture filtering when displaying the console's framebuffer to the screen. Disabling filtering will producer a sharper, blockier/pixelated image. Enabling will smooth out the image. The option will be less noticable the higher the resolution scale. Usa textura bilinear filtrando todo buffer para a tela principal.Desabilitar esta filtragem produzirá uma imagem mais nítida porém pixelada. Ativar irá deixar a imagem mais suave. Esta opção fica menos notável em resoluções mais altas. - + Adds padding to the display area to ensure that the ratio between pixels on the host to pixels in the console is an integer number. May result in a sharper image in some 2D games. Adiciona preenchimento na tela para garantir que a proporção entre pixels seja um número inteiro. Pode resultar em uma imagem mais nítida em alguns jogos 2D. - + Enables synchronization with the host display when possible. Enabling this option will provide better frame pacing and smoother motion with fewer duplicated frames. VSync is automatically disabled when it is not possible (e.g. running at non-100% speed). Ativa a sincronização quando possível. A ativação dessa opção fornecerá melhor ritmo de quadros por segundo e movimento mais suave com menos quadros duplicados.<br><br>O V-Sync é desativado automaticamente quando não é possível usá-lo (por exemplo quando o jogo não estiver rodando a 100%). - + Resolution Scale Escala de Resolução - + Enables the upscaling of 3D objects rendered to the console's framebuffer. Only applies to the hardware backends. This option is usually safe, with most games looking fine at higher resolutions. Higher resolutions require a more powerful GPU. Permite o aumento de escala de objetos 3D renderizados, aplica-se apenas aos back-end de hardware é seguro usar essa opção na maioria dos jogos ficando melhor ainda em resoluções mais altas; Isto implica também no maior uso da sua Placa de Video. - + Forces the precision of colours output to the console's framebuffer to use the full 8 bits of precision per channel. This produces nicer looking gradients at the cost of making some colours look slightly different. Disabling the option also enables dithering, which makes the transition between colours less sharp by applying a pattern around those pixels. Most games are compatible with this option, but there is a number which aren't and will have broken effects with it enabled. Only applies to the hardware renderers. Força a precisão das cores produz efeitos de gradientes mais agradável ao custo de fazer com que algumas cores pareçam um pouco diferentes. Desativar a opção também ativa alguns pontilhados, o que torna a transição entre cores menos nítida a maioria dos jogos é compatível com esta opção, os que não forem terão efeitos quebrados com a opção ativada. Aplica-se apenas aos renderizadores por hardware. - + Scales the dither pattern to the resolution scale of the emulated GPU. This makes the dither pattern much less obvious at higher resolutions. Usually safe to enable, and only supported by the hardware renderers. Escalona os 'ditherings' - pontilhados na imagem para a placa de Video.Torna a visão destes pontos muito menos visiveis em resoluções mais altas.Geralmente seguro ativar e suportado apenas pelos rederizadores por Hardware (ou seja usando sua placa de vídeo). @@ -806,62 +834,67 @@ Reduz 'tremeliques' nos poligonos tentando preservar os mesmos na hora da transferência para a memória. Funciona apenas se rederizado por hardware e pode não é compatível com todos os jogos. - + Smooths out the blockyness of magnified textures on 3D object by using bilinear filtering. Will have a greater effect on higher resolution scales. Only applies to the hardware renderers. Suaviza texturas ampliadas em objetos 3D usando filtragem bilinear. Terá efeito maior em resoluções mais altas. Aplica-se apenas aos rederizadores por hardware. - + Scales vertex positions in screen-space to a widescreen aspect ratio, essentially increasing the field of view from 4:3 to 16:9 in 3D games. <br>For 2D games, or games which use pre-rendered backgrounds, this enhancement will not work as expected. <b><u>May not be compatible with all games.</u></b> Escala as posições de vértices para uma proporção de aspecto esticado, aumentando o campo de visão de 4:3 para 16:9 em jogos 3D. <br>Para jogos 2D, ou jogos que usam fundos pré-rederizados, este aprimoramento não funcionará como esperado. <b><u>Pode não ser compatível com todos os jogos</u></b> - + Reduces "wobbly" polygons and "warping" textures that are common in PS1 games. <br>Only works with the hardware renderers. <b><u>May not be compatible with all games.</u></b> Reduz "tremeliques" nos poligonos tentando preservar os mesmos na hora da transferência para a memória. Funciona apenas se rederizado por hardware e pode não ser compatível com todos os jogos.</u></b> - + Increases the precision of polygon culling, reducing the number of holes in geometry. Requires geometry correction enabled. Aumenta a precisão das curvas nos poligonos, reduzindo o número de buracos na geometria do mesmo. Requer a Correção Geometrica ativada. - + Uses perspective-correct interpolation for texture coordinates and colors, straightening out warped textures. Requires geometry correction enabled. Utiliza interpolação corretiva em perspetiva para cordenadas e das cores na textura, endireitando as que estiverem distorcidas. Requer correção de geometria ativada. - + Uses screen coordinates as a fallback when tracking vertices through memory fails. May improve PGXP compatibility. Quando a correção de vertices falha, essa opção se encarrega de usar as coordenadas da tela para o rastreamento. Pode melhorar a compatibilidade com o PGXP. - + + Tries to track vertex manipulation through the CPU. Some games require this option for PGXP to be effective. Very slow, and incompatible with the recompiler. + Tenta manipular o rastreamento dos vértices (extremidades) direto para o processador. Alguns jogos exigem esta opção para que o aprimoramento - PGXP. tenha o efeito desejado. Atenção, este modo é MUITO LENTO, e imcompativel com o recompilador se ativo. + + + (for 720p) >(720p) - + (for 1080p) >(1080p) - + (for 1440p) >(1440p) - + (for 4K) >(4k) - + Automatic based on window size Automático, baseado no tamanho da janela aberta - + %1x%2 %1x%2 @@ -870,8 +903,8 @@ %1x (%2x%3 VRAM) - - + + (Default) Padrão @@ -879,37 +912,37 @@ GameListModel - + Type Tipo - + Code Código - + Title Titulo - + File Title Titulo do Jogo (Na pasta) - + Size Tamanho - + Region Região - + Compatibility Compatibilidade @@ -1177,43 +1210,43 @@ This will download approximately 4 megabytes over your current internet connecti - + Pause On Start Pausar ao Iniciar - + Confirm Power Off Confirmar ao Fechar - + Save State On Exit Salvar ao Fechar - + Load Devices From Save States Carregar a partir do estado salvo - + Start Fullscreen Iniciar em Tela Cheia - + Render To Main Window Carregar Jogo na janela principal - + Emulation Speed Velocidade da emulação @@ -1224,13 +1257,13 @@ This will download approximately 4 megabytes over your current internet connecti - + Enable Speed Limiter Ativa Limitador de Velocidade - + Increase Timer Resolution Aumentar Resolução em Tempo Real @@ -1246,7 +1279,7 @@ This will download approximately 4 megabytes over your current internet connecti - + Show FPS Mostar FPS @@ -1257,131 +1290,136 @@ This will download approximately 4 megabytes over your current internet connecti - + Show VPS Mostrar VPS - - - - - - - + + Show Resolution + Mostrar Resolução + + + + + + + + + Checked Marcado - + Determines whether a prompt will be displayed to confirm shutting down the emulator/game when the hotkey is pressed. Determina se uma janela será mostrara para confirmar o fechamento do emulador ou jogo quando atalho é pressionado. - + Automatically saves the emulator state when powering down or exiting. You can then resume directly from where you left off next time. Salva automaticamente o estado do emulador ao desligar ou sair. Você pode retomar diretamente de onde parou na próxima vez. - - - - - - - + + + + + + + Unchecked Desmarcado - + Automatically switches to fullscreen mode when a game is started. Muda para o modo Tela Cheia assim que um Jogo é Iniciado. - + Renders the display of the simulated console to the main window of the application, over the game list. If unchecked, the display will render in a separate window. Renderiza o jogo na janela principal do emulador sob a janela da lista de jogos. Se desmarcado, o jogo irá rodar em uma janela separada. - + Pauses the emulator when a game is started. Pausa a emulação quando um jogo é iniciado. - + When enabled, memory cards and controllers will be overwritten when save states are loaded. This can result in lost saves, and controller type mismatches. For deterministic save states, enable this option, otherwise leave disabled. Quando ativado, os cartões de memória e os controles serão substituídos assim que os saves states forem carregados. Isso pode resultar em perda de Saves e incompatibilidade nos controles. sendo assim, deixe isto desativado. - + Throttles the emulation speed to the chosen speed above. If unchecked, the emulator will run as fast as possible, which may not be playable. Acelera a velocidade da emulação para a velocidade escolhida acima. Se desmarcado, o emulador será executado o mais rápido possível, pode ser que não seja possivel sequer jogar. - + Increases the system timer resolution when emulation is started to provide more accurate frame pacing. May increase battery usage on laptops. Aumenta a resolução em tempo real quando emulador é iniciado dando maior precisão nos quadros emulados. Pode aumentar o consumo de bateria em Laptops. - + Sets the target emulation speed. It is not guaranteed that this speed will be reached, and if not, the emulator will run as fast as it can manage. Ajusta a velocidade da emulação. Não é garantido que a velocidade será alcançada sendo assim o emulador irá rodar o mais rápido que pode. - + Show OSD Messages Mostar mensagens em Tela - + Shows on-screen-display messages when events occur such as save states being created/loaded, screenshots being taken, etc. Mostrar as mensagens na tela quando eventos ocorrerem;Quando um SaveState é criado ou carregado, capturas de tela forem feitas etc. - + Shows the internal frame rate of the game in the top-right corner of the display. Mostra o FPS atual do jogo no topo superior direito da tela. - + Shows the number of frames (or v-syncs) displayed per second by the system in the top-right corner of the display. Mostra o FPS no canto superior direito da tela. - + Show Speed Mostrar Velocidade - + Shows the current emulation speed of the system in the top-right corner of the display as a percentage. Mostra a velocidade de emulação atual do sistema no canto superior direito da tela registrado em porcentagem. - - + + Enable Discord Presence Ativar Presença Rica do Discord - + Shows the game you are currently playing as part of your profile in Discord. Mostra o jogo que estiver jogando em seu perfil no Discord quando logado. - - + + Enable Automatic Update Check Verificar Por Atualizações - + Automatically checks for updates to the program on startup. Updates can be deferred until later or skipped entirely. Verifica automaticamente por atualizações assim que o emulador for iniciado. Atualizações podem ser postergadas ou ignoradas completamente. - + %1% %1% @@ -1443,9 +1481,9 @@ This will download approximately 4 megabytes over your current internet connecti MainWindow - - - + + + DuckStation DuckStation @@ -1456,287 +1494,297 @@ This will download approximately 4 megabytes over your current internet connecti - + Change Disc Mudar Disco - + + From Playlist... + Da lista de Jogos... + + + Load State Carregar Estado - + Save State Salvar Estado - + S&ettings Configurações - + Theme Tema - + Language Linguagem - + &Help Ajuda - + &Debug Depurar - + Switch GPU Renderer Mudar Renderizador da GPU - + Switch CPU Emulation Mode Mudar Modo de emulação para CPU - + toolBar - + Start &Disc... Iniciar Disco... - + Start &BIOS Iniciar BIOS - + &Scan For New Games Escanear Jogos Novos - + &Rescan All Games Scanear Todos os Jogos - + Power &Off Desligar - + &Reset Reiniciar - + &Pause Pausar - + &Load State Carregar Estado - + &Save State Salvar Estado - + E&xit Sair - + C&onsole Settings... Configuração do Console - + &Controller Settings... Configuração de Controles... - + &Hotkey Settings... Configuração de Atalhos... - + &GPU Settings... Configuração da GPU - + Fullscreen Tela Cheia - + Resolution Scale Escala de Resolução - + &GitHub Repository... Repositório no Github... - + &Issue Tracker... Problemas Abertos... - + &Discord Server... Servidor no Discord... - + Check for &Updates... Checar por Atualizações... - + &About... Sobre... - + Change Disc... Mudar Disco... - + Audio Settings... Configurações de Audio... - + Game List Settings... Configurar lista de Jogos... - + General Settings... Configurações Gerais... - + Advanced Settings... Configurações Avançadas... - + Add Game Directory... Adicionar Diretório de Jogo... - + &Settings... Configurações... - + From File... Do Arquivo... - + From Game List... Da lista de Jogos... - + Remove Disc Remover Disco - + Resume State Resumir Estado - + Global State Estado Global - + Show VRAM Mostrar VRAM - + Dump CPU to VRAM Copies Despejar cópias do CPU para a VRAM - + Dump VRAM to CPU Copies Despejar cópias da VRAM para o CPU - + Dump Audio Despejar Audio - + + Dump RAM... + Despejar para RAM... + + + Show GPU State Mostrar Estado da GPU - + Show CDROM State Mostrar estado do CD-Rom - + Show SPU State Mostrar estado do SPU - + Show Timers State Mostrar estado do Temporizador - + Show MDEC State Mostrar estado do MDEC - + &Screenshot Captura de Tela - + &Memory Card Settings... Configurações de Memory Card... - + Resume Resumir - + Resumes the last save state created. Resumir Último Estado Salvo @@ -1745,7 +1793,7 @@ This will download approximately 4 megabytes over your current internet connecti Falha ao tentar obter informação da janela - + Failed to create host display device context. Falha ao criar uma amostra de contexto da tela. @@ -1754,73 +1802,78 @@ This will download approximately 4 megabytes over your current internet connecti Falha ao tentar obter novas informações da janela - - + + Select Disc Image Escolha uma imagem de Disco - + Properties... Propriedades... - + Open Containing Directory... Abrir diretório... - + Default Boot Inicio Padrão - + Fast Boot Inicio Rápido - + Full Boot Inicio Completo - + Add Search Directory... - Adicione diretório de busca... + Adiciona um novo diretório... - + Language changed. Please restart the application to apply. Lingua Alterada. Reinicie para Aplicar!. - + + Destination File + Destino do Arquivo + + + Default Padrão - + DarkFusion DarkFusion - + QDarkStyle Escuro - + Updater Error Erro na Atualização - + <p>Sorry, you are trying to update a DuckStation version which is not an official GitHub release. To prevent incompatibilities, the auto-updater is only enabled on official builds.</p><p>To obtain an official build, please follow the instructions under "Downloading and Running" at the link below:</p><p><a href="https://github.com/stenzek/duckstation/">https://github.com/stenzek/duckstation/</a></p> <p>Desculpe mas, Você está tentando atualizar uma versão não oficial do Duckstation Para evitarmos imcompatibilidade, o atualizador automático só poderá ser usado nas versões oficiais! </p><p>Para obtê-las, Siga as instruções de como e onde no link "Baixando e Rodando" conforme abaixo:</p><p><a href="https://github.com/stenzek/duckstation/">https://github.com/stenzek/duckstation/</a></p> - + Automatic updating is not supported on the current platform. Atualizções automáticas não são suportadas na plataforma atual. @@ -1828,37 +1881,58 @@ This will download approximately 4 megabytes over your current internet connecti MemoryCardSettingsWidget - + + Shared Settings + Configurações Compartilhadas + + + + + Use Single Card For Playlist + Usar um único cartão para jogos Multiplos (CD1, CD2) + + + + Checked + Marcado + + + + When using a playlist (m3u) and per-game (title) memory cards, a single memory card will be used for all discs. If unchecked, a separate card will be used for each disc. + Esta opção é útil quando se tem jogos com mulitplos CDs; Se desmarcado, um cartão separado será usado para cada disco. + + + If one of the "separate card per game" memory card modes is chosen, these memory cards will be saved to the memcards directory. Se um dos modos de cartão de memória "cartão separado por jogo" for escolhido, esses cartões de memória serão salvos no diretório padrão. - + Open... Abrir... - + Memory Card %1 Cartão de Memória %1 - + Memory Card Type: Tipo de Cartão de Memória: - + Browse... Procurar... - + Shared Memory Card Path: Caminho de Cartões de Memória compartilhados: - + Select path to memory card image Escolha o caminho para os Cartões de Memória @@ -1876,12 +1950,12 @@ This will download approximately 4 megabytes over your current internet connecti Falha ao Iniciar Interface. Não é possivel Continuar. - + Failed to open URL Falha ao abrir Página - + Failed to open URL. The URL was: %1 @@ -1891,42 +1965,62 @@ The URL was: %1 QtHostInterface - + + Game Save %1 (%2) + Jogo Salvo %1 (%2) + + + + Game Save %1 (Empty) + Jogo Salvo %1 (Vazio) + + + + Global Save %1 (%2) + Compartimento Global %1 (%2) + + + + Global Save %1 (Empty) + Compartimento Global %1 (Vazio) + + + Resume Resumir - + Load State Carregar Estado - + Resume (%1) Resumir (%1) - + %1 Save %2 (%3) - %1 Salvar %2 (%3) + %1 Salvo %2 (%3) - + Game Jogo - + Delete Save States... Apagar Jogos Salvos... - + Confirm Save State Deletion Confirma deleção de Estado Salvo - + Are you sure you want to delete all save states for %1? The saves will not be recoverable. diff --git a/src/duckstation-qt/update_translations.bat b/src/duckstation-qt/update_translations.bat index 9c88cd3e9..e6b4298b8 100644 --- a/src/duckstation-qt/update_translations.bat +++ b/src/duckstation-qt/update_translations.bat @@ -1,6 +1,10 @@ -..\..\dep\msvc\qt\5.15.0\msvc2017_64\bin\lupdate.exe ./ -ts translations\duckstation-qt_de.ts -..\..\dep\msvc\qt\5.15.0\msvc2017_64\bin\lupdate.exe ./ -ts translations\duckstation-qt_he.ts -..\..\dep\msvc\qt\5.15.0\msvc2017_64\bin\lupdate.exe ./ -ts translations\duckstation-qt_pt-br.ts -..\..\dep\msvc\qt\5.15.0\msvc2017_64\bin\lupdate.exe ./ -ts translations\duckstation-qt_pt-pt.ts -..\..\dep\msvc\qt\5.15.0\msvc2017_64\bin\lupdate.exe ./ -ts translations\duckstation-qt_zh-cn.ts +set LUPDATE=..\..\dep\msvc\qt\5.15.0\msvc2017_64\bin\lupdate.exe ./ ../core/ ../frontend-common/ -tr-function-alias translate+=TranslateString -tr-function-alias translate+=TranslateStdString -tr-function-alias QT_TRANSLATE_NOOP+=TRANSLATABLE + +%LUPDATE% -ts translations\duckstation-qt_de.ts +%LUPDATE% -ts translations\duckstation-qt_es.ts +%LUPDATE% -ts translations\duckstation-qt_he.ts +%LUPDATE% -ts translations\duckstation-qt_it.ts +%LUPDATE% -ts translations\duckstation-qt_pt-br.ts +%LUPDATE% -ts translations\duckstation-qt_pt-pt.ts +%LUPDATE% -ts translations\duckstation-qt_zh-cn.ts pause diff --git a/src/duckstation-sdl/sdl_host_interface.cpp b/src/duckstation-sdl/sdl_host_interface.cpp index 06dcb5bd2..b4d6bd393 100644 --- a/src/duckstation-sdl/sdl_host_interface.cpp +++ b/src/duckstation-sdl/sdl_host_interface.cpp @@ -321,6 +321,12 @@ void SDLHostInterface::OnRunningGameChanged() { CommonHostInterface::OnRunningGameChanged(); + Settings old_settings(std::move(g_settings)); + CommonHostInterface::LoadSettings(*m_settings_interface.get()); + CommonHostInterface::ApplyGameSettings(true); + CommonHostInterface::FixIncompatibleSettings(true); + CheckForSettingsChanges(old_settings); + if (!System::GetRunningTitle().empty()) SDL_SetWindowTitle(m_window, System::GetRunningTitle().c_str()); else @@ -347,6 +353,8 @@ void SDLHostInterface::SaveAndUpdateSettings() Settings old_settings(std::move(g_settings)); CommonHostInterface::LoadSettings(*m_settings_interface.get()); + CommonHostInterface::ApplyGameSettings(false); + CommonHostInterface::FixIncompatibleSettings(false); CheckForSettingsChanges(old_settings); m_settings_interface->Save(); @@ -454,8 +462,9 @@ void SDLHostInterface::LoadSettings() { // Settings need to be loaded prior to creating the window for OpenGL bits. m_settings_interface = std::make_unique(GetSettingsFileName()); + m_settings_copy.Load(*m_settings_interface); CommonHostInterface::LoadSettings(*m_settings_interface.get()); - m_settings_copy = g_settings; + CommonHostInterface::FixIncompatibleSettings(false); } void SDLHostInterface::ReportError(const char* message) @@ -807,7 +816,7 @@ void SDLHostInterface::DrawQuickSettingsMenu() if (ImGui::BeginMenu("CPU Execution Mode")) { - const CPUExecutionMode current = g_settings.cpu_execution_mode; + const CPUExecutionMode current = m_settings_copy.cpu_execution_mode; for (u32 i = 0; i < static_cast(CPUExecutionMode::Count); i++) { if (ImGui::MenuItem(Settings::GetCPUExecutionModeDisplayName(static_cast(i)), nullptr, @@ -874,6 +883,8 @@ void SDLHostInterface::DrawQuickSettingsMenu() &m_settings_copy.gpu_pgxp_texture_correction, m_settings_copy.gpu_pgxp_enable); settings_changed |= ImGui::MenuItem("PGXP Vertex Cache", nullptr, &m_settings_copy.gpu_pgxp_vertex_cache, m_settings_copy.gpu_pgxp_enable); + settings_changed |= + ImGui::MenuItem("PGXP CPU Instructions", nullptr, &m_settings_copy.gpu_pgxp_cpu, m_settings_copy.gpu_pgxp_enable); ImGui::EndMenu(); } @@ -1347,6 +1358,7 @@ void SDLHostInterface::DrawSettingsWindow() settings_changed |= ImGui::Checkbox("PGXP Culling", &m_settings_copy.gpu_pgxp_culling); settings_changed |= ImGui::Checkbox("PGXP Texture Correction", &m_settings_copy.gpu_pgxp_texture_correction); settings_changed |= ImGui::Checkbox("PGXP Vertex Cache", &m_settings_copy.gpu_pgxp_vertex_cache); + settings_changed |= ImGui::Checkbox("PGXP CPU", &m_settings_copy.gpu_pgxp_cpu); } ImGui::EndTabItem(); diff --git a/src/frontend-common/common_host_interface.cpp b/src/frontend-common/common_host_interface.cpp index c17dd9669..b9181a44c 100644 --- a/src/frontend-common/common_host_interface.cpp +++ b/src/frontend-common/common_host_interface.cpp @@ -71,6 +71,8 @@ bool CommonHostInterface::Initialize() m_game_list->SetCacheFilename(GetUserDirectoryRelativePath("cache/gamelist.cache")); m_game_list->SetDatabaseFilename(GetUserDirectoryRelativePath("cache/redump.dat")); m_game_list->SetCompatibilityFilename(GetProgramDirectoryRelativePath("database/compatibility.xml")); + m_game_list->SetGameSettingsFilename(GetProgramDirectoryRelativePath("database/gamesettings.ini")); + m_game_list->SetUserGameSettingsFilename(GetUserDirectoryRelativePath("gamesettings.ini")); m_save_state_selector_ui = std::make_unique(this); @@ -1238,44 +1240,51 @@ bool CommonHostInterface::AddRumbleToInputMap(const std::string& binding, u32 co void CommonHostInterface::RegisterGeneralHotkeys() { - RegisterHotkey(StaticString("General"), StaticString("FastForward"), StaticString("Fast Forward"), + RegisterHotkey(StaticString("General"), StaticString("FastForward"), TRANSLATABLE("Hotkeys", "Fast Forward"), [this](bool pressed) { m_speed_limiter_temp_disabled = pressed; UpdateSpeedLimiterState(); }); - RegisterHotkey(StaticString("General"), StaticString("ToggleFastForward"), StaticString("Toggle Fast Forward"), - [this](bool pressed) { + RegisterHotkey(StaticString("General"), StaticString("ToggleFastForward"), + StaticString(TRANSLATABLE("Hotkeys", "Toggle Fast Forward")), [this](bool pressed) { if (!pressed) { m_speed_limiter_temp_disabled = !m_speed_limiter_temp_disabled; UpdateSpeedLimiterState(); - AddFormattedOSDMessage(2.0f, "Speed limiter %s.", - m_speed_limiter_enabled ? "enabled" : "disabled"); + AddOSDMessage(m_speed_limiter_enabled ? + TranslateStdString("OSDMessage", "Speed limiter enabled.") : + TranslateStdString("OSDMessage", "Speed limiter disabled."), + 2.0f); } }); - RegisterHotkey(StaticString("General"), StaticString("ToggleFullscreen"), StaticString("Toggle Fullscreen"), - [this](bool pressed) { + RegisterHotkey(StaticString("General"), StaticString("ToggleFullscreen"), + StaticString(TRANSLATABLE("Hotkeys", "Toggle Fullscreen")), [this](bool pressed) { if (!pressed) SetFullscreen(!IsFullscreen()); }); - RegisterHotkey(StaticString("General"), StaticString("TogglePause"), StaticString("Toggle Pause"), - [this](bool pressed) { + RegisterHotkey(StaticString("General"), StaticString("TogglePause"), + StaticString(TRANSLATABLE("Hotkeys", "Toggle Pause")), [this](bool pressed) { if (System::IsValid() && !pressed) PauseSystem(!System::IsPaused()); }); - RegisterHotkey(StaticString("General"), StaticString("PowerOff"), StaticString("Power Off System"), - [this](bool pressed) { + RegisterHotkey(StaticString("General"), StaticString("PowerOff"), + StaticString(TRANSLATABLE("Hotkeys", "Power Off System")), [this](bool pressed) { if (!pressed && System::IsValid()) { if (g_settings.confim_power_off && !m_batch_mode) { - SmallString confirmation_message("Are you sure you want to stop emulation?"); + SmallString confirmation_message( + TranslateString("CommonHostInterface", "Are you sure you want to stop emulation?")); if (g_settings.save_state_on_exit) - confirmation_message.AppendString("\n\nThe current state will be saved."); + { + confirmation_message.AppendString("\n\n"); + confirmation_message.AppendString( + TranslateString("CommonHostInterface", "The current state will be saved.")); + } if (!ConfirmMessage(confirmation_message)) { @@ -1288,53 +1297,55 @@ void CommonHostInterface::RegisterGeneralHotkeys() } }); - RegisterHotkey(StaticString("General"), StaticString("Screenshot"), StaticString("Save Screenshot"), - [this](bool pressed) { + RegisterHotkey(StaticString("General"), StaticString("Screenshot"), + StaticString(TRANSLATABLE("Hotkeys", "Save Screenshot")), [this](bool pressed) { if (!pressed && System::IsValid()) SaveScreenshot(); }); - RegisterHotkey(StaticString("General"), StaticString("FrameStep"), StaticString("Frame Step"), [this](bool pressed) { - if (!pressed) - { - DoFrameStep(); - } - }); + RegisterHotkey(StaticString("General"), StaticString("FrameStep"), + StaticString(TRANSLATABLE("Hotkeys", "Frame Step")), [this](bool pressed) { + if (!pressed) + { + DoFrameStep(); + } + }); } void CommonHostInterface::RegisterGraphicsHotkeys() { RegisterHotkey(StaticString("Graphics"), StaticString("ToggleSoftwareRendering"), - StaticString("Toggle Software Rendering"), [this](bool pressed) { + StaticString(TRANSLATABLE("Hotkeys", "Toggle Software Rendering")), [this](bool pressed) { if (!pressed) ToggleSoftwareRendering(); }); - RegisterHotkey( - StaticString("Graphics"), StaticString("TogglePGXP"), StaticString("Toggle PGXP"), [this](bool pressed) { - if (!pressed) - { - g_settings.gpu_pgxp_enable = !g_settings.gpu_pgxp_enable; - g_gpu->UpdateSettings(); - AddFormattedOSDMessage(5.0f, "PGXP is now %s.", g_settings.gpu_pgxp_enable ? "enabled" : "disabled"); + RegisterHotkey(StaticString("Graphics"), StaticString("TogglePGXP"), + StaticString(TRANSLATABLE("Hotkeys", "Toggle PGXP")), [this](bool pressed) { + if (!pressed) + { + g_settings.gpu_pgxp_enable = !g_settings.gpu_pgxp_enable; + g_gpu->UpdateSettings(); + AddFormattedOSDMessage(5.0f, "PGXP is now %s.", + g_settings.gpu_pgxp_enable ? "enabled" : "disabled"); - if (g_settings.gpu_pgxp_enable) - PGXP::Initialize(); + if (g_settings.gpu_pgxp_enable) + PGXP::Initialize(); - // we need to recompile all blocks if pgxp is toggled on/off - if (g_settings.IsUsingCodeCache()) - CPU::CodeCache::Flush(); - } - }); + // we need to recompile all blocks if pgxp is toggled on/off + if (g_settings.IsUsingCodeCache()) + CPU::CodeCache::Flush(); + } + }); RegisterHotkey(StaticString("Graphics"), StaticString("IncreaseResolutionScale"), - StaticString("Increase Resolution Scale"), [this](bool pressed) { + StaticString(TRANSLATABLE("Hotkeys", "Increase Resolution Scale")), [this](bool pressed) { if (!pressed) ModifyResolutionScale(1); }); RegisterHotkey(StaticString("Graphics"), StaticString("DecreaseResolutionScale"), - StaticString("Decrease Resolution Scale"), [this](bool pressed) { + StaticString(TRANSLATABLE("Hotkeys", "Decrease Resolution Scale")), [this](bool pressed) { if (!pressed) ModifyResolutionScale(-1); }); @@ -1343,80 +1354,94 @@ void CommonHostInterface::RegisterGraphicsHotkeys() void CommonHostInterface::RegisterSaveStateHotkeys() { RegisterHotkey(StaticString("Save States"), StaticString("LoadSelectedSaveState"), - StaticString("Load From Selected Slot"), [this](bool pressed) { + StaticString(TRANSLATABLE("Hotkeys", "Load From Selected Slot")), [this](bool pressed) { if (!pressed) m_save_state_selector_ui->LoadCurrentSlot(); }); RegisterHotkey(StaticString("Save States"), StaticString("SaveSelectedSaveState"), - StaticString("Save To Selected Slot"), [this](bool pressed) { + StaticString(TRANSLATABLE("Hotkeys", "Save To Selected Slot")), [this](bool pressed) { if (!pressed) m_save_state_selector_ui->SaveCurrentSlot(); }); RegisterHotkey(StaticString("Save States"), StaticString("SelectPreviousSaveStateSlot"), - StaticString("Select Previous Save Slot"), [this](bool pressed) { + StaticString(TRANSLATABLE("Hotkeys", "Select Previous Save Slot")), [this](bool pressed) { if (!pressed) m_save_state_selector_ui->SelectPreviousSlot(); }); RegisterHotkey(StaticString("Save States"), StaticString("SelectNextSaveStateSlot"), - StaticString("Select Next Save Slot"), [this](bool pressed) { + StaticString(TRANSLATABLE("Hotkeys", "Select Next Save Slot")), [this](bool pressed) { if (!pressed) m_save_state_selector_ui->SelectNextSlot(); }); - for (u32 global_i = 0; global_i < 2; global_i++) + for (u32 slot = 1; slot <= PER_GAME_SAVE_STATE_SLOTS; slot++) { - const bool global = ConvertToBoolUnchecked(global_i); - const u32 count = global ? GLOBAL_SAVE_STATE_SLOTS : PER_GAME_SAVE_STATE_SLOTS; - for (u32 slot = 1; slot <= count; slot++) - { - RegisterHotkey(StaticString("Save States"), - TinyString::FromFormat("Load%sState%u", global ? "Global" : "Game", slot), - TinyString::FromFormat("Load %s State %u", global ? "Global" : "Game", slot), - [this, global, slot](bool pressed) { - if (!pressed) - LoadState(global, slot); - }); - RegisterHotkey(StaticString("Save States"), - TinyString::FromFormat("Save%sState%u", global ? "Global" : "Game", slot), - TinyString::FromFormat("Save %s State %u", global ? "Global" : "Game", slot), - [this, global, slot](bool pressed) { - if (!pressed) - SaveState(global, slot); - }); - } + RegisterHotkey(StaticString("Save States"), TinyString::FromFormat("LoadGameState%u", slot), + TinyString::FromFormat(TRANSLATABLE("Hotkeys", "Load Game State %u"), slot), + [this, slot](bool pressed) { + if (!pressed) + LoadState(false, slot); + }); + RegisterHotkey(StaticString("Save States"), TinyString::FromFormat("SaveGameState%u", slot), + TinyString::FromFormat(TRANSLATABLE("Hotkeys", "Save Game State %u"), slot), + [this, slot](bool pressed) { + if (!pressed) + SaveState(false, slot); + }); + } + + for (u32 slot = 1; slot <= GLOBAL_SAVE_STATE_SLOTS; slot++) + { + RegisterHotkey(StaticString("Save States"), TinyString::FromFormat("LoadGlobalState%u", slot), + TinyString::FromFormat(TRANSLATABLE("Hotkeys", "Load Global State %u"), slot), + [this, slot](bool pressed) { + if (!pressed) + LoadState(true, slot); + }); + RegisterHotkey(StaticString("Save States"), TinyString::FromFormat("SaveGlobalState%u", slot), + TinyString::FromFormat(TRANSLATABLE("Hotkeys", "Save Global State %u"), slot), + [this, slot](bool pressed) { + if (!pressed) + SaveState(true, slot); + }); } } void CommonHostInterface::RegisterAudioHotkeys() { - RegisterHotkey(StaticString("Audio"), StaticString("AudioMute"), StaticString("Toggle Mute"), [this](bool pressed) { - if (System::IsValid() && !pressed) - { - g_settings.audio_output_muted = !g_settings.audio_output_muted; - m_audio_stream->SetOutputVolume(g_settings.audio_output_muted ? 0 : g_settings.audio_output_volume); - if (g_settings.audio_output_muted) - AddOSDMessage("Volume: Muted", 2.0f); - else - AddFormattedOSDMessage(2.0f, "Volume: %d%%", g_settings.audio_output_volume); - } - }); - RegisterHotkey(StaticString("Audio"), StaticString("AudioVolumeUp"), StaticString("Volume Up"), [this](bool pressed) { - if (System::IsValid() && pressed) - { - g_settings.audio_output_volume = std::min(g_settings.audio_output_volume + 10, 100); - g_settings.audio_output_muted = false; - m_audio_stream->SetOutputVolume(g_settings.audio_output_volume); - AddFormattedOSDMessage(2.0f, "Volume: %d%%", g_settings.audio_output_volume); - } - }); - RegisterHotkey(StaticString("Audio"), StaticString("AudioVolumeDown"), StaticString("Volume Down"), - [this](bool pressed) { + RegisterHotkey( + StaticString("Audio"), StaticString("AudioMute"), StaticString(TRANSLATABLE("Hotkeys", "Toggle Mute")), + [this](bool pressed) { + if (System::IsValid() && !pressed) + { + g_settings.audio_output_muted = !g_settings.audio_output_muted; + m_audio_stream->SetOutputVolume(g_settings.audio_output_muted ? 0 : g_settings.audio_output_volume); + if (g_settings.audio_output_muted) + AddOSDMessage(TranslateStdString("OSDMessage", "Volume: Muted"), 2.0f); + else + AddFormattedOSDMessage(2.0f, TranslateString("OSDMessage", "Volume: %d%%"), g_settings.audio_output_volume); + } + }); + RegisterHotkey(StaticString("Audio"), StaticString("AudioVolumeUp"), + StaticString(TRANSLATABLE("Hotkeys", "Volume Up")), [this](bool pressed) { + if (System::IsValid() && pressed) + { + g_settings.audio_output_volume = std::min(g_settings.audio_output_volume + 10, 100); + g_settings.audio_output_muted = false; + m_audio_stream->SetOutputVolume(g_settings.audio_output_volume); + AddFormattedOSDMessage(2.0f, TranslateString("OSDMessage", "Volume: %d%%"), + g_settings.audio_output_volume); + } + }); + RegisterHotkey(StaticString("Audio"), StaticString("AudioVolumeDown"), + StaticString(TRANSLATABLE("Hotkeys", "Volume Down")), [this](bool pressed) { if (System::IsValid() && pressed) { g_settings.audio_output_volume = std::max(g_settings.audio_output_volume - 10, 0); g_settings.audio_output_muted = false; m_audio_stream->SetOutputVolume(g_settings.audio_output_volume); - AddFormattedOSDMessage(2.0f, "Volume: %d%%", g_settings.audio_output_volume); + AddFormattedOSDMessage(2.0f, TranslateString("OSDMessage", "Volume: %d%%"), + g_settings.audio_output_volume); } }); } @@ -1552,7 +1577,7 @@ void CommonHostInterface::ApplyInputProfile(const char* profile_path, SettingsIn UpdateInputMap(si); - ReportFormattedMessage("Loaded input profile from '%s'", profile_path); + ReportFormattedMessage(TranslateString("OSDMessage", "Loaded input profile from '%s'"), profile_path); } bool CommonHostInterface::SaveInputProfile(const char* profile_path, SettingsInterface& si) @@ -2016,14 +2041,25 @@ bool CommonHostInterface::SaveScreenshot(const char* filename /* = nullptr */, b const bool screenshot_saved = m_display->WriteDisplayTextureToFile(filename, full_resolution, apply_aspect_ratio); if (!screenshot_saved) { - AddFormattedOSDMessage(10.0f, "Failed to save screenshot to '%s'", filename); + AddFormattedOSDMessage(10.0f, TranslateString("OSDMessage", "Failed to save screenshot to '%s'"), filename); return false; } - AddFormattedOSDMessage(5.0f, "Screenshot saved to '%s'.", filename); + AddFormattedOSDMessage(5.0f, TranslateString("OSDMessage", "Screenshot saved to '%s'."), filename); return true; } +void CommonHostInterface::ApplyGameSettings(bool display_osd_messages) +{ + // this gets called while booting, so can't use valid + if (System::IsShutdown() || System::GetRunningCode().empty() || !g_settings.apply_game_settings) + return; + + const GameSettings::Entry* gs = m_game_list->GetGameSettings(System::GetRunningPath(), System::GetRunningCode()); + if (gs) + gs->ApplySettings(display_osd_messages); +} + #ifdef WITH_DISCORD_PRESENCE void CommonHostInterface::SetDiscordPresenceEnabled(bool enabled) diff --git a/src/frontend-common/common_host_interface.h b/src/frontend-common/common_host_interface.h index 16be28a6e..b365f5dd3 100644 --- a/src/frontend-common/common_host_interface.h +++ b/src/frontend-common/common_host_interface.h @@ -266,6 +266,8 @@ protected: void RecreateSystem() override; + void ApplyGameSettings(bool display_osd_messages); + virtual void DrawImGuiWindows(); void DrawFPSWindow(); diff --git a/src/frontend-common/sdl_controller_interface.cpp b/src/frontend-common/sdl_controller_interface.cpp index 7b2c93b0e..96a07b04d 100644 --- a/src/frontend-common/sdl_controller_interface.cpp +++ b/src/frontend-common/sdl_controller_interface.cpp @@ -1,5 +1,6 @@ #include "sdl_controller_interface.h" #include "common/assert.h" +#include "common/file_system.h" #include "common/log.h" #include "core/controller.h" #include "core/host_interface.h" @@ -23,6 +24,17 @@ bool SDLControllerInterface::Initialize(CommonHostInterface* host_interface) FrontendCommon::EnsureSDLInitialized(); + const std::string gcdb_file_name = GetGameControllerDBFileName(); + if (FileSystem::FileExists(gcdb_file_name.c_str())) + { + Log_InfoPrintf("Loading game controller mappings from '%s'", gcdb_file_name.c_str()); + if (SDL_GameControllerAddMappingsFromFile(gcdb_file_name.c_str()) < 0) + { + Log_ErrorPrintf("SDL_GameControllerAddMappingsFromFile(%s) failed: %s", + gcdb_file_name.c_str(), SDL_GetError()); + } + } + if (SDL_InitSubSystem(SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER | SDL_INIT_HAPTIC) < 0) { Log_ErrorPrintf("SDL_InitSubSystem(SDL_INIT_JOYSTICK |SDL_INIT_GAMECONTROLLER | SDL_INIT_HAPTIC) failed"); @@ -48,6 +60,11 @@ void SDLControllerInterface::Shutdown() ControllerInterface::Shutdown(); } +std::string SDLControllerInterface::GetGameControllerDBFileName() const +{ + return m_host_interface->GetUserDirectoryRelativePath("gamecontrollerdb.txt"); +} + void SDLControllerInterface::PollEvents() { for (;;) diff --git a/src/frontend-common/sdl_controller_interface.h b/src/frontend-common/sdl_controller_interface.h index 1aacab523..7636d9372 100644 --- a/src/frontend-common/sdl_controller_interface.h +++ b/src/frontend-common/sdl_controller_interface.h @@ -17,6 +17,9 @@ public: bool Initialize(CommonHostInterface* host_interface) override; void Shutdown() override; + /// Returns the path of the optional game controller database file. + std::string GetGameControllerDBFileName() const; + // Removes all bindings. Call before setting new bindings. void ClearBindings() override; diff --git a/src/scmversion/gen_scmversion.bat b/src/scmversion/gen_scmversion.bat index 79a19729a..31495cff7 100644 --- a/src/scmversion/gen_scmversion.bat +++ b/src/scmversion/gen_scmversion.bat @@ -4,8 +4,9 @@ SET VERSIONFILE="scmversion.cpp" FOR /F "tokens=* USEBACKQ" %%g IN (`git rev-parse HEAD`) do (SET "HASH=%%g") FOR /F "tokens=* USEBACKQ" %%g IN (`git rev-parse --abbrev-ref HEAD`) do (SET "BRANCH=%%g") FOR /F "tokens=* USEBACKQ" %%g IN (`git describe --tags --dirty --exclude latest`) do (SET "TAG=%%g") +FOR /F "tokens=* USEBACKQ" %%g IN (`git log -1 --date=iso8601-strict "--format=%%cd"`) do (SET "CDATE=%%g") -SET SIGNATURELINE=// %HASH% %BRANCH% %TAG% +SET SIGNATURELINE=// %HASH% %BRANCH% %TAG% %CDATE% SET /P EXISTINGLINE=< %VERSIONFILE% IF "%EXISTINGLINE%"=="%SIGNATURELINE%" ( @@ -19,6 +20,7 @@ ECHO Updating %VERSIONFILE%... ECHO const char* g_scm_hash_str = "%HASH%"; ECHO const char* g_scm_branch_str = "%BRANCH%"; ECHO const char* g_scm_tag_str = "%TAG%"; +ECHO const char* g_scm_date_str = "%CDATE%"; )>%VERSIONFILE% EXIT diff --git a/src/scmversion/gen_scmversion.sh b/src/scmversion/gen_scmversion.sh index a6d92b8ac..eed028b26 100755 --- a/src/scmversion/gen_scmversion.sh +++ b/src/scmversion/gen_scmversion.sh @@ -4,8 +4,9 @@ VERSION_FILE="scmversion.cpp" HASH=$(git rev-parse HEAD) BRANCH=$(git rev-parse --abbrev-ref HEAD | tr -d '\r\n') TAG=$(git describe --tags --dirty --exclude latest | tr -d '\r\n') +DATE=$(git log -1 --date=iso8601-strict --format=%cd) -SIGNATURE_LINE="// ${HASH} ${BRANCH} ${TAG}" +SIGNATURE_LINE="// ${HASH} ${BRANCH} ${TAG} ${DATE}" if [ -f $VERSION_FILE ]; then EXISTING_LINE=$(head -n1 $VERSION_FILE | tr -d '\n') @@ -22,6 +23,7 @@ ${SIGNATURE_LINE} const char* g_scm_hash_str = "${HASH}"; const char* g_scm_branch_str = "${BRANCH}"; const char* g_scm_tag_str = "${TAG}"; +const char* g_scm_date_str = "${DATE}"; EOF diff --git a/src/scmversion/scmversion.h b/src/scmversion/scmversion.h index 681da7971..a27ff7a5c 100644 --- a/src/scmversion/scmversion.h +++ b/src/scmversion/scmversion.h @@ -3,4 +3,5 @@ extern const char* g_scm_hash_str; extern const char* g_scm_branch_str; extern const char* g_scm_tag_str; +extern const char* g_scm_date_str;