Compare commits

...

65 Commits

Author SHA1 Message Date
cristian64 dc88326d60
Merge 788f140936 into 2c83a256ae 2025-01-17 14:44:27 +05:00
Admiral H. Curtiss 2c83a256ae
Merge pull request #13277 from jordan-woyak/iowindow-debug-assert-fix
DolphinQt: Fix ASSERT failure in IOWindow in debug build.
2025-01-16 10:04:55 +01:00
Jordan Woyak c3f66e83e6 DolphinQt: Fix ASSERT failure in IOWindow in debug build. 2025-01-15 16:39:45 -06:00
OatmealDome c344514ba2
Merge pull request #13270 from OatmealDome/vk-metal-layer
Vulkan: Only attempt to create a CAMetalLayer on macOS
2025-01-15 15:08:03 -05:00
Admiral H. Curtiss 3ea870ef8c
Merge pull request #13244 from dreamsyntax/bugfix-double-osd-texture-messages
Core/VideoCommon: Fix duplicate OSD Custom Textures messages
2025-01-12 16:16:09 +01:00
Admiral H. Curtiss ede963d4db
Merge pull request #13272 from dreamsyntax/ppc-crlf
PPCSymbolDB: Fix loading maps with CRLF endings
2025-01-12 14:26:27 +01:00
Admiral H. Curtiss b0e5ebc80d
Merge pull request #13247 from sepalani/debug-map-ranges
PPCSymbolDB: Refactor SymbolMap Save/Load
2025-01-12 14:26:04 +01:00
JosJuice da0a0c87c2
Merge pull request #13274 from dpeukert/master
Clean up CRLF line endings
2025-01-12 14:11:50 +01:00
Daniel Peukert f28e134c88
Clean up CRLF line endings 2025-01-12 11:06:04 +01:00
dreamsyntax b9a2d89035 PPCSymbolDB: Fix loading maps with CRLF endings
Symbol maps ending in CRLF were not properly loading on non-windows
systems.
2025-01-10 14:37:58 -07:00
Admiral H. Curtiss 93fc5c02ac
Merge pull request #13179 from TellowKrinkle/ForceSystemLib
CMake: Optional system library fixes
2025-01-10 04:56:08 +01:00
Admiral H. Curtiss 98a80239f1
Merge pull request #13030 from JosJuice/android-wait-for-surface-in-run
Android: Wait for surface in Run
2025-01-10 04:50:34 +01:00
Admiral H. Curtiss 75abda6a3a
Merge pull request #12864 from JosJuice/android-window-compat
Android: Replace deprecated setSystemUiVisibility
2025-01-10 04:45:11 +01:00
Admiral H. Curtiss d10cb9dfc4
Merge pull request #13238 from JosJuice/jitarm64-rlwinmx-imm-mask
JitArm64: Handle rlwinmx with zero mask
2025-01-10 04:44:22 +01:00
Tilka e24e107b3c
Merge pull request #13265 from Sintendo/warnings
Fix several minor warnings
2025-01-08 21:44:24 +00:00
Tilka ffa312f8e8
Merge pull request #13158 from jordan-woyak/wiiu-rename
DolphinQt: Rename "GameCube Adapter for Wii U".
2025-01-08 21:39:08 +00:00
Admiral H. Curtiss 0e5d7d0f2d
Merge pull request #13268 from AdmiralCurtiss/ini-delete-case-sensitive
Common/IniFile: Fix case sensitivity mismatch in IniFile::Section::Delete()
2025-01-08 21:42:32 +01:00
OatmealDome 2633b84b98 Vulkan: Only attempt to create a CAMetalLayer on macOS 2025-01-08 13:51:53 -05:00
Admiral H. Curtiss 8f9e3ffd83
Merge pull request #13228 from OatmealDome/fix-curl-2
curl: Update to 8.11.1 and use CMake to configure
2025-01-08 18:14:10 +01:00
Admiral H. Curtiss 22dc21cca4
Merge pull request #13262 from Ferdi265/feature/fix-fmt11
Fix compiling with libfmt>=11.0
2025-01-08 18:12:19 +01:00
Admiral H. Curtiss c567248b73
Common/IniFile: Fix case sensitivity mismatch in IniFile::Section::Delete()
values uses a case insensitive comparison, so erasing the equivalent key in keys_order also must do so.
2025-01-08 05:30:39 +01:00
Admiral H. Curtiss 7133bfbb0e
Merge pull request #13180 from jordan-woyak/eof-logic
Core/VideoCommon: Fix some weird (!eof) logic.
2025-01-08 05:27:42 +01:00
OatmealDome d525776ae6 GeckoCodeConfig: Remove HTTPS workaround for Android 2025-01-07 23:02:13 -05:00
OatmealDome 4dc368d8df WiiUtils: Remove HTTPS workaround for Android 2025-01-07 23:02:13 -05:00
OatmealDome 0b7f399436 curl: Set CA path on Android to the system-provided store 2025-01-07 23:02:13 -05:00
OatmealDome b5918effeb curl: Update to 8.11.1 and use CMake to configure 2025-01-07 23:02:10 -05:00
OatmealDome 696b363f47
Merge pull request #13162 from jordan-woyak/non-blocking-input-detection
DolphinQt/InputCommon: Make input mapping and output testing non-blocking.
2025-01-07 16:55:55 -05:00
Sintendo 532e25be12 Fix several minor warnings 2025-01-06 11:31:39 +01:00
JosJuice b35f7af355
Merge pull request #13257 from CasualPokePlayer/dtm_country_code
Add SYSCONF country code to DTM
2025-01-06 10:39:53 +01:00
JosJuice eec2e2f07a
Merge pull request #13251 from Sintendo/carry-opts
JitArm64_Integer: Carry flag optimizations
2025-01-06 10:39:43 +01:00
JosJuice 43d5f61a60
Merge pull request #13149 from Sintendo/dcbx-msub
JitArm64_LoadStore: Small dcbx optimization
2025-01-06 09:29:08 +01:00
OatmealDome 8d9ec2fde9
Merge pull request #13210 from OatmealDome/fix-scmrevgen
ScmRevGen: Don't generate Info.plist files directly
2025-01-06 01:18:20 -05:00
Ferdinand Bachmann b79bdb13c0 Common: Fix compile failure with fmt>=11 2025-01-04 19:13:05 +01:00
Ferdinand Bachmann 825092ad33 BBA/HLE: Fix incorrect fmt format string 2025-01-04 18:45:32 +01:00
Sepalani bbf835b30b PPCSymbolDB: Check SplitString result 2025-01-04 17:02:13 +04:00
Sepalani 77e77863dc PPCSymbolDB: Add alignment detection heuristic
Update parse_entry_of in accordance to the sscanf change
2025-01-04 15:32:52 +04:00
Sepalani 5778cb42db PPCSymbolDB: Deduplicate parsing of the 'entry of' string 2025-01-04 15:32:52 +04:00
Sepalani 5c151c11ac PPCSymbolDB: Use ranges in SaveSymbolMap 2025-01-04 15:32:52 +04:00
Jordan Woyak f12846a0e9 DolphinQt: Make input mapping and output testing non-blocking. 2025-01-01 16:48:32 -06:00
Jordan Woyak bc95c001c8 InputCommon: Move input mapping function into a class for non-blocking usage. 2025-01-01 16:48:32 -06:00
Jordan Woyak 0938fca6e3 Core/VideoCommon: Fix some weird (!eof) logic. 2024-12-30 20:13:59 -06:00
CasualPokePlayer c1698c93e2 Add SYSCONF country code to DTM
Recently there was some issues in TASVideos trying to sync a Donkey Kong Country Returns TAS. It eventually was synced by directly using the config from the TAS author. The exact setting which caused the desync was narrowed down to being in SYSCONF, with the country code. The TAS author lives in the US, so the country code matched the US country code, while the person attempting to sync the TAS did not live in the US.

Adding SYSCONF country code to the DTM should avoid this being an issue for future Dolphin versions.
2024-12-30 07:50:22 -08:00
Sintendo d81bfe94eb JitArm64_Integer: addzex - Optimize InHostCarry case for 0
Before:
0x5280000d   mov    w13, #0x0                 ; =0
0x1a1f01ae   adc    w14, w13, wzr

After:
0x1a9f37ee   cset   w14, hs
2024-12-29 12:21:34 +01:00
Sintendo c817b4779d JitArm64_Integer: addzex - Optimize InPPCState case for 0
Before:
0x52800019   mov    w25, #0x0                 ; =0
0x394bd3b8   ldrb   w24, [x29, #0x2f4]
0x2b180339   adds   w25, w25, w24

After:
0x394bd3b9   ldrb   w25, [x29, #0x2f4]
2024-12-28 23:20:22 +01:00
Sintendo 14641b06fc JitArm64_Integer: addzex - Optimize ConstantFalse and ConstantTrue
When the input register and carry flags are known, we can always
precompute the result.

We still materialize the immediate when the condition register
needs to be updated, but this seems to be a general problem. I might
look into that one day, but for now this'll do.

- ConstantFalse
Before:
0x52800119   mov    w25, #0x8                 ; =8
0x2a1903fa   mov    w26, w25

After:
N/A

- ConstantTrue
Before:
0x52800119   mov    w25, #0x8                 ; =8
0x1100073a   add    w26, w25, #0x1

After:
N/A
2024-12-28 23:07:38 +01:00
Sintendo a4ba13b4c9 JitArm64_Integer: addex - Optimize InHostCarry for -1
Same thing we did for subfex.

Before:
0x1280001a   mov    w26, #-0x1                ; =-1
0x1a1f035a   adc    w26, w26, wzr

After:
0x5a9f23fa   csetm  w26, lo
2024-12-28 22:12:50 +01:00
Sintendo d2bfa157dc JitArm64_Integer: addex - Optimize InHostCarry for 0
Similar to what we did for subfex, but for 0.

Before:
0x5280001b   mov    w27, #0x0                 ; =0
0x1a1f037b   adc    w27, w27, wzr

After:
0x1a9f37fb   cset   w27, hs
2024-12-28 21:55:57 +01:00
Sintendo ad7dba5413 JitArm64_Integer: addex - Optimize InPPCState case for 0
Same optimization we did for subfex. Skip loading the carry flag into a
temporary register first when we're dealing with zero.

Before:
0x394bd3b8   ldrb   w24, [x29, #0x2f4]
0x2a1803f9   mov    w25, w24

After:
0x394bd3b9   ldrb   w25, [x29, #0x2f4]
2024-12-28 21:41:51 +01:00
Sintendo 7410bc2025 JitArm64_Integer: subfzex - Constant folding
When both the input register and the carry flag are constants, the
result can be precomputed.

Before:
0x52800016   mov    w22, #0x0                 ; =0
0x2a3603f6   mvn    w22, w22

After:
2024-12-28 20:58:24 +01:00
Sintendo fa13457abb JitArm64_Integer: subfex - Optimize InHostCarry case for -1
The result is either -1 or 0 depending on the state of the carry flag.
This can be done with a csetm instruction.

Before:
0x1280001a   mov    w26, #-0x1                ; =-1
0x1a1f035a   adc    w26, w26, wzr

After:
0x5a9f23fa   csetm  w26, lo
2024-12-28 20:18:14 +01:00
Sintendo 18dd3f69f1 JitArm64_Integer: subfex - Optimize InPPCState case for 0
When the immediate is zero, we can load the carry flag from memory
directly to the destination register.

Before:
0x394bd3b8   ldrb   w24, [x29, #0x2f4]
0x2a1803f9   mov    w25, w24

After:
0x394bd3b9   ldrb   w25, [x29, #0x2f4]
2024-12-28 18:15:48 +01:00
Sintendo e54bfd6605 JitArm64_Integer: Refactor subfex 2024-12-28 18:12:13 +01:00
Sintendo 5cc9bde1c1 JitBase: Improve const-correctness 2024-12-28 16:44:58 +01:00
Sintendo 50d991780f JitBase: Add HasConstantCarry helper 2024-12-28 16:44:52 +01:00
dreamsyntax 07f712f8a0 Core/VideoCommon: Fix duplicate OSD Custom Textures messages
Resolves duplicate OSD messages for Loading and Found custom textures.
VideoBackend initialization results in HiresTexture::Init being called.
We already call HiresTexture::Update when OnNewTitleLoad is called.
Thus we can remove HiresTextures::Init completely as it is redundant.
2024-12-28 01:09:50 -07:00
JosJuice 6384ea97f1 JitArm64: Handle rlwinmx with zero mask
No games seem to use this, so this isn't useful as a performance
optimization, but it's required for correctness because the (sh == 0)
case of our implementation doesn't handle zero masks.
2024-12-25 15:15:24 +01:00
cristian64 788f140936 DolphinQt: Extract creator name from code name in Gecko codes.
Gecko codes in Dolphin feature a dedicated field for the creator of the
cheat code. When saved into the INI file, the code name and the creator
name are concatenated, and then inserted in the `[Gecko]` section:

```ini
[Gecko]
$<cheat code name> [<creator>]
<code line 1>
<code line 2>
<code line 3>
<...>
$<other cheat code name> [<creator>]
<code line 1>
<code line 2>
<code line 3>
<...>
```

On the other hand, enabled codes are listed under the `[Gecko_Enabled]`
section, but in this case the creator name is omitted from the line:

```ini
[Gecko_Enabled]
$<cheat code name>
$<other cheat code name>
```

Having the creator name in the `[Gecko]` section but not in the
`[Gecko_Enabled]` section is arguably not ideal, but this is legacy
behavior in Dolphin.

The **Cheat Code Editor** dialog is not acknowledging this subtle
behavior in Dolphin: the cheat code name and the creator name *can* be
both inserted in the name field. This issue manifests as an inconsistent
state where a Gecko code that *appears* to be enabled has no effect when
the game is launched.

As part of this fix, the creator name (if present) is now moved into the
dedicated creator field before the code is stored internally.

Test plan:

- Right-click on any game and open the **Properties** dialog.
- Switch to the **Gecko Codes** tab.
- Press the **Add New Code...** button.
- In the **Cheat Code Editor** dialog:
  - Enter `This is a test [Jane Doe]` in the **Name:** field.
  - Enter `01234567 00000000` in the **Code:** field.
  - Press **Save**.
- Observe that the newly added code is now in the list, and *appears* to
  be enabled.
- Close the **Properties** dialog.
- Right-click on the same game and open the **Properties** dialog again.

**Without** the fix, the newly added code, while still on the list, has
been inadvertently disabled (it was never really enabled!).

**With** the fix, the newly added code is the list and remains enabled.

This fixes https://bugs.dolphin-emu.org/issues/13695.
2024-12-10 18:53:05 +00:00
OatmealDome e05b033dd2 ScmRevGen: Don't generate Info.plist files directly
Some generators (like Unix Makefiles and Xcode) copy an app's Info.plist at configure time.
This causes a problem when we need to generate the Info.plist at build time, like how we
currently do it with ScmRevGen. Instead of generating the Info.plist directly in ScmRevGen,
provide an Info.plist without any version information to CMake at configure time, have
ScmRevGen generate a separate plist file with the version information at build time, and
then merge the two together to create the final Info.plist.
2024-12-05 14:56:08 -05:00
OatmealDome 3c27c38e71 DolphinQt: Use TARGET_BUNDLE_DIR generator expression instead of specifying the bundle path ourselves 2024-12-04 22:53:39 -05:00
TellowKrinkle 613c959bda CMake: Pass RESOLVED_USE_SYSTEM_<lib> as a parameter
Because we were setting it with a scope, it wasn't making its way into called functions that would try to inspect it.  Now it does.
2024-11-06 21:47:10 -06:00
TellowKrinkle 432d4a2a06 CMake: Properly abort when requested system lib is missing 2024-11-06 21:30:29 -06:00
Jordan Woyak bffc26da27 DolphinQt: Rename "GameCube Adapter for Wii U" to "GameCube Controller Adapter". 2024-10-31 01:25:36 -05:00
Sintendo e6fd843c0b JitArm64_LoadStore: Small dcbx optimization
MUL and SUB can be combined in one instruction.

Before:
0x1b1a7c01   mul    w1, w0, w26
0x4b010318   sub    w24, w24, w1

After:
0x1b1ae018   msub   w24, w0, w26, w24
2024-10-24 23:05:42 +02:00
JosJuice dcf8ab0189 Android: Wait for surface in Run
When we boot the core, it needs to have a valid surface to draw graphics
to. Our Kotlin code does wait for a valid surface to exist before it
calls NativeLibrary.Run, but there's a chance for the surface to be
deleted before Run locks s_surface_lock. If that happens, the core boots
without a valid surface, which presumably would cause a crash. (I
haven't been able to reproduce the problem myself.)
2024-08-21 20:36:46 +02:00
JosJuice 2fdeb85f0e Android: Replace deprecated setSystemUiVisibility
No functional change.
2024-06-15 15:02:50 +02:00
63 changed files with 1251 additions and 1939 deletions

View File

@ -0,0 +1,24 @@
function(dolphin_inject_version_info target)
set(INFO_PLIST_PATH "$<TARGET_BUNDLE_DIR:${target}>/Contents/Info.plist")
add_custom_command(TARGET ${target}
POST_BUILD
COMMAND /usr/libexec/PlistBuddy -c
"Delete :CFBundleShortVersionString"
"${INFO_PLIST_PATH}"
|| true
COMMAND /usr/libexec/PlistBuddy -c
"Delete :CFBundleLongVersionString"
"${INFO_PLIST_PATH}"
|| true
COMMAND /usr/libexec/PlistBuddy -c
"Delete :CFBundleVersion"
"${INFO_PLIST_PATH}"
|| true
COMMAND /usr/libexec/PlistBuddy -c
"Merge '${CMAKE_BINARY_DIR}/Source/Core/VersionInfo.plist'"
"${INFO_PLIST_PATH}")
endfunction()

View File

@ -19,28 +19,27 @@ function(dolphin_make_imported_target_if_missing target lib)
endif()
endfunction()
function(dolphin_optional_system_library library)
function(dolphin_optional_system_library out_use_system library)
string(TOUPPER ${library} upperlib)
set(USE_SYSTEM_${upperlib} "" CACHE STRING "Use system ${library} instead of bundled. ON - Always use system and fail if unavailable, OFF - Always use bundled, AUTO - Use system if available, otherwise use bundled, blank - Delegate to USE_SYSTEM_LIBS. Default is blank.")
if("${USE_SYSTEM_${upperlib}}" STREQUAL "")
if(APPROVED_VENDORED_DEPENDENCIES)
string(TOLOWER ${library} lowerlib)
if(lowerlib IN_LIST APPROVED_VENDORED_DEPENDENCIES)
set(RESOLVED_USE_SYSTEM_${upperlib} AUTO PARENT_SCOPE)
set(${out_use_system} AUTO PARENT_SCOPE)
else()
set(RESOLVED_USE_SYSTEM_${upperlib} ON PARENT_SCOPE)
set(${out_use_system} ON PARENT_SCOPE)
endif()
else()
set(RESOLVED_USE_SYSTEM_${upperlib} ${USE_SYSTEM_LIBS} PARENT_SCOPE)
set(${out_use_system} ${USE_SYSTEM_LIBS} PARENT_SCOPE)
endif()
else()
set(RESOLVED_USE_SYSTEM_${upperlib} ${USE_SYSTEM_${upperlib}} PARENT_SCOPE)
set(${out_use_system} ${USE_SYSTEM_${upperlib}} PARENT_SCOPE)
endif()
endfunction()
function(dolphin_add_bundled_library library bundled_path)
string(TOUPPER ${library} upperlib)
if (${RESOLVED_USE_SYSTEM_${upperlib}} STREQUAL "AUTO")
function(dolphin_add_bundled_library library use_system bundled_path)
if (${use_system} STREQUAL "AUTO")
message(STATUS "No system ${library} was found. Using static ${library} from Externals.")
else()
message(STATUS "Using static ${library} from Externals")
@ -52,9 +51,9 @@ function(dolphin_add_bundled_library library bundled_path)
endfunction()
function(dolphin_find_optional_system_library library bundled_path)
dolphin_optional_system_library(${library})
dolphin_optional_system_library(use_system ${library})
string(TOUPPER ${library} upperlib)
if(RESOLVED_USE_SYSTEM_${upperlib})
if(use_system)
find_package(${library} ${ARGN})
# Yay for cmake packages being inconsistent
if(DEFINED ${library}_FOUND)
@ -62,7 +61,7 @@ function(dolphin_find_optional_system_library library bundled_path)
else()
set(prefix ${upperlib})
endif()
if((NOT ${found}) AND (NOT ${RESOLVED_USE_SYSTEM_${upperlib}} STREQUAL "AUTO"))
if((NOT ${prefix}_FOUND) AND (NOT ${use_system} STREQUAL "AUTO"))
message(FATAL_ERROR "No system ${library} was found. Please install it or set USE_SYSTEM_${upperlib} to AUTO or OFF.")
endif()
endif()
@ -70,17 +69,17 @@ function(dolphin_find_optional_system_library library bundled_path)
message(STATUS "Using system ${library}")
set(${prefix}_TYPE "System" PARENT_SCOPE)
else()
dolphin_add_bundled_library(${library} ${bundled_path})
dolphin_add_bundled_library(${library} ${use_system} ${bundled_path})
set(${prefix}_TYPE "Bundled" PARENT_SCOPE)
endif()
endfunction()
function(dolphin_find_optional_system_library_pkgconfig library search alias bundled_path)
dolphin_optional_system_library(${library})
dolphin_optional_system_library(use_system ${library})
string(TOUPPER ${library} upperlib)
if(RESOLVED_USE_SYSTEM_${upperlib})
if(use_system)
pkg_search_module(${library} ${search} ${ARGN} IMPORTED_TARGET)
if((NOT ${library}_FOUND) AND (NOT ${RESOLVED_USE_SYSTEM_${upperlib}} STREQUAL "AUTO"))
if((NOT ${library}_FOUND) AND (NOT ${use_system} STREQUAL "AUTO"))
message(FATAL_ERROR "No system ${library} was found. Please install it or set USE_SYSTEM_${upperlib} to AUTO or OFF.")
endif()
endif()
@ -89,7 +88,7 @@ function(dolphin_find_optional_system_library_pkgconfig library search alias bun
dolphin_alias_library(${alias} PkgConfig::${library})
set(${library}_TYPE "System" PARENT_SCOPE)
else()
dolphin_add_bundled_library(${library} ${bundled_path})
dolphin_add_bundled_library(${library} ${use_system} ${bundled_path})
set(${library}_TYPE "Bundled" PARENT_SCOPE)
endif()
endfunction()

View File

@ -65,6 +65,5 @@ endfunction()
configure_source_file("Source/Core/Common/scmrev.h")
if(APPLE)
configure_source_file("Source/Core/DolphinQt/Info.plist")
configure_source_file("Source/Core/MacUpdater/Info.plist")
configure_source_file("Source/Core/VersionInfo.plist")
endif()

View File

@ -783,14 +783,9 @@ if (NOT EXISTS ${CMAKE_CURRENT_BINARY_DIR}/Source/Core/Common/scmrev.h)
endif()
if(APPLE)
file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/Source/Core/DolphinQt)
if (NOT EXISTS ${CMAKE_CURRENT_BINARY_DIR}/Source/Core/DolphinQt/Info.plist)
file(TOUCH ${CMAKE_CURRENT_BINARY_DIR}/Source/Core/DolphinQt/Info.plist)
endif()
file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/Source/Core/MacUpdater)
if (NOT EXISTS ${CMAKE_CURRENT_BINARY_DIR}/Source/Core/MacUpdater/Info.plist)
file(TOUCH ${CMAKE_CURRENT_BINARY_DIR}/Source/Core/MacUpdater/Info.plist)
file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/Source/Core)
if (NOT EXISTS ${CMAKE_CURRENT_BINARY_DIR}/Source/Core/VersionInfo.plist)
file(TOUCH ${CMAKE_CURRENT_BINARY_DIR}/Source/Core/VersionInfo.plist)
endif()
endif()

View File

@ -1,62 +1,53 @@
include_directories(${CMAKE_CURRENT_SOURCE_DIR})
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/curl/include)
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/curl/lib)
set(BUILD_CURL_EXE OFF)
set(BUILD_EXAMPLES OFF)
set(BUILD_LIBCURL_DOCS OFF)
set(BUILD_MISC_DOCS OFF)
set(BUILD_SHARED_LIBS OFF)
set(BUILD_STATIC_LIBS ON)
set(BUILD_TESTING OFF)
set(CURL_ENABLE_EXPORT_TARGET OFF)
set(CURL_LIBS MbedTLS::mbedtls zlibstatic)
if(CMAKE_SYSTEM_NAME STREQUAL "Darwin")
set(use_core_foundation ON)
set(HTTP_ONLY ON)
find_library(SYSTEMCONFIGURATION_FRAMEWORK "SystemConfiguration")
if(NOT SYSTEMCONFIGURATION_FRAMEWORK)
message(FATAL_ERROR "SystemConfiguration framework not found")
endif()
set(CURL_USE_LIBPSL OFF)
set(CURL_USE_LIBSSH2 OFF)
set(CURL_ZLIB OFF CACHE BOOL "" FORCE)
set(CURL_ZSTD OFF)
set(USE_LIBIDN2 OFF)
set(USE_NGHTTP2 OFF)
list(APPEND CURL_LIBS "-framework SystemConfiguration")
if(UNIX)
# We use mbedtls on Unix(-like) systems and Android.
set(CURL_USE_OPENSSL OFF)
# This is set if the dolphin_find_optional_system_library call from the main CMakeLists.txt
# is able to find mbedtls on the system.
if(MBEDTLS_FOUND)
# We can just enable CURL_USE_MBEDTLS.
set(CURL_USE_MBEDTLS ON)
else()
# HACK: Set some internal variables to pretend like mbedtls was found on the system.
# We can't use CURL_USE_MBEDTLS with our copy from Externals, as that flag expects
# mbedtls to be installed (the CMakeLists attempts to search for it with find_package).
set(_ssl_enabled ON)
set(USE_MBEDTLS ON)
set(_curl_ca_bundle_supported TRUE)
endif()
endif()
file(GLOB SRCS curl/lib/*.c curl/lib/vauth/*.c curl/lib/vquic/*.c curl/lib/vssh/*.c curl/lib/vtls/*.c)
add_library(
curl
STATIC
curl/include/curl/curl.h
${SRCS}
)
set(SEARCH_CA_BUNDLE_PATHS
/etc/ssl/certs/ca-certificates.crt
/etc/pki/tls/certs/ca-bundle.crt
/usr/share/ssl/certs/ca-bundle.crt
/usr/local/share/certs/ca-root-nss.crt
/etc/ssl/cert.pem)
foreach(SEARCH_CA_BUNDLE_PATH ${SEARCH_CA_BUNDLE_PATHS})
if(EXISTS "${SEARCH_CA_BUNDLE_PATH}")
message(STATUS "Found CA bundle: ${SEARCH_CA_BUNDLE_PATH}")
set(CURL_CA_BUNDLE "${SEARCH_CA_BUNDLE_PATH}")
set(CURL_CA_BUNDLE_SET TRUE)
break()
endif()
endforeach()
if(NOT CURL_CA_PATH_SET)
if(EXISTS "/etc/ssl/certs")
set(CURL_CA_PATH "/etc/ssl/certs")
set(CURL_CA_PATH_SET TRUE)
endif()
if(WIN32)
set(CURL_USE_SCHANNEL ON)
endif()
dolphin_disable_warnings(curl)
target_link_libraries(curl ${CURL_LIBS})
target_include_directories(curl PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/curl/include")
target_compile_definitions(curl PRIVATE "BUILDING_LIBCURL=1")
if (WIN32)
target_compile_definitions(curl PUBLIC "CURL_STATICLIB=1" "CURL_DISABLE_LDAP" "USE_WINDOWS_SSPI" "USE_SCHANNEL")
target_link_libraries(curl Crypt32.lib)
else()
target_compile_definitions(curl PUBLIC "CURL_STATICLIB=1" "USE_MBEDTLS=1" "HAVE_CONFIG_H=1" "CURL_DISABLE_LDAP")
if (CURL_CA_PATH_SET)
target_compile_definitions(curl PUBLIC CURL_CA_PATH="${CURL_CA_PATH}")
endif()
if(ANDROID)
set(CURL_CA_PATH "/system/etc/security/cacerts" CACHE STRING "")
endif()
add_library(CURL::libcurl ALIAS curl)
add_subdirectory(curl)
if(UNIX AND NOT MBEDTLS_FOUND)
# HACK: Manually link with the mbedtls libraries.
target_link_libraries(libcurl_static PRIVATE
MbedTLS::mbedtls
MbedTLS::mbedx509)
endif()

View File

@ -1,22 +0,0 @@
COPYRIGHT AND PERMISSION NOTICE
Copyright (c) 1996 - 2016, Daniel Stenberg, <daniel@haxx.se>, and many
contributors, see the THANKS file.
All rights reserved.
Permission to use, copy, modify, and distribute this software for any purpose
with or without fee is hereby granted, provided that the above copyright
notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS. IN
NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
OR OTHER DEALINGS IN THE SOFTWARE.
Except as contained in this notice, the name of a copyright holder shall not
be used in advertising or otherwise to promote the sale, use or other dealings
in this Software without prior written authorization of the copyright holder.

2
Externals/curl/curl vendored

@ -1 +1 @@
Subproject commit d755a5f7c009dd63a61b2c745180d8ba937cbfeb
Subproject commit 75a2079d5c28debb2eaa848ca9430f1fe0d7844c

View File

@ -50,14 +50,14 @@
<ClCompile Include="curl\lib\curl_memrchr.c" />
<ClCompile Include="curl\lib\curl_multibyte.c" />
<ClCompile Include="curl\lib\curl_ntlm_core.c" />
<ClCompile Include="curl\lib\curl_ntlm_wb.c" />
<ClCompile Include="curl\lib\curl_path.c" />
<ClCompile Include="curl\lib\curl_range.c" />
<ClCompile Include="curl\lib\curl_rtmp.c" />
<ClCompile Include="curl\lib\curl_sasl.c" />
<ClCompile Include="curl\lib\curl_sha512_256.c" />
<ClCompile Include="curl\lib\curl_sspi.c" />
<ClCompile Include="curl\lib\curl_threads.c" />
<ClCompile Include="curl\lib\curl_trc.c" />
<ClCompile Include="curl\lib\cw-out.c" />
<ClCompile Include="curl\lib\dict.c" />
<ClCompile Include="curl\lib\doh.c" />
<ClCompile Include="curl\lib\dynbuf.c" />
@ -120,6 +120,7 @@
<ClCompile Include="curl\lib\psl.c" />
<ClCompile Include="curl\lib\rand.c" />
<ClCompile Include="curl\lib\rename.c" />
<ClCompile Include="curl\lib\request.c" />
<ClCompile Include="curl\lib\rtsp.c" />
<ClCompile Include="curl\lib\select.c" />
<ClCompile Include="curl\lib\sendf.c" />
@ -167,12 +168,16 @@
<ClCompile Include="curl\lib\vauth\vauth.c" />
<ClCompile Include="curl\lib\vquic\curl_msh3.c" />
<ClCompile Include="curl\lib\vquic\curl_ngtcp2.c" />
<ClCompile Include="curl\lib\vquic\curl_osslq.c" />
<ClCompile Include="curl\lib\vquic\curl_quiche.c" />
<ClCompile Include="curl\lib\vquic\vquic.c" />
<ClCompile Include="curl\lib\vquic\vquic-tls.c" />
<ClCompile Include="curl\lib\vssh\curl_path.c" />
<ClCompile Include="curl\lib\vssh\libssh.c" />
<ClCompile Include="curl\lib\vssh\libssh2.c" />
<ClCompile Include="curl\lib\vssh\wolfssh.c" />
<ClCompile Include="curl\lib\vtls\bearssl.c" />
<ClCompile Include="curl\lib\vtls\cipher_suite.c" />
<ClCompile Include="curl\lib\vtls\gtls.c" />
<ClCompile Include="curl\lib\vtls\hostcheck.c" />
<ClCompile Include="curl\lib\vtls\keylog.c" />
@ -244,8 +249,6 @@
<ClInclude Include="curl\lib\curl_memrchr.h" />
<ClInclude Include="curl\lib\curl_multibyte.h" />
<ClInclude Include="curl\lib\curl_ntlm_core.h" />
<ClInclude Include="curl\lib\curl_ntlm_wb.h" />
<ClInclude Include="curl\lib\curl_path.h" />
<ClInclude Include="curl\lib\curl_printf.h" />
<ClInclude Include="curl\lib\curl_range.h" />
<ClInclude Include="curl\lib\curl_rtmp.h" />
@ -253,9 +256,11 @@
<ClInclude Include="curl\lib\curl_setup.h" />
<ClInclude Include="curl\lib\curl_setup_once.h" />
<ClInclude Include="curl\lib\curl_sha256.h" />
<ClInclude Include="curl\lib\curl_sha512_256.h" />
<ClInclude Include="curl\lib\curl_sspi.h" />
<ClInclude Include="curl\lib\curl_threads.h" />
<ClInclude Include="curl\lib\curl_trc.h" />
<ClInclude Include="curl\lib\cw_out.h" />
<ClInclude Include="curl\lib\dict.h" />
<ClInclude Include="curl\lib\doh.h" />
<ClInclude Include="curl\lib\dynbuf.h" />
@ -308,6 +313,7 @@
<ClInclude Include="curl\lib\psl.h" />
<ClInclude Include="curl\lib\rand.h" />
<ClInclude Include="curl\lib\rename.h" />
<ClInclude Include="curl\lib\request.h" />
<ClInclude Include="curl\lib\rtsp.h" />
<ClInclude Include="curl\lib\select.h" />
<ClInclude Include="curl\lib\sendf.h" />
@ -347,11 +353,15 @@
<ClInclude Include="curl\lib\vauth\vauth.h" />
<ClInclude Include="curl\lib\vquic\curl_msh3.h" />
<ClInclude Include="curl\lib\vquic\curl_ngtcp2.h" />
<ClInclude Include="curl\lib\vquic\curl_osslq.h" />
<ClInclude Include="curl\lib\vquic\curl_quiche.h" />
<ClInclude Include="curl\lib\vquic\vquic.h" />
<ClInclude Include="curl\lib\vquic\vquic-tls.h" />
<ClInclude Include="curl\lib\vquic\vquic_int.h" />
<ClInclude Include="curl\lib\vssh\curl_path.h" />
<ClInclude Include="curl\lib\vssh\ssh.h" />
<ClInclude Include="curl\lib\vtls\bearssl.h" />
<ClInclude Include="curl\lib\vtls\cipher_suites.h" />
<ClInclude Include="curl\lib\vtls\gtls.h" />
<ClInclude Include="curl\lib\vtls\hostcheck.h" />
<ClInclude Include="curl\lib\vtls\keylog.h" />

File diff suppressed because it is too large Load Diff

View File

@ -19,7 +19,9 @@ import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.widget.PopupMenu
import androidx.core.view.ViewCompat
import androidx.core.view.WindowCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.WindowInsetsControllerCompat
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import androidx.fragment.app.FragmentManager
@ -326,12 +328,11 @@ class EmulationActivity : AppCompatActivity(), ThemeProvider {
}
private fun enableFullscreenImmersive() {
window.decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_LAYOUT_STABLE or
View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or
View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or
View.SYSTEM_UI_FLAG_HIDE_NAVIGATION or
View.SYSTEM_UI_FLAG_FULLSCREEN or
View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
WindowCompat.setDecorFitsSystemWindows(window, false)
WindowInsetsControllerCompat(window, window.decorView).let { controller ->
controller.hide(WindowInsetsCompat.Type.systemBars())
controller.systemBarsBehavior = WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
}
}
private fun updateDisplaySettings() {

View File

@ -191,7 +191,7 @@ Java_org_dolphinemu_dolphinemu_features_cheats_model_GeckoCheat_downloadCodes(JN
const std::string gametdb_id = GetJString(env, jGameTdbId);
bool success = true;
const std::vector<Gecko::GeckoCode> codes = Gecko::DownloadCodes(gametdb_id, &success, false);
const std::vector<Gecko::GeckoCode> codes = Gecko::DownloadCodes(gametdb_id, &success);
if (!success)
return nullptr;

View File

@ -1,13 +1,9 @@
// Copyright 2003 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <EGL/egl.h>
#include <android/log.h>
#include <android/native_window_jni.h>
#include <condition_variable>
#include <cstdio>
#include <cstdlib>
#include <fmt/format.h>
#include <jni.h>
#include <memory>
#include <mutex>
#include <optional>
@ -15,6 +11,12 @@
#include <thread>
#include <utility>
#include <EGL/egl.h>
#include <android/log.h>
#include <android/native_window_jni.h>
#include <fmt/format.h>
#include <jni.h>
#include "Common/AndroidAnalytics.h"
#include "Common/Assert.h"
#include "Common/CPUDetect.h"
@ -77,6 +79,8 @@ Common::Event s_update_main_frame_event;
// This exists to prevent surfaces from being destroyed during the boot process,
// as that can lead to the boot process dereferencing nullptr.
std::mutex s_surface_lock;
std::condition_variable s_surface_cv;
bool s_need_nonblocking_alert_msg;
Common::Flag s_is_booting;
@ -482,6 +486,8 @@ JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_SurfaceChang
if (g_presenter)
g_presenter->ChangeSurface(s_surf);
s_surface_cv.notify_all();
}
JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_SurfaceDestroyed(JNIEnv*,
@ -515,6 +521,8 @@ JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_SurfaceDestr
ANativeWindow_release(s_surf);
s_surf = nullptr;
}
s_surface_cv.notify_all();
}
JNIEXPORT jboolean JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_HasSurface(JNIEnv*, jclass)
@ -606,12 +614,14 @@ static void Run(JNIEnv* env, std::unique_ptr<BootParameters>&& boot, bool riivol
volume.GetDiscNumber()));
}
WindowSystemInfo wsi(WindowSystemType::Android, nullptr, s_surf, s_surf);
wsi.render_surface_scale = GetRenderSurfaceScale(env);
s_need_nonblocking_alert_msg = true;
std::unique_lock<std::mutex> surface_guard(s_surface_lock);
s_surface_cv.wait(surface_guard, []() { return s_surf != nullptr; });
WindowSystemInfo wsi(WindowSystemType::Android, nullptr, s_surf, s_surf);
wsi.render_surface_scale = GetRenderSurfaceScale(env);
if (BootManager::BootCore(Core::System::GetInstance(), std::move(boot), wsi))
{
static constexpr int WAIT_STEP = 25;

View File

@ -85,7 +85,8 @@ bool IniFile::Section::Delete(std::string_view key)
return false;
values.erase(it);
keys_order.erase(std::ranges::find(keys_order, key));
keys_order.erase(std::ranges::find_if(
keys_order, [&](std::string_view v) { return CaseInsensitiveEquals(key, v); }));
return true;
}

View File

@ -99,7 +99,13 @@ void GenericLogFmt(LogLevel level, LogType type, const char* file, int line, con
static_assert(NumFields == sizeof...(args),
"Unexpected number of replacement fields in format string; did you pass too few or "
"too many arguments?");
GenericLogFmtImpl(level, type, file, line, format, fmt::make_format_args(args...));
#if FMT_VERSION >= 110000
auto&& format_str = fmt::format_string<Args...>(format);
#else
auto&& format_str = format;
#endif
GenericLogFmtImpl(level, type, file, line, format_str, fmt::make_format_args(args...));
}
} // namespace Common::Log

View File

@ -41,12 +41,17 @@ bool MsgAlertFmt(bool yes_no, MsgType style, Common::Log::LogType log_type, cons
static_assert(NumFields == sizeof...(args),
"Unexpected number of replacement fields in format string; did you pass too few or "
"too many arguments?");
#if FMT_VERSION >= 90000
#if FMT_VERSION >= 110000
static_assert(std::is_base_of_v<fmt::detail::compile_string, S>);
auto&& format_str = fmt::format_string<Args...>(format);
#elif FMT_VERSION >= 90000
static_assert(fmt::detail::is_compile_string<S>::value);
auto&& format_str = format;
#else
static_assert(fmt::is_compile_string<S>::value);
auto&& format_str = format;
#endif
return MsgAlertFmtImpl(yes_no, style, log_type, file, line, format,
return MsgAlertFmtImpl(yes_no, style, log_type, file, line, format_str,
fmt::make_format_args(args...));
}
@ -60,7 +65,9 @@ bool MsgAlertFmtT(bool yes_no, MsgType style, Common::Log::LogType log_type, con
static_assert(NumFields == sizeof...(args),
"Unexpected number of replacement fields in format string; did you pass too few or "
"too many arguments?");
#if FMT_VERSION >= 90000
#if FMT_VERSION >= 110000
static_assert(std::is_base_of_v<fmt::detail::compile_string, S>);
#elif FMT_VERSION >= 90000
static_assert(fmt::detail::is_compile_string<S>::value);
#else
static_assert(fmt::is_compile_string<S>::value);

View File

@ -41,6 +41,7 @@ static void LoadFromDTM(Config::Layer* config_layer, Movie::DTMHeader* dtm)
else
config_layer->Set(Config::MAIN_GC_LANGUAGE, static_cast<int>(dtm->language));
config_layer->Set(Config::SYSCONF_WIDESCREEN, dtm->bWidescreen);
config_layer->Set(Config::SYSCONF_COUNTRY, dtm->countryCode);
config_layer->Set(Config::GFX_HACK_EFB_ACCESS_ENABLE, dtm->bEFBAccessEnable);
config_layer->Set(Config::GFX_HACK_SKIP_EFB_COPY_TO_RAM, dtm->bSkipEFBCopyToRam);
@ -69,6 +70,7 @@ void SaveToDTM(Movie::DTMHeader* dtm)
else
dtm->language = Config::Get(Config::MAIN_GC_LANGUAGE);
dtm->bWidescreen = Config::Get(Config::SYSCONF_WIDESCREEN);
dtm->countryCode = Config::Get(Config::SYSCONF_COUNTRY);
dtm->bEFBAccessEnable = Config::Get(Config::GFX_HACK_EFB_ACCESS_ENABLE);
dtm->bSkipEFBCopyToRam = Config::Get(Config::GFX_HACK_SKIP_EFB_COPY_TO_RAM);

View File

@ -776,14 +776,11 @@ bool DSPAssembler::AssemblePass(const std::string& text, int pass)
m_location.line_num = 0;
m_cur_pass = pass;
#define LINEBUF_SIZE 1024
char line[LINEBUF_SIZE] = {0};
while (!m_failed && !fsrc.fail() && !fsrc.eof())
constexpr int LINEBUF_SIZE = 1024;
char line[LINEBUF_SIZE] = {};
while (!m_failed && fsrc.getline(line, LINEBUF_SIZE))
{
int opcode_size = 0;
fsrc.getline(line, LINEBUF_SIZE);
if (fsrc.fail())
break;
m_location.line_text = line;
m_location.line_num++;

View File

@ -17,13 +17,10 @@
namespace Gecko
{
std::vector<GeckoCode> DownloadCodes(std::string gametdb_id, bool* succeeded, bool use_https)
std::vector<GeckoCode> DownloadCodes(std::string gametdb_id, bool* succeeded)
{
// TODO: Fix https://bugs.dolphin-emu.org/issues/11772 so we don't need this workaround
const std::string protocol = use_https ? "https://" : "http://";
// codes.rc24.xyz is a mirror of the now defunct geckocodes.org.
std::string endpoint{protocol + "codes.rc24.xyz/txt.php?txt=" + gametdb_id};
std::string endpoint{"https://codes.rc24.xyz/txt.php?txt=" + gametdb_id};
Common::HttpRequest http;
// The server always redirects once to the same location.

View File

@ -17,8 +17,7 @@ class IniFile;
namespace Gecko
{
std::vector<GeckoCode> LoadCodes(const Common::IniFile& globalIni, const Common::IniFile& localIni);
std::vector<GeckoCode> DownloadCodes(std::string gametdb_id, bool* succeeded,
bool use_https = true);
std::vector<GeckoCode> DownloadCodes(std::string gametdb_id, bool* succeeded);
void SaveCodes(Common::IniFile& inifile, const std::vector<GeckoCode>& gcodes);
std::optional<GeckoCode::Code> DeserializeLine(const std::string& line);

View File

@ -686,7 +686,7 @@ bool CEXIETHERNET::BuiltInBBAInterface::SendFrame(const u8* frame, u32 size)
}
default:
ERROR_LOG_FMT(SP1, "Unsupported EtherType {#06x}", *ethertype);
ERROR_LOG_FMT(SP1, "Unsupported EtherType {:#06x}", *ethertype);
return false;
}

View File

@ -132,7 +132,8 @@ struct DTMHeader
bool bUseFMA;
u8 GBAControllers; // GBA Controllers plugged in (the bits are ports 1-4)
bool bWidescreen; // true indicates SYSCONF aspect ratio is 16:9, false for 4:3
std::array<u8, 6> reserved; // Padding for any new config options
u8 countryCode; // SYSCONF country code
std::array<u8, 5> reserved; // Padding for any new config options
std::array<char, 40> discChange; // Name of iso file to switch to, for two disc games.
std::array<u8, 20> revision; // Git hash
u32 DSPiromHash;

View File

@ -765,6 +765,14 @@ void JitArm64::rlwinmx_internal(UGeckoInstruction inst, u32 sh)
return;
}
if (mask == 0)
{
gpr.SetImmediate(a, 0);
if (inst.Rc)
ComputeRC0(0);
return;
}
gpr.BindToRegister(a, a == s);
if (sh == 0 && mask == 0xFFFFFFFF)
@ -1128,47 +1136,85 @@ void JitArm64::addzex(UGeckoInstruction inst)
int a = inst.RA, d = inst.RD;
switch (js.carryFlag)
if (gpr.IsImm(a) && (gpr.GetImm(a) == 0 || HasConstantCarry()))
{
case CarryFlag::InPPCState:
{
const bool allocate_reg = d == a;
gpr.BindToRegister(d, allocate_reg);
const u32 imm = gpr.GetImm(a);
const bool is_all_ones = imm == 0xFFFFFFFF;
switch (js.carryFlag)
{
auto WA = allocate_reg ? gpr.GetScopedReg() : Arm64GPRCache::ScopedARM64Reg(gpr.R(d));
LDRB(IndexType::Unsigned, WA, PPC_REG, PPCSTATE_OFF(xer_ca));
CARRY_IF_NEEDED(ADD, ADDS, gpr.R(d), gpr.R(a), WA);
}
ComputeCarry();
break;
}
case CarryFlag::InHostCarry:
{
gpr.BindToRegister(d, d == a);
CARRY_IF_NEEDED(ADC, ADCS, gpr.R(d), gpr.R(a), ARM64Reg::WZR);
ComputeCarry();
break;
}
case CarryFlag::ConstantTrue:
{
gpr.BindToRegister(d, d == a);
CARRY_IF_NEEDED(ADD, ADDS, gpr.R(d), gpr.R(a), 1);
ComputeCarry();
break;
}
case CarryFlag::ConstantFalse:
{
if (d != a)
case CarryFlag::InPPCState:
{
gpr.BindToRegister(d, false);
MOV(gpr.R(d), gpr.R(a));
LDRB(IndexType::Unsigned, gpr.R(d), PPC_REG, PPCSTATE_OFF(xer_ca));
ComputeCarry(false);
break;
}
case CarryFlag::InHostCarry:
{
gpr.BindToRegister(d, false);
CSET(gpr.R(d), CCFlags::CC_CS);
ComputeCarry(false);
break;
}
case CarryFlag::ConstantTrue:
{
gpr.SetImmediate(d, imm + 1);
ComputeCarry(is_all_ones);
break;
}
case CarryFlag::ConstantFalse:
{
gpr.SetImmediate(d, imm);
ComputeCarry(false);
break;
}
}
ComputeCarry(false);
break;
}
else
{
switch (js.carryFlag)
{
case CarryFlag::InPPCState:
{
const bool allocate_reg = d == a;
gpr.BindToRegister(d, allocate_reg);
{
auto WA = allocate_reg ? gpr.GetScopedReg() : Arm64GPRCache::ScopedARM64Reg(gpr.R(d));
LDRB(IndexType::Unsigned, WA, PPC_REG, PPCSTATE_OFF(xer_ca));
CARRY_IF_NEEDED(ADD, ADDS, gpr.R(d), gpr.R(a), WA);
}
ComputeCarry();
break;
}
case CarryFlag::InHostCarry:
{
gpr.BindToRegister(d, d == a);
CARRY_IF_NEEDED(ADC, ADCS, gpr.R(d), gpr.R(a), ARM64Reg::WZR);
ComputeCarry();
break;
}
case CarryFlag::ConstantTrue:
{
gpr.BindToRegister(d, d == a);
CARRY_IF_NEEDED(ADD, ADDS, gpr.R(d), gpr.R(a), 1);
ComputeCarry();
break;
}
case CarryFlag::ConstantFalse:
{
if (d != a)
{
gpr.BindToRegister(d, false);
MOV(gpr.R(d), gpr.R(a));
}
ComputeCarry(false);
break;
}
}
}
if (inst.Rc)
@ -1216,40 +1262,62 @@ void JitArm64::subfex(UGeckoInstruction inst)
if (gpr.IsImm(a) && (mex || gpr.IsImm(b)))
{
u32 i = gpr.GetImm(a), j = mex ? -1 : gpr.GetImm(b);
gpr.BindToRegister(d, false);
const u32 i = gpr.GetImm(a);
const u32 j = mex ? -1 : gpr.GetImm(b);
const u32 imm = ~i + j;
const bool is_zero = imm == 0;
const bool is_all_ones = imm == 0xFFFFFFFF;
switch (js.carryFlag)
{
case CarryFlag::InPPCState:
{
auto WA = gpr.GetScopedReg();
LDRB(IndexType::Unsigned, WA, PPC_REG, PPCSTATE_OFF(xer_ca));
ADDI2R(gpr.R(d), WA, ~i + j, gpr.R(d));
gpr.BindToRegister(d, false);
ARM64Reg RD = gpr.R(d);
if (is_zero)
{
LDRB(IndexType::Unsigned, RD, PPC_REG, PPCSTATE_OFF(xer_ca));
}
else
{
auto WA = gpr.GetScopedReg();
LDRB(IndexType::Unsigned, WA, PPC_REG, PPCSTATE_OFF(xer_ca));
ADDI2R(RD, WA, imm, RD);
}
break;
}
case CarryFlag::InHostCarry:
{
auto WA = gpr.GetScopedReg();
MOVI2R(WA, ~i + j);
ADC(gpr.R(d), WA, ARM64Reg::WZR);
gpr.BindToRegister(d, false);
ARM64Reg RD = gpr.R(d);
if (is_all_ones)
{
// RD = -1 + carry = carry ? 0 : -1
// CSETM sets the destination to -1 if the condition is true, 0
// otherwise. Hence, the condition must be carry clear.
CSETM(RD, CC_CC);
}
else
{
MOVI2R(RD, imm);
ADC(RD, RD, ARM64Reg::WZR);
}
break;
}
case CarryFlag::ConstantTrue:
{
gpr.SetImmediate(d, ~i + j + 1);
gpr.SetImmediate(d, imm + 1);
break;
}
case CarryFlag::ConstantFalse:
{
gpr.SetImmediate(d, ~i + j);
gpr.SetImmediate(d, imm);
break;
}
}
const bool must_have_carry = Interpreter::Helper_Carry(~i, j);
const bool might_have_carry = (~i + j) == 0xFFFFFFFF;
const bool might_have_carry = is_all_ones;
if (must_have_carry)
{
@ -1337,39 +1405,49 @@ void JitArm64::subfzex(UGeckoInstruction inst)
int a = inst.RA, d = inst.RD;
gpr.BindToRegister(d, d == a);
if (gpr.IsImm(a) && HasConstantCarry())
{
const u32 imm = ~gpr.GetImm(a);
const u32 carry = js.carryFlag == CarryFlag::ConstantTrue;
gpr.SetImmediate(d, imm + carry);
ComputeCarry(Interpreter::Helper_Carry(imm, carry));
}
else
{
gpr.BindToRegister(d, d == a);
switch (js.carryFlag)
{
case CarryFlag::InPPCState:
{
switch (js.carryFlag)
{
auto WA = gpr.GetScopedReg();
LDRB(IndexType::Unsigned, WA, PPC_REG, PPCSTATE_OFF(xer_ca));
MVN(gpr.R(d), gpr.R(a));
CARRY_IF_NEEDED(ADD, ADDS, gpr.R(d), gpr.R(d), WA);
case CarryFlag::InPPCState:
{
{
auto WA = gpr.GetScopedReg();
LDRB(IndexType::Unsigned, WA, PPC_REG, PPCSTATE_OFF(xer_ca));
MVN(gpr.R(d), gpr.R(a));
CARRY_IF_NEEDED(ADD, ADDS, gpr.R(d), gpr.R(d), WA);
}
ComputeCarry();
break;
}
case CarryFlag::InHostCarry:
{
CARRY_IF_NEEDED(SBC, SBCS, gpr.R(d), ARM64Reg::WZR, gpr.R(a));
ComputeCarry();
break;
}
case CarryFlag::ConstantTrue:
{
CARRY_IF_NEEDED(NEG, NEGS, gpr.R(d), gpr.R(a));
ComputeCarry();
break;
}
case CarryFlag::ConstantFalse:
{
MVN(gpr.R(d), gpr.R(a));
ComputeCarry(false);
break;
}
}
ComputeCarry();
break;
}
case CarryFlag::InHostCarry:
{
CARRY_IF_NEEDED(SBC, SBCS, gpr.R(d), ARM64Reg::WZR, gpr.R(a));
ComputeCarry();
break;
}
case CarryFlag::ConstantTrue:
{
CARRY_IF_NEEDED(NEG, NEGS, gpr.R(d), gpr.R(a));
ComputeCarry();
break;
}
case CarryFlag::ConstantFalse:
{
MVN(gpr.R(d), gpr.R(a));
ComputeCarry(false);
break;
}
}
if (inst.Rc)
@ -1436,40 +1514,66 @@ void JitArm64::addex(UGeckoInstruction inst)
if (gpr.IsImm(a) && (mex || gpr.IsImm(b)))
{
u32 i = gpr.GetImm(a), j = mex ? -1 : gpr.GetImm(b);
gpr.BindToRegister(d, false);
const u32 i = gpr.GetImm(a), j = mex ? -1 : gpr.GetImm(b);
const u32 imm = i + j;
const bool is_zero = imm == 0;
const bool is_all_ones = imm == 0xFFFFFFFF;
switch (js.carryFlag)
{
case CarryFlag::InPPCState:
{
auto WA = gpr.GetScopedReg();
LDRB(IndexType::Unsigned, WA, PPC_REG, PPCSTATE_OFF(xer_ca));
ADDI2R(gpr.R(d), WA, i + j, gpr.R(d));
gpr.BindToRegister(d, false);
ARM64Reg RD = gpr.R(d);
if (is_zero)
{
LDRB(IndexType::Unsigned, RD, PPC_REG, PPCSTATE_OFF(xer_ca));
}
else
{
auto WA = gpr.GetScopedReg();
LDRB(IndexType::Unsigned, WA, PPC_REG, PPCSTATE_OFF(xer_ca));
ADDI2R(RD, WA, imm, RD);
}
break;
}
case CarryFlag::InHostCarry:
{
gpr.BindToRegister(d, false);
ARM64Reg RD = gpr.R(d);
MOVI2R(RD, i + j);
ADC(RD, RD, ARM64Reg::WZR);
if (is_zero)
{
// RD = 0 + carry = carry ? 1 : 0
CSET(RD, CC_CS);
}
else if (is_all_ones)
{
// RD = -1 + carry = carry ? 0 : -1
// Note that CSETM sets the destination to -1 if the condition is true,
// and 0 otherwise. Hence, the condition must be carry clear.
CSETM(RD, CC_CC);
}
else
{
MOVI2R(RD, imm);
ADC(RD, RD, ARM64Reg::WZR);
}
break;
}
case CarryFlag::ConstantTrue:
{
gpr.SetImmediate(d, i + j + 1);
gpr.SetImmediate(d, imm + 1);
break;
}
case CarryFlag::ConstantFalse:
{
gpr.SetImmediate(d, i + j);
gpr.SetImmediate(d, imm);
break;
}
}
const bool must_have_carry = Interpreter::Helper_Carry(i, j);
const bool might_have_carry = (i + j) == 0xFFFFFFFF;
const bool might_have_carry = is_all_ones;
if (must_have_carry)
{

View File

@ -817,9 +817,8 @@ void JitArm64::dcbx(UGeckoInstruction inst)
STR(IndexType::Unsigned, loop_counter, PPC_REG, PPCSTATE_OFF_SPR(SPR_CTR));
// downcount -= (WA * reg_cycle_count)
MUL(WB, WA, reg_cycle_count);
MSUB(reg_downcount, WA, reg_cycle_count, reg_downcount);
// ^ Note that this cannot overflow because it's limited by (downcount/cycle_count).
SUB(reg_downcount, reg_downcount, WB);
STR(IndexType::Unsigned, reg_downcount, PPC_REG, PPCSTATE_OFF(downcount));
SetJumpTarget(downcount_is_zero_or_negative);

View File

@ -110,7 +110,7 @@ JitBase::~JitBase()
CPUThreadConfigCallback::RemoveConfigChangedCallback(m_registered_config_callback_id);
}
bool JitBase::DoesConfigNeedRefresh()
bool JitBase::DoesConfigNeedRefresh() const
{
return std::ranges::any_of(JIT_SETTINGS, [this](const auto& pair) {
return this->*pair.first != Config::Get(*pair.second);
@ -276,7 +276,7 @@ bool JitBase::CanMergeNextInstructions(int count) const
return true;
}
bool JitBase::ShouldHandleFPExceptionForInstruction(const PPCAnalyst::CodeOp* op)
bool JitBase::ShouldHandleFPExceptionForInstruction(const PPCAnalyst::CodeOp* op) const
{
if (jo.fp_exceptions)
return (op->opinfo->flags & FL_FLOAT_EXCEPTION) != 0;

View File

@ -167,7 +167,7 @@ protected:
static const std::array<std::pair<bool JitBase::*, const Config::Info<bool>*>, 23> JIT_SETTINGS;
bool DoesConfigNeedRefresh();
bool DoesConfigNeedRefresh() const;
void RefreshConfig();
void InitFastmemArena();
@ -178,8 +178,16 @@ protected:
void CleanUpAfterStackFault();
bool CanMergeNextInstructions(int count) const;
bool HasConstantCarry() const
{
#ifdef _M_ARM_64
return js.carryFlag == CarryFlag::ConstantTrue || js.carryFlag == CarryFlag::ConstantFalse;
#else
return false;
#endif
}
bool ShouldHandleFPExceptionForInstruction(const PPCAnalyst::CodeOp* op);
bool ShouldHandleFPExceptionForInstruction(const PPCAnalyst::CodeOp* op) const;
public:
explicit JitBase(Core::System& system);

View File

@ -6,11 +6,11 @@
#include <algorithm>
#include <cstring>
#include <map>
#include <ranges>
#include <sstream>
#include <string>
#include <string_view>
#include <utility>
#include <vector>
#include <fmt/format.h>
@ -18,6 +18,7 @@
#include "Common/IOFile.h"
#include "Common/Logging/Log.h"
#include "Common/StringUtil.h"
#include "Common/Unreachable.h"
#include "Core/Core.h"
#include "Core/Debugger/DebugInterface.h"
#include "Core/PowerPC/MMU.h"
@ -251,7 +252,7 @@ bool PPCSymbolDB::LoadMap(const Core::CPUThreadGuard& guard, const std::string&
continue;
// Support CodeWarrior and Dolphin map
if (std::string_view{line}.ends_with(" section layout\n") || strcmp(temp, ".text") == 0 ||
if (StripWhitespace(line).ends_with(" section layout") || strcmp(temp, ".text") == 0 ||
strcmp(temp, ".init") == 0)
{
section_name = temp;
@ -310,7 +311,7 @@ bool PPCSymbolDB::LoadMap(const Core::CPUThreadGuard& guard, const std::string&
continue;
column_count = 2;
// Three columns format:
// Three columns format (with optional alignment):
// Starting Virtual
// address Size address
// -----------------------
@ -319,7 +320,7 @@ bool PPCSymbolDB::LoadMap(const Core::CPUThreadGuard& guard, const std::string&
else
iss.str("");
// Four columns format:
// Four columns format (with optional alignment):
// Starting Virtual File
// address Size address offset
// ---------------------------------
@ -327,85 +328,77 @@ bool PPCSymbolDB::LoadMap(const Core::CPUThreadGuard& guard, const std::string&
column_count = 4;
}
u32 address, vaddress, size, offset, alignment;
char name[512], container[512];
if (column_count == 4)
{
// sometimes there is no alignment value, and sometimes it is because it is an entry of
// something else
if (length > 37 && line[37] == ' ')
u32 address;
u32 vaddress;
u32 size = 0;
u32 offset = 0;
u32 alignment = 0;
char name[512]{};
static constexpr char ENTRY_OF_STRING[] = " (entry of ";
static constexpr std::string_view ENTRY_OF_VIEW(ENTRY_OF_STRING);
auto parse_entry_of = [](char* name) {
if (char* s1 = strstr(name, ENTRY_OF_STRING); s1 != nullptr)
{
alignment = 0;
sscanf(line, "%08x %08x %08x %08x %511s", &address, &size, &vaddress, &offset, name);
char* s = strstr(line, "(entry of ");
if (s)
char container[512];
char* ptr = s1 + ENTRY_OF_VIEW.size();
sscanf(ptr, "%511s", container);
// Skip sections, those start with a dot, e.g. (entry of .text)
if (char* s2 = strchr(container, ')'); s2 != nullptr && *container != '.')
{
sscanf(s + 10, "%511s", container);
char* s2 = (strchr(container, ')'));
if (s2 && container[0] != '.')
{
s2[0] = '\0';
strcat(container, "::");
strcat(container, name);
strcpy(name, container);
}
ptr += strlen(container);
// Preserve data after the entry part, usually it contains object names
strcpy(s1, ptr);
*s2 = '\0';
strcat(container, "::");
strcat(container, name);
strcpy(name, container);
}
}
else
{
sscanf(line, "%08x %08x %08x %08x %i %511s", &address, &size, &vaddress, &offset,
&alignment, name);
}
}
else if (column_count == 3)
};
auto was_alignment = [](const char* name) {
return *name == ' ' || (*name >= '0' && *name <= '9');
};
auto parse_alignment = [](char* name, u32* alignment) {
const std::string buffer(StripWhitespace(name));
return sscanf(buffer.c_str(), "%i %511[^\r\n]", alignment, name);
};
switch (column_count)
{
case 4:
// sometimes there is no alignment value, and sometimes it is because it is an entry of
// something else
sscanf(line, "%08x %08x %08x %08x %511[^\r\n]", &address, &size, &vaddress, &offset, name);
if (was_alignment(name))
parse_alignment(name, &alignment);
// The `else` statement was omitted to handle symbol already saved in Dolphin symbol map
// since it doesn't omit the alignment on save for such case.
parse_entry_of(name);
break;
case 3:
// some entries in the table have a function name followed by " (entry of " followed by a
// container name, followed by ")"
// instead of a space followed by a number followed by a space followed by a name
if (length > 27 && line[27] != ' ' && strstr(line, "(entry of "))
{
alignment = 0;
sscanf(line, "%08x %08x %08x %511s", &address, &size, &vaddress, name);
char* s = strstr(line, "(entry of ");
if (s)
{
sscanf(s + 10, "%511s", container);
char* s2 = (strchr(container, ')'));
if (s2 && container[0] != '.')
{
s2[0] = '\0';
strcat(container, "::");
strcat(container, name);
strcpy(name, container);
}
}
}
else
{
sscanf(line, "%08x %08x %08x %i %511s", &address, &size, &vaddress, &alignment, name);
}
}
else if (column_count == 2)
{
sscanf(line, "%08x %511s", &address, name);
sscanf(line, "%08x %08x %08x %511[^\r\n]", &address, &size, &vaddress, name);
if (was_alignment(name))
parse_alignment(name, &alignment);
// The `else` statement was omitted to handle symbol already saved in Dolphin symbol map
// since it doesn't omit the alignment on save for such case.
parse_entry_of(name);
break;
case 2:
sscanf(line, "%08x %511[^\r\n]", &address, name);
vaddress = address;
size = 0;
}
else
{
break;
default:
// Should never happen
Common::Unreachable();
break;
}
const char* namepos = strstr(line, name);
if (namepos != nullptr) // would be odd if not :P
strcpy(name, namepos);
name[strlen(name) - 1] = 0;
if (name[strlen(name) - 1] == '\r')
name[strlen(name) - 1] = 0;
// Split the current name string into separate parts, and get the object name
// if it exists.
const std::vector<std::string> parts = SplitString(name, '\t');
const std::string name_string(StripWhitespace(parts[0]));
const std::string name_string(StripWhitespace(parts.size() > 0 ? parts[0] : name));
const std::string object_filename_string =
parts.size() > 1 ? std::string(StripWhitespace(parts[1])) : "";
@ -466,42 +459,38 @@ bool PPCSymbolDB::SaveSymbolMap(const std::string& filename) const
if (!f)
return false;
std::vector<const Common::Symbol*> function_symbols;
std::vector<const Common::Symbol*> data_symbols;
for (const auto& function : m_functions)
{
const Common::Symbol& symbol = function.second;
if (symbol.type == Common::Symbol::Type::Function)
function_symbols.push_back(&symbol);
else
data_symbols.push_back(&symbol);
}
// Write .text section
auto function_symbols =
m_functions |
std::views::filter([](auto f) { return f.second.type == Common::Symbol::Type::Function; }) |
std::views::transform([](auto f) { return f.second; });
f.WriteString(".text section layout\n");
for (const auto& symbol : function_symbols)
{
// Write symbol address, size, virtual address, alignment, name
std::string line = fmt::format("{0:08x} {1:06x} {2:08x} {3} {4}", symbol->address, symbol->size,
symbol->address, 0, symbol->name);
std::string line = fmt::format("{:08x} {:06x} {:08x} {} {}", symbol.address, symbol.size,
symbol.address, 0, symbol.name);
// Also write the object name if it exists
if (!symbol->object_name.empty())
line += fmt::format(" \t{0}", symbol->object_name);
if (!symbol.object_name.empty())
line += fmt::format(" \t{0}", symbol.object_name);
line += "\n";
f.WriteString(line);
}
// Write .data section
auto data_symbols =
m_functions |
std::views::filter([](auto f) { return f.second.type == Common::Symbol::Type::Data; }) |
std::views::transform([](auto f) { return f.second; });
f.WriteString("\n.data section layout\n");
for (const auto& symbol : data_symbols)
{
// Write symbol address, size, virtual address, alignment, name
std::string line = fmt::format("{0:08x} {1:06x} {2:08x} {3} {4}", symbol->address, symbol->size,
symbol->address, 0, symbol->name);
std::string line = fmt::format("{:08x} {:06x} {:08x} {} {}", symbol.address, symbol.size,
symbol.address, 0, symbol.name);
// Also write the object name if it exists
if (!symbol->object_name.empty())
line += fmt::format(" \t{0}", symbol->object_name);
if (!symbol.object_name.empty())
line += fmt::format(" \t{0}", symbol.object_name);
line += "\n";
f.WriteString(line);
}

View File

@ -467,12 +467,7 @@ OnlineSystemUpdater::Response OnlineSystemUpdater::GetSystemTitles()
// but the backing data CDN is still active and accessible from other URLs. We take advantage
// of this by hosting our own NetUpdateSOAP endpoint which serves the correct list of titles to
// install along with URLs for the Wii U CDN.
#ifdef ANDROID
// HTTPS is unsupported on Android (https://bugs.dolphin-emu.org/issues/11772).
base_url = "http://fakenus.dolphin-emu.org";
#else
base_url = "https://fakenus.dolphin-emu.org";
#endif
}
const std::string url = fmt::format("{}/nus/services/NetUpdateSOAP", base_url);

View File

@ -573,12 +573,11 @@ endif()
if(APPLE)
include(BundleUtilities)
set(BUNDLE_PATH ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/DolphinQt.app)
# Ask for an application bundle.
set_target_properties(dolphin-emu PROPERTIES
MACOSX_BUNDLE true
MACOSX_BUNDLE_INFO_PLIST "${CMAKE_CURRENT_BINARY_DIR}/Info.plist"
MACOSX_BUNDLE_INFO_PLIST "${CMAKE_CURRENT_SOURCE_DIR}/Info.plist.in"
XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY ""
OUTPUT_NAME DolphinQt
)
@ -615,6 +614,9 @@ if(APPLE)
source_group("Resources" FILES "${CMAKE_SOURCE_DIR}/Data/${res}")
endforeach()
include(DolphinInjectVersionInfo)
dolphin_inject_version_info(dolphin-emu)
# Copy MoltenVK into the bundle
if(ENABLE_VULKAN)
if(USE_BUNDLED_MOLTENVK)
@ -649,7 +651,7 @@ if(APPLE)
COMMAND "${CMAKE_SOURCE_DIR}/Tools/mac-codesign.sh"
"-e" "${CMAKE_CURRENT_SOURCE_DIR}/DolphinEmu$<$<CONFIG:Debug>:Debug>.entitlements"
"${MACOS_CODE_SIGNING_IDENTITY}"
"${BUNDLE_PATH}"
"$<TARGET_BUNDLE_DIR:dolphin-emu>"
)
endif()
else()

View File

@ -3,6 +3,8 @@
#include "DolphinQt/Config/CheatCodeEditor.h"
#include <regex>
#include <QDialogButtonBox>
#include <QFontDatabase>
#include <QGridLayout>
@ -251,12 +253,26 @@ bool CheatCodeEditor::AcceptGecko()
return false;
}
m_gecko_code->name = name.toStdString();
m_gecko_code->creator = m_creator_edit->text().toStdString();
m_gecko_code->name = name.trimmed().toStdString();
m_gecko_code->creator = m_creator_edit->text().trimmed().toStdString();
m_gecko_code->codes = std::move(entries);
m_gecko_code->notes = SplitString(m_notes_edit->toPlainText().toStdString(), '\n');
m_gecko_code->user_defined = true;
{
// The creator name is not expected to be present in the cheat code name. It will be extracted
// and moved into its dedicated "creator" field.
std::smatch matches;
if (std::regex_search(m_gecko_code->name, matches, std::regex{"(.*)(\\[(.*)\\])"}))
{
m_gecko_code->name = StripWhitespace(matches[1].str());
if (m_gecko_code->creator.empty())
{
m_gecko_code->creator = StripWhitespace(matches[3].str());
}
}
}
return true;
}

View File

@ -74,7 +74,7 @@ void ConfigStringChoice::OnConfigChanged()
ConfigComplexChoice::ConfigComplexChoice(const InfoVariant setting1, const InfoVariant setting2,
Config::Layer* layer)
: m_setting1(setting1), m_setting2(setting2), m_layer(layer)
: m_layer(layer), m_setting1(setting1), m_setting2(setting2)
{
connect(&Settings::Instance(), &Settings::ConfigChanged, this, &ConfigComplexChoice::Refresh);
connect(this, &QComboBox::currentIndexChanged, this, &ConfigComplexChoice::SaveValue);
@ -115,7 +115,7 @@ void ConfigComplexChoice::Reset()
void ConfigComplexChoice::SaveValue(int choice)
{
auto Set = [this, choice](auto& setting, auto& value) {
auto Set = [this](auto& setting, auto& value) {
if (m_layer != nullptr)
{
m_layer->Set(setting.GetLocation(), value);

View File

@ -34,7 +34,8 @@ using SIDeviceName = std::pair<SerialInterface::SIDevices, const char*>;
static constexpr std::array s_gc_types = {
SIDeviceName{SerialInterface::SIDEVICE_NONE, _trans("None")},
SIDeviceName{SerialInterface::SIDEVICE_GC_CONTROLLER, _trans("Standard Controller")},
SIDeviceName{SerialInterface::SIDEVICE_WIIU_ADAPTER, _trans("GameCube Adapter for Wii U")},
SIDeviceName{SerialInterface::SIDEVICE_WIIU_ADAPTER,
_trans("GameCube Controller Adapter (USB)")},
SIDeviceName{SerialInterface::SIDEVICE_GC_STEERING, _trans("Steering Wheel")},
SIDeviceName{SerialInterface::SIDEVICE_DANCEMAT, _trans("Dance Mat")},
SIDeviceName{SerialInterface::SIDEVICE_GC_TARUKONGA, _trans("DK Bongos")},

View File

@ -36,7 +36,7 @@ AdvancedWidget::AdvancedWidget(GraphicsWindow* parent)
OnEmulationStateChanged(state != Core::State::Uninitialized);
});
connect(m_manual_texture_sampling, &QCheckBox::toggled,
[this, parent] { emit parent->UseFastTextureSamplingChanged(); });
[parent] { emit parent->UseFastTextureSamplingChanged(); });
OnBackendChanged();
OnEmulationStateChanged(!Core::IsUninitialized(Core::System::GetInstance()));
@ -262,7 +262,7 @@ void AdvancedWidget::ConnectWidgets()
m_dump_base_textures->setEnabled(checked);
});
connect(m_enable_graphics_mods, &QCheckBox::toggled, this,
[this](bool checked) { emit Settings::Instance().EnableGfxModsChanged(checked); });
[](bool checked) { emit Settings::Instance().EnableGfxModsChanged(checked); });
#if defined(HAVE_FFMPEG)
connect(m_dump_use_lossless, &QCheckBox::toggled, this,
[this](bool checked) { m_dump_bitrate->setEnabled(!checked); });

View File

@ -32,7 +32,7 @@ GCPadWiiUConfigDialog::~GCPadWiiUConfigDialog()
void GCPadWiiUConfigDialog::CreateLayout()
{
setWindowTitle(tr("GameCube Adapter for Wii U at Port %1").arg(m_port + 1));
setWindowTitle(tr("GameCube Controller Adapter at Port %1").arg(m_port + 1));
m_layout = new QVBoxLayout();
m_status_label = new QLabel();

View File

@ -4,7 +4,6 @@
#include "DolphinQt/Config/Mapping/IOWindow.h"
#include <optional>
#include <thread>
#include <QBrush>
#include <QColor>
@ -20,16 +19,15 @@
#include <QSlider>
#include <QSpinBox>
#include <QTableWidget>
#include <QTimer>
#include <QVBoxLayout>
#include "Core/Core.h"
#include "DolphinQt/Config/Mapping/MappingCommon.h"
#include "DolphinQt/Config/Mapping/MappingIndicator.h"
#include "DolphinQt/Config/Mapping/MappingWidget.h"
#include "DolphinQt/Config/Mapping/MappingWindow.h"
#include "DolphinQt/QtUtils/BlockUserInputFilter.h"
#include "DolphinQt/QtUtils/ModalMessageBox.h"
#include "DolphinQt/QtUtils/SetWindowDecorations.h"
#include "DolphinQt/Settings.h"
#include "InputCommon/ControlReference/ControlReference.h"
@ -40,6 +38,9 @@
namespace
{
constexpr auto INPUT_DETECT_TIME = std::chrono::seconds(2);
constexpr auto OUTPUT_TEST_TIME = std::chrono::seconds(2);
QTextCharFormat GetSpecialCharFormat()
{
QTextCharFormat format;
@ -228,15 +229,17 @@ private:
bool m_should_paint_state_indicator = false;
};
IOWindow::IOWindow(MappingWidget* parent, ControllerEmu::EmulatedController* controller,
IOWindow::IOWindow(MappingWindow* window, ControllerEmu::EmulatedController* controller,
ControlReference* ref, IOWindow::Type type)
: QDialog(parent), m_reference(ref), m_original_expression(ref->GetExpression()),
: QDialog(window), m_reference(ref), m_original_expression(ref->GetExpression()),
m_controller(controller), m_type(type)
{
SetQWidgetWindowDecorations(this);
CreateMainLayout();
connect(parent, &MappingWidget::Update, this, &IOWindow::Update);
connect(parent->GetParent(), &MappingWindow::ConfigChanged, this, &IOWindow::ConfigChanged);
connect(window, &MappingWindow::Update, this, &IOWindow::Update);
connect(window, &MappingWindow::ConfigChanged, this, &IOWindow::ConfigChanged);
connect(&Settings::Instance(), &Settings::ConfigChanged, this, &IOWindow::ConfigChanged);
setWindowTitle(type == IOWindow::Type::Input ? tr("Configure Input") : tr("Configure Output"));
@ -258,18 +261,29 @@ void IOWindow::CreateMainLayout()
m_devices_combo = new QComboBox();
m_option_list = new QTableWidget();
m_select_button = new QPushButton(tr("Select"));
m_detect_button = new QPushButton(tr("Detect"), this);
m_test_button = new QPushButton(tr("Test"), this);
m_select_button =
new QPushButton(m_type == IOWindow::Type::Input ? tr("Insert Input") : tr("Insert Output"));
m_detect_button = new QPushButton(tr("Detect Input"), this);
m_test_button = new QPushButton(tr("Test Output"), this);
m_button_box = new QDialogButtonBox();
m_clear_button = new QPushButton(tr("Clear"));
m_scalar_spinbox = new QSpinBox();
m_parse_text = new InputStateLineEdit([this] {
const auto lock = m_controller->GetStateLock();
return m_reference->GetState<ControlState>();
});
m_parse_text->setReadOnly(true);
if (m_type == Type::Input)
{
m_parse_text = new InputStateLineEdit([this] {
const auto lock = m_controller->GetStateLock();
return m_reference->GetState<ControlState>();
});
}
else
{
m_parse_text = new InputStateLineEdit([this] {
const auto lock = m_controller->GetStateLock();
return m_output_test_timer->isActive() * m_reference->range;
});
}
m_expression_text = new QPlainTextEdit();
m_expression_text->setFont(QFontDatabase::systemFont(QFontDatabase::FixedFont));
@ -419,11 +433,17 @@ void IOWindow::CreateMainLayout()
m_button_box->addButton(m_clear_button, QDialogButtonBox::ActionRole);
m_button_box->addButton(QDialogButtonBox::Ok);
m_output_test_timer = new QTimer(this);
m_output_test_timer->setSingleShot(true);
setLayout(m_main_layout);
}
void IOWindow::ConfigChanged()
{
emit DetectInputComplete();
emit TestOutputComplete();
const QSignalBlocker blocker(this);
const auto lock = ControllerEmu::EmulatedController::GetStateLock();
@ -444,6 +464,31 @@ void IOWindow::Update()
{
m_option_list->viewport()->update();
m_parse_text->update();
if (!m_input_detector)
return;
if (m_input_detector->IsComplete())
{
const auto results = m_input_detector->TakeResults();
emit DetectInputComplete();
if (results.empty())
return;
// Select the first detected input.
auto list = m_option_list->findItems(QString::fromStdString(results.front().input->GetName()),
Qt::MatchFixedString);
if (list.empty())
return;
m_option_list->setCurrentItem(list.front());
}
else
{
m_input_detector->Update(INPUT_DETECT_TIME, {}, INPUT_DETECT_TIME);
}
}
void IOWindow::ConnectWidgets()
@ -453,8 +498,50 @@ void IOWindow::ConnectWidgets()
connect(&Settings::Instance(), &Settings::ReleaseDevices, this, &IOWindow::ReleaseDevices);
connect(&Settings::Instance(), &Settings::DevicesChanged, this, &IOWindow::UpdateDeviceList);
connect(m_detect_button, &QPushButton::clicked, this, &IOWindow::OnDetectButtonPressed);
connect(m_test_button, &QPushButton::clicked, this, &IOWindow::OnTestButtonPressed);
// Input detection:
// Clicking "Detect" button starts a timer before the actual detection.
auto* const input_detect_start_timer = new QTimer(this);
input_detect_start_timer->setSingleShot(true);
connect(m_detect_button, &QPushButton::clicked, [this, input_detect_start_timer] {
m_detect_button->setText(tr("[ ... ]"));
input_detect_start_timer->start(MappingCommon::INPUT_DETECT_INITIAL_DELAY);
});
connect(input_detect_start_timer, &QTimer::timeout, [this] {
m_detect_button->setText(tr("[ Press Now ]"));
m_input_detector = std::make_unique<ciface::Core::InputDetector>();
const auto lock = m_controller->GetStateLock();
m_input_detector->Start(g_controller_interface, {m_devq.ToString()});
QtUtils::InstallKeyboardBlocker(m_detect_button, this, &IOWindow::DetectInputComplete);
});
connect(this, &IOWindow::DetectInputComplete,
[this, initial_text = m_detect_button->text(), input_detect_start_timer] {
input_detect_start_timer->stop();
m_input_detector.reset();
m_detect_button->setText(initial_text);
});
// Rumble testing:
connect(m_test_button, &QPushButton::clicked, [this] {
// Stop if already started.
if (m_output_test_timer->isActive())
{
emit IOWindow::TestOutputComplete();
return;
}
m_test_button->setText(QStringLiteral("[ ... ]"));
m_output_test_timer->start(OUTPUT_TEST_TIME);
const auto lock = m_controller->GetStateLock();
m_reference->State(1.0);
});
connect(m_output_test_timer, &QTimer::timeout,
[this, initial_text = m_test_button->text()] { emit TestOutputComplete(); });
connect(this, &IOWindow::TestOutputComplete, [this, initial_text = m_test_button->text()] {
m_output_test_timer->stop();
m_test_button->setText(initial_text);
const auto lock = m_controller->GetStateLock();
m_reference->State(0.0);
});
connect(this, &IOWindow::closeEvent, this, &IOWindow::TestOutputComplete);
connect(m_button_box, &QDialogButtonBox::clicked, this, &IOWindow::OnDialogButtonPressed);
connect(m_devices_combo, &QComboBox::currentTextChanged, this, &IOWindow::OnDeviceChanged);
@ -546,30 +633,10 @@ void IOWindow::OnDialogButtonPressed(QAbstractButton* button)
}
}
void IOWindow::OnDetectButtonPressed()
{
const auto expression =
MappingCommon::DetectExpression(m_detect_button, g_controller_interface, {m_devq.ToString()},
m_devq, ciface::MappingCommon::Quote::Off);
if (expression.isEmpty())
return;
const auto list = m_option_list->findItems(expression, Qt::MatchFixedString);
// Try to select the first. If this fails, the last selected item would still appear as such
if (!list.empty())
m_option_list->setCurrentItem(list[0]);
}
void IOWindow::OnTestButtonPressed()
{
MappingCommon::TestOutput(m_test_button, static_cast<OutputReference*>(m_reference));
}
void IOWindow::OnRangeChanged(int value)
{
m_reference->range = value / 100.0;
emit TestOutputComplete();
}
void IOWindow::ReleaseDevices()
@ -670,6 +737,8 @@ void IOWindow::UpdateDeviceList()
void IOWindow::UpdateExpression(std::string new_expression, UpdateMode mode)
{
emit TestOutputComplete();
const auto lock = m_controller->GetStateLock();
if (mode != UpdateMode::Force && new_expression == m_reference->GetExpression())
return;
@ -719,6 +788,7 @@ InputStateDelegate::InputStateDelegate(IOWindow* parent, int column,
InputStateLineEdit::InputStateLineEdit(std::function<ControlState()> state_evaluator)
: m_state_evaluator(std::move(state_evaluator))
{
setReadOnly(true);
}
static void PaintStateIndicator(QPainter& painter, const QRect& region, ControlState state)

View File

@ -12,11 +12,10 @@
#include <QString>
#include <QSyntaxHighlighter>
#include "Common/Flag.h"
#include "InputCommon/ControllerInterface/CoreDevice.h"
class ControlReference;
class MappingWidget;
class MappingWindow;
class QAbstractButton;
class QDialogButtonBox;
class QLineEdit;
@ -66,9 +65,13 @@ public:
Output
};
explicit IOWindow(MappingWidget* parent, ControllerEmu::EmulatedController* m_controller,
explicit IOWindow(MappingWindow* window, ControllerEmu::EmulatedController* m_controller,
ControlReference* ref, Type type);
signals:
void DetectInputComplete();
void TestOutputComplete();
private:
std::shared_ptr<ciface::Core::Device> GetSelectedDevice() const;
@ -79,8 +82,6 @@ private:
void OnDialogButtonPressed(QAbstractButton* button);
void OnDeviceChanged();
void OnDetectButtonPressed();
void OnTestButtonPressed();
void OnRangeChanged(int range);
void AppendSelectedOption();
@ -115,10 +116,12 @@ private:
// Input actions
QPushButton* m_detect_button;
std::unique_ptr<ciface::Core::InputDetector> m_input_detector;
QComboBox* m_functions_combo;
// Output actions
QPushButton* m_test_button;
QTimer* m_output_test_timer;
// Textarea
QPlainTextEdit* m_expression_text;

View File

@ -9,13 +9,12 @@
#include <QString>
#include "DolphinQt/Config/Mapping/IOWindow.h"
#include "DolphinQt/Config/Mapping/MappingCommon.h"
#include "DolphinQt/Config/Mapping/MappingWidget.h"
#include "DolphinQt/Config/Mapping/MappingWindow.h"
#include "DolphinQt/QtUtils/BlockUserInputFilter.h"
#include "DolphinQt/QtUtils/SetWindowDecorations.h"
#include "InputCommon/ControlReference/ControlReference.h"
#include "InputCommon/ControllerEmu/ControlGroup/Buttons.h"
#include "InputCommon/ControllerEmu/ControllerEmu.h"
#include "InputCommon/ControllerInterface/ControllerInterface.h"
@ -74,7 +73,7 @@ bool MappingButton::IsInput() const
}
MappingButton::MappingButton(MappingWidget* parent, ControlReference* ref, bool indicator)
: ElidedButton(RefToDisplayString(ref)), m_parent(parent), m_reference(ref)
: ElidedButton(RefToDisplayString(ref)), m_mapping_window(parent->GetParent()), m_reference(ref)
{
if (IsInput())
{
@ -92,17 +91,22 @@ MappingButton::MappingButton(MappingWidget* parent, ControlReference* ref, bool
connect(parent, &MappingWidget::Update, this, &MappingButton::UpdateIndicator);
connect(parent, &MappingWidget::ConfigChanged, this, &MappingButton::ConfigChanged);
connect(this, &MappingButton::ConfigChanged, [this] {
setText(RefToDisplayString(m_reference));
m_is_mapping = false;
});
}
void MappingButton::AdvancedPressed()
{
IOWindow io(m_parent, m_parent->GetController(), m_reference,
m_mapping_window->CancelMapping();
IOWindow io(m_mapping_window, m_mapping_window->GetController(), m_reference,
m_reference->IsInput() ? IOWindow::Type::Input : IOWindow::Type::Output);
SetQWidgetWindowDecorations(&io);
io.exec();
ConfigChanged();
m_parent->SaveSettings();
m_mapping_window->Save();
}
void MappingButton::Clicked()
@ -113,31 +117,8 @@ void MappingButton::Clicked()
return;
}
const auto default_device_qualifier = m_parent->GetController()->GetDefaultDevice();
QString expression;
if (m_parent->GetParent()->IsMappingAllDevices())
{
expression = MappingCommon::DetectExpression(this, g_controller_interface,
g_controller_interface.GetAllDeviceStrings(),
default_device_qualifier);
}
else
{
expression = MappingCommon::DetectExpression(this, g_controller_interface,
{default_device_qualifier.ToString()},
default_device_qualifier);
}
if (expression.isEmpty())
return;
m_reference->SetExpression(expression.toStdString());
m_parent->GetController()->UpdateSingleControlReference(g_controller_interface, m_reference);
ConfigChanged();
m_parent->SaveSettings();
m_is_mapping = true;
m_mapping_window->QueueInputDetection(this);
}
void MappingButton::Clear()
@ -145,22 +126,21 @@ void MappingButton::Clear()
m_reference->range = 100.0 / SLIDER_TICK_COUNT;
m_reference->SetExpression("");
m_parent->GetController()->UpdateSingleControlReference(g_controller_interface, m_reference);
m_mapping_window->GetController()->UpdateSingleControlReference(g_controller_interface,
m_reference);
m_parent->SaveSettings();
ConfigChanged();
m_mapping_window->Save();
m_mapping_window->UnQueueInputDetection(this);
}
void MappingButton::UpdateIndicator()
{
if (!isActiveWindow())
return;
QFont f = m_mapping_window->font();
QFont f = m_parent->font();
// If the input state is "true" (we can't know the state of outputs), show it in bold.
if (m_reference->IsInput() && m_reference->GetState<bool>())
if (isActiveWindow() && m_reference->IsInput() && m_reference->GetState<bool>() && !m_is_mapping)
f.setBold(true);
// If the expression has failed to parse, show it in italic.
// Some expressions still work even the failed to parse so don't prevent the GetState() above.
if (m_reference->GetParseStatus() == ciface::ExpressionParser::ParseStatus::SyntaxError)
@ -169,9 +149,12 @@ void MappingButton::UpdateIndicator()
setFont(f);
}
void MappingButton::ConfigChanged()
void MappingButton::StartMapping()
{
setText(RefToDisplayString(m_reference));
// Focus just makes it more clear which button is currently being mapped.
setFocus();
setText(tr("[ Press Now ]"));
QtUtils::InstallKeyboardBlocker(this, this, &MappingButton::ConfigChanged);
}
void MappingButton::mouseReleaseEvent(QMouseEvent* event)
@ -189,3 +172,8 @@ void MappingButton::mouseReleaseEvent(QMouseEvent* event)
return;
}
}
ControlReference* MappingButton::GetControlReference()
{
return m_reference;
}

View File

@ -3,11 +3,11 @@
#pragma once
#include "Common/Flag.h"
#include "DolphinQt/QtUtils/ElidedButton.h"
class ControlReference;
class MappingWidget;
class MappingWindow;
class QEvent;
class QMouseEvent;
@ -18,16 +18,21 @@ public:
MappingButton(MappingWidget* widget, ControlReference* ref, bool indicator);
bool IsInput() const;
ControlReference* GetControlReference();
void StartMapping();
signals:
void ConfigChanged();
private:
void Clear();
void UpdateIndicator();
void ConfigChanged();
void AdvancedPressed();
void Clicked();
void mouseReleaseEvent(QMouseEvent* event) override;
MappingWidget* m_parent;
ControlReference* m_reference;
MappingWindow* const m_mapping_window;
ControlReference* const m_reference;
bool m_is_mapping = false;
};

View File

@ -3,19 +3,18 @@
#include "DolphinQt/Config/Mapping/MappingCommon.h"
#include <chrono>
#include <deque>
#include <memory>
#include <QApplication>
#include <QPushButton>
#include <QRegularExpression>
#include <QString>
#include <QTimer>
#include "DolphinQt/QtUtils/BlockUserInputFilter.h"
#include "InputCommon/ControlReference/ControlReference.h"
#include "InputCommon/ControllerInterface/MappingCommon.h"
#include "DolphinQt/Config/Mapping/MappingButton.h"
#include "DolphinQt/Config/Mapping/MappingWindow.h"
#include "Common/Thread.h"
#include "InputCommon/ControlReference/ControlReference.h"
#include "InputCommon/ControllerEmu/ControllerEmu.h"
#include "InputCommon/ControllerInterface/ControllerInterface.h"
#include "InputCommon/ControllerInterface/MappingCommon.h"
namespace MappingCommon
{
@ -23,65 +22,128 @@ constexpr auto INPUT_DETECT_INITIAL_TIME = std::chrono::seconds(3);
constexpr auto INPUT_DETECT_CONFIRMATION_TIME = std::chrono::milliseconds(0);
constexpr auto INPUT_DETECT_MAXIMUM_TIME = std::chrono::seconds(5);
constexpr auto OUTPUT_TEST_TIME = std::chrono::seconds(2);
QString DetectExpression(QPushButton* button, ciface::Core::DeviceContainer& device_container,
const std::vector<std::string>& device_strings,
const ciface::Core::DeviceQualifier& default_device,
ciface::MappingCommon::Quote quote)
class MappingProcessor : public QWidget
{
const auto filter = new BlockUserInputFilter(button);
public:
MappingProcessor(MappingWindow* parent) : QWidget{parent}, m_parent{parent}
{
using MW = MappingWindow;
using MP = MappingProcessor;
button->installEventFilter(filter);
button->grabKeyboard();
button->grabMouse();
connect(parent, &MW::Update, this, &MP::ProcessMappingButtons);
connect(parent, &MW::ConfigChanged, this, &MP::CancelMapping);
const auto old_text = button->text();
button->setText(QStringLiteral("..."));
connect(parent, &MW::UnQueueInputDetection, this, &MP::UnQueueInputDetection);
connect(parent, &MW::QueueInputDetection, this, &MP::QueueInputDetection);
connect(parent, &MW::CancelMapping, this, &MP::CancelMapping);
// The button text won't be updated if we don't process events here
QApplication::processEvents();
m_input_detection_start_timer = new QTimer(this);
m_input_detection_start_timer->setSingleShot(true);
connect(m_input_detection_start_timer, &QTimer::timeout, this, &MP::StartInputDetection);
}
// Avoid that the button press itself is registered as an event
Common::SleepCurrentThread(50);
void StartInputDetection()
{
const auto& default_device = m_parent->GetController()->GetDefaultDevice();
auto& button = m_clicked_mapping_buttons.front();
auto detections =
device_container.DetectInput(device_strings, INPUT_DETECT_INITIAL_TIME,
INPUT_DETECT_CONFIRMATION_TIME, INPUT_DETECT_MAXIMUM_TIME);
button->StartMapping();
ciface::MappingCommon::RemoveSpuriousTriggerCombinations(&detections);
std::vector device_strings{default_device.ToString()};
if (m_parent->IsMappingAllDevices())
device_strings = g_controller_interface.GetAllDeviceStrings();
const auto timer = new QTimer(button);
m_input_detector = std::make_unique<ciface::Core::InputDetector>();
const auto lock = m_parent->GetController()->GetStateLock();
m_input_detector->Start(g_controller_interface, device_strings);
}
timer->setSingleShot(true);
void ProcessMappingButtons()
{
if (!m_input_detector)
return;
button->connect(timer, &QTimer::timeout, [button, filter] {
button->releaseMouse();
button->releaseKeyboard();
button->removeEventFilter(filter);
});
m_input_detector->Update(INPUT_DETECT_INITIAL_TIME, INPUT_DETECT_CONFIRMATION_TIME,
INPUT_DETECT_MAXIMUM_TIME);
// Prevent mappings of "space", "return", or mouse clicks from re-activating detection.
timer->start(500);
if (m_input_detector->IsComplete())
{
auto detections = m_input_detector->TakeResults();
ciface::MappingCommon::RemoveSpuriousTriggerCombinations(&detections);
button->setText(old_text);
// No inputs detected. Cancel this and any other queued mappings.
if (detections.empty())
{
CancelMapping();
return;
}
return QString::fromStdString(BuildExpression(detections, default_device, quote));
}
const auto& default_device = m_parent->GetController()->GetDefaultDevice();
auto& button = m_clicked_mapping_buttons.front();
auto* const control_reference = button->GetControlReference();
void TestOutput(QPushButton* button, OutputReference* reference)
control_reference->SetExpression(
BuildExpression(detections, default_device, ciface::MappingCommon::Quote::On));
m_parent->Save();
m_parent->GetController()->UpdateSingleControlReference(g_controller_interface,
control_reference);
UnQueueInputDetection(button);
}
}
void UpdateInputDetectionStartTimer()
{
m_input_detector.reset();
if (m_clicked_mapping_buttons.empty())
m_input_detection_start_timer->stop();
else
m_input_detection_start_timer->start(INPUT_DETECT_INITIAL_DELAY);
}
void UnQueueInputDetection(MappingButton* button)
{
std::erase(m_clicked_mapping_buttons, button);
button->ConfigChanged();
UpdateInputDetectionStartTimer();
}
void QueueInputDetection(MappingButton* button)
{
// UnQueue if already queued.
if (std::erase(m_clicked_mapping_buttons, button))
{
button->ConfigChanged();
UpdateInputDetectionStartTimer();
return;
}
button->setText(QStringLiteral("[ ... ]"));
m_clicked_mapping_buttons.push_back(button);
UpdateInputDetectionStartTimer();
}
void CancelMapping()
{
// Signal buttons to take on their proper input expression text.
for (auto* button : m_clicked_mapping_buttons)
button->ConfigChanged();
m_clicked_mapping_buttons = {};
UpdateInputDetectionStartTimer();
}
private:
std::deque<MappingButton*> m_clicked_mapping_buttons;
std::unique_ptr<ciface::Core::InputDetector> m_input_detector;
QTimer* m_input_detection_start_timer;
MappingWindow* const m_parent;
};
void CreateMappingProcessor(MappingWindow* window)
{
const auto old_text = button->text();
button->setText(QStringLiteral("..."));
// The button text won't be updated if we don't process events here
QApplication::processEvents();
reference->State(1.0);
std::this_thread::sleep_for(OUTPUT_TEST_TIME);
reference->State(0.0);
button->setText(old_text);
new MappingProcessor{window};
}
} // namespace MappingCommon

View File

@ -3,23 +3,14 @@
#pragma once
#include <string>
#include <vector>
#include <chrono>
#include "InputCommon/ControllerInterface/CoreDevice.h"
#include "InputCommon/ControllerInterface/MappingCommon.h"
class QString;
class OutputReference;
class QPushButton;
class MappingWindow;
namespace MappingCommon
{
QString DetectExpression(QPushButton* button, ciface::Core::DeviceContainer& device_container,
const std::vector<std::string>& device_strings,
const ciface::Core::DeviceQualifier& default_device,
ciface::MappingCommon::Quote quote = ciface::MappingCommon::Quote::On);
void TestOutput(QPushButton* button, OutputReference* reference);
// A slight delay improves behavior when "clicking" the detect button via key-press.
static constexpr auto INPUT_DETECT_INITIAL_DELAY = std::chrono::milliseconds{100};
void CreateMappingProcessor(MappingWindow*);
} // namespace MappingCommon

View File

@ -20,7 +20,6 @@
#include "DolphinQt/Config/Mapping/MappingWindow.h"
#include "DolphinQt/QtUtils/SetWindowDecorations.h"
#include "InputCommon/ControlReference/ControlReference.h"
#include "InputCommon/ControllerEmu/Control/Control.h"
#include "InputCommon/ControllerEmu/ControlGroup/ControlGroup.h"
#include "InputCommon/ControllerEmu/ControlGroup/MixedTriggers.h"
@ -340,10 +339,10 @@ MappingWidget::CreateSettingAdvancedMappingButton(ControllerEmu::NumericSettingB
setting.SetExpressionFromValue();
// Ensure the UI has the game-controller indicator while editing the expression.
// And cancel in-progress mappings.
ConfigChanged();
IOWindow io(this, GetController(), &setting.GetInputReference(), IOWindow::Type::Input);
SetQWidgetWindowDecorations(&io);
IOWindow io(GetParent(), GetController(), &setting.GetInputReference(), IOWindow::Type::Input);
io.exec();
setting.SimplifyIfPossible();

View File

@ -3,15 +3,10 @@
#pragma once
#include <memory>
#include <vector>
#include <QString>
#include <QWidget>
class ControlGroupBox;
class InputConfig;
class MappingButton;
class MappingNumeric;
class MappingWindow;
class QFormLayout;

View File

@ -41,6 +41,7 @@
#include "DolphinQt/Config/Mapping/HotkeyTAS.h"
#include "DolphinQt/Config/Mapping/HotkeyUSBEmu.h"
#include "DolphinQt/Config/Mapping/HotkeyWii.h"
#include "DolphinQt/Config/Mapping/MappingCommon.h"
#include "DolphinQt/Config/Mapping/WiimoteEmuExtension.h"
#include "DolphinQt/Config/Mapping/WiimoteEmuExtensionMotionInput.h"
#include "DolphinQt/Config/Mapping/WiimoteEmuExtensionMotionSimulation.h"
@ -93,6 +94,8 @@ MappingWindow::MappingWindow(QWidget* parent, Type type, int port_num)
[] { HotkeyManagerEmu::Enable(true); });
filter->connect(filter, &WindowActivationEventFilter::windowActivated,
[] { HotkeyManagerEmu::Enable(false); });
MappingCommon::CreateMappingProcessor(this);
}
void MappingWindow::CreateDevicesLayout()
@ -185,9 +188,8 @@ void MappingWindow::CreateMainLayout()
void MappingWindow::ConnectWidgets()
{
connect(&Settings::Instance(), &Settings::DevicesChanged, this,
&MappingWindow::OnGlobalDevicesChanged);
connect(this, &MappingWindow::ConfigChanged, this, &MappingWindow::OnGlobalDevicesChanged);
connect(&Settings::Instance(), &Settings::DevicesChanged, this, &MappingWindow::ConfigChanged);
connect(this, &MappingWindow::ConfigChanged, this, &MappingWindow::UpdateDeviceList);
connect(m_devices_combo, &QComboBox::currentIndexChanged, this, &MappingWindow::OnSelectDevice);
connect(m_reset_clear, &QPushButton::clicked, this, &MappingWindow::OnClearFieldsPressed);
@ -203,6 +205,8 @@ void MappingWindow::ConnectWidgets()
// We currently use the "Close" button as an "Accept" button so we must save on reject.
connect(this, &QDialog::rejected, [this] { emit Save(); });
connect(m_button_box, &QDialogButtonBox::rejected, this, &QDialog::reject);
connect(m_tab_widget, &QTabWidget::currentChanged, this, &MappingWindow::CancelMapping);
}
void MappingWindow::UpdateProfileIndex()
@ -345,6 +349,8 @@ void MappingWindow::OnSelectDevice(int)
const auto device = m_devices_combo->currentData().toString().toStdString();
m_controller->SetDefaultDevice(device);
emit ConfigChanged();
m_controller->UpdateReferences(g_controller_interface);
}
@ -358,7 +364,7 @@ void MappingWindow::RefreshDevices()
g_controller_interface.RefreshDevices();
}
void MappingWindow::OnGlobalDevicesChanged()
void MappingWindow::UpdateDeviceList()
{
const QSignalBlocker blocker(m_devices_combo);

View File

@ -5,9 +5,6 @@
#include <QDialog>
#include <QString>
#include <memory>
#include "InputCommon/ControllerInterface/CoreDevice.h"
namespace ControllerEmu
{
@ -15,6 +12,8 @@ class EmulatedController;
}
class InputConfig;
class MappingButton;
class QComboBox;
class QDialogButtonBox;
class QEvent;
@ -58,10 +57,14 @@ public:
signals:
// Emitted when config has changed so widgets can update to reflect the change.
void ConfigChanged();
// Emitted at 30hz for real-time indicators to be updated.
// Emitted at INDICATOR_UPDATE_FREQ Hz for real-time indicators to be updated.
void Update();
void Save();
void UnQueueInputDetection(MappingButton*);
void QueueInputDetection(MappingButton*);
void CancelMapping();
private:
void SetMappingType(Type type);
void CreateDevicesLayout();
@ -82,11 +85,11 @@ private:
void UpdateProfileIndex();
void UpdateProfileButtonState();
void PopulateProfileSelection();
void UpdateDeviceList();
void OnDefaultFieldsPressed();
void OnClearFieldsPressed();
void OnSelectDevice(int index);
void OnGlobalDevicesChanged();
ControllerEmu::EmulatedController* m_controller = nullptr;

View File

@ -39,12 +39,6 @@
<string>English</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>${DOLPHIN_WC_DESCRIBE}</string>
<key>CFBundleLongVersionString</key>
<string>${DOLPHIN_WC_REVISION}</string>
<key>CFBundleVersion</key>
<string>${DOLPHIN_VERSION_MAJOR}.${DOLPHIN_VERSION_MINOR}</string>
<key>NSHumanReadableCopyright</key>
<string>Licensed under GPL version 2 or later (GPLv2+)</string>
<key>LSApplicationCategoryType</key>

View File

@ -3,12 +3,34 @@
#include "DolphinQt/QtUtils/BlockUserInputFilter.h"
#include <QEvent>
#include <chrono>
bool BlockUserInputFilter::eventFilter(QObject* object, QEvent* event)
#include <QEvent>
#include <QTimer>
namespace QtUtils
{
const QEvent::Type event_type = event->type();
return event_type == QEvent::KeyPress || event_type == QEvent::KeyRelease ||
event_type == QEvent::MouseButtonPress || event_type == QEvent::MouseButtonRelease ||
event_type == QEvent::MouseButtonDblClick;
// Leave filter active for a bit to prevent Return/Space detection from reactivating the button.
constexpr auto REMOVAL_DELAY = std::chrono::milliseconds{100};
BlockKeyboardInputFilter::BlockKeyboardInputFilter(QObject* parent) : QObject{parent}
{
parent->installEventFilter(this);
}
void BlockKeyboardInputFilter::ScheduleRemoval()
{
auto* const timer = new QTimer(this);
timer->setSingleShot(true);
connect(timer, &QTimer::timeout, [this] { delete this; });
timer->start(REMOVAL_DELAY);
}
bool BlockKeyboardInputFilter::eventFilter(QObject* object, QEvent* event)
{
const auto event_type = event->type();
return event_type == QEvent::KeyPress || event_type == QEvent::KeyRelease;
}
} // namespace QtUtils

View File

@ -5,14 +5,26 @@
#include <QObject>
class QEvent;
namespace QtUtils
{
class BlockUserInputFilter : public QObject
class BlockKeyboardInputFilter : public QObject
{
Q_OBJECT
public:
using QObject::QObject;
BlockKeyboardInputFilter(QObject* parent);
void ScheduleRemoval();
private:
bool eventFilter(QObject* object, QEvent* event) override;
};
template <typename T>
void InstallKeyboardBlocker(QObject* obj, T* removal_signal_object, void (T::*removal_signal)())
{
removal_signal_object->connect(removal_signal_object, removal_signal,
new QtUtils::BlockKeyboardInputFilter{obj},
&QtUtils::BlockKeyboardInputFilter::ScheduleRemoval);
}
} // namespace QtUtils

View File

@ -345,6 +345,20 @@ auto DeviceContainer::DetectInput(const std::vector<std::string>& device_strings
std::chrono::milliseconds confirmation_wait,
std::chrono::milliseconds maximum_wait) const
-> std::vector<InputDetection>
{
InputDetector input_detector;
input_detector.Start(*this, device_strings);
while (!input_detector.IsComplete())
{
Common::SleepCurrentThread(10);
input_detector.Update(initial_wait, confirmation_wait, maximum_wait);
}
return input_detector.TakeResults();
}
struct InputDetector::Impl
{
struct InputState
{
@ -355,7 +369,7 @@ auto DeviceContainer::DetectInput(const std::vector<std::string>& device_strings
ControlState last_state = initial_state;
MathUtil::RunningVariance<ControlState> stats;
// Prevent multiiple detections until after release.
// Prevent multiple detections until after release.
bool is_ready = true;
void Update()
@ -392,18 +406,32 @@ auto DeviceContainer::DetectInput(const std::vector<std::string>& device_strings
std::vector<InputState> input_states;
};
// Acquire devices and initial input states.
std::vector<DeviceState> device_states;
};
InputDetector::InputDetector() : m_start_time{}, m_state{}
{
}
void InputDetector::Start(const DeviceContainer& container,
const std::vector<std::string>& device_strings)
{
m_start_time = Clock::now();
m_detections = {};
m_state = std::make_unique<Impl>();
// Acquire devices and initial input states.
for (const auto& device_string : device_strings)
{
DeviceQualifier dq;
dq.FromString(device_string);
auto device = FindDevice(dq);
auto device = container.FindDevice(dq);
if (!device)
continue;
std::vector<InputState> input_states;
std::vector<Impl::InputState> input_states;
for (auto* input : device->Inputs())
{
@ -413,38 +441,42 @@ auto DeviceContainer::DetectInput(const std::vector<std::string>& device_strings
// Undesirable axes will have negative values here when trying to map a
// "FullAnalogSurface".
input_states.push_back(InputState{input});
input_states.push_back(Impl::InputState{input});
}
if (!input_states.empty())
device_states.emplace_back(DeviceState{std::move(device), std::move(input_states)});
{
m_state->device_states.emplace_back(
Impl::DeviceState{std::move(device), std::move(input_states)});
}
}
if (device_states.empty())
return {};
// If no inputs were found via the supplied device strings, immediately complete.
if (m_state->device_states.empty())
m_state.reset();
}
std::vector<InputDetection> detections;
const auto start_time = Clock::now();
while (true)
void InputDetector::Update(std::chrono::milliseconds initial_wait,
std::chrono::milliseconds confirmation_wait,
std::chrono::milliseconds maximum_wait)
{
if (m_state)
{
const auto now = Clock::now();
const auto elapsed_time = now - start_time;
const auto elapsed_time = now - m_start_time;
if (elapsed_time >= maximum_wait || (detections.empty() && elapsed_time >= initial_wait) ||
(!detections.empty() && detections.back().release_time.has_value() &&
now >= *detections.back().release_time + confirmation_wait))
if (elapsed_time >= maximum_wait || (m_detections.empty() && elapsed_time >= initial_wait) ||
(!m_detections.empty() && m_detections.back().release_time.has_value() &&
now >= *m_detections.back().release_time + confirmation_wait))
{
break;
m_state.reset();
return;
}
Common::SleepCurrentThread(10);
for (auto& device_state : device_states)
for (auto& device_state : m_state->device_states)
{
for (std::size_t i = 0; i != device_state.input_states.size(); ++i)
for (auto& input_state : device_state.input_states)
{
auto& input_state = device_state.input_states[i];
input_state.Update();
if (input_state.IsPressed())
@ -456,26 +488,42 @@ auto DeviceContainer::DetectInput(const std::vector<std::string>& device_strings
const auto smoothness =
1 / std::sqrt(input_state.stats.Variance() / input_state.stats.Mean());
InputDetection new_detection;
Detection new_detection;
new_detection.device = device_state.device;
new_detection.input = input_state.input;
new_detection.press_time = Clock::now();
new_detection.smoothness = smoothness;
// We found an input. Add it to our detections.
detections.emplace_back(std::move(new_detection));
m_detections.emplace_back(std::move(new_detection));
}
}
}
// Check for any releases of our detected inputs.
for (auto& d : detections)
for (auto& d : m_detections)
{
if (!d.release_time.has_value() && d.input->GetState() < (1 - INPUT_DETECT_THRESHOLD))
d.release_time = Clock::now();
}
}
return detections;
}
InputDetector::~InputDetector() = default;
bool InputDetector::IsComplete() const
{
return !m_state;
}
auto InputDetector::GetResults() const -> const std::vector<Detection>&
{
return m_detections;
}
auto InputDetector::TakeResults() -> std::vector<Detection>
{
return std::move(m_detections);
}
} // namespace ciface::Core

View File

@ -245,5 +245,32 @@ protected:
mutable std::recursive_mutex m_devices_mutex;
std::vector<std::shared_ptr<Device>> m_devices;
};
class InputDetector
{
public:
using Detection = DeviceContainer::InputDetection;
InputDetector();
~InputDetector();
void Start(const DeviceContainer& container, const std::vector<std::string>& device_strings);
void Update(std::chrono::milliseconds initial_wait, std::chrono::milliseconds confirmation_wait,
std::chrono::milliseconds maximum_wait);
bool IsComplete() const;
const std::vector<Detection>& GetResults() const;
// move-return'd to prevent copying.
std::vector<Detection> TakeResults();
private:
struct Impl;
Clock::time_point m_start_time;
std::vector<Detection> m_detections;
std::unique_ptr<Impl> m_state;
};
} // namespace Core
} // namespace ciface

View File

@ -16,7 +16,7 @@ add_dependencies(MacUpdater dolphin_scmrev)
set_target_properties(MacUpdater PROPERTIES
MACOSX_BUNDLE true
MACOSX_BUNDLE_INFO_PLIST "${CMAKE_CURRENT_BINARY_DIR}/Info.plist"
MACOSX_BUNDLE_INFO_PLIST "${CMAKE_CURRENT_SOURCE_DIR}/Info.plist.in"
OUTPUT_NAME "Dolphin Updater")
target_compile_options(MacUpdater PRIVATE -x objective-c++)
@ -53,6 +53,9 @@ foreach(sb ${STORYBOARDS})
COMMENT "Compiling Storyboard ${sb}...")
endforeach()
include(DolphinInjectVersionInfo)
dolphin_inject_version_info(MacUpdater)
if(NOT SKIP_POSTPROCESS_BUNDLE)
# Update library references to make the bundle portable
include(DolphinPostprocessBundle)

View File

@ -16,10 +16,6 @@
<string>Dolphin Updater</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>${DOLPHIN_WC_DESCRIBE}</string>
<key>CFBundleVersion</key>
<string>${DOLPHIN_VERSION_MAJOR}.${DOLPHIN_VERSION_MINOR}</string>
<key>LSMinimumSystemVersion</key>
<string>${CMAKE_OSX_DEPLOYMENT_TARGET}</string>
<key>NSHumanReadableCopyright</key>

View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<!-- This plist file is merged with the application's Info.plist to set its version info. -->
<key>CFBundleShortVersionString</key>
<string>${DOLPHIN_WC_DESCRIBE}</string>
<key>CFBundleLongVersionString</key>
<string>${DOLPHIN_WC_REVISION}</string>
<key>CFBundleVersion</key>
<string>${DOLPHIN_VERSION_MAJOR}.${DOLPHIN_VERSION_MINOR}</string>
</dict>
</plist>

View File

@ -259,6 +259,10 @@ void VideoBackend::Shutdown()
void VideoBackend::PrepareWindow(WindowSystemInfo& wsi)
{
#if defined(VK_USE_PLATFORM_METAL_EXT)
// We only need to manually create the CAMetalLayer on macOS.
if (wsi.type != WindowSystemType::MacOS)
return;
// This is kinda messy, but it avoids having to write Objective C++ just to create a metal layer.
id view = reinterpret_cast<id>(wsi.render_surface);
Class clsCAMetalLayer = objc_getClass("CAMetalLayer");

View File

@ -77,11 +77,6 @@ std::pair<std::string, bool> GetNameArbPair(const TextureInfo& texture_info)
}
} // namespace
void HiresTexture::Init()
{
Update();
}
void HiresTexture::Shutdown()
{
Clear();

View File

@ -22,7 +22,6 @@ std::set<std::string> GetTextureDirectoriesWithGameId(const std::string& root_di
class HiresTexture
{
public:
static void Init();
static void Update();
static void Clear();
static void Shutdown();

View File

@ -137,43 +137,40 @@ void PostProcessingConfiguration::LoadOptions(const std::string& code)
std::vector<GLSLStringOption> option_strings;
GLSLStringOption* current_strings = nullptr;
while (!in.eof())
std::string line_str;
while (std::getline(in, line_str))
{
std::string line_str;
if (std::getline(in, line_str))
{
std::string_view line = line_str;
std::string_view line = line_str;
#ifndef _WIN32
// Check for CRLF eol and convert it to LF
if (!line.empty() && line.at(line.size() - 1) == '\r')
line.remove_suffix(1);
// Check for CRLF eol and convert it to LF
if (!line.empty() && line.at(line.size() - 1) == '\r')
line.remove_suffix(1);
#endif
if (!line.empty())
if (!line.empty())
{
if (line[0] == '[')
{
if (line[0] == '[')
{
size_t endpos = line.find("]");
size_t endpos = line.find("]");
if (endpos != std::string::npos)
{
// New section!
std::string_view sub = line.substr(1, endpos - 1);
option_strings.push_back({std::string(sub)});
current_strings = &option_strings.back();
}
if (endpos != std::string::npos)
{
// New section!
std::string_view sub = line.substr(1, endpos - 1);
option_strings.push_back({std::string(sub)});
current_strings = &option_strings.back();
}
else
}
else
{
if (current_strings)
{
if (current_strings)
{
std::string key, value;
Common::IniFile::ParseLine(line, &key, &value);
std::string key, value;
Common::IniFile::ParseLine(line, &key, &value);
if (!(key.empty() && value.empty()))
current_strings->m_options.emplace_back(key, value);
}
if (!(key.empty() && value.empty()))
current_strings->m_options.emplace_back(key, value);
}
}
}

View File

@ -99,8 +99,6 @@ TextureCacheBase::TextureCacheBase()
TexDecoder_SetTexFmtOverlayOptions(m_backup_config.texfmt_overlay,
m_backup_config.texfmt_overlay_center);
HiresTexture::Init();
TMEM::InvalidateAll();
}

View File

@ -24,7 +24,7 @@ protected:
}
}
void SetUp()
void SetUp() override
{
if (m_parent_directory.empty())
{

View File

@ -44,7 +44,7 @@ TEST(TMDReader, Validity)
class TMDReaderTest : public ::testing::Test
{
protected:
virtual void SetUp() = 0;
void SetUp() override = 0;
// Common tests.
void TestGeneralInfo();

View File

@ -41,7 +41,7 @@ protected:
m_fs.reset();
File::DeleteDirRecursively(m_profile_path);
}
void SetUp()
void SetUp() override
{
if (UserDirectoryCreationFailed())
{