Merge branch 'master' into patch-6

This commit is contained in:
Connor McLaughlin 2020-08-22 13:09:16 +10:00 committed by GitHub
commit 7100277ad5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
75 changed files with 6006 additions and 825 deletions

View file

@ -144,6 +144,12 @@ jobs:
with: with:
fetch-depth: 0 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 - name: Compile and zip Linux x64 libretro core
shell: bash shell: bash
run: | run: |
@ -151,13 +157,28 @@ jobs:
cd build-libretro-linux-x64 cd build-libretro-linux-x64
cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_LIBRETRO_CORE=ON .. cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_LIBRETRO_CORE=ON ..
cmake --build . --parallel 2 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 - name: Upload Linux x64 libretro core
uses: actions/upload-artifact@v1 uses: actions/upload-artifact@v1
with: with:
name: "linux-libretro" 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 - name: Compile and zip Android AArch64 libretro core
shell: bash shell: bash
@ -271,7 +292,8 @@ jobs:
linux-x64-appimage-sdl-zsync/duckstation-sdl-x64.AppImage.zsync linux-x64-appimage-sdl-zsync/duckstation-sdl-x64.AppImage.zsync
linux-x64-appimage-qt/duckstation-qt-x64.AppImage linux-x64-appimage-qt/duckstation-qt-x64.AppImage
linux-x64-appimage-qt-zsync/duckstation-qt-x64.AppImage.zsync 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 linux-libretro/duckstation_libretro_android_aarch64.so.zip
android/duckstation-android-aarch64.apk android/duckstation-android-aarch64.apk

View file

@ -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)

View file

@ -12,6 +12,7 @@ The following people have contributed to the project in some way, and are credit
- @phoe-nix - Chinese (Simplified) - @phoe-nix - Chinese (Simplified)
- Sorer - @MojoJojoDojo - Hebrew - Sorer - @MojoJojoDojo - Hebrew
- Hipnosis183 - Spanish - Hipnosis183 - Spanish
- @RaydenX93 - Italian
## Game Compatibility Database ## Game Compatibility Database
- @Zet-sensei - @Zet-sensei

View file

@ -11,6 +11,8 @@ A "BIOS" ROM image is required to to start the emulator and to play games. You c
## Latest News ## 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/15: Playlist support/single memcard for multi-disc games in Qt frontend added.
- 2020/08/07: Automatic updater for standalone Windows builds. - 2020/08/07: Automatic updater for standalone Windows builds.
- 2020/08/01: Initial PGXP (geometry/perspective correction) support. - 2020/08/01: Initial PGXP (geometry/perspective correction) support.
@ -36,6 +38,7 @@ Other features include:
- CPU Recompiler/JIT (x86-64 and AArch64) - CPU Recompiler/JIT (x86-64 and AArch64)
- Hardware (D3D11, OpenGL, Vulkan) and software rendering - Hardware (D3D11, OpenGL, Vulkan) and software rendering
- Upscaling and true colour (24-bit) in hardware renderers - Upscaling and true colour (24-bit) in hardware renderers
- PGXP for geometry precision and texture correction
- "Fast boot" for skipping BIOS splash/intro - "Fast boot" for skipping BIOS splash/intro
- Save state support - Save state support
- Windows, Linux, **highly experimental** macOS 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: 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 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 - 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. To use, download and extract, and install the core file in RetroArch or your preferred frontend.

View file

@ -132,6 +132,7 @@ void AndroidHostInterface::SetUserDirectory()
void AndroidHostInterface::LoadSettings() void AndroidHostInterface::LoadSettings()
{ {
CommonHostInterface::LoadSettings(m_settings_interface); CommonHostInterface::LoadSettings(m_settings_interface);
CommonHostInterface::FixIncompatibleSettings(false);
CommonHostInterface::UpdateInputMap(m_settings_interface); CommonHostInterface::UpdateInputMap(m_settings_interface);
} }

View file

@ -583,6 +583,10 @@
<compatibility>No Issues</compatibility> <compatibility>No Issues</compatibility>
<version-tested>0.1-1308-g622e50fa</version-tested> <version-tested>0.1-1308-g622e50fa</version-tested>
</entry> </entry>
<entry code="SLUS-01144" compatibility="5" region="NTSC-U" title="Bugs Bunny &amp; Taz - Time Busters (USA) (En,Fr,Es)">
<compatibility>No Issues</compatibility>
<version-tested>0.1-1529-ga895c027</version-tested>
</entry>
<entry code="SLUS-00838" compatibility="5" region="NTSC-U" title="Bugs Bunny - Lost in Time (USA) (En,Fr,Es)"> <entry code="SLUS-00838" compatibility="5" region="NTSC-U" title="Bugs Bunny - Lost in Time (USA) (En,Fr,Es)">
<compatibility>No Issues</compatibility> <compatibility>No Issues</compatibility>
<version-tested>0.1-1308-g622e50fa</version-tested> <version-tested>0.1-1308-g622e50fa</version-tested>
@ -591,6 +595,14 @@
<compatibility>No Issues</compatibility> <compatibility>No Issues</compatibility>
<version-tested>0.1-1115-g0b261e8</version-tested> <version-tested>0.1-1115-g0b261e8</version-tested>
</entry> </entry>
<entry code="SCUS-94180" compatibility="5" region="NTSC-U" title="Bushido Blade (USA)">
<compatibility>No Issues</compatibility>
<version-tested>0.1-1529-ga895c027</version-tested>
</entry>
<entry code="SLUS-00663" compatibility="5" region="NTSC-U" title="Bushido Blade 2 (USA)">
<compatibility>No Issues</compatibility>
<version-tested>0.1-1529-ga895c027</version-tested>
</entry>
<entry code="SCUS-94263" compatibility="5" region="NTSC-U" title="Bust A Groove (USA)"> <entry code="SCUS-94263" compatibility="5" region="NTSC-U" title="Bust A Groove (USA)">
<compatibility>No Issues</compatibility> <compatibility>No Issues</compatibility>
<upscaling-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.</upscaling-issues> <upscaling-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.</upscaling-issues>
@ -1314,11 +1326,40 @@ Tetris with Card Captor Sakura (Japan)
<compatibility>No Issues</compatibility> <compatibility>No Issues</compatibility>
<version-tested>0.1-986-gfc911de1</version-tested> <version-tested>0.1-986-gfc911de1</version-tested>
</entry> </entry>
<entry code="SCUS-94242" compatibility="4" region="NTSC-U" title="Interactive CD Sampler Disc 6 (USA)">
<compatibility>Graphical/Audio Issues</compatibility>
<version-tested>0.1-1530-g6d75f42e</version-tested>
<comments>Various graphical errors in menu (Issue #772).</comments>
</entry>
<entry code="PBPX-95011" compatibility="5" region="NTSC-U" title="Interactive CD Sampler Disc Vol. 10 (USA)">
<compatibility>No Issues</compatibility>
<version-tested>0.1-1530-g6d75f42e</version-tested>
</entry>
<entry code="SCUS-94463" compatibility="5" region="NTSC-U" title="Interactive CD Sampler Disc Vol. 11 (USA)">
<compatibility>No Issues</compatibility>
<version-tested>0.1-1530-g6d75f42e</version-tested>
</entry>
<entry code="PBPX-95010" compatibility="5" region="NTSC-U" title="Interactive CD Sampler Disc Vol. 9 (USA)">
<compatibility>No Issues</compatibility>
<version-tested>0.1-1530-g6d75f42e</version-tested>
</entry>
<entry code="PBPX-95002" compatibility="3" region="NTSC-U" title="Interactive CD Sampler Disc Volume 4 (USA)"> <entry code="PBPX-95002" compatibility="3" region="NTSC-U" title="Interactive CD Sampler Disc Volume 4 (USA)">
<compatibility>Crashes In-Game</compatibility> <compatibility>Crashes In-Game</compatibility>
<version-tested>0.1-1490-g76978986</version-tested> <version-tested>0.1-1490-g76978986</version-tested>
<comments>Hangs when choosing Armored core (Issue #751).</comments> <comments>Hangs when choosing Armored core (Issue #751).</comments>
</entry> </entry>
<entry code="PBPX-95006" compatibility="5" region="NTSC-U" title="Interactive CD Sampler Disc Volume 7 (USA)">
<compatibility>No Issues</compatibility>
<version-tested>0.1-1530-g6d75f42e</version-tested>
</entry>
<entry code="PBPX-95009" compatibility="5" region="NTSC-U" title="Interactive CD Sampler Disc Volume 8 (USA)">
<compatibility>No Issues</compatibility>
<version-tested>0.1-1530-g6d75f42e</version-tested>
</entry>
<entry code="PBPX-95003" compatibility="5" region="NTSC-U" title="Interactive CD Sampler Disk Volume 5 (USA)">
<compatibility>No Issues</compatibility>
<version-tested>0.1-1530-g6d75f42e</version-tested>
</entry>
<entry code="SCUS-94957" compatibility="5" region="NTSC-U" title="Interactive CD Sampler Pack Volume 2 (USA)"> <entry code="SCUS-94957" compatibility="5" region="NTSC-U" title="Interactive CD Sampler Pack Volume 2 (USA)">
<compatibility>No Issues</compatibility> <compatibility>No Issues</compatibility>
<version-tested>0.1-1490-g76978986</version-tested> <version-tested>0.1-1490-g76978986</version-tested>
@ -1623,6 +1664,10 @@ Tetris with Card Captor Sakura (Japan)
<entry code="SLES-01485" compatibility="5" region="PAL" title="Mega Man Legends"> <entry code="SLES-01485" compatibility="5" region="PAL" title="Mega Man Legends">
<compatibility>No Issues</compatibility> <compatibility>No Issues</compatibility>
</entry> </entry>
<entry code="SLUS-01140" compatibility="5" region="NTSC-U" title="Mega Man Legends 2 (USA)">
<compatibility>No Issues</compatibility>
<version-tested>0.1-1529-ga895c027</version-tested>
</entry>
<entry code="SLES-00503" compatibility="5" region="PAL" title="Mega Man X3"> <entry code="SLES-00503" compatibility="5" region="PAL" title="Mega Man X3">
<compatibility>No Issues</compatibility> <compatibility>No Issues</compatibility>
<version-tested>0.1-826-g712168c</version-tested> <version-tested>0.1-826-g712168c</version-tested>
@ -2006,6 +2051,30 @@ Tetris with Card Captor Sakura (Japan)
<compatibility>No Issues</compatibility> <compatibility>No Issues</compatibility>
<version-tested>0.1-1445-ge5c46a54</version-tested> <version-tested>0.1-1445-ge5c46a54</version-tested>
</entry> </entry>
<entry code="SCUS-94482" compatibility="5" region="NTSC-U" title="PlayStation Demo Disc - Shock Your System! (USA) (SCUS-94482)">
<compatibility>No Issues</compatibility>
<version-tested>0.1-1530-g6d75f42e</version-tested>
</entry>
<entry code="SCUS-94618" compatibility="5" region="NTSC-U" title="PlayStation Demo Disc Version 1.3 (USA)">
<compatibility>No Issues</compatibility>
<version-tested>0.1-1530-g6d75f42e</version-tested>
</entry>
<entry code="SCUS-94650" compatibility="5" region="NTSC-U" title="PlayStation Demo Disc Version 1.4 (USA)">
<compatibility>No Issues</compatibility>
<version-tested>0.1-1530-g6d75f42e</version-tested>
</entry>
<entry code="SCUS-94674" compatibility="5" region="NTSC-U" title="PlayStation Demo Disc Version 1.5 (USA)">
<compatibility>No Issues</compatibility>
<version-tested>0.1-1530-g6d75f42e</version-tested>
</entry>
<entry code="SCUS-94950" compatibility="5" region="NTSC-U" title="PlayStation Developer's Demo Disc (USA) (SCUS-94950)">
<compatibility>No Issues</compatibility>
<version-tested>0.1-1530-g6d75f42e</version-tested>
</entry>
<entry code="SCUS-94954" compatibility="5" region="NTSC-U" title="PlayStation Developer's Demo Disc (USA) (SCUS-94954)">
<compatibility>No Issues</compatibility>
<version-tested>0.1-1530-g6d75f42e</version-tested>
</entry>
<entry code="SLPS-01360" compatibility="5" region="NTSC-J" title="Pocket Fighter (Japan)"> <entry code="SLPS-01360" compatibility="5" region="NTSC-J" title="Pocket Fighter (Japan)">
<compatibility>No Issues</compatibility> <compatibility>No Issues</compatibility>
<version-tested>0.1-884-g096ed21</version-tested> <version-tested>0.1-884-g096ed21</version-tested>
@ -2035,6 +2104,10 @@ Tetris with Card Captor Sakura (Japan)
<compatibility>No Issues</compatibility> <compatibility>No Issues</compatibility>
<version-tested>0.1-774-g5a1b008</version-tested> <version-tested>0.1-774-g5a1b008</version-tested>
</entry> </entry>
<entry code="SLUS-01423" compatibility="5" region="NTSC-U" title="Powerpuff Girls, The - Chemical X-Traction (USA) (En,Es)">
<compatibility>No Issues</compatibility>
<version-tested>0.1-1529-ga895c027</version-tested>
</entry>
<entry code="SLUS-00102" compatibility="5" region="NTSC-U" title="Powerslave"> <entry code="SLUS-00102" compatibility="5" region="NTSC-U" title="Powerslave">
<compatibility>No Issues</compatibility> <compatibility>No Issues</compatibility>
<upscaling-issues>2D elements on title screen</upscaling-issues> <upscaling-issues>2D elements on title screen</upscaling-issues>
@ -2369,6 +2442,14 @@ Tetris with Card Captor Sakura (Japan)
<entry code="SLPS-03444" compatibility="5" region="NTSC-J" title="Simple Character 2000 Series Vol. 08 - Kagaku Ninjatai Gatchaman - The Shooting (Japan)"> <entry code="SLPS-03444" compatibility="5" region="NTSC-J" title="Simple Character 2000 Series Vol. 08 - Kagaku Ninjatai Gatchaman - The Shooting (Japan)">
<compatibility>No Issues</compatibility> <compatibility>No Issues</compatibility>
</entry> </entry>
<entry code="SLUS-01227" compatibility="5" region="NTSC-U" title="Simpsons Wrestling, The (USA)">
<compatibility>No Issues</compatibility>
<version-tested>0.1-1529-ga895c027</version-tested>
</entry>
<entry code="SLUS-00209" compatibility="5" region="NTSC-U" title="Skeleton Warriors (USA)">
<compatibility>No Issues</compatibility>
<version-tested>0.1-1529-ga895c027</version-tested>
</entry>
<entry code="SLUS-00601" compatibility="5" region="NTSC-U" title="Skullmonkeys (USA)"> <entry code="SLUS-00601" compatibility="5" region="NTSC-U" title="Skullmonkeys (USA)">
<compatibility>No Issues</compatibility> <compatibility>No Issues</compatibility>
<version-tested>0.1-1308-g622e50fa</version-tested> <version-tested>0.1-1308-g622e50fa</version-tested>

View file

@ -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

View file

@ -267,10 +267,12 @@ public:
{ {
#if WIN32 #if WIN32
// delete the temporary file // delete the temporary file
if (!DeleteFileA(m_temporaryFileName.c_str())) if (!DeleteFileW(StringUtil::UTF8StringToWideString(m_temporaryFileName).c_str()))
{
Log_WarningPrintf( Log_WarningPrintf(
"AtomicUpdatedFileByteStream::~AtomicUpdatedFileByteStream(): Failed to delete temporary file '%s'", "AtomicUpdatedFileByteStream::~AtomicUpdatedFileByteStream(): Failed to delete temporary file '%s'",
m_temporaryFileName.c_str()); m_temporaryFileName.c_str());
}
#else #else
// delete the temporary file // delete the temporary file
if (remove(m_temporaryFileName.c_str()) < 0) if (remove(m_temporaryFileName.c_str()) < 0)
@ -308,7 +310,8 @@ public:
#ifdef WIN32 #ifdef WIN32
// move the atomic file name to the original file name // 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'", Log_WarningPrintf("AtomicUpdatedFileByteStream::Commit(): Failed to rename temporary file '%s' to '%s'",
m_temporaryFileName.c_str(), m_originalFileName.c_str()); m_temporaryFileName.c_str(), m_originalFileName.c_str());

View file

@ -15,6 +15,13 @@
#endif #endif
#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 // unreferenced parameter macro
#ifndef UNREFERENCED_VARIABLE #ifndef UNREFERENCED_VARIABLE
#if defined(_MSC_VER) #if defined(_MSC_VER)

View file

@ -26,6 +26,8 @@ add_library(core
dma.h dma.h
game_list.cpp game_list.cpp
game_list.h game_list.h
game_settings.cpp
game_settings.h
gpu.cpp gpu.cpp
gpu.h gpu.h
gpu_commands.cpp gpu_commands.cpp
@ -96,7 +98,7 @@ set(RECOMPILER_SRCS
target_include_directories(core PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/..") target_include_directories(core PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/..")
target_include_directories(core PUBLIC "${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) target_link_libraries(core PRIVATE glad stb)
if(WIN32) if(WIN32)

View file

@ -53,8 +53,12 @@ bool AnalogController::DoState(StateWrapper& sw)
if (old_analog_mode != m_analog_mode) if (old_analog_mode != m_analog_mode)
{ {
g_host_interface->AddFormattedOSDMessage(5.0f, "Controller %u switched to %s mode.", m_index + 1u, g_host_interface->AddFormattedOSDMessage(
m_analog_mode ? "analog" : "digital"); 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; return true;
@ -95,8 +99,13 @@ void AnalogController::SetButtonState(Button button, bool pressed)
{ {
if (m_analog_locked) if (m_analog_locked)
{ {
g_host_interface->AddFormattedOSDMessage(5.0f, "Controller %u is locked to %s mode by the game.", m_index + 1u, g_host_interface->AddFormattedOSDMessage(
m_analog_mode ? "analog" : "digital"); 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 else
{ {
@ -155,8 +164,11 @@ void AnalogController::SetAnalogMode(bool enabled)
return; return;
Log_InfoPrintf("Controller %u switched to %s mode.", m_index + 1u, enabled ? "analog" : "digital"); 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, g_host_interface->AddFormattedOSDMessage(
enabled ? "analog" : "digital"); 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; m_analog_mode = enabled;
} }

View file

@ -1917,6 +1917,17 @@ void CDROM::DoSectorRead()
// TODO: Error handling // TODO: Error handling
const CDImage::SubChannelQ& subq = m_reader.GetSectorSubQ(); 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) 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()); 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()); 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) if (is_data_sector && m_drive_state == DriveState::Reading)
{ {
ProcessDataSector(m_reader.GetSectorBuffer().data(), subq); ProcessDataSector(m_reader.GetSectorBuffer().data(), subq);

View file

@ -60,6 +60,7 @@
<ClCompile Include="cpu_types.cpp" /> <ClCompile Include="cpu_types.cpp" />
<ClCompile Include="digital_controller.cpp" /> <ClCompile Include="digital_controller.cpp" />
<ClCompile Include="game_list.cpp" /> <ClCompile Include="game_list.cpp" />
<ClCompile Include="game_settings.cpp" />
<ClCompile Include="gpu_commands.cpp" /> <ClCompile Include="gpu_commands.cpp" />
<ClCompile Include="gpu_hw_d3d11.cpp" /> <ClCompile Include="gpu_hw_d3d11.cpp" />
<ClCompile Include="gpu_hw_shadergen.cpp" /> <ClCompile Include="gpu_hw_shadergen.cpp" />
@ -107,6 +108,7 @@
<ClInclude Include="cpu_recompiler_types.h" /> <ClInclude Include="cpu_recompiler_types.h" />
<ClInclude Include="digital_controller.h" /> <ClInclude Include="digital_controller.h" />
<ClInclude Include="game_list.h" /> <ClInclude Include="game_list.h" />
<ClInclude Include="game_settings.h" />
<ClInclude Include="gpu_hw_d3d11.h" /> <ClInclude Include="gpu_hw_d3d11.h" />
<ClInclude Include="gpu_hw_shadergen.h" /> <ClInclude Include="gpu_hw_shadergen.h" />
<ClInclude Include="gpu_hw_vulkan.h" /> <ClInclude Include="gpu_hw_vulkan.h" />
@ -142,6 +144,12 @@
<ClInclude Include="types.h" /> <ClInclude Include="types.h" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\..\dep\imgui\imgui.vcxproj">
<Project>{bb08260f-6fbc-46af-8924-090ee71360c6}</Project>
</ProjectReference>
<ProjectReference Include="..\..\dep\simpleini\simpleini.vcxproj">
<Project>{3773f4cc-614e-4028-8595-22e08ca649e3}</Project>
</ProjectReference>
<ProjectReference Include="..\..\dep\stb\stb.vcxproj"> <ProjectReference Include="..\..\dep\stb\stb.vcxproj">
<Project>{ed601289-ac1a-46b8-a8ed-17db9eb73423}</Project> <Project>{ed601289-ac1a-46b8-a8ed-17db9eb73423}</Project>
</ProjectReference> </ProjectReference>
@ -299,7 +307,7 @@
<PreprocessorDefinitions>WITH_RECOMPILER=1;_CRT_SECURE_NO_WARNINGS;WIN32;_DEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions> <PreprocessorDefinitions>WITH_RECOMPILER=1;_CRT_SECURE_NO_WARNINGS;WIN32;_DEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<SDLCheck>true</SDLCheck> <SDLCheck>true</SDLCheck>
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat> <DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
<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\zlib\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> <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)</AdditionalIncludeDirectories>
<MultiProcessorCompilation>true</MultiProcessorCompilation> <MultiProcessorCompilation>true</MultiProcessorCompilation>
<MinimalRebuild>false</MinimalRebuild> <MinimalRebuild>false</MinimalRebuild>
<LanguageStandard>stdcpp17</LanguageStandard> <LanguageStandard>stdcpp17</LanguageStandard>
@ -325,7 +333,7 @@
<PreprocessorDefinitions>WITH_RECOMPILER=1;_CRT_SECURE_NO_WARNINGS;WIN32;_DEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions> <PreprocessorDefinitions>WITH_RECOMPILER=1;_CRT_SECURE_NO_WARNINGS;WIN32;_DEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<SDLCheck>true</SDLCheck> <SDLCheck>true</SDLCheck>
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat> <DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
<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\zlib\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> <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)</AdditionalIncludeDirectories>
<MultiProcessorCompilation>true</MultiProcessorCompilation> <MultiProcessorCompilation>true</MultiProcessorCompilation>
<MinimalRebuild>false</MinimalRebuild> <MinimalRebuild>false</MinimalRebuild>
<LanguageStandard>stdcpp17</LanguageStandard> <LanguageStandard>stdcpp17</LanguageStandard>
@ -351,7 +359,7 @@
<PreprocessorDefinitions>WITH_RECOMPILER=1;_ITERATOR_DEBUG_LEVEL=1;_CRT_SECURE_NO_WARNINGS;WIN32;_DEBUGFAST;_DEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions> <PreprocessorDefinitions>WITH_RECOMPILER=1;_ITERATOR_DEBUG_LEVEL=1;_CRT_SECURE_NO_WARNINGS;WIN32;_DEBUGFAST;_DEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<SDLCheck>true</SDLCheck> <SDLCheck>true</SDLCheck>
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat> <DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
<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\zlib\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> <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)</AdditionalIncludeDirectories>
<BasicRuntimeChecks>Default</BasicRuntimeChecks> <BasicRuntimeChecks>Default</BasicRuntimeChecks>
<MultiProcessorCompilation>true</MultiProcessorCompilation> <MultiProcessorCompilation>true</MultiProcessorCompilation>
<MinimalRebuild>false</MinimalRebuild> <MinimalRebuild>false</MinimalRebuild>
@ -380,7 +388,7 @@
<PreprocessorDefinitions>WITH_RECOMPILER=1;_ITERATOR_DEBUG_LEVEL=1;_CRT_SECURE_NO_WARNINGS;WIN32;_DEBUGFAST;_DEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions> <PreprocessorDefinitions>WITH_RECOMPILER=1;_ITERATOR_DEBUG_LEVEL=1;_CRT_SECURE_NO_WARNINGS;WIN32;_DEBUGFAST;_DEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<SDLCheck>true</SDLCheck> <SDLCheck>true</SDLCheck>
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat> <DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
<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\zlib\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> <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)</AdditionalIncludeDirectories>
<BasicRuntimeChecks>Default</BasicRuntimeChecks> <BasicRuntimeChecks>Default</BasicRuntimeChecks>
<MultiProcessorCompilation>true</MultiProcessorCompilation> <MultiProcessorCompilation>true</MultiProcessorCompilation>
<MinimalRebuild>false</MinimalRebuild> <MinimalRebuild>false</MinimalRebuild>
@ -408,7 +416,7 @@
<Optimization>MaxSpeed</Optimization> <Optimization>MaxSpeed</Optimization>
<IntrinsicFunctions>true</IntrinsicFunctions> <IntrinsicFunctions>true</IntrinsicFunctions>
<PreprocessorDefinitions>WITH_RECOMPILER=1;_CRT_SECURE_NO_WARNINGS;WIN32;NDEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions> <PreprocessorDefinitions>WITH_RECOMPILER=1;_CRT_SECURE_NO_WARNINGS;WIN32;NDEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<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\zlib\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> <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)</AdditionalIncludeDirectories>
<MultiProcessorCompilation>true</MultiProcessorCompilation> <MultiProcessorCompilation>true</MultiProcessorCompilation>
<WholeProgramOptimization>false</WholeProgramOptimization> <WholeProgramOptimization>false</WholeProgramOptimization>
<LanguageStandard>stdcpp17</LanguageStandard> <LanguageStandard>stdcpp17</LanguageStandard>
@ -435,7 +443,7 @@
<Optimization>MaxSpeed</Optimization> <Optimization>MaxSpeed</Optimization>
<IntrinsicFunctions>true</IntrinsicFunctions> <IntrinsicFunctions>true</IntrinsicFunctions>
<PreprocessorDefinitions>WITH_RECOMPILER=1;_CRT_SECURE_NO_WARNINGS;WIN32;NDEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions> <PreprocessorDefinitions>WITH_RECOMPILER=1;_CRT_SECURE_NO_WARNINGS;WIN32;NDEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<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\zlib\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> <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)</AdditionalIncludeDirectories>
<MultiProcessorCompilation>true</MultiProcessorCompilation> <MultiProcessorCompilation>true</MultiProcessorCompilation>
<WholeProgramOptimization>true</WholeProgramOptimization> <WholeProgramOptimization>true</WholeProgramOptimization>
<LanguageStandard>stdcpp17</LanguageStandard> <LanguageStandard>stdcpp17</LanguageStandard>
@ -463,7 +471,7 @@
<Optimization>MaxSpeed</Optimization> <Optimization>MaxSpeed</Optimization>
<IntrinsicFunctions>true</IntrinsicFunctions> <IntrinsicFunctions>true</IntrinsicFunctions>
<PreprocessorDefinitions>WITH_RECOMPILER=1;_CRT_SECURE_NO_WARNINGS;WIN32;NDEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions> <PreprocessorDefinitions>WITH_RECOMPILER=1;_CRT_SECURE_NO_WARNINGS;WIN32;NDEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<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\zlib\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> <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)</AdditionalIncludeDirectories>
<MultiProcessorCompilation>true</MultiProcessorCompilation> <MultiProcessorCompilation>true</MultiProcessorCompilation>
<WholeProgramOptimization>false</WholeProgramOptimization> <WholeProgramOptimization>false</WholeProgramOptimization>
<LanguageStandard>stdcpp17</LanguageStandard> <LanguageStandard>stdcpp17</LanguageStandard>
@ -490,7 +498,7 @@
<Optimization>MaxSpeed</Optimization> <Optimization>MaxSpeed</Optimization>
<IntrinsicFunctions>true</IntrinsicFunctions> <IntrinsicFunctions>true</IntrinsicFunctions>
<PreprocessorDefinitions>WITH_RECOMPILER=1;_CRT_SECURE_NO_WARNINGS;WIN32;NDEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions> <PreprocessorDefinitions>WITH_RECOMPILER=1;_CRT_SECURE_NO_WARNINGS;WIN32;NDEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<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\zlib\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> <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)</AdditionalIncludeDirectories>
<MultiProcessorCompilation>true</MultiProcessorCompilation> <MultiProcessorCompilation>true</MultiProcessorCompilation>
<WholeProgramOptimization>true</WholeProgramOptimization> <WholeProgramOptimization>true</WholeProgramOptimization>
<LanguageStandard>stdcpp17</LanguageStandard> <LanguageStandard>stdcpp17</LanguageStandard>

View file

@ -47,6 +47,7 @@
<ClCompile Include="resources.cpp" /> <ClCompile Include="resources.cpp" />
<ClCompile Include="host_interface_progress_callback.cpp" /> <ClCompile Include="host_interface_progress_callback.cpp" />
<ClCompile Include="pgxp.cpp" /> <ClCompile Include="pgxp.cpp" />
<ClCompile Include="game_settings.cpp" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ClInclude Include="types.h" /> <ClInclude Include="types.h" />
@ -97,5 +98,6 @@
<ClInclude Include="gte_types.h" /> <ClInclude Include="gte_types.h" />
<ClInclude Include="pgxp.h" /> <ClInclude Include="pgxp.h" />
<ClInclude Include="cpu_core_private.h" /> <ClInclude Include="cpu_core_private.h" />
<ClInclude Include="game_settings.h" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View file

@ -110,7 +110,8 @@ void Shutdown()
#endif #endif
} }
void Execute() template<PGXPMode pgxp_mode>
static void ExecuteImpl()
{ {
CodeBlockKey next_block_key; CodeBlockKey next_block_key;
@ -157,7 +158,7 @@ void Execute()
} }
else else
{ {
InterpretCachedBlock(*block); InterpretCachedBlock<pgxp_mode>(*block);
} }
if (g_state.pending_ticks >= g_state.downcount) if (g_state.pending_ticks >= g_state.downcount)
@ -212,6 +213,21 @@ void Execute()
g_state.regs.npc = g_state.regs.pc; g_state.regs.npc = g_state.regs.pc;
} }
void Execute()
{
if (g_settings.gpu_pgxp_enable)
{
if (g_settings.gpu_pgxp_cpu)
ExecuteImpl<PGXPMode::CPU>();
else
ExecuteImpl<PGXPMode::Memory>();
}
else
{
ExecuteImpl<PGXPMode::Disabled>();
}
}
#ifdef WITH_RECOMPILER #ifdef WITH_RECOMPILER
void ExecuteRecompiler() void ExecuteRecompiler()

View file

@ -96,6 +96,7 @@ void SetUseRecompiler(bool enable);
/// Invalidates all blocks which are in the range of the specified code page. /// Invalidates all blocks which are in the range of the specified code page.
void InvalidateBlocksWithPageIndex(u32 page_index); void InvalidateBlocksWithPageIndex(u32 page_index);
template<PGXPMode pgxp_mode>
void InterpretCachedBlock(const CodeBlock& block); void InterpretCachedBlock(const CodeBlock& block);
void InterpretUncachedBlock(); void InterpretUncachedBlock();

View file

@ -15,19 +15,9 @@ Log_SetChannel(CPU::Core);
namespace CPU { namespace CPU {
/// Sets the PC and flushes the pipeline.
static void SetPC(u32 new_pc); static void SetPC(u32 new_pc);
// Updates load delays - call after each instruction
static void UpdateLoadDelay(); static void UpdateLoadDelay();
// Fetches the instruction at m_regs.npc
static void ExecuteInstruction();
static void ExecuteCop0Instruction();
static void ExecuteCop2Instruction();
static void Branch(u32 target); static void Branch(u32 target);
// clears pipeline of load/branch delays
static void FlushPipeline(); static void FlushPipeline();
State g_state; State g_state;
@ -139,14 +129,14 @@ bool DoState(StateWrapper& sw)
return !sw.HasError(); return !sw.HasError();
} }
void SetPC(u32 new_pc) ALWAYS_INLINE_RELEASE void SetPC(u32 new_pc)
{ {
DebugAssert(Common::IsAlignedPow2(new_pc, 4)); DebugAssert(Common::IsAlignedPow2(new_pc, 4));
g_state.regs.npc = new_pc; g_state.regs.npc = new_pc;
FlushPipeline(); FlushPipeline();
} }
void Branch(u32 target) ALWAYS_INLINE_RELEASE void Branch(u32 target)
{ {
if (!Common::IsAlignedPow2(target, 4)) if (!Common::IsAlignedPow2(target, 4))
{ {
@ -240,7 +230,7 @@ void ClearExternalInterrupt(u8 bit)
g_state.cop0_regs.cause.Ip &= static_cast<u8>(~(1u << bit)); g_state.cop0_regs.cause.Ip &= static_cast<u8>(~(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 // the old value is needed in case the delay slot instruction overwrites the same register
if (g_state.load_delay_reg != Reg::count) if (g_state.load_delay_reg != Reg::count)
@ -251,7 +241,7 @@ void UpdateLoadDelay()
g_state.next_load_delay_reg = Reg::count; g_state.next_load_delay_reg = Reg::count;
} }
void FlushPipeline() ALWAYS_INLINE_RELEASE static void FlushPipeline()
{ {
// loads are flushed // loads are flushed
g_state.next_load_delay_reg = Reg::count; g_state.next_load_delay_reg = Reg::count;
@ -275,12 +265,12 @@ void FlushPipeline()
g_state.current_instruction_was_branch_taken = false; 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<u8>(rs)]; return g_state.regs.r[static_cast<u8>(rs)];
} }
ALWAYS_INLINE void WriteReg(Reg rd, u32 value) ALWAYS_INLINE static void WriteReg(Reg rd, u32 value)
{ {
g_state.regs.r[static_cast<u8>(rd)] = value; g_state.regs.r[static_cast<u8>(rd)] = value;
g_state.load_delay_reg = (rd == g_state.load_delay_reg) ? Reg::count : g_state.load_delay_reg; 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; 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); Assert(g_state.next_load_delay_reg == Reg::count);
if (rd == Reg::zero) if (rd == Reg::zero)
@ -304,7 +294,7 @@ static void WriteRegDelayed(Reg rd, u32 value)
g_state.next_load_delay_value = value; g_state.next_load_delay_value = value;
} }
static std::optional<u32> ReadCop0Reg(Cop0Reg reg) ALWAYS_INLINE_RELEASE static std::optional<u32> ReadCop0Reg(Cop0Reg reg)
{ {
switch (reg) switch (reg)
{ {
@ -347,7 +337,7 @@ static std::optional<u32> ReadCop0Reg(Cop0Reg reg)
} }
} }
static void WriteCop0Reg(Cop0Reg reg, u32 value) ALWAYS_INLINE_RELEASE static void WriteCop0Reg(Cop0Reg reg, u32 value)
{ {
switch (reg) switch (reg)
{ {
@ -431,12 +421,12 @@ static void LogInstruction(u32 bits, u32 pc, Registers* regs)
WriteToExecutionLog("%08x: %08x %s\n", pc, bits, instr.GetCharArray()); 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; 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; 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() template<PGXPMode pgxp_mode>
{ ALWAYS_INLINE_RELEASE static void ExecuteInstruction()
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()
{ {
const Instruction inst = g_state.current_instruction; const Instruction inst = g_state.current_instruction;
@ -525,14 +470,6 @@ void ExecuteInstruction()
} }
#endif #endif
#if 0
if (g_state.m_current_instruction_pc == 0x8002bf50)
{
TRACE_EXECUTION = true;
__debugbreak();
}
#endif
#ifdef _DEBUG #ifdef _DEBUG
if (TRACE_EXECUTION) if (TRACE_EXECUTION)
PrintInstruction(inst.bits, g_state.current_instruction_pc, &g_state.regs); 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); LogInstruction(inst.bits, g_state.current_instruction_pc, &g_state.regs);
#endif #endif
// Skip nops. Makes PGXP-CPU quicker, but also the regular interpreter.
if (inst.bits == 0)
return;
switch (inst.op) switch (inst.op)
{ {
case InstructionOp::funct: case InstructionOp::funct:
@ -549,6 +490,9 @@ void ExecuteInstruction()
case InstructionFunct::sll: case InstructionFunct::sll:
{ {
const u32 new_value = ReadReg(inst.r.rt) << inst.r.shamt; 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); WriteReg(inst.r.rd, new_value);
} }
break; break;
@ -556,6 +500,9 @@ void ExecuteInstruction()
case InstructionFunct::srl: case InstructionFunct::srl:
{ {
const u32 new_value = ReadReg(inst.r.rt) >> inst.r.shamt; 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); WriteReg(inst.r.rd, new_value);
} }
break; break;
@ -563,6 +510,9 @@ void ExecuteInstruction()
case InstructionFunct::sra: case InstructionFunct::sra:
{ {
const u32 new_value = static_cast<u32>(static_cast<s32>(ReadReg(inst.r.rt)) >> inst.r.shamt); const u32 new_value = static_cast<u32>(static_cast<s32>(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); WriteReg(inst.r.rd, new_value);
} }
break; break;
@ -571,6 +521,9 @@ void ExecuteInstruction()
{ {
const u32 shift_amount = ReadReg(inst.r.rs) & UINT32_C(0x1F); const u32 shift_amount = ReadReg(inst.r.rs) & UINT32_C(0x1F);
const u32 new_value = ReadReg(inst.r.rt) << shift_amount; 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); WriteReg(inst.r.rd, new_value);
} }
break; break;
@ -579,6 +532,9 @@ void ExecuteInstruction()
{ {
const u32 shift_amount = ReadReg(inst.r.rs) & UINT32_C(0x1F); const u32 shift_amount = ReadReg(inst.r.rs) & UINT32_C(0x1F);
const u32 new_value = ReadReg(inst.r.rt) >> shift_amount; 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); WriteReg(inst.r.rd, new_value);
} }
break; break;
@ -587,6 +543,9 @@ void ExecuteInstruction()
{ {
const u32 shift_amount = ReadReg(inst.r.rs) & UINT32_C(0x1F); const u32 shift_amount = ReadReg(inst.r.rs) & UINT32_C(0x1F);
const u32 new_value = static_cast<u32>(static_cast<s32>(ReadReg(inst.r.rt)) >> shift_amount); const u32 new_value = static_cast<u32>(static_cast<s32>(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); WriteReg(inst.r.rd, new_value);
} }
break; break;
@ -594,6 +553,9 @@ void ExecuteInstruction()
case InstructionFunct::and_: case InstructionFunct::and_:
{ {
const u32 new_value = ReadReg(inst.r.rs) & ReadReg(inst.r.rt); 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); WriteReg(inst.r.rd, new_value);
} }
break; break;
@ -601,6 +563,9 @@ void ExecuteInstruction()
case InstructionFunct::or_: case InstructionFunct::or_:
{ {
const u32 new_value = ReadReg(inst.r.rs) | ReadReg(inst.r.rt); 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); WriteReg(inst.r.rd, new_value);
} }
break; break;
@ -608,6 +573,9 @@ void ExecuteInstruction()
case InstructionFunct::xor_: case InstructionFunct::xor_:
{ {
const u32 new_value = ReadReg(inst.r.rs) ^ ReadReg(inst.r.rt); 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); WriteReg(inst.r.rd, new_value);
} }
break; break;
@ -615,6 +583,9 @@ void ExecuteInstruction()
case InstructionFunct::nor: case InstructionFunct::nor:
{ {
const u32 new_value = ~(ReadReg(inst.r.rs) | ReadReg(inst.r.rt)); 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); WriteReg(inst.r.rd, new_value);
} }
break; break;
@ -630,6 +601,9 @@ void ExecuteInstruction()
return; 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); WriteReg(inst.r.rd, new_value);
} }
break; break;
@ -637,6 +611,9 @@ void ExecuteInstruction()
case InstructionFunct::addu: case InstructionFunct::addu:
{ {
const u32 new_value = ReadReg(inst.r.rs) + ReadReg(inst.r.rt); 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); WriteReg(inst.r.rd, new_value);
} }
break; break;
@ -652,6 +629,9 @@ void ExecuteInstruction()
return; 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); WriteReg(inst.r.rd, new_value);
} }
break; break;
@ -659,6 +639,9 @@ void ExecuteInstruction()
case InstructionFunct::subu: case InstructionFunct::subu:
{ {
const u32 new_value = ReadReg(inst.r.rs) - ReadReg(inst.r.rt); 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); WriteReg(inst.r.rd, new_value);
} }
break; break;
@ -666,6 +649,9 @@ void ExecuteInstruction()
case InstructionFunct::slt: case InstructionFunct::slt:
{ {
const u32 result = BoolToUInt32(static_cast<s32>(ReadReg(inst.r.rs)) < static_cast<s32>(ReadReg(inst.r.rt))); const u32 result = BoolToUInt32(static_cast<s32>(ReadReg(inst.r.rs)) < static_cast<s32>(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); WriteReg(inst.r.rd, result);
} }
break; break;
@ -673,12 +659,18 @@ void ExecuteInstruction()
case InstructionFunct::sltu: case InstructionFunct::sltu:
{ {
const u32 result = BoolToUInt32(ReadReg(inst.r.rs) < ReadReg(inst.r.rt)); 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); WriteReg(inst.r.rd, result);
} }
break; break;
case InstructionFunct::mfhi: 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); WriteReg(inst.r.rd, g_state.regs.hi);
} }
break; break;
@ -686,12 +678,18 @@ void ExecuteInstruction()
case InstructionFunct::mthi: case InstructionFunct::mthi:
{ {
const u32 value = ReadReg(inst.r.rs); 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; g_state.regs.hi = value;
} }
break; break;
case InstructionFunct::mflo: 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); WriteReg(inst.r.rd, g_state.regs.lo);
} }
break; break;
@ -699,6 +697,9 @@ void ExecuteInstruction()
case InstructionFunct::mtlo: case InstructionFunct::mtlo:
{ {
const u32 value = ReadReg(inst.r.rs); 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; g_state.regs.lo = value;
} }
break; break;
@ -709,8 +710,12 @@ void ExecuteInstruction()
const u32 rhs = ReadReg(inst.r.rt); const u32 rhs = ReadReg(inst.r.rt);
const u64 result = const u64 result =
static_cast<u64>(static_cast<s64>(SignExtend64(lhs)) * static_cast<s64>(SignExtend64(rhs))); static_cast<u64>(static_cast<s64>(SignExtend64(lhs)) * static_cast<s64>(SignExtend64(rhs)));
g_state.regs.hi = Truncate32(result >> 32); g_state.regs.hi = Truncate32(result >> 32);
g_state.regs.lo = Truncate32(result); 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; break;
@ -719,6 +724,10 @@ void ExecuteInstruction()
const u32 lhs = ReadReg(inst.r.rs); const u32 lhs = ReadReg(inst.r.rs);
const u32 rhs = ReadReg(inst.r.rt); const u32 rhs = ReadReg(inst.r.rt);
const u64 result = ZeroExtend64(lhs) * ZeroExtend64(rhs); 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.hi = Truncate32(result >> 32);
g_state.regs.lo = Truncate32(result); g_state.regs.lo = Truncate32(result);
} }
@ -746,6 +755,9 @@ void ExecuteInstruction()
g_state.regs.lo = static_cast<u32>(num / denom); g_state.regs.lo = static_cast<u32>(num / denom);
g_state.regs.hi = static_cast<u32>(num % denom); g_state.regs.hi = static_cast<u32>(num % denom);
} }
if constexpr (pgxp_mode >= PGXPMode::CPU)
PGXP::CPU_DIV(inst.bits, g_state.regs.hi, g_state.regs.lo, num, denom);
} }
break; break;
@ -765,6 +777,9 @@ void ExecuteInstruction()
g_state.regs.lo = num / denom; g_state.regs.lo = num / denom;
g_state.regs.hi = 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; break;
@ -808,25 +823,44 @@ void ExecuteInstruction()
case InstructionOp::lui: 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; break;
case InstructionOp::andi: 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; break;
case InstructionOp::ori: 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; break;
case InstructionOp::xori: 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; break;
@ -841,19 +875,31 @@ void ExecuteInstruction()
return; return;
} }
if constexpr (pgxp_mode >= PGXPMode::CPU)
PGXP::CPU_ANDI(inst.bits, new_value, ReadReg(inst.i.rs));
WriteReg(inst.i.rt, new_value); WriteReg(inst.i.rt, new_value);
} }
break; break;
case InstructionOp::addiu: 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; break;
case InstructionOp::slti: case InstructionOp::slti:
{ {
const u32 result = BoolToUInt32(static_cast<s32>(ReadReg(inst.i.rs)) < static_cast<s32>(inst.i.imm_sext32())); const u32 result = BoolToUInt32(static_cast<s32>(ReadReg(inst.i.rs)) < static_cast<s32>(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); WriteReg(inst.i.rt, result);
} }
break; break;
@ -861,6 +907,10 @@ void ExecuteInstruction()
case InstructionOp::sltiu: case InstructionOp::sltiu:
{ {
const u32 result = BoolToUInt32(ReadReg(inst.i.rs) < inst.i.imm_sext32()); 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); WriteReg(inst.i.rt, result);
} }
break; break;
@ -876,7 +926,7 @@ void ExecuteInstruction()
WriteRegDelayed(inst.i.rt, sxvalue); WriteRegDelayed(inst.i.rt, sxvalue);
if (g_settings.gpu_pgxp_enable) if constexpr (pgxp_mode >= PGXPMode::Memory)
PGXP::CPU_LBx(inst.bits, sxvalue, addr); PGXP::CPU_LBx(inst.bits, sxvalue, addr);
} }
break; break;
@ -891,7 +941,7 @@ void ExecuteInstruction()
const u32 sxvalue = SignExtend32(value); const u32 sxvalue = SignExtend32(value);
WriteRegDelayed(inst.i.rt, sxvalue); WriteRegDelayed(inst.i.rt, sxvalue);
if (g_settings.gpu_pgxp_enable) if constexpr (pgxp_mode >= PGXPMode::Memory)
PGXP::CPU_LHx(inst.bits, sxvalue, addr); PGXP::CPU_LHx(inst.bits, sxvalue, addr);
} }
break; break;
@ -905,7 +955,7 @@ void ExecuteInstruction()
WriteRegDelayed(inst.i.rt, value); WriteRegDelayed(inst.i.rt, value);
if (g_settings.gpu_pgxp_enable) if constexpr (pgxp_mode >= PGXPMode::Memory)
PGXP::CPU_LW(inst.bits, value, addr); PGXP::CPU_LW(inst.bits, value, addr);
} }
break; break;
@ -920,7 +970,7 @@ void ExecuteInstruction()
const u32 zxvalue = ZeroExtend32(value); const u32 zxvalue = ZeroExtend32(value);
WriteRegDelayed(inst.i.rt, zxvalue); WriteRegDelayed(inst.i.rt, zxvalue);
if (g_settings.gpu_pgxp_enable) if constexpr (pgxp_mode >= PGXPMode::Memory)
PGXP::CPU_LBx(inst.bits, zxvalue, addr); PGXP::CPU_LBx(inst.bits, zxvalue, addr);
} }
break; break;
@ -935,7 +985,7 @@ void ExecuteInstruction()
const u32 zxvalue = ZeroExtend32(value); const u32 zxvalue = ZeroExtend32(value);
WriteRegDelayed(inst.i.rt, zxvalue); WriteRegDelayed(inst.i.rt, zxvalue);
if (g_settings.gpu_pgxp_enable) if constexpr (pgxp_mode >= PGXPMode::Memory)
PGXP::CPU_LHx(inst.bits, zxvalue, addr); PGXP::CPU_LHx(inst.bits, zxvalue, addr);
} }
break; break;
@ -966,7 +1016,7 @@ void ExecuteInstruction()
WriteRegDelayed(inst.i.rt, new_value); 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); PGXP::CPU_LW(inst.bits, new_value, addr);
} }
break; break;
@ -977,7 +1027,7 @@ void ExecuteInstruction()
const u8 value = Truncate8(ReadReg(inst.i.rt)); const u8 value = Truncate8(ReadReg(inst.i.rt));
WriteMemoryByte(addr, value); WriteMemoryByte(addr, value);
if (g_settings.gpu_pgxp_enable) if constexpr (pgxp_mode >= PGXPMode::Memory)
PGXP::CPU_SB(inst.bits, value, addr); PGXP::CPU_SB(inst.bits, value, addr);
} }
break; break;
@ -988,7 +1038,7 @@ void ExecuteInstruction()
const u16 value = Truncate16(ReadReg(inst.i.rt)); const u16 value = Truncate16(ReadReg(inst.i.rt));
WriteMemoryHalfWord(addr, value); WriteMemoryHalfWord(addr, value);
if (g_settings.gpu_pgxp_enable) if constexpr (pgxp_mode >= PGXPMode::Memory)
PGXP::CPU_SH(inst.bits, value, addr); PGXP::CPU_SH(inst.bits, value, addr);
} }
break; break;
@ -999,7 +1049,7 @@ void ExecuteInstruction()
const u32 value = ReadReg(inst.i.rt); const u32 value = ReadReg(inst.i.rt);
WriteMemoryWord(addr, value); WriteMemoryWord(addr, value);
if (g_settings.gpu_pgxp_enable) if constexpr (pgxp_mode >= PGXPMode::Memory)
PGXP::CPU_SW(inst.bits, value, addr); PGXP::CPU_SW(inst.bits, value, addr);
} }
break; break;
@ -1029,7 +1079,7 @@ void ExecuteInstruction()
WriteMemoryWord(aligned_addr, new_value); 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); PGXP::CPU_SW(inst.bits, new_value, addr);
} }
break; break;
@ -1114,88 +1164,6 @@ void ExecuteInstruction()
return; return;
} }
ExecuteCop0Instruction();
}
break;
case InstructionOp::cop2:
{
if (!g_state.cop0_regs.sr.CE2)
{
Log_WarningPrintf("Coprocessor 2 not enabled");
RaiseException(Exception::CpU);
return;
}
ExecuteCop2Instruction();
}
break;
case InstructionOp::lwc2:
{
if (!g_state.cop0_regs.sr.CE2)
{
Log_WarningPrintf("Coprocessor 2 not enabled");
RaiseException(Exception::CpU);
return;
}
const VirtualMemoryAddress addr = ReadReg(inst.i.rs) + inst.i.imm_sext32();
u32 value;
if (!ReadMemoryWord(addr, &value))
return;
GTE::WriteRegister(ZeroExtend32(static_cast<u8>(inst.i.rt.GetValue())), value);
if (g_settings.gpu_pgxp_enable)
PGXP::CPU_LWC2(inst.bits, value, addr);
}
break;
case InstructionOp::swc2:
{
if (!g_state.cop0_regs.sr.CE2)
{
Log_WarningPrintf("Coprocessor 2 not enabled");
RaiseException(Exception::CpU);
return;
}
const VirtualMemoryAddress addr = ReadReg(inst.i.rs) + inst.i.imm_sext32();
const u32 value = GTE::ReadRegister(ZeroExtend32(static_cast<u8>(inst.i.rt.GetValue())));
WriteMemoryWord(addr, value);
if (g_settings.gpu_pgxp_enable)
PGXP::CPU_SWC2(inst.bits, value, addr);
}
break;
// swc0/lwc0/cop1/cop3 are essentially no-ops
case InstructionOp::cop1:
case InstructionOp::cop3:
case InstructionOp::lwc0:
case InstructionOp::lwc1:
case InstructionOp::lwc3:
case InstructionOp::swc0:
case InstructionOp::swc1:
case InstructionOp::swc3:
{
}
break;
// everything else is reserved/invalid
default:
{
RaiseException(Exception::RI);
}
break;
}
}
void ExecuteCop0Instruction()
{
const Instruction inst = g_state.current_instruction;
if (inst.cop.IsCommonInstruction()) if (inst.cop.IsCommonInstruction())
{ {
switch (inst.cop.CommonOp()) switch (inst.cop.CommonOp())
@ -1203,6 +1171,10 @@ void ExecuteCop0Instruction()
case CopCommonInstruction::mfcn: case CopCommonInstruction::mfcn:
{ {
const std::optional<u32> value = ReadCop0Reg(static_cast<Cop0Reg>(inst.r.rd.GetValue())); const std::optional<u32> value = ReadCop0Reg(static_cast<Cop0Reg>(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) if (value)
WriteRegDelayed(inst.r.rt, value.value()); WriteRegDelayed(inst.r.rt, value.value());
else else
@ -1213,6 +1185,12 @@ void ExecuteCop0Instruction()
case CopCommonInstruction::mtcn: case CopCommonInstruction::mtcn:
{ {
WriteCop0Reg(static_cast<Cop0Reg>(inst.r.rd.GetValue()), ReadReg(inst.r.rt)); WriteCop0Reg(static_cast<Cop0Reg>(inst.r.rd.GetValue()), ReadReg(inst.r.rt));
if constexpr (pgxp_mode == PGXPMode::CPU)
{
PGXP::CPU_MTC0(inst.bits, ReadCop0Reg(static_cast<Cop0Reg>(inst.r.rd.GetValue())).value_or(0),
ReadReg(inst.i.rs));
}
} }
break; break;
@ -1239,10 +1217,16 @@ void ExecuteCop0Instruction()
} }
} }
} }
break;
void ExecuteCop2Instruction() case InstructionOp::cop2:
{ {
const Instruction inst = g_state.current_instruction; if (!g_state.cop0_regs.sr.CE2)
{
Log_WarningPrintf("Coprocessor 2 not enabled");
RaiseException(Exception::CpU);
return;
}
if (inst.cop.IsCommonInstruction()) if (inst.cop.IsCommonInstruction())
{ {
@ -1254,7 +1238,7 @@ void ExecuteCop2Instruction()
const u32 value = GTE::ReadRegister(static_cast<u32>(inst.r.rd.GetValue()) + 32); const u32 value = GTE::ReadRegister(static_cast<u32>(inst.r.rd.GetValue()) + 32);
WriteRegDelayed(inst.r.rt, value); WriteRegDelayed(inst.r.rt, value);
if (g_settings.gpu_pgxp_enable) if constexpr (pgxp_mode >= PGXPMode::Memory)
PGXP::CPU_CFC2(inst.bits, value, value); PGXP::CPU_CFC2(inst.bits, value, value);
} }
break; break;
@ -1264,7 +1248,7 @@ void ExecuteCop2Instruction()
const u32 value = ReadReg(inst.r.rt); const u32 value = ReadReg(inst.r.rt);
GTE::WriteRegister(static_cast<u32>(inst.r.rd.GetValue()) + 32, value); GTE::WriteRegister(static_cast<u32>(inst.r.rd.GetValue()) + 32, value);
if (g_settings.gpu_pgxp_enable) if constexpr (pgxp_mode >= PGXPMode::Memory)
PGXP::CPU_CTC2(inst.bits, value, value); PGXP::CPU_CTC2(inst.bits, value, value);
} }
break; break;
@ -1274,7 +1258,7 @@ void ExecuteCop2Instruction()
const u32 value = GTE::ReadRegister(static_cast<u32>(inst.r.rd.GetValue())); const u32 value = GTE::ReadRegister(static_cast<u32>(inst.r.rd.GetValue()));
WriteRegDelayed(inst.r.rt, value); WriteRegDelayed(inst.r.rt, value);
if (g_settings.gpu_pgxp_enable) if constexpr (pgxp_mode >= PGXPMode::Memory)
PGXP::CPU_MFC2(inst.bits, value, value); PGXP::CPU_MFC2(inst.bits, value, value);
} }
break; break;
@ -1284,7 +1268,7 @@ void ExecuteCop2Instruction()
const u32 value = ReadReg(inst.r.rt); const u32 value = ReadReg(inst.r.rt);
GTE::WriteRegister(static_cast<u32>(inst.r.rd.GetValue()), value); GTE::WriteRegister(static_cast<u32>(inst.r.rd.GetValue()), value);
if (g_settings.gpu_pgxp_enable) if constexpr (pgxp_mode >= PGXPMode::Memory)
PGXP::CPU_MTC2(inst.bits, value, value); PGXP::CPU_MTC2(inst.bits, value, value);
} }
break; break;
@ -1300,9 +1284,134 @@ void ExecuteCop2Instruction()
GTE::ExecuteInstruction(inst.bits); GTE::ExecuteInstruction(inst.bits);
} }
} }
break;
case InstructionOp::lwc2:
{
if (!g_state.cop0_regs.sr.CE2)
{
Log_WarningPrintf("Coprocessor 2 not enabled");
RaiseException(Exception::CpU);
return;
}
const VirtualMemoryAddress addr = ReadReg(inst.i.rs) + inst.i.imm_sext32();
u32 value;
if (!ReadMemoryWord(addr, &value))
return;
GTE::WriteRegister(ZeroExtend32(static_cast<u8>(inst.i.rt.GetValue())), value);
if constexpr (pgxp_mode >= PGXPMode::Memory)
PGXP::CPU_LWC2(inst.bits, value, addr);
}
break;
case InstructionOp::swc2:
{
if (!g_state.cop0_regs.sr.CE2)
{
Log_WarningPrintf("Coprocessor 2 not enabled");
RaiseException(Exception::CpU);
return;
}
const VirtualMemoryAddress addr = ReadReg(inst.i.rs) + inst.i.imm_sext32();
const u32 value = GTE::ReadRegister(ZeroExtend32(static_cast<u8>(inst.i.rt.GetValue())));
WriteMemoryWord(addr, value);
if constexpr (pgxp_mode >= PGXPMode::Memory)
PGXP::CPU_SWC2(inst.bits, value, addr);
}
break;
// swc0/lwc0/cop1/cop3 are essentially no-ops
case InstructionOp::cop1:
case InstructionOp::cop3:
case InstructionOp::lwc0:
case InstructionOp::lwc1:
case InstructionOp::lwc3:
case InstructionOp::swc0:
case InstructionOp::swc1:
case InstructionOp::swc3:
{
}
break;
// everything else is reserved/invalid
default:
{
RaiseException(Exception::RI);
}
break;
}
}
template<PGXPMode pgxp_mode>
static void ExecuteImpl()
{
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<pgxp_mode>();
// next load delay
UpdateLoadDelay();
}
TimingEvents::RunEvents();
}
}
void Execute()
{
if (g_settings.gpu_pgxp_enable)
{
if (g_settings.gpu_pgxp_cpu)
ExecuteImpl<PGXPMode::CPU>();
else
ExecuteImpl<PGXPMode::Memory>();
}
else
{
ExecuteImpl<PGXPMode::Disabled>();
}
}
namespace CodeCache { namespace CodeCache {
template<PGXPMode pgxp_mode>
void InterpretCachedBlock(const CodeBlock& block) void InterpretCachedBlock(const CodeBlock& block)
{ {
// set up the state so we've already fetched the instruction // 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; g_state.regs.npc += 4;
// execute the instruction we previously fetched // execute the instruction we previously fetched
ExecuteInstruction(); ExecuteInstruction<pgxp_mode>();
// next load delay // next load delay
UpdateLoadDelay(); UpdateLoadDelay();
@ -1340,6 +1449,10 @@ void InterpretCachedBlock(const CodeBlock& block)
g_state.next_instruction_is_branch_delay_slot = false; g_state.next_instruction_is_branch_delay_slot = false;
} }
template void InterpretCachedBlock<PGXPMode::Disabled>(const CodeBlock& block);
template void InterpretCachedBlock<PGXPMode::Memory>(const CodeBlock& block);
template void InterpretCachedBlock<PGXPMode::CPU>(const CodeBlock& block);
void InterpretUncachedBlock() void InterpretUncachedBlock()
{ {
Panic("Fixme with regards to re-fetching PC"); Panic("Fixme with regards to re-fetching PC");
@ -1365,7 +1478,7 @@ void InterpretUncachedBlock()
break; break;
// execute the instruction we previously fetched // execute the instruction we previously fetched
ExecuteInstruction(); ExecuteInstruction<PGXPMode::Disabled>();
// next load delay // next load delay
UpdateLoadDelay(); UpdateLoadDelay();
@ -1387,7 +1500,13 @@ namespace Recompiler::Thunks {
bool InterpretInstruction() bool InterpretInstruction()
{ {
ExecuteInstruction(); ExecuteInstruction<PGXPMode::Disabled>();
return g_state.exception_raised;
}
bool InterpretInstructionPGXP()
{
ExecuteInstruction<PGXPMode::Memory>();
return g_state.exception_raised; return g_state.exception_raised;
} }

View file

@ -1014,12 +1014,14 @@ bool CodeGenerator::Compile_Fallback(const CodeBlockInstruction& cbi)
{ {
// TODO: Use carry flag or something here too // TODO: Use carry flag or something here too
Value return_value = m_register_cache.AllocateScratch(RegSize_8); 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); EmitExceptionExitOnBool(return_value);
} }
else 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; m_current_instruction_in_branch_delay_slot_dirty = cbi.is_branch_instruction;

View file

@ -13,6 +13,7 @@ namespace Recompiler::Thunks {
// TODO: Abuse carry flag or something else for exception // TODO: Abuse carry flag or something else for exception
////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////
bool InterpretInstruction(); bool InterpretInstruction();
bool InterpretInstructionPGXP();
// Memory access functions for the JIT - MSB is set on exception. // Memory access functions for the JIT - MSB is set on exception.
u64 ReadMemoryByte(u32 address); u64 ReadMemoryByte(u32 address);

View file

@ -135,7 +135,6 @@ enum class InstructionFunct : u8
or_ = 37, or_ = 37,
xor_ = 38, xor_ = 38,
nor = 39, nor = 39,
sh = 41,
slt = 42, slt = 42,
sltu = 43 sltu = 43
}; };

View file

@ -8,6 +8,7 @@
#include "common/log.h" #include "common/log.h"
#include "common/progress_callback.h" #include "common/progress_callback.h"
#include "common/string_util.h" #include "common/string_util.h"
#include "host_interface.h"
#include "settings.h" #include "settings.h"
#include <algorithm> #include <algorithm>
#include <array> #include <array>
@ -270,7 +271,12 @@ std::vector<std::string> GameList::ParseM3UFile(const char* path)
const char* GameList::GetGameListCompatibilityRatingString(GameListCompatibilityRating rating) const char* GameList::GetGameListCompatibilityRatingString(GameListCompatibilityRating rating)
{ {
static constexpr std::array<const char*, static_cast<size_t>(GameListCompatibilityRating::Count)> names = { static constexpr std::array<const char*, static_cast<size_t>(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) ? return (rating >= GameListCompatibilityRating::Unknown && rating < GameListCompatibilityRating::Count) ?
names[static_cast<int>(rating)] : names[static_cast<int>(rating)] :
""; "";
@ -446,6 +452,12 @@ bool GameList::GetGameListEntry(const std::string& path, GameListEntry* entry)
entry->compatibility_rating = compatibility_entry->compatibility_rating; entry->compatibility_rating = compatibility_entry->compatibility_rating;
else else
Log_WarningPrintf("'%s' (%s) not found in compatibility list", entry->code.c_str(), entry->title.c_str()); 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; FILESYSTEM_STAT_DATA ffd;
@ -577,6 +589,12 @@ bool GameList::LoadEntriesFromCache(ByteStream* stream)
ge.type = static_cast<GameListEntryType>(type); ge.type = static_cast<GameListEntryType>(type);
ge.compatibility_rating = static_cast<GameListCompatibilityRating>(compatibility_rating); ge.compatibility_rating = static_cast<GameListCompatibilityRating>(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); auto iter = m_cache_map.find(ge.path);
if (iter != m_cache_map.end()) if (iter != m_cache_map.end())
iter->second = std::move(ge); iter->second = std::move(ge);
@ -625,6 +643,7 @@ bool GameList::WriteEntryToCache(const GameListEntry* entry, ByteStream* stream)
result &= WriteU8(stream, static_cast<u8>(entry->region)); result &= WriteU8(stream, static_cast<u8>(entry->region));
result &= WriteU8(stream, static_cast<u8>(entry->type)); result &= WriteU8(stream, static_cast<u8>(entry->type));
result &= WriteU8(stream, static_cast<u8>(entry->compatibility_rating)); result &= WriteU8(stream, static_cast<u8>(entry->compatibility_rating));
result &= entry->settings.SaveToStream(stream);
return result; return result;
} }
@ -853,6 +872,18 @@ const GameListEntry* GameList::GetEntryForPath(const char* path) const
return nullptr; 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 const GameListDatabaseEntry* GameList::GetDatabaseEntryForCode(const std::string& code) const
{ {
if (!m_database_load_tried) if (!m_database_load_tried)
@ -1272,3 +1303,46 @@ std::string GameList::ExportCompatibilityEntry(const GameListCompatibilityEntry*
entry_elem->Accept(&printer); entry_elem->Accept(&printer);
return std::string(printer.CStr(), printer.CStrSize()); 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());
}
}

View file

@ -1,4 +1,5 @@
#pragma once #pragma once
#include "game_settings.h"
#include "types.h" #include "types.h"
#include <memory> #include <memory>
#include <optional> #include <optional>
@ -48,6 +49,7 @@ struct GameListEntry
DiscRegion region; DiscRegion region;
GameListEntryType type; GameListEntryType type;
GameListCompatibilityRating compatibility_rating; GameListCompatibilityRating compatibility_rating;
GameSettings::Entry settings;
}; };
struct GameListCompatibilityEntry struct GameListCompatibilityEntry
@ -109,6 +111,8 @@ public:
void SetCacheFilename(std::string filename) { m_cache_filename = std::move(filename); } void SetCacheFilename(std::string filename) { m_cache_filename = std::move(filename); }
void SetDatabaseFilename(std::string filename) { m_database_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 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); void SetSearchDirectoriesFromSettings(SettingsInterface& si);
bool IsDatabasePresent() const; bool IsDatabasePresent() const;
@ -120,11 +124,15 @@ public:
static std::string ExportCompatibilityEntry(const GameListCompatibilityEntry* entry); 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: private:
enum : u32 enum : u32
{ {
GAME_LIST_CACHE_SIGNATURE = 0x45434C47, GAME_LIST_CACHE_SIGNATURE = 0x45434C47,
GAME_LIST_CACHE_VERSION = 5 GAME_LIST_CACHE_VERSION = 6
}; };
using DatabaseMap = std::unordered_map<std::string, GameListDatabaseEntry>; using DatabaseMap = std::unordered_map<std::string, GameListDatabaseEntry>;
@ -140,6 +148,8 @@ private:
class RedumpDatVisitor; class RedumpDatVisitor;
class CompatibilityListVisitor; class CompatibilityListVisitor;
GameListEntry* GetMutableEntryForPath(const char* path);
static bool GetExeListEntry(const char* path, GameListEntry* entry); static bool GetExeListEntry(const char* path, GameListEntry* entry);
bool GetM3UListEntry(const char* path, GameListEntry* entry); bool GetM3UListEntry(const char* path, GameListEntry* entry);
@ -163,16 +173,22 @@ private:
bool SaveCompatibilityDatabase(); bool SaveCompatibilityDatabase();
bool SaveCompatibilityDatabaseForEntry(const GameListCompatibilityEntry* entry); bool SaveCompatibilityDatabaseForEntry(const GameListCompatibilityEntry* entry);
void LoadGameSettings();
DatabaseMap m_database; DatabaseMap m_database;
EntryList m_entries; EntryList m_entries;
CacheMap m_cache_map; CacheMap m_cache_map;
CompatibilityMap m_compatibility_list; CompatibilityMap m_compatibility_list;
GameSettings::Database m_game_settings;
std::unique_ptr<ByteStream> m_cache_write_stream; std::unique_ptr<ByteStream> m_cache_write_stream;
std::vector<DirectoryEntry> m_search_directories; std::vector<DirectoryEntry> m_search_directories;
std::string m_cache_filename; std::string m_cache_filename;
std::string m_database_filename; std::string m_database_filename;
std::string m_compatibility_list_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_database_load_tried = false;
bool m_compatibility_list_load_tried = false; bool m_compatibility_list_load_tried = false;
bool m_game_settings_load_tried = false;
}; };

429
src/core/game_settings.cpp Normal file
View file

@ -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 <array>
#include <utility>
Log_SetChannel(GameSettings);
#ifdef WIN32
#include "common/windows_headers.h"
#endif
#include "SimpleIni.h"
namespace GameSettings {
std::array<std::pair<const char*, const char*>, static_cast<u32>(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<u32>(trait)].first;
}
const char* GetTraitDisplayName(Trait trait)
{
DebugAssert(trait < Trait::Count);
return s_trait_names[static_cast<u32>(trait)].second;
}
bool Entry::HasAnySettings() const
{
return traits.any() || display_active_start_offset >= 0 || display_active_end_offset > 0;
}
template<typename T>
bool ReadOptionalFromStream(ByteStream* stream, std::optional<T>* 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<typename T>
bool WriteOptionalToStream(ByteStream* stream, const std::optional<T>& 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<u32>(Trait::Count) + 7) / 8;
std::array<u8, num_bytes> 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<int>(Trait::Count); i++)
{
if ((bits[i / 8] & (1u << (i % 8))) != 0)
AddTrait(static_cast<Trait>(i));
}
return true;
}
bool Entry::SaveToStream(ByteStream* stream) const
{
constexpr u32 num_bytes = (static_cast<u32>(Trait::Count) + 7) / 8;
std::array<u8, num_bytes> bits;
bits.fill(0);
for (u32 i = 0; i < static_cast<int>(Trait::Count); i++)
{
if (HasTrait(static_cast<Trait>(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<u32>(Trait::Count); trait++)
{
if (ini.GetBoolValue(section, s_trait_names[trait].first, false))
entry->AddTrait(static_cast<Trait>(trait));
}
long lvalue = ini.GetLongValue(section, "DisplayActiveStartOffset", 0);
if (lvalue != 0)
entry->display_active_start_offset = static_cast<s16>(lvalue);
lvalue = ini.GetLongValue(section, "DisplayActiveEndOffset", 0);
if (lvalue != 0)
entry->display_active_end_offset = static_cast<s16>(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<bool>(cvalue);
}
static void StoreIniSection(const Entry& entry, const char* section, CSimpleIniA& ini)
{
for (u32 trait = 0; trait < static_cast<u32>(Trait::Count); trait++)
{
if (entry.HasTrait(static_cast<Trait>(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<int>(err));
return false;
}
std::list<CSimpleIniA::Entry> 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<int>(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<int>(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

74
src/core/game_settings.h Normal file
View file

@ -0,0 +1,74 @@
#pragma once
#include "types.h"
#include <bitset>
#include <optional>
#include <string>
#include <unordered_map>
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<static_cast<int>(Trait::Count)> traits{};
std::optional<s16> display_active_start_offset;
std::optional<s16> display_active_end_offset;
// user settings
std::optional<DisplayCropMode> display_crop_mode;
std::optional<DisplayAspectRatio> display_aspect_ratio;
std::optional<ControllerType> controller_1_type;
std::optional<ControllerType> controller_2_type;
std::optional<bool> gpu_widescreen_hack;
ALWAYS_INLINE bool HasTrait(Trait trait) const { return traits[static_cast<int>(trait)]; }
ALWAYS_INLINE void AddTrait(Trait trait) { traits[static_cast<int>(trait)] = true; }
ALWAYS_INLINE void RemoveTrait(Trait trait) { traits[static_cast<int>(trait)] = false; }
ALWAYS_INLINE void SetTrait(Trait trait, bool enabled) { traits[static_cast<int>(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<std::string, Entry> m_entries;
};
}; // namespace GameSettings

View file

@ -513,15 +513,15 @@ void GPU::UpdateCRTCDisplayParameters()
switch (crop_mode) switch (crop_mode)
{ {
case DisplayCropMode::None: case DisplayCropMode::None:
cs.horizontal_active_start = 487; cs.horizontal_active_start = static_cast<u16>(std::max<int>(0, 487 + g_settings.display_active_start_offset));
cs.horizontal_active_end = 3282; cs.horizontal_active_end = static_cast<u16>(std::max<int>(0, 3282 + g_settings.display_active_end_offset));
cs.vertical_active_start = 20; cs.vertical_active_start = 20;
cs.vertical_active_end = 308; cs.vertical_active_end = 308;
break; break;
case DisplayCropMode::Overscan: case DisplayCropMode::Overscan:
cs.horizontal_active_start = 628; cs.horizontal_active_start = static_cast<u16>(std::max<int>(0, 628 + g_settings.display_active_start_offset));
cs.horizontal_active_end = 3188; cs.horizontal_active_end = static_cast<u16>(std::max<int>(0, 3188 + g_settings.display_active_end_offset));
cs.vertical_active_start = 30; cs.vertical_active_start = 30;
cs.vertical_active_end = 298; cs.vertical_active_end = 298;
break; break;
@ -540,15 +540,15 @@ void GPU::UpdateCRTCDisplayParameters()
switch (crop_mode) switch (crop_mode)
{ {
case DisplayCropMode::None: case DisplayCropMode::None:
cs.horizontal_active_start = 488; cs.horizontal_active_start = static_cast<u16>(std::max<int>(0, 488 + g_settings.display_active_start_offset));
cs.horizontal_active_end = 3288; cs.horizontal_active_end = static_cast<u16>(std::max<int>(0, 3288 + g_settings.display_active_end_offset));
cs.vertical_active_start = 16; cs.vertical_active_start = 16;
cs.vertical_active_end = 256; cs.vertical_active_end = 256;
break; break;
case DisplayCropMode::Overscan: case DisplayCropMode::Overscan:
cs.horizontal_active_start = 608; cs.horizontal_active_start = static_cast<u16>(std::max<int>(0, 608 + g_settings.display_active_start_offset));
cs.horizontal_active_end = 3168; cs.horizontal_active_end = static_cast<u16>(std::max<int>(0, 3168 + g_settings.display_active_end_offset));
cs.vertical_active_start = 24; cs.vertical_active_start = 24;
cs.vertical_active_end = 248; cs.vertical_active_end = 248;
break; break;
@ -759,9 +759,15 @@ void GPU::CRTCTickEvent(TickCount ticks)
// start the new frame // start the new frame
m_crtc_state.current_scanline = 0; m_crtc_state.current_scanline = 0;
if (m_GPUSTAT.vertical_interlace) if (m_GPUSTAT.vertical_interlace)
{
m_crtc_state.interlaced_field ^= 1u; m_crtc_state.interlaced_field ^= 1u;
m_GPUSTAT.interlaced_field = m_crtc_state.interlaced_field;
}
else else
{
m_crtc_state.interlaced_field = 0; m_crtc_state.interlaced_field = 0;
m_GPUSTAT.interlaced_field = 0u; // new GPU = 1, old GPU = 0
}
} }
} }

View file

@ -489,7 +489,7 @@ protected:
BitField<u32, bool, 10, 1> draw_to_displayed_field; BitField<u32, bool, 10, 1> draw_to_displayed_field;
BitField<u32, bool, 11, 1> set_mask_while_drawing; BitField<u32, bool, 11, 1> set_mask_while_drawing;
BitField<u32, bool, 12, 1> check_mask_before_draw; BitField<u32, bool, 12, 1> check_mask_before_draw;
BitField<u32, bool, 13, 1> interlaced_field; BitField<u32, u8, 13, 1> interlaced_field;
BitField<u32, bool, 14, 1> reverse_flag; BitField<u32, bool, 14, 1> reverse_flag;
BitField<u32, bool, 15, 1> texture_disable; BitField<u32, bool, 15, 1> texture_disable;
BitField<u32, u8, 16, 1> horizontal_resolution_2; BitField<u32, u8, 16, 1> horizontal_resolution_2;

View file

@ -588,7 +588,7 @@ void GPU_HW_OpenGL::ClearDisplay()
glClearColor(0.0f, 0.0f, 0.0f, 1.0f); glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT); glClear(GL_COLOR_BUFFER_BIT);
glEnable(GL_SCISSOR_TEST); glEnable(GL_SCISSOR_TEST);
m_vram_texture.BindFramebuffer(GL_DRAW_FRAMEBUFFER); glBindFramebuffer(GL_DRAW_FRAMEBUFFER, m_vram_fbo_id);
} }
void GPU_HW_OpenGL::UpdateDisplay() void GPU_HW_OpenGL::UpdateDisplay()

View file

@ -625,7 +625,7 @@ static void RTPS(const s16 V[3], u8 shift, bool lm, bool last)
if (g_settings.gpu_pgxp_enable) if (g_settings.gpu_pgxp_enable)
{ {
// this can potentially use increased precision on Z // this can potentially use increased precision on Z
const float precise_z = std::max<float>((float)REGS.H / 2.f, (float)REGS.SZ3); const float precise_z = std::max<float>((float)REGS.H / 2.f, (float)z / 4096.0f);
const float precise_h_div_sz = (float)REGS.H / precise_z; const float precise_h_div_sz = (float)REGS.H / precise_z;
const float fofx = ((float)REGS.OFX / (float)(1 << 16)); const float fofx = ((float)REGS.OFX / (float)(1 << 16));
const float fofy = ((float)REGS.OFY / (float)(1 << 16)); const float fofy = ((float)REGS.OFY / (float)(1 << 16));

View file

@ -114,7 +114,7 @@ void HostInterface::ResetSystem()
{ {
System::Reset(); System::Reset();
System::ResetPerformanceCounters(); System::ResetPerformanceCounters();
AddOSDMessage("System reset."); AddOSDMessage(TranslateStdString("OSDMessage", "System reset."));
} }
void HostInterface::PowerOffSystem() void HostInterface::PowerOffSystem()
@ -284,13 +284,13 @@ bool HostInterface::LoadState(const char* filename)
if (!stream) if (!stream)
return false; 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::IsShutdown())
{ {
if (!System::LoadState(stream.get())) if (!System::LoadState(stream.get()))
{ {
ReportFormattedError("Loading state from '%s' failed. Resetting.", filename); ReportFormattedError(TranslateString("OSDMessage", "Loading state from '%s' failed. Resetting."), filename);
ResetSystem(); ResetSystem();
return false; return false;
} }
@ -318,12 +318,12 @@ bool HostInterface::SaveState(const char* filename)
const bool result = System::SaveState(stream.get()); const bool result = System::SaveState(stream.get());
if (!result) if (!result)
{ {
ReportFormattedError("Saving state to '%s' failed.", filename); ReportFormattedError(TranslateString("OSDMessage", "Saving state to '%s' failed."), filename);
stream->Discard(); stream->Discard();
} }
else else
{ {
AddFormattedOSDMessage(5.0f, "State saved to '%s'.", filename); AddFormattedOSDMessage(5.0f, TranslateString("OSDMessage", "State saved to '%s'."), filename);
stream->Commit(); stream->Commit();
} }
@ -358,6 +358,7 @@ void HostInterface::SetDefaultSettings(SettingsInterface& si)
si.SetBoolValue("Main", "SaveStateOnExit", true); si.SetBoolValue("Main", "SaveStateOnExit", true);
si.SetBoolValue("Main", "ConfirmPowerOff", true); si.SetBoolValue("Main", "ConfirmPowerOff", true);
si.SetBoolValue("Main", "LoadDevicesFromSaveStates", false); si.SetBoolValue("Main", "LoadDevicesFromSaveStates", false);
si.SetBoolValue("Main", "ApplyGameSettings", true);
si.SetStringValue("CPU", "ExecutionMode", Settings::GetCPUExecutionModeName(Settings::DEFAULT_CPU_EXECUTION_MODE)); si.SetStringValue("CPU", "ExecutionMode", Settings::GetCPUExecutionModeName(Settings::DEFAULT_CPU_EXECUTION_MODE));
si.SetBoolValue("CPU", "RecompilerMemoryExceptions", false); si.SetBoolValue("CPU", "RecompilerMemoryExceptions", false);
@ -375,8 +376,11 @@ void HostInterface::SetDefaultSettings(SettingsInterface& si)
si.SetBoolValue("GPU", "PGXPCulling", true); si.SetBoolValue("GPU", "PGXPCulling", true);
si.SetBoolValue("GPU", "PGXPTextureCorrection", true); si.SetBoolValue("GPU", "PGXPTextureCorrection", true);
si.SetBoolValue("GPU", "PGXPVertexCache", false); si.SetBoolValue("GPU", "PGXPVertexCache", false);
si.SetBoolValue("GPU", "PGXPCPU", false);
si.SetStringValue("Display", "CropMode", Settings::GetDisplayCropModeName(Settings::DEFAULT_DISPLAY_CROP_MODE)); 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", si.SetStringValue("Display", "AspectRatio",
Settings::GetDisplayAspectRatioName(Settings::DEFAULT_DISPLAY_ASPECT_RATIO)); Settings::GetDisplayAspectRatioName(Settings::DEFAULT_DISPLAY_ASPECT_RATIO));
si.SetBoolValue("Display", "LinearFiltering", true); si.SetBoolValue("Display", "LinearFiltering", true);
@ -440,6 +444,32 @@ void HostInterface::LoadSettings(SettingsInterface& si)
g_settings.Load(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) void HostInterface::SaveSettings(SettingsInterface& si)
{ {
g_settings.Save(si); g_settings.Save(si);
@ -447,7 +477,7 @@ void HostInterface::SaveSettings(SettingsInterface& si)
void HostInterface::CheckForSettingsChanges(const Settings& old_settings) void HostInterface::CheckForSettingsChanges(const Settings& old_settings)
{ {
if (!System::IsShutdown()) if (System::IsValid())
{ {
if (g_settings.gpu_renderer != old_settings.gpu_renderer || if (g_settings.gpu_renderer != old_settings.gpu_renderer ||
g_settings.gpu_use_debug_device != old_settings.gpu_use_debug_device) 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.gpu_force_ntsc_timings != old_settings.gpu_force_ntsc_timings ||
g_settings.display_crop_mode != old_settings.display_crop_mode || g_settings.display_crop_mode != old_settings.display_crop_mode ||
g_settings.display_aspect_ratio != old_settings.display_aspect_ratio || 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(); g_gpu->UpdateSettings();
} }
@ -658,6 +690,16 @@ float HostInterface::GetFloatSettingValue(const char* section, const char* key,
return float_value.value_or(default_value); 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() void HostInterface::ToggleSoftwareRendering()
{ {
if (System::IsShutdown() || g_settings.gpu_renderer == GPURenderer::Software) if (System::IsShutdown() || g_settings.gpu_renderer == GPURenderer::Software)

View file

@ -109,6 +109,10 @@ public:
/// Returns a float setting from the configuration. /// Returns a float setting from the configuration.
virtual float GetFloatSettingValue(const char* section, const char* key, float default_value = 0.0f); 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. /// Loads the BIOS image for the specified region.
std::optional<std::vector<u8>> GetBIOSImage(ConsoleRegion region); std::optional<std::vector<u8>> GetBIOSImage(ConsoleRegion region);
@ -134,6 +138,9 @@ protected:
/// Saves current settings variables to ini. /// Saves current settings variables to ini.
virtual void SaveSettings(SettingsInterface& si); 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. /// Checks for settings changes, std::move() the old settings away for comparing beforehand.
virtual void CheckForSettingsChanges(const Settings& old_settings); virtual void CheckForSettingsChanges(const Settings& old_settings);
@ -161,4 +168,6 @@ protected:
std::string m_user_directory; std::string m_user_directory;
}; };
#define TRANSLATABLE(context, str) str
extern HostInterface* g_host_interface; extern HostInterface* g_host_interface;

File diff suppressed because it is too large Load diff

View file

@ -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_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 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 // -- CPU functions
void CPU_LW(u32 instr, u32 rtVal, u32 addr); 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_SH(u32 instr, u16 rtVal, u32 addr);
void CPU_SW(u32 instr, u32 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 } // namespace PGXP

View file

@ -84,6 +84,7 @@ void Settings::Load(SettingsInterface& si)
save_state_on_exit = si.GetBoolValue("Main", "SaveStateOnExit", true); save_state_on_exit = si.GetBoolValue("Main", "SaveStateOnExit", true);
confim_power_off = si.GetBoolValue("Main", "ConfirmPowerOff", true); confim_power_off = si.GetBoolValue("Main", "ConfirmPowerOff", true);
load_devices_from_save_states = si.GetBoolValue("Main", "LoadDevicesFromSaveStates", false); load_devices_from_save_states = si.GetBoolValue("Main", "LoadDevicesFromSaveStates", false);
apply_game_settings = si.GetBoolValue("Main", "ApplyGameSettings", true);
cpu_execution_mode = cpu_execution_mode =
ParseCPUExecutionMode( ParseCPUExecutionMode(
@ -106,6 +107,7 @@ void Settings::Load(SettingsInterface& si)
gpu_pgxp_culling = si.GetBoolValue("GPU", "PGXPCulling", true); gpu_pgxp_culling = si.GetBoolValue("GPU", "PGXPCulling", true);
gpu_pgxp_texture_correction = si.GetBoolValue("GPU", "PGXPTextureCorrection", true); gpu_pgxp_texture_correction = si.GetBoolValue("GPU", "PGXPTextureCorrection", true);
gpu_pgxp_vertex_cache = si.GetBoolValue("GPU", "PGXPVertexCache", false); gpu_pgxp_vertex_cache = si.GetBoolValue("GPU", "PGXPVertexCache", false);
gpu_pgxp_cpu = si.GetBoolValue("GPU", "PGXPCPU", false);
display_crop_mode = display_crop_mode =
ParseDisplayCropMode( ParseDisplayCropMode(
@ -115,6 +117,8 @@ void Settings::Load(SettingsInterface& si)
ParseDisplayAspectRatio( ParseDisplayAspectRatio(
si.GetStringValue("Display", "AspectRatio", GetDisplayAspectRatioName(DEFAULT_DISPLAY_ASPECT_RATIO)).c_str()) si.GetStringValue("Display", "AspectRatio", GetDisplayAspectRatioName(DEFAULT_DISPLAY_ASPECT_RATIO)).c_str())
.value_or(DEFAULT_DISPLAY_ASPECT_RATIO); .value_or(DEFAULT_DISPLAY_ASPECT_RATIO);
display_active_start_offset = static_cast<s16>(si.GetIntValue("Display", "ActiveStartOffset", 0));
display_active_end_offset = static_cast<s16>(si.GetIntValue("Display", "ActiveEndOffset", 0));
display_linear_filtering = si.GetBoolValue("Display", "LinearFiltering", true); display_linear_filtering = si.GetBoolValue("Display", "LinearFiltering", true);
display_integer_scaling = si.GetBoolValue("Display", "IntegerScaling", false); display_integer_scaling = si.GetBoolValue("Display", "IntegerScaling", false);
display_show_osd_messages = si.GetBoolValue("Display", "ShowOSDMessages", true); 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", "SaveStateOnExit", save_state_on_exit);
si.SetBoolValue("Main", "ConfirmPowerOff", confim_power_off); si.SetBoolValue("Main", "ConfirmPowerOff", confim_power_off);
si.SetBoolValue("Main", "LoadDevicesFromSaveStates", load_devices_from_save_states); 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.SetStringValue("CPU", "ExecutionMode", GetCPUExecutionModeName(cpu_execution_mode));
si.SetBoolValue("CPU", "RecompilerMemoryExceptions", cpu_recompiler_memory_exceptions); 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", "PGXPCulling", gpu_pgxp_culling);
si.SetBoolValue("GPU", "PGXPTextureCorrection", gpu_pgxp_texture_correction); si.SetBoolValue("GPU", "PGXPTextureCorrection", gpu_pgxp_texture_correction);
si.SetBoolValue("GPU", "PGXPVertexCache", gpu_pgxp_vertex_cache); si.SetBoolValue("GPU", "PGXPVertexCache", gpu_pgxp_vertex_cache);
si.SetBoolValue("GPU", "PGXPCPU", gpu_pgxp_cpu);
si.SetStringValue("Display", "CropMode", GetDisplayCropModeName(display_crop_mode)); 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.SetStringValue("Display", "AspectRatio", GetDisplayAspectRatioName(display_aspect_ratio));
si.SetBoolValue("Display", "LinearFiltering", display_linear_filtering); si.SetBoolValue("Display", "LinearFiltering", display_linear_filtering);
si.SetBoolValue("Display", "IntegerScaling", display_integer_scaling); si.SetBoolValue("Display", "IntegerScaling", display_integer_scaling);
@ -283,7 +291,10 @@ void Settings::Save(SettingsInterface& si) const
static std::array<const char*, LOGLEVEL_COUNT> s_log_level_names = { static std::array<const char*, LOGLEVEL_COUNT> s_log_level_names = {
{"None", "Error", "Warning", "Perf", "Success", "Info", "Dev", "Profile", "Debug", "Trace"}}; {"None", "Error", "Warning", "Perf", "Success", "Info", "Dev", "Profile", "Debug", "Trace"}};
static std::array<const char*, LOGLEVEL_COUNT> s_log_level_display_names = { static std::array<const char*, LOGLEVEL_COUNT> 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<LOGLEVEL> Settings::ParseLogLevelName(const char* str) std::optional<LOGLEVEL> Settings::ParseLogLevelName(const char* str)
{ {
@ -311,7 +322,8 @@ const char* Settings::GetLogLevelDisplayName(LOGLEVEL level)
static std::array<const char*, 4> s_console_region_names = {{"Auto", "NTSC-J", "NTSC-U", "PAL"}}; static std::array<const char*, 4> s_console_region_names = {{"Auto", "NTSC-J", "NTSC-U", "PAL"}};
static std::array<const char*, 4> s_console_region_display_names = { static std::array<const char*, 4> 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<ConsoleRegion> Settings::ParseConsoleRegionName(const char* str) std::optional<ConsoleRegion> Settings::ParseConsoleRegionName(const char* str)
{ {
@ -339,7 +351,8 @@ const char* Settings::GetConsoleRegionDisplayName(ConsoleRegion region)
static std::array<const char*, 4> s_disc_region_names = {{"NTSC-J", "NTSC-U", "PAL", "Other"}}; static std::array<const char*, 4> s_disc_region_names = {{"NTSC-J", "NTSC-U", "PAL", "Other"}};
static std::array<const char*, 4> s_disc_region_display_names = { static std::array<const char*, 4> 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<DiscRegion> Settings::ParseDiscRegionName(const char* str) std::optional<DiscRegion> Settings::ParseDiscRegionName(const char* str)
{ {
@ -367,7 +380,8 @@ const char* Settings::GetDiscRegionDisplayName(DiscRegion region)
static std::array<const char*, 3> s_cpu_execution_mode_names = {{"Interpreter", "CachedInterpreter", "Recompiler"}}; static std::array<const char*, 3> s_cpu_execution_mode_names = {{"Interpreter", "CachedInterpreter", "Recompiler"}};
static std::array<const char*, 3> s_cpu_execution_mode_display_names = { static std::array<const char*, 3> 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<CPUExecutionMode> Settings::ParseCPUExecutionMode(const char* str) std::optional<CPUExecutionMode> Settings::ParseCPUExecutionMode(const char* str)
{ {
@ -400,9 +414,10 @@ static std::array<const char*, 4> s_gpu_renderer_names = {{
"Vulkan", "OpenGL", "Software"}}; "Vulkan", "OpenGL", "Software"}};
static std::array<const char*, 4> s_gpu_renderer_display_names = {{ static std::array<const char*, 4> s_gpu_renderer_display_names = {{
#ifdef WIN32 #ifdef WIN32
"Hardware (D3D11)", TRANSLATABLE("GPURenderer", "Hardware (D3D11)"),
#endif #endif
"Hardware (Vulkan)", "Hardware (OpenGL)", "Software"}}; TRANSLATABLE("GPURenderer", "Hardware (Vulkan)"), TRANSLATABLE("GPURenderer", "Hardware (OpenGL)"),
TRANSLATABLE("GPURenderer", "Software")}};
std::optional<GPURenderer> Settings::ParseRendererName(const char* str) std::optional<GPURenderer> Settings::ParseRendererName(const char* str)
{ {
@ -429,7 +444,9 @@ const char* Settings::GetRendererDisplayName(GPURenderer renderer)
} }
static std::array<const char*, 3> s_display_crop_mode_names = {{"None", "Overscan", "Borders"}}; static std::array<const char*, 3> s_display_crop_mode_names = {{"None", "Overscan", "Borders"}};
static std::array<const char*, 3> s_display_crop_mode_display_names = {{"None", "Only Overscan Area", "All Borders"}}; static std::array<const char*, 3> s_display_crop_mode_display_names = {{TRANSLATABLE("DisplayCropMode", "None"),
TRANSLATABLE("DisplayCropMode", "Only Overscan Area"),
TRANSLATABLE("DisplayCropMode", "All Borders")}};
std::optional<DisplayCropMode> Settings::ParseDisplayCropMode(const char* str) std::optional<DisplayCropMode> Settings::ParseDisplayCropMode(const char* str)
{ {
@ -485,7 +502,8 @@ float Settings::GetDisplayAspectRatioValue(DisplayAspectRatio ar)
} }
static std::array<const char*, 3> s_audio_backend_names = {{"Null", "Cubeb", "SDL"}}; static std::array<const char*, 3> s_audio_backend_names = {{"Null", "Cubeb", "SDL"}};
static std::array<const char*, 3> s_audio_backend_display_names = {{"Null (No Output)", "Cubeb", "SDL"}}; static std::array<const char*, 3> s_audio_backend_display_names = {
{TRANSLATABLE("AudioBackend", "Null (No Output)"), TRANSLATABLE("AudioBackend", "Cubeb"), TRANSLATABLE("AudioBackend", "SDL")}};
std::optional<AudioBackend> Settings::ParseAudioBackend(const char* str) std::optional<AudioBackend> Settings::ParseAudioBackend(const char* str)
{ {
@ -514,7 +532,9 @@ const char* Settings::GetAudioBackendDisplayName(AudioBackend backend)
static std::array<const char*, 6> s_controller_type_names = { static std::array<const char*, 6> s_controller_type_names = {
{"None", "DigitalController", "AnalogController", "NamcoGunCon", "PlayStationMouse", "NeGcon"}}; {"None", "DigitalController", "AnalogController", "NamcoGunCon", "PlayStationMouse", "NeGcon"}};
static std::array<const char*, 6> s_controller_display_names = { static std::array<const char*, 6> 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<ControllerType> Settings::ParseControllerTypeName(const char* str) std::optional<ControllerType> Settings::ParseControllerTypeName(const char* str)
{ {
@ -541,9 +561,10 @@ const char* Settings::GetControllerTypeDisplayName(ControllerType type)
} }
static std::array<const char*, 4> s_memory_card_type_names = {{"None", "Shared", "PerGame", "PerGameTitle"}}; static std::array<const char*, 4> s_memory_card_type_names = {{"None", "Shared", "PerGame", "PerGameTitle"}};
static std::array<const char*, 4> s_memory_card_type_display_names = {{"No Memory Card", "Shared Between All Games", static std::array<const char*, 4> s_memory_card_type_display_names = {
"Separate Card Per Game (Game Code)", {TRANSLATABLE("MemoryCardType", "No Memory Card"), TRANSLATABLE("MemoryCardType", "Shared Between All Games"),
"Separate Card Per Game (Game Title)"}}; TRANSLATABLE("MemoryCardType", "Separate Card Per Game (Game Code)"),
TRANSLATABLE("MemoryCardType", "Separate Card Per Game (Game Title)")}};
std::optional<MemoryCardType> Settings::ParseMemoryCardTypeName(const char* str) std::optional<MemoryCardType> Settings::ParseMemoryCardTypeName(const char* str)
{ {

View file

@ -78,6 +78,7 @@ struct Settings
bool save_state_on_exit = true; bool save_state_on_exit = true;
bool confim_power_off = true; bool confim_power_off = true;
bool load_devices_from_save_states = false; bool load_devices_from_save_states = false;
bool apply_game_settings = true;
GPURenderer gpu_renderer = GPURenderer::Software; GPURenderer gpu_renderer = GPURenderer::Software;
std::string gpu_adapter; std::string gpu_adapter;
@ -93,7 +94,10 @@ struct Settings
bool gpu_pgxp_culling = true; bool gpu_pgxp_culling = true;
bool gpu_pgxp_texture_correction = true; bool gpu_pgxp_texture_correction = true;
bool gpu_pgxp_vertex_cache = false; bool gpu_pgxp_vertex_cache = false;
bool gpu_pgxp_cpu = false;
DisplayCropMode display_crop_mode = DisplayCropMode::None; 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; DisplayAspectRatio display_aspect_ratio = DisplayAspectRatio::R4_3;
bool display_linear_filtering = true; bool display_linear_filtering = true;
bool display_integer_scaling = false; 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 IsUsingRecompiler() const { return (cpu_execution_mode == CPUExecutionMode::Recompiler); }
ALWAYS_INLINE bool IsUsingSoftwareRenderer() const { return (gpu_renderer == GPURenderer::Software); } 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; bool HasAnyPerGameMemoryCards() const;
enum : u32 enum : u32

View file

@ -131,7 +131,7 @@ bool IsShutdown()
bool IsValid() bool IsValid()
{ {
return s_state != State::Shutdown; return s_state != State::Shutdown && s_state != State::Starting;
} }
ConsoleRegion GetRegion() ConsoleRegion GetRegion()
@ -384,6 +384,9 @@ bool Boot(const SystemBootParameters& params)
return false; return false;
} }
// Notify change of disc.
UpdateRunningGame(media ? media->GetFileName().c_str() : params.filename.c_str(), media.get());
// Component setup. // Component setup.
if (!Initialize(params.force_software_renderer)) if (!Initialize(params.force_software_renderer))
{ {
@ -391,8 +394,6 @@ bool Boot(const SystemBootParameters& params)
return false; return false;
} }
// Notify change of disc.
UpdateRunningGame(params.filename.c_str(), media.get());
UpdateControllers(); UpdateControllers();
UpdateMemoryCards(); UpdateMemoryCards();
Reset(); Reset();
@ -649,7 +650,9 @@ bool DoLoadState(ByteStream* state, bool force_software_renderer)
if (header.version != SAVE_STATE_VERSION) if (header.version != SAVE_STATE_VERSION)
{ {
g_host_interface->ReportFormattedError("Save state is incompatible: expecting version %u but state is version %u.", 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); SAVE_STATE_VERSION, header.version);
return false; return false;
} }
@ -671,7 +674,8 @@ bool DoLoadState(ByteStream* state, bool force_software_renderer)
media = OpenCDImage(media_filename.c_str(), false); media = OpenCDImage(media_filename.c_str(), false);
if (!media) if (!media)
{ {
g_host_interface->ReportFormattedError("Failed to open CD image from save state: '%s'.", g_host_interface->ReportFormattedError(
g_host_interface->TranslateString("System", "Failed to open CD image from save state: '%s'."),
media_filename.c_str()); media_filename.c_str());
return false; return false;
} }
@ -1172,9 +1176,11 @@ void UpdateMemoryCards()
{ {
if (s_running_game_code.empty()) if (s_running_game_code.empty())
{ {
g_host_interface->AddFormattedOSDMessage(5.0f, g_host_interface->AddFormattedOSDMessage(
5.0f,
g_host_interface->TranslateString("System",
"Per-game memory card cannot be used for slot %u as the running " "Per-game memory card cannot be used for slot %u as the running "
"game has no code. Using shared card instead.", "game has no code. Using shared card instead."),
i + 1u); i + 1u);
card = MemoryCard::Open(g_host_interface->GetSharedMemoryCardPath(i)); card = MemoryCard::Open(g_host_interface->GetSharedMemoryCardPath(i));
} }
@ -1194,9 +1200,11 @@ void UpdateMemoryCards()
} }
else if (s_running_game_title.empty()) else if (s_running_game_title.empty())
{ {
g_host_interface->AddFormattedOSDMessage(5.0f, g_host_interface->AddFormattedOSDMessage(
5.0f,
g_host_interface->TranslateString("System",
"Per-game memory card cannot be used for slot %u as the running " "Per-game memory card cannot be used for slot %u as the running "
"game has no title. Using shared card instead.", "game has no title. Using shared card instead."),
i + 1u); i + 1u);
card = MemoryCard::Open(g_host_interface->GetSharedMemoryCardPath(i)); card = MemoryCard::Open(g_host_interface->GetSharedMemoryCardPath(i));
} }
@ -1211,7 +1219,9 @@ void UpdateMemoryCards()
{ {
if (g_settings.memory_card_paths[i].empty()) if (g_settings.memory_card_paths[i].empty())
{ {
g_host_interface->AddFormattedOSDMessage(10.0f, "Memory card path for slot %u is missing, using default.", g_host_interface->AddFormattedOSDMessage(
10.0f,
g_host_interface->TranslateString("System", "Memory card path for slot %u is missing, using default."),
i + 1u); i + 1u);
card = MemoryCard::Open(g_host_interface->GetSharedMemoryCardPath(i)); card = MemoryCard::Open(g_host_interface->GetSharedMemoryCardPath(i));
} }
@ -1255,7 +1265,8 @@ bool InsertMedia(const char* path)
if (g_settings.HasAnyPerGameMemoryCards()) 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(); UpdateMemoryCards();
} }
@ -1269,6 +1280,9 @@ void RemoveMedia()
void UpdateRunningGame(const char* path, CDImage* image) void UpdateRunningGame(const char* path, CDImage* image)
{ {
if (s_running_game_path == path)
return;
s_running_game_path.clear(); s_running_game_path.clear();
s_running_game_code.clear(); s_running_game_code.clear();
s_running_game_title.clear(); s_running_game_title.clear();

View file

@ -48,6 +48,13 @@ enum class CPUExecutionMode : u8
Count Count
}; };
enum class PGXPMode : u8
{
Disabled,
Memory,
CPU
};
enum class GPURenderer : u8 enum class GPURenderer : u8
{ {
#ifdef WIN32 #ifdef WIN32

View file

@ -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() void LibretroHostInterface::InitLogging()
{ {
if (s_libretro_log_callback_registered) if (s_libretro_log_callback_registered)
@ -84,6 +101,7 @@ bool LibretroHostInterface::Initialize()
return false; return false;
LoadSettings(); LoadSettings();
FixIncompatibleSettings(true);
UpdateLogging(); UpdateLogging();
return true; return true;
} }
@ -352,7 +370,7 @@ void LibretroHostInterface::OnSystemDestroyed()
m_using_hardware_renderer = false; m_using_hardware_renderer = false;
} }
static std::array<retro_core_option_definition, 30> s_option_definitions = {{ static std::array<retro_core_option_definition, 31> s_option_definitions = {{
{"duckstation_Console.Region", {"duckstation_Console.Region",
"Console Region", "Console Region",
"Determines which region/hardware to emulate. Auto-Detect will use the region of the disc inserted.", "Determines which region/hardware to emulate. Auto-Detect will use the region of the disc inserted.",
@ -483,6 +501,12 @@ static std::array<retro_core_option_definition, 30> s_option_definitions = {{
"Uses screen coordinates as a fallback when tracking vertices through memory fails. May improve PGXP compatibility.", "Uses screen coordinates as a fallback when tracking vertices through memory fails. May improve PGXP compatibility.",
{{"true", "Enabled"}, {"false", "Disabled"}}, {{"true", "Enabled"}, {"false", "Disabled"}},
"false"}, "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", {"duckstation_Display.CropMode",
"Crop Mode", "Crop Mode",
"Changes how much of the image is cropped. Some games display garbage in the overscan area which is typically " "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<retro_core_option_definition, 30> s_option_definitions = {{
"Sets the core-provided aspect ratio.", "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", "4:3"}, {"16:9", "16:9"}, {"2:1", "2:1 (VRAM 1:1)"}, {"1:1", "1:1"}},
"4:3"}, "4:3"},
{"duckstation_MemoryCards.LoadFromSaveStates", {"duckstation_Main.LoadDevicesFromSaveStates",
"Load Memory Cards From Save States", "Load Devices From Save States",
"Sets whether the contents of memory cards will be loaded when a save state is loaded.", "Sets whether the contents of devices and memory cards will be loaded when a save state is loaded.",
{{"true", "Enabled"}, {"false", "Disabled"}}, {{"true", "Enabled"}, {"false", "Disabled"}},
"false"}, "false"},
{"duckstation_MemoryCards.Card1Type", {"duckstation_MemoryCards.Card1Type",
@ -520,7 +544,7 @@ static std::array<retro_core_option_definition, 30> s_option_definitions = {{
"When using a playlist (m3u) and per-game (title) memory cards, a single memory card " "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.", "will be used for all discs. If unchecked, a separate card will be used for each disc.",
{{"true", "Enabled"}, {"false", "Disabled"}}, {{"true", "Enabled"}, {"false", "Disabled"}},
"false"}, "true"},
{"duckstation_Controller1.Type", {"duckstation_Controller1.Type",
"Controller 1 Type", "Controller 1 Type",
"Sets the type of controller for Slot 1.", "Sets the type of controller for Slot 1.",
@ -590,7 +614,7 @@ bool LibretroHostInterface::HasCoreVariablesChanged()
void LibretroHostInterface::LoadSettings() void LibretroHostInterface::LoadSettings()
{ {
LibretroSettingsInterface si; LibretroSettingsInterface si;
g_settings.Load(si); HostInterface::LoadSettings(si);
// Assume BIOS files are located in system directory. // Assume BIOS files are located in system directory.
const char* system_directory = nullptr; const char* system_directory = nullptr;
@ -608,6 +632,7 @@ void LibretroHostInterface::UpdateSettings()
{ {
Settings old_settings(std::move(g_settings)); Settings old_settings(std::move(g_settings));
LoadSettings(); LoadSettings();
FixIncompatibleSettings(false);
if (g_settings.gpu_resolution_scale != old_settings.gpu_resolution_scale && if (g_settings.gpu_resolution_scale != old_settings.gpu_resolution_scale &&
g_settings.gpu_renderer != GPURenderer::Software) g_settings.gpu_renderer != GPURenderer::Software)
@ -650,6 +675,11 @@ void LibretroHostInterface::CheckForSettingsChanges(const Settings& old_settings
UpdateLogging(); UpdateLogging();
} }
void LibretroHostInterface::InitRumbleInterface()
{
m_rumble_interface_valid = g_retro_environment_callback(RETRO_ENVIRONMENT_GET_RUMBLE_INTERFACE, &m_rumble_interface);
}
void LibretroHostInterface::UpdateControllers() void LibretroHostInterface::UpdateControllers()
{ {
g_retro_input_poll_callback(); g_retro_input_poll_callback();
@ -698,12 +728,21 @@ void LibretroHostInterface::UpdateControllersDigitalController(u32 index)
{DigitalController::Button::R1, RETRO_DEVICE_ID_JOYPAD_R}, {DigitalController::Button::R1, RETRO_DEVICE_ID_JOYPAD_R},
{DigitalController::Button::R2, RETRO_DEVICE_ID_JOYPAD_R2}}}; {DigitalController::Button::R2, RETRO_DEVICE_ID_JOYPAD_R2}}};
if (m_supports_input_bitmasks)
{
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<u16>(1u) << it.second)) != 0u);
}
else
{
for (const auto& it : mapping) for (const auto& it : mapping)
{ {
const int16_t state = g_retro_input_state_callback(index, RETRO_DEVICE_JOYPAD, 0, it.second); const int16_t state = g_retro_input_state_callback(index, RETRO_DEVICE_JOYPAD, 0, it.second);
controller->SetButtonState(it.first, state != 0); controller->SetButtonState(it.first, state != 0);
} }
} }
}
void LibretroHostInterface::UpdateControllersAnalogController(u32 index) void LibretroHostInterface::UpdateControllersAnalogController(u32 index)
{ {
@ -734,17 +773,34 @@ void LibretroHostInterface::UpdateControllersAnalogController(u32 index)
{AnalogController::Axis::RightX, {RETRO_DEVICE_INDEX_ANALOG_RIGHT, RETRO_DEVICE_ID_ANALOG_X}}, {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}}}}; {AnalogController::Axis::RightY, {RETRO_DEVICE_INDEX_ANALOG_RIGHT, RETRO_DEVICE_ID_ANALOG_Y}}}};
if (m_supports_input_bitmasks)
{
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<u16>(1u) << it.second)) != 0u);
}
else
{
for (const auto& it : button_mapping) for (const auto& it : button_mapping)
{ {
const int16_t state = g_retro_input_state_callback(index, RETRO_DEVICE_JOYPAD, 0, it.second); const int16_t state = g_retro_input_state_callback(index, RETRO_DEVICE_JOYPAD, 0, it.second);
controller->SetButtonState(it.first, state != 0); controller->SetButtonState(it.first, state != 0);
} }
}
for (const auto& it : axis_mapping) for (const auto& it : axis_mapping)
{ {
const int16_t state = g_retro_input_state_callback(index, RETRO_DEVICE_ANALOG, it.second.first, it.second.second); const int16_t state = g_retro_input_state_callback(index, RETRO_DEVICE_ANALOG, it.second.first, it.second.second);
controller->SetAxisState(static_cast<s32>(it.first), std::clamp(static_cast<float>(state) / 32767.0f, -1.0f, 1.0f)); controller->SetAxisState(static_cast<s32>(it.first), std::clamp(static_cast<float>(state) / 32767.0f, -1.0f, 1.0f));
} }
if (m_rumble_interface_valid)
{
const u16 strong = static_cast<u16>(static_cast<u32>(controller->GetVibrationMotorStrength(0) * 65565.0f));
const u16 weak = static_cast<u16>(static_cast<u32>(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<GPURenderer> RetroHwContextToRenderer(retro_hw_context_type type) static std::optional<GPURenderer> RetroHwContextToRenderer(retro_hw_context_type type)

View file

@ -11,10 +11,7 @@ public:
LibretroHostInterface(); LibretroHostInterface();
~LibretroHostInterface() override; ~LibretroHostInterface() override;
static void InitLogging(); void InitInterfaces();
static bool SetCoreOptions();
static bool HasCoreVariablesChanged();
static void InitDiskControlInterface();
ALWAYS_INLINE u32 GetResolutionScale() const { return g_settings.gpu_resolution_scale; } ALWAYS_INLINE u32 GetResolutionScale() const { return g_settings.gpu_resolution_scale; }
@ -51,6 +48,12 @@ protected:
void CheckForSettingsChanges(const Settings& old_settings) override; void CheckForSettingsChanges(const Settings& old_settings) override;
private: private:
bool SetCoreOptions();
bool HasCoreVariablesChanged();
void InitLogging();
void InitDiskControlInterface();
void InitRumbleInterface();
void LoadSettings(); void LoadSettings();
void UpdateSettings(); void UpdateSettings();
void UpdateControllers(); void UpdateControllers();
@ -86,6 +89,11 @@ private:
bool m_hw_render_callback_valid = false; bool m_hw_render_callback_valid = false;
bool m_using_hardware_renderer = false; bool m_using_hardware_renderer = false;
std::optional<u32> m_next_disc_index; std::optional<u32> 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; extern LibretroHostInterface g_libretro_host_interface;

View file

@ -122,12 +122,7 @@ RETRO_API size_t retro_get_memory_size(unsigned id)
RETRO_API void retro_set_environment(retro_environment_t f) RETRO_API void retro_set_environment(retro_environment_t f)
{ {
g_retro_environment_callback = f; g_retro_environment_callback = f;
g_libretro_host_interface.InitInterfaces();
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();
} }
RETRO_API void retro_set_video_refresh(retro_video_refresh_t f) RETRO_API void retro_set_video_refresh(retro_video_refresh_t f)

View file

@ -68,6 +68,7 @@ set(TS_FILES
translations/duckstation-qt_de.ts translations/duckstation-qt_de.ts
translations/duckstation-qt_es.ts translations/duckstation-qt_es.ts
translations/duckstation-qt_he.ts translations/duckstation-qt_he.ts
translations/duckstation-qt_it.ts
translations/duckstation-qt_pt-br.ts translations/duckstation-qt_pt-br.ts
translations/duckstation-qt_pt-pt.ts translations/duckstation-qt_pt-pt.ts
translations/duckstation-qt_zh-cn.ts translations/duckstation-qt_zh-cn.ts

View file

@ -1,4 +1,5 @@
#include "advancedsettingswidget.h" #include "advancedsettingswidget.h"
#include "mainwindow.h"
#include "settingsdialog.h" #include "settingsdialog.h"
#include "settingwidgetbinder.h" #include "settingwidgetbinder.h"
@ -8,7 +9,7 @@ AdvancedSettingsWidget::AdvancedSettingsWidget(QtHostInterface* host_interface,
m_ui.setupUi(this); m_ui.setupUi(this);
for (u32 i = 0; i < static_cast<u32>(LOGLEVEL_COUNT); i++) for (u32 i = 0; i < static_cast<u32>(LOGLEVEL_COUNT); i++)
m_ui.logLevel->addItem(tr(Settings::GetLogLevelDisplayName(static_cast<LOGLEVEL>(i)))); m_ui.logLevel->addItem(qApp->translate("LogLevel", Settings::GetLogLevelDisplayName(static_cast<LOGLEVEL>(i))));
SettingWidgetBinder::BindWidgetToEnumSetting(m_host_interface, m_ui.logLevel, "Logging", "LogLevel", SettingWidgetBinder::BindWidgetToEnumSetting(m_host_interface, m_ui.logLevel, "Logging", "LogLevel",
&Settings::ParseLogLevelName, &Settings::GetLogLevelName, &Settings::ParseLogLevelName, &Settings::GetLogLevelName,
@ -27,9 +28,12 @@ AdvancedSettingsWidget::AdvancedSettingsWidget(QtHostInterface* host_interface,
SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.cpuRecompilerMemoryExceptions, "CPU", SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.cpuRecompilerMemoryExceptions, "CPU",
"RecompilerMemoryExceptions", false); "RecompilerMemoryExceptions", false);
SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.showDebugMenu, "Main", "ShowDebugMenu");
SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.gpuUseDebugDevice, "GPU", "UseDebugDevice"); SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.gpuUseDebugDevice, "GPU", "UseDebugDevice");
connect(m_ui.resetToDefaultButton, &QPushButton::clicked, this, &AdvancedSettingsWidget::onResetToDefaultClicked); 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"), 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. " tr("Enables the usage of debug devices and shaders for rendering APIs which support them. "

View file

@ -207,7 +207,14 @@
<string>System Settings</string> <string>System Settings</string>
</property> </property>
<layout class="QFormLayout" name="formLayout"> <layout class="QFormLayout" name="formLayout">
<item row="0" column="0" colspan="2"> <item row="0" column="0">
<widget class="QCheckBox" name="showDebugMenu">
<property name="text">
<string>Show Debug Menu</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QCheckBox" name="gpuUseDebugDevice"> <widget class="QCheckBox" name="gpuUseDebugDevice">
<property name="text"> <property name="text">
<string>Use Debug Host GPU Device</string> <string>Use Debug Host GPU Device</string>

View file

@ -11,7 +11,10 @@ AudioSettingsWidget::AudioSettingsWidget(QtHostInterface* host_interface, QWidge
m_ui.setupUi(this); m_ui.setupUi(this);
for (u32 i = 0; i < static_cast<u32>(AudioBackend::Count); i++) for (u32 i = 0; i < static_cast<u32>(AudioBackend::Count); i++)
m_ui.audioBackend->addItem(tr(Settings::GetAudioBackendDisplayName(static_cast<AudioBackend>(i)))); {
m_ui.audioBackend->addItem(
qApp->translate("AudioBackend", Settings::GetAudioBackendDisplayName(static_cast<AudioBackend>(i))));
}
SettingWidgetBinder::BindWidgetToEnumSetting(m_host_interface, m_ui.audioBackend, "Audio", "Backend", SettingWidgetBinder::BindWidgetToEnumSetting(m_host_interface, m_ui.audioBackend, "Audio", "Backend",
&Settings::ParseAudioBackend, &Settings::GetAudioBackendName, &Settings::ParseAudioBackend, &Settings::GetAudioBackendName,

View file

@ -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_TAG_URL[] = "https://api.github.com/repos/stenzek/duckstation/tags";
static constexpr char LATEST_RELEASE_URL[] = static constexpr char LATEST_RELEASE_URL[] =
"https://api.github.com/repos/stenzek/duckstation/releases/tags/" SCM_RELEASE_TAG; "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; static constexpr char UPDATE_ASSET_FILENAME[] = SCM_RELEASE_ASSET;
#endif #endif
@ -196,10 +197,11 @@ void AutoUpdaterDialog::getLatestReleaseComplete(QNetworkReply* reply)
m_download_url = asset_obj["browser_download_url"].toString(); m_download_url = asset_obj["browser_download_url"].toString();
if (!m_download_url.isEmpty()) 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( m_ui.newVersion->setText(
tr("New Version: %1 (%2)").arg(m_latest_sha).arg(doc_object["published_at"].toString())); 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(); exec();
emit updateCheckCompleted(); emit updateCheckCompleted();
return; return;
@ -223,6 +225,68 @@ void AutoUpdaterDialog::getLatestReleaseComplete(QNetworkReply* reply)
#endif #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<int>(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("<ul>");
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("<li>%1 <i>(%2)</i></li>").arg(message.toHtmlEscaped()).arg(author.toHtmlEscaped());
}
changes_html += "</ul>";
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<int>(reply->error()));
}
#endif
m_ui.downloadAndInstall->setEnabled(true);
}
void AutoUpdaterDialog::downloadUpdateClicked() void AutoUpdaterDialog::downloadUpdateClicked()
{ {
QUrl url(m_download_url); QUrl url(m_download_url);

View file

@ -28,6 +28,9 @@ private Q_SLOTS:
void getLatestTagComplete(QNetworkReply* reply); void getLatestTagComplete(QNetworkReply* reply);
void getLatestReleaseComplete(QNetworkReply* reply); void getLatestReleaseComplete(QNetworkReply* reply);
void queueGetChanges();
void getChangesComplete(QNetworkReply* reply);
void downloadUpdateClicked(); void downloadUpdateClicked();
void skipThisUpdateClicked(); void skipThisUpdateClicked();
void remindMeLaterClicked(); void remindMeLaterClicked();

View file

@ -82,6 +82,9 @@
<property name="text"> <property name="text">
<string>Download and Install...</string> <string>Download and Install...</string>
</property> </property>
<property name="enabled">
<bool>false</bool>
</property>
</widget> </widget>
</item> </item>
<item> <item>

View file

@ -11,10 +11,16 @@ ConsoleSettingsWidget::ConsoleSettingsWidget(QtHostInterface* host_interface, QW
m_ui.setupUi(this); m_ui.setupUi(this);
for (u32 i = 0; i < static_cast<u32>(ConsoleRegion::Count); i++) for (u32 i = 0; i < static_cast<u32>(ConsoleRegion::Count); i++)
m_ui.region->addItem(tr(Settings::GetConsoleRegionDisplayName(static_cast<ConsoleRegion>(i)))); {
m_ui.region->addItem(
qApp->translate("ConsoleRegion", Settings::GetConsoleRegionDisplayName(static_cast<ConsoleRegion>(i))));
}
for (u32 i = 0; i < static_cast<u32>(CPUExecutionMode::Count); i++) for (u32 i = 0; i < static_cast<u32>(CPUExecutionMode::Count); i++)
m_ui.cpuExecutionMode->addItem(tr(Settings::GetCPUExecutionModeDisplayName(static_cast<CPUExecutionMode>(i)))); {
m_ui.cpuExecutionMode->addItem(
qApp->translate("CPUExecutionMode", Settings::GetCPUExecutionModeDisplayName(static_cast<CPUExecutionMode>(i))));
}
SettingWidgetBinder::BindWidgetToEnumSetting(m_host_interface, m_ui.region, "Console", "Region", SettingWidgetBinder::BindWidgetToEnumSetting(m_host_interface, m_ui.region, "Console", "Region",
&Settings::ParseConsoleRegionName, &Settings::GetConsoleRegionName, &Settings::ParseConsoleRegionName, &Settings::GetConsoleRegionName,

View file

@ -9,6 +9,7 @@
#include <QtCore/QSignalBlocker> #include <QtCore/QSignalBlocker>
#include <QtCore/QTimer> #include <QtCore/QTimer>
#include <QtGui/QCursor> #include <QtGui/QCursor>
#include <QtGui/QGuiApplication>
#include <QtGui/QKeyEvent> #include <QtGui/QKeyEvent>
#include <QtWidgets/QFileDialog> #include <QtWidgets/QFileDialog>
#include <QtWidgets/QInputDialog> #include <QtWidgets/QInputDialog>
@ -86,7 +87,7 @@ void ControllerSettingsWidget::createPortSettingsUi(int index, PortSettingsUI* u
for (int i = 0; i < static_cast<int>(ControllerType::Count); i++) for (int i = 0; i < static_cast<int>(ControllerType::Count); i++)
{ {
ui->controller_type->addItem( ui->controller_type->addItem(
QString::fromUtf8(Settings::GetControllerTypeDisplayName(static_cast<ControllerType>(i)))); qApp->translate("ControllerType", Settings::GetControllerTypeDisplayName(static_cast<ControllerType>(i))));
} }
ControllerType ctype = ControllerType ctype =
Settings::ParseControllerTypeName( Settings::ParseControllerTypeName(

View file

@ -212,6 +212,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<None Include="translations\duckstation-qt_es.ts" /> <None Include="translations\duckstation-qt_es.ts" />
<None Include="translations\duckstation-qt_it.ts" />
</ItemGroup> </ItemGroup>
<Target Name="CopyCommonDataFiles" AfterTargets="Build" Inputs="@(CommonDataFiles)" Outputs="@(CommonDataFiles -> '$(BinaryOutputDir)%(RecursiveDir)%(Filename)%(Extension)')"> <Target Name="CopyCommonDataFiles" AfterTargets="Build" Inputs="@(CommonDataFiles)" Outputs="@(CommonDataFiles -> '$(BinaryOutputDir)%(RecursiveDir)%(Filename)%(Extension)')">
<Message Text="Copying common data files" Importance="High" /> <Message Text="Copying common data files" Importance="High" />

View file

@ -113,15 +113,28 @@
<Image Include="duckstation-qt.ico" /> <Image Include="duckstation-qt.ico" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<QtTs Include="translations\duckstation-qt_de.ts" /> <QtTs Include="translations\duckstation-qt_de.ts">
<QtTs Include="translations\duckstation-qt_he.ts" /> <Filter>translations</Filter>
<QtTs Include="translations\duckstation-qt_pt-br.ts" /> </QtTs>
<QtTs Include="translations\duckstation-qt_pt-pt.ts" /> <QtTs Include="translations\duckstation-qt_he.ts">
<QtTs Include="translations\duckstation-qt_zh-cn.ts" /> <Filter>translations</Filter>
</QtTs>
<QtTs Include="translations\duckstation-qt_pt-br.ts">
<Filter>translations</Filter>
</QtTs>
<QtTs Include="translations\duckstation-qt_pt-pt.ts">
<Filter>translations</Filter>
</QtTs>
<QtTs Include="translations\duckstation-qt_zh-cn.ts">
<Filter>translations</Filter>
</QtTs>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<None Include="translations\duckstation-qt_es.ts"> <None Include="translations\duckstation-qt_es.ts">
<Filter>translations</Filter> <Filter>translations</Filter>
</None> </None>
<None Include="translations\duckstation-qt_it.ts">
<Filter>translations</Filter>
</None>
</ItemGroup> </ItemGroup>
</Project> </Project>

View file

@ -74,6 +74,11 @@ void GamePropertiesDialog::populate(const GameListEntry* ge)
} }
populateTracksInfo(ge->path); 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) void GamePropertiesDialog::populateCompatibilityInfo(const std::string& game_code)
@ -99,12 +104,50 @@ void GamePropertiesDialog::populateCompatibilityInfo(const std::string& game_cod
void GamePropertiesDialog::setupAdditionalUi() void GamePropertiesDialog::setupAdditionalUi()
{ {
for (u8 i = 0; i < static_cast<u8>(DiscRegion::Count); i++) for (u8 i = 0; i < static_cast<u8>(DiscRegion::Count); i++)
m_ui.region->addItem(tr(Settings::GetDiscRegionDisplayName(static_cast<DiscRegion>(i)))); m_ui.region->addItem(qApp->translate("DiscRegion", Settings::GetDiscRegionDisplayName(static_cast<DiscRegion>(i))));
for (int i = 0; i < static_cast<int>(GameListCompatibilityRating::Count); i++) for (int i = 0; i < static_cast<int>(GameListCompatibilityRating::Count); i++)
{ {
m_ui.compatibility->addItem( m_ui.compatibility->addItem(
tr(GameList::GetGameListCompatibilityRatingString(static_cast<GameListCompatibilityRating>(i)))); qApp->translate("GameListCompatibilityRating",
GameList::GetGameListCompatibilityRatingString(static_cast<GameListCompatibilityRating>(i))));
}
m_ui.userAspectRatio->addItem(tr("(unchanged)"));
for (u32 i = 0; i < static_cast<u32>(DisplayAspectRatio::Count); i++)
{
m_ui.userAspectRatio->addItem(
QString::fromUtf8(Settings::GetDisplayAspectRatioName(static_cast<DisplayAspectRatio>(i))));
}
m_ui.userCropMode->addItem(tr("(unchanged)"));
for (u32 i = 0; i < static_cast<u32>(DisplayCropMode::Count); i++)
{
m_ui.userCropMode->addItem(
qApp->translate("DisplayCropMode", Settings::GetDisplayCropModeDisplayName(static_cast<DisplayCropMode>(i))));
}
m_ui.userControllerType1->addItem(tr("(unchanged)"));
for (u32 i = 0; i < static_cast<u32>(ControllerType::Count); i++)
{
m_ui.userControllerType1->addItem(
qApp->translate("ControllerType", Settings::GetControllerTypeDisplayName(static_cast<ControllerType>(i))));
}
m_ui.userControllerType2->addItem(tr("(unchanged)"));
for (u32 i = 0; i < static_cast<u32>(ControllerType::Count); i++)
{
m_ui.userControllerType2->addItem(
qApp->translate("ControllerType", Settings::GetControllerTypeDisplayName(static_cast<ControllerType>(i))));
}
QGridLayout* traits_layout = new QGridLayout(m_ui.compatibilityTraits);
for (u32 i = 0; i < static_cast<u32>(GameSettings::Trait::Count); i++)
{
m_trait_checkboxes[i] = new QCheckBox(
qApp->translate("GameSettingsTrait", GameSettings::GetTraitDisplayName(static_cast<GameSettings::Trait>(i))),
m_ui.compatibilityTraits);
traits_layout->addWidget(m_trait_checkboxes[i], i / 2, i % 2);
} }
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); 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"}}; {"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_ui.tracks->clearContents();
m_image_path = image_path; m_path = image_path;
std::unique_ptr<CDImage> image = CDImage::Open(image_path.c_str()); std::unique_ptr<CDImage> image = CDImage::Open(image_path.c_str());
if (!image) if (!image)
@ -147,14 +190,74 @@ void GamePropertiesDialog::populateTracksInfo(const std::string& image_path)
const CDImage::TrackMode mode = image->GetTrackMode(static_cast<u8>(track)); const CDImage::TrackMode mode = image->GetTrackMode(static_cast<u8>(track));
const int row = static_cast<int>(track - 1u); const int row = static_cast<int>(track - 1u);
m_ui.tracks->insertRow(row); m_ui.tracks->insertRow(row);
m_ui.tracks->setItem(row, 0, new QTableWidgetItem(tr("%1").arg(track))); m_ui.tracks->setItem(row, 0, new QTableWidgetItem(QStringLiteral("%1").arg(track)));
m_ui.tracks->setItem(row, 1, new QTableWidgetItem(tr(track_mode_strings[static_cast<u32>(mode)]))); m_ui.tracks->setItem(row, 1, new QTableWidgetItem(track_mode_strings[static_cast<u32>(mode)]));
m_ui.tracks->setItem(row, 2, new QTableWidgetItem(MSFTotString(position))); 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, 3, new QTableWidgetItem(MSFTotString(length)));
m_ui.tracks->setItem(row, 4, new QTableWidgetItem(tr("<not computed>"))); m_ui.tracks->setItem(row, 4, new QTableWidgetItem(tr("<not computed>")));
} }
} }
void GamePropertiesDialog::populateGameSettings()
{
const GameSettings::Entry& gs = m_game_settings;
for (u32 i = 0; i < static_cast<u32>(GameSettings::Trait::Count); i++)
{
QSignalBlocker sb(m_trait_checkboxes[i]);
m_trait_checkboxes[i]->setChecked(gs.HasTrait(static_cast<GameSettings::Trait>(i)));
}
if (gs.display_active_start_offset.has_value())
{
QSignalBlocker sb(m_ui.displayActiveStartOffset);
m_ui.displayActiveStartOffset->setValue(static_cast<int>(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<int>(gs.display_active_end_offset.value()));
}
if (gs.display_crop_mode.has_value())
{
QSignalBlocker sb(m_ui.userCropMode);
m_ui.userCropMode->setCurrentIndex(static_cast<int>(gs.display_crop_mode.value()) + 1);
}
if (gs.display_aspect_ratio.has_value())
{
QSignalBlocker sb(m_ui.userAspectRatio);
m_ui.userAspectRatio->setCurrentIndex(static_cast<int>(gs.display_aspect_ratio.value()) + 1);
}
if (gs.controller_1_type.has_value())
{
QSignalBlocker sb(m_ui.userControllerType1);
m_ui.userControllerType1->setCurrentIndex(static_cast<int>(gs.controller_1_type.value()) + 1);
}
if (gs.controller_2_type.has_value())
{
QSignalBlocker sb(m_ui.userControllerType2);
m_ui.userControllerType2->setCurrentIndex(static_cast<int>(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) void GamePropertiesDialog::closeEvent(QCloseEvent* ev)
{ {
deleteLater(); deleteLater();
@ -186,6 +289,69 @@ void GamePropertiesDialog::connectUi()
connect(m_ui.exportCompatibilityInfo, &QPushButton::clicked, this, connect(m_ui.exportCompatibilityInfo, &QPushButton::clicked, this,
&GamePropertiesDialog::onExportCompatibilityInfoClicked); &GamePropertiesDialog::onExportCompatibilityInfoClicked);
connect(m_ui.close, &QPushButton::clicked, this, &QDialog::close); connect(m_ui.close, &QPushButton::clicked, this, &QDialog::close);
connect(m_ui.userAspectRatio, QOverload<int>::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<DisplayAspectRatio>(index - 1);
saveGameSettings();
});
connect(m_ui.userCropMode, QOverload<int>::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<DisplayCropMode>(index - 1);
saveGameSettings();
});
connect(m_ui.userControllerType1, QOverload<int>::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<ControllerType>(index - 1);
saveGameSettings();
});
connect(m_ui.userControllerType2, QOverload<int>::of(&QComboBox::currentIndexChanged), [this](int index) {
if (index <= 0)
m_game_settings.controller_2_type.reset();
else
m_game_settings.controller_2_type = static_cast<ControllerType>(index - 1);
saveGameSettings();
});
connect(m_ui.userWidescreenHack, &QCheckBox::stateChanged, [this](int state) {
if (state == Qt::PartiallyChecked)
m_game_settings.gpu_widescreen_hack.reset();
else
m_game_settings.gpu_widescreen_hack = (state == Qt::Checked);
saveGameSettings();
});
for (u32 i = 0; i < static_cast<u32>(GameSettings::Trait::Count); i++)
{
connect(m_trait_checkboxes[i], &QCheckBox::toggled, [this, i](bool checked) {
m_game_settings.SetTrait(static_cast<GameSettings::Trait>(i), checked);
saveGameSettings();
});
}
connect(m_ui.displayActiveStartOffset, QOverload<int>::of(&QSpinBox::valueChanged), [this](int value) {
if (value == 0)
m_game_settings.display_active_start_offset.reset();
else
m_game_settings.display_active_start_offset = static_cast<s16>(value);
saveGameSettings();
});
connect(m_ui.displayActiveEndOffset, QOverload<int>::of(&QSpinBox::valueChanged), [this](int value) {
if (value == 0)
m_game_settings.display_active_end_offset.reset();
else
m_game_settings.display_active_end_offset = static_cast<s16>(value);
saveGameSettings();
});
} }
void GamePropertiesDialog::fillEntryFromUi(GameListCompatibilityEntry* entry) void GamePropertiesDialog::fillEntryFromUi(GameListCompatibilityEntry* entry)
@ -263,10 +429,10 @@ void GamePropertiesDialog::onExportCompatibilityInfoClicked()
void GamePropertiesDialog::computeTrackHashes() void GamePropertiesDialog::computeTrackHashes()
{ {
if (m_image_path.empty()) if (m_path.empty())
return; return;
std::unique_ptr<CDImage> image = CDImage::Open(m_image_path.c_str()); std::unique_ptr<CDImage> image = CDImage::Open(m_path.c_str());
if (!image) if (!image)
return; return;

View file

@ -1,6 +1,8 @@
#pragma once #pragma once
#include "core/game_settings.h"
#include "ui_gamepropertiesdialog.h" #include "ui_gamepropertiesdialog.h"
#include <QtWidgets/QDialog> #include <QtWidgets/QDialog>
#include <array>
struct GameListEntry; struct GameListEntry;
struct GameListCompatibilityEntry; struct GameListCompatibilityEntry;
@ -40,15 +42,22 @@ private:
void connectUi(); void connectUi();
void populateCompatibilityInfo(const std::string& game_code); void populateCompatibilityInfo(const std::string& game_code);
void populateTracksInfo(const std::string& image_path); void populateTracksInfo(const std::string& image_path);
void populateGameSettings();
void saveGameSettings();
void fillEntryFromUi(GameListCompatibilityEntry* entry); void fillEntryFromUi(GameListCompatibilityEntry* entry);
void computeTrackHashes(); void computeTrackHashes();
void onResize(); void onResize();
Ui::GamePropertiesDialog m_ui; Ui::GamePropertiesDialog m_ui;
std::array<QCheckBox*, static_cast<u32>(GameSettings::Trait::Count)> m_trait_checkboxes{};
QtHostInterface* m_host_interface; QtHostInterface* m_host_interface;
std::string m_image_path; std::string m_path;
std::string m_game_code;
std::string m_game_title;
GameSettings::Entry m_game_settings;
bool m_compatibility_info_changed = false; bool m_compatibility_info_changed = false;
bool m_tracks_hashed = false; bool m_tracks_hashed = false;

View file

@ -6,18 +6,28 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>722</width> <width>793</width>
<height>466</height> <height>647</height>
</rect> </rect>
</property> </property>
<property name="windowTitle"> <property name="windowTitle">
<string>Dialog</string> <string>Dialog</string>
</property> </property>
<property name="windowIcon"> <property name="windowIcon">
<iconset resource="resources/icons.qrc"> <iconset>
<normaloff>:/icons/duck.png</normaloff>:/icons/duck.png</iconset> <normaloff>:/icons/duck.png</normaloff>:/icons/duck.png</iconset>
</property> </property>
<layout class="QFormLayout" name="formLayout"> <layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QTabWidget" name="tabWidget">
<property name="currentIndex">
<number>0</number>
</property>
<widget class="QWidget" name="tab">
<attribute name="title">
<string>Properties</string>
</attribute>
<layout class="QFormLayout" name="formLayout_2">
<item row="0" column="0"> <item row="0" column="0">
<widget class="QLabel" name="label_4"> <widget class="QLabel" name="label_4">
<property name="text"> <property name="text">
@ -125,14 +135,14 @@
</item> </item>
</layout> </layout>
</item> </item>
<item row="8" column="0"> <item row="8" column="0" colspan="2">
<widget class="QLabel" name="label_8"> <widget class="QLabel" name="label_8">
<property name="text"> <property name="text">
<string>Tracks:</string> <string>Tracks:</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="8" column="1"> <item row="9" column="0" colspan="2">
<widget class="QTableWidget" name="tracks"> <widget class="QTableWidget" name="tracks">
<property name="editTriggers"> <property name="editTriggers">
<set>QAbstractItemView::NoEditTriggers</set> <set>QAbstractItemView::NoEditTriggers</set>
@ -170,7 +180,172 @@
</column> </column>
</widget> </widget>
</item> </item>
<item row="10" column="0" colspan="2"> </layout>
</widget>
<widget class="QWidget" name="tab_3">
<attribute name="title">
<string>User Settings</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<widget class="QGroupBox" name="groupBox_4">
<property name="title">
<string>GPU Settings</string>
</property>
<layout class="QFormLayout" name="formLayout_5">
<item row="0" column="0">
<widget class="QLabel" name="label_17">
<property name="text">
<string>Crop Mode:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QComboBox" name="userCropMode"/>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_18">
<property name="text">
<string>Aspect Ratio:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QComboBox" name="userAspectRatio"/>
</item>
<item row="2" column="0" colspan="2">
<widget class="QCheckBox" name="userWidescreenHack">
<property name="text">
<string>Widescreen Hack</string>
</property>
<property name="tristate">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox_3">
<property name="title">
<string>Controller Settings</string>
</property>
<layout class="QFormLayout" name="formLayout_4">
<item row="0" column="0">
<widget class="QLabel" name="label_15">
<property name="text">
<string>Controller 1 Type:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QComboBox" name="userControllerType1"/>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_16">
<property name="text">
<string>Controller 2 Type:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QComboBox" name="userControllerType2"/>
</item>
</layout>
</widget>
</item>
<item>
<spacer name="verticalSpacer_3">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<widget class="QWidget" name="tab_2">
<attribute name="title">
<string>Compatibility Settings</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QGroupBox" name="compatibilityTraits">
<property name="title">
<string>Traits</string>
</property>
</widget>
</item>
<item>
<widget class="QGroupBox" name="compatibilityOverrides">
<property name="title">
<string>Overrides</string>
</property>
<layout class="QFormLayout" name="formLayout">
<item row="0" column="0">
<widget class="QLabel" name="label_10">
<property name="text">
<string>Display Active Offset:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QSpinBox" name="displayActiveStartOffset">
<property name="minimum">
<number>-5000</number>
</property>
<property name="maximum">
<number>5000</number>
</property>
<property name="value">
<number>0</number>
</property>
</widget>
</item>
<item>
<widget class="QSpinBox" name="displayActiveEndOffset">
<property name="minimum">
<number>-5000</number>
</property>
<property name="maximum">
<number>5000</number>
</property>
<property name="value">
<number>0</number>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2"> <layout class="QHBoxLayout" name="horizontalLayout_2">
<item> <item>
<spacer name="horizontalSpacer"> <spacer name="horizontalSpacer">

View file

@ -16,12 +16,15 @@ GeneralSettingsWidget::GeneralSettingsWidget(QtHostInterface* host_interface, QW
SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.confirmPowerOff, "Main", "ConfirmPowerOff", true); SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.confirmPowerOff, "Main", "ConfirmPowerOff", true);
SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.loadDevicesFromSaveStates, "Main", SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.loadDevicesFromSaveStates, "Main",
"LoadDevicesFromSaveStates", false); "LoadDevicesFromSaveStates", false);
SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.applyGameSettings, "Main", "ApplyGameSettings",
true);
SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.showOSDMessages, "Display", "ShowOSDMessages", SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.showOSDMessages, "Display", "ShowOSDMessages",
true); true);
SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.showFPS, "Display", "ShowFPS", false); SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.showFPS, "Display", "ShowFPS", false);
SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.showVPS, "Display", "ShowVPS", false); SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.showVPS, "Display", "ShowVPS", false);
SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.showSpeed, "Display", "ShowSpeed", false); SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.showSpeed, "Display", "ShowSpeed", false);
SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.showResolution, "Display", "ShowResolution", false); SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.showResolution, "Display", "ShowResolution",
false);
SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.enableSpeedLimiter, "Main", "SpeedLimiterEnabled", SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.enableSpeedLimiter, "Main", "SpeedLimiterEnabled",
true); true);
@ -57,6 +60,10 @@ GeneralSettingsWidget::GeneralSettingsWidget(QtHostInterface* host_interface, QW
tr("When enabled, memory cards and controllers will be overwritten when save states are loaded. This can " tr("When enabled, memory cards and controllers will be overwritten when save states are loaded. This can "
"result in lost saves, and controller type mismatches. For deterministic save states, enable this option, " "result in lost saves, and controller type mismatches. For deterministic save states, enable this option, "
"otherwise leave disabled.")); "otherwise leave disabled."));
dialog->registerWidgetHelp(
m_ui.applyGameSettings, tr("Apply Per-Game Settings"), tr("Checked"),
tr("When enabled, per-game settings will be applied, and incompatible enhancements will be disabled. You should "
"leave this option enabled except when testing enhancements with incompatible games."));
dialog->registerWidgetHelp( dialog->registerWidgetHelp(
m_ui.enableSpeedLimiter, tr("Enable Speed Limiter"), tr("Checked"), m_ui.enableSpeedLimiter, tr("Enable Speed Limiter"), tr("Checked"),
tr("Throttles the emulation speed to the chosen speed above. If unchecked, the emulator will " tr("Throttles the emulation speed to the chosen speed above. If unchecked, the emulator will "
@ -82,26 +89,33 @@ GeneralSettingsWidget::GeneralSettingsWidget(QtHostInterface* host_interface, QW
tr("Shows the current emulation speed of the system in the top-right corner of the display as a percentage.")); tr("Shows the current emulation speed of the system in the top-right corner of the display as a percentage."));
// Since this one is compile-time selected, we don't put it in the .ui file. // Since this one is compile-time selected, we don't put it in the .ui file.
const int last_row_count = m_ui.formLayout_4->rowCount(); int current_col = 1;
int current_row = m_ui.formLayout_4->rowCount() - current_col;
#ifdef WITH_DISCORD_PRESENCE #ifdef WITH_DISCORD_PRESENCE
{ {
QCheckBox* enableDiscordPresence = new QCheckBox(tr("Enable Discord Presence"), m_ui.groupBox_4); QCheckBox* enableDiscordPresence = new QCheckBox(tr("Enable Discord Presence"), m_ui.groupBox_4);
SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, enableDiscordPresence, "Main", SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, enableDiscordPresence, "Main",
"EnableDiscordPresence"); "EnableDiscordPresence");
m_ui.formLayout_4->addWidget(enableDiscordPresence, last_row_count, 0); m_ui.formLayout_4->addWidget(enableDiscordPresence, current_row, current_col);
dialog->registerWidgetHelp(enableDiscordPresence, tr("Enable Discord Presence"), tr("Unchecked"), dialog->registerWidgetHelp(enableDiscordPresence, tr("Enable Discord Presence"), tr("Unchecked"),
tr("Shows the game you are currently playing as part of your profile in Discord.")); tr("Shows the game you are currently playing as part of your profile in Discord."));
current_col++;
current_row += (current_col / 2);
current_col %= 2;
} }
#endif #endif
if (AutoUpdaterDialog::isSupported()) if (AutoUpdaterDialog::isSupported())
{ {
QCheckBox* enableDiscordPresence = new QCheckBox(tr("Enable Automatic Update Check"), m_ui.groupBox_4); QCheckBox* enableDiscordPresence = new QCheckBox(tr("Enable Automatic Update Check"), m_ui.groupBox_4);
SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, enableDiscordPresence, "AutoUpdater", SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, enableDiscordPresence, "AutoUpdater",
"CheckAtStartup"); "CheckAtStartup", true);
m_ui.formLayout_4->addWidget(enableDiscordPresence, last_row_count, 1); m_ui.formLayout_4->addWidget(enableDiscordPresence, current_row, current_col);
dialog->registerWidgetHelp(enableDiscordPresence, tr("Enable Automatic Update Check"), tr("Checked"), dialog->registerWidgetHelp(enableDiscordPresence, tr("Enable Automatic Update Check"), tr("Checked"),
tr("Automatically checks for updates to the program on startup. Updates can be deferred " tr("Automatically checks for updates to the program on startup. Updates can be deferred "
"until later or skipped entirely.")); "until later or skipped entirely."));
current_col++;
current_row += (current_col / 2);
current_col %= 2;
} }
} }

View file

@ -32,6 +32,20 @@
<string>Behaviour</string> <string>Behaviour</string>
</property> </property>
<layout class="QGridLayout" name="formLayout_4"> <layout class="QGridLayout" name="formLayout_4">
<item row="0" column="1">
<widget class="QCheckBox" name="confirmPowerOff">
<property name="text">
<string>Confirm Power Off</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QCheckBox" name="renderToMain">
<property name="text">
<string>Render To Main Window</string>
</property>
</widget>
</item>
<item row="0" column="0"> <item row="0" column="0">
<widget class="QCheckBox" name="pauseOnStart"> <widget class="QCheckBox" name="pauseOnStart">
<property name="text"> <property name="text">
@ -39,10 +53,10 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="0" column="1"> <item row="2" column="0">
<widget class="QCheckBox" name="confirmPowerOff"> <widget class="QCheckBox" name="startFullscreen">
<property name="text"> <property name="text">
<string>Confirm Power Off</string> <string>Start Fullscreen</string>
</property> </property>
</widget> </widget>
</item> </item>
@ -60,17 +74,10 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="2" column="0"> <item row="3" column="0">
<widget class="QCheckBox" name="startFullscreen"> <widget class="QCheckBox" name="applyGameSettings">
<property name="text"> <property name="text">
<string>Start Fullscreen</string> <string>Apply Per-Game Settings</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QCheckBox" name="renderToMain">
<property name="text">
<string>Render To Main Window</string>
</property> </property>
</widget> </widget>
</item> </item>

View file

@ -44,6 +44,7 @@ GPUSettingsWidget::GPUSettingsWidget(QtHostInterface* host_interface, QWidget* p
SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.pgxpTextureCorrection, "GPU", SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.pgxpTextureCorrection, "GPU",
"PGXPTextureCorrection", true); "PGXPTextureCorrection", true);
SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.pgxpVertexCache, "GPU", "PGXPVertexCache", false); SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.pgxpVertexCache, "GPU", "PGXPVertexCache", false);
SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.pgxpCPUMode, "GPU", "PGXPCPUMode", false);
connect(m_ui.resolutionScale, QOverload<int>::of(&QComboBox::currentIndexChanged), this, connect(m_ui.resolutionScale, QOverload<int>::of(&QComboBox::currentIndexChanged), this,
&GPUSettingsWidget::updateScaledDitheringEnabled); &GPUSettingsWidget::updateScaledDitheringEnabled);
@ -128,7 +129,8 @@ GPUSettingsWidget::GPUSettingsWidget(QtHostInterface* host_interface, QWidget* p
m_ui.widescreenHack, tr("Widescreen Hack"), tr("Unchecked"), m_ui.widescreenHack, tr("Widescreen Hack"), tr("Unchecked"),
tr("Scales vertex positions in screen-space to a widescreen aspect ratio, essentially " tr("Scales vertex positions in screen-space to a widescreen aspect ratio, essentially "
"increasing the field of view from 4:3 to 16:9 in 3D games. <br>For 2D games, or games which " "increasing the field of view from 4:3 to 16:9 in 3D games. <br>For 2D games, or games which "
"use pre-rendered backgrounds, this enhancement will not work as expected. <b><u>May not be compatible with all games.</u></b>")); "use pre-rendered backgrounds, this enhancement will not work as expected. <b><u>May not be compatible with all "
"games.</u></b>"));
dialog->registerWidgetHelp( dialog->registerWidgetHelp(
m_ui.pgxpEnable, tr("Geometry Correction"), tr("Unchecked"), m_ui.pgxpEnable, tr("Geometry Correction"), tr("Unchecked"),
tr("Reduces \"wobbly\" polygons and \"warping\" textures that are common in PS1 games. <br>Only " tr("Reduces \"wobbly\" polygons and \"warping\" textures that are common in PS1 games. <br>Only "
@ -142,6 +144,10 @@ GPUSettingsWidget::GPUSettingsWidget(QtHostInterface* host_interface, QWidget* p
dialog->registerWidgetHelp(m_ui.pgxpVertexCache, tr("Vertex Cache"), tr("Unchecked"), dialog->registerWidgetHelp(m_ui.pgxpVertexCache, tr("Vertex Cache"), tr("Unchecked"),
tr("Uses screen coordinates as a fallback when tracking vertices through memory fails. " tr("Uses screen coordinates as a fallback when tracking vertices through memory fails. "
"May improve PGXP compatibility.")); "May improve PGXP compatibility."));
dialog->registerWidgetHelp(
m_ui.pgxpCPUMode, tr("CPU Mode"), tr("Unchecked"),
tr("Tries to track vertex manipulation through the CPU. Some games require this option for PGXP to be effective. "
"Very slow, and incompatible with the recompiler."));
} }
GPUSettingsWidget::~GPUSettingsWidget() = default; GPUSettingsWidget::~GPUSettingsWidget() = default;
@ -157,7 +163,10 @@ void GPUSettingsWidget::updateScaledDitheringEnabled()
void GPUSettingsWidget::setupAdditionalUi() void GPUSettingsWidget::setupAdditionalUi()
{ {
for (u32 i = 0; i < static_cast<u32>(GPURenderer::Count); i++) for (u32 i = 0; i < static_cast<u32>(GPURenderer::Count); i++)
m_ui.renderer->addItem(QString::fromUtf8(Settings::GetRendererDisplayName(static_cast<GPURenderer>(i)))); {
m_ui.renderer->addItem(
qApp->translate("GPURenderer", Settings::GetRendererDisplayName(static_cast<GPURenderer>(i))));
}
for (u32 i = 0; i < static_cast<u32>(DisplayAspectRatio::Count); i++) for (u32 i = 0; i < static_cast<u32>(DisplayAspectRatio::Count); i++)
{ {
@ -168,7 +177,7 @@ void GPUSettingsWidget::setupAdditionalUi()
for (u32 i = 0; i < static_cast<u32>(DisplayCropMode::Count); i++) for (u32 i = 0; i < static_cast<u32>(DisplayCropMode::Count); i++)
{ {
m_ui.displayCropMode->addItem( m_ui.displayCropMode->addItem(
QString::fromUtf8(Settings::GetDisplayCropModeDisplayName(static_cast<DisplayCropMode>(i)))); qApp->translate("DisplayCropMode", Settings::GetDisplayCropModeDisplayName(static_cast<DisplayCropMode>(i))));
} }
std::array<QString, GPU::MAX_RESOLUTION_SCALE + 1> resolution_suffixes = {{ std::array<QString, GPU::MAX_RESOLUTION_SCALE + 1> resolution_suffixes = {{
@ -255,4 +264,5 @@ void GPUSettingsWidget::updatePGXPSettingsEnabled()
m_ui.pgxpCulling->setEnabled(enabled); m_ui.pgxpCulling->setEnabled(enabled);
m_ui.pgxpTextureCorrection->setEnabled(enabled); m_ui.pgxpTextureCorrection->setEnabled(enabled);
m_ui.pgxpVertexCache->setEnabled(enabled); m_ui.pgxpVertexCache->setEnabled(enabled);
m_ui.pgxpCPUMode->setEnabled(enabled);
} }

View file

@ -215,6 +215,13 @@
</property> </property>
</widget> </widget>
</item> </item>
<item>
<widget class="QCheckBox" name="pgxpCPUMode">
<property name="text">
<string>CPU Mode</string>
</property>
</widget>
</item>
</layout> </layout>
</widget> </widget>
</item> </item>

View file

@ -4,6 +4,7 @@
#include "inputbindingwidgets.h" #include "inputbindingwidgets.h"
#include "qthostinterface.h" #include "qthostinterface.h"
#include "qtutils.h" #include "qtutils.h"
#include <QtCore/QCoreApplication>
#include <QtCore/QTimer> #include <QtCore/QTimer>
#include <QtGui/QKeyEvent> #include <QtGui/QKeyEvent>
#include <QtWidgets/QGridLayout> #include <QtWidgets/QGridLayout>
@ -63,7 +64,7 @@ void HotkeySettingsWidget::createButtons()
std::string section_name("Hotkeys"); std::string section_name("Hotkeys");
std::string key_name(hi.name.GetCharArray()); std::string key_name(hi.name.GetCharArray());
layout->addWidget(new QLabel(QString::fromUtf8(hi.display_name), container), target_row, 0); layout->addWidget(new QLabel(qApp->translate("Hotkeys", hi.display_name), container), target_row, 0);
layout->addWidget( layout->addWidget(
new InputButtonBindingWidget(m_host_interface, std::move(section_name), std::move(key_name), container), new InputButtonBindingWidget(m_host_interface, std::move(section_name), std::move(key_name), container),
target_row, 1); target_row, 1);

View file

@ -310,6 +310,34 @@ void MainWindow::onRemoveDiscActionTriggered()
m_host_interface->changeDisc(QString()); m_host_interface->changeDisc(QString());
} }
void MainWindow::onViewToolbarActionToggled(bool checked)
{
m_host_interface->SetBoolSettingValue("UI", "ShowToolbar", checked);
m_ui.toolBar->setVisible(checked);
}
void MainWindow::onViewStatusBarActionToggled(bool checked)
{
m_host_interface->SetBoolSettingValue("UI", "ShowStatusBar", checked);
m_ui.statusBar->setVisible(checked);
}
void MainWindow::onViewGameListActionTriggered()
{
if (m_emulation_running)
m_host_interface->pauseSystem(true);
switchToGameListView();
}
void MainWindow::onViewSystemDisplayTriggered()
{
if (m_emulation_running)
{
switchToEmulationView();
m_host_interface->pauseSystem(false);
}
}
void MainWindow::onGitHubRepositoryActionTriggered() void MainWindow::onGitHubRepositoryActionTriggered()
{ {
QtUtils::OpenURL(this, "https://github.com/stenzek/duckstation/"); QtUtils::OpenURL(this, "https://github.com/stenzek/duckstation/");
@ -431,6 +459,14 @@ void MainWindow::setupAdditionalUi()
{ {
setWindowTitle(getWindowTitle()); setWindowTitle(getWindowTitle());
const bool toolbar_visible = m_host_interface->GetBoolSettingValue("UI", "ShowToolbar", true);
m_ui.actionViewToolbar->setChecked(toolbar_visible);
m_ui.toolBar->setVisible(toolbar_visible);
const bool status_bar_visible = m_host_interface->GetBoolSettingValue("UI", "ShowStatusBar", true);
m_ui.actionViewStatusBar->setChecked(status_bar_visible);
m_ui.statusBar->setVisible(status_bar_visible);
m_game_list_widget = new GameListWidget(m_ui.mainContainer); m_game_list_widget = new GameListWidget(m_ui.mainContainer);
m_game_list_widget->initialize(m_host_interface); m_game_list_widget->initialize(m_host_interface);
m_ui.mainContainer->insertWidget(0, m_game_list_widget); m_ui.mainContainer->insertWidget(0, m_game_list_widget);
@ -451,6 +487,8 @@ void MainWindow::setupAdditionalUi()
m_status_frame_time_widget->setFixedSize(190, 16); m_status_frame_time_widget->setFixedSize(190, 16);
m_status_frame_time_widget->hide(); m_status_frame_time_widget->hide();
updateDebugMenuVisibility();
for (u32 i = 0; i < static_cast<u32>(CPUExecutionMode::Count); i++) for (u32 i = 0; i < static_cast<u32>(CPUExecutionMode::Count); i++)
{ {
const CPUExecutionMode mode = static_cast<CPUExecutionMode>(i); const CPUExecutionMode mode = static_cast<CPUExecutionMode>(i);
@ -507,6 +545,7 @@ void MainWindow::updateEmulationActions(bool starting, bool running)
m_ui.actionPause->setDisabled(starting || !running); m_ui.actionPause->setDisabled(starting || !running);
m_ui.actionChangeDisc->setDisabled(starting || !running); m_ui.actionChangeDisc->setDisabled(starting || !running);
m_ui.actionScreenshot->setDisabled(starting || !running); m_ui.actionScreenshot->setDisabled(starting || !running);
m_ui.actionViewSystemDisplay->setEnabled(starting || running);
m_ui.menuChangeDisc->setDisabled(starting || !running); m_ui.menuChangeDisc->setDisabled(starting || !running);
m_ui.actionSaveState->setDisabled(starting || !running); m_ui.actionSaveState->setDisabled(starting || !running);
@ -615,6 +654,10 @@ void MainWindow::connectSignals()
[this]() { doSettings(SettingsDialog::Category::AudioSettings); }); [this]() { doSettings(SettingsDialog::Category::AudioSettings); });
connect(m_ui.actionAdvancedSettings, &QAction::triggered, connect(m_ui.actionAdvancedSettings, &QAction::triggered,
[this]() { doSettings(SettingsDialog::Category::AdvancedSettings); }); [this]() { doSettings(SettingsDialog::Category::AdvancedSettings); });
connect(m_ui.actionViewToolbar, &QAction::toggled, this, &MainWindow::onViewToolbarActionToggled);
connect(m_ui.actionViewStatusBar, &QAction::toggled, this, &MainWindow::onViewStatusBarActionToggled);
connect(m_ui.actionViewGameList, &QAction::triggered, this, &MainWindow::onViewGameListActionTriggered);
connect(m_ui.actionViewSystemDisplay, &QAction::triggered, this, &MainWindow::onViewSystemDisplayTriggered);
connect(m_ui.actionGitHubRepository, &QAction::triggered, this, &MainWindow::onGitHubRepositoryActionTriggered); connect(m_ui.actionGitHubRepository, &QAction::triggered, this, &MainWindow::onGitHubRepositoryActionTriggered);
connect(m_ui.actionIssueTracker, &QAction::triggered, this, &MainWindow::onIssueTrackerActionTriggered); connect(m_ui.actionIssueTracker, &QAction::triggered, this, &MainWindow::onIssueTrackerActionTriggered);
connect(m_ui.actionDiscordServer, &QAction::triggered, this, &MainWindow::onDiscordServerActionTriggered); connect(m_ui.actionDiscordServer, &QAction::triggered, this, &MainWindow::onDiscordServerActionTriggered);
@ -847,6 +890,12 @@ void MainWindow::startupUpdateCheck()
checkForUpdates(false); checkForUpdates(false);
} }
void MainWindow::updateDebugMenuVisibility()
{
const bool visible = m_host_interface->GetBoolSettingValue("Main", "ShowDebugMenu", false);
m_ui.menuDebug->menuAction()->setVisible(visible);
}
void MainWindow::checkForUpdates(bool display_message) void MainWindow::checkForUpdates(bool display_message)
{ {
if (!AutoUpdaterDialog::isSupported()) if (!AutoUpdaterDialog::isSupported())

View file

@ -29,6 +29,10 @@ public:
/// Performs update check if enabled in settings. /// Performs update check if enabled in settings.
void startupUpdateCheck(); void startupUpdateCheck();
public Q_SLOTS:
/// Updates debug menu visibility (hides if disabled).
void updateDebugMenuVisibility();
private Q_SLOTS: private Q_SLOTS:
void reportError(const QString& message); void reportError(const QString& message);
void reportMessage(const QString& message); void reportMessage(const QString& message);
@ -58,6 +62,10 @@ private Q_SLOTS:
void onChangeDiscFromPlaylistMenuAboutToShow(); void onChangeDiscFromPlaylistMenuAboutToShow();
void onChangeDiscFromPlaylistMenuAboutToHide(); void onChangeDiscFromPlaylistMenuAboutToHide();
void onRemoveDiscActionTriggered(); void onRemoveDiscActionTriggered();
void onViewToolbarActionToggled(bool checked);
void onViewStatusBarActionToggled(bool checked);
void onViewGameListActionTriggered();
void onViewSystemDisplayTriggered();
void onGitHubRepositoryActionTriggered(); void onGitHubRepositoryActionTriggered();
void onIssueTrackerActionTriggered(); void onIssueTrackerActionTriggered();
void onDiscordServerActionTriggered(); void onDiscordServerActionTriggered();

View file

@ -30,7 +30,7 @@
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>754</width> <width>754</width>
<height>22</height> <height>30</height>
</rect> </rect>
</property> </property>
<widget class="QMenu" name="menuSystem"> <widget class="QMenu" name="menuSystem">
@ -161,8 +161,19 @@
<addaction name="actionDebugShowTimersState"/> <addaction name="actionDebugShowTimersState"/>
<addaction name="actionDebugShowMDECState"/> <addaction name="actionDebugShowMDECState"/>
</widget> </widget>
<widget class="QMenu" name="menu_View">
<property name="title">
<string>&amp;View</string>
</property>
<addaction name="actionViewToolbar"/>
<addaction name="actionViewStatusBar"/>
<addaction name="separator"/>
<addaction name="actionViewGameList"/>
<addaction name="actionViewSystemDisplay"/>
</widget>
<addaction name="menuSystem"/> <addaction name="menuSystem"/>
<addaction name="menuSettings"/> <addaction name="menuSettings"/>
<addaction name="menu_View"/>
<addaction name="menuDebug"/> <addaction name="menuDebug"/>
<addaction name="menuHelp"/> <addaction name="menuHelp"/>
</widget> </widget>
@ -560,6 +571,41 @@
<string>Resumes the last save state created.</string> <string>Resumes the last save state created.</string>
</property> </property>
</action> </action>
<action name="actionViewToolbar">
<property name="checkable">
<bool>true</bool>
</property>
<property name="checked">
<bool>true</bool>
</property>
<property name="text">
<string>&amp;Toolbar</string>
</property>
</action>
<action name="actionViewStatusBar">
<property name="checkable">
<bool>true</bool>
</property>
<property name="checked">
<bool>true</bool>
</property>
<property name="text">
<string>&amp;Status Bar</string>
</property>
</action>
<action name="actionViewGameList">
<property name="text">
<string>&amp;Game List</string>
</property>
</action>
<action name="actionViewSystemDisplay">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>System &amp;Display</string>
</property>
</action>
</widget> </widget>
<resources> <resources>
<include location="resources/icons.qrc"/> <include location="resources/icons.qrc"/>

View file

@ -76,7 +76,7 @@ void MemoryCardSettingsWidget::createPortSettingsUi(SettingsDialog* dialog, int
for (int i = 0; i < static_cast<int>(MemoryCardType::Count); i++) for (int i = 0; i < static_cast<int>(MemoryCardType::Count); i++)
{ {
ui->memory_card_type->addItem( ui->memory_card_type->addItem(
QString::fromUtf8(Settings::GetMemoryCardTypeDisplayName(static_cast<MemoryCardType>(i)))); qApp->translate("MemoryCardType", Settings::GetMemoryCardTypeDisplayName(static_cast<MemoryCardType>(i))));
} }
const MemoryCardType default_value = (index == 0) ? MemoryCardType::PerGameTitle : MemoryCardType::None; const MemoryCardType default_value = (index == 0) ? MemoryCardType::PerGameTitle : MemoryCardType::None;

View file

@ -34,7 +34,10 @@
Log_SetChannel(QtHostInterface); Log_SetChannel(QtHostInterface);
#ifdef WIN32 #ifdef WIN32
#include "common/windows_headers.h"
#include "frontend-common/d3d11_host_display.h" #include "frontend-common/d3d11_host_display.h"
#include <KnownFolders.h>
#include <ShlObj.h>
#endif #endif
QtHostInterface::QtHostInterface(QObject* parent) : QObject(parent), CommonHostInterface() QtHostInterface::QtHostInterface(QObject* parent) : QObject(parent), CommonHostInterface()
@ -58,6 +61,7 @@ std::vector<std::pair<QString, QString>> QtHostInterface::getAvailableLanguageLi
{QStringLiteral("Deutsch"), QStringLiteral("de")}, {QStringLiteral("Deutsch"), QStringLiteral("de")},
{QStringLiteral("Español"), QStringLiteral("es")}, {QStringLiteral("Español"), QStringLiteral("es")},
{QStringLiteral("עברית"), QStringLiteral("he")}, {QStringLiteral("עברית"), QStringLiteral("he")},
{QStringLiteral("Italiano"), QStringLiteral("it")},
{QStringLiteral("Português (Pt)"), QStringLiteral("pt-pt")}, {QStringLiteral("Português (Pt)"), QStringLiteral("pt-pt")},
{QStringLiteral("Português (Br)"), QStringLiteral("pt-br")}, {QStringLiteral("Português (Br)"), QStringLiteral("pt-br")},
{QStringLiteral("简体中文"), QStringLiteral("zh-cn")}}; {QStringLiteral("简体中文"), QStringLiteral("zh-cn")}};
@ -279,16 +283,18 @@ void QtHostInterface::setDefaultSettings()
m_settings_interface->Save(); m_settings_interface->Save();
CommonHostInterface::LoadSettings(*m_settings_interface.get()); CommonHostInterface::LoadSettings(*m_settings_interface.get());
CommonHostInterface::ApplyGameSettings(false);
CommonHostInterface::FixIncompatibleSettings(false);
} }
CheckForSettingsChanges(old_settings); CheckForSettingsChanges(old_settings);
} }
void QtHostInterface::applySettings() void QtHostInterface::applySettings(bool display_osd_messages /* = false */)
{ {
if (!isOnWorkerThread()) if (!isOnWorkerThread())
{ {
QMetaObject::invokeMethod(this, "applySettings", Qt::QueuedConnection); QMetaObject::invokeMethod(this, "applySettings", Qt::QueuedConnection, Q_ARG(bool, display_osd_messages));
return; return;
} }
@ -296,6 +302,8 @@ void QtHostInterface::applySettings()
{ {
std::lock_guard<std::recursive_mutex> guard(m_settings_mutex); std::lock_guard<std::recursive_mutex> guard(m_settings_mutex);
CommonHostInterface::LoadSettings(*m_settings_interface.get()); CommonHostInterface::LoadSettings(*m_settings_interface.get());
CommonHostInterface::ApplyGameSettings(display_osd_messages);
CommonHostInterface::FixIncompatibleSettings(display_osd_messages);
} }
CheckForSettingsChanges(old_settings); CheckForSettingsChanges(old_settings);
@ -648,6 +656,7 @@ void QtHostInterface::OnSystemPerformanceCountersUpdated()
void QtHostInterface::OnRunningGameChanged() void QtHostInterface::OnRunningGameChanged()
{ {
CommonHostInterface::OnRunningGameChanged(); CommonHostInterface::OnRunningGameChanged();
applySettings(true);
if (!System::IsShutdown()) if (!System::IsShutdown())
{ {
@ -672,6 +681,7 @@ void QtHostInterface::LoadSettings()
CommonHostInterface::CheckSettings(*m_settings_interface.get()); CommonHostInterface::CheckSettings(*m_settings_interface.get());
CommonHostInterface::LoadSettings(*m_settings_interface.get()); CommonHostInterface::LoadSettings(*m_settings_interface.get());
CommonHostInterface::FixIncompatibleSettings(false);
} }
void QtHostInterface::SetDefaultSettings(SettingsInterface& si) void QtHostInterface::SetDefaultSettings(SettingsInterface& si)
@ -1179,6 +1189,49 @@ void QtHostInterface::wakeThread()
QMetaObject::invokeMethod(m_worker_thread_event_loop, "quit", Qt::QueuedConnection); QMetaObject::invokeMethod(m_worker_thread_event_loop, "quit", Qt::QueuedConnection);
} }
static std::string GetFontPath(const char* name)
{
#ifdef WIN32
PWSTR folder_path;
if (FAILED(SHGetKnownFolderPath(FOLDERID_Fonts, 0, nullptr, &folder_path)))
return StringUtil::StdStringFromFormat("C:\\Windows\\Fonts\\%s", name);
std::string font_path(StringUtil::WideStringToUTF8String(folder_path));
CoTaskMemFree(folder_path);
font_path += "\\";
font_path += name;
return font_path;
#else
return name;
#endif
}
static bool AddImGuiFont(const std::string& language, float size, float framebuffer_scale)
{
std::string path;
const ImWchar* range = nullptr;
#ifdef WIN32
if (language == "jp")
{
path = GetFontPath("msgothic.ttc");
range = ImGui::GetIO().Fonts->GetGlyphRangesJapanese();
}
else if (language == "zh-cn")
{
path = GetFontPath("msyh.ttc");
range = ImGui::GetIO().Fonts->GetGlyphRangesChineseSimplifiedCommon();
}
#endif
if (!path.empty())
{
return (ImGui::GetIO().Fonts->AddFontFromFileTTF(path.c_str(), size * framebuffer_scale, nullptr, range) !=
nullptr);
}
return false;
}
void QtHostInterface::createImGuiContext(float framebuffer_scale) void QtHostInterface::createImGuiContext(float framebuffer_scale)
{ {
ImGui::CreateContext(); ImGui::CreateContext();
@ -1190,6 +1243,9 @@ void QtHostInterface::createImGuiContext(float framebuffer_scale)
ImGui::GetStyle().ScaleAllSizes(framebuffer_scale); ImGui::GetStyle().ScaleAllSizes(framebuffer_scale);
ImGui::StyleColorsDarker(); ImGui::StyleColorsDarker();
std::string language = GetStringSettingValue("Main", "Language", "");
if (!AddImGuiFont(language, 15.0f, framebuffer_scale))
ImGui::AddRobotoRegularFont(15.0f * framebuffer_scale); ImGui::AddRobotoRegularFont(15.0f * framebuffer_scale);
} }
@ -1198,6 +1254,24 @@ void QtHostInterface::destroyImGuiContext()
ImGui::DestroyContext(); ImGui::DestroyContext();
} }
TinyString QtHostInterface::TranslateString(const char* context, const char* str) const
{
const QString translated(m_translator->translate(context, str));
if (translated.isEmpty())
return TinyString(str);
return TinyString(translated.toUtf8().constData());
}
std::string QtHostInterface::TranslateStdString(const char* context, const char* str) const
{
const QString translated(m_translator->translate(context, str));
if (translated.isEmpty())
return std::string(str);
return translated.toStdString();
}
QtHostInterface::Thread::Thread(QtHostInterface* parent) : QThread(parent), m_parent(parent) {} QtHostInterface::Thread::Thread(QtHostInterface* parent) : QThread(parent), m_parent(parent) {}
QtHostInterface::Thread::~Thread() = default; QtHostInterface::Thread::~Thread() = default;

View file

@ -64,6 +64,9 @@ public:
void SetStringListSettingValue(const char* section, const char* key, const std::vector<std::string>& values); void SetStringListSettingValue(const char* section, const char* key, const std::vector<std::string>& values);
void RemoveSettingValue(const char* section, const char* key); void RemoveSettingValue(const char* section, const char* key);
TinyString TranslateString(const char* context, const char* str) const;
std::string TranslateStdString(const char* context, const char* str) const;
ALWAYS_INLINE const GameList* getGameList() const { return m_game_list.get(); } ALWAYS_INLINE const GameList* getGameList() const { return m_game_list.get(); }
ALWAYS_INLINE GameList* getGameList() { return m_game_list.get(); } ALWAYS_INLINE GameList* getGameList() { return m_game_list.get(); }
void refreshGameList(bool invalidate_cache = false, bool invalidate_database = false); void refreshGameList(bool invalidate_cache = false, bool invalidate_database = false);
@ -129,7 +132,7 @@ Q_SIGNALS:
public Q_SLOTS: public Q_SLOTS:
void setDefaultSettings(); void setDefaultSettings();
void applySettings(); void applySettings(bool display_osd_messages = false);
void updateInputMap(); void updateInputMap();
void applyInputProfile(const QString& profile_path); void applyInputProfile(const QString& profile_path);
void onDisplayWindowKeyEvent(int key, bool pressed); void onDisplayWindowKeyEvent(int key, bool pressed);

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -1,6 +1,10 @@
..\..\dep\msvc\qt\5.15.0\msvc2017_64\bin\lupdate.exe ./ -ts translations\duckstation-qt_de.ts set LUPDATE=..\..\dep\msvc\qt\5.15.0\msvc2017_64\bin\lupdate.exe ./ ../core/ ../frontend-common/ -tr-function-alias translate+=TranslateString -tr-function-alias translate+=TranslateStdString -tr-function-alias QT_TRANSLATE_NOOP+=TRANSLATABLE
..\..\dep\msvc\qt\5.15.0\msvc2017_64\bin\lupdate.exe ./ -ts translations\duckstation-qt_he.ts
..\..\dep\msvc\qt\5.15.0\msvc2017_64\bin\lupdate.exe ./ -ts translations\duckstation-qt_pt-br.ts %LUPDATE% -ts translations\duckstation-qt_de.ts
..\..\dep\msvc\qt\5.15.0\msvc2017_64\bin\lupdate.exe ./ -ts translations\duckstation-qt_pt-pt.ts %LUPDATE% -ts translations\duckstation-qt_es.ts
..\..\dep\msvc\qt\5.15.0\msvc2017_64\bin\lupdate.exe ./ -ts translations\duckstation-qt_zh-cn.ts %LUPDATE% -ts translations\duckstation-qt_he.ts
%LUPDATE% -ts translations\duckstation-qt_it.ts
%LUPDATE% -ts translations\duckstation-qt_pt-br.ts
%LUPDATE% -ts translations\duckstation-qt_pt-pt.ts
%LUPDATE% -ts translations\duckstation-qt_zh-cn.ts
pause pause

View file

@ -321,6 +321,12 @@ void SDLHostInterface::OnRunningGameChanged()
{ {
CommonHostInterface::OnRunningGameChanged(); CommonHostInterface::OnRunningGameChanged();
Settings old_settings(std::move(g_settings));
CommonHostInterface::LoadSettings(*m_settings_interface.get());
CommonHostInterface::ApplyGameSettings(true);
CommonHostInterface::FixIncompatibleSettings(true);
CheckForSettingsChanges(old_settings);
if (!System::GetRunningTitle().empty()) if (!System::GetRunningTitle().empty())
SDL_SetWindowTitle(m_window, System::GetRunningTitle().c_str()); SDL_SetWindowTitle(m_window, System::GetRunningTitle().c_str());
else else
@ -347,6 +353,8 @@ void SDLHostInterface::SaveAndUpdateSettings()
Settings old_settings(std::move(g_settings)); Settings old_settings(std::move(g_settings));
CommonHostInterface::LoadSettings(*m_settings_interface.get()); CommonHostInterface::LoadSettings(*m_settings_interface.get());
CommonHostInterface::ApplyGameSettings(false);
CommonHostInterface::FixIncompatibleSettings(false);
CheckForSettingsChanges(old_settings); CheckForSettingsChanges(old_settings);
m_settings_interface->Save(); m_settings_interface->Save();
@ -454,8 +462,9 @@ void SDLHostInterface::LoadSettings()
{ {
// Settings need to be loaded prior to creating the window for OpenGL bits. // Settings need to be loaded prior to creating the window for OpenGL bits.
m_settings_interface = std::make_unique<INISettingsInterface>(GetSettingsFileName()); m_settings_interface = std::make_unique<INISettingsInterface>(GetSettingsFileName());
m_settings_copy.Load(*m_settings_interface);
CommonHostInterface::LoadSettings(*m_settings_interface.get()); CommonHostInterface::LoadSettings(*m_settings_interface.get());
m_settings_copy = g_settings; CommonHostInterface::FixIncompatibleSettings(false);
} }
void SDLHostInterface::ReportError(const char* message) void SDLHostInterface::ReportError(const char* message)
@ -807,7 +816,7 @@ void SDLHostInterface::DrawQuickSettingsMenu()
if (ImGui::BeginMenu("CPU Execution Mode")) if (ImGui::BeginMenu("CPU Execution Mode"))
{ {
const CPUExecutionMode current = g_settings.cpu_execution_mode; const CPUExecutionMode current = m_settings_copy.cpu_execution_mode;
for (u32 i = 0; i < static_cast<u32>(CPUExecutionMode::Count); i++) for (u32 i = 0; i < static_cast<u32>(CPUExecutionMode::Count); i++)
{ {
if (ImGui::MenuItem(Settings::GetCPUExecutionModeDisplayName(static_cast<CPUExecutionMode>(i)), nullptr, if (ImGui::MenuItem(Settings::GetCPUExecutionModeDisplayName(static_cast<CPUExecutionMode>(i)), nullptr,
@ -874,6 +883,8 @@ void SDLHostInterface::DrawQuickSettingsMenu()
&m_settings_copy.gpu_pgxp_texture_correction, m_settings_copy.gpu_pgxp_enable); &m_settings_copy.gpu_pgxp_texture_correction, m_settings_copy.gpu_pgxp_enable);
settings_changed |= ImGui::MenuItem("PGXP Vertex Cache", nullptr, &m_settings_copy.gpu_pgxp_vertex_cache, settings_changed |= ImGui::MenuItem("PGXP Vertex Cache", nullptr, &m_settings_copy.gpu_pgxp_vertex_cache,
m_settings_copy.gpu_pgxp_enable); m_settings_copy.gpu_pgxp_enable);
settings_changed |=
ImGui::MenuItem("PGXP CPU Instructions", nullptr, &m_settings_copy.gpu_pgxp_cpu, m_settings_copy.gpu_pgxp_enable);
ImGui::EndMenu(); ImGui::EndMenu();
} }
@ -1347,6 +1358,7 @@ void SDLHostInterface::DrawSettingsWindow()
settings_changed |= ImGui::Checkbox("PGXP Culling", &m_settings_copy.gpu_pgxp_culling); settings_changed |= ImGui::Checkbox("PGXP Culling", &m_settings_copy.gpu_pgxp_culling);
settings_changed |= ImGui::Checkbox("PGXP Texture Correction", &m_settings_copy.gpu_pgxp_texture_correction); settings_changed |= ImGui::Checkbox("PGXP Texture Correction", &m_settings_copy.gpu_pgxp_texture_correction);
settings_changed |= ImGui::Checkbox("PGXP Vertex Cache", &m_settings_copy.gpu_pgxp_vertex_cache); settings_changed |= ImGui::Checkbox("PGXP Vertex Cache", &m_settings_copy.gpu_pgxp_vertex_cache);
settings_changed |= ImGui::Checkbox("PGXP CPU", &m_settings_copy.gpu_pgxp_cpu);
} }
ImGui::EndTabItem(); ImGui::EndTabItem();

View file

@ -71,6 +71,8 @@ bool CommonHostInterface::Initialize()
m_game_list->SetCacheFilename(GetUserDirectoryRelativePath("cache/gamelist.cache")); m_game_list->SetCacheFilename(GetUserDirectoryRelativePath("cache/gamelist.cache"));
m_game_list->SetDatabaseFilename(GetUserDirectoryRelativePath("cache/redump.dat")); m_game_list->SetDatabaseFilename(GetUserDirectoryRelativePath("cache/redump.dat"));
m_game_list->SetCompatibilityFilename(GetProgramDirectoryRelativePath("database/compatibility.xml")); m_game_list->SetCompatibilityFilename(GetProgramDirectoryRelativePath("database/compatibility.xml"));
m_game_list->SetGameSettingsFilename(GetProgramDirectoryRelativePath("database/gamesettings.ini"));
m_game_list->SetUserGameSettingsFilename(GetUserDirectoryRelativePath("gamesettings.ini"));
m_save_state_selector_ui = std::make_unique<FrontendCommon::SaveStateSelectorUI>(this); m_save_state_selector_ui = std::make_unique<FrontendCommon::SaveStateSelectorUI>(this);
@ -1238,44 +1240,51 @@ bool CommonHostInterface::AddRumbleToInputMap(const std::string& binding, u32 co
void CommonHostInterface::RegisterGeneralHotkeys() void CommonHostInterface::RegisterGeneralHotkeys()
{ {
RegisterHotkey(StaticString("General"), StaticString("FastForward"), StaticString("Fast Forward"), RegisterHotkey(StaticString("General"), StaticString("FastForward"), TRANSLATABLE("Hotkeys", "Fast Forward"),
[this](bool pressed) { [this](bool pressed) {
m_speed_limiter_temp_disabled = pressed; m_speed_limiter_temp_disabled = pressed;
UpdateSpeedLimiterState(); UpdateSpeedLimiterState();
}); });
RegisterHotkey(StaticString("General"), StaticString("ToggleFastForward"), StaticString("Toggle Fast Forward"), RegisterHotkey(StaticString("General"), StaticString("ToggleFastForward"),
[this](bool pressed) { StaticString(TRANSLATABLE("Hotkeys", "Toggle Fast Forward")), [this](bool pressed) {
if (!pressed) if (!pressed)
{ {
m_speed_limiter_temp_disabled = !m_speed_limiter_temp_disabled; m_speed_limiter_temp_disabled = !m_speed_limiter_temp_disabled;
UpdateSpeedLimiterState(); UpdateSpeedLimiterState();
AddFormattedOSDMessage(2.0f, "Speed limiter %s.", AddOSDMessage(m_speed_limiter_enabled ?
m_speed_limiter_enabled ? "enabled" : "disabled"); TranslateStdString("OSDMessage", "Speed limiter enabled.") :
TranslateStdString("OSDMessage", "Speed limiter disabled."),
2.0f);
} }
}); });
RegisterHotkey(StaticString("General"), StaticString("ToggleFullscreen"), StaticString("Toggle Fullscreen"), RegisterHotkey(StaticString("General"), StaticString("ToggleFullscreen"),
[this](bool pressed) { StaticString(TRANSLATABLE("Hotkeys", "Toggle Fullscreen")), [this](bool pressed) {
if (!pressed) if (!pressed)
SetFullscreen(!IsFullscreen()); SetFullscreen(!IsFullscreen());
}); });
RegisterHotkey(StaticString("General"), StaticString("TogglePause"), StaticString("Toggle Pause"), RegisterHotkey(StaticString("General"), StaticString("TogglePause"),
[this](bool pressed) { StaticString(TRANSLATABLE("Hotkeys", "Toggle Pause")), [this](bool pressed) {
if (System::IsValid() && !pressed) if (System::IsValid() && !pressed)
PauseSystem(!System::IsPaused()); PauseSystem(!System::IsPaused());
}); });
RegisterHotkey(StaticString("General"), StaticString("PowerOff"), StaticString("Power Off System"), RegisterHotkey(StaticString("General"), StaticString("PowerOff"),
[this](bool pressed) { StaticString(TRANSLATABLE("Hotkeys", "Power Off System")), [this](bool pressed) {
if (!pressed && System::IsValid()) if (!pressed && System::IsValid())
{ {
if (g_settings.confim_power_off && !m_batch_mode) if (g_settings.confim_power_off && !m_batch_mode)
{ {
SmallString confirmation_message("Are you sure you want to stop emulation?"); SmallString confirmation_message(
TranslateString("CommonHostInterface", "Are you sure you want to stop emulation?"));
if (g_settings.save_state_on_exit) if (g_settings.save_state_on_exit)
confirmation_message.AppendString("\n\nThe current state will be saved."); {
confirmation_message.AppendString("\n\n");
confirmation_message.AppendString(
TranslateString("CommonHostInterface", "The current state will be saved."));
}
if (!ConfirmMessage(confirmation_message)) if (!ConfirmMessage(confirmation_message))
{ {
@ -1288,13 +1297,14 @@ void CommonHostInterface::RegisterGeneralHotkeys()
} }
}); });
RegisterHotkey(StaticString("General"), StaticString("Screenshot"), StaticString("Save Screenshot"), RegisterHotkey(StaticString("General"), StaticString("Screenshot"),
[this](bool pressed) { StaticString(TRANSLATABLE("Hotkeys", "Save Screenshot")), [this](bool pressed) {
if (!pressed && System::IsValid()) if (!pressed && System::IsValid())
SaveScreenshot(); SaveScreenshot();
}); });
RegisterHotkey(StaticString("General"), StaticString("FrameStep"), StaticString("Frame Step"), [this](bool pressed) { RegisterHotkey(StaticString("General"), StaticString("FrameStep"),
StaticString(TRANSLATABLE("Hotkeys", "Frame Step")), [this](bool pressed) {
if (!pressed) if (!pressed)
{ {
DoFrameStep(); DoFrameStep();
@ -1305,18 +1315,19 @@ void CommonHostInterface::RegisterGeneralHotkeys()
void CommonHostInterface::RegisterGraphicsHotkeys() void CommonHostInterface::RegisterGraphicsHotkeys()
{ {
RegisterHotkey(StaticString("Graphics"), StaticString("ToggleSoftwareRendering"), RegisterHotkey(StaticString("Graphics"), StaticString("ToggleSoftwareRendering"),
StaticString("Toggle Software Rendering"), [this](bool pressed) { StaticString(TRANSLATABLE("Hotkeys", "Toggle Software Rendering")), [this](bool pressed) {
if (!pressed) if (!pressed)
ToggleSoftwareRendering(); ToggleSoftwareRendering();
}); });
RegisterHotkey( RegisterHotkey(StaticString("Graphics"), StaticString("TogglePGXP"),
StaticString("Graphics"), StaticString("TogglePGXP"), StaticString("Toggle PGXP"), [this](bool pressed) { StaticString(TRANSLATABLE("Hotkeys", "Toggle PGXP")), [this](bool pressed) {
if (!pressed) if (!pressed)
{ {
g_settings.gpu_pgxp_enable = !g_settings.gpu_pgxp_enable; g_settings.gpu_pgxp_enable = !g_settings.gpu_pgxp_enable;
g_gpu->UpdateSettings(); g_gpu->UpdateSettings();
AddFormattedOSDMessage(5.0f, "PGXP is now %s.", g_settings.gpu_pgxp_enable ? "enabled" : "disabled"); AddFormattedOSDMessage(5.0f, "PGXP is now %s.",
g_settings.gpu_pgxp_enable ? "enabled" : "disabled");
if (g_settings.gpu_pgxp_enable) if (g_settings.gpu_pgxp_enable)
PGXP::Initialize(); PGXP::Initialize();
@ -1328,13 +1339,13 @@ void CommonHostInterface::RegisterGraphicsHotkeys()
}); });
RegisterHotkey(StaticString("Graphics"), StaticString("IncreaseResolutionScale"), RegisterHotkey(StaticString("Graphics"), StaticString("IncreaseResolutionScale"),
StaticString("Increase Resolution Scale"), [this](bool pressed) { StaticString(TRANSLATABLE("Hotkeys", "Increase Resolution Scale")), [this](bool pressed) {
if (!pressed) if (!pressed)
ModifyResolutionScale(1); ModifyResolutionScale(1);
}); });
RegisterHotkey(StaticString("Graphics"), StaticString("DecreaseResolutionScale"), RegisterHotkey(StaticString("Graphics"), StaticString("DecreaseResolutionScale"),
StaticString("Decrease Resolution Scale"), [this](bool pressed) { StaticString(TRANSLATABLE("Hotkeys", "Decrease Resolution Scale")), [this](bool pressed) {
if (!pressed) if (!pressed)
ModifyResolutionScale(-1); ModifyResolutionScale(-1);
}); });
@ -1343,80 +1354,94 @@ void CommonHostInterface::RegisterGraphicsHotkeys()
void CommonHostInterface::RegisterSaveStateHotkeys() void CommonHostInterface::RegisterSaveStateHotkeys()
{ {
RegisterHotkey(StaticString("Save States"), StaticString("LoadSelectedSaveState"), RegisterHotkey(StaticString("Save States"), StaticString("LoadSelectedSaveState"),
StaticString("Load From Selected Slot"), [this](bool pressed) { StaticString(TRANSLATABLE("Hotkeys", "Load From Selected Slot")), [this](bool pressed) {
if (!pressed) if (!pressed)
m_save_state_selector_ui->LoadCurrentSlot(); m_save_state_selector_ui->LoadCurrentSlot();
}); });
RegisterHotkey(StaticString("Save States"), StaticString("SaveSelectedSaveState"), RegisterHotkey(StaticString("Save States"), StaticString("SaveSelectedSaveState"),
StaticString("Save To Selected Slot"), [this](bool pressed) { StaticString(TRANSLATABLE("Hotkeys", "Save To Selected Slot")), [this](bool pressed) {
if (!pressed) if (!pressed)
m_save_state_selector_ui->SaveCurrentSlot(); m_save_state_selector_ui->SaveCurrentSlot();
}); });
RegisterHotkey(StaticString("Save States"), StaticString("SelectPreviousSaveStateSlot"), RegisterHotkey(StaticString("Save States"), StaticString("SelectPreviousSaveStateSlot"),
StaticString("Select Previous Save Slot"), [this](bool pressed) { StaticString(TRANSLATABLE("Hotkeys", "Select Previous Save Slot")), [this](bool pressed) {
if (!pressed) if (!pressed)
m_save_state_selector_ui->SelectPreviousSlot(); m_save_state_selector_ui->SelectPreviousSlot();
}); });
RegisterHotkey(StaticString("Save States"), StaticString("SelectNextSaveStateSlot"), RegisterHotkey(StaticString("Save States"), StaticString("SelectNextSaveStateSlot"),
StaticString("Select Next Save Slot"), [this](bool pressed) { StaticString(TRANSLATABLE("Hotkeys", "Select Next Save Slot")), [this](bool pressed) {
if (!pressed) if (!pressed)
m_save_state_selector_ui->SelectNextSlot(); m_save_state_selector_ui->SelectNextSlot();
}); });
for (u32 global_i = 0; global_i < 2; global_i++) for (u32 slot = 1; slot <= PER_GAME_SAVE_STATE_SLOTS; slot++)
{ {
const bool global = ConvertToBoolUnchecked(global_i); RegisterHotkey(StaticString("Save States"), TinyString::FromFormat("LoadGameState%u", slot),
const u32 count = global ? GLOBAL_SAVE_STATE_SLOTS : PER_GAME_SAVE_STATE_SLOTS; TinyString::FromFormat(TRANSLATABLE("Hotkeys", "Load Game State %u"), slot),
for (u32 slot = 1; slot <= count; slot++) [this, slot](bool pressed) {
{
RegisterHotkey(StaticString("Save States"),
TinyString::FromFormat("Load%sState%u", global ? "Global" : "Game", slot),
TinyString::FromFormat("Load %s State %u", global ? "Global" : "Game", slot),
[this, global, slot](bool pressed) {
if (!pressed) if (!pressed)
LoadState(global, slot); LoadState(false, slot);
}); });
RegisterHotkey(StaticString("Save States"), RegisterHotkey(StaticString("Save States"), TinyString::FromFormat("SaveGameState%u", slot),
TinyString::FromFormat("Save%sState%u", global ? "Global" : "Game", slot), TinyString::FromFormat(TRANSLATABLE("Hotkeys", "Save Game State %u"), slot),
TinyString::FromFormat("Save %s State %u", global ? "Global" : "Game", slot), [this, slot](bool pressed) {
[this, global, slot](bool pressed) {
if (!pressed) if (!pressed)
SaveState(global, slot); SaveState(false, slot);
}); });
} }
for (u32 slot = 1; slot <= GLOBAL_SAVE_STATE_SLOTS; slot++)
{
RegisterHotkey(StaticString("Save States"), TinyString::FromFormat("LoadGlobalState%u", slot),
TinyString::FromFormat(TRANSLATABLE("Hotkeys", "Load Global State %u"), slot),
[this, slot](bool pressed) {
if (!pressed)
LoadState(true, slot);
});
RegisterHotkey(StaticString("Save States"), TinyString::FromFormat("SaveGlobalState%u", slot),
TinyString::FromFormat(TRANSLATABLE("Hotkeys", "Save Global State %u"), slot),
[this, slot](bool pressed) {
if (!pressed)
SaveState(true, slot);
});
} }
} }
void CommonHostInterface::RegisterAudioHotkeys() void CommonHostInterface::RegisterAudioHotkeys()
{ {
RegisterHotkey(StaticString("Audio"), StaticString("AudioMute"), StaticString("Toggle Mute"), [this](bool pressed) { RegisterHotkey(
StaticString("Audio"), StaticString("AudioMute"), StaticString(TRANSLATABLE("Hotkeys", "Toggle Mute")),
[this](bool pressed) {
if (System::IsValid() && !pressed) if (System::IsValid() && !pressed)
{ {
g_settings.audio_output_muted = !g_settings.audio_output_muted; g_settings.audio_output_muted = !g_settings.audio_output_muted;
m_audio_stream->SetOutputVolume(g_settings.audio_output_muted ? 0 : g_settings.audio_output_volume); m_audio_stream->SetOutputVolume(g_settings.audio_output_muted ? 0 : g_settings.audio_output_volume);
if (g_settings.audio_output_muted) if (g_settings.audio_output_muted)
AddOSDMessage("Volume: Muted", 2.0f); AddOSDMessage(TranslateStdString("OSDMessage", "Volume: Muted"), 2.0f);
else else
AddFormattedOSDMessage(2.0f, "Volume: %d%%", g_settings.audio_output_volume); AddFormattedOSDMessage(2.0f, TranslateString("OSDMessage", "Volume: %d%%"), g_settings.audio_output_volume);
} }
}); });
RegisterHotkey(StaticString("Audio"), StaticString("AudioVolumeUp"), StaticString("Volume Up"), [this](bool pressed) { RegisterHotkey(StaticString("Audio"), StaticString("AudioVolumeUp"),
StaticString(TRANSLATABLE("Hotkeys", "Volume Up")), [this](bool pressed) {
if (System::IsValid() && pressed) if (System::IsValid() && pressed)
{ {
g_settings.audio_output_volume = std::min<s32>(g_settings.audio_output_volume + 10, 100); g_settings.audio_output_volume = std::min<s32>(g_settings.audio_output_volume + 10, 100);
g_settings.audio_output_muted = false; g_settings.audio_output_muted = false;
m_audio_stream->SetOutputVolume(g_settings.audio_output_volume); m_audio_stream->SetOutputVolume(g_settings.audio_output_volume);
AddFormattedOSDMessage(2.0f, "Volume: %d%%", g_settings.audio_output_volume); AddFormattedOSDMessage(2.0f, TranslateString("OSDMessage", "Volume: %d%%"),
g_settings.audio_output_volume);
} }
}); });
RegisterHotkey(StaticString("Audio"), StaticString("AudioVolumeDown"), StaticString("Volume Down"), RegisterHotkey(StaticString("Audio"), StaticString("AudioVolumeDown"),
[this](bool pressed) { StaticString(TRANSLATABLE("Hotkeys", "Volume Down")), [this](bool pressed) {
if (System::IsValid() && pressed) if (System::IsValid() && pressed)
{ {
g_settings.audio_output_volume = std::max<s32>(g_settings.audio_output_volume - 10, 0); g_settings.audio_output_volume = std::max<s32>(g_settings.audio_output_volume - 10, 0);
g_settings.audio_output_muted = false; g_settings.audio_output_muted = false;
m_audio_stream->SetOutputVolume(g_settings.audio_output_volume); m_audio_stream->SetOutputVolume(g_settings.audio_output_volume);
AddFormattedOSDMessage(2.0f, "Volume: %d%%", g_settings.audio_output_volume); AddFormattedOSDMessage(2.0f, TranslateString("OSDMessage", "Volume: %d%%"),
g_settings.audio_output_volume);
} }
}); });
} }
@ -1552,7 +1577,7 @@ void CommonHostInterface::ApplyInputProfile(const char* profile_path, SettingsIn
UpdateInputMap(si); UpdateInputMap(si);
ReportFormattedMessage("Loaded input profile from '%s'", profile_path); ReportFormattedMessage(TranslateString("OSDMessage", "Loaded input profile from '%s'"), profile_path);
} }
bool CommonHostInterface::SaveInputProfile(const char* profile_path, SettingsInterface& si) bool CommonHostInterface::SaveInputProfile(const char* profile_path, SettingsInterface& si)
@ -2016,14 +2041,25 @@ bool CommonHostInterface::SaveScreenshot(const char* filename /* = nullptr */, b
const bool screenshot_saved = m_display->WriteDisplayTextureToFile(filename, full_resolution, apply_aspect_ratio); const bool screenshot_saved = m_display->WriteDisplayTextureToFile(filename, full_resolution, apply_aspect_ratio);
if (!screenshot_saved) if (!screenshot_saved)
{ {
AddFormattedOSDMessage(10.0f, "Failed to save screenshot to '%s'", filename); AddFormattedOSDMessage(10.0f, TranslateString("OSDMessage", "Failed to save screenshot to '%s'"), filename);
return false; return false;
} }
AddFormattedOSDMessage(5.0f, "Screenshot saved to '%s'.", filename); AddFormattedOSDMessage(5.0f, TranslateString("OSDMessage", "Screenshot saved to '%s'."), filename);
return true; return true;
} }
void CommonHostInterface::ApplyGameSettings(bool display_osd_messages)
{
// this gets called while booting, so can't use valid
if (System::IsShutdown() || System::GetRunningCode().empty() || !g_settings.apply_game_settings)
return;
const GameSettings::Entry* gs = m_game_list->GetGameSettings(System::GetRunningPath(), System::GetRunningCode());
if (gs)
gs->ApplySettings(display_osd_messages);
}
#ifdef WITH_DISCORD_PRESENCE #ifdef WITH_DISCORD_PRESENCE
void CommonHostInterface::SetDiscordPresenceEnabled(bool enabled) void CommonHostInterface::SetDiscordPresenceEnabled(bool enabled)

View file

@ -266,6 +266,8 @@ protected:
void RecreateSystem() override; void RecreateSystem() override;
void ApplyGameSettings(bool display_osd_messages);
virtual void DrawImGuiWindows(); virtual void DrawImGuiWindows();
void DrawFPSWindow(); void DrawFPSWindow();

View file

@ -1,5 +1,6 @@
#include "sdl_controller_interface.h" #include "sdl_controller_interface.h"
#include "common/assert.h" #include "common/assert.h"
#include "common/file_system.h"
#include "common/log.h" #include "common/log.h"
#include "core/controller.h" #include "core/controller.h"
#include "core/host_interface.h" #include "core/host_interface.h"
@ -23,6 +24,17 @@ bool SDLControllerInterface::Initialize(CommonHostInterface* host_interface)
FrontendCommon::EnsureSDLInitialized(); FrontendCommon::EnsureSDLInitialized();
const std::string gcdb_file_name = GetGameControllerDBFileName();
if (FileSystem::FileExists(gcdb_file_name.c_str()))
{
Log_InfoPrintf("Loading game controller mappings from '%s'", gcdb_file_name.c_str());
if (SDL_GameControllerAddMappingsFromFile(gcdb_file_name.c_str()) < 0)
{
Log_ErrorPrintf("SDL_GameControllerAddMappingsFromFile(%s) failed: %s",
gcdb_file_name.c_str(), SDL_GetError());
}
}
if (SDL_InitSubSystem(SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER | SDL_INIT_HAPTIC) < 0) if (SDL_InitSubSystem(SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER | SDL_INIT_HAPTIC) < 0)
{ {
Log_ErrorPrintf("SDL_InitSubSystem(SDL_INIT_JOYSTICK |SDL_INIT_GAMECONTROLLER | SDL_INIT_HAPTIC) failed"); Log_ErrorPrintf("SDL_InitSubSystem(SDL_INIT_JOYSTICK |SDL_INIT_GAMECONTROLLER | SDL_INIT_HAPTIC) failed");
@ -48,6 +60,11 @@ void SDLControllerInterface::Shutdown()
ControllerInterface::Shutdown(); ControllerInterface::Shutdown();
} }
std::string SDLControllerInterface::GetGameControllerDBFileName() const
{
return m_host_interface->GetUserDirectoryRelativePath("gamecontrollerdb.txt");
}
void SDLControllerInterface::PollEvents() void SDLControllerInterface::PollEvents()
{ {
for (;;) for (;;)

View file

@ -17,6 +17,9 @@ public:
bool Initialize(CommonHostInterface* host_interface) override; bool Initialize(CommonHostInterface* host_interface) override;
void Shutdown() override; void Shutdown() override;
/// Returns the path of the optional game controller database file.
std::string GetGameControllerDBFileName() const;
// Removes all bindings. Call before setting new bindings. // Removes all bindings. Call before setting new bindings.
void ClearBindings() override; void ClearBindings() override;

View file

@ -4,8 +4,9 @@ SET VERSIONFILE="scmversion.cpp"
FOR /F "tokens=* USEBACKQ" %%g IN (`git rev-parse HEAD`) do (SET "HASH=%%g") FOR /F "tokens=* USEBACKQ" %%g IN (`git rev-parse HEAD`) do (SET "HASH=%%g")
FOR /F "tokens=* USEBACKQ" %%g IN (`git rev-parse --abbrev-ref HEAD`) do (SET "BRANCH=%%g") FOR /F "tokens=* USEBACKQ" %%g IN (`git rev-parse --abbrev-ref HEAD`) do (SET "BRANCH=%%g")
FOR /F "tokens=* USEBACKQ" %%g IN (`git describe --tags --dirty --exclude latest`) do (SET "TAG=%%g") FOR /F "tokens=* USEBACKQ" %%g IN (`git describe --tags --dirty --exclude latest`) do (SET "TAG=%%g")
FOR /F "tokens=* USEBACKQ" %%g IN (`git log -1 --date=iso8601-strict "--format=%%cd"`) do (SET "CDATE=%%g")
SET SIGNATURELINE=// %HASH% %BRANCH% %TAG% SET SIGNATURELINE=// %HASH% %BRANCH% %TAG% %CDATE%
SET /P EXISTINGLINE=< %VERSIONFILE% SET /P EXISTINGLINE=< %VERSIONFILE%
IF "%EXISTINGLINE%"=="%SIGNATURELINE%" ( IF "%EXISTINGLINE%"=="%SIGNATURELINE%" (
@ -19,6 +20,7 @@ ECHO Updating %VERSIONFILE%...
ECHO const char* g_scm_hash_str = "%HASH%"; ECHO const char* g_scm_hash_str = "%HASH%";
ECHO const char* g_scm_branch_str = "%BRANCH%"; ECHO const char* g_scm_branch_str = "%BRANCH%";
ECHO const char* g_scm_tag_str = "%TAG%"; ECHO const char* g_scm_tag_str = "%TAG%";
ECHO const char* g_scm_date_str = "%CDATE%";
)>%VERSIONFILE% )>%VERSIONFILE%
EXIT EXIT

View file

@ -4,8 +4,9 @@ VERSION_FILE="scmversion.cpp"
HASH=$(git rev-parse HEAD) HASH=$(git rev-parse HEAD)
BRANCH=$(git rev-parse --abbrev-ref HEAD | tr -d '\r\n') BRANCH=$(git rev-parse --abbrev-ref HEAD | tr -d '\r\n')
TAG=$(git describe --tags --dirty --exclude latest | tr -d '\r\n') TAG=$(git describe --tags --dirty --exclude latest | tr -d '\r\n')
DATE=$(git log -1 --date=iso8601-strict --format=%cd)
SIGNATURE_LINE="// ${HASH} ${BRANCH} ${TAG}" SIGNATURE_LINE="// ${HASH} ${BRANCH} ${TAG} ${DATE}"
if [ -f $VERSION_FILE ]; then if [ -f $VERSION_FILE ]; then
EXISTING_LINE=$(head -n1 $VERSION_FILE | tr -d '\n') EXISTING_LINE=$(head -n1 $VERSION_FILE | tr -d '\n')
@ -22,6 +23,7 @@ ${SIGNATURE_LINE}
const char* g_scm_hash_str = "${HASH}"; const char* g_scm_hash_str = "${HASH}";
const char* g_scm_branch_str = "${BRANCH}"; const char* g_scm_branch_str = "${BRANCH}";
const char* g_scm_tag_str = "${TAG}"; const char* g_scm_tag_str = "${TAG}";
const char* g_scm_date_str = "${DATE}";
EOF EOF

View file

@ -3,4 +3,5 @@
extern const char* g_scm_hash_str; extern const char* g_scm_hash_str;
extern const char* g_scm_branch_str; extern const char* g_scm_branch_str;
extern const char* g_scm_tag_str; extern const char* g_scm_tag_str;
extern const char* g_scm_date_str;