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:
fetch-depth: 0
- name: Install packages
shell: bash
run: |
sudo apt-get update
sudo apt-get -y install gcc-aarch64-linux-gnu g++-aarch64-linux-gnu
- name: Compile and zip Linux x64 libretro core
shell: bash
run: |
@ -151,13 +157,28 @@ jobs:
cd build-libretro-linux-x64
cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_LIBRETRO_CORE=ON ..
cmake --build . --parallel 2
zip -j duckstation_libretro.so.zip duckstation_libretro.so
zip -j duckstation_libretro_x64.so.zip duckstation_libretro.so
- name: Upload Linux x64 libretro core
uses: actions/upload-artifact@v1
with:
name: "linux-libretro"
path: "build-libretro-linux-x64/duckstation_libretro.so.zip"
path: "build-libretro-linux-x64/duckstation_libretro_x64.so.zip"
- name: Compile and zip Linux AArch64 libretro core
shell: bash
run: |
mkdir build-libretro-linux-aarch64
cd build-libretro-linux-aarch64
cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_LIBRETRO_CORE=ON -DCMAKE_TOOLCHAIN_FILE=../CMakeModules/aarch64-cross-toolchain.cmake ..
cmake --build . --parallel 2
zip -j duckstation_libretro_linux_aarch64.so.zip duckstation_libretro.so
- name: Upload Linux AArch64 libretro core
uses: actions/upload-artifact@v1
with:
name: "linux-libretro"
path: "build-libretro-linux-aarch64/duckstation_libretro_linux_aarch64.so.zip"
- name: Compile and zip Android AArch64 libretro core
shell: bash
@ -271,7 +292,8 @@ jobs:
linux-x64-appimage-sdl-zsync/duckstation-sdl-x64.AppImage.zsync
linux-x64-appimage-qt/duckstation-qt-x64.AppImage
linux-x64-appimage-qt-zsync/duckstation-qt-x64.AppImage.zsync
linux-libretro/duckstation_libretro.so.zip
linux-libretro/duckstation_libretro_x64.so.zip
linux-libretro/duckstation_libretro_linux_aarch64.so.zip
linux-libretro/duckstation_libretro_android_aarch64.so.zip
android/duckstation-android-aarch64.apk

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)
- Sorer - @MojoJojoDojo - Hebrew
- Hipnosis183 - Spanish
- @RaydenX93 - Italian
## Game Compatibility Database
- @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
- 2020/08/20: Per-game setting overrides added. Mostly for compatibility, but some options are customizable.
- 2020/08/19: CPU PGXP mode added. It is very slow and incompatible with the recompiler, only use for games which need it.
- 2020/08/15: Playlist support/single memcard for multi-disc games in Qt frontend added.
- 2020/08/07: Automatic updater for standalone Windows builds.
- 2020/08/01: Initial PGXP (geometry/perspective correction) support.
@ -36,6 +38,7 @@ Other features include:
- CPU Recompiler/JIT (x86-64 and AArch64)
- Hardware (D3D11, OpenGL, Vulkan) and software rendering
- Upscaling and true colour (24-bit) in hardware renderers
- PGXP for geometry precision and texture correction
- "Fast boot" for skipping BIOS splash/intro
- Save state support
- Windows, Linux, **highly experimental** macOS support
@ -228,7 +231,8 @@ DuckStation is available as a libretro core, which can be loaded into a frontend
Prebuilt binaries for 64-bit Windows, Linux and Android can be found on the releases page. Direct links:
- 64-bit Windows: https://github.com/stenzek/duckstation/releases/download/latest/duckstation_libretro.dll.zip
- 64-bit Linux: https://github.com/stenzek/duckstation/releases/download/latest/duckstation_libretro.so.zip
- 64-bit Linux: https://github.com/stenzek/duckstation/releases/download/latest/duckstation_libretro_x64.so.zip
- AArch64 Linux: https://github.com/stenzek/duckstation/releases/download/latest/duckstation_libretro_linux_aarch64.so.zip
- AArch64 Android: https://github.com/stenzek/duckstation/releases/download/latest/duckstation_libretro_android_aarch64.so.zip
To use, download and extract, and install the core file in RetroArch or your preferred frontend.

View file

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

View file

@ -583,6 +583,10 @@
<compatibility>No Issues</compatibility>
<version-tested>0.1-1308-g622e50fa</version-tested>
</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)">
<compatibility>No Issues</compatibility>
<version-tested>0.1-1308-g622e50fa</version-tested>
@ -591,6 +595,14 @@
<compatibility>No Issues</compatibility>
<version-tested>0.1-1115-g0b261e8</version-tested>
</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)">
<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>
@ -1314,11 +1326,40 @@ Tetris with Card Captor Sakura (Japan)
<compatibility>No Issues</compatibility>
<version-tested>0.1-986-gfc911de1</version-tested>
</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)">
<compatibility>Crashes In-Game</compatibility>
<version-tested>0.1-1490-g76978986</version-tested>
<comments>Hangs when choosing Armored core (Issue #751).</comments>
</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)">
<compatibility>No Issues</compatibility>
<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">
<compatibility>No Issues</compatibility>
</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">
<compatibility>No Issues</compatibility>
<version-tested>0.1-826-g712168c</version-tested>
@ -2006,6 +2051,30 @@ Tetris with Card Captor Sakura (Japan)
<compatibility>No Issues</compatibility>
<version-tested>0.1-1445-ge5c46a54</version-tested>
</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)">
<compatibility>No Issues</compatibility>
<version-tested>0.1-884-g096ed21</version-tested>
@ -2035,6 +2104,10 @@ Tetris with Card Captor Sakura (Japan)
<compatibility>No Issues</compatibility>
<version-tested>0.1-774-g5a1b008</version-tested>
</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">
<compatibility>No Issues</compatibility>
<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)">
<compatibility>No Issues</compatibility>
</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)">
<compatibility>No Issues</compatibility>
<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
// delete the temporary file
if (!DeleteFileA(m_temporaryFileName.c_str()))
if (!DeleteFileW(StringUtil::UTF8StringToWideString(m_temporaryFileName).c_str()))
{
Log_WarningPrintf(
"AtomicUpdatedFileByteStream::~AtomicUpdatedFileByteStream(): Failed to delete temporary file '%s'",
m_temporaryFileName.c_str());
}
#else
// delete the temporary file
if (remove(m_temporaryFileName.c_str()) < 0)
@ -308,7 +310,8 @@ public:
#ifdef WIN32
// move the atomic file name to the original file name
if (!MoveFileExA(m_temporaryFileName.c_str(), m_originalFileName.c_str(), MOVEFILE_REPLACE_EXISTING))
if (!MoveFileExW(StringUtil::UTF8StringToWideString(m_temporaryFileName).c_str(),
StringUtil::UTF8StringToWideString(m_originalFileName).c_str(), MOVEFILE_REPLACE_EXISTING))
{
Log_WarningPrintf("AtomicUpdatedFileByteStream::Commit(): Failed to rename temporary file '%s' to '%s'",
m_temporaryFileName.c_str(), m_originalFileName.c_str());

View file

@ -15,6 +15,13 @@
#endif
#endif
// Force inline in non-debug helper
#ifdef _DEBUG
#define ALWAYS_INLINE_RELEASE
#else
#define ALWAYS_INLINE_RELEASE ALWAYS_INLINE
#endif
// unreferenced parameter macro
#ifndef UNREFERENCED_VARIABLE
#if defined(_MSC_VER)

View file

@ -26,6 +26,8 @@ add_library(core
dma.h
game_list.cpp
game_list.h
game_settings.cpp
game_settings.h
gpu.cpp
gpu.h
gpu_commands.cpp
@ -96,7 +98,7 @@ set(RECOMPILER_SRCS
target_include_directories(core PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/..")
target_include_directories(core PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/..")
target_link_libraries(core PUBLIC Threads::Threads common imgui tinyxml2 zlib vulkan-loader)
target_link_libraries(core PUBLIC Threads::Threads common imgui tinyxml2 zlib vulkan-loader simpleini)
target_link_libraries(core PRIVATE glad stb)
if(WIN32)

View file

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

View file

@ -1917,6 +1917,17 @@ void CDROM::DoSectorRead()
// TODO: Error handling
const CDImage::SubChannelQ& subq = m_reader.GetSectorSubQ();
if (subq.IsCRCValid())
{
m_last_subq = subq;
}
else
{
const CDImage::Position pos(CDImage::Position::FromLBA(m_current_lba));
Log_DevPrintf("Sector %u [%02u:%02u:%02u] has invalid subchannel Q", m_current_lba, pos.minute, pos.second,
pos.frame);
}
if (subq.track_number_bcd == CDImage::LEAD_OUT_TRACK_NUMBER)
{
Log_DevPrintf("Read reached lead-out area of disc at LBA %u, pausing", m_reader.GetLastReadSector());
@ -1948,17 +1959,6 @@ void CDROM::DoSectorRead()
ProcessDataSectorHeader(m_reader.GetSectorBuffer().data());
}
if (subq.IsCRCValid())
{
m_last_subq = subq;
}
else
{
const CDImage::Position pos(CDImage::Position::FromLBA(m_current_lba));
Log_DevPrintf("Sector %u [%02u:%02u:%02u] has invalid subchannel Q", m_current_lba, pos.minute, pos.second,
pos.frame);
}
if (is_data_sector && m_drive_state == DriveState::Reading)
{
ProcessDataSector(m_reader.GetSectorBuffer().data(), subq);

View file

@ -60,6 +60,7 @@
<ClCompile Include="cpu_types.cpp" />
<ClCompile Include="digital_controller.cpp" />
<ClCompile Include="game_list.cpp" />
<ClCompile Include="game_settings.cpp" />
<ClCompile Include="gpu_commands.cpp" />
<ClCompile Include="gpu_hw_d3d11.cpp" />
<ClCompile Include="gpu_hw_shadergen.cpp" />
@ -107,6 +108,7 @@
<ClInclude Include="cpu_recompiler_types.h" />
<ClInclude Include="digital_controller.h" />
<ClInclude Include="game_list.h" />
<ClInclude Include="game_settings.h" />
<ClInclude Include="gpu_hw_d3d11.h" />
<ClInclude Include="gpu_hw_shadergen.h" />
<ClInclude Include="gpu_hw_vulkan.h" />
@ -142,6 +144,12 @@
<ClInclude Include="types.h" />
</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">
<Project>{ed601289-ac1a-46b8-a8ed-17db9eb73423}</Project>
</ProjectReference>
@ -299,7 +307,7 @@
<PreprocessorDefinitions>WITH_RECOMPILER=1;_CRT_SECURE_NO_WARNINGS;WIN32;_DEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<SDLCheck>true</SDLCheck>
<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>
<MinimalRebuild>false</MinimalRebuild>
<LanguageStandard>stdcpp17</LanguageStandard>
@ -325,7 +333,7 @@
<PreprocessorDefinitions>WITH_RECOMPILER=1;_CRT_SECURE_NO_WARNINGS;WIN32;_DEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<SDLCheck>true</SDLCheck>
<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>
<MinimalRebuild>false</MinimalRebuild>
<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>
<SDLCheck>true</SDLCheck>
<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>
<MultiProcessorCompilation>true</MultiProcessorCompilation>
<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>
<SDLCheck>true</SDLCheck>
<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>
<MultiProcessorCompilation>true</MultiProcessorCompilation>
<MinimalRebuild>false</MinimalRebuild>
@ -408,7 +416,7 @@
<Optimization>MaxSpeed</Optimization>
<IntrinsicFunctions>true</IntrinsicFunctions>
<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>
<WholeProgramOptimization>false</WholeProgramOptimization>
<LanguageStandard>stdcpp17</LanguageStandard>
@ -435,7 +443,7 @@
<Optimization>MaxSpeed</Optimization>
<IntrinsicFunctions>true</IntrinsicFunctions>
<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>
<WholeProgramOptimization>true</WholeProgramOptimization>
<LanguageStandard>stdcpp17</LanguageStandard>
@ -463,7 +471,7 @@
<Optimization>MaxSpeed</Optimization>
<IntrinsicFunctions>true</IntrinsicFunctions>
<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>
<WholeProgramOptimization>false</WholeProgramOptimization>
<LanguageStandard>stdcpp17</LanguageStandard>
@ -490,7 +498,7 @@
<Optimization>MaxSpeed</Optimization>
<IntrinsicFunctions>true</IntrinsicFunctions>
<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>
<WholeProgramOptimization>true</WholeProgramOptimization>
<LanguageStandard>stdcpp17</LanguageStandard>

View file

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

View file

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

View file

@ -15,19 +15,9 @@ Log_SetChannel(CPU::Core);
namespace CPU {
/// Sets the PC and flushes the pipeline.
static void SetPC(u32 new_pc);
// Updates load delays - call after each instruction
static void UpdateLoadDelay();
// Fetches the instruction at m_regs.npc
static void ExecuteInstruction();
static void ExecuteCop0Instruction();
static void ExecuteCop2Instruction();
static void Branch(u32 target);
// clears pipeline of load/branch delays
static void FlushPipeline();
State g_state;
@ -139,14 +129,14 @@ bool DoState(StateWrapper& sw)
return !sw.HasError();
}
void SetPC(u32 new_pc)
ALWAYS_INLINE_RELEASE void SetPC(u32 new_pc)
{
DebugAssert(Common::IsAlignedPow2(new_pc, 4));
g_state.regs.npc = new_pc;
FlushPipeline();
}
void Branch(u32 target)
ALWAYS_INLINE_RELEASE void Branch(u32 target)
{
if (!Common::IsAlignedPow2(target, 4))
{
@ -240,7 +230,7 @@ void ClearExternalInterrupt(u8 bit)
g_state.cop0_regs.cause.Ip &= static_cast<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
if (g_state.load_delay_reg != Reg::count)
@ -251,7 +241,7 @@ void UpdateLoadDelay()
g_state.next_load_delay_reg = Reg::count;
}
void FlushPipeline()
ALWAYS_INLINE_RELEASE static void FlushPipeline()
{
// loads are flushed
g_state.next_load_delay_reg = Reg::count;
@ -275,12 +265,12 @@ void FlushPipeline()
g_state.current_instruction_was_branch_taken = false;
}
ALWAYS_INLINE u32 ReadReg(Reg rs)
ALWAYS_INLINE static u32 ReadReg(Reg rs)
{
return g_state.regs.r[static_cast<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.load_delay_reg = (rd == g_state.load_delay_reg) ? Reg::count : g_state.load_delay_reg;
@ -289,7 +279,7 @@ ALWAYS_INLINE void WriteReg(Reg rd, u32 value)
g_state.regs.zero = 0;
}
static void WriteRegDelayed(Reg rd, u32 value)
ALWAYS_INLINE_RELEASE static void WriteRegDelayed(Reg rd, u32 value)
{
Assert(g_state.next_load_delay_reg == Reg::count);
if (rd == Reg::zero)
@ -304,7 +294,7 @@ static void WriteRegDelayed(Reg rd, u32 value)
g_state.next_load_delay_value = value;
}
static std::optional<u32> ReadCop0Reg(Cop0Reg reg)
ALWAYS_INLINE_RELEASE static std::optional<u32> ReadCop0Reg(Cop0Reg 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)
{
@ -431,12 +421,12 @@ static void LogInstruction(u32 bits, u32 pc, Registers* regs)
WriteToExecutionLog("%08x: %08x %s\n", pc, bits, instr.GetCharArray());
}
static constexpr bool AddOverflow(u32 old_value, u32 add_value, u32 new_value)
ALWAYS_INLINE static constexpr bool AddOverflow(u32 old_value, u32 add_value, u32 new_value)
{
return (((new_value ^ old_value) & (new_value ^ add_value)) & UINT32_C(0x80000000)) != 0;
}
static constexpr bool SubOverflow(u32 old_value, u32 sub_value, u32 new_value)
ALWAYS_INLINE static constexpr bool SubOverflow(u32 old_value, u32 sub_value, u32 new_value)
{
return (((new_value ^ old_value) & (old_value ^ sub_value)) & UINT32_C(0x80000000)) != 0;
}
@ -467,53 +457,8 @@ void DisassembleAndPrint(u32 addr, u32 instructions_before /* = 0 */, u32 instru
}
}
void Execute()
{
g_state.frame_done = false;
while (!g_state.frame_done)
{
TimingEvents::UpdateCPUDowncount();
while (g_state.pending_ticks <= g_state.downcount)
{
if (HasPendingInterrupt())
DispatchInterrupt();
g_state.pending_ticks++;
// now executing the instruction we previously fetched
g_state.current_instruction.bits = g_state.next_instruction.bits;
g_state.current_instruction_pc = g_state.regs.pc;
g_state.current_instruction_in_branch_delay_slot = g_state.next_instruction_is_branch_delay_slot;
g_state.current_instruction_was_branch_taken = g_state.branch_was_taken;
g_state.next_instruction_is_branch_delay_slot = false;
g_state.branch_was_taken = false;
g_state.exception_raised = false;
// fetch the next instruction
if (!FetchInstruction())
continue;
#if 0 // GTE flag test debugging
if (g_state.m_current_instruction_pc == 0x8002cdf4)
{
if (g_state.m_regs.v1 != g_state.m_regs.v0)
printf("Got %08X Expected? %08X\n", g_state.m_regs.v1, g_state.m_regs.v0);
}
#endif
// execute the instruction we previously fetched
ExecuteInstruction();
// next load delay
UpdateLoadDelay();
}
TimingEvents::RunEvents();
}
}
void ExecuteInstruction()
template<PGXPMode pgxp_mode>
ALWAYS_INLINE_RELEASE static void ExecuteInstruction()
{
const Instruction inst = g_state.current_instruction;
@ -525,14 +470,6 @@ void ExecuteInstruction()
}
#endif
#if 0
if (g_state.m_current_instruction_pc == 0x8002bf50)
{
TRACE_EXECUTION = true;
__debugbreak();
}
#endif
#ifdef _DEBUG
if (TRACE_EXECUTION)
PrintInstruction(inst.bits, g_state.current_instruction_pc, &g_state.regs);
@ -540,6 +477,10 @@ void ExecuteInstruction()
LogInstruction(inst.bits, g_state.current_instruction_pc, &g_state.regs);
#endif
// Skip nops. Makes PGXP-CPU quicker, but also the regular interpreter.
if (inst.bits == 0)
return;
switch (inst.op)
{
case InstructionOp::funct:
@ -549,6 +490,9 @@ void ExecuteInstruction()
case InstructionFunct::sll:
{
const u32 new_value = ReadReg(inst.r.rt) << inst.r.shamt;
if constexpr (pgxp_mode >= PGXPMode::CPU)
PGXP::CPU_SLL(inst.bits, new_value, ReadReg(inst.r.rt));
WriteReg(inst.r.rd, new_value);
}
break;
@ -556,6 +500,9 @@ void ExecuteInstruction()
case InstructionFunct::srl:
{
const u32 new_value = ReadReg(inst.r.rt) >> inst.r.shamt;
if constexpr (pgxp_mode >= PGXPMode::CPU)
PGXP::CPU_SRL(inst.bits, new_value, ReadReg(inst.r.rt));
WriteReg(inst.r.rd, new_value);
}
break;
@ -563,6 +510,9 @@ void ExecuteInstruction()
case InstructionFunct::sra:
{
const u32 new_value = static_cast<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);
}
break;
@ -571,6 +521,9 @@ void ExecuteInstruction()
{
const u32 shift_amount = ReadReg(inst.r.rs) & UINT32_C(0x1F);
const u32 new_value = ReadReg(inst.r.rt) << shift_amount;
if constexpr (pgxp_mode >= PGXPMode::CPU)
PGXP::CPU_SLLV(inst.bits, new_value, ReadReg(inst.r.rt), shift_amount);
WriteReg(inst.r.rd, new_value);
}
break;
@ -579,6 +532,9 @@ void ExecuteInstruction()
{
const u32 shift_amount = ReadReg(inst.r.rs) & UINT32_C(0x1F);
const u32 new_value = ReadReg(inst.r.rt) >> shift_amount;
if constexpr (pgxp_mode >= PGXPMode::CPU)
PGXP::CPU_SRLV(inst.bits, new_value, ReadReg(inst.r.rt), shift_amount);
WriteReg(inst.r.rd, new_value);
}
break;
@ -587,6 +543,9 @@ void ExecuteInstruction()
{
const u32 shift_amount = ReadReg(inst.r.rs) & UINT32_C(0x1F);
const u32 new_value = static_cast<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);
}
break;
@ -594,6 +553,9 @@ void ExecuteInstruction()
case InstructionFunct::and_:
{
const u32 new_value = ReadReg(inst.r.rs) & ReadReg(inst.r.rt);
if constexpr (pgxp_mode >= PGXPMode::CPU)
PGXP::CPU_AND_(inst.bits, new_value, ReadReg(inst.r.rs), ReadReg(inst.r.rt));
WriteReg(inst.r.rd, new_value);
}
break;
@ -601,6 +563,9 @@ void ExecuteInstruction()
case InstructionFunct::or_:
{
const u32 new_value = ReadReg(inst.r.rs) | ReadReg(inst.r.rt);
if constexpr (pgxp_mode >= PGXPMode::CPU)
PGXP::CPU_OR_(inst.bits, new_value, ReadReg(inst.r.rs), ReadReg(inst.r.rt));
WriteReg(inst.r.rd, new_value);
}
break;
@ -608,6 +573,9 @@ void ExecuteInstruction()
case InstructionFunct::xor_:
{
const u32 new_value = ReadReg(inst.r.rs) ^ ReadReg(inst.r.rt);
if constexpr (pgxp_mode >= PGXPMode::CPU)
PGXP::CPU_XOR_(inst.bits, new_value, ReadReg(inst.r.rs), ReadReg(inst.r.rt));
WriteReg(inst.r.rd, new_value);
}
break;
@ -615,6 +583,9 @@ void ExecuteInstruction()
case InstructionFunct::nor:
{
const u32 new_value = ~(ReadReg(inst.r.rs) | ReadReg(inst.r.rt));
if constexpr (pgxp_mode >= PGXPMode::CPU)
PGXP::CPU_NOR(inst.bits, new_value, ReadReg(inst.r.rs), ReadReg(inst.r.rt));
WriteReg(inst.r.rd, new_value);
}
break;
@ -630,6 +601,9 @@ void ExecuteInstruction()
return;
}
if constexpr (pgxp_mode == PGXPMode::CPU)
PGXP::CPU_ADD(inst.bits, new_value, ReadReg(inst.r.rs), ReadReg(inst.r.rt));
WriteReg(inst.r.rd, new_value);
}
break;
@ -637,6 +611,9 @@ void ExecuteInstruction()
case InstructionFunct::addu:
{
const u32 new_value = ReadReg(inst.r.rs) + ReadReg(inst.r.rt);
if constexpr (pgxp_mode >= PGXPMode::CPU)
PGXP::CPU_ADDU(inst.bits, new_value, ReadReg(inst.r.rs), ReadReg(inst.r.rt));
WriteReg(inst.r.rd, new_value);
}
break;
@ -652,6 +629,9 @@ void ExecuteInstruction()
return;
}
if constexpr (pgxp_mode >= PGXPMode::CPU)
PGXP::CPU_SUB(inst.bits, new_value, ReadReg(inst.r.rs), ReadReg(inst.r.rt));
WriteReg(inst.r.rd, new_value);
}
break;
@ -659,6 +639,9 @@ void ExecuteInstruction()
case InstructionFunct::subu:
{
const u32 new_value = ReadReg(inst.r.rs) - ReadReg(inst.r.rt);
if constexpr (pgxp_mode >= PGXPMode::CPU)
PGXP::CPU_SUBU(inst.bits, new_value, ReadReg(inst.r.rs), ReadReg(inst.r.rt));
WriteReg(inst.r.rd, new_value);
}
break;
@ -666,6 +649,9 @@ void ExecuteInstruction()
case InstructionFunct::slt:
{
const u32 result = BoolToUInt32(static_cast<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);
}
break;
@ -673,12 +659,18 @@ void ExecuteInstruction()
case InstructionFunct::sltu:
{
const u32 result = BoolToUInt32(ReadReg(inst.r.rs) < ReadReg(inst.r.rt));
if constexpr (pgxp_mode >= PGXPMode::CPU)
PGXP::CPU_SLTU(inst.bits, result, ReadReg(inst.r.rs), ReadReg(inst.r.rt));
WriteReg(inst.r.rd, result);
}
break;
case InstructionFunct::mfhi:
{
if constexpr (pgxp_mode >= PGXPMode::CPU)
PGXP::CPU_MFHI(inst.bits, ReadReg(inst.r.rd), g_state.regs.hi);
WriteReg(inst.r.rd, g_state.regs.hi);
}
break;
@ -686,12 +678,18 @@ void ExecuteInstruction()
case InstructionFunct::mthi:
{
const u32 value = ReadReg(inst.r.rs);
if constexpr (pgxp_mode >= PGXPMode::CPU)
PGXP::CPU_MTHI(inst.bits, g_state.regs.hi, value);
g_state.regs.hi = value;
}
break;
case InstructionFunct::mflo:
{
if constexpr (pgxp_mode >= PGXPMode::CPU)
PGXP::CPU_MFLO(inst.bits, ReadReg(inst.r.rd), g_state.regs.lo);
WriteReg(inst.r.rd, g_state.regs.lo);
}
break;
@ -699,6 +697,9 @@ void ExecuteInstruction()
case InstructionFunct::mtlo:
{
const u32 value = ReadReg(inst.r.rs);
if constexpr (pgxp_mode == PGXPMode::CPU)
PGXP::CPU_MTLO(inst.bits, g_state.regs.lo, value);
g_state.regs.lo = value;
}
break;
@ -709,8 +710,12 @@ void ExecuteInstruction()
const u32 rhs = ReadReg(inst.r.rt);
const u64 result =
static_cast<u64>(static_cast<s64>(SignExtend64(lhs)) * static_cast<s64>(SignExtend64(rhs)));
g_state.regs.hi = Truncate32(result >> 32);
g_state.regs.lo = Truncate32(result);
if constexpr (pgxp_mode >= PGXPMode::CPU)
PGXP::CPU_MULT(inst.bits, g_state.regs.hi, g_state.regs.lo, lhs, rhs);
}
break;
@ -719,6 +724,10 @@ void ExecuteInstruction()
const u32 lhs = ReadReg(inst.r.rs);
const u32 rhs = ReadReg(inst.r.rt);
const u64 result = ZeroExtend64(lhs) * ZeroExtend64(rhs);
if constexpr (pgxp_mode >= PGXPMode::CPU)
PGXP::CPU_MULTU(inst.bits, g_state.regs.hi, g_state.regs.lo, lhs, rhs);
g_state.regs.hi = Truncate32(result >> 32);
g_state.regs.lo = Truncate32(result);
}
@ -746,6 +755,9 @@ void ExecuteInstruction()
g_state.regs.lo = static_cast<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;
@ -765,6 +777,9 @@ void ExecuteInstruction()
g_state.regs.lo = num / denom;
g_state.regs.hi = num % denom;
}
if constexpr (pgxp_mode >= PGXPMode::CPU)
PGXP::CPU_DIVU(inst.bits, g_state.regs.hi, g_state.regs.lo, num, denom);
}
break;
@ -808,25 +823,44 @@ void ExecuteInstruction()
case InstructionOp::lui:
{
WriteReg(inst.i.rt, inst.i.imm_zext32() << 16);
const u32 value = inst.i.imm_zext32() << 16;
WriteReg(inst.i.rt, value);
if constexpr (pgxp_mode >= PGXPMode::CPU)
PGXP::CPU_LUI(inst.bits, value);
}
break;
case InstructionOp::andi:
{
WriteReg(inst.i.rt, ReadReg(inst.i.rs) & inst.i.imm_zext32());
const u32 new_value = ReadReg(inst.i.rs) & inst.i.imm_zext32();
if constexpr (pgxp_mode >= PGXPMode::CPU)
PGXP::CPU_ANDI(inst.bits, new_value, ReadReg(inst.i.rs));
WriteReg(inst.i.rt, new_value);
}
break;
case InstructionOp::ori:
{
WriteReg(inst.i.rt, ReadReg(inst.i.rs) | inst.i.imm_zext32());
const u32 new_value = ReadReg(inst.i.rs) | inst.i.imm_zext32();
if constexpr (pgxp_mode >= PGXPMode::CPU)
PGXP::CPU_ORI(inst.bits, new_value, ReadReg(inst.i.rs));
WriteReg(inst.i.rt, new_value);
}
break;
case InstructionOp::xori:
{
WriteReg(inst.i.rt, ReadReg(inst.i.rs) ^ inst.i.imm_zext32());
const u32 new_value = ReadReg(inst.i.rs) ^ inst.i.imm_zext32();
if constexpr (pgxp_mode >= PGXPMode::CPU)
PGXP::CPU_XORI(inst.bits, new_value, ReadReg(inst.i.rs));
WriteReg(inst.i.rt, new_value);
}
break;
@ -841,19 +875,31 @@ void ExecuteInstruction()
return;
}
if constexpr (pgxp_mode >= PGXPMode::CPU)
PGXP::CPU_ANDI(inst.bits, new_value, ReadReg(inst.i.rs));
WriteReg(inst.i.rt, new_value);
}
break;
case InstructionOp::addiu:
{
WriteReg(inst.i.rt, ReadReg(inst.i.rs) + inst.i.imm_sext32());
const u32 new_value = ReadReg(inst.i.rs) + inst.i.imm_sext32();
if constexpr (pgxp_mode >= PGXPMode::CPU)
PGXP::CPU_ADDIU(inst.bits, new_value, ReadReg(inst.i.rs));
WriteReg(inst.i.rt, new_value);
}
break;
case InstructionOp::slti:
{
const u32 result = BoolToUInt32(static_cast<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);
}
break;
@ -861,6 +907,10 @@ void ExecuteInstruction()
case InstructionOp::sltiu:
{
const u32 result = BoolToUInt32(ReadReg(inst.i.rs) < inst.i.imm_sext32());
if constexpr (pgxp_mode >= PGXPMode::CPU)
PGXP::CPU_SLTIU(inst.bits, result, ReadReg(inst.i.rs));
WriteReg(inst.i.rt, result);
}
break;
@ -876,7 +926,7 @@ void ExecuteInstruction()
WriteRegDelayed(inst.i.rt, sxvalue);
if (g_settings.gpu_pgxp_enable)
if constexpr (pgxp_mode >= PGXPMode::Memory)
PGXP::CPU_LBx(inst.bits, sxvalue, addr);
}
break;
@ -891,7 +941,7 @@ void ExecuteInstruction()
const u32 sxvalue = SignExtend32(value);
WriteRegDelayed(inst.i.rt, sxvalue);
if (g_settings.gpu_pgxp_enable)
if constexpr (pgxp_mode >= PGXPMode::Memory)
PGXP::CPU_LHx(inst.bits, sxvalue, addr);
}
break;
@ -905,7 +955,7 @@ void ExecuteInstruction()
WriteRegDelayed(inst.i.rt, value);
if (g_settings.gpu_pgxp_enable)
if constexpr (pgxp_mode >= PGXPMode::Memory)
PGXP::CPU_LW(inst.bits, value, addr);
}
break;
@ -920,7 +970,7 @@ void ExecuteInstruction()
const u32 zxvalue = ZeroExtend32(value);
WriteRegDelayed(inst.i.rt, zxvalue);
if (g_settings.gpu_pgxp_enable)
if constexpr (pgxp_mode >= PGXPMode::Memory)
PGXP::CPU_LBx(inst.bits, zxvalue, addr);
}
break;
@ -935,7 +985,7 @@ void ExecuteInstruction()
const u32 zxvalue = ZeroExtend32(value);
WriteRegDelayed(inst.i.rt, zxvalue);
if (g_settings.gpu_pgxp_enable)
if constexpr (pgxp_mode >= PGXPMode::Memory)
PGXP::CPU_LHx(inst.bits, zxvalue, addr);
}
break;
@ -966,7 +1016,7 @@ void ExecuteInstruction()
WriteRegDelayed(inst.i.rt, new_value);
if (g_settings.gpu_pgxp_enable)
if constexpr (pgxp_mode >= PGXPMode::Memory)
PGXP::CPU_LW(inst.bits, new_value, addr);
}
break;
@ -977,7 +1027,7 @@ void ExecuteInstruction()
const u8 value = Truncate8(ReadReg(inst.i.rt));
WriteMemoryByte(addr, value);
if (g_settings.gpu_pgxp_enable)
if constexpr (pgxp_mode >= PGXPMode::Memory)
PGXP::CPU_SB(inst.bits, value, addr);
}
break;
@ -988,7 +1038,7 @@ void ExecuteInstruction()
const u16 value = Truncate16(ReadReg(inst.i.rt));
WriteMemoryHalfWord(addr, value);
if (g_settings.gpu_pgxp_enable)
if constexpr (pgxp_mode >= PGXPMode::Memory)
PGXP::CPU_SH(inst.bits, value, addr);
}
break;
@ -999,7 +1049,7 @@ void ExecuteInstruction()
const u32 value = ReadReg(inst.i.rt);
WriteMemoryWord(addr, value);
if (g_settings.gpu_pgxp_enable)
if constexpr (pgxp_mode >= PGXPMode::Memory)
PGXP::CPU_SW(inst.bits, value, addr);
}
break;
@ -1029,7 +1079,7 @@ void ExecuteInstruction()
WriteMemoryWord(aligned_addr, new_value);
if (g_settings.gpu_pgxp_enable)
if constexpr (pgxp_mode >= PGXPMode::Memory)
PGXP::CPU_SW(inst.bits, new_value, addr);
}
break;
@ -1114,88 +1164,6 @@ void ExecuteInstruction()
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())
{
switch (inst.cop.CommonOp())
@ -1203,6 +1171,10 @@ void ExecuteCop0Instruction()
case CopCommonInstruction::mfcn:
{
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)
WriteRegDelayed(inst.r.rt, value.value());
else
@ -1213,6 +1185,12 @@ void ExecuteCop0Instruction()
case CopCommonInstruction::mtcn:
{
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;
@ -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())
{
@ -1254,7 +1238,7 @@ void ExecuteCop2Instruction()
const u32 value = GTE::ReadRegister(static_cast<u32>(inst.r.rd.GetValue()) + 32);
WriteRegDelayed(inst.r.rt, value);
if (g_settings.gpu_pgxp_enable)
if constexpr (pgxp_mode >= PGXPMode::Memory)
PGXP::CPU_CFC2(inst.bits, value, value);
}
break;
@ -1264,7 +1248,7 @@ void ExecuteCop2Instruction()
const u32 value = ReadReg(inst.r.rt);
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);
}
break;
@ -1274,7 +1258,7 @@ void ExecuteCop2Instruction()
const u32 value = GTE::ReadRegister(static_cast<u32>(inst.r.rd.GetValue()));
WriteRegDelayed(inst.r.rt, value);
if (g_settings.gpu_pgxp_enable)
if constexpr (pgxp_mode >= PGXPMode::Memory)
PGXP::CPU_MFC2(inst.bits, value, value);
}
break;
@ -1284,7 +1268,7 @@ void ExecuteCop2Instruction()
const u32 value = ReadReg(inst.r.rt);
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);
}
break;
@ -1300,9 +1284,134 @@ void ExecuteCop2Instruction()
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 {
template<PGXPMode pgxp_mode>
void InterpretCachedBlock(const CodeBlock& block)
{
// set up the state so we've already fetched the instruction
@ -1327,7 +1436,7 @@ void InterpretCachedBlock(const CodeBlock& block)
g_state.regs.npc += 4;
// execute the instruction we previously fetched
ExecuteInstruction();
ExecuteInstruction<pgxp_mode>();
// next load delay
UpdateLoadDelay();
@ -1340,6 +1449,10 @@ void InterpretCachedBlock(const CodeBlock& block)
g_state.next_instruction_is_branch_delay_slot = false;
}
template void InterpretCachedBlock<PGXPMode::Disabled>(const CodeBlock& block);
template void InterpretCachedBlock<PGXPMode::Memory>(const CodeBlock& block);
template void InterpretCachedBlock<PGXPMode::CPU>(const CodeBlock& block);
void InterpretUncachedBlock()
{
Panic("Fixme with regards to re-fetching PC");
@ -1365,7 +1478,7 @@ void InterpretUncachedBlock()
break;
// execute the instruction we previously fetched
ExecuteInstruction();
ExecuteInstruction<PGXPMode::Disabled>();
// next load delay
UpdateLoadDelay();
@ -1387,7 +1500,13 @@ namespace Recompiler::Thunks {
bool InterpretInstruction()
{
ExecuteInstruction();
ExecuteInstruction<PGXPMode::Disabled>();
return g_state.exception_raised;
}
bool InterpretInstructionPGXP()
{
ExecuteInstruction<PGXPMode::Memory>();
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
Value return_value = m_register_cache.AllocateScratch(RegSize_8);
EmitFunctionCall(&return_value, &Thunks::InterpretInstruction);
EmitFunctionCall(&return_value,
g_settings.gpu_pgxp_enable ? &Thunks::InterpretInstructionPGXP : &Thunks::InterpretInstruction);
EmitExceptionExitOnBool(return_value);
}
else
{
EmitFunctionCall(nullptr, &Thunks::InterpretInstruction);
EmitFunctionCall(nullptr,
g_settings.gpu_pgxp_enable ? &Thunks::InterpretInstructionPGXP : &Thunks::InterpretInstruction);
}
m_current_instruction_in_branch_delay_slot_dirty = cbi.is_branch_instruction;

View file

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

View file

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

View file

@ -8,6 +8,7 @@
#include "common/log.h"
#include "common/progress_callback.h"
#include "common/string_util.h"
#include "host_interface.h"
#include "settings.h"
#include <algorithm>
#include <array>
@ -270,7 +271,12 @@ std::vector<std::string> GameList::ParseM3UFile(const char* path)
const char* GameList::GetGameListCompatibilityRatingString(GameListCompatibilityRating rating)
{
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) ?
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;
else
Log_WarningPrintf("'%s' (%s) not found in compatibility list", entry->code.c_str(), entry->title.c_str());
if (!m_game_settings_load_tried)
LoadGameSettings();
const GameSettings::Entry* settings = m_game_settings.GetEntry(entry->code);
if (settings)
entry->settings = *settings;
}
FILESYSTEM_STAT_DATA ffd;
@ -577,6 +589,12 @@ bool GameList::LoadEntriesFromCache(ByteStream* stream)
ge.type = static_cast<GameListEntryType>(type);
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);
if (iter != m_cache_map.end())
iter->second = std::move(ge);
@ -625,6 +643,7 @@ bool GameList::WriteEntryToCache(const GameListEntry* entry, ByteStream* stream)
result &= WriteU8(stream, static_cast<u8>(entry->region));
result &= WriteU8(stream, static_cast<u8>(entry->type));
result &= WriteU8(stream, static_cast<u8>(entry->compatibility_rating));
result &= entry->settings.SaveToStream(stream);
return result;
}
@ -853,6 +872,18 @@ const GameListEntry* GameList::GetEntryForPath(const char* path) const
return nullptr;
}
GameListEntry* GameList::GetMutableEntryForPath(const char* path)
{
const size_t path_length = std::strlen(path);
for (GameListEntry& entry : m_entries)
{
if (entry.path.size() == path_length && StringUtil::Strcasecmp(entry.path.c_str(), path) == 0)
return &entry;
}
return nullptr;
}
const GameListDatabaseEntry* GameList::GetDatabaseEntryForCode(const std::string& code) const
{
if (!m_database_load_tried)
@ -1272,3 +1303,46 @@ std::string GameList::ExportCompatibilityEntry(const GameListCompatibilityEntry*
entry_elem->Accept(&printer);
return std::string(printer.CStr(), printer.CStrSize());
}
void GameList::LoadGameSettings()
{
if (m_game_settings_load_tried)
return;
m_game_settings_load_tried = true;
if (!m_game_settings_filename.empty() && FileSystem::FileExists(m_user_game_settings_filename.c_str()))
m_game_settings.Load(m_game_settings_filename.c_str());
if (!m_user_game_settings_filename.empty() && FileSystem::FileExists(m_user_game_settings_filename.c_str()))
m_game_settings.Load(m_user_game_settings_filename.c_str());
}
const GameSettings::Entry* GameList::GetGameSettings(const std::string& filename, const std::string& game_code)
{
const GameListEntry* entry = GetMutableEntryForPath(filename.c_str());
if (entry)
return &entry->settings;
if (!m_game_settings_load_tried)
LoadGameSettings();
return m_game_settings.GetEntry(game_code);
}
void GameList::UpdateGameSettings(const std::string& filename, const std::string& game_code,
const std::string& game_title, const GameSettings::Entry& new_entry,
bool save_to_list /* = true */, bool save_to_user /* = true */)
{
GameListEntry* entry = GetMutableEntryForPath(filename.c_str());
if (entry)
{
entry->settings = new_entry;
RewriteCacheFile();
}
if (save_to_list)
{
m_game_settings.SetEntry(game_code, game_title, new_entry,
save_to_user ? m_user_game_settings_filename.c_str() : m_game_settings_filename.c_str());
}
}

View file

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

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

View file

@ -489,7 +489,7 @@ protected:
BitField<u32, bool, 10, 1> draw_to_displayed_field;
BitField<u32, bool, 11, 1> set_mask_while_drawing;
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, 15, 1> texture_disable;
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);
glClear(GL_COLOR_BUFFER_BIT);
glEnable(GL_SCISSOR_TEST);
m_vram_texture.BindFramebuffer(GL_DRAW_FRAMEBUFFER);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, m_vram_fbo_id);
}
void GPU_HW_OpenGL::UpdateDisplay()

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)
{
// 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 fofx = ((float)REGS.OFX / (float)(1 << 16));
const float fofy = ((float)REGS.OFY / (float)(1 << 16));

View file

@ -114,7 +114,7 @@ void HostInterface::ResetSystem()
{
System::Reset();
System::ResetPerformanceCounters();
AddOSDMessage("System reset.");
AddOSDMessage(TranslateStdString("OSDMessage", "System reset."));
}
void HostInterface::PowerOffSystem()
@ -284,13 +284,13 @@ bool HostInterface::LoadState(const char* filename)
if (!stream)
return false;
AddFormattedOSDMessage(5.0f, "Loading state from '%s'...", filename);
AddFormattedOSDMessage(5.0f, TranslateString("OSDMessage", "Loading state from '%s'..."), filename);
if (!System::IsShutdown())
{
if (!System::LoadState(stream.get()))
{
ReportFormattedError("Loading state from '%s' failed. Resetting.", filename);
ReportFormattedError(TranslateString("OSDMessage", "Loading state from '%s' failed. Resetting."), filename);
ResetSystem();
return false;
}
@ -318,12 +318,12 @@ bool HostInterface::SaveState(const char* filename)
const bool result = System::SaveState(stream.get());
if (!result)
{
ReportFormattedError("Saving state to '%s' failed.", filename);
ReportFormattedError(TranslateString("OSDMessage", "Saving state to '%s' failed."), filename);
stream->Discard();
}
else
{
AddFormattedOSDMessage(5.0f, "State saved to '%s'.", filename);
AddFormattedOSDMessage(5.0f, TranslateString("OSDMessage", "State saved to '%s'."), filename);
stream->Commit();
}
@ -358,6 +358,7 @@ void HostInterface::SetDefaultSettings(SettingsInterface& si)
si.SetBoolValue("Main", "SaveStateOnExit", true);
si.SetBoolValue("Main", "ConfirmPowerOff", true);
si.SetBoolValue("Main", "LoadDevicesFromSaveStates", false);
si.SetBoolValue("Main", "ApplyGameSettings", true);
si.SetStringValue("CPU", "ExecutionMode", Settings::GetCPUExecutionModeName(Settings::DEFAULT_CPU_EXECUTION_MODE));
si.SetBoolValue("CPU", "RecompilerMemoryExceptions", false);
@ -375,8 +376,11 @@ void HostInterface::SetDefaultSettings(SettingsInterface& si)
si.SetBoolValue("GPU", "PGXPCulling", true);
si.SetBoolValue("GPU", "PGXPTextureCorrection", true);
si.SetBoolValue("GPU", "PGXPVertexCache", false);
si.SetBoolValue("GPU", "PGXPCPU", false);
si.SetStringValue("Display", "CropMode", Settings::GetDisplayCropModeName(Settings::DEFAULT_DISPLAY_CROP_MODE));
si.SetIntValue("Display", "OverscanActiveStartOffset", 0);
si.SetIntValue("Display", "OverscanActiveEndOffset", 0);
si.SetStringValue("Display", "AspectRatio",
Settings::GetDisplayAspectRatioName(Settings::DEFAULT_DISPLAY_ASPECT_RATIO));
si.SetBoolValue("Display", "LinearFiltering", true);
@ -440,6 +444,32 @@ void HostInterface::LoadSettings(SettingsInterface& si)
g_settings.Load(si);
}
void HostInterface::FixIncompatibleSettings(bool display_osd_messages)
{
if (g_settings.gpu_pgxp_enable)
{
if (g_settings.gpu_renderer == GPURenderer::Software)
{
if (display_osd_messages)
{
AddOSDMessage(TranslateStdString("OSDMessage", "PGXP is incompatible with the software renderer, disabling PGXP."), 10.0f);
}
g_settings.gpu_pgxp_enable = false;
}
else if (g_settings.gpu_pgxp_cpu && g_settings.cpu_execution_mode == CPUExecutionMode::Recompiler)
{
if (display_osd_messages)
{
AddOSDMessage(
TranslateStdString("OSDMessage",
"PGXP CPU mode is incompatible with the recompiler, using Cached Interpreter instead."),
10.0f);
}
g_settings.cpu_execution_mode = CPUExecutionMode::CachedInterpreter;
}
}
}
void HostInterface::SaveSettings(SettingsInterface& si)
{
g_settings.Save(si);
@ -447,7 +477,7 @@ void HostInterface::SaveSettings(SettingsInterface& si)
void HostInterface::CheckForSettingsChanges(const Settings& old_settings)
{
if (!System::IsShutdown())
if (System::IsValid())
{
if (g_settings.gpu_renderer != old_settings.gpu_renderer ||
g_settings.gpu_use_debug_device != old_settings.gpu_use_debug_device)
@ -502,7 +532,9 @@ void HostInterface::CheckForSettingsChanges(const Settings& old_settings)
g_settings.gpu_force_ntsc_timings != old_settings.gpu_force_ntsc_timings ||
g_settings.display_crop_mode != old_settings.display_crop_mode ||
g_settings.display_aspect_ratio != old_settings.display_aspect_ratio ||
g_settings.gpu_pgxp_enable != old_settings.gpu_pgxp_enable)
g_settings.gpu_pgxp_enable != old_settings.gpu_pgxp_enable ||
g_settings.display_active_start_offset != old_settings.display_active_start_offset ||
g_settings.display_active_end_offset != old_settings.display_active_end_offset)
{
g_gpu->UpdateSettings();
}
@ -658,6 +690,16 @@ float HostInterface::GetFloatSettingValue(const char* section, const char* key,
return float_value.value_or(default_value);
}
TinyString HostInterface::TranslateString(const char* context, const char* str) const
{
return str;
}
std::string HostInterface::TranslateStdString(const char* context, const char* str) const
{
return str;
}
void HostInterface::ToggleSoftwareRendering()
{
if (System::IsShutdown() || g_settings.gpu_renderer == GPURenderer::Software)

View file

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

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_SWC2(u32 instr, u32 rtVal, u32 addr); // copy GTE reg to memory
bool GetPreciseVertex(u32 addr, u32 value, int x, int y, int xOffs, int yOffs, float* out_x, float* out_y, float* out_w);
bool GetPreciseVertex(u32 addr, u32 value, int x, int y, int xOffs, int yOffs, float* out_x, float* out_y,
float* out_w);
// -- CPU functions
void CPU_LW(u32 instr, u32 rtVal, u32 addr);
@ -51,4 +52,56 @@ void CPU_SB(u32 instr, u8 rtVal, u32 addr);
void CPU_SH(u32 instr, u16 rtVal, u32 addr);
void CPU_SW(u32 instr, u32 rtVal, u32 addr);
// Arithmetic with immediate value
void CPU_ADDI(u32 instr, u32 rtVal, u32 rsVal);
void CPU_ADDIU(u32 instr, u32 rtVal, u32 rsVal);
void CPU_ANDI(u32 instr, u32 rtVal, u32 rsVal);
void CPU_ORI(u32 instr, u32 rtVal, u32 rsVal);
void CPU_XORI(u32 instr, u32 rtVal, u32 rsVal);
void CPU_SLTI(u32 instr, u32 rtVal, u32 rsVal);
void CPU_SLTIU(u32 instr, u32 rtVal, u32 rsVal);
// Load Upper
void CPU_LUI(u32 instr, u32 rtVal);
// Register Arithmetic
void CPU_ADD(u32 instr, u32 rdVal, u32 rsVal, u32 rtVal);
void CPU_ADDU(u32 instr, u32 rdVal, u32 rsVal, u32 rtVal);
void CPU_SUB(u32 instr, u32 rdVal, u32 rsVal, u32 rtVal);
void CPU_SUBU(u32 instr, u32 rdVal, u32 rsVal, u32 rtVal);
void CPU_AND_(u32 instr, u32 rdVal, u32 rsVal, u32 rtVal);
void CPU_OR_(u32 instr, u32 rdVal, u32 rsVal, u32 rtVal);
void CPU_XOR_(u32 instr, u32 rdVal, u32 rsVal, u32 rtVal);
void CPU_NOR(u32 instr, u32 rdVal, u32 rsVal, u32 rtVal);
void CPU_SLT(u32 instr, u32 rdVal, u32 rsVal, u32 rtVal);
void CPU_SLTU(u32 instr, u32 rdVal, u32 rsVal, u32 rtVal);
// Register mult/div
void CPU_MULT(u32 instr, u32 hiVal, u32 loVal, u32 rsVal, u32 rtVal);
void CPU_MULTU(u32 instr, u32 hiVal, u32 loVal, u32 rsVal, u32 rtVal);
void CPU_DIV(u32 instr, u32 hiVal, u32 loVal, u32 rsVal, u32 rtVal);
void CPU_DIVU(u32 instr, u32 hiVal, u32 loVal, u32 rsVal, u32 rtVal);
// Shift operations (sa)
void CPU_SLL(u32 instr, u32 rdVal, u32 rtVal);
void CPU_SRL(u32 instr, u32 rdVal, u32 rtVal);
void CPU_SRA(u32 instr, u32 rdVal, u32 rtVal);
// Shift operations variable
void CPU_SLLV(u32 instr, u32 rdVal, u32 rtVal, u32 rsVal);
void CPU_SRLV(u32 instr, u32 rdVal, u32 rtVal, u32 rsVal);
void CPU_SRAV(u32 instr, u32 rdVal, u32 rtVal, u32 rsVal);
// Move registers
void CPU_MFHI(u32 instr, u32 rdVal, u32 hiVal);
void CPU_MTHI(u32 instr, u32 hiVal, u32 rdVal);
void CPU_MFLO(u32 instr, u32 rdVal, u32 loVal);
void CPU_MTLO(u32 instr, u32 loVal, u32 rdVal);
// CP0 Data transfer tracking
void CPU_MFC0(u32 instr, u32 rtVal, u32 rdVal);
void CPU_MTC0(u32 instr, u32 rdVal, u32 rtVal);
void CPU_CFC0(u32 instr, u32 rtVal, u32 rdVal);
void CPU_CTC0(u32 instr, u32 rdVal, u32 rtVal);
} // namespace PGXP

View file

@ -84,6 +84,7 @@ void Settings::Load(SettingsInterface& si)
save_state_on_exit = si.GetBoolValue("Main", "SaveStateOnExit", true);
confim_power_off = si.GetBoolValue("Main", "ConfirmPowerOff", true);
load_devices_from_save_states = si.GetBoolValue("Main", "LoadDevicesFromSaveStates", false);
apply_game_settings = si.GetBoolValue("Main", "ApplyGameSettings", true);
cpu_execution_mode =
ParseCPUExecutionMode(
@ -106,6 +107,7 @@ void Settings::Load(SettingsInterface& si)
gpu_pgxp_culling = si.GetBoolValue("GPU", "PGXPCulling", true);
gpu_pgxp_texture_correction = si.GetBoolValue("GPU", "PGXPTextureCorrection", true);
gpu_pgxp_vertex_cache = si.GetBoolValue("GPU", "PGXPVertexCache", false);
gpu_pgxp_cpu = si.GetBoolValue("GPU", "PGXPCPU", false);
display_crop_mode =
ParseDisplayCropMode(
@ -115,6 +117,8 @@ void Settings::Load(SettingsInterface& si)
ParseDisplayAspectRatio(
si.GetStringValue("Display", "AspectRatio", GetDisplayAspectRatioName(DEFAULT_DISPLAY_ASPECT_RATIO)).c_str())
.value_or(DEFAULT_DISPLAY_ASPECT_RATIO);
display_active_start_offset = static_cast<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_integer_scaling = si.GetBoolValue("Display", "IntegerScaling", false);
display_show_osd_messages = si.GetBoolValue("Display", "ShowOSDMessages", true);
@ -197,6 +201,7 @@ void Settings::Save(SettingsInterface& si) const
si.SetBoolValue("Main", "SaveStateOnExit", save_state_on_exit);
si.SetBoolValue("Main", "ConfirmPowerOff", confim_power_off);
si.SetBoolValue("Main", "LoadDevicesFromSaveStates", load_devices_from_save_states);
si.SetBoolValue("Main", "ApplyGameSettings", apply_game_settings);
si.SetStringValue("CPU", "ExecutionMode", GetCPUExecutionModeName(cpu_execution_mode));
si.SetBoolValue("CPU", "RecompilerMemoryExceptions", cpu_recompiler_memory_exceptions);
@ -215,8 +220,11 @@ void Settings::Save(SettingsInterface& si) const
si.SetBoolValue("GPU", "PGXPCulling", gpu_pgxp_culling);
si.SetBoolValue("GPU", "PGXPTextureCorrection", gpu_pgxp_texture_correction);
si.SetBoolValue("GPU", "PGXPVertexCache", gpu_pgxp_vertex_cache);
si.SetBoolValue("GPU", "PGXPCPU", gpu_pgxp_cpu);
si.SetStringValue("Display", "CropMode", GetDisplayCropModeName(display_crop_mode));
si.SetIntValue("Display", "ActiveStartOffset", display_active_start_offset);
si.SetIntValue("Display", "ActiveEndOffset", display_active_end_offset);
si.SetStringValue("Display", "AspectRatio", GetDisplayAspectRatioName(display_aspect_ratio));
si.SetBoolValue("Display", "LinearFiltering", display_linear_filtering);
si.SetBoolValue("Display", "IntegerScaling", display_integer_scaling);
@ -283,7 +291,10 @@ void Settings::Save(SettingsInterface& si) const
static std::array<const char*, LOGLEVEL_COUNT> s_log_level_names = {
{"None", "Error", "Warning", "Perf", "Success", "Info", "Dev", "Profile", "Debug", "Trace"}};
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)
{
@ -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_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)
{
@ -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_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)
{
@ -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_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)
{
@ -400,9 +414,10 @@ static std::array<const char*, 4> s_gpu_renderer_names = {{
"Vulkan", "OpenGL", "Software"}};
static std::array<const char*, 4> s_gpu_renderer_display_names = {{
#ifdef WIN32
"Hardware (D3D11)",
TRANSLATABLE("GPURenderer", "Hardware (D3D11)"),
#endif
"Hardware (Vulkan)", "Hardware (OpenGL)", "Software"}};
TRANSLATABLE("GPURenderer", "Hardware (Vulkan)"), TRANSLATABLE("GPURenderer", "Hardware (OpenGL)"),
TRANSLATABLE("GPURenderer", "Software")}};
std::optional<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_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)
{
@ -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_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)
{
@ -514,7 +532,9 @@ const char* Settings::GetAudioBackendDisplayName(AudioBackend backend)
static std::array<const char*, 6> s_controller_type_names = {
{"None", "DigitalController", "AnalogController", "NamcoGunCon", "PlayStationMouse", "NeGcon"}};
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)
{
@ -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_display_names = {{"No Memory Card", "Shared Between All Games",
"Separate Card Per Game (Game Code)",
"Separate Card Per Game (Game Title)"}};
static std::array<const char*, 4> s_memory_card_type_display_names = {
{TRANSLATABLE("MemoryCardType", "No Memory Card"), TRANSLATABLE("MemoryCardType", "Shared Between All Games"),
TRANSLATABLE("MemoryCardType", "Separate Card Per Game (Game Code)"),
TRANSLATABLE("MemoryCardType", "Separate Card Per Game (Game Title)")}};
std::optional<MemoryCardType> Settings::ParseMemoryCardTypeName(const char* str)
{

View file

@ -78,6 +78,7 @@ struct Settings
bool save_state_on_exit = true;
bool confim_power_off = true;
bool load_devices_from_save_states = false;
bool apply_game_settings = true;
GPURenderer gpu_renderer = GPURenderer::Software;
std::string gpu_adapter;
@ -93,7 +94,10 @@ struct Settings
bool gpu_pgxp_culling = true;
bool gpu_pgxp_texture_correction = true;
bool gpu_pgxp_vertex_cache = false;
bool gpu_pgxp_cpu = false;
DisplayCropMode display_crop_mode = DisplayCropMode::None;
s16 display_active_start_offset = 0;
s16 display_active_end_offset = 0;
DisplayAspectRatio display_aspect_ratio = DisplayAspectRatio::R4_3;
bool display_linear_filtering = true;
bool display_integer_scaling = false;
@ -157,6 +161,11 @@ struct Settings
ALWAYS_INLINE bool IsUsingRecompiler() const { return (cpu_execution_mode == CPUExecutionMode::Recompiler); }
ALWAYS_INLINE bool IsUsingSoftwareRenderer() const { return (gpu_renderer == GPURenderer::Software); }
ALWAYS_INLINE PGXPMode GetPGXPMode()
{
return gpu_pgxp_enable ? (gpu_pgxp_cpu ? PGXPMode::CPU : PGXPMode::Memory) : PGXPMode::Disabled;
}
bool HasAnyPerGameMemoryCards() const;
enum : u32

View file

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

View file

@ -48,6 +48,13 @@ enum class CPUExecutionMode : u8
Count
};
enum class PGXPMode : u8
{
Disabled,
Memory,
CPU
};
enum class GPURenderer : u8
{
#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()
{
if (s_libretro_log_callback_registered)
@ -84,6 +101,7 @@ bool LibretroHostInterface::Initialize()
return false;
LoadSettings();
FixIncompatibleSettings(true);
UpdateLogging();
return true;
}
@ -352,7 +370,7 @@ void LibretroHostInterface::OnSystemDestroyed()
m_using_hardware_renderer = false;
}
static std::array<retro_core_option_definition, 30> s_option_definitions = {{
static std::array<retro_core_option_definition, 31> s_option_definitions = {{
{"duckstation_Console.Region",
"Console Region",
"Determines which region/hardware to emulate. Auto-Detect will use the region of the disc inserted.",
@ -483,6 +501,12 @@ static std::array<retro_core_option_definition, 30> s_option_definitions = {{
"Uses screen coordinates as a fallback when tracking vertices through memory fails. May improve PGXP compatibility.",
{{"true", "Enabled"}, {"false", "Disabled"}},
"false"},
{"duckstation_GPU.PGXPCPU",
"PGXP CPU Mode",
"Tries to track vertex manipulation through the CPU. Some games require this option for PGXP to be effective. "
"Very slow, and incompatible with the recompiler.",
{{"true", "Enabled"}, {"false", "Disabled"}},
"false"},
{"duckstation_Display.CropMode",
"Crop Mode",
"Changes how much of the image is cropped. Some games display garbage in the overscan area which is typically "
@ -494,9 +518,9 @@ static std::array<retro_core_option_definition, 30> s_option_definitions = {{
"Sets the core-provided aspect ratio.",
{{"4:3", "4:3"}, {"16:9", "16:9"}, {"2:1", "2:1 (VRAM 1:1)"}, {"1:1", "1:1"}},
"4:3"},
{"duckstation_MemoryCards.LoadFromSaveStates",
"Load Memory Cards From Save States",
"Sets whether the contents of memory cards will be loaded when a save state is loaded.",
{"duckstation_Main.LoadDevicesFromSaveStates",
"Load Devices From Save States",
"Sets whether the contents of devices and memory cards will be loaded when a save state is loaded.",
{{"true", "Enabled"}, {"false", "Disabled"}},
"false"},
{"duckstation_MemoryCards.Card1Type",
@ -520,7 +544,7 @@ static std::array<retro_core_option_definition, 30> s_option_definitions = {{
"When using a playlist (m3u) and per-game (title) memory cards, a single memory card "
"will be used for all discs. If unchecked, a separate card will be used for each disc.",
{{"true", "Enabled"}, {"false", "Disabled"}},
"false"},
"true"},
{"duckstation_Controller1.Type",
"Controller 1 Type",
"Sets the type of controller for Slot 1.",
@ -590,7 +614,7 @@ bool LibretroHostInterface::HasCoreVariablesChanged()
void LibretroHostInterface::LoadSettings()
{
LibretroSettingsInterface si;
g_settings.Load(si);
HostInterface::LoadSettings(si);
// Assume BIOS files are located in system directory.
const char* system_directory = nullptr;
@ -608,6 +632,7 @@ void LibretroHostInterface::UpdateSettings()
{
Settings old_settings(std::move(g_settings));
LoadSettings();
FixIncompatibleSettings(false);
if (g_settings.gpu_resolution_scale != old_settings.gpu_resolution_scale &&
g_settings.gpu_renderer != GPURenderer::Software)
@ -650,6 +675,11 @@ void LibretroHostInterface::CheckForSettingsChanges(const Settings& old_settings
UpdateLogging();
}
void LibretroHostInterface::InitRumbleInterface()
{
m_rumble_interface_valid = g_retro_environment_callback(RETRO_ENVIRONMENT_GET_RUMBLE_INTERFACE, &m_rumble_interface);
}
void LibretroHostInterface::UpdateControllers()
{
g_retro_input_poll_callback();
@ -698,12 +728,21 @@ void LibretroHostInterface::UpdateControllersDigitalController(u32 index)
{DigitalController::Button::R1, RETRO_DEVICE_ID_JOYPAD_R},
{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)
{
const int16_t state = g_retro_input_state_callback(index, RETRO_DEVICE_JOYPAD, 0, it.second);
controller->SetButtonState(it.first, state != 0);
}
}
}
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::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)
{
const int16_t state = g_retro_input_state_callback(index, RETRO_DEVICE_JOYPAD, 0, it.second);
controller->SetButtonState(it.first, state != 0);
}
}
for (const auto& it : axis_mapping)
{
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));
}
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)

View file

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

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)
{
g_retro_environment_callback = f;
if (!g_libretro_host_interface.SetCoreOptions())
Log_WarningPrintf("Failed to set core options, settings will not be changeable.");
g_libretro_host_interface.InitLogging();
g_libretro_host_interface.InitDiskControlInterface();
g_libretro_host_interface.InitInterfaces();
}
RETRO_API void retro_set_video_refresh(retro_video_refresh_t f)

View file

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

View file

@ -1,4 +1,5 @@
#include "advancedsettingswidget.h"
#include "mainwindow.h"
#include "settingsdialog.h"
#include "settingwidgetbinder.h"
@ -8,7 +9,7 @@ AdvancedSettingsWidget::AdvancedSettingsWidget(QtHostInterface* host_interface,
m_ui.setupUi(this);
for (u32 i = 0; i < static_cast<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",
&Settings::ParseLogLevelName, &Settings::GetLogLevelName,
@ -27,9 +28,12 @@ AdvancedSettingsWidget::AdvancedSettingsWidget(QtHostInterface* host_interface,
SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.cpuRecompilerMemoryExceptions, "CPU",
"RecompilerMemoryExceptions", false);
SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.showDebugMenu, "Main", "ShowDebugMenu");
SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.gpuUseDebugDevice, "GPU", "UseDebugDevice");
connect(m_ui.resetToDefaultButton, &QPushButton::clicked, this, &AdvancedSettingsWidget::onResetToDefaultClicked);
connect(m_ui.showDebugMenu, &QCheckBox::toggled, m_host_interface->getMainWindow(),
&MainWindow::updateDebugMenuVisibility, Qt::QueuedConnection);
dialog->registerWidgetHelp(m_ui.gpuUseDebugDevice, tr("Use Debug Host GPU Device"), tr("Unchecked"),
tr("Enables the usage of debug devices and shaders for rendering APIs which support them. "

View file

@ -207,7 +207,14 @@
<string>System Settings</string>
</property>
<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">
<property name="text">
<string>Use Debug Host GPU Device</string>

View file

@ -11,7 +11,10 @@ AudioSettingsWidget::AudioSettingsWidget(QtHostInterface* host_interface, QWidge
m_ui.setupUi(this);
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",
&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_RELEASE_URL[] =
"https://api.github.com/repos/stenzek/duckstation/releases/tags/" SCM_RELEASE_TAG;
static constexpr char CHANGES_URL[] = "https://api.github.com/repos/stenzek/duckstation/compare/%s..." SCM_RELEASE_TAG;
static constexpr char UPDATE_ASSET_FILENAME[] = SCM_RELEASE_ASSET;
#endif
@ -196,10 +197,11 @@ void AutoUpdaterDialog::getLatestReleaseComplete(QNetworkReply* reply)
m_download_url = asset_obj["browser_download_url"].toString();
if (!m_download_url.isEmpty())
{
m_ui.currentVersion->setText(tr("Current Version: %1 (%2)").arg(g_scm_hash_str).arg(__TIMESTAMP__));
m_ui.currentVersion->setText(tr("Current Version: %1 (%2)").arg(g_scm_hash_str).arg(g_scm_date_str));
m_ui.newVersion->setText(
tr("New Version: %1 (%2)").arg(m_latest_sha).arg(doc_object["published_at"].toString()));
m_ui.updateNotes->setText(doc_object["body"].toString());
m_ui.updateNotes->setText(tr("Loading..."));
queueGetChanges();
exec();
emit updateCheckCompleted();
return;
@ -223,6 +225,68 @@ void AutoUpdaterDialog::getLatestReleaseComplete(QNetworkReply* reply)
#endif
}
void AutoUpdaterDialog::queueGetChanges()
{
#ifdef AUTO_UPDATER_SUPPORTED
connect(m_network_access_mgr, &QNetworkAccessManager::finished, this, &AutoUpdaterDialog::getChangesComplete);
const std::string url_string(StringUtil::StdStringFromFormat(CHANGES_URL, g_scm_hash_str));
QUrl url(QUrl::fromEncoded(QByteArray(url_string.c_str(), static_cast<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()
{
QUrl url(m_download_url);

View file

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

View file

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

View file

@ -11,10 +11,16 @@ ConsoleSettingsWidget::ConsoleSettingsWidget(QtHostInterface* host_interface, QW
m_ui.setupUi(this);
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++)
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",
&Settings::ParseConsoleRegionName, &Settings::GetConsoleRegionName,

View file

@ -9,6 +9,7 @@
#include <QtCore/QSignalBlocker>
#include <QtCore/QTimer>
#include <QtGui/QCursor>
#include <QtGui/QGuiApplication>
#include <QtGui/QKeyEvent>
#include <QtWidgets/QFileDialog>
#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++)
{
ui->controller_type->addItem(
QString::fromUtf8(Settings::GetControllerTypeDisplayName(static_cast<ControllerType>(i))));
qApp->translate("ControllerType", Settings::GetControllerTypeDisplayName(static_cast<ControllerType>(i))));
}
ControllerType ctype =
Settings::ParseControllerTypeName(

View file

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

View file

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

View file

@ -74,6 +74,11 @@ void GamePropertiesDialog::populate(const GameListEntry* ge)
}
populateTracksInfo(ge->path);
m_game_code = ge->code;
m_game_title = ge->title;
m_game_settings = ge->settings;
populateGameSettings();
}
void GamePropertiesDialog::populateCompatibilityInfo(const std::string& game_code)
@ -99,12 +104,50 @@ void GamePropertiesDialog::populateCompatibilityInfo(const std::string& game_cod
void GamePropertiesDialog::setupAdditionalUi()
{
for (u8 i = 0; i < static_cast<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++)
{
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);
@ -133,7 +176,7 @@ void GamePropertiesDialog::populateTracksInfo(const std::string& image_path)
{"Audio", "Mode 1", "Mode 1/Raw", "Mode 2", "Mode 2/Form 1", "Mode 2/Form 2", "Mode 2/Mix", "Mode 2/Raw"}};
m_ui.tracks->clearContents();
m_image_path = image_path;
m_path = image_path;
std::unique_ptr<CDImage> image = CDImage::Open(image_path.c_str());
if (!image)
@ -147,14 +190,74 @@ void GamePropertiesDialog::populateTracksInfo(const std::string& image_path)
const CDImage::TrackMode mode = image->GetTrackMode(static_cast<u8>(track));
const int row = static_cast<int>(track - 1u);
m_ui.tracks->insertRow(row);
m_ui.tracks->setItem(row, 0, new QTableWidgetItem(tr("%1").arg(track)));
m_ui.tracks->setItem(row, 1, new QTableWidgetItem(tr(track_mode_strings[static_cast<u32>(mode)])));
m_ui.tracks->setItem(row, 0, new QTableWidgetItem(QStringLiteral("%1").arg(track)));
m_ui.tracks->setItem(row, 1, new QTableWidgetItem(track_mode_strings[static_cast<u32>(mode)]));
m_ui.tracks->setItem(row, 2, new QTableWidgetItem(MSFTotString(position)));
m_ui.tracks->setItem(row, 3, new QTableWidgetItem(MSFTotString(length)));
m_ui.tracks->setItem(row, 4, new QTableWidgetItem(tr("<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)
{
deleteLater();
@ -186,6 +289,69 @@ void GamePropertiesDialog::connectUi()
connect(m_ui.exportCompatibilityInfo, &QPushButton::clicked, this,
&GamePropertiesDialog::onExportCompatibilityInfoClicked);
connect(m_ui.close, &QPushButton::clicked, this, &QDialog::close);
connect(m_ui.userAspectRatio, QOverload<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)
@ -263,10 +429,10 @@ void GamePropertiesDialog::onExportCompatibilityInfoClicked()
void GamePropertiesDialog::computeTrackHashes()
{
if (m_image_path.empty())
if (m_path.empty())
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)
return;

View file

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

View file

@ -6,18 +6,28 @@
<rect>
<x>0</x>
<y>0</y>
<width>722</width>
<height>466</height>
<width>793</width>
<height>647</height>
</rect>
</property>
<property name="windowTitle">
<string>Dialog</string>
</property>
<property name="windowIcon">
<iconset resource="resources/icons.qrc">
<iconset>
<normaloff>:/icons/duck.png</normaloff>:/icons/duck.png</iconset>
</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">
<widget class="QLabel" name="label_4">
<property name="text">
@ -125,14 +135,14 @@
</item>
</layout>
</item>
<item row="8" column="0">
<item row="8" column="0" colspan="2">
<widget class="QLabel" name="label_8">
<property name="text">
<string>Tracks:</string>
</property>
</widget>
</item>
<item row="8" column="1">
<item row="9" column="0" colspan="2">
<widget class="QTableWidget" name="tracks">
<property name="editTriggers">
<set>QAbstractItemView::NoEditTriggers</set>
@ -170,7 +180,172 @@
</column>
</widget>
</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">
<item>
<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.loadDevicesFromSaveStates, "Main",
"LoadDevicesFromSaveStates", false);
SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.applyGameSettings, "Main", "ApplyGameSettings",
true);
SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.showOSDMessages, "Display", "ShowOSDMessages",
true);
SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.showFPS, "Display", "ShowFPS", false);
SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.showVPS, "Display", "ShowVPS", false);
SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.showSpeed, "Display", "ShowSpeed", false);
SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.showResolution, "Display", "ShowResolution", false);
SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.showResolution, "Display", "ShowResolution",
false);
SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.enableSpeedLimiter, "Main", "SpeedLimiterEnabled",
true);
@ -57,6 +60,10 @@ GeneralSettingsWidget::GeneralSettingsWidget(QtHostInterface* host_interface, QW
tr("When enabled, memory cards and controllers will be overwritten when save states are loaded. This can "
"result in lost saves, and controller type mismatches. For deterministic save states, enable this option, "
"otherwise leave disabled."));
dialog->registerWidgetHelp(
m_ui.applyGameSettings, tr("Apply Per-Game Settings"), tr("Checked"),
tr("When enabled, per-game settings will be applied, and incompatible enhancements will be disabled. You should "
"leave this option enabled except when testing enhancements with incompatible games."));
dialog->registerWidgetHelp(
m_ui.enableSpeedLimiter, tr("Enable Speed Limiter"), tr("Checked"),
tr("Throttles the emulation speed to the chosen speed above. If unchecked, the emulator will "
@ -82,26 +89,33 @@ GeneralSettingsWidget::GeneralSettingsWidget(QtHostInterface* host_interface, QW
tr("Shows the current emulation speed of the system in the top-right corner of the display as a percentage."));
// Since this one is compile-time selected, we don't put it in the .ui file.
const int last_row_count = m_ui.formLayout_4->rowCount();
int current_col = 1;
int current_row = m_ui.formLayout_4->rowCount() - current_col;
#ifdef WITH_DISCORD_PRESENCE
{
QCheckBox* enableDiscordPresence = new QCheckBox(tr("Enable Discord Presence"), m_ui.groupBox_4);
SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, enableDiscordPresence, "Main",
"EnableDiscordPresence");
m_ui.formLayout_4->addWidget(enableDiscordPresence, last_row_count, 0);
m_ui.formLayout_4->addWidget(enableDiscordPresence, current_row, current_col);
dialog->registerWidgetHelp(enableDiscordPresence, tr("Enable Discord Presence"), tr("Unchecked"),
tr("Shows the game you are currently playing as part of your profile in Discord."));
current_col++;
current_row += (current_col / 2);
current_col %= 2;
}
#endif
if (AutoUpdaterDialog::isSupported())
{
QCheckBox* enableDiscordPresence = new QCheckBox(tr("Enable Automatic Update Check"), m_ui.groupBox_4);
SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, enableDiscordPresence, "AutoUpdater",
"CheckAtStartup");
m_ui.formLayout_4->addWidget(enableDiscordPresence, last_row_count, 1);
"CheckAtStartup", true);
m_ui.formLayout_4->addWidget(enableDiscordPresence, current_row, current_col);
dialog->registerWidgetHelp(enableDiscordPresence, tr("Enable Automatic Update Check"), tr("Checked"),
tr("Automatically checks for updates to the program on startup. Updates can be deferred "
"until later or skipped entirely."));
current_col++;
current_row += (current_col / 2);
current_col %= 2;
}
}

View file

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

View file

@ -44,6 +44,7 @@ GPUSettingsWidget::GPUSettingsWidget(QtHostInterface* host_interface, QWidget* p
SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.pgxpTextureCorrection, "GPU",
"PGXPTextureCorrection", true);
SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.pgxpVertexCache, "GPU", "PGXPVertexCache", false);
SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.pgxpCPUMode, "GPU", "PGXPCPUMode", false);
connect(m_ui.resolutionScale, QOverload<int>::of(&QComboBox::currentIndexChanged), this,
&GPUSettingsWidget::updateScaledDitheringEnabled);
@ -128,7 +129,8 @@ GPUSettingsWidget::GPUSettingsWidget(QtHostInterface* host_interface, QWidget* p
m_ui.widescreenHack, tr("Widescreen Hack"), tr("Unchecked"),
tr("Scales vertex positions in screen-space to a widescreen aspect ratio, essentially "
"increasing the field of view from 4:3 to 16:9 in 3D games. <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(
m_ui.pgxpEnable, tr("Geometry Correction"), tr("Unchecked"),
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"),
tr("Uses screen coordinates as a fallback when tracking vertices through memory fails. "
"May improve PGXP compatibility."));
dialog->registerWidgetHelp(
m_ui.pgxpCPUMode, tr("CPU Mode"), tr("Unchecked"),
tr("Tries to track vertex manipulation through the CPU. Some games require this option for PGXP to be effective. "
"Very slow, and incompatible with the recompiler."));
}
GPUSettingsWidget::~GPUSettingsWidget() = default;
@ -157,7 +163,10 @@ void GPUSettingsWidget::updateScaledDitheringEnabled()
void GPUSettingsWidget::setupAdditionalUi()
{
for (u32 i = 0; i < static_cast<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++)
{
@ -168,7 +177,7 @@ void GPUSettingsWidget::setupAdditionalUi()
for (u32 i = 0; i < static_cast<u32>(DisplayCropMode::Count); i++)
{
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 = {{
@ -255,4 +264,5 @@ void GPUSettingsWidget::updatePGXPSettingsEnabled()
m_ui.pgxpCulling->setEnabled(enabled);
m_ui.pgxpTextureCorrection->setEnabled(enabled);
m_ui.pgxpVertexCache->setEnabled(enabled);
m_ui.pgxpCPUMode->setEnabled(enabled);
}

View file

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

View file

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

View file

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

View file

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

View file

@ -30,7 +30,7 @@
<x>0</x>
<y>0</y>
<width>754</width>
<height>22</height>
<height>30</height>
</rect>
</property>
<widget class="QMenu" name="menuSystem">
@ -161,8 +161,19 @@
<addaction name="actionDebugShowTimersState"/>
<addaction name="actionDebugShowMDECState"/>
</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="menuSettings"/>
<addaction name="menu_View"/>
<addaction name="menuDebug"/>
<addaction name="menuHelp"/>
</widget>
@ -560,6 +571,41 @@
<string>Resumes the last save state created.</string>
</property>
</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>
<resources>
<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++)
{
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;

View file

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

View file

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

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
..\..\dep\msvc\qt\5.15.0\msvc2017_64\bin\lupdate.exe ./ -ts translations\duckstation-qt_he.ts
..\..\dep\msvc\qt\5.15.0\msvc2017_64\bin\lupdate.exe ./ -ts translations\duckstation-qt_pt-br.ts
..\..\dep\msvc\qt\5.15.0\msvc2017_64\bin\lupdate.exe ./ -ts translations\duckstation-qt_pt-pt.ts
..\..\dep\msvc\qt\5.15.0\msvc2017_64\bin\lupdate.exe ./ -ts translations\duckstation-qt_zh-cn.ts
set LUPDATE=..\..\dep\msvc\qt\5.15.0\msvc2017_64\bin\lupdate.exe ./ ../core/ ../frontend-common/ -tr-function-alias translate+=TranslateString -tr-function-alias translate+=TranslateStdString -tr-function-alias QT_TRANSLATE_NOOP+=TRANSLATABLE
%LUPDATE% -ts translations\duckstation-qt_de.ts
%LUPDATE% -ts translations\duckstation-qt_es.ts
%LUPDATE% -ts translations\duckstation-qt_he.ts
%LUPDATE% -ts translations\duckstation-qt_it.ts
%LUPDATE% -ts translations\duckstation-qt_pt-br.ts
%LUPDATE% -ts translations\duckstation-qt_pt-pt.ts
%LUPDATE% -ts translations\duckstation-qt_zh-cn.ts
pause

View file

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

View file

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

View file

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

View file

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

View file

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

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

View file

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

View file

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