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