Compare commits

...

45 Commits

Author SHA1 Message Date
PabloMK7
864414f8fd
Update README.md 2024-10-29 10:10:00 +01:00
kongfl888 K
cdce02607a
FFmpeg: adapt to ffmpeg7 for dumping. (#248)
* FFmpeg: update layout api for ffmpeg 6.x and ffmpeg 7.

After ffmpeg 5.1, it is deprecated, and has been completely removed in ffmpeg 7.

Our current project is using 6.0 headers, modifying it will not have any side effects.

Since lavu 57.24.100(ffmpeg 5.1)

* FFmpeg: dynamic_library and dumping adapt to ffmpeg7.1.

But keeping support for older versions.

* externals/library-headers: update ffmpeg to 7.1
2024-10-12 13:38:58 +02:00
PabloMK7
7d00f47c5e
Make plugins only load for apps and not applets (#265) 2024-09-28 13:26:13 +02:00
PabloMK7
608383ec84
Match changes to svcMapProcessMemoryEx from latest Luma3DS (#264) 2024-09-28 01:00:28 +02:00
David Griswold
f3fb038587
Reenable dpad axes option (#262)
* Reenable dpad axes option

* Update Header Setting to allow for a description under tht title

* Add header descriptions for dpad axis and buttons
2024-09-27 23:50:36 +02:00
Reg Tiangha
a652fb1697
citra_qt: Build fixes for QT 6.8 (#258)
Replace deprecated `stateChanged` function with `checkStateChanged` that was first introduced in QT 6.7 but keep the old code to maintain compatibility with older versions of QT.
2024-09-23 11:07:20 +00:00
kleidis
a9775d486e
android: Make Asynchronous shader compilation non runtime editable (#260)
I'm sure this was an oversight by the original devs and not intended to actually be runtime editable guessing by Qt's behavior

See https://github.com/Lime3DS/Lime3DS/issues/442
2024-09-23 11:06:08 +00:00
Reg Tiangha
0ec72a122d
Update citra(6) man page (#257) 2024-09-15 19:29:35 +02:00
Wunk
3e5bbac5a1
shader_jit_a64: Compact host executable memory (#230)
* common/aarch64: Allow generic code generator types

Use the templated `BasicCodeGenerator` type rather than the specialized
`CodeGenerator` type.
Allows `VectorCodeGenerator` to work with these functions.

* common/aarch64: Add `VectorCodeGenerator` to `CallFarFunction`

`VectorCodeGenerator` will always do far-calls since we cannot resolve any absolute addresses here.

* shader_jit_a64: Implement position-independent VectorCodeGenerator

Generates more position-independent assembly to allow for code to be
generated within a resizable vector before copying into executable
memory, allowing for more compact memory allocations and usage rather
than a statically defined worst-case for all-cases.

`VectorCodeGenerator` will need to generate position-independent code
rather than use absolute addresses. Assumes all far function calls in the
case of `VectorCodeGenerator` to use absolute addresses rather than
potentially use a relative `BL` branch after memory relocation.
2024-09-01 12:24:13 +02:00
Reg Tiangha
82faf2e557
Android: Add Pedometer Steps per Hour settings UI (#242) 2024-09-01 12:23:53 +02:00
Jugurta
2f0b2f9839
Refactor Vulkan stream buffer memory type selection (#238)
* Refactor Vulkan stream buffer memory type selection

This is a fix for GPUs with Vulkan V1.1 and V1.2

* add requested refactoring

* clang format

* fix typo
2024-08-25 10:27:04 +02:00
kleidis
9c1f778e79
android: Fix pause emulator button crashing the emulator (#239) 2024-08-22 23:33:05 +02:00
Daniel López Guimaraes
5115f640c2
act: Add more command names and implement GetErrorCode (#237)
* act: Add more command names and implement GetErrorCode

The command names have been extracted from 3dbrew and the JavaScript
bindings that the NNID settings uses internally.

The error names have been extracted from the Wii U implementation
(102-XXXX), which are compatible with the 3DS ones except for 022-5XXX,
which are error codes specific to the 3DS.

* act: Split error codes into separate file
2024-08-21 20:01:05 +02:00
kleidis
d8760dee6a
qt: Fix default language not being set to the main one the system uses (#236)
* qt: Fix default language not being set to the main one the system uses

* cleanup code
2024-08-21 19:59:07 +02:00
szdarkhack
e1ff3b8dbe
Add "Steps per hour" system setting (#211)
* Implements a steps per hour (global) setting that gets returned by PTM GetStepHistory

* Make setting label text clearer

* Add setting to SDL frontend

* Add setting to Android (no UI)

* Remove IntSetting enum value

* Follow convension in android default ini
2024-08-20 21:04:29 +02:00
PabloMK7
8538a57be0
Update submodules and build environments (#232)
* Update Vulkan and QT submodules

* Undefine FORTIFY_SOURCE before defining again

* Update discord-rpc submodule
2024-08-20 16:44:33 +02:00
David Griswold
8656655bb1
Use buttons instead of axes for the d-pad, enabling mapping from gamepads with only digital dpad outputs (#203) 2024-08-20 10:34:40 +02:00
kongfl888 K
e3b156bd96
Android: add the titleId to the game card. (#227)
* Android: replace company in the game card with titleId.

TitleId is more useful for users than companies
because it can help them find game saves and the cheat file.

* Android: restore the company name on the game card.
2024-08-20 10:20:52 +02:00
Wunk
52f06f757f
shader_jit: Fix/optimize conditional evaluation (#234)
* shader_jit: Add conditional unit-tests

Tests all permutations of X, Y, AND, OR with each possible input value.

* video_core: Fix shader-interpreter conditional-code initialization

Rather than reserving the incoming state of the conditional codes, the
shader-interpreter was setting them both to false. In pretty much all
cases, the initial state of a shaderunit can be zero-initialized
statically. Just running the interpreter shouldn't necessarily reset the
conditional codes though.  The JIT loads incoming conditional codes
while the shader-interpreter resets them to false. This makes the
interpreter match the behavior of the shader-jit.

* shader_jit_a64: Fix/optimize conditional evaluation

Fix some of the regressions introduced by the previous optimization.
EOR does not support a constant of `0` in its immediate. In these cases
the COND{0,1} registers can be utilized immediately.

* shader_jit_x64: Fix conditional evaluation extended-bit hazard

The unit test seems to have identified a bug in the x64 jit too. The x64
jit was doing 32-bit comparisons despite the condition flags being 8-bit
values and is sensitive to garbage being in the upper 24 bits of the
register. This is fixed by using the proper 8-bit register types rather
than the 32-bit ones(`eax,`ebx` -> `al`, `bl`).

* shader_jit_x64: Zero-extend conditional-code bytes

`mov` was doing a partial update of bits within the register, allowing
garbage to be introduced in the upper bits of the register.
2024-08-20 10:19:04 +02:00
Wunk
f248fefe06
shader_jit_a64: Optimize conditional tests (#229)
These conditional tests are a 1:1 translation from the x64 code but do
not have to be. Reference-values are known at emit-time and can be
embedded as an immediate into an `EOR` instruction rather than moved
into a register. The `TST` instruction can be utilized to more optimally
test and update the `EQ`/`NE` status flags.
2024-08-15 10:45:20 +02:00
Miguel
e55e619328
bundle qt wayland into appimage by rtiangha (#212) 2024-07-25 13:42:19 +02:00
PabloMK7
d017955946
pica: Move mutex lock below tracing check on register write (#210)
Co-authored-by: IndecisiveTurtle <47210458+raphaelthegreat@users.noreply.github.com>
2024-07-21 14:07:48 +02:00
PabloMK7
63450b662e
Add instant log flush option (#209) 2024-07-21 12:36:00 +02:00
PabloMK7
518f7234f7
Artic Base: Fix issue when 0 bytes are read from file (#199) 2024-07-17 14:37:55 +02:00
PabloMK7
959a66d839
renderer_vulkan: Address vulkan surface recreation issues (#198) 2024-07-16 23:54:02 +02:00
PabloMK7
55748d7d1a
Artic Base: Add Artic Controller support (#195) 2024-07-16 22:00:21 +02:00
PabloMK7
9de19ff7a1
Revert "android: implement device rotation options (#182)" (#194)
This reverts commit 93025c95f2ce4baed37a7b587236f12ef667c540.
2024-07-16 12:50:12 +02:00
Felix Nüsse
eafb03ad38
allow remapping of unknown keycodes (#189) 2024-07-15 19:16:03 +02:00
Felix Nüsse
93025c95f2
android: implement device rotation options (#182)
* android: implement device rotation options

* fix naming

* move orientation-setting to different section
2024-07-14 00:54:57 +02:00
Felix Nüsse
45f52709a6
android: add quicksave hotkeys (#181) 2024-07-14 00:54:19 +02:00
PabloMK7
ed3d5a9f7f renderer_vulkan: Wait for window to present before destroying instance 2024-07-14 00:53:06 +02:00
PabloMK7
c697aaf656 renderer_vulkan: Reduce amount of wait semaphores 2024-07-14 00:53:06 +02:00
PabloMK7
40851682ce
Workaround a performance bug in older Mali GPUs (#185) 2024-07-13 23:47:11 +02:00
PabloMK7
e90795b616
Implement game render thread delay (#180)
More details: https://www.reddit.com/r/Citra/comments/1e1v4e1/fixing_luigis_mansion_2_performance_issues_once/
2024-07-13 00:57:03 +02:00
PabloMK7
cc220928bd
Fixup logic for GSP_GPU::TriggerCmdReqQueue (#177) 2024-07-12 13:22:31 +02:00
PabloMK7
4780a7134d
Artic Base: Implement DLC support and other fixes (#173)
* Artic Base: Implement DLC support and other fixes

* Fix per game settings not working with artic loader

* Fix compilation error
2024-07-09 23:35:29 +02:00
PabloMK7
1e2be72e5e
Artic Base: Fix fallback read/write not being chunked (#171) 2024-07-07 19:10:47 +02:00
PabloMK7
9b39b43e90
Update dynarmic to the latest version. (#170) 2024-07-07 15:10:35 +02:00
Wunk
d2bd58287c
shader_jit/tests: Test both the shader interpreter and jit (#72)
* shader_jit/tests: Test both the shader interpreter and jit

Uses Catch2's `TEMPLATE_TEST_CASE`-feature to test both the JIT and the
interpreter.

* shader_jit/tests: Use generator-expressions for nested-loop test cases

Tests more permutations of inputs than just the two it had before

* shader/tests: Refactor `shader_jit` tests to just `shader` tests

Since these tests will test both the interpreter and the jit, they are no longer jit-specific tests and are more general shader-tests.

* shaders/tests: Disable Nested-Loop `ShaderInterpreterTest` test

Restoring loop-state on nested loops is bugged on the ShaderInterpreter.

* shader/tests: Fix Nested Loop generator expressions
2024-07-04 08:42:31 +02:00
wheremyfoodat
f782903587
Y2R: Set is_busy_conversion to false when stopping conversion (#165) 2024-07-04 08:41:34 +02:00
PabloMK7
4f174f1c0b
Fix crash when cubemap face id is invalid (#154)
* Fix crash when cubemap face id is invalid

* make variable const
2024-06-15 20:49:08 +02:00
kongfl888 K
09dc3a5592
Fix gcc 13+ compilation and update fmt. (#142)
* Soc and artic_bass: gcc 13+ compatibility fix.

* externals/fmt: update to HEAD fcd3e1e19.
It will fix error.
  integer_sequence<bool, (Is == Is)...>  [-Werror=tautological-compare]
The updating is helpful and needed.
Fmt has gone through two public versions since its last update
and has fixed many bugs, including new compiler optimizations.
But neither of these two public versions can fix the errors encountered above.
We need to switch to a working version.
It can be fixed after fmt/8e62172.There are still many optimizations,
Such as this one:
  Std. h c++23 build fix (# 3856)
And these:
  C++23 compatibility: basicstring_view cannot be constructed from nullptr (# 3846)
  Fix warning C4702 emitted from format.h (MSVC) (#3866)
Of course, there are other functional improvements as well.
Very helpful.
The selected version is the one that has been checked and works well.

And synchronously updating local code.

* citra_qt/ui: clean up duplicate naming warnings.
2024-06-14 13:23:07 +02:00
PabloMK7
de1f082e75
Fix HTTP RequestState values (fixes Pretendo Network support with HLE http) (#143)
* Fix http RequestState values

* Fix formatting
2024-06-01 23:26:37 +02:00
RocketRobz
e15d4c0d4a
Run screenshot capture function in paused state by capturing next frame (#116)
Some checks failed
citra-transifex / transifex (push) Has been skipped
citra-build / source (push) Failing after 2m21s
citra-build / linux (appimage) (push) Failing after 7s
citra-build / linux (fresh) (push) Failing after 5s
citra-build / android (push) Failing after 6m33s
citra-format / clang-format (push) Failing after 4s
citra-build / macos (arm64) (push) Has been cancelled
citra-build / macos (x86_64) (push) Has been cancelled
citra-build / macos-universal (push) Has been cancelled
citra-build / windows (msvc) (push) Has been cancelled
citra-build / windows (msys2) (push) Has been cancelled
citra-build / ios (push) Has been cancelled
citra-build / release (push) Has been cancelled
* Allow screenshot capture in paused state by unpausing to capture next frame

* Change `QMessageBox::No` to `QMessageBox::Yes`

* Fix formatting

* Fix formatting
2024-05-23 20:21:54 +02:00
Reg Tiangha
54aee70e68
citra_qt: Add scroll bar to System tab (#135) 2024-05-23 20:21:27 +02:00
133 changed files with 5107 additions and 1457 deletions

View File

@ -3,6 +3,9 @@
if [ "$TARGET" = "appimage" ]; then if [ "$TARGET" = "appimage" ]; then
# Compile the AppImage we distribute with Clang. # Compile the AppImage we distribute with Clang.
export EXTRA_CMAKE_FLAGS=(-DCMAKE_CXX_COMPILER=clang++ -DCMAKE_C_COMPILER=clang -DCMAKE_LINKER=/etc/bin/ld.lld) export EXTRA_CMAKE_FLAGS=(-DCMAKE_CXX_COMPILER=clang++ -DCMAKE_C_COMPILER=clang -DCMAKE_LINKER=/etc/bin/ld.lld)
# Bundle required QT wayland libraries
export EXTRA_QT_PLUGINS="waylandcompositor"
export EXTRA_PLATFORM_PLUGINS="libqwayland-egl.so;libqwayland-generic.so"
else else
# For the linux-fresh verification target, verify compilation without PCH as well. # For the linux-fresh verification target, verify compilation without PCH as well.
export EXTRA_CMAKE_FLAGS=(-DCITRA_USE_PRECOMPILED_HEADERS=OFF) export EXTRA_CMAKE_FLAGS=(-DCITRA_USE_PRECOMPILED_HEADERS=OFF)

View File

@ -235,7 +235,7 @@ find_package(Threads REQUIRED)
if (ENABLE_QT) if (ENABLE_QT)
if (NOT USE_SYSTEM_QT) if (NOT USE_SYSTEM_QT)
download_qt(6.6.0) download_qt(6.7.2)
endif() endif()
find_package(Qt6 REQUIRED COMPONENTS Widgets Multimedia Concurrent) find_package(Qt6 REQUIRED COMPONENTS Widgets Multimedia Concurrent)

View File

@ -1 +1,3 @@
ñ # Citra "PabloMK7 fork"
This fork has stopped development. A new more serious Citra continuation project will soon follow.

16
dist/citra.6 vendored
View File

@ -1,4 +1,4 @@
.Dd November 22 2016 .Dd September 13 2024
.Dt citra 6 .Dt citra 6
.Os .Os
.Sh NAME .Sh NAME
@ -12,6 +12,20 @@
.Bl -tag -width Ds .Bl -tag -width Ds
.It Fl g Ar port , Fl Fl gdbport Ar port .It Fl g Ar port , Fl Fl gdbport Ar port
Starts the GDB stub on the specified port Starts the GDB stub on the specified port
.It Fl i , Fl Fl install Ar FILE
Installs a specified CIA file
.It Fl m , Fl Fl multiplayer Ar nick:password@address:port
Nickname, password, address and port for multiplayer
.It Fl r , Fl Fl movie-record Ar file
Record a movie (game inputs) to the given file
.It Fl a , Fl Fl movie-record-author Ar AUTHOR
Sets the author of the movie to be recorded
.It Fl p , Fl Fl movie-play Ar file
Playback the movie (game inputs) from the given file
.It Fl d , Fl Fl dump-video Ar file
Dumps audio and video to the given video file
.It Fl f , Fl Fl fullscreen
Start in fullscreen mode
.It Fl h , Fl Fl help .It Fl h , Fl Fl help
Shows syntax help and exits Shows syntax help and exits
.It Fl v , Fl Fl version .It Fl v , Fl Fl version

@ -1 +1 @@
Subproject commit 20cc99aeffa08a4834f156b6ab49ed68618cf94a Subproject commit 54eb03c20351f94850bfca3955cb87465a860ef6

2
externals/dynarmic vendored

@ -1 +1 @@
Subproject commit 30f1a3c6289075ef4af08f5ec502be2fc8627a0c Subproject commit a41c380246d3d9f9874f0f792d234dc0cc17c180

2
externals/fmt vendored

@ -1 +1 @@
Subproject commit 2dd4fa8742fdac36468f8d8ea3e06e78215551f8 Subproject commit fcd3e1e19c8d2df94bb6cb40d7f1c97a9872cf2b

@ -1 +1 @@
Subproject commit 3b3e28dbe6d033395ce2967fa8030825e7b89de7 Subproject commit 9fb99108ee5c630ca365c8fa4a11087b84e1f58b

2
externals/vma vendored

@ -1 +1 @@
Subproject commit 0e89587db3ebee4d463f191bd296374c5fafc8ea Subproject commit 009ecd192c1289c7529bff248a16cfe896254816

@ -1 +1 @@
Subproject commit 5a5c9a643484d888873e32c5d7d484fae8e71d3d Subproject commit 595c8d4794410a4e64b98dc58d27c0310d7ea2fd

View File

@ -117,7 +117,7 @@ else()
if (NOT CMAKE_BUILD_TYPE STREQUAL Debug) if (NOT CMAKE_BUILD_TYPE STREQUAL Debug)
# _FORTIFY_SOURCE can't be used without optimizations. # _FORTIFY_SOURCE can't be used without optimizations.
add_compile_options(-Wp,-D_FORTIFY_SOURCE=2) add_compile_options(-U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=2)
endif() endif()
if (CITRA_WARNINGS_AS_ERRORS) if (CITRA_WARNINGS_AS_ERRORS)

View File

@ -29,7 +29,7 @@ android {
namespace = "org.citra.citra_emu" namespace = "org.citra.citra_emu"
compileSdkVersion = "android-34" compileSdkVersion = "android-34"
ndkVersion = "26.1.10909125" ndkVersion = "26.3.11579264"
compileOptions { compileOptions {
sourceCompatibility = JavaVersion.VERSION_17 sourceCompatibility = JavaVersion.VERSION_17

View File

@ -527,12 +527,28 @@ object NativeLibrary {
external fun removeAmiibo() external fun removeAmiibo()
const val SAVESTATE_SLOT_COUNT = 10 const val SAVESTATE_SLOT_COUNT = 11
const val QUICKSAVE_SLOT = 0
external fun getSavestateInfo(): Array<SaveStateInfo>? external fun getSavestateInfo(): Array<SaveStateInfo>?
external fun saveState(slot: Int) external fun saveState(slot: Int)
fun loadStateIfAvailable(slot: Int): Boolean {
var available = false
getSavestateInfo()?.forEach {
if (it.slot == slot){
available = true
return@forEach
}
}
if (available) {
loadState(slot)
return true
}
return false
}
external fun loadState(slot: Int) external fun loadState(slot: Int)
/** /**

View File

@ -38,7 +38,6 @@ import org.citra.citra_emu.features.settings.model.view.InputBindingSetting
import org.citra.citra_emu.fragments.MessageDialogFragment import org.citra.citra_emu.fragments.MessageDialogFragment
import org.citra.citra_emu.utils.ControllerMappingHelper import org.citra.citra_emu.utils.ControllerMappingHelper
import org.citra.citra_emu.utils.FileBrowserHelper import org.citra.citra_emu.utils.FileBrowserHelper
import org.citra.citra_emu.utils.ForegroundService
import org.citra.citra_emu.utils.EmulationLifecycleUtil import org.citra.citra_emu.utils.EmulationLifecycleUtil
import org.citra.citra_emu.utils.EmulationMenuSettings import org.citra.citra_emu.utils.EmulationMenuSettings
import org.citra.citra_emu.utils.ThemeUtil import org.citra.citra_emu.utils.ThemeUtil
@ -47,7 +46,6 @@ import org.citra.citra_emu.viewmodel.EmulationViewModel
class EmulationActivity : AppCompatActivity() { class EmulationActivity : AppCompatActivity() {
private val preferences: SharedPreferences private val preferences: SharedPreferences
get() = PreferenceManager.getDefaultSharedPreferences(CitraApplication.appContext) get() = PreferenceManager.getDefaultSharedPreferences(CitraApplication.appContext)
private var foregroundService: Intent? = null
var isActivityRecreated = false var isActivityRecreated = false
private val settingsViewModel: SettingsViewModel by viewModels() private val settingsViewModel: SettingsViewModel by viewModels()
@ -66,7 +64,7 @@ class EmulationActivity : AppCompatActivity() {
binding = ActivityEmulationBinding.inflate(layoutInflater) binding = ActivityEmulationBinding.inflate(layoutInflater)
screenAdjustmentUtil = ScreenAdjustmentUtil(windowManager, settingsViewModel.settings) screenAdjustmentUtil = ScreenAdjustmentUtil(windowManager, settingsViewModel.settings)
hotkeyUtility = HotkeyUtility(screenAdjustmentUtil) hotkeyUtility = HotkeyUtility(screenAdjustmentUtil, this)
setContentView(binding.root) setContentView(binding.root)
val navHostFragment = val navHostFragment =
@ -85,10 +83,6 @@ class EmulationActivity : AppCompatActivity() {
windowManager.defaultDisplay.rotation windowManager.defaultDisplay.rotation
) )
// Start a foreground service to prevent the app from getting killed in the background
foregroundService = Intent(this, ForegroundService::class.java)
startForegroundService(foregroundService)
EmulationLifecycleUtil.addShutdownHook(hook = { this.finish() }) EmulationLifecycleUtil.addShutdownHook(hook = { this.finish() })
} }
@ -112,7 +106,6 @@ class EmulationActivity : AppCompatActivity() {
override fun onDestroy() { override fun onDestroy() {
EmulationLifecycleUtil.clear() EmulationLifecycleUtil.clear()
stopForegroundService(this)
super.onDestroy() super.onDestroy()
} }
@ -186,8 +179,7 @@ class EmulationActivity : AppCompatActivity() {
return false return false
} }
val button = val button = preferences.getInt(InputBindingSetting.getInputButtonKey(event), event.scanCode)
preferences.getInt(InputBindingSetting.getInputButtonKey(event.keyCode), event.keyCode)
val action: Int = when (event.action) { val action: Int = when (event.action) {
KeyEvent.ACTION_DOWN -> { KeyEvent.ACTION_DOWN -> {
// On some devices, the back gesture / button press is not intercepted by androidx // On some devices, the back gesture / button press is not intercepted by androidx
@ -453,12 +445,4 @@ class EmulationActivity : AppCompatActivity() {
OnFilePickerResult(result.toString()) OnFilePickerResult(result.toString())
} }
companion object {
fun stopForegroundService(activity: Activity) {
val startIntent = Intent(activity, ForegroundService::class.java)
startIntent.action = ForegroundService.ACTION_STOP
activity.startForegroundService(startIntent)
}
}
} }

View File

@ -153,9 +153,15 @@ class GameAdapter(private val activity: AppCompatActivity) :
} else { } else {
View.VISIBLE View.VISIBLE
} }
binding.textGameId.visibility = if (game.titleId == 0L) {
View.GONE
} else {
View.VISIBLE
}
binding.textGameTitle.text = game.title binding.textGameTitle.text = game.title
binding.textCompany.text = game.company binding.textCompany.text = game.company
binding.textGameId.text = String.format("ID: %016X", game.titleId)
binding.textFilename.text = game.filename binding.textFilename.text = game.filename
val backgroundColorId = val backgroundColorId =
@ -181,6 +187,9 @@ class GameAdapter(private val activity: AppCompatActivity) :
binding.textCompany.ellipsize = TextUtils.TruncateAt.MARQUEE binding.textCompany.ellipsize = TextUtils.TruncateAt.MARQUEE
binding.textCompany.isSelected = true binding.textCompany.isSelected = true
binding.textGameId.ellipsize = TextUtils.TruncateAt.MARQUEE
binding.textGameId.isSelected = true
binding.textFilename.ellipsize = TextUtils.TruncateAt.MARQUEE binding.textFilename.ellipsize = TextUtils.TruncateAt.MARQUEE
binding.textFilename.isSelected = true binding.textFilename.isSelected = true
}, },

View File

@ -8,5 +8,7 @@ enum class Hotkey(val button: Int) {
SWAP_SCREEN(10001), SWAP_SCREEN(10001),
CYCLE_LAYOUT(10002), CYCLE_LAYOUT(10002),
CLOSE_GAME(10003), CLOSE_GAME(10003),
PAUSE_OR_RESUME(10004); PAUSE_OR_RESUME(10004),
QUICKSAVE(10005),
QUICKLOAD(10006);
} }

View File

@ -4,10 +4,14 @@
package org.citra.citra_emu.features.hotkeys package org.citra.citra_emu.features.hotkeys
import android.content.Context
import android.widget.Toast
import org.citra.citra_emu.NativeLibrary
import org.citra.citra_emu.R
import org.citra.citra_emu.utils.EmulationLifecycleUtil import org.citra.citra_emu.utils.EmulationLifecycleUtil
import org.citra.citra_emu.display.ScreenAdjustmentUtil import org.citra.citra_emu.display.ScreenAdjustmentUtil
class HotkeyUtility(private val screenAdjustmentUtil: ScreenAdjustmentUtil) { class HotkeyUtility(private val screenAdjustmentUtil: ScreenAdjustmentUtil, private val context: Context) {
val hotkeyButtons = Hotkey.entries.map { it.button } val hotkeyButtons = Hotkey.entries.map { it.button }
@ -18,6 +22,23 @@ class HotkeyUtility(private val screenAdjustmentUtil: ScreenAdjustmentUtil) {
Hotkey.CYCLE_LAYOUT.button -> screenAdjustmentUtil.cycleLayouts() Hotkey.CYCLE_LAYOUT.button -> screenAdjustmentUtil.cycleLayouts()
Hotkey.CLOSE_GAME.button -> EmulationLifecycleUtil.closeGame() Hotkey.CLOSE_GAME.button -> EmulationLifecycleUtil.closeGame()
Hotkey.PAUSE_OR_RESUME.button -> EmulationLifecycleUtil.pauseOrResume() Hotkey.PAUSE_OR_RESUME.button -> EmulationLifecycleUtil.pauseOrResume()
Hotkey.QUICKSAVE.button -> {
NativeLibrary.saveState(NativeLibrary.QUICKSAVE_SLOT)
Toast.makeText(context,
context.getString(R.string.quicksave_saving),
Toast.LENGTH_SHORT).show()
}
Hotkey.QUICKLOAD.button -> {
val wasLoaded = NativeLibrary.loadStateIfAvailable(NativeLibrary.QUICKSAVE_SLOT)
val stringRes = if(wasLoaded) {
R.string.quickload_loading
} else {
R.string.quickload_not_found
}
Toast.makeText(context,
context.getString(stringRes),
Toast.LENGTH_SHORT).show()
}
else -> {} else -> {}
} }
return true return true

View File

@ -13,7 +13,8 @@ enum class BooleanSetting(
ASYNC_SHADERS("async_shader_compilation", Settings.SECTION_RENDERER, false), ASYNC_SHADERS("async_shader_compilation", Settings.SECTION_RENDERER, false),
PLUGIN_LOADER("plugin_loader", Settings.SECTION_SYSTEM, false), PLUGIN_LOADER("plugin_loader", Settings.SECTION_SYSTEM, false),
ALLOW_PLUGIN_LOADER("allow_plugin_loader", Settings.SECTION_SYSTEM, true), ALLOW_PLUGIN_LOADER("allow_plugin_loader", Settings.SECTION_SYSTEM, true),
SWAP_SCREEN("swap_screen", Settings.SECTION_LAYOUT, false); SWAP_SCREEN("swap_screen", Settings.SECTION_LAYOUT, false),
INSTANT_DEBUG_LOG("instant_debug_log", Settings.SECTION_DEBUG, false);
override var boolean: Boolean = defaultValue override var boolean: Boolean = defaultValue
@ -33,7 +34,8 @@ enum class BooleanSetting(
companion object { companion object {
private val NOT_RUNTIME_EDITABLE = listOf( private val NOT_RUNTIME_EDITABLE = listOf(
PLUGIN_LOADER, PLUGIN_LOADER,
ALLOW_PLUGIN_LOADER ALLOW_PLUGIN_LOADER,
ASYNC_SHADERS
) )
fun from(key: String): BooleanSetting? = fun from(key: String): BooleanSetting? =

View File

@ -19,6 +19,7 @@ enum class IntSetting(
RESOLUTION_FACTOR("resolution_factor", Settings.SECTION_RENDERER, 1), RESOLUTION_FACTOR("resolution_factor", Settings.SECTION_RENDERER, 1),
STEREOSCOPIC_3D_MODE("render_3d", Settings.SECTION_RENDERER, 0), STEREOSCOPIC_3D_MODE("render_3d", Settings.SECTION_RENDERER, 0),
STEREOSCOPIC_3D_DEPTH("factor_3d", Settings.SECTION_RENDERER, 0), STEREOSCOPIC_3D_DEPTH("factor_3d", Settings.SECTION_RENDERER, 0),
STEPS_PER_HOUR("steps_per_hour", Settings.SECTION_SYSTEM, 0),
CARDBOARD_SCREEN_SIZE("cardboard_screen_size", Settings.SECTION_LAYOUT, 85), CARDBOARD_SCREEN_SIZE("cardboard_screen_size", Settings.SECTION_LAYOUT, 85),
CARDBOARD_X_SHIFT("cardboard_x_shift", Settings.SECTION_LAYOUT, 0), CARDBOARD_X_SHIFT("cardboard_x_shift", Settings.SECTION_LAYOUT, 0),
CARDBOARD_Y_SHIFT("cardboard_y_shift", Settings.SECTION_LAYOUT, 0), CARDBOARD_Y_SHIFT("cardboard_y_shift", Settings.SECTION_LAYOUT, 0),
@ -40,7 +41,9 @@ enum class IntSetting(
VSYNC("use_vsync_new", Settings.SECTION_RENDERER, 1), VSYNC("use_vsync_new", Settings.SECTION_RENDERER, 1),
DEBUG_RENDERER("renderer_debug", Settings.SECTION_DEBUG, 0), DEBUG_RENDERER("renderer_debug", Settings.SECTION_DEBUG, 0),
TEXTURE_FILTER("texture_filter", Settings.SECTION_RENDERER, 0), TEXTURE_FILTER("texture_filter", Settings.SECTION_RENDERER, 0),
USE_FRAME_LIMIT("use_frame_limit", Settings.SECTION_RENDERER, 1); USE_FRAME_LIMIT("use_frame_limit", Settings.SECTION_RENDERER, 1),
DELAY_RENDER_THREAD_US("delay_game_render_thread_us", Settings.SECTION_RENDERER, 0),
USE_ARTIC_BASE_CONTROLLER("use_artic_base_controller", Settings.SECTION_CONTROLS, 0);
override var int: Int = defaultValue override var int: Int = defaultValue
@ -68,7 +71,8 @@ enum class IntSetting(
DEBUG_RENDERER, DEBUG_RENDERER,
CPU_JIT, CPU_JIT,
ASYNC_CUSTOM_LOADING, ASYNC_CUSTOM_LOADING,
AUDIO_INPUT_TYPE AUDIO_INPUT_TYPE,
USE_ARTIC_BASE_CONTROLLER
) )
fun from(key: String): IntSetting? = IntSetting.values().firstOrNull { it.key == key } fun from(key: String): IntSetting? = IntSetting.values().firstOrNull { it.key == key }

View File

@ -136,6 +136,8 @@ class Settings {
const val HOTKEY_CYCLE_LAYOUT = "hotkey_toggle_layout" const val HOTKEY_CYCLE_LAYOUT = "hotkey_toggle_layout"
const val HOTKEY_CLOSE_GAME = "hotkey_close_game" const val HOTKEY_CLOSE_GAME = "hotkey_close_game"
const val HOTKEY_PAUSE_OR_RESUME = "hotkey_pause_or_resume_game" const val HOTKEY_PAUSE_OR_RESUME = "hotkey_pause_or_resume_game"
const val HOTKEY_QUICKSAVE = "hotkey_quickload"
const val HOTKEY_QUICKlOAD = "hotkey_quickpause"
val buttonKeys = listOf( val buttonKeys = listOf(
KEY_BUTTON_A, KEY_BUTTON_A,
@ -163,14 +165,28 @@ class Settings {
KEY_CSTICK_AXIS_VERTICAL, KEY_CSTICK_AXIS_VERTICAL,
KEY_CSTICK_AXIS_HORIZONTAL KEY_CSTICK_AXIS_HORIZONTAL
) )
val dPadKeys = listOf( val dPadAxisKeys = listOf(
KEY_DPAD_AXIS_VERTICAL, KEY_DPAD_AXIS_VERTICAL,
KEY_DPAD_AXIS_HORIZONTAL KEY_DPAD_AXIS_HORIZONTAL
) )
val dPadButtonKeys = listOf(
KEY_BUTTON_UP,
KEY_BUTTON_DOWN,
KEY_BUTTON_LEFT,
KEY_BUTTON_RIGHT
)
val axisTitles = listOf( val axisTitles = listOf(
R.string.controller_axis_vertical, R.string.controller_axis_vertical,
R.string.controller_axis_horizontal R.string.controller_axis_horizontal
) )
val dPadTitles = listOf(
R.string.direction_up,
R.string.direction_down,
R.string.direction_left,
R.string.direction_right,
)
val triggerKeys = listOf( val triggerKeys = listOf(
KEY_BUTTON_L, KEY_BUTTON_L,
KEY_BUTTON_R, KEY_BUTTON_R,
@ -187,13 +203,17 @@ class Settings {
HOTKEY_SCREEN_SWAP, HOTKEY_SCREEN_SWAP,
HOTKEY_CYCLE_LAYOUT, HOTKEY_CYCLE_LAYOUT,
HOTKEY_CLOSE_GAME, HOTKEY_CLOSE_GAME,
HOTKEY_PAUSE_OR_RESUME HOTKEY_PAUSE_OR_RESUME,
HOTKEY_QUICKSAVE,
HOTKEY_QUICKlOAD
) )
val hotkeyTitles = listOf( val hotkeyTitles = listOf(
R.string.emulation_swap_screens, R.string.emulation_swap_screens,
R.string.emulation_cycle_landscape_layouts, R.string.emulation_cycle_landscape_layouts,
R.string.emulation_close_game, R.string.emulation_close_game,
R.string.emulation_toggle_pause R.string.emulation_toggle_pause,
R.string.emulation_quicksave,
R.string.emulation_quickload,
) )
const val PREF_FIRST_APP_LAUNCH = "FirstApplicationLaunch" const val PREF_FIRST_APP_LAUNCH = "FirstApplicationLaunch"

View File

@ -4,6 +4,6 @@
package org.citra.citra_emu.features.settings.model.view package org.citra.citra_emu.features.settings.model.view
class HeaderSetting(titleId: Int) : SettingsItem(null, titleId, 0) { class HeaderSetting(titleId: Int,descId: Int = 0) : SettingsItem(null, titleId, descId) {
override val type = TYPE_HEADER override val type = TYPE_HEADER
} }

View File

@ -78,7 +78,6 @@ class InputBindingSetting(
else -> false else -> false
} }
/** /**
* Returns true if this key is for the 3DS L/R or ZL/ZR buttons. Note, these are not real * Returns true if this key is for the 3DS L/R or ZL/ZR buttons. Note, these are not real
* triggers on the 3DS, but we support them as such on a physical gamepad. * triggers on the 3DS, but we support them as such on a physical gamepad.
@ -128,11 +127,12 @@ class InputBindingSetting(
Settings.KEY_BUTTON_DOWN -> NativeLibrary.ButtonType.DPAD_DOWN Settings.KEY_BUTTON_DOWN -> NativeLibrary.ButtonType.DPAD_DOWN
Settings.KEY_BUTTON_LEFT -> NativeLibrary.ButtonType.DPAD_LEFT Settings.KEY_BUTTON_LEFT -> NativeLibrary.ButtonType.DPAD_LEFT
Settings.KEY_BUTTON_RIGHT -> NativeLibrary.ButtonType.DPAD_RIGHT Settings.KEY_BUTTON_RIGHT -> NativeLibrary.ButtonType.DPAD_RIGHT
Settings.HOTKEY_SCREEN_SWAP -> Hotkey.SWAP_SCREEN.button Settings.HOTKEY_SCREEN_SWAP -> Hotkey.SWAP_SCREEN.button
Settings.HOTKEY_CYCLE_LAYOUT -> Hotkey.CYCLE_LAYOUT.button Settings.HOTKEY_CYCLE_LAYOUT -> Hotkey.CYCLE_LAYOUT.button
Settings.HOTKEY_CLOSE_GAME -> Hotkey.CLOSE_GAME.button Settings.HOTKEY_CLOSE_GAME -> Hotkey.CLOSE_GAME.button
Settings.HOTKEY_PAUSE_OR_RESUME -> Hotkey.PAUSE_OR_RESUME.button Settings.HOTKEY_PAUSE_OR_RESUME -> Hotkey.PAUSE_OR_RESUME.button
Settings.HOTKEY_QUICKSAVE -> Hotkey.QUICKSAVE.button
Settings.HOTKEY_QUICKlOAD -> Hotkey.QUICKLOAD.button
else -> -1 else -> -1
} }
@ -222,8 +222,10 @@ class InputBindingSetting(
Toast.makeText(context, R.string.input_message_analog_only, Toast.LENGTH_LONG).show() Toast.makeText(context, R.string.input_message_analog_only, Toast.LENGTH_LONG).show()
return return
} }
writeButtonMapping(getInputButtonKey(keyEvent.keyCode))
val uiString = "${keyEvent.device.name}: Button ${keyEvent.keyCode}" val code = translateEventToKeyId(keyEvent)
writeButtonMapping(getInputButtonKey(code))
val uiString = "${keyEvent.device.name}: Button $code"
value = uiString value = uiString
} }
@ -283,9 +285,17 @@ class InputBindingSetting(
/** /**
* Helper function to get the settings key for an gamepad button. * Helper function to get the settings key for an gamepad button.
*
*/ */
@Deprecated("Use the new getInputButtonKey(keyEvent) method to handle unknown keys")
fun getInputButtonKey(keyCode: Int): String = "${INPUT_MAPPING_PREFIX}_HostAxis_${keyCode}" fun getInputButtonKey(keyCode: Int): String = "${INPUT_MAPPING_PREFIX}_HostAxis_${keyCode}"
/**
* Helper function to get the settings key for an gamepad button.
*
*/
fun getInputButtonKey(event: KeyEvent): String = "${INPUT_MAPPING_PREFIX}_HostAxis_${translateEventToKeyId(event)}"
/** /**
* Helper function to get the settings key for an gamepad axis. * Helper function to get the settings key for an gamepad axis.
*/ */
@ -301,5 +311,23 @@ class InputBindingSetting(
*/ */
fun getInputAxisOrientationKey(axis: Int): String = fun getInputAxisOrientationKey(axis: Int): String =
"${getInputAxisKey(axis)}_GuestOrientation" "${getInputAxisKey(axis)}_GuestOrientation"
/**
* This function translates a keyEvent into an "keyid"
* This key id is either the keyCode from the event, or
* the raw scanCode.
* Only when the keyCode itself is 0, (so it is an unknown key)
* we fall back to the raw scan code.
* This handles keys like the media-keys on google statia-controllers
* that don't have a conventional "mapping" and report as "unknown"
*/
fun translateEventToKeyId(event: KeyEvent): Int {
return if (event.keyCode == 0) {
event.scanCode
} else {
event.keyCode
}
}
} }
} }

View File

@ -191,7 +191,7 @@ class SettingsActivity : AppCompatActivity(), SettingsActivityView {
presenter.onSettingsReset() presenter.onSettingsReset()
val controllerKeys = Settings.buttonKeys + Settings.circlePadKeys + Settings.cStickKeys + val controllerKeys = Settings.buttonKeys + Settings.circlePadKeys + Settings.cStickKeys +
Settings.dPadKeys + Settings.triggerKeys Settings.dPadButtonKeys + Settings.dPadAxisKeys + Settings.triggerKeys
val editor = val editor =
PreferenceManager.getDefaultSharedPreferences(CitraApplication.appContext).edit() PreferenceManager.getDefaultSharedPreferences(CitraApplication.appContext).edit()
controllerKeys.forEach { editor.remove(it) } controllerKeys.forEach { editor.remove(it) }

View File

@ -298,6 +298,18 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
"" ""
) )
) )
add(
SliderSetting(
IntSetting.STEPS_PER_HOUR,
R.string.steps_per_hour,
R.string.steps_per_hour_description,
0,
65535,
" steps",
IntSetting.STEPS_PER_HOUR.key,
IntSetting.STEPS_PER_HOUR.defaultValue.toFloat()
)
)
add( add(
RunnableSetting( RunnableSetting(
R.string.console_id, R.string.console_id,
@ -609,11 +621,16 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
add(InputBindingSetting(button, Settings.axisTitles[i])) add(InputBindingSetting(button, Settings.axisTitles[i]))
} }
add(HeaderSetting(R.string.controller_dpad)) add(HeaderSetting(R.string.controller_dpad_axis,R.string.controller_dpad_axis_description))
Settings.dPadKeys.forEachIndexed { i: Int, key: String -> Settings.dPadAxisKeys.forEachIndexed { i: Int, key: String ->
val button = getInputObject(key) val button = getInputObject(key)
add(InputBindingSetting(button, Settings.axisTitles[i])) add(InputBindingSetting(button, Settings.axisTitles[i]))
} }
add(HeaderSetting(R.string.controller_dpad_button,R.string.controller_dpad_button_description))
Settings.dPadButtonKeys.forEachIndexed { i: Int, key: String ->
val button = getInputObject(key)
add(InputBindingSetting(button, Settings.dPadTitles[i]))
}
add(HeaderSetting(R.string.controller_triggers)) add(HeaderSetting(R.string.controller_triggers))
Settings.triggerKeys.forEachIndexed { i: Int, key: String -> Settings.triggerKeys.forEachIndexed { i: Int, key: String ->
@ -626,6 +643,16 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
val button = getInputObject(key) val button = getInputObject(key)
add(InputBindingSetting(button, Settings.hotkeyTitles[i])) add(InputBindingSetting(button, Settings.hotkeyTitles[i]))
} }
add(HeaderSetting(R.string.miscellaneous))
add(
SwitchSetting(
IntSetting.USE_ARTIC_BASE_CONTROLLER,
R.string.use_artic_base_controller,
R.string.use_artic_base_controller_desc,
IntSetting.USE_ARTIC_BASE_CONTROLLER.key,
IntSetting.USE_ARTIC_BASE_CONTROLLER.defaultValue
)
)
} }
} }
@ -729,6 +756,18 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
IntSetting.TEXTURE_FILTER.defaultValue IntSetting.TEXTURE_FILTER.defaultValue
) )
) )
add(
SliderSetting(
IntSetting.DELAY_RENDER_THREAD_US,
R.string.delay_render_thread,
R.string.delay_render_thread_description,
0,
16000,
" μs",
IntSetting.DELAY_RENDER_THREAD_US.key,
IntSetting.DELAY_RENDER_THREAD_US.defaultValue.toFloat()
)
)
add(HeaderSetting(R.string.stereoscopy)) add(HeaderSetting(R.string.stereoscopy))
add( add(
@ -964,6 +1003,15 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
IntSetting.DEBUG_RENDERER.defaultValue IntSetting.DEBUG_RENDERER.defaultValue
) )
) )
add(
SwitchSetting(
BooleanSetting.INSTANT_DEBUG_LOG,
R.string.instant_debug_log,
R.string.instant_debug_log_desc,
BooleanSetting.INSTANT_DEBUG_LOG.key,
BooleanSetting.INSTANT_DEBUG_LOG.defaultValue
)
)
} }
} }

View File

@ -18,6 +18,12 @@ class HeaderViewHolder(val binding: ListItemSettingsHeaderBinding, adapter: Sett
override fun bind(item: SettingsItem) { override fun bind(item: SettingsItem) {
binding.textHeaderName.setText(item.nameId) binding.textHeaderName.setText(item.nameId)
if (item.descriptionId != 0) {
binding.textHeaderDescription.visibility = View.VISIBLE
binding.textHeaderDescription.setText(item.descriptionId)
}else {
binding.textHeaderDescription.visibility = View.GONE
}
} }
override fun onClick(clicked: View) { override fun onClick(clicked: View) {

View File

@ -481,12 +481,12 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
popupMenu.setOnMenuItemClickListener { popupMenu.setOnMenuItemClickListener {
when (it.itemId) { when (it.itemId) {
R.id.menu_emulation_save_state -> { R.id.menu_emulation_save_state -> {
showSaveStateSubmenu() showStateSubmenu(true)
true true
} }
R.id.menu_emulation_load_state -> { R.id.menu_emulation_load_state -> {
showLoadStateSubmenu() showStateSubmenu(false)
true true
} }
@ -497,7 +497,8 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
popupMenu.show() popupMenu.show()
} }
private fun showSaveStateSubmenu() { private fun showStateSubmenu(isSaving: Boolean) {
val savestates = NativeLibrary.getSavestateInfo() val savestates = NativeLibrary.getSavestateInfo()
val popupMenu = PopupMenu( val popupMenu = PopupMenu(
@ -507,19 +508,40 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
popupMenu.menu.apply { popupMenu.menu.apply {
for (i in 0 until NativeLibrary.SAVESTATE_SLOT_COUNT) { for (i in 0 until NativeLibrary.SAVESTATE_SLOT_COUNT) {
val slot = i + 1 val slot = i
val text = getString(R.string.emulation_empty_state_slot, slot) var enableClick = isSaving
add(text).setEnabled(true).setOnMenuItemClickListener { val text = if (slot == NativeLibrary.QUICKSAVE_SLOT) {
displaySavestateWarning() enableClick = false
getString(R.string.emulation_quicksave_slot)
} else {
getString(R.string.emulation_empty_state_slot, slot)
}
add(text).setEnabled(enableClick).setOnMenuItemClickListener {
if(isSaving) {
NativeLibrary.saveState(slot) NativeLibrary.saveState(slot)
} else {
NativeLibrary.loadState(slot)
binding.drawerLayout.close()
Toast.makeText(context,
getString(R.string.quickload_loading),
Toast.LENGTH_SHORT).show()
}
true true
} }
} }
} }
savestates?.forEach { savestates?.forEach {
val text = getString(R.string.emulation_occupied_state_slot, it.slot, it.time) var enableClick = true
popupMenu.menu.getItem(it.slot - 1).setTitle(text) val text = if(it.slot == NativeLibrary.QUICKSAVE_SLOT) {
// do not allow saving in quicksave slot
enableClick = !isSaving
getString(R.string.emulation_occupied_quicksave_slot, it.time)
} else{
getString(R.string.emulation_occupied_state_slot, it.slot, it.time)
}
popupMenu.menu.getItem(it.slot).setTitle(text).setEnabled(enableClick)
} }
popupMenu.show() popupMenu.show()

View File

@ -156,9 +156,6 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
} }
} }
// Dismiss previous notifications (should not happen unless a crash occurred)
EmulationActivity.stopForegroundService(this)
setInsets() setInsets()
} }
@ -170,7 +167,6 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
} }
override fun onDestroy() { override fun onDestroy() {
EmulationActivity.stopForegroundService(this)
super.onDestroy() super.onDestroy()
} }

View File

@ -128,6 +128,8 @@ void Config::ReadValues() {
static_cast<u16>(sdl2_config->GetInteger("Controls", "udp_input_port", static_cast<u16>(sdl2_config->GetInteger("Controls", "udp_input_port",
InputCommon::CemuhookUDP::DEFAULT_PORT)); InputCommon::CemuhookUDP::DEFAULT_PORT));
ReadSetting("Controls", Settings::values.use_artic_base_controller);
// Core // Core
ReadSetting("Core", Settings::values.use_cpu_jit); ReadSetting("Core", Settings::values.use_cpu_jit);
ReadSetting("Core", Settings::values.cpu_clock_percentage); ReadSetting("Core", Settings::values.cpu_clock_percentage);
@ -169,6 +171,7 @@ void Config::ReadValues() {
ReadSetting("Renderer", Settings::values.bg_red); ReadSetting("Renderer", Settings::values.bg_red);
ReadSetting("Renderer", Settings::values.bg_green); ReadSetting("Renderer", Settings::values.bg_green);
ReadSetting("Renderer", Settings::values.bg_blue); ReadSetting("Renderer", Settings::values.bg_blue);
ReadSetting("Renderer", Settings::values.delay_game_render_thread_us);
// Layout // Layout
Settings::values.layout_option = static_cast<Settings::LayoutOption>(sdl2_config->GetInteger( Settings::values.layout_option = static_cast<Settings::LayoutOption>(sdl2_config->GetInteger(
@ -220,6 +223,7 @@ void Config::ReadValues() {
ReadSetting("System", Settings::values.init_ticks_override); ReadSetting("System", Settings::values.init_ticks_override);
ReadSetting("System", Settings::values.plugin_loader_enabled); ReadSetting("System", Settings::values.plugin_loader_enabled);
ReadSetting("System", Settings::values.allow_plugin_loader); ReadSetting("System", Settings::values.allow_plugin_loader);
ReadSetting("System", Settings::values.steps_per_hour);
// Camera // Camera
using namespace Service::CAM; using namespace Service::CAM;
@ -259,6 +263,7 @@ void Config::ReadValues() {
ReadSetting("Debugging", Settings::values.renderer_debug); ReadSetting("Debugging", Settings::values.renderer_debug);
ReadSetting("Debugging", Settings::values.use_gdbstub); ReadSetting("Debugging", Settings::values.use_gdbstub);
ReadSetting("Debugging", Settings::values.gdbstub_port); ReadSetting("Debugging", Settings::values.gdbstub_port);
ReadSetting("Debugging", Settings::values.instant_debug_log);
for (const auto& service_module : Service::service_module_map) { for (const auto& service_module : Service::service_module_map) {
bool use_lle = sdl2_config->GetBoolean("Debugging", "LLE\\" + service_module.name, false); bool use_lle = sdl2_config->GetBoolean("Debugging", "LLE\\" + service_module.name, false);

View File

@ -86,6 +86,9 @@ udp_input_port=
# The pad to request data on. Should be between 0 (Pad 1) and 3 (Pad 4). (Default 0) # The pad to request data on. Should be between 0 (Pad 1) and 3 (Pad 4). (Default 0)
udp_pad_index= udp_pad_index=
# Use Artic Controller when connected to Artic Base Server. (Default 0)
use_artic_base_controller=
[Core] [Core]
# Whether to use the Just-In-Time (JIT) compiler for CPU emulation # Whether to use the Just-In-Time (JIT) compiler for CPU emulation
# 0: Interpreter (slow), 1 (default): JIT (fast) # 0: Interpreter (slow), 1 (default): JIT (fast)
@ -175,6 +178,10 @@ anaglyph_shader_name =
# 0: Nearest, 1 (default): Linear # 0: Nearest, 1 (default): Linear
filter_mode = filter_mode =
# Delays the game render thread by the specified amount of microseconds
# Set to 0 for no delay, only useful in dynamic-fps games to simulate GPU delay.
delay_game_render_thread_us =
[Layout] [Layout]
# Layout for the screen inside the render window. # Layout for the screen inside the render window.
# 0 (default): Default Top Bottom Screen, 1: Single Screen Only, 2: Large Screen Small Screen, 3: Side by Side # 0 (default): Default Top Bottom Screen, 1: Single Screen Only, 2: Large Screen Small Screen, 3: Side by Side
@ -300,6 +307,10 @@ init_ticks_type =
# Defaults to 0. # Defaults to 0.
init_ticks_override = init_ticks_override =
# Number of steps per hour reported by the pedometer. Range from 0 to 65,535.
# Defaults to 0.
steps_per_hour =
# Plugin loader state, if enabled plugins will be loaded from the SD card. # Plugin loader state, if enabled plugins will be loaded from the SD card.
# You can also set if homebrew apps are allowed to enable the plugin loader # You can also set if homebrew apps are allowed to enable the plugin loader
plugin_loader = plugin_loader =
@ -350,6 +361,10 @@ renderer_debug =
use_gdbstub=false use_gdbstub=false
gdbstub_port=24689 gdbstub_port=24689
# Flush log output on every message
# Immediately commits the debug log to file. Use this if citra crashes and the log output is being cut.
instant_debug_log =
# To LLE a service module add "LLE\<module name>=true" # To LLE a service module add "LLE\<module name>=true"
[WebService] [WebService]

View File

@ -27,14 +27,18 @@ static void UpdateLandscapeScreenLayout() {
IDCache::GetNativeLibraryClass(), IDCache::GetLandscapeScreenLayout())); IDCache::GetNativeLibraryClass(), IDCache::GetLandscapeScreenLayout()));
} }
void EmuWindow_Android::OnSurfaceChanged(ANativeWindow* surface) { bool EmuWindow_Android::OnSurfaceChanged(ANativeWindow* surface) {
render_window = surface; if (render_window == surface) {
return false;
}
render_window = surface;
window_info.type = Frontend::WindowSystemType::Android; window_info.type = Frontend::WindowSystemType::Android;
window_info.render_surface = surface; window_info.render_surface = surface;
StopPresenting(); StopPresenting();
OnFramebufferSizeChanged(); OnFramebufferSizeChanged();
return true;
} }
bool EmuWindow_Android::OnTouchEvent(int x, int y, bool pressed) { bool EmuWindow_Android::OnTouchEvent(int x, int y, bool pressed) {

View File

@ -17,7 +17,7 @@ public:
~EmuWindow_Android(); ~EmuWindow_Android();
/// Called by the onSurfaceChanges() method to change the surface /// Called by the onSurfaceChanges() method to change the surface
void OnSurfaceChanged(ANativeWindow* surface); bool OnSurfaceChanged(ANativeWindow* surface);
/// Handles touch event that occur.(Touched or released) /// Handles touch event that occur.(Touched or released)
bool OnTouchEvent(int x, int y, bool pressed); bool OnTouchEvent(int x, int y, bool pressed);

View File

@ -294,12 +294,13 @@ void Java_org_citra_citra_1emu_NativeLibrary_surfaceChanged(JNIEnv* env,
jobject surf) { jobject surf) {
s_surf = ANativeWindow_fromSurface(env, surf); s_surf = ANativeWindow_fromSurface(env, surf);
bool notify = false;
if (window) { if (window) {
window->OnSurfaceChanged(s_surf); notify = window->OnSurfaceChanged(s_surf);
} }
auto& system = Core::System::GetInstance(); auto& system = Core::System::GetInstance();
if (system.IsPoweredOn()) { if (notify && system.IsPoweredOn()) {
system.GPU().Renderer().NotifySurfaceChanged(); system.GPU().Renderer().NotifySurfaceChanged();
} }
@ -308,11 +309,13 @@ void Java_org_citra_citra_1emu_NativeLibrary_surfaceChanged(JNIEnv* env,
void Java_org_citra_citra_1emu_NativeLibrary_surfaceDestroyed([[maybe_unused]] JNIEnv* env, void Java_org_citra_citra_1emu_NativeLibrary_surfaceDestroyed([[maybe_unused]] JNIEnv* env,
[[maybe_unused]] jobject obj) { [[maybe_unused]] jobject obj) {
if (s_surf != nullptr) {
ANativeWindow_release(s_surf); ANativeWindow_release(s_surf);
s_surf = nullptr; s_surf = nullptr;
if (window) { if (window) {
window->OnSurfaceChanged(s_surf); window->OnSurfaceChanged(s_surf);
} }
}
} }
void Java_org_citra_citra_1emu_NativeLibrary_doFrame([[maybe_unused]] JNIEnv* env, void Java_org_citra_citra_1emu_NativeLibrary_doFrame([[maybe_unused]] JNIEnv* env,

View File

@ -22,8 +22,8 @@
<ImageView <ImageView
android:id="@+id/image_game_screen" android:id="@+id/image_game_screen"
android:layout_width="56dp" android:layout_width="75dp"
android:layout_height="56dp" android:layout_height="75dp"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" /> app:layout_constraintTop_toTopOf="parent" />
@ -63,6 +63,18 @@
android:requiresFadingEdge="horizontal" android:requiresFadingEdge="horizontal"
tools:text="Nintendo" /> tools:text="Nintendo" />
<TextView
android:id="@+id/text_game_id"
style="@style/TextAppearance.Material3.BodySmall"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAlignment="viewStart"
android:singleLine="true"
android:marqueeRepeatLimit="marquee_forever"
android:ellipsize="none"
android:requiresFadingEdge="horizontal"
tools:text="0004000000033400" />
<TextView <TextView
android:id="@+id/text_filename" android:id="@+id/text_filename"
style="@style/TextAppearance.Material3.BodySmall" style="@style/TextAppearance.Material3.BodySmall"

View File

@ -1,16 +1,33 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<com.google.android.material.textview.MaterialTextView xmlns:android="http://schemas.android.com/apk/res/android" <LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:focusable="false"
android:clickable="false"
android:paddingVertical="16dp"
>
<com.google.android.material.textview.MaterialTextView
android:id="@+id/text_header_name" android:id="@+id/text_header_name"
style="@style/TextAppearance.Material3.TitleSmall" style="@style/TextAppearance.Material3.TitleSmall"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="start|center_vertical" android:layout_gravity="start|center_vertical"
android:paddingHorizontal="@dimen/spacing_large"
android:paddingVertical="16dp"
android:focusable="false"
android:clickable="false"
android:textAlignment="viewStart" android:textAlignment="viewStart"
android:textColor="?attr/colorPrimary" android:textColor="?attr/colorPrimary"
android:textStyle="bold" android:textStyle="bold"
tools:text="CPU Settings" /> tools:text="CPU Settings" />
<com.google.android.material.textview.MaterialTextView
android:id="@+id/text_header_description"
style="@style/TextAppearance.Material3.BodySmall"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/spacing_small"
android:textAlignment="viewStart"
tools:text="@string/app_disclaimer" />
</LinearLayout>

View File

@ -663,5 +663,10 @@ Se esperan fallos gráficos temporales cuando ésta esté activado.</string>
<string name="artic_base_connect">Conectar con Artic Base</string> <string name="artic_base_connect">Conectar con Artic Base</string>
<string name="artic_base_connect_description">Conectar con una consola real que esté ejecutando un servidor Artic Base</string> <string name="artic_base_connect_description">Conectar con una consola real que esté ejecutando un servidor Artic Base</string>
<string name="artic_base_enter_address">Introduce la dirección del servidor Artic Base</string> <string name="artic_base_enter_address">Introduce la dirección del servidor Artic Base</string>
<string name="delay_render_thread">Retrasa el hilo de dibujado del juego</string>
<string name="delay_render_thread_description">Retrasa el hilo de dibujado del juego cuando envía datos a la GPU. Ayuda con problemas de rendimiento en los (muy pocos) juegos de fps dinámicos.</string>
<string name="miscellaneous">Misceláneo</string>
<string name="use_artic_base_controller">Usar Artic Controller cuando se está conectado a Artic Base Server</string>
<string name="use_artic_base_controller_desc">Usa los controles proporcionados por Artic Base Server cuando esté conectado a él en lugar del dispositivo de entrada configurado.</string>
</resources> </resources>

View File

@ -108,8 +108,16 @@
<string name="controller_triggers">Triggers</string> <string name="controller_triggers">Triggers</string>
<string name="controller_trigger">Trigger</string> <string name="controller_trigger">Trigger</string>
<string name="controller_dpad">D-Pad</string> <string name="controller_dpad">D-Pad</string>
<string name="controller_dpad_axis">D-Pad (Axis)</string>
<string name="controller_dpad_axis_description">Some controllers will not be able to map their dpad as an axis. In that case, use the D-Pad (buttons) section below instead.</string>
<string name="controller_dpad_button">D-Pad (Buttons)</string>
<string name="controller_dpad_button_description">Only map these buttons if the D-Pad (Axis) settings above do not work with your controller.</string>
<string name="controller_axis_vertical">Up/Down Axis</string> <string name="controller_axis_vertical">Up/Down Axis</string>
<string name="controller_axis_horizontal">Left/Right Axis</string> <string name="controller_axis_horizontal">Left/Right Axis</string>
<string name="direction_up">Up</string>
<string name="direction_down">Down</string>
<string name="direction_left">Left</string>
<string name="direction_right">Right</string>
<string name="input_dialog_title">Bind %1$s %2$s</string> <string name="input_dialog_title">Bind %1$s %2$s</string>
<string name="input_dialog_description">Press or move an input.</string> <string name="input_dialog_description">Press or move an input.</string>
<string name="input_binding">Input Binding</string> <string name="input_binding">Input Binding</string>
@ -182,6 +190,8 @@
<string name="birthday_day">Day</string> <string name="birthday_day">Day</string>
<string name="country">Country</string> <string name="country">Country</string>
<string name="play_coins">Play Coins</string> <string name="play_coins">Play Coins</string>
<string name="steps_per_hour">Pedometer Steps per Hour</string>
<string name="steps_per_hour_description">Number of steps per hour reported by the pedometer. Range from 0 to 65,535.</string>
<string name="console_id">Console ID</string> <string name="console_id">Console ID</string>
<string name="regenerate_console_id">Regenerate Console ID</string> <string name="regenerate_console_id">Regenerate Console ID</string>
<string name="regenerate_console_id_description">This will replace your current virtual 3DS with a new one. Your current virtual 3DS will not be recoverable. This might have unexpected effects in games. This might fail if you use an outdated config savegame. Continue?</string> <string name="regenerate_console_id_description">This will replace your current virtual 3DS with a new one. Your current virtual 3DS will not be recoverable. This might have unexpected effects in games. This might fail if you use an outdated config savegame. Continue?</string>
@ -689,5 +699,21 @@
<string name="artic_base_connect_description">Connect to a real console that is running an Artic Base server</string> <string name="artic_base_connect_description">Connect to a real console that is running an Artic Base server</string>
<string name="artic_base_connect">Connect to Artic Base</string> <string name="artic_base_connect">Connect to Artic Base</string>
<string name="artic_base_enter_address">Enter Artic Base server address</string> <string name="artic_base_enter_address">Enter Artic Base server address</string>
<string name="delay_render_thread">Delay game render thread</string>
<string name="delay_render_thread_description">Delay the game render thread when it submits data to the GPU. Helps with performance issues in the (very few) dynamic-fps games.</string>
<!-- Quickload&Save-->
<string name="emulation_quicksave_slot">Quicksave</string>
<string name="emulation_quicksave">Quicksave</string>
<string name="emulation_quickload">Quickload</string>
<string name="emulation_occupied_quicksave_slot">Quicksave - %1$tF %1$tR</string>
<string name="quicksave_saving">Saving…</string>
<string name="quickload_loading">Loading…</string>
<string name="quickload_not_found">No Quicksave available.</string>
<string name="miscellaneous">Miscellaneous</string>
<string name="use_artic_base_controller">Use Artic Controller when connected to Artic Base Server</string>
<string name="use_artic_base_controller_desc">Use the controls provided by Artic Base Server when connected to it instead of the configured input device.</string>
<string name="instant_debug_log">Flush log output on every message</string>
<string name="instant_debug_log_desc">Immediately commits the debug log to file. Use this if citra crashes and the log output is being cut.</string>
</resources> </resources>

View File

@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip

View File

@ -126,6 +126,7 @@ void Config::ReadValues() {
Settings::values.current_input_profile.udp_input_port = Settings::values.current_input_profile.udp_input_port =
static_cast<u16>(sdl2_config->GetInteger("Controls", "udp_input_port", static_cast<u16>(sdl2_config->GetInteger("Controls", "udp_input_port",
InputCommon::CemuhookUDP::DEFAULT_PORT)); InputCommon::CemuhookUDP::DEFAULT_PORT));
ReadSetting("Controls", Settings::values.use_artic_base_controller);
// Core // Core
ReadSetting("Core", Settings::values.use_cpu_jit); ReadSetting("Core", Settings::values.use_cpu_jit);
@ -147,6 +148,7 @@ void Config::ReadValues() {
ReadSetting("Renderer", Settings::values.use_vsync_new); ReadSetting("Renderer", Settings::values.use_vsync_new);
ReadSetting("Renderer", Settings::values.texture_filter); ReadSetting("Renderer", Settings::values.texture_filter);
ReadSetting("Renderer", Settings::values.texture_sampling); ReadSetting("Renderer", Settings::values.texture_sampling);
ReadSetting("Renderer", Settings::values.delay_game_render_thread_us);
ReadSetting("Renderer", Settings::values.mono_render_option); ReadSetting("Renderer", Settings::values.mono_render_option);
ReadSetting("Renderer", Settings::values.render_3d); ReadSetting("Renderer", Settings::values.render_3d);
@ -230,6 +232,7 @@ void Config::ReadValues() {
ReadSetting("System", Settings::values.init_ticks_override); ReadSetting("System", Settings::values.init_ticks_override);
ReadSetting("System", Settings::values.plugin_loader_enabled); ReadSetting("System", Settings::values.plugin_loader_enabled);
ReadSetting("System", Settings::values.allow_plugin_loader); ReadSetting("System", Settings::values.allow_plugin_loader);
ReadSetting("System", Settings::values.steps_per_hour);
{ {
constexpr const char* default_init_time_offset = "0 00:00:00"; constexpr const char* default_init_time_offset = "0 00:00:00";
@ -321,6 +324,7 @@ void Config::ReadValues() {
ReadSetting("Debugging", Settings::values.renderer_debug); ReadSetting("Debugging", Settings::values.renderer_debug);
ReadSetting("Debugging", Settings::values.use_gdbstub); ReadSetting("Debugging", Settings::values.use_gdbstub);
ReadSetting("Debugging", Settings::values.gdbstub_port); ReadSetting("Debugging", Settings::values.gdbstub_port);
ReadSetting("Debugging", Settings::values.instant_debug_log);
for (const auto& service_module : Service::service_module_map) { for (const auto& service_module : Service::service_module_map) {
bool use_lle = sdl2_config->GetBoolean("Debugging", "LLE\\" + service_module.name, false); bool use_lle = sdl2_config->GetBoolean("Debugging", "LLE\\" + service_module.name, false);

View File

@ -319,6 +319,10 @@ init_ticks_type =
# Defaults to 0. # Defaults to 0.
init_ticks_override = init_ticks_override =
# Number of steps per hour reported by the pedometer. Range from 0 to 65,535.
# Defaults to 0.
steps_per_hour =
[Camera] [Camera]
# Which camera engine to use for the right outer camera # Which camera engine to use for the right outer camera
# blank (default): a dummy camera that always returns black image # blank (default): a dummy camera that always returns black image

View File

@ -327,6 +327,8 @@ void Config::ReadCameraValues() {
void Config::ReadControlValues() { void Config::ReadControlValues() {
qt_config->beginGroup(QStringLiteral("Controls")); qt_config->beginGroup(QStringLiteral("Controls"));
ReadBasicSetting(Settings::values.use_artic_base_controller);
int num_touch_from_button_maps = int num_touch_from_button_maps =
qt_config->beginReadArray(QStringLiteral("touch_from_button_maps")); qt_config->beginReadArray(QStringLiteral("touch_from_button_maps"));
@ -492,6 +494,7 @@ void Config::ReadDebuggingValues() {
ReadBasicSetting(Settings::values.gdbstub_port); ReadBasicSetting(Settings::values.gdbstub_port);
ReadBasicSetting(Settings::values.renderer_debug); ReadBasicSetting(Settings::values.renderer_debug);
ReadBasicSetting(Settings::values.dump_command_buffers); ReadBasicSetting(Settings::values.dump_command_buffers);
ReadBasicSetting(Settings::values.instant_debug_log);
qt_config->beginGroup(QStringLiteral("LLE")); qt_config->beginGroup(QStringLiteral("LLE"));
for (const auto& service_module : Service::service_module_map) { for (const auto& service_module : Service::service_module_map) {
@ -667,6 +670,8 @@ void Config::ReadRendererValues() {
ReadGlobalSetting(Settings::values.texture_filter); ReadGlobalSetting(Settings::values.texture_filter);
ReadGlobalSetting(Settings::values.texture_sampling); ReadGlobalSetting(Settings::values.texture_sampling);
ReadGlobalSetting(Settings::values.delay_game_render_thread_us);
if (global) { if (global) {
ReadBasicSetting(Settings::values.use_shader_jit); ReadBasicSetting(Settings::values.use_shader_jit);
} }
@ -708,6 +713,7 @@ void Config::ReadSystemValues() {
ReadBasicSetting(Settings::values.init_time_offset); ReadBasicSetting(Settings::values.init_time_offset);
ReadBasicSetting(Settings::values.init_ticks_type); ReadBasicSetting(Settings::values.init_ticks_type);
ReadBasicSetting(Settings::values.init_ticks_override); ReadBasicSetting(Settings::values.init_ticks_override);
ReadBasicSetting(Settings::values.steps_per_hour);
ReadBasicSetting(Settings::values.plugin_loader_enabled); ReadBasicSetting(Settings::values.plugin_loader_enabled);
ReadBasicSetting(Settings::values.allow_plugin_loader); ReadBasicSetting(Settings::values.allow_plugin_loader);
} }
@ -922,6 +928,8 @@ void Config::SaveCameraValues() {
void Config::SaveControlValues() { void Config::SaveControlValues() {
qt_config->beginGroup(QStringLiteral("Controls")); qt_config->beginGroup(QStringLiteral("Controls"));
WriteBasicSetting(Settings::values.use_artic_base_controller);
WriteSetting(QStringLiteral("profile"), Settings::values.current_input_profile_index, 0); WriteSetting(QStringLiteral("profile"), Settings::values.current_input_profile_index, 0);
qt_config->beginWriteArray(QStringLiteral("profiles")); qt_config->beginWriteArray(QStringLiteral("profiles"));
for (std::size_t p = 0; p < Settings::values.input_profiles.size(); ++p) { for (std::size_t p = 0; p < Settings::values.input_profiles.size(); ++p) {
@ -1025,6 +1033,7 @@ void Config::SaveDebuggingValues() {
WriteBasicSetting(Settings::values.use_gdbstub); WriteBasicSetting(Settings::values.use_gdbstub);
WriteBasicSetting(Settings::values.gdbstub_port); WriteBasicSetting(Settings::values.gdbstub_port);
WriteBasicSetting(Settings::values.renderer_debug); WriteBasicSetting(Settings::values.renderer_debug);
WriteBasicSetting(Settings::values.instant_debug_log);
qt_config->beginGroup(QStringLiteral("LLE")); qt_config->beginGroup(QStringLiteral("LLE"));
for (const auto& service_module : Settings::values.lle_modules) { for (const auto& service_module : Settings::values.lle_modules) {
@ -1168,6 +1177,8 @@ void Config::SaveRendererValues() {
WriteGlobalSetting(Settings::values.texture_filter); WriteGlobalSetting(Settings::values.texture_filter);
WriteGlobalSetting(Settings::values.texture_sampling); WriteGlobalSetting(Settings::values.texture_sampling);
WriteGlobalSetting(Settings::values.delay_game_render_thread_us);
if (global) { if (global) {
WriteSetting(QStringLiteral("use_shader_jit"), Settings::values.use_shader_jit.GetValue(), WriteSetting(QStringLiteral("use_shader_jit"), Settings::values.use_shader_jit.GetValue(),
true); true);
@ -1209,6 +1220,7 @@ void Config::SaveSystemValues() {
WriteBasicSetting(Settings::values.init_time_offset); WriteBasicSetting(Settings::values.init_time_offset);
WriteBasicSetting(Settings::values.init_ticks_type); WriteBasicSetting(Settings::values.init_ticks_type);
WriteBasicSetting(Settings::values.init_ticks_override); WriteBasicSetting(Settings::values.init_ticks_override);
WriteBasicSetting(Settings::values.steps_per_hour);
WriteBasicSetting(Settings::values.plugin_loader_enabled); WriteBasicSetting(Settings::values.plugin_loader_enabled);
WriteBasicSetting(Settings::values.allow_plugin_loader); WriteBasicSetting(Settings::values.allow_plugin_loader);
} }

View File

@ -9,6 +9,7 @@
#include <QMediaDevices> #include <QMediaDevices>
#include <QMessageBox> #include <QMessageBox>
#include <QWidget> #include <QWidget>
#include <QtGlobal>
#include "citra_qt/configuration/configure_camera.h" #include "citra_qt/configuration/configure_camera.h"
#include "common/settings.h" #include "common/settings.h"
#include "core/frontend/camera/factory.h" #include "core/frontend/camera/factory.h"
@ -86,7 +87,11 @@ void ConfigureCamera::ConnectEvents() {
}); });
connect(ui->toolButton, &QToolButton::clicked, this, &ConfigureCamera::OnToolButtonClicked); connect(ui->toolButton, &QToolButton::clicked, this, &ConfigureCamera::OnToolButtonClicked);
connect(ui->preview_button, &QPushButton::clicked, this, [this] { StartPreviewing(); }); connect(ui->preview_button, &QPushButton::clicked, this, [this] { StartPreviewing(); });
#if QT_VERSION < QT_VERSION_CHECK(6, 7, 0)
connect(ui->prompt_before_load, &QCheckBox::stateChanged, this, [this](int state) { connect(ui->prompt_before_load, &QCheckBox::stateChanged, this, [this](int state) {
#else
connect(ui->prompt_before_load, &QCheckBox::checkStateChanged, this, [this](int state) {
#endif
ui->camera_file->setDisabled(state == Qt::Checked); ui->camera_file->setDisabled(state == Qt::Checked);
ui->toolButton->setDisabled(state == Qt::Checked); ui->toolButton->setDisabled(state == Qt::Checked);
if (state == Qt::Checked) { if (state == Qt::Checked) {

View File

@ -5,6 +5,7 @@
#include <QCheckBox> #include <QCheckBox>
#include <QMessageBox> #include <QMessageBox>
#include <QTableWidgetItem> #include <QTableWidgetItem>
#include <QtGlobal>
#include "configure_cheats.h" #include "configure_cheats.h"
#include "core/cheats/cheat_base.h" #include "core/cheats/cheat_base.h"
#include "core/cheats/cheats.h" #include "core/cheats/cheats.h"
@ -60,7 +61,11 @@ void ConfigureCheats::LoadCheats() {
i, 2, new QTableWidgetItem(QString::fromStdString(cheats[i]->GetType()))); i, 2, new QTableWidgetItem(QString::fromStdString(cheats[i]->GetType())));
enabled->setProperty("row", static_cast<int>(i)); enabled->setProperty("row", static_cast<int>(i));
#if QT_VERSION < QT_VERSION_CHECK(6, 7, 0)
connect(enabled, &QCheckBox::stateChanged, this, &ConfigureCheats::OnCheckChanged); connect(enabled, &QCheckBox::stateChanged, this, &ConfigureCheats::OnCheckChanged);
#else
connect(enabled, &QCheckBox::checkStateChanged, this, &ConfigureCheats::OnCheckChanged);
#endif
} }
} }

View File

@ -121,6 +121,7 @@ void ConfigureDebug::SetConfiguration() {
SettingsToSlider(Settings::values.cpu_clock_percentage.GetValue())); SettingsToSlider(Settings::values.cpu_clock_percentage.GetValue()));
ui->clock_display_label->setText( ui->clock_display_label->setText(
QStringLiteral("%1%").arg(Settings::values.cpu_clock_percentage.GetValue())); QStringLiteral("%1%").arg(Settings::values.cpu_clock_percentage.GetValue()));
ui->instant_debug_log->setChecked(Settings::values.instant_debug_log.GetValue());
} }
void ConfigureDebug::ApplyConfiguration() { void ConfigureDebug::ApplyConfiguration() {
@ -138,6 +139,7 @@ void ConfigureDebug::ApplyConfiguration() {
Settings::values.delay_start_for_lle_modules = ui->delay_start_for_lle_modules->isChecked(); Settings::values.delay_start_for_lle_modules = ui->delay_start_for_lle_modules->isChecked();
Settings::values.renderer_debug = ui->toggle_renderer_debug->isChecked(); Settings::values.renderer_debug = ui->toggle_renderer_debug->isChecked();
Settings::values.dump_command_buffers = ui->toggle_dump_command_buffers->isChecked(); Settings::values.dump_command_buffers = ui->toggle_dump_command_buffers->isChecked();
Settings::values.instant_debug_log = ui->instant_debug_log->isChecked();
ConfigurationShared::ApplyPerGameSetting( ConfigurationShared::ApplyPerGameSetting(
&Settings::values.cpu_clock_percentage, ui->clock_speed_combo, &Settings::values.cpu_clock_percentage, ui->clock_speed_combo,

View File

@ -86,7 +86,7 @@
</layout> </layout>
</item> </item>
<item> <item>
<layout class="QHBoxLayout" name="horizontalLayout"> <layout class="QHBoxLayout" name="horizontalLayout1">
<item> <item>
<widget class="QLabel" name="label_3"> <widget class="QLabel" name="label_3">
<property name="text"> <property name="text">
@ -100,7 +100,7 @@
</layout> </layout>
</item> </item>
<item> <item>
<layout class="QHBoxLayout" name="horizontalLayout_2"> <layout class="QHBoxLayout" name="horizontalLayout2">
<item> <item>
<widget class="QCheckBox" name="toggle_console"> <widget class="QCheckBox" name="toggle_console">
<property name="text"> <property name="text">
@ -117,6 +117,16 @@
</item> </item>
</layout> </layout>
</item> </item>
<item>
<widget class="QCheckBox" name="instant_debug_log">
<property name="text">
<string>Flush log output on every message</string>
</property>
<property name="toolTip">
<string>&lt;html&gt;&lt;body&gt;Immediately commits the debug log to file. Use this if citra crashes and the log output is being cut.&lt;br&gt;Enabling this feature will decrease performance, only use it for debugging purposes.&lt;/body&gt;&lt;/html&gt;</string>
</property>
</widget>
</item>
</layout> </layout>
</widget> </widget>
</item> </item>
@ -125,7 +135,7 @@
<property name="title"> <property name="title">
<string>CPU</string> <string>CPU</string>
</property> </property>
<layout class="QGridLayout" name="gridLayout_2"> <layout class="QGridLayout" name="clock_speed_GLayout">
<item row="1" column="0"> <item row="1" column="0">
<widget class="QWidget" name="clock_speed_widget" native="true"> <widget class="QWidget" name="clock_speed_widget" native="true">
<layout class="QHBoxLayout" name="clock_speed_layout"> <layout class="QHBoxLayout" name="clock_speed_layout">

View File

@ -29,7 +29,7 @@ ConfigureDialog::ConfigureDialog(QWidget* parent, HotkeyRegistry& registry_, Cor
system{system_}, is_powered_on{system.IsPoweredOn()}, system{system_}, is_powered_on{system.IsPoweredOn()},
general_tab{std::make_unique<ConfigureGeneral>(this)}, general_tab{std::make_unique<ConfigureGeneral>(this)},
system_tab{std::make_unique<ConfigureSystem>(system, this)}, system_tab{std::make_unique<ConfigureSystem>(system, this)},
input_tab{std::make_unique<ConfigureInput>(this)}, input_tab{std::make_unique<ConfigureInput>(system, this)},
hotkeys_tab{std::make_unique<ConfigureHotkeys>(this)}, hotkeys_tab{std::make_unique<ConfigureHotkeys>(this)},
graphics_tab{ graphics_tab{
std::make_unique<ConfigureGraphics>(gl_renderer, physical_devices, is_powered_on, this)}, std::make_unique<ConfigureGraphics>(gl_renderer, physical_devices, is_powered_on, this)},

View File

@ -26,6 +26,10 @@ ConfigureGraphics::ConfigureGraphics(QString gl_renderer, std::span<const QStrin
// Set the index to -1 to ensure the below lambda is called with setCurrentIndex // Set the index to -1 to ensure the below lambda is called with setCurrentIndex
ui->graphics_api_combo->setCurrentIndex(-1); ui->graphics_api_combo->setCurrentIndex(-1);
const auto width = static_cast<int>(QString::fromStdString("000000000").size() * 6);
ui->delay_render_display_label->setMinimumWidth(width);
ui->delay_render_combo->setVisible(!Settings::IsConfiguringGlobal());
auto graphics_api_combo_model = auto graphics_api_combo_model =
qobject_cast<QStandardItemModel*>(ui->graphics_api_combo->model()); qobject_cast<QStandardItemModel*>(ui->graphics_api_combo->model());
#ifndef ENABLE_SOFTWARE_RENDERER #ifndef ENABLE_SOFTWARE_RENDERER
@ -82,12 +86,25 @@ ConfigureGraphics::ConfigureGraphics(QString gl_renderer, std::span<const QStrin
connect(ui->graphics_api_combo, qOverload<int>(&QComboBox::currentIndexChanged), this, connect(ui->graphics_api_combo, qOverload<int>(&QComboBox::currentIndexChanged), this,
&ConfigureGraphics::SetPhysicalDeviceComboVisibility); &ConfigureGraphics::SetPhysicalDeviceComboVisibility);
connect(ui->delay_render_slider, &QSlider::valueChanged, this, [&](int value) {
ui->delay_render_display_label->setText(
QStringLiteral("%1 ms")
.arg(((double)value) / 1000.f, 0, 'f', 3)
.rightJustified(QString::fromStdString("000000000").size()));
});
SetConfiguration(); SetConfiguration();
} }
ConfigureGraphics::~ConfigureGraphics() = default; ConfigureGraphics::~ConfigureGraphics() = default;
void ConfigureGraphics::SetConfiguration() { void ConfigureGraphics::SetConfiguration() {
ui->delay_render_slider->setValue(Settings::values.delay_game_render_thread_us.GetValue());
ui->delay_render_display_label->setText(
QStringLiteral("%1 ms")
.arg(((double)ui->delay_render_slider->value()) / 1000, 0, 'f', 3)
.rightJustified(QString::fromStdString("000000000").size()));
if (!Settings::IsConfiguringGlobal()) { if (!Settings::IsConfiguringGlobal()) {
ConfigurationShared::SetHighlight(ui->graphics_api_group, ConfigurationShared::SetHighlight(ui->graphics_api_group,
!Settings::values.graphics_api.UsingGlobal()); !Settings::values.graphics_api.UsingGlobal());
@ -101,6 +118,16 @@ void ConfigureGraphics::SetConfiguration() {
&Settings::values.texture_sampling); &Settings::values.texture_sampling);
ConfigurationShared::SetHighlight(ui->widget_texture_sampling, ConfigurationShared::SetHighlight(ui->widget_texture_sampling,
!Settings::values.texture_sampling.UsingGlobal()); !Settings::values.texture_sampling.UsingGlobal());
ConfigurationShared::SetHighlight(
ui->delay_render_layout, !Settings::values.delay_game_render_thread_us.UsingGlobal());
if (Settings::values.delay_game_render_thread_us.UsingGlobal()) {
ui->delay_render_combo->setCurrentIndex(0);
ui->delay_render_slider->setEnabled(false);
} else {
ui->delay_render_combo->setCurrentIndex(1);
ui->delay_render_slider->setEnabled(true);
}
} else { } else {
ui->graphics_api_combo->setCurrentIndex( ui->graphics_api_combo->setCurrentIndex(
static_cast<int>(Settings::values.graphics_api.GetValue())); static_cast<int>(Settings::values.graphics_api.GetValue()));
@ -144,6 +171,9 @@ void ConfigureGraphics::ApplyConfiguration() {
ui->toggle_disk_shader_cache, use_disk_shader_cache); ui->toggle_disk_shader_cache, use_disk_shader_cache);
ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_vsync_new, ui->toggle_vsync_new, ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_vsync_new, ui->toggle_vsync_new,
use_vsync_new); use_vsync_new);
ConfigurationShared::ApplyPerGameSetting(
&Settings::values.delay_game_render_thread_us, ui->delay_render_combo,
[this](s32) { return ui->delay_render_slider->value(); });
if (Settings::IsConfiguringGlobal()) { if (Settings::IsConfiguringGlobal()) {
Settings::values.use_shader_jit = ui->toggle_shader_jit->isChecked(); Settings::values.use_shader_jit = ui->toggle_shader_jit->isChecked();
@ -170,9 +200,16 @@ void ConfigureGraphics::SetupPerGameUI() {
ui->toggle_async_present->setEnabled(Settings::values.async_presentation.UsingGlobal()); ui->toggle_async_present->setEnabled(Settings::values.async_presentation.UsingGlobal());
ui->graphics_api_combo->setEnabled(Settings::values.graphics_api.UsingGlobal()); ui->graphics_api_combo->setEnabled(Settings::values.graphics_api.UsingGlobal());
ui->physical_device_combo->setEnabled(Settings::values.physical_device.UsingGlobal()); ui->physical_device_combo->setEnabled(Settings::values.physical_device.UsingGlobal());
ui->delay_render_combo->setEnabled(
Settings::values.delay_game_render_thread_us.UsingGlobal());
return; return;
} }
connect(ui->delay_render_combo, qOverload<int>(&QComboBox::activated), this, [this](int index) {
ui->delay_render_slider->setEnabled(index == 1);
ConfigurationShared::SetHighlight(ui->delay_render_layout, index == 1);
});
ui->toggle_shader_jit->setVisible(false); ui->toggle_shader_jit->setVisible(false);
ConfigurationShared::SetColoredComboBox( ConfigurationShared::SetColoredComboBox(

View File

@ -307,6 +307,83 @@
</property> </property>
</widget> </widget>
</item> </item>
<item>
<widget class="QWidget" name="delay_render_layout" native="true">
<layout class="QHBoxLayout" name="delay_render_layout_inner">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QComboBox" name="delay_render_combo">
<item>
<property name="text">
<string>Use global</string>
</property>
</item>
<item>
<property name="text">
<string>Use per-game</string>
</property>
</item>
</widget>
</item>
<item>
<widget class="QLabel" name="label_delay_render">
<property name="text">
<string>Delay game render thread:</string>
</property>
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Delays the emulated game render thread the specified amount of milliseconds every time it submits render commands to the GPU.&lt;/p&gt;&lt;p&gt;Adjust this feature in the (very few) dynamic-fps games to fix performance issues.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
</widget>
</item>
<item>
<widget class="QSlider" name="delay_render_slider">
<property name="minimum">
<number>0</number>
</property>
<property name="maximum">
<number>16000</number>
</property>
<property name="singleStep">
<number>100</number>
</property>
<property name="pageStep">
<number>250</number>
</property>
<property name="value">
<number>0</number>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="tickPosition">
<enum>QSlider::TicksBelow</enum>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="delay_render_display_label">
<property name="text">
<string/>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout> </layout>
</widget> </widget>
</item> </item>

View File

@ -16,6 +16,7 @@
#include "citra_qt/configuration/configure_input.h" #include "citra_qt/configuration/configure_input.h"
#include "citra_qt/configuration/configure_motion_touch.h" #include "citra_qt/configuration/configure_motion_touch.h"
#include "common/param_package.h" #include "common/param_package.h"
#include "core/core.h"
#include "ui_configure_input.h" #include "ui_configure_input.h"
const std::array<std::string, ConfigureInput::ANALOG_SUB_BUTTONS_NUM> const std::array<std::string, ConfigureInput::ANALOG_SUB_BUTTONS_NUM>
@ -145,8 +146,8 @@ static QString AnalogToText(const Common::ParamPackage& param, const std::string
return QObject::tr("[unknown]"); return QObject::tr("[unknown]");
} }
ConfigureInput::ConfigureInput(QWidget* parent) ConfigureInput::ConfigureInput(Core::System& _system, QWidget* parent)
: QWidget(parent), ui(std::make_unique<Ui::ConfigureInput>()), : QWidget(parent), system(_system), ui(std::make_unique<Ui::ConfigureInput>()),
timeout_timer(std::make_unique<QTimer>()), poll_timer(std::make_unique<QTimer>()) { timeout_timer(std::make_unique<QTimer>()), poll_timer(std::make_unique<QTimer>()) {
ui->setupUi(this); ui->setupUi(this);
setFocusPolicy(Qt::ClickFocus); setFocusPolicy(Qt::ClickFocus);
@ -400,6 +401,9 @@ ConfigureInput::ConfigureInput(QWidget* parent)
ConfigureInput::~ConfigureInput() = default; ConfigureInput::~ConfigureInput() = default;
void ConfigureInput::ApplyConfiguration() { void ConfigureInput::ApplyConfiguration() {
Settings::values.use_artic_base_controller = ui->use_artic_controller->isChecked();
std::transform(buttons_param.begin(), buttons_param.end(), std::transform(buttons_param.begin(), buttons_param.end(),
Settings::values.current_input_profile.buttons.begin(), Settings::values.current_input_profile.buttons.begin(),
[](const Common::ParamPackage& param) { return param.Serialize(); }); [](const Common::ParamPackage& param) { return param.Serialize(); });
@ -444,6 +448,10 @@ QList<QKeySequence> ConfigureInput::GetUsedKeyboardKeys() {
} }
void ConfigureInput::LoadConfiguration() { void ConfigureInput::LoadConfiguration() {
ui->use_artic_controller->setChecked(Settings::values.use_artic_base_controller.GetValue());
ui->use_artic_controller->setEnabled(!system.IsPoweredOn());
std::transform(Settings::values.current_input_profile.buttons.begin(), std::transform(Settings::values.current_input_profile.buttons.begin(),
Settings::values.current_input_profile.buttons.end(), buttons_param.begin(), Settings::values.current_input_profile.buttons.end(), buttons_param.begin(),
[](const std::string& str) { return Common::ParamPackage(str); }); [](const std::string& str) { return Common::ParamPackage(str); });

View File

@ -30,7 +30,7 @@ class ConfigureInput : public QWidget {
Q_OBJECT Q_OBJECT
public: public:
explicit ConfigureInput(QWidget* parent = nullptr); explicit ConfigureInput(Core::System& system, QWidget* parent = nullptr);
~ConfigureInput() override; ~ConfigureInput() override;
/// Save all button configurations to settings file /// Save all button configurations to settings file
@ -50,6 +50,7 @@ signals:
void InputKeysChanged(QList<QKeySequence> new_key_list); void InputKeysChanged(QList<QKeySequence> new_key_list);
private: private:
Core::System& system;
std::unique_ptr<Ui::ConfigureInput> ui; std::unique_ptr<Ui::ConfigureInput> ui;
std::unique_ptr<QTimer> timeout_timer; std::unique_ptr<QTimer> timeout_timer;

View File

@ -841,6 +841,13 @@
</item> </item>
</layout> </layout>
</item> </item>
<item>
<widget class="QCheckBox" name="use_artic_controller">
<property name="text">
<string>Use Artic Controller when connected to Artic Base Server</string>
</property>
</widget>
</item>
</layout> </layout>
</item> </item>
</layout> </layout>

View File

@ -151,7 +151,14 @@ void ConfigurePerGame::LoadConfiguration() {
ui->display_title_id->setText( ui->display_title_id->setText(
QStringLiteral("%1").arg(title_id, 16, 16, QLatin1Char{'0'}).toUpper()); QStringLiteral("%1").arg(title_id, 16, 16, QLatin1Char{'0'}).toUpper());
const auto loader = Loader::GetLoader(filename); std::unique_ptr<Loader::AppLoader> loader_ptr;
Loader::AppLoader* loader;
if (system.IsPoweredOn()) {
loader = &system.GetAppLoader();
} else {
loader_ptr = Loader::GetLoader(filename);
loader = loader_ptr.get();
}
std::string title; std::string title;
if (loader->ReadTitle(title) == Loader::ResultStatus::Success) if (loader->ReadTitle(title) == Loader::ResultStatus::Success)

View File

@ -328,6 +328,8 @@ void ConfigureSystem::SetConfiguration() {
ui->edit_init_ticks_value->setText( ui->edit_init_ticks_value->setText(
QString::number(Settings::values.init_ticks_override.GetValue())); QString::number(Settings::values.init_ticks_override.GetValue()));
ui->spinBox_steps_per_hour->setValue(Settings::values.steps_per_hour.GetValue());
cfg = Service::CFG::GetModule(system); cfg = Service::CFG::GetModule(system);
ReadSystemSettings(); ReadSystemSettings();
@ -460,6 +462,8 @@ void ConfigureSystem::ApplyConfiguration() {
Settings::values.init_ticks_override = Settings::values.init_ticks_override =
static_cast<s64>(ui->edit_init_ticks_value->text().toLongLong()); static_cast<s64>(ui->edit_init_ticks_value->text().toLongLong());
Settings::values.steps_per_hour = static_cast<u16>(ui->spinBox_steps_per_hour->value());
s64 time_offset_time = ui->edit_init_time_offset_time->time().msecsSinceStartOfDay() / 1000; s64 time_offset_time = ui->edit_init_time_offset_time->time().msecsSinceStartOfDay() / 1000;
s64 time_offset_days = ui->edit_init_time_offset_days->value() * 86400; s64 time_offset_days = ui->edit_init_time_offset_days->value() * 86400;
@ -631,8 +635,10 @@ void ConfigureSystem::SetupPerGameUI() {
ui->label_language->setVisible(false); ui->label_language->setVisible(false);
ui->label_country->setVisible(false); ui->label_country->setVisible(false);
ui->label_play_coins->setVisible(false); ui->label_play_coins->setVisible(false);
ui->label_steps_per_hour->setVisible(false);
ui->edit_username->setVisible(false); ui->edit_username->setVisible(false);
ui->spinBox_play_coins->setVisible(false); ui->spinBox_play_coins->setVisible(false);
ui->spinBox_steps_per_hour->setVisible(false);
ui->combo_birthday->setVisible(false); ui->combo_birthday->setVisible(false);
ui->combo_birthmonth->setVisible(false); ui->combo_birthmonth->setVisible(false);
ui->combo_init_clock->setVisible(false); ui->combo_init_clock->setVisible(false);

View File

@ -13,6 +13,33 @@
<property name="windowTitle"> <property name="windowTitle">
<string>Form</string> <string>Form</string>
</property> </property>
<layout class="QVBoxLayout" name="verticalLayout_scrollbar">
<item>
<widget class="QScrollArea" name="scrollArea">
<property name="minimumSize">
<size>
<width>0</width>
<height>480</height>
</size>
</property>
<property name="frameShape">
<enum>QFrame::NoFrame</enum>
</property>
<property name="lineWidth">
<number>1</number>
</property>
<property name="widgetResizable">
<bool>true</bool>
</property>
<widget class="QWidget" name="scrollAreaWidgetContents">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>422</width>
<height>500</height>
</rect>
</property>
<layout class="QHBoxLayout" name="horizontalLayout"> <layout class="QHBoxLayout" name="horizontalLayout">
<item> <item>
<layout class="QVBoxLayout" name="verticalLayout"> <layout class="QVBoxLayout" name="verticalLayout">
@ -291,7 +318,7 @@
<item row="0" column="0"> <item row="0" column="0">
<widget class="QSpinBox" name="edit_init_time_offset_days"> <widget class="QSpinBox" name="edit_init_time_offset_days">
<property name="suffix"> <property name="suffix">
<string> days</string> <string>days</string>
</property> </property>
<property name="minimum"> <property name="minimum">
<number>-2147483648</number> <number>-2147483648</number>
@ -354,7 +381,7 @@
<item row="12" column="0"> <item row="12" column="0">
<widget class="QLabel" name="label_play_coins"> <widget class="QLabel" name="label_play_coins">
<property name="text"> <property name="text">
<string>Play Coins:</string> <string>Play Coins</string>
</property> </property>
</widget> </widget>
</item> </item>
@ -365,21 +392,38 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="13" column="0">
<widget class="QLabel" name="label_steps_per_hour">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Number of steps per hour reported by the pedometer. Range from 0 to 65,535.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Pedometer Steps per Hour</string>
</property>
</widget>
</item>
<item row="13" column="1"> <item row="13" column="1">
<widget class="QSpinBox" name="spinBox_steps_per_hour">
<property name="maximum">
<number>9999</number>
</property>
</widget>
</item>
<item row="14" column="1">
<widget class="QCheckBox" name="toggle_system_setup"> <widget class="QCheckBox" name="toggle_system_setup">
<property name="text"> <property name="text">
<string>Run System Setup when Home Menu is launched</string> <string>Run System Setup when Home Menu is launched</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="14" column="0"> <item row="15" column="0">
<widget class="QLabel" name="label_console_id"> <widget class="QLabel" name="label_console_id">
<property name="text"> <property name="text">
<string>Console ID:</string> <string>Console ID:</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="14" column="1"> <item row="15" column="1">
<widget class="QPushButton" name="button_regenerate_console_id"> <widget class="QPushButton" name="button_regenerate_console_id">
<property name="sizePolicy"> <property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed"> <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
@ -395,35 +439,35 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="15" column="0"> <item row="16" column="0">
<widget class="QLabel" name="label_plugin_loader"> <widget class="QLabel" name="label_plugin_loader">
<property name="text"> <property name="text">
<string>3GX Plugin Loader:</string> <string>3GX Plugin Loader:</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="15" column="1"> <item row="16" column="1">
<widget class="QCheckBox" name="plugin_loader"> <widget class="QCheckBox" name="plugin_loader">
<property name="text"> <property name="text">
<string>Enable 3GX plugin loader</string> <string>Enable 3GX plugin loader</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="16" column="1"> <item row="17" column="1">
<widget class="QCheckBox" name="allow_plugin_loader"> <widget class="QCheckBox" name="allow_plugin_loader">
<property name="text"> <property name="text">
<string>Allow games to change plugin loader state</string> <string>Allow games to change plugin loader state</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="17" column="0"> <item row="18" column="0">
<widget class="QLabel" name="label_nus_download"> <widget class="QLabel" name="label_nus_download">
<property name="text"> <property name="text">
<string>Download System Files from Nitendo servers</string> <string>Download System Files from Nitendo servers</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="17" column="1"> <item row="18" column="1">
<widget class="QWidget" name="body_nus_download"> <widget class="QWidget" name="body_nus_download">
<layout class="QHBoxLayout" name="horizontalLayout_nus_download"> <layout class="QHBoxLayout" name="horizontalLayout_nus_download">
<item> <item>
@ -525,7 +569,7 @@
<item> <item>
<widget class="QLabel" name="label_secure_info_status"> <widget class="QLabel" name="label_secure_info_status">
<property name="text"> <property name="text">
<string></string> <string/>
</property> </property>
</widget> </widget>
</item> </item>
@ -561,7 +605,7 @@
<item> <item>
<widget class="QLabel" name="label_friend_code_seed_status"> <widget class="QLabel" name="label_friend_code_seed_status">
<property name="text"> <property name="text">
<string></string> <string/>
</property> </property>
</widget> </widget>
</item> </item>
@ -597,7 +641,7 @@
<item> <item>
<widget class="QLabel" name="label_ct_cert_status"> <widget class="QLabel" name="label_ct_cert_status">
<property name="text"> <property name="text">
<string></string> <string/>
</property> </property>
</widget> </widget>
</item> </item>
@ -650,6 +694,10 @@
</item> </item>
</layout> </layout>
</widget> </widget>
</widget>
</item>
</layout>
</widget>
<tabstops> <tabstops>
<tabstop>toggle_new_3ds</tabstop> <tabstop>toggle_new_3ds</tabstop>
<tabstop>edit_username</tabstop> <tabstop>edit_username</tabstop>
@ -661,6 +709,7 @@
<tabstop>combo_init_clock</tabstop> <tabstop>combo_init_clock</tabstop>
<tabstop>edit_init_time</tabstop> <tabstop>edit_init_time</tabstop>
<tabstop>spinBox_play_coins</tabstop> <tabstop>spinBox_play_coins</tabstop>
<tabstop>spinBox_steps_per_hour</tabstop>
<tabstop>button_regenerate_console_id</tabstop> <tabstop>button_regenerate_console_id</tabstop>
</tabstops> </tabstops>
<resources/> <resources/>

View File

@ -5,6 +5,7 @@
#include <QBrush> #include <QBrush>
#include <QString> #include <QString>
#include <QTreeWidgetItem> #include <QTreeWidgetItem>
#include <QtGlobal>
#include <fmt/format.h> #include <fmt/format.h>
#include "citra_qt/debugger/ipc/record_dialog.h" #include "citra_qt/debugger/ipc/record_dialog.h"
#include "citra_qt/debugger/ipc/recorder.h" #include "citra_qt/debugger/ipc/recorder.h"
@ -22,8 +23,13 @@ IPCRecorderWidget::IPCRecorderWidget(Core::System& system_, QWidget* parent)
ui->setupUi(this); ui->setupUi(this);
qRegisterMetaType<IPCDebugger::RequestRecord>(); qRegisterMetaType<IPCDebugger::RequestRecord>();
#if QT_VERSION < QT_VERSION_CHECK(6, 7, 0)
connect(ui->enabled, &QCheckBox::stateChanged, this, connect(ui->enabled, &QCheckBox::stateChanged, this,
[this](int new_state) { SetEnabled(new_state == Qt::Checked); }); [this](int new_state) { SetEnabled(new_state == Qt::Checked); });
#else
connect(ui->enabled, &QCheckBox::checkStateChanged, this,
[this](int new_state) { SetEnabled(new_state == Qt::Checked); });
#endif
connect(ui->clearButton, &QPushButton::clicked, this, &IPCRecorderWidget::Clear); connect(ui->clearButton, &QPushButton::clicked, this, &IPCRecorderWidget::Clear);
connect(ui->filter, &QLineEdit::textChanged, this, &IPCRecorderWidget::ApplyFilterToAll); connect(ui->filter, &QLineEdit::textChanged, this, &IPCRecorderWidget::ApplyFilterToAll);
connect(ui->main, &QTreeWidget::itemDoubleClicked, this, &IPCRecorderWidget::OpenRecordDialog); connect(ui->main, &QTreeWidget::itemDoubleClicked, this, &IPCRecorderWidget::OpenRecordDialog);

View File

@ -27,7 +27,7 @@ static const std::unordered_map<AVOptionType, const char*> TypeNameMap{{
{AV_OPT_TYPE_STRING, QT_TR_NOOP("string")}, {AV_OPT_TYPE_STRING, QT_TR_NOOP("string")},
{AV_OPT_TYPE_DICT, QT_TR_NOOP("dictionary")}, {AV_OPT_TYPE_DICT, QT_TR_NOOP("dictionary")},
{AV_OPT_TYPE_VIDEO_RATE, QT_TR_NOOP("video rate")}, {AV_OPT_TYPE_VIDEO_RATE, QT_TR_NOOP("video rate")},
{AV_OPT_TYPE_CHANNEL_LAYOUT, QT_TR_NOOP("channel layout")}, {AV_OPT_TYPE_CHLAYOUT, QT_TR_NOOP("channel layout")},
}}; }};
static const std::unordered_map<AVOptionType, const char*> TypeDescriptionMap{{ static const std::unordered_map<AVOptionType, const char*> TypeDescriptionMap{{
@ -39,7 +39,7 @@ static const std::unordered_map<AVOptionType, const char*> TypeDescriptionMap{{
{AV_OPT_TYPE_DICT, {AV_OPT_TYPE_DICT,
QT_TR_NOOP("Comma-splitted list of &lt;key>=&lt;value>. Do not put spaces.")}, QT_TR_NOOP("Comma-splitted list of &lt;key>=&lt;value>. Do not put spaces.")},
{AV_OPT_TYPE_VIDEO_RATE, QT_TR_NOOP("&lt;num>/&lt;den>, or preset values like 'pal'.")}, {AV_OPT_TYPE_VIDEO_RATE, QT_TR_NOOP("&lt;num>/&lt;den>, or preset values like 'pal'.")},
{AV_OPT_TYPE_CHANNEL_LAYOUT, QT_TR_NOOP("Hexadecimal channel layout mask starting with '0x'.")}, {AV_OPT_TYPE_CHLAYOUT, QT_TR_NOOP("Hexadecimal channel layout mask starting with '0x'.")},
}}; }};
/// Get the preset values of an option. returns {display value, real value} /// Get the preset values of an option. returns {display value, real value}

View File

@ -974,7 +974,7 @@ void GMainWindow::UpdateMenuState() {
action->setEnabled(emulation_running); action->setEnabled(emulation_running);
} }
ui->action_Capture_Screenshot->setEnabled(emulation_running && !is_paused); ui->action_Capture_Screenshot->setEnabled(emulation_running);
if (emulation_running && is_paused) { if (emulation_running && is_paused) {
ui->action_Pause->setText(tr("&Continue")); ui->action_Pause->setText(tr("&Continue"));
@ -1216,7 +1216,10 @@ bool GMainWindow::LoadROM(const QString& filename) {
case Core::System::ResultStatus::ErrorArticDisconnected: case Core::System::ResultStatus::ErrorArticDisconnected:
QMessageBox::critical( QMessageBox::critical(
this, tr("Artic Base Server"), this, tr("Artic Base Server"),
tr("An error has occurred whilst communicating with the Artic Base Server.")); tr(fmt::format(
"An error has occurred whilst communicating with the Artic Base Server.\n{}",
system.GetStatusDetails())
.c_str()));
break; break;
default: default:
QMessageBox::critical( QMessageBox::critical(
@ -1238,6 +1241,10 @@ bool GMainWindow::LoadROM(const QString& filename) {
} }
void GMainWindow::BootGame(const QString& filename) { void GMainWindow::BootGame(const QString& filename) {
if (emu_thread) {
ShutdownGame();
}
const bool is_artic = filename.startsWith(QString::fromStdString("articbase://")); const bool is_artic = filename.startsWith(QString::fromStdString("articbase://"));
if (!is_artic && filename.endsWith(QStringLiteral(".cia"))) { if (!is_artic && filename.endsWith(QStringLiteral(".cia"))) {
@ -2412,15 +2419,25 @@ void GMainWindow::OnSaveMovie() {
} }
void GMainWindow::OnCaptureScreenshot() { void GMainWindow::OnCaptureScreenshot() {
if (!emu_thread || !emu_thread->IsRunning()) [[unlikely]] { if (!emu_thread) [[unlikely]] {
return; return;
} }
const bool was_running = emu_thread->IsRunning();
if (was_running ||
(QMessageBox::question(
this, tr("Game will unpause"),
tr("The game will be unpaused, and the next frame will be captured. Is this okay?"),
QMessageBox::Yes | QMessageBox::No, QMessageBox::No) == QMessageBox::Yes)) {
if (was_running) {
OnPauseGame(); OnPauseGame();
}
std::string path = UISettings::values.screenshot_path.GetValue(); std::string path = UISettings::values.screenshot_path.GetValue();
if (!FileUtil::IsDirectory(path)) { if (!FileUtil::IsDirectory(path)) {
if (!FileUtil::CreateFullPath(path)) { if (!FileUtil::CreateFullPath(path)) {
QMessageBox::information(this, tr("Invalid Screenshot Directory"), QMessageBox::information(
this, tr("Invalid Screenshot Directory"),
tr("Cannot create specified screenshot directory. Screenshot " tr("Cannot create specified screenshot directory. Screenshot "
"path is set back to its default value.")); "path is set back to its default value."));
path = FileUtil::GetUserPath(FileUtil::UserPath::UserDir); path = FileUtil::GetUserPath(FileUtil::UserPath::UserDir);
@ -2431,14 +2448,18 @@ void GMainWindow::OnCaptureScreenshot() {
static QRegularExpression expr(QStringLiteral("[\\/:?\"<>|]")); static QRegularExpression expr(QStringLiteral("[\\/:?\"<>|]"));
const std::string filename = game_title.remove(expr).toStdString(); const std::string filename = game_title.remove(expr).toStdString();
const std::string timestamp = const std::string timestamp = QDateTime::currentDateTime()
QDateTime::currentDateTime().toString(QStringLiteral("dd.MM.yy_hh.mm.ss.z")).toStdString(); .toString(QStringLiteral("dd.MM.yy_hh.mm.ss.z"))
.toStdString();
path.append(fmt::format("/{}_{}.png", filename, timestamp)); path.append(fmt::format("/{}_{}.png", filename, timestamp));
auto* const screenshot_window = secondary_window->HasFocus() ? secondary_window : render_window; auto* const screenshot_window =
screenshot_window->CaptureScreenshot(UISettings::values.screenshot_resolution_factor.GetValue(), secondary_window->HasFocus() ? secondary_window : render_window;
screenshot_window->CaptureScreenshot(
UISettings::values.screenshot_resolution_factor.GetValue(),
QString::fromStdString(path)); QString::fromStdString(path));
OnStartGame(); OnStartGame();
}
} }
void GMainWindow::OnDumpVideo() { void GMainWindow::OnDumpVideo() {
@ -2626,10 +2647,12 @@ void GMainWindow::UpdateStatusBar() {
const bool do_mb = results.artic_transmitted >= (1000.0 * 1000.0); const bool do_mb = results.artic_transmitted >= (1000.0 * 1000.0);
const double value = do_mb ? (results.artic_transmitted / (1000.0 * 1000.0)) const double value = do_mb ? (results.artic_transmitted / (1000.0 * 1000.0))
: (results.artic_transmitted / 1000.0); : (results.artic_transmitted / 1000.0);
static const std::array<std::pair<Core::PerfStats::PerfArticEventBits, QString>, 4> static const std::array<std::pair<Core::PerfStats::PerfArticEventBits, QString>, 5>
perf_events = { perf_events = {
std::make_pair(Core::PerfStats::PerfArticEventBits::ARTIC_SHARED_EXT_DATA, std::make_pair(Core::PerfStats::PerfArticEventBits::ARTIC_SHARED_EXT_DATA,
tr("(Accessing SharedExtData)")), tr("(Accessing SharedExtData)")),
std::make_pair(Core::PerfStats::PerfArticEventBits::ARTIC_SYSTEM_SAVE_DATA,
tr("(Accessing SystemSaveData)")),
std::make_pair(Core::PerfStats::PerfArticEventBits::ARTIC_BOSS_EXT_DATA, std::make_pair(Core::PerfStats::PerfArticEventBits::ARTIC_BOSS_EXT_DATA,
tr("(Accessing BossExtData)")), tr("(Accessing BossExtData)")),
std::make_pair(Core::PerfStats::PerfArticEventBits::ARTIC_EXT_DATA, std::make_pair(Core::PerfStats::PerfArticEventBits::ARTIC_EXT_DATA,
@ -2854,7 +2877,9 @@ void GMainWindow::OnCoreError(Core::System::ResultStatus result, std::string det
error_severity_icon = QMessageBox::Icon::Warning; error_severity_icon = QMessageBox::Icon::Warning;
} else if (result == Core::System::ResultStatus::ErrorArticDisconnected) { } else if (result == Core::System::ResultStatus::ErrorArticDisconnected) {
title = tr("Artic Base Server"); title = tr("Artic Base Server");
message = tr("A communication error has occurred. The game will quit."); message =
tr(fmt::format("A communication error has occurred. The game will quit.\n{}", details)
.c_str());
error_severity_icon = QMessageBox::Icon::Critical; error_severity_icon = QMessageBox::Icon::Critical;
can_continue = false; can_continue = false;
} else { } else {
@ -3068,8 +3093,8 @@ void GMainWindow::LoadTranslation() {
bool loaded; bool loaded;
if (UISettings::values.language.isEmpty()) { if (UISettings::values.language.isEmpty()) {
// If the selected language is empty, use system locale // Use the system's default locale
loaded = translator.load(QLocale(), {}, {}, QStringLiteral(":/languages/")); loaded = translator.load(QLocale::system(), {}, {}, QStringLiteral(":/languages/"));
} else { } else {
// Otherwise load from the specified file // Otherwise load from the specified file
loaded = translator.load(UISettings::values.language, QStringLiteral(":/languages/")); loaded = translator.load(UISettings::values.language, QStringLiteral(":/languages/"));

View File

@ -78,7 +78,8 @@ inline ABIFrameInfo ABI_CalculateFrameSize(std::bitset<64> regs, std::size_t fra
return ABIFrameInfo{static_cast<u32>(total_size), static_cast<u32>(fprs_base_subtraction)}; return ABIFrameInfo{static_cast<u32>(total_size), static_cast<u32>(fprs_base_subtraction)};
} }
inline void ABI_PushRegisters(oaknut::CodeGenerator& code, std::bitset<64> regs, template <typename Policy>
inline void ABI_PushRegisters(oaknut::BasicCodeGenerator<Policy>& code, std::bitset<64> regs,
std::size_t frame_size = 0) { std::size_t frame_size = 0) {
using namespace oaknut; using namespace oaknut;
using namespace oaknut::util; using namespace oaknut::util;
@ -137,7 +138,8 @@ inline void ABI_PushRegisters(oaknut::CodeGenerator& code, std::bitset<64> regs,
} }
} }
inline void ABI_PopRegisters(oaknut::CodeGenerator& code, std::bitset<64> regs, template <typename Policy>
inline void ABI_PopRegisters(oaknut::BasicCodeGenerator<Policy>& code, std::bitset<64> regs,
std::size_t frame_size = 0) { std::size_t frame_size = 0) {
using namespace oaknut; using namespace oaknut;
using namespace oaknut::util; using namespace oaknut::util;

View File

@ -38,6 +38,16 @@ inline void CallFarFunction(oaknut::CodeGenerator& code, const T f) {
} }
} }
template <typename T>
inline void CallFarFunction(oaknut::VectorCodeGenerator& code, const T f) {
static_assert(std::is_pointer_v<T>, "Argument must be a (function) pointer.");
// X16(IP0) and X17(IP1) is the standard veneer register
// LR is also available as an intermediate register
// https://developer.arm.com/documentation/102374/0101/Procedure-Call-Standard
code.MOVP2R(oaknut::util::X16, reinterpret_cast<const void*>(f));
code.BLR(oaknut::util::X16);
}
} // namespace Common::A64 } // namespace Common::A64
#endif // CITRA_ARCH(arm64) #endif // CITRA_ARCH(arm64)

View File

@ -65,6 +65,9 @@ avcodec_find_encoder_by_name_func avcodec_find_encoder_by_name;
avcodec_free_context_func avcodec_free_context; avcodec_free_context_func avcodec_free_context;
avcodec_get_class_func avcodec_get_class; avcodec_get_class_func avcodec_get_class;
avcodec_get_hw_config_func avcodec_get_hw_config; avcodec_get_hw_config_func avcodec_get_hw_config;
#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(61, 13, 100) // lavc 61.13.100
avcodec_get_supported_config_func avcodec_get_supported_config;
#endif
avcodec_open2_func avcodec_open2; avcodec_open2_func avcodec_open2;
avcodec_parameters_from_context_func avcodec_parameters_from_context; avcodec_parameters_from_context_func avcodec_parameters_from_context;
avcodec_receive_frame_func avcodec_receive_frame; avcodec_receive_frame_func avcodec_receive_frame;
@ -232,6 +235,9 @@ static bool LoadAVCodec() {
LOAD_SYMBOL(avcodec, avcodec_free_context); LOAD_SYMBOL(avcodec, avcodec_free_context);
LOAD_SYMBOL(avcodec, avcodec_get_class); LOAD_SYMBOL(avcodec, avcodec_get_class);
LOAD_SYMBOL(avcodec, avcodec_get_hw_config); LOAD_SYMBOL(avcodec, avcodec_get_hw_config);
#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(61, 13, 100) // lavc 61.13.100
LOAD_SYMBOL(avcodec, avcodec_get_supported_config);
#endif
LOAD_SYMBOL(avcodec, avcodec_open2); LOAD_SYMBOL(avcodec, avcodec_open2);
LOAD_SYMBOL(avcodec, avcodec_parameters_from_context); LOAD_SYMBOL(avcodec, avcodec_parameters_from_context);
LOAD_SYMBOL(avcodec, avcodec_receive_frame); LOAD_SYMBOL(avcodec, avcodec_receive_frame);

View File

@ -114,6 +114,10 @@ typedef const AVCodec* (*avcodec_find_encoder_by_name_func)(const char*);
typedef void (*avcodec_free_context_func)(AVCodecContext**); typedef void (*avcodec_free_context_func)(AVCodecContext**);
typedef const AVClass* (*avcodec_get_class_func)(); typedef const AVClass* (*avcodec_get_class_func)();
typedef const AVCodecHWConfig* (*avcodec_get_hw_config_func)(const AVCodec*, int); typedef const AVCodecHWConfig* (*avcodec_get_hw_config_func)(const AVCodec*, int);
#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(61, 13, 100) // lavc 61.13.100
typedef int (*avcodec_get_supported_config_func)(const AVCodecContext*, const AVCodec*,
enum AVCodecConfig, unsigned, const void**, int*);
#endif
typedef int (*avcodec_open2_func)(AVCodecContext*, const AVCodec*, AVDictionary**); typedef int (*avcodec_open2_func)(AVCodecContext*, const AVCodec*, AVDictionary**);
typedef int (*avcodec_parameters_from_context_func)(AVCodecParameters* par, const AVCodecContext*); typedef int (*avcodec_parameters_from_context_func)(AVCodecParameters* par, const AVCodecContext*);
typedef int (*avcodec_receive_frame_func)(AVCodecContext*, AVFrame*); typedef int (*avcodec_receive_frame_func)(AVCodecContext*, AVFrame*);
@ -138,6 +142,9 @@ extern avcodec_find_encoder_by_name_func avcodec_find_encoder_by_name;
extern avcodec_free_context_func avcodec_free_context; extern avcodec_free_context_func avcodec_free_context;
extern avcodec_get_class_func avcodec_get_class; extern avcodec_get_class_func avcodec_get_class;
extern avcodec_get_hw_config_func avcodec_get_hw_config; extern avcodec_get_hw_config_func avcodec_get_hw_config;
#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(61, 13, 100) // lavc 61.13.100
extern avcodec_get_supported_config_func avcodec_get_supported_config;
#endif
extern avcodec_open2_func avcodec_open2; extern avcodec_open2_func avcodec_open2;
extern avcodec_parameters_from_context_func avcodec_parameters_from_context; extern avcodec_parameters_from_context_func avcodec_parameters_from_context;
extern avcodec_receive_frame_func avcodec_receive_frame; extern avcodec_receive_frame_func avcodec_receive_frame;

View File

@ -68,7 +68,7 @@ public:
} }
void Flush() override { void Flush() override {
// stderr shouldn't be buffered std::fflush(stderr);
} }
void EnableForStacktrace() override { void EnableForStacktrace() override {
@ -263,8 +263,15 @@ public:
!boost::regex_search(FormatLogMessage(new_entry), regex_filter)) { !boost::regex_search(FormatLogMessage(new_entry), regex_filter)) {
return; return;
} }
if (Settings::values.instant_debug_log.GetValue()) {
ForEachBackend([&new_entry](Backend& backend) {
backend.Write(new_entry);
backend.Flush();
});
} else {
message_queue.EmplaceWait(new_entry); message_queue.EmplaceWait(new_entry);
} }
}
private: private:
Impl(const std::string& file_backend_filename, const Filter& filter_) Impl(const std::string& file_backend_filename, const Filter& filter_)

View File

@ -12,9 +12,9 @@
#if FMT_VERSION >= 80100 #if FMT_VERSION >= 80100
template <typename T> template <typename T>
struct fmt::formatter<T, std::enable_if_t<std::is_enum_v<T>, char>> struct fmt::formatter<T, std::enable_if_t<std::is_enum_v<T>, char>>
: formatter<std::underlying_type_t<T>> { : fmt::formatter<std::underlying_type_t<T>> {
template <typename FormatContext> template <typename FormatContext>
auto format(const T& value, FormatContext& ctx) -> decltype(ctx.out()) { auto format(const T& value, FormatContext& ctx) const -> decltype(ctx.out()) {
return fmt::formatter<std::underlying_type_t<T>>::format( return fmt::formatter<std::underlying_type_t<T>>::format(
static_cast<std::underlying_type_t<T>>(value), ctx); static_cast<std::underlying_type_t<T>>(value), ctx);
} }

View File

@ -83,6 +83,7 @@ void LogSettings() {
LOG_INFO(Config, "Citra Configuration:"); LOG_INFO(Config, "Citra Configuration:");
log_setting("Core_UseCpuJit", values.use_cpu_jit.GetValue()); log_setting("Core_UseCpuJit", values.use_cpu_jit.GetValue());
log_setting("Core_CPUClockPercentage", values.cpu_clock_percentage.GetValue()); log_setting("Core_CPUClockPercentage", values.cpu_clock_percentage.GetValue());
log_setting("Controller_UseArticController", values.use_artic_base_controller.GetValue());
log_setting("Renderer_UseGLES", values.use_gles.GetValue()); log_setting("Renderer_UseGLES", values.use_gles.GetValue());
log_setting("Renderer_GraphicsAPI", GetGraphicsAPIName(values.graphics_api.GetValue())); log_setting("Renderer_GraphicsAPI", GetGraphicsAPIName(values.graphics_api.GetValue()));
log_setting("Renderer_AsyncShaders", values.async_shader_compilation.GetValue()); log_setting("Renderer_AsyncShaders", values.async_shader_compilation.GetValue());
@ -100,6 +101,7 @@ void LogSettings() {
log_setting("Renderer_TextureFilter", GetTextureFilterName(values.texture_filter.GetValue())); log_setting("Renderer_TextureFilter", GetTextureFilterName(values.texture_filter.GetValue()));
log_setting("Renderer_TextureSampling", log_setting("Renderer_TextureSampling",
GetTextureSamplingName(values.texture_sampling.GetValue())); GetTextureSamplingName(values.texture_sampling.GetValue()));
log_setting("Renderer_DelayGameRenderThreasUs", values.delay_game_render_thread_us.GetValue());
log_setting("Stereoscopy_Render3d", values.render_3d.GetValue()); log_setting("Stereoscopy_Render3d", values.render_3d.GetValue());
log_setting("Stereoscopy_Factor3d", values.factor_3d.GetValue()); log_setting("Stereoscopy_Factor3d", values.factor_3d.GetValue());
log_setting("Stereoscopy_MonoRenderOption", values.mono_render_option.GetValue()); log_setting("Stereoscopy_MonoRenderOption", values.mono_render_option.GetValue());
@ -145,6 +147,7 @@ void LogSettings() {
log_setting("Debugging_DelayStartForLLEModules", values.delay_start_for_lle_modules.GetValue()); log_setting("Debugging_DelayStartForLLEModules", values.delay_start_for_lle_modules.GetValue());
log_setting("Debugging_UseGdbstub", values.use_gdbstub.GetValue()); log_setting("Debugging_UseGdbstub", values.use_gdbstub.GetValue());
log_setting("Debugging_GdbstubPort", values.gdbstub_port.GetValue()); log_setting("Debugging_GdbstubPort", values.gdbstub_port.GetValue());
log_setting("Debugging_InstantDebugLog", values.instant_debug_log.GetValue());
} }
bool IsConfiguringGlobal() { bool IsConfiguringGlobal() {
@ -192,6 +195,7 @@ void RestoreGlobalState(bool is_powered_on) {
values.frame_limit.SetGlobal(true); values.frame_limit.SetGlobal(true);
values.texture_filter.SetGlobal(true); values.texture_filter.SetGlobal(true);
values.texture_sampling.SetGlobal(true); values.texture_sampling.SetGlobal(true);
values.delay_game_render_thread_us.SetGlobal(true);
values.layout_option.SetGlobal(true); values.layout_option.SetGlobal(true);
values.swap_screen.SetGlobal(true); values.swap_screen.SetGlobal(true);
values.upright_screen.SetGlobal(true); values.upright_screen.SetGlobal(true);

View File

@ -425,6 +425,7 @@ struct Values {
int current_input_profile_index; ///< The current input profile index int current_input_profile_index; ///< The current input profile index
std::vector<InputProfile> input_profiles; ///< The list of input profiles std::vector<InputProfile> input_profiles; ///< The list of input profiles
std::vector<TouchFromButtonMap> touch_from_button_maps; std::vector<TouchFromButtonMap> touch_from_button_maps;
Setting<bool> use_artic_base_controller{false, "use_artic_base_controller"};
SwitchableSetting<bool> enable_gamemode{true, "enable_gamemode"}; SwitchableSetting<bool> enable_gamemode{true, "enable_gamemode"};
@ -447,6 +448,7 @@ struct Values {
Setting<s64> init_ticks_override{0, "init_ticks_override"}; Setting<s64> init_ticks_override{0, "init_ticks_override"};
Setting<bool> plugin_loader_enabled{false, "plugin_loader"}; Setting<bool> plugin_loader_enabled{false, "plugin_loader"};
Setting<bool> allow_plugin_loader{true, "allow_plugin_loader"}; Setting<bool> allow_plugin_loader{true, "allow_plugin_loader"};
Setting<u16> steps_per_hour{0, "steps_per_hour"};
// Renderer // Renderer
SwitchableSetting<GraphicsAPI, true> graphics_api { SwitchableSetting<GraphicsAPI, true> graphics_api {
@ -479,6 +481,8 @@ struct Values {
SwitchableSetting<TextureFilter> texture_filter{TextureFilter::None, "texture_filter"}; SwitchableSetting<TextureFilter> texture_filter{TextureFilter::None, "texture_filter"};
SwitchableSetting<TextureSampling> texture_sampling{TextureSampling::GameControlled, SwitchableSetting<TextureSampling> texture_sampling{TextureSampling::GameControlled,
"texture_sampling"}; "texture_sampling"};
SwitchableSetting<u16, true> delay_game_render_thread_us{0, 0, 16000,
"delay_game_render_thread_us"};
SwitchableSetting<LayoutOption> layout_option{LayoutOption::Default, "layout_option"}; SwitchableSetting<LayoutOption> layout_option{LayoutOption::Default, "layout_option"};
SwitchableSetting<bool> swap_screen{false, "swap_screen"}; SwitchableSetting<bool> swap_screen{false, "swap_screen"};
@ -539,6 +543,7 @@ struct Values {
Setting<bool> delay_start_for_lle_modules{true, "delay_start_for_lle_modules"}; Setting<bool> delay_start_for_lle_modules{true, "delay_start_for_lle_modules"};
Setting<bool> use_gdbstub{false, "use_gdbstub"}; Setting<bool> use_gdbstub{false, "use_gdbstub"};
Setting<u16> gdbstub_port{24689, "gdbstub_port"}; Setting<u16> gdbstub_port{24689, "gdbstub_port"};
Setting<bool> instant_debug_log{false, "instant_debug_log"};
// Miscellaneous // Miscellaneous
Setting<std::string> log_filter{"*:Info", "log_filter"}; Setting<std::string> log_filter{"*:Info", "log_filter"};

View File

@ -203,6 +203,8 @@ add_library(citra_core STATIC
hle/service/ac/ac_u.h hle/service/ac/ac_u.h
hle/service/act/act.cpp hle/service/act/act.cpp
hle/service/act/act.h hle/service/act/act.h
hle/service/act/act_errors.cpp
hle/service/act/act_errors.h
hle/service/act/act_a.cpp hle/service/act/act_a.cpp
hle/service/act/act_a.h hle/service/act/act_a.h
hle/service/act/act_u.cpp hle/service/act/act_u.cpp

View File

@ -171,12 +171,21 @@ bool FFmpegVideoStream::Init(FFmpegMuxer& muxer, const Layout::FramebufferLayout
codec_context->gop_size = 12; codec_context->gop_size = 12;
// Get pixel format for codec // Get pixel format for codec
#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(61, 13, 100) // lavc 61.13.100
const enum AVPixelFormat* pix_fmts = NULL;
int ret = FFmpeg::avcodec_get_supported_config(NULL, codec, AV_CODEC_CONFIG_PIX_FORMAT, 0,
(const void**)&pix_fmts, NULL);
#else
const enum AVPixelFormat* pix_fmts = codec->pix_fmts;
int ret = 0;
#endif
auto options = ToAVDictionary(Settings::values.video_encoder_options); auto options = ToAVDictionary(Settings::values.video_encoder_options);
auto pixel_format_opt = FFmpeg::av_dict_get(options, "pixel_format", nullptr, 0); auto pixel_format_opt = FFmpeg::av_dict_get(options, "pixel_format", nullptr, 0);
if (pixel_format_opt) { if (pixel_format_opt) {
sw_pixel_format = FFmpeg::av_get_pix_fmt(pixel_format_opt->value); sw_pixel_format = FFmpeg::av_get_pix_fmt(pixel_format_opt->value);
} else if (codec->pix_fmts) { } else if (ret >= 0 && pix_fmts) {
sw_pixel_format = GetPixelFormat(codec_context.get(), codec->pix_fmts); sw_pixel_format = GetPixelFormat(codec_context.get(), pix_fmts);
} else { } else {
sw_pixel_format = AV_PIX_FMT_YUV420P; sw_pixel_format = AV_PIX_FMT_YUV420P;
} }
@ -285,11 +294,20 @@ void FFmpegVideoStream::ProcessFrame(VideoFrame& frame) {
} }
bool FFmpegVideoStream::InitHWContext(const AVCodec* codec) { bool FFmpegVideoStream::InitHWContext(const AVCodec* codec) {
for (std::size_t i = 0; codec->pix_fmts[i] != AV_PIX_FMT_NONE; ++i) { #if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(61, 13, 100) // lavc 61.13.100
const enum AVPixelFormat* pix_fmts = NULL;
int ret = FFmpeg::avcodec_get_supported_config(NULL, codec, AV_CODEC_CONFIG_PIX_FORMAT, 0,
(const void**)&pix_fmts, NULL);
#else
const enum AVPixelFormat* pix_fmts = codec->pix_fmts;
int ret = 0;
#endif
for (std::size_t i = 0; (ret >= 0 && pix_fmts) && (pix_fmts[i] != AV_PIX_FMT_NONE); ++i) {
const AVCodecHWConfig* config; const AVCodecHWConfig* config;
for (int j = 0;; ++j) { for (int j = 0;; ++j) {
config = FFmpeg::avcodec_get_hw_config(codec, j); config = FFmpeg::avcodec_get_hw_config(codec, j);
if (!config || config->pix_fmt == codec->pix_fmts[i]) { if (!config || config->pix_fmt == pix_fmts[i]) {
break; break;
} }
} }
@ -303,7 +321,7 @@ bool FFmpegVideoStream::InitHWContext(const AVCodec* codec) {
continue; continue;
} }
codec_context->pix_fmt = codec->pix_fmts[i]; codec_context->pix_fmt = pix_fmts[i];
// Create HW device context // Create HW device context
AVBufferRef* hw_device_context; AVBufferRef* hw_device_context;
@ -351,7 +369,7 @@ bool FFmpegVideoStream::InitHWContext(const AVCodec* codec) {
AVHWFramesContext* hw_frames_context = AVHWFramesContext* hw_frames_context =
reinterpret_cast<AVHWFramesContext*>(hw_frames_context_ref->data); reinterpret_cast<AVHWFramesContext*>(hw_frames_context_ref->data);
hw_frames_context->format = codec->pix_fmts[i]; hw_frames_context->format = pix_fmts[i];
hw_frames_context->sw_format = sw_pixel_format; hw_frames_context->sw_format = sw_pixel_format;
hw_frames_context->width = codec_context->width; hw_frames_context->width = codec_context->width;
hw_frames_context->height = codec_context->height; hw_frames_context->height = codec_context->height;
@ -455,6 +473,7 @@ bool FFmpegAudioStream::Init(FFmpegMuxer& muxer) {
} }
frame_count = 0; frame_count = 0;
int ret;
// Initialize audio codec // Initialize audio codec
const AVCodec* codec = const AVCodec* codec =
@ -468,16 +487,33 @@ bool FFmpegAudioStream::Init(FFmpegMuxer& muxer) {
// Configure audio codec context // Configure audio codec context
codec_context->codec_type = AVMEDIA_TYPE_AUDIO; codec_context->codec_type = AVMEDIA_TYPE_AUDIO;
codec_context->bit_rate = Settings::values.audio_bitrate; codec_context->bit_rate = Settings::values.audio_bitrate;
if (codec->sample_fmts) {
codec_context->sample_fmt = codec->sample_fmts[0]; #if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(61, 13, 100) // lavc 61.13.100
const enum AVSampleFormat* sample_fmts = NULL;
ret = FFmpeg::avcodec_get_supported_config(NULL, codec, AV_CODEC_CONFIG_SAMPLE_FORMAT, 0,
(const void**)&sample_fmts, NULL);
#else
const enum AVSampleFormat* sample_fmts = codec->sample_fmts;
ret = 0;
#endif
if (ret >= 0 && sample_fmts) {
codec_context->sample_fmt = sample_fmts[0];
} else { } else {
codec_context->sample_fmt = AV_SAMPLE_FMT_S16P; codec_context->sample_fmt = AV_SAMPLE_FMT_S16P;
} }
if (codec->supported_samplerates) { #if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(61, 13, 100) // lavc 61.13.100
codec_context->sample_rate = codec->supported_samplerates[0]; const int* supported_samplerates = NULL;
ret = FFmpeg::avcodec_get_supported_config(NULL, codec, AV_CODEC_CONFIG_SAMPLE_RATE, 0,
(const void**)&supported_samplerates, NULL);
#else
const int* supported_samplerates = codec->supported_samplerates;
ret = 0;
#endif
if (ret >= 0 && supported_samplerates) {
codec_context->sample_rate = supported_samplerates[0];
// Prefer native sample rate if supported // Prefer native sample rate if supported
const int* ptr = codec->supported_samplerates; const int* ptr = supported_samplerates;
while ((*ptr)) { while ((*ptr)) {
if ((*ptr) == AudioCore::native_sample_rate) { if ((*ptr) == AudioCore::native_sample_rate) {
codec_context->sample_rate = AudioCore::native_sample_rate; codec_context->sample_rate = AudioCore::native_sample_rate;
@ -956,7 +992,7 @@ std::string FormatDefaultValue(const AVOption* option,
case AV_OPT_TYPE_VIDEO_RATE: { case AV_OPT_TYPE_VIDEO_RATE: {
return ToStdString(option->default_val.str); return ToStdString(option->default_val.str);
} }
case AV_OPT_TYPE_CHANNEL_LAYOUT: { case AV_OPT_TYPE_CHLAYOUT: {
return fmt::format("{:#x}", option->default_val.i64); return fmt::format("{:#x}", option->default_val.i64);
} }
default: default:

View File

@ -387,24 +387,35 @@ ResultVal<std::size_t> ArticFileBackend::Read(u64 offset, std::size_t length, u8
return cache->Read(file_handle, offset, length, buffer); return cache->Read(file_handle, offset, length, buffer);
} }
size_t read_amount = 0;
while (read_amount != length) {
size_t to_read =
std::min<size_t>(client->GetServerRequestMaxSize() - 0x100, length - read_amount);
auto req = client->NewRequest("FSFILE_Read"); auto req = client->NewRequest("FSFILE_Read");
req.AddParameterS32(file_handle); req.AddParameterS32(file_handle);
req.AddParameterU64(offset); req.AddParameterS64(static_cast<s64>(offset + read_amount));
req.AddParameterU32(static_cast<u32>(length)); req.AddParameterS32(static_cast<s32>(to_read));
auto resp = client->Send(req); auto resp = client->Send(req);
auto res = ArticArchive::RespResult(resp); if (!resp.has_value() || !resp->Succeeded())
return Result(-1);
auto res = Result(static_cast<u32>(resp->GetMethodResult()));
if (res.IsError()) if (res.IsError())
return res; return res;
auto read_buf = resp->GetResponseBuffer(0); auto read_buff = resp->GetResponseBuffer(0);
if (!read_buf || read_buf->second > length) { size_t actually_read = 0;
return std::size_t(0); if (read_buff.has_value()) {
actually_read = read_buff->second;
memcpy(buffer + read_amount, read_buff->first, actually_read);
} }
memcpy(buffer, read_buf->first, read_buf->second); read_amount += actually_read;
return read_buf->second; if (actually_read != to_read)
break;
}
return read_amount;
} }
ResultVal<std::size_t> ArticFileBackend::Write(u64 offset, std::size_t length, bool flush, ResultVal<std::size_t> ArticFileBackend::Write(u64 offset, std::size_t length, bool flush,
@ -415,25 +426,36 @@ ResultVal<std::size_t> ArticFileBackend::Write(u64 offset, std::size_t length, b
if (cache != nullptr) { if (cache != nullptr) {
return cache->Write(file_handle, offset, length, buffer, flags); return cache->Write(file_handle, offset, length, buffer, flags);
} else { } else {
size_t written_amount = 0;
while (written_amount != length) {
size_t to_write = std::min<size_t>(client->GetServerRequestMaxSize() - 0x100,
length - written_amount);
auto req = client->NewRequest("FSFILE_Write"); auto req = client->NewRequest("FSFILE_Write");
req.AddParameterS32(file_handle); req.AddParameterS32(file_handle);
req.AddParameterU64(offset); req.AddParameterS64(static_cast<s64>(offset + written_amount));
req.AddParameterU32(static_cast<u32>(length)); req.AddParameterS32(static_cast<s32>(to_write));
req.AddParameterU32(flags); req.AddParameterS32(static_cast<s32>(flags));
req.AddParameterBuffer(buffer, length); req.AddParameterBuffer(buffer + written_amount, to_write);
auto resp = client->Send(req); auto resp = client->Send(req);
auto res = ArticArchive::RespResult(resp); if (!resp.has_value() || !resp->Succeeded())
return Result(-1);
auto res = Result(static_cast<u32>(resp->GetMethodResult()));
if (res.IsError()) if (res.IsError())
return res; return res;
auto writen_buf = resp->GetResponseS32(0); auto actually_written_opt = resp->GetResponseS32(0);
if (!writen_buf) { if (!actually_written_opt.has_value())
return std::size_t(0); return Result(-1);
}
return std::size_t(*writen_buf); size_t actually_written = static_cast<size_t>(actually_written_opt.value());
written_amount += actually_written;
if (actually_written != to_write)
break;
}
return written_amount;
} }
} }

View File

@ -10,6 +10,7 @@
#include "common/archives.h" #include "common/archives.h"
#include "common/common_types.h" #include "common/common_types.h"
#include "common/file_util.h" #include "common/file_util.h"
#include "core/file_sys/archive_artic.h"
#include "core/file_sys/archive_systemsavedata.h" #include "core/file_sys/archive_systemsavedata.h"
#include "core/file_sys/errors.h" #include "core/file_sys/errors.h"
#include "core/file_sys/savedata_archive.h" #include "core/file_sys/savedata_archive.h"
@ -52,24 +53,45 @@ Path ConstructSystemSaveDataBinaryPath(u32 high, u32 low) {
ArchiveFactory_SystemSaveData::ArchiveFactory_SystemSaveData(const std::string& nand_path) ArchiveFactory_SystemSaveData::ArchiveFactory_SystemSaveData(const std::string& nand_path)
: base_path(GetSystemSaveDataContainerPath(nand_path)) {} : base_path(GetSystemSaveDataContainerPath(nand_path)) {}
static bool AllowArticSystemSaveData(const Path& path) {
constexpr u32 APP_SYSTEM_SAVE_DATA_MASK = 0x00020000;
if (path.GetType() == FileSys::LowPathType::Binary) {
std::vector<u8> path_data = path.AsBinary();
return path_data.size() == 8 &&
(*reinterpret_cast<u32*>(path_data.data() + 4) & APP_SYSTEM_SAVE_DATA_MASK) != 0;
}
return false;
}
ResultVal<std::unique_ptr<ArchiveBackend>> ArchiveFactory_SystemSaveData::Open(const Path& path, ResultVal<std::unique_ptr<ArchiveBackend>> ArchiveFactory_SystemSaveData::Open(const Path& path,
u64 program_id) { u64 program_id) {
if (IsUsingArtic() && AllowArticSystemSaveData(path)) {
EnsureCacheCreated();
return ArticArchive::Open(artic_client, Service::FS::ArchiveIdCode::SystemSaveData, path,
Core::PerfStats::PerfArticEventBits::ARTIC_SYSTEM_SAVE_DATA,
*this, false);
} else {
std::string fullpath = GetSystemSaveDataPath(base_path, path); std::string fullpath = GetSystemSaveDataPath(base_path, path);
if (!FileUtil::Exists(fullpath)) { if (!FileUtil::Exists(fullpath)) {
// TODO(Subv): Check error code, this one is probably wrong // TODO(Subv): Check error code, this one is probably wrong
return ResultNotFound; return ResultNotFound;
} }
return std::make_unique<SaveDataArchive>(fullpath); return std::make_unique<SaveDataArchive>(fullpath);
}
} }
Result ArchiveFactory_SystemSaveData::Format(const Path& path, Result ArchiveFactory_SystemSaveData::Format(const Path& path,
const FileSys::ArchiveFormatInfo& format_info, const FileSys::ArchiveFormatInfo& format_info,
u64 program_id, u32 directory_buckets, u64 program_id, u32 directory_buckets,
u32 file_buckets) { u32 file_buckets) {
std::string fullpath = GetSystemSaveDataPath(base_path, path); const std::vector<u8> vec_data = path.AsBinary();
FileUtil::DeleteDirRecursively(fullpath); u32 save_low;
FileUtil::CreateFullPath(fullpath); u32 save_high;
return ResultSuccess; std::memcpy(&save_low, &vec_data[4], sizeof(u32));
std::memcpy(&save_high, &vec_data[0], sizeof(u32));
return FormatAsSysData(save_high, save_low, format_info.total_size, 0x1000,
format_info.number_directories, format_info.number_files,
directory_buckets, file_buckets, format_info.duplicate_data);
} }
ResultVal<ArchiveFormatInfo> ArchiveFactory_SystemSaveData::GetFormatInfo(const Path& path, ResultVal<ArchiveFormatInfo> ArchiveFactory_SystemSaveData::GetFormatInfo(const Path& path,
@ -79,4 +101,45 @@ ResultVal<ArchiveFormatInfo> ArchiveFactory_SystemSaveData::GetFormatInfo(const
return ResultUnknown; return ResultUnknown;
} }
Result ArchiveFactory_SystemSaveData::FormatAsSysData(u32 high, u32 low, u32 total_size,
u32 block_size, u32 number_directories,
u32 number_files,
u32 number_directory_buckets,
u32 number_file_buckets, u8 duplicate_data) {
if (IsUsingArtic() &&
AllowArticSystemSaveData(FileSys::ConstructSystemSaveDataBinaryPath(high, low))) {
auto req = artic_client->NewRequest("FSUSER_CreateSysSaveData");
req.AddParameterU32(high);
req.AddParameterU32(low);
req.AddParameterU32(total_size);
req.AddParameterU32(block_size);
req.AddParameterU32(number_directories);
req.AddParameterU32(number_files);
req.AddParameterU32(number_directory_buckets);
req.AddParameterU32(number_file_buckets);
req.AddParameterU8(duplicate_data);
auto resp = artic_client->Send(req);
if (!resp.has_value() || !resp->Succeeded()) {
return ResultUnknown;
}
Result res(static_cast<u32>(resp->GetMethodResult()));
return res;
} else {
// Construct the binary path to the archive first
const FileSys::Path path = FileSys::ConstructSystemSaveDataBinaryPath(high, low);
const std::string& nand_directory = FileUtil::GetUserPath(FileUtil::UserPath::NANDDir);
const std::string base_path = FileSys::GetSystemSaveDataContainerPath(nand_directory);
const std::string systemsavedata_path = FileSys::GetSystemSaveDataPath(base_path, path);
if (!FileUtil::CreateFullPath(systemsavedata_path)) {
return ResultUnknown; // TODO(Subv): Find the right error code
}
return ResultSuccess;
}
}
} // namespace FileSys } // namespace FileSys

View File

@ -10,12 +10,15 @@
#include <boost/serialization/string.hpp> #include <boost/serialization/string.hpp>
#include "common/common_types.h" #include "common/common_types.h"
#include "core/file_sys/archive_backend.h" #include "core/file_sys/archive_backend.h"
#include "core/file_sys/artic_cache.h"
#include "core/hle/result.h" #include "core/hle/result.h"
#include "core/hle/service/fs/archive.h"
#include "network/artic_base/artic_base_client.h"
namespace FileSys { namespace FileSys {
/// File system interface to the SystemSaveData archive /// File system interface to the SystemSaveData archive
class ArchiveFactory_SystemSaveData final : public ArchiveFactory { class ArchiveFactory_SystemSaveData final : public ArchiveFactory, public ArticCacheProvider {
public: public:
explicit ArchiveFactory_SystemSaveData(const std::string& mount_point); explicit ArchiveFactory_SystemSaveData(const std::string& mount_point);
@ -24,13 +27,31 @@ public:
u32 directory_buckets, u32 file_buckets) override; u32 directory_buckets, u32 file_buckets) override;
ResultVal<ArchiveFormatInfo> GetFormatInfo(const Path& path, u64 program_id) const override; ResultVal<ArchiveFormatInfo> GetFormatInfo(const Path& path, u64 program_id) const override;
Result FormatAsSysData(u32 high, u32 low, u32 total_size, u32 block_size,
u32 number_directories, u32 number_files, u32 number_directory_buckets,
u32 number_file_buckets, u8 duplicate_data);
std::string GetName() const override { std::string GetName() const override {
return "SystemSaveData"; return "SystemSaveData";
} }
bool IsSlow() override {
return IsUsingArtic();
}
void RegisterArtic(std::shared_ptr<Network::ArticBase::Client>& client) {
artic_client = client;
}
bool IsUsingArtic() const {
return artic_client.get() != nullptr;
}
private: private:
std::string base_path; std::string base_path;
std::shared_ptr<Network::ArticBase::Client> artic_client = nullptr;
ArchiveFactory_SystemSaveData() = default; ArchiveFactory_SystemSaveData() = default;
template <class Archive> template <class Archive>
void serialize(Archive& ar, const unsigned int) { void serialize(Archive& ar, const unsigned int) {

View File

@ -200,11 +200,12 @@ ResultVal<size_t> ArticCache::ReadFromArtic(s32 file_handle, u8* buffer, size_t
return res; return res;
auto read_buff = resp->GetResponseBuffer(0); auto read_buff = resp->GetResponseBuffer(0);
if (!read_buff.has_value()) size_t actually_read = 0;
return Result(-1); if (read_buff.has_value()) {
size_t actually_read = read_buff->second; actually_read = read_buff->second;
memcpy(buffer + read_amount, read_buff->first, actually_read); memcpy(buffer + read_amount, read_buff->first, actually_read);
}
read_amount += actually_read; read_amount += actually_read;
if (actually_read != to_read) if (actually_read != to_read)
break; break;

View File

@ -179,6 +179,8 @@ Loader::ResultStatus FileSys::Plugin3GXLoader::Map(
4 * 1024 * 1024 // 4 MiB 4 * 1024 * 1024 // 4 MiB
}; };
const bool is_mem_private = header.infos.flags.use_private_memory != 0;
// Map memory block. This behaviour mimics how plugins are loaded on 3DS as much as possible. // Map memory block. This behaviour mimics how plugins are loaded on 3DS as much as possible.
// Calculate the sizes of the different memory regions // Calculate the sizes of the different memory regions
const u32 block_size = mem_region_sizes[header.infos.flags.memory_region_size.Value()]; const u32 block_size = mem_region_sizes[header.infos.flags.memory_region_size.Value()];
@ -199,7 +201,8 @@ Loader::ResultStatus FileSys::Plugin3GXLoader::Map(
std::fill(backing_memory_fb.GetPtr(), backing_memory_fb.GetPtr() + _3GX_fb_size, 0); std::fill(backing_memory_fb.GetPtr(), backing_memory_fb.GetPtr() + _3GX_fb_size, 0);
auto vma_heap_fb = process.vm_manager.MapBackingMemory( auto vma_heap_fb = process.vm_manager.MapBackingMemory(
_3GX_heap_load_addr, backing_memory_fb, _3GX_fb_size, Kernel::MemoryState::Continuous); _3GX_heap_load_addr, backing_memory_fb, _3GX_fb_size,
is_mem_private ? Kernel::MemoryState::Private : Kernel::MemoryState::Shared);
ASSERT(vma_heap_fb.Succeeded()); ASSERT(vma_heap_fb.Succeeded());
process.vm_manager.Reprotect(vma_heap_fb.Unwrap(), Kernel::VMAPermission::ReadWrite); process.vm_manager.Reprotect(vma_heap_fb.Unwrap(), Kernel::VMAPermission::ReadWrite);
@ -217,7 +220,8 @@ Loader::ResultStatus FileSys::Plugin3GXLoader::Map(
// Then we map part of the memory, which contains the executable // Then we map part of the memory, which contains the executable
auto vma = process.vm_manager.MapBackingMemory(_3GX_exe_load_addr, backing_memory, exe_size, auto vma = process.vm_manager.MapBackingMemory(_3GX_exe_load_addr, backing_memory, exe_size,
Kernel::MemoryState::Continuous); is_mem_private ? Kernel::MemoryState::Private
: Kernel::MemoryState::Shared);
ASSERT(vma.Succeeded()); ASSERT(vma.Succeeded());
process.vm_manager.Reprotect(vma.Unwrap(), Kernel::VMAPermission::ReadWriteExecute); process.vm_manager.Reprotect(vma.Unwrap(), Kernel::VMAPermission::ReadWriteExecute);
@ -256,7 +260,8 @@ Loader::ResultStatus FileSys::Plugin3GXLoader::Map(
// Map the rest of the memory at the heap location // Map the rest of the memory at the heap location
auto vma_heap = process.vm_manager.MapBackingMemory( auto vma_heap = process.vm_manager.MapBackingMemory(
_3GX_heap_load_addr + _3GX_fb_size, backing_memory_heap, _3GX_heap_load_addr + _3GX_fb_size, backing_memory_heap,
block_size - exe_size - _3GX_fb_size, Kernel::MemoryState::Continuous); block_size - exe_size - _3GX_fb_size,
is_mem_private ? Kernel::MemoryState::Private : Kernel::MemoryState::Shared);
ASSERT(vma_heap.Succeeded()); ASSERT(vma_heap.Succeeded());
process.vm_manager.Reprotect(vma_heap.Unwrap(), Kernel::VMAPermission::ReadWriteExecute); process.vm_manager.Reprotect(vma_heap.Unwrap(), Kernel::VMAPermission::ReadWriteExecute);

View File

@ -94,6 +94,9 @@ private:
BitField<1, 1, u32_le> embedded_swap_func; BitField<1, 1, u32_le> embedded_swap_func;
BitField<2, 2, u32_le> memory_region_size; BitField<2, 2, u32_le> memory_region_size;
BitField<4, 2, u32_le> compatibility; BitField<4, 2, u32_le> compatibility;
BitField<6, 1, u32_le> events_self_managed;
BitField<7, 1, u32_le> swap_not_needed;
BitField<8, 1, u32_le> use_private_memory;
} flags; } flags;
u32_le exe_load_checksum; u32_le exe_load_checksum;
u32_le builtin_load_exe_args[4]; u32_le builtin_load_exe_args[4];

View File

@ -369,6 +369,16 @@ enum class ControlProcessOP {
PROCESSOP_DISABLE_CREATE_THREAD_RESTRICTIONS, PROCESSOP_DISABLE_CREATE_THREAD_RESTRICTIONS,
}; };
/**
* Accepted by the custom svcMapProcessMemoryEx.
*/
enum class MapMemoryExFlag {
/**
* Maps the memory region as PRIVATE instead of SHARED
*/
MAPEXFLAGS_PRIVATE = (1 << 0),
};
class SVC : public SVCWrapper<SVC> { class SVC : public SVCWrapper<SVC> {
public: public:
SVC(Core::System& system); SVC(Core::System& system);
@ -460,7 +470,8 @@ private:
Result InvalidateEntireInstructionCache(); Result InvalidateEntireInstructionCache();
u32 ConvertVaToPa(u32 addr); u32 ConvertVaToPa(u32 addr);
Result MapProcessMemoryEx(Handle dst_process_handle, u32 dst_address, Handle src_process_handle, Result MapProcessMemoryEx(Handle dst_process_handle, u32 dst_address, Handle src_process_handle,
u32 src_address, u32 size); u32 src_address, u32 size, MapMemoryExFlag flags,
Handle dst_process_handle_backup);
Result UnmapProcessMemoryEx(Handle process, u32 dst_address, u32 size); Result UnmapProcessMemoryEx(Handle process, u32 dst_address, u32 size);
Result ControlProcess(Handle process_handle, u32 process_OP, u32 varg2, u32 varg3); Result ControlProcess(Handle process_handle, u32 process_OP, u32 varg2, u32 varg3);
@ -2012,7 +2023,22 @@ u32 SVC::ConvertVaToPa(u32 addr) {
} }
Result SVC::MapProcessMemoryEx(Handle dst_process_handle, u32 dst_address, Result SVC::MapProcessMemoryEx(Handle dst_process_handle, u32 dst_address,
Handle src_process_handle, u32 src_address, u32 size) { Handle src_process_handle, u32 src_address, u32 size,
MapMemoryExFlag flags, Handle dst_process_handle_backup) {
// Determine if this is the second version of the svc by checking the value at R0.
constexpr u32 SVC_VERSION2_MAGIC = 0xFFFFFFF2;
if (static_cast<u32>(dst_process_handle) == SVC_VERSION2_MAGIC) {
// Version 2, actual handle is provided in 6th argument
dst_process_handle = dst_process_handle_backup;
} else {
// Version 1, the flags argument is not used
flags = static_cast<MapMemoryExFlag>(0);
}
const bool map_as_private =
(static_cast<u32>(flags) & static_cast<u32>(MapMemoryExFlag::MAPEXFLAGS_PRIVATE)) != 0;
std::shared_ptr<Process> dst_process = std::shared_ptr<Process> dst_process =
kernel.GetCurrentProcess()->handle_table.Get<Process>(dst_process_handle); kernel.GetCurrentProcess()->handle_table.Get<Process>(dst_process_handle);
std::shared_ptr<Process> src_process = std::shared_ptr<Process> src_process =
@ -2024,11 +2050,12 @@ Result SVC::MapProcessMemoryEx(Handle dst_process_handle, u32 dst_address,
size = (size & ~0xFFF) + Memory::CITRA_PAGE_SIZE; size = (size & ~0xFFF) + Memory::CITRA_PAGE_SIZE;
} }
// TODO(PabloMK7) Fix-up this svc.
// Only linear memory supported // Only linear memory supported
auto vma = src_process->vm_manager.FindVMA(src_address); auto vma = src_process->vm_manager.FindVMA(src_address);
R_UNLESS(vma != src_process->vm_manager.vma_map.end() && R_UNLESS(vma != src_process->vm_manager.vma_map.end() &&
vma->second.type == VMAType::BackingMemory && vma->second.type == VMAType::BackingMemory,
vma->second.meminfo_state == MemoryState::Continuous,
ResultInvalidAddress); ResultInvalidAddress);
const u32 offset = src_address - vma->second.base; const u32 offset = src_address - vma->second.base;
@ -2038,7 +2065,7 @@ Result SVC::MapProcessMemoryEx(Handle dst_process_handle, u32 dst_address,
dst_address, dst_address,
memory.GetFCRAMRef(vma->second.backing_memory.GetPtr() + offset - memory.GetFCRAMRef(vma->second.backing_memory.GetPtr() + offset -
kernel.memory.GetFCRAMPointer(0)), kernel.memory.GetFCRAMPointer(0)),
size, Kernel::MemoryState::Continuous); size, map_as_private ? MemoryState::Private : MemoryState::Shared);
if (!vma_res.Succeeded()) { if (!vma_res.Succeeded()) {
return ResultInvalidAddressState; return ResultInvalidAddressState;
@ -2060,8 +2087,7 @@ Result SVC::UnmapProcessMemoryEx(Handle process, u32 dst_address, u32 size) {
// Only linear memory supported // Only linear memory supported
auto vma = dst_process->vm_manager.FindVMA(dst_address); auto vma = dst_process->vm_manager.FindVMA(dst_address);
R_UNLESS(vma != dst_process->vm_manager.vma_map.end() && R_UNLESS(vma != dst_process->vm_manager.vma_map.end() &&
vma->second.type == VMAType::BackingMemory && vma->second.type == VMAType::BackingMemory,
vma->second.meminfo_state == MemoryState::Continuous,
ResultInvalidAddress); ResultInvalidAddress);
dst_process->vm_manager.UnmapRange(dst_address, size); dst_process->vm_manager.UnmapRange(dst_address, size);

View File

@ -7,6 +7,7 @@
#include "core/hle/kernel/shared_memory.h" #include "core/hle/kernel/shared_memory.h"
#include "core/hle/service/act/act.h" #include "core/hle/service/act/act.h"
#include "core/hle/service/act/act_a.h" #include "core/hle/service/act/act_a.h"
#include "core/hle/service/act/act_errors.h"
#include "core/hle/service/act/act_u.h" #include "core/hle/service/act/act_u.h"
namespace Service::ACT { namespace Service::ACT {
@ -31,15 +32,28 @@ void Module::Interface::Initialize(Kernel::HLERequestContext& ctx) {
rb.Push(ResultSuccess); rb.Push(ResultSuccess);
} }
void Module::Interface::GetAccountDataBlock(Kernel::HLERequestContext& ctx) { void Module::Interface::GetErrorCode(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx); IPC::RequestParser rp(ctx);
const auto unknown = rp.Pop<u8>(); const auto result = rp.Pop<Result>();
LOG_DEBUG(Service_ACT, "called result={:08X}", result.raw);
const u32 error_code = GetACTErrorCode(result);
IPC::RequestBuilder rb = rp.MakeBuilder(2, 0);
rb.Push(ResultSuccess);
rb.Push(error_code);
}
void Module::Interface::GetAccountInfo(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx);
const auto account_slot = rp.Pop<u8>();
const auto size = rp.Pop<u32>(); const auto size = rp.Pop<u32>();
const auto block_id = rp.Pop<u32>(); const auto block_id = rp.Pop<u32>();
[[maybe_unused]] auto output_buffer = rp.PopMappedBuffer(); [[maybe_unused]] auto output_buffer = rp.PopMappedBuffer();
LOG_DEBUG(Service_ACT, "(STUBBED) called unknown={:02X}, size={:08X}, block_id={:08X}", unknown, LOG_DEBUG(Service_ACT, "(STUBBED) called account_slot={:02X}, size={:08X}, block_id={:08X}",
size, block_id); account_slot, size, block_id);
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
rb.Push(ResultSuccess); rb.Push(ResultSuccess);

View File

@ -38,9 +38,19 @@ public:
void Initialize(Kernel::HLERequestContext& ctx); void Initialize(Kernel::HLERequestContext& ctx);
/** /**
* ACT::GetAccountDataBlock service function. * ACT::GetErrorCode service function.
* Inputs: * Inputs:
* 1 : u8 Unknown * 1 : Result code
* Outputs:
* 1 : Result of function, 0 on success, otherwise error code
* 2 : Error code
*/
void GetErrorCode(Kernel::HLERequestContext& ctx);
/**
* ACT::GetAccountInfo service function.
* Inputs:
* 1 : Account slot
* 2 : Size * 2 : Size
* 3 : Block ID * 3 : Block ID
* 4 : Output Buffer Mapping Translation Header ((Size << 4) | 0xC) * 4 : Output Buffer Mapping Translation Header ((Size << 4) | 0xC)
@ -48,7 +58,7 @@ public:
* Outputs: * Outputs:
* 1 : Result of function, 0 on success, otherwise error code * 1 : Result of function, 0 on success, otherwise error code
*/ */
void GetAccountDataBlock(Kernel::HLERequestContext& ctx); void GetAccountInfo(Kernel::HLERequestContext& ctx);
}; };
private: private:

View File

@ -12,15 +12,61 @@ ACT_A::ACT_A(std::shared_ptr<Module> act) : Module::Interface(std::move(act), "a
// act:u shared commands // act:u shared commands
// clang-format off // clang-format off
{0x0001, &ACT_A::Initialize, "Initialize"}, {0x0001, &ACT_A::Initialize, "Initialize"},
{0x0002, nullptr, "GetErrorCode"}, {0x0002, &ACT_A::GetErrorCode, "GetErrorCode"},
{0x0006, &ACT_A::GetAccountDataBlock, "GetAccountDataBlock"}, {0x0003, nullptr, "GetLastResponseCode"},
{0x0005, nullptr, "GetCommonInfo"},
{0x0006, &ACT_A::GetAccountInfo, "GetAccountInfo"},
{0x0007, nullptr, "GetResultAsync"},
{0x0008, nullptr, "GetMiiImageData"},
{0x0009, nullptr, "SetNfsPassword"},
{0x000B, nullptr, "AcquireEulaList"}, {0x000B, nullptr, "AcquireEulaList"},
{0x000C, nullptr, "AcquireTimeZoneList"},
{0x000D, nullptr, "GenerateUuid"}, {0x000D, nullptr, "GenerateUuid"},
{0x000F, nullptr, "FindSlotNoByUuid"},
{0x0010, nullptr, "SaveData"},
{0x0011, nullptr, "GetTransferableId"},
{0x0012, nullptr, "AcquireNexServiceToken"},
{0x0013, nullptr, "GetNexServiceToken"},
{0x0014, nullptr, "AcquireIndependentServiceToken"},
{0x0015, nullptr, "GetIndependentServiceToken"},
{0x0016, nullptr, "AcquireAccountInfo"},
{0x0017, nullptr, "AcquireAccountIdByPrincipalId"},
{0x0018, nullptr, "AcquirePrincipalIdByAccountId"},
{0x0019, nullptr, "AcquireMii"},
{0x001A, nullptr, "AcquireAccountInfoEx"},
{0x001D, nullptr, "InquireMailAddress"},
{0x001E, nullptr, "AcquireEula"},
{0x001F, nullptr, "AcquireEulaLanguageList"},
// act:a // act:a
{0x0402, nullptr, "CreateConsoleAccount"},
{0x0403, nullptr, "CommitConsoleAccount"},
{0x0404, nullptr, "UnbindServerAccount"},
{0x0405, nullptr, "DeleteConsoleAccount"},
{0x0407, nullptr, "UnloadConsoleAccount"},
{0x0408, nullptr, "EnableAccountPasswordCache"},
{0x0409, nullptr, "SetDefaultAccount"},
{0x040A, nullptr, "ReplaceAccountId"},
{0x040B, nullptr, "GetSupportContext"},
{0x0412, nullptr, "UpdateMii"},
{0x0413, nullptr, "UpdateMiiImage"}, {0x0413, nullptr, "UpdateMiiImage"},
{0x0414, nullptr, "InquireAccountIdAvailability"},
{0x0415, nullptr, "BindToNewServerAccount"},
{0x0416, nullptr, "BindToExistentServerAccount"},
{0x0417, nullptr, "InquireBindingToExistentServerAccount"},
{0x041A, nullptr, "AcquireAccountTokenEx"},
{0x041B, nullptr, "AgreeEula"}, {0x041B, nullptr, "AgreeEula"},
{0x041C, nullptr, "SyncAccountInfo"},
{0x041E, nullptr, "UpdateAccountPassword"},
{0x041F, nullptr, "ReissueAccountPassword"},
{0x0420, nullptr, "SetAccountPasswordInput"},
{0x0421, nullptr, "UploadMii"}, {0x0421, nullptr, "UploadMii"},
{0x0423, nullptr, "ValidateMailAddress"}, {0x0423, nullptr, "ValidateMailAddress"},
{0x0423, nullptr, "SendConfirmationMail"},
{0x0428, nullptr, "ApproveByCreditCard"},
{0x0428, nullptr, "SendCoppaCodeMail"},
{0x042F, nullptr, "UpdateAccountInfoEx"},
{0x0430, nullptr, "UpdateAccountMailAddress"},
{0x0435, nullptr, "DeleteServerAccount"},
// clang-format on // clang-format on
}; };
RegisterHandlers(functions); RegisterHandlers(functions);

View File

@ -0,0 +1,757 @@
// Copyright 2024 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include "core/hle/service/act/act_errors.h"
namespace Service::ACT {
u32 GetACTErrorCode(Result result) {
u32 error_code = ErrCodes::Unknown;
if (result.module == ErrorModule::ACT) {
switch (result.description) {
case ErrDescriptions::MySuccess:
error_code = ErrCodes::MySuccess;
break;
case ErrDescriptions::MailAddressNotConfirmed:
error_code = ErrCodes::MailAddressNotConfirmed;
break;
case ErrDescriptions::LibraryError:
error_code = ErrCodes::LibraryError;
break;
case ErrDescriptions::NotInitialized:
error_code = ErrCodes::NotInitialized;
break;
case ErrDescriptions::AlreadyInitialized:
error_code = ErrCodes::AlreadyInitialized;
break;
case ErrDescriptions::ErrDesc103:
error_code = ErrCodes::ErrCode225103;
break;
case ErrDescriptions::ErrDesc104:
error_code = ErrCodes::ErrCode225104;
break;
case ErrDescriptions::Busy:
error_code = ErrCodes::Busy;
break;
case ErrDescriptions::ErrDesc112:
error_code = ErrCodes::ErrCode225112;
break;
case ErrDescriptions::NotImplemented:
error_code = ErrCodes::NotImplemented;
break;
case ErrDescriptions::Deprecated:
error_code = ErrCodes::Deprecated;
break;
case ErrDescriptions::DevelopmentOnly:
error_code = ErrCodes::DevelopmentOnly;
break;
case ErrDescriptions::InvalidArgument:
error_code = ErrCodes::InvalidArgument;
break;
case ErrDescriptions::InvalidPointer:
error_code = ErrCodes::InvalidPointer;
break;
case ErrDescriptions::OutOfRange:
error_code = ErrCodes::OutOfRange;
break;
case ErrDescriptions::InvalidSize:
error_code = ErrCodes::InvalidSize;
break;
case ErrDescriptions::InvalidFormat:
error_code = ErrCodes::InvalidFormat;
break;
case ErrDescriptions::InvalidHandle:
error_code = ErrCodes::InvalidHandle;
break;
case ErrDescriptions::InvalidValue:
error_code = ErrCodes::InvalidValue;
break;
case ErrDescriptions::InternalError:
error_code = ErrCodes::InternalError;
break;
case ErrDescriptions::EndOfStream:
error_code = ErrCodes::EndOfStream;
break;
case ErrDescriptions::FileError:
error_code = ErrCodes::FileError;
break;
case ErrDescriptions::FileNotFound:
error_code = ErrCodes::FileNotFound;
break;
case ErrDescriptions::FileVersionMismatch:
error_code = ErrCodes::FileVersionMismatch;
break;
case ErrDescriptions::FileIOError:
error_code = ErrCodes::FileIOError;
break;
case ErrDescriptions::FileTypeMismatch:
error_code = ErrCodes::FileTypeMismatch;
break;
case ErrDescriptions::ErrDesc315:
error_code = ErrCodes::ErrCode225315;
break;
case ErrDescriptions::OutOfResource:
error_code = ErrCodes::OutOfResource;
break;
case ErrDescriptions::ShortOfBuffer:
error_code = ErrCodes::ShortOfBuffer;
break;
case ErrDescriptions::OutOfMemory:
error_code = ErrCodes::OutOfMemory;
break;
case ErrDescriptions::OutOfGlobalHeap:
error_code = ErrCodes::OutOfGlobalHeap;
break;
case ErrDescriptions::ErrDesc350:
error_code = ErrCodes::ErrCode225350;
break;
case ErrDescriptions::ErrDesc351:
error_code = ErrCodes::ErrCode225351;
break;
case ErrDescriptions::ErrDesc352:
error_code = ErrCodes::ErrCode225352;
break;
case ErrDescriptions::ErrDesc360:
error_code = ErrCodes::ErrCode225360;
break;
case ErrDescriptions::ErrDesc361:
error_code = ErrCodes::ErrCode225361;
break;
case ErrDescriptions::ErrDesc362:
error_code = ErrCodes::ErrCode225362;
break;
case ErrDescriptions::ErrDesc363:
error_code = ErrCodes::ErrCode225363;
break;
case ErrDescriptions::AccountManagementError:
error_code = ErrCodes::AccountManagementError;
break;
case ErrDescriptions::AccountNotFound:
error_code = ErrCodes::AccountNotFound;
break;
case ErrDescriptions::SlotsFull:
error_code = ErrCodes::SlotsFull;
break;
case ErrDescriptions::AccountNotLoaded:
error_code = ErrCodes::AccountNotLoaded;
break;
case ErrDescriptions::AccountAlreadyLoaded:
error_code = ErrCodes::AccountAlreadyLoaded;
break;
case ErrDescriptions::AccountLocked:
error_code = ErrCodes::AccountLocked;
break;
case ErrDescriptions::NotNetworkAccount:
error_code = ErrCodes::NotNetworkAccount;
break;
case ErrDescriptions::NotLocalAccount:
error_code = ErrCodes::NotLocalAccount;
break;
case ErrDescriptions::AccountNotCommited:
error_code = ErrCodes::AccountCommited;
break;
case ErrDescriptions::ErrDesc431:
error_code = ErrCodes::ErrCode225431;
break;
case ErrDescriptions::ErrDesc432:
error_code = ErrCodes::ErrCode225432;
break;
case ErrDescriptions::ErrDesc433:
error_code = ErrCodes::ErrCode225433;
break;
case ErrDescriptions::ErrDesc451:
error_code = ErrCodes::ErrCode221101;
break;
case ErrDescriptions::AuthenticationError:
error_code = ErrCodes::AuthenticationError;
break;
case ErrDescriptions::HttpError:
error_code = ErrCodes::HttpError;
break;
case ErrDescriptions::ErrDesc502:
error_code = ErrCodes::ErrCode225502;
break;
case ErrDescriptions::ErrDesc503:
error_code = ErrCodes::ErrCode225503;
break;
case ErrDescriptions::ErrDesc504:
error_code = ErrCodes::ErrCode225504;
break;
case ErrDescriptions::ErrDesc505:
error_code = ErrCodes::ErrCode225505;
break;
case ErrDescriptions::ErrDesc506:
error_code = ErrCodes::ErrCode225506;
break;
case ErrDescriptions::ErrDesc507:
error_code = ErrCodes::ErrCode225507;
break;
case ErrDescriptions::ErrDesc508:
error_code = ErrCodes::ErrCode225508;
break;
case ErrDescriptions::ErrDesc509:
error_code = ErrCodes::ErrCode225509;
break;
case ErrDescriptions::ErrDesc510:
error_code = ErrCodes::ErrCode225510;
break;
case ErrDescriptions::ErrDesc511:
error_code = ErrCodes::ErrCode225511;
break;
case ErrDescriptions::ErrDesc512:
error_code = ErrCodes::ErrCode225512;
break;
case ErrDescriptions::ErrDesc513:
error_code = ErrCodes::ErrCode225513;
break;
case ErrDescriptions::ErrDesc514:
error_code = ErrCodes::ErrCode225514;
break;
case ErrDescriptions::ErrDesc515:
error_code = ErrCodes::ErrCode225515;
break;
case ErrDescriptions::ErrDesc516:
error_code = ErrCodes::ErrCode225516;
break;
case ErrDescriptions::ErrDesc517:
error_code = ErrCodes::ErrCode225517;
break;
case ErrDescriptions::ErrDesc518:
error_code = ErrCodes::ErrCode225518;
break;
case ErrDescriptions::ErrDesc519:
error_code = ErrCodes::ErrCode225519;
break;
case ErrDescriptions::ErrDesc520:
error_code = ErrCodes::ErrCode225520;
break;
case ErrDescriptions::ErrDesc521:
error_code = ErrCodes::ErrCode225521;
break;
case ErrDescriptions::ErrDesc522:
error_code = ErrCodes::ErrCode225522;
break;
case ErrDescriptions::ErrDesc523:
error_code = ErrCodes::ErrCode225523;
break;
case ErrDescriptions::ErrDesc524:
error_code = ErrCodes::ErrCode225524;
break;
case ErrDescriptions::ErrDesc525:
error_code = ErrCodes::ErrCode225525;
break;
case ErrDescriptions::ErrDesc526:
error_code = ErrCodes::ErrCode225526;
break;
case ErrDescriptions::ErrDesc527:
error_code = ErrCodes::ErrCode225527;
break;
case ErrDescriptions::ErrDesc528:
error_code = ErrCodes::ErrCode225528;
break;
case ErrDescriptions::ErrDesc529:
error_code = ErrCodes::ErrCode225529;
break;
case ErrDescriptions::ErrDesc530:
error_code = ErrCodes::ErrCode225530;
break;
case ErrDescriptions::ErrDesc531:
error_code = ErrCodes::ErrCode225531;
break;
case ErrDescriptions::ErrDesc532:
error_code = ErrCodes::ErrCode225532;
break;
case ErrDescriptions::ErrDesc533:
error_code = ErrCodes::ErrCode225533;
break;
case ErrDescriptions::ErrDesc534:
error_code = ErrCodes::ErrCode225534;
break;
case ErrDescriptions::ErrDesc535:
error_code = ErrCodes::ErrCode225535;
break;
case ErrDescriptions::ErrDesc536:
error_code = ErrCodes::ErrCode225536;
break;
case ErrDescriptions::ErrDesc537:
error_code = ErrCodes::ErrCode225537;
break;
case ErrDescriptions::ErrDesc538:
error_code = ErrCodes::ErrCode225538;
break;
case ErrDescriptions::ErrDesc539:
error_code = ErrCodes::ErrCode225539;
break;
case ErrDescriptions::ErrDesc540:
error_code = ErrCodes::ErrCode225540;
break;
case ErrDescriptions::ErrDesc541:
error_code = ErrCodes::ErrCode225541;
break;
case ErrDescriptions::ErrDesc542:
error_code = ErrCodes::ErrCode225542;
break;
case ErrDescriptions::ErrDesc543:
error_code = ErrCodes::ErrCode225543;
break;
case ErrDescriptions::ErrDesc544:
error_code = ErrCodes::ErrCode225544;
break;
case ErrDescriptions::ErrDesc545:
error_code = ErrCodes::ErrCode225545;
break;
case ErrDescriptions::ErrDesc546:
error_code = ErrCodes::ErrCode225546;
break;
case ErrDescriptions::ErrDesc547:
error_code = ErrCodes::ErrCode225547;
break;
case ErrDescriptions::ErrDesc548:
error_code = ErrCodes::ErrCode225548;
break;
case ErrDescriptions::ErrDesc549:
error_code = ErrCodes::ErrCode225549;
break;
case ErrDescriptions::ErrDesc550:
error_code = ErrCodes::ErrCode225550;
break;
case ErrDescriptions::ErrDesc551:
error_code = ErrCodes::ErrCode225551;
break;
case ErrDescriptions::ErrDesc552:
error_code = ErrCodes::ErrCode225552;
break;
case ErrDescriptions::ErrDesc553:
error_code = ErrCodes::ErrCode225553;
break;
case ErrDescriptions::RequestError:
error_code = ErrCodes::RequestError;
break;
case ErrDescriptions::BadFormatParameter:
error_code = ErrCodes::BadFormatParameter;
break;
case ErrDescriptions::BadFormatRequest:
error_code = ErrCodes::BadFormatRequest;
break;
case ErrDescriptions::RequestParameterMissing:
error_code = ErrCodes::RequestParameterMissing;
break;
case ErrDescriptions::WrongHttpMethod:
error_code = ErrCodes::WrongHttpMethod;
break;
case ErrDescriptions::ResponseError:
error_code = ErrCodes::ResponseError;
break;
case ErrDescriptions::BadFormatResponse:
error_code = ErrCodes::BadFormatResponse;
break;
case ErrDescriptions::ResponseItemMissing:
error_code = ErrCodes::ResponseItemMissing;
break;
case ErrDescriptions::ResponseTooLarge:
error_code = ErrCodes::ResponseTooLarge;
break;
case ErrDescriptions::InvalidCommonParameter:
error_code = ErrCodes::InvalidCommonParameter;
break;
case ErrDescriptions::InvalidPlatformId:
error_code = ErrCodes::InvalidPlatformId;
break;
case ErrDescriptions::UnauthorizedDevice:
error_code = ErrCodes::UnauthorizedDevice;
break;
case ErrDescriptions::InvalidSerialId:
error_code = ErrCodes::InvalidSerialId;
break;
case ErrDescriptions::InvalidMacAddress:
error_code = ErrCodes::InvalidMacAddress;
break;
case ErrDescriptions::InvalidRegion:
error_code = ErrCodes::InvalidRegion;
break;
case ErrDescriptions::InvalidCountry:
error_code = ErrCodes::InvalidCountry;
break;
case ErrDescriptions::InvalidLanguage:
error_code = ErrCodes::InvalidLanguage;
break;
case ErrDescriptions::UnauthorizedClient:
error_code = ErrCodes::UnauthorizedClient;
break;
case ErrDescriptions::DeviceIdEmpty:
error_code = ErrCodes::DeviceIdEmpty;
break;
case ErrDescriptions::SerialIdEmpty:
error_code = ErrCodes::SerialIdEmpty;
break;
case ErrDescriptions::PlatformIdEmpty:
error_code = ErrCodes::PlatformIdEmpty;
break;
case ErrDescriptions::InvalidUniqueId:
error_code = ErrCodes::InvalidUniqueId;
break;
case ErrDescriptions::InvalidClientId:
error_code = ErrCodes::InvalidClientId;
break;
case ErrDescriptions::InvalidClientKey:
error_code = ErrCodes::InvalidClientKey;
break;
case ErrDescriptions::InvalidNexClientId:
error_code = ErrCodes::InvalidNexClientId;
break;
case ErrDescriptions::InvalidGameServerId:
error_code = ErrCodes::InvalidGameServerId;
break;
case ErrDescriptions::GameServerIdEnvironmentNotFound:
error_code = ErrCodes::GameServerIdEnvironmentNotFound;
break;
case ErrDescriptions::GameServerIdUniqueIdNotLinked:
error_code = ErrCodes::GameServerIdUniqueIdNotLinked;
break;
case ErrDescriptions::ClientIdUniqueIdNotLinked:
error_code = ErrCodes::ClientIdUniqueIdNotLinked;
break;
case ErrDescriptions::DeviceMismatch:
error_code = ErrCodes::DeviceMismatch;
break;
case ErrDescriptions::CountryMismatch:
error_code = ErrCodes::CountryMismatch;
break;
case ErrDescriptions::EulaNotAccepted:
error_code = ErrCodes::EulaNotAccepted;
break;
case ErrDescriptions::UpdateRequired:
error_code = ErrCodes::UpdateRequired;
break;
case ErrDescriptions::SystemUpdateRequired:
error_code = ErrCodes::SystemUpdateRequired;
break;
case ErrDescriptions::ApplicationUpdateRequired:
error_code = ErrCodes::ApplicationUpdateRequired;
break;
case ErrDescriptions::UnauthorizedRequest:
error_code = ErrCodes::UnauthorizedRequest;
break;
case ErrDescriptions::RequestForbidden:
error_code = ErrCodes::RequestForbidden;
break;
case ErrDescriptions::ResourceNotFound:
error_code = ErrCodes::ResourceNotFound;
break;
case ErrDescriptions::PidNotFound:
error_code = ErrCodes::PidNotFound;
break;
case ErrDescriptions::NexAccountNotFound:
error_code = ErrCodes::NexAccountNotFound;
break;
case ErrDescriptions::GenerateTokenFailure:
error_code = ErrCodes::GenerateTokenFailure;
break;
case ErrDescriptions::RequestNotFound:
error_code = ErrCodes::RequestNotFound;
break;
case ErrDescriptions::MasterPinNotFound:
error_code = ErrCodes::MasterPinNotFound;
break;
case ErrDescriptions::MailTextNotFound:
error_code = ErrCodes::MailTextNotFound;
break;
case ErrDescriptions::SendMailFailure:
error_code = ErrCodes::SendMailFailure;
break;
case ErrDescriptions::ApprovalIdNotFound:
error_code = ErrCodes::ApprovalIdNotFound;
break;
case ErrDescriptions::InvalidEulaParameter:
error_code = ErrCodes::InvalidEulaParameter;
break;
case ErrDescriptions::InvalidEulaCountry:
error_code = ErrCodes::InvalidEulaCountry;
break;
case ErrDescriptions::InvalidEulaCountryAndVersion:
error_code = ErrCodes::InvalidEulaCountryAndVersion;
break;
case ErrDescriptions::EulaNotFound:
error_code = ErrCodes::EulaNotFound;
break;
case ErrDescriptions::PhraseNotAcceptable:
error_code = ErrCodes::PhraseNotAcceptable;
break;
case ErrDescriptions::AccountIdAlreadyExists:
error_code = ErrCodes::AccountIdAlreadyExists;
break;
case ErrDescriptions::AccountIdNotAcceptable:
error_code = ErrCodes::AccountIdNotAcceptable;
break;
case ErrDescriptions::AccountPasswordNotAcceptable:
error_code = ErrCodes::AccountPasswordNotAcceptable;
break;
case ErrDescriptions::MiiNameNotAcceptable:
error_code = ErrCodes::MiiNameNotAcceptable;
break;
case ErrDescriptions::MailAddressNotAcceptable:
error_code = ErrCodes::MailAddressNotAcceptable;
break;
case ErrDescriptions::AccountIdFormatInvalid:
error_code = ErrCodes::AccountIdFormatInvalid;
break;
case ErrDescriptions::AccountIdPasswordSame:
error_code = ErrCodes::AccountIdPasswordSame;
break;
case ErrDescriptions::AccountIdCharNotAcceptable:
error_code = ErrCodes::AccountIdCharNotAcceptable;
break;
case ErrDescriptions::AccountIdSuccessiveSymbol:
error_code = ErrCodes::AccountIdSuccessiveSymbol;
break;
case ErrDescriptions::AccountIdSymbolPositionNotAcceptable:
error_code = ErrCodes::AccountIdSymbolPositionNotAcceptable;
break;
case ErrDescriptions::AccountIdTooManyDigit:
error_code = ErrCodes::AccountIdTooManyDigit;
break;
case ErrDescriptions::AccountPasswordCharNotAcceptable:
error_code = ErrCodes::AccountPasswordCharNotAcceptable;
break;
case ErrDescriptions::AccountPasswordTooFewCharTypes:
error_code = ErrCodes::AccountPasswordTooFewCharTypes;
break;
case ErrDescriptions::AccountPasswordSuccessiveSameChar:
error_code = ErrCodes::AccountPasswordSuccessiveSameChar;
break;
case ErrDescriptions::MailAddressDomainNameNotAcceptable:
error_code = ErrCodes::MailAddressDomainNameNotAcceptable;
break;
case ErrDescriptions::MailAddressDomainNameNotResolved:
error_code = ErrCodes::MailAddressDomainNameNotResolved;
break;
case ErrDescriptions::ErrDesc787:
error_code = ErrCodes::ErrCode222587;
break;
case ErrDescriptions::ReachedAssociationLimit:
error_code = ErrCodes::ReachedAssociationLimit;
break;
case ErrDescriptions::ReachedRegistrationLimit:
error_code = ErrCodes::ReachedRegistrationLimit;
break;
case ErrDescriptions::CoppaNotAccepted:
error_code = ErrCodes::CoppaNotAccepted;
break;
case ErrDescriptions::ParentalControlsRequired:
error_code = ErrCodes::ParentalControlsRequired;
break;
case ErrDescriptions::MiiNotRegistered:
error_code = ErrCodes::MiiNotRegistered;
break;
case ErrDescriptions::DeviceEulaCountryMismatch:
error_code = ErrCodes::DeviceEulaCountryMismatch;
break;
case ErrDescriptions::PendingMigration:
error_code = ErrCodes::PendingMigration;
break;
case ErrDescriptions::WrongUserInput:
error_code = ErrCodes::WrongUserInput;
break;
case ErrDescriptions::WrongAccountPassword:
error_code = ErrCodes::WrongAccountPassword;
break;
case ErrDescriptions::WrongMailAddress:
error_code = ErrCodes::WrongMailAddress;
break;
case ErrDescriptions::WrongAccountPasswordOrMailAddress:
error_code = ErrCodes::WrongAccountPasswordOrMailAddress;
break;
case ErrDescriptions::WrongConfirmationCode:
error_code = ErrCodes::WrongConfirmationCode;
break;
case ErrDescriptions::WrongBirthDateOrMailAddress:
error_code = ErrCodes::WrongBirthDateOrMailAddress;
break;
case ErrDescriptions::WrongAccountMail:
error_code = ErrCodes::WrongAccountMail;
break;
case ErrDescriptions::AccountAlreadyDeleted:
error_code = ErrCodes::AccountAlreadyDeleted;
break;
case ErrDescriptions::AccountIdChanged:
error_code = ErrCodes::AccountIdChanged;
break;
case ErrDescriptions::AuthenticationLocked:
error_code = ErrCodes::AuthenticationLocked;
break;
case ErrDescriptions::DeviceInactive:
error_code = ErrCodes::DeviceInactive;
break;
case ErrDescriptions::CoppaAgreementCanceled:
error_code = ErrCodes::CoppaAgreementCanceled;
break;
case ErrDescriptions::DomainAccountAlreadyExists:
error_code = ErrCodes::DomainAccountAlreadyExists;
break;
case ErrDescriptions::AccountTokenExpired:
error_code = ErrCodes::AccountTokenExpired;
break;
case ErrDescriptions::InvalidAccountToken:
error_code = ErrCodes::InvalidAccountToken;
break;
case ErrDescriptions::AuthenticationRequired:
error_code = ErrCodes::AuthenticationRequired;
break;
case ErrDescriptions::ErrDesc844:
error_code = ErrCodes::ErrCode225844;
break;
case ErrDescriptions::ConfirmationCodeExpired:
error_code = ErrCodes::ConfirmationCodeExpired;
break;
case ErrDescriptions::MailAddressNotValidated:
error_code = ErrCodes::MailAddressNotValidated;
break;
case ErrDescriptions::ExcessiveMailSendRequest:
error_code = ErrCodes::ExcessiveMailSendRequest;
break;
case ErrDescriptions::CreditCardError:
error_code = ErrCodes::CreditCardError;
break;
case ErrDescriptions::CreditCardGeneralFailure:
error_code = ErrCodes::CreditCardGeneralFailure;
break;
case ErrDescriptions::CreditCardDeclined:
error_code = ErrCodes::CreditCardDeclined;
break;
case ErrDescriptions::CreditCardBlacklisted:
error_code = ErrCodes::CreditCardBlacklisted;
break;
case ErrDescriptions::InvalidCreditCardNumber:
error_code = ErrCodes::InvalidCreditCardNumber;
break;
case ErrDescriptions::InvalidCreditCardDate:
error_code = ErrCodes::InvalidCreditCardDate;
break;
case ErrDescriptions::InvalidCreditCardPin:
error_code = ErrCodes::InvalidCreditCardPin;
break;
case ErrDescriptions::InvalidPostalCode:
error_code = ErrCodes::InvalidPostalCode;
break;
case ErrDescriptions::InvalidLocation:
error_code = ErrCodes::InvalidLocation;
break;
case ErrDescriptions::CreditCardDateExpired:
error_code = ErrCodes::CreditCardDateExpired;
break;
case ErrDescriptions::CreditCardNumberWrong:
error_code = ErrCodes::CreditCardNumberWrong;
break;
case ErrDescriptions::CreditCardPinWrong:
error_code = ErrCodes::CreditCardPinWrong;
break;
case ErrDescriptions::Banned:
error_code = ErrCodes::Banned;
break;
case ErrDescriptions::BannedAccount:
error_code = ErrCodes::BannedAccount;
break;
case ErrDescriptions::BannedAccountAll:
error_code = ErrCodes::BannedAccountAll;
break;
case ErrDescriptions::BannedAccountInApplication:
error_code = ErrCodes::BannedAccountInApplication;
break;
case ErrDescriptions::BannedAccountInNexService:
error_code = ErrCodes::BannedAccountInNexService;
break;
case ErrDescriptions::BannedAccountInIndependentService:
error_code = ErrCodes::BannedAccountInIndependentService;
break;
case ErrDescriptions::BannedDevice:
error_code = ErrCodes::BannedDevice;
break;
case ErrDescriptions::BannedDeviceAll:
error_code = ErrCodes::BannedDeviceAll;
break;
case ErrDescriptions::BannedDeviceInApplication:
error_code = ErrCodes::BannedDeviceInApplication;
break;
case ErrDescriptions::BannedDeviceInNexService:
error_code = ErrCodes::BannedDeviceInNexService;
break;
case ErrDescriptions::BannedDeviceInIndependentService:
error_code = ErrCodes::BannedDeviceInIndependentService;
break;
case ErrDescriptions::BannedAccountTemporarily:
error_code = ErrCodes::BannedAccountTemporarily;
break;
case ErrDescriptions::BannedAccountAllTemporarily:
error_code = ErrCodes::BannedAccountAllTemporarily;
break;
case ErrDescriptions::BannedAccountInApplicationTemporarily:
error_code = ErrCodes::BannedAccountInApplicationTemporarily;
break;
case ErrDescriptions::BannedAccountInNexServiceTemporarily:
error_code = ErrCodes::BannedAccountInNexServiceTemporarily;
break;
case ErrDescriptions::BannedAccountInIndependentServiceTemporarily:
error_code = ErrCodes::BannedAccountInIndependentServiceTemporarily;
break;
case ErrDescriptions::BannedDeviceTemporarily:
error_code = ErrCodes::BannedDeviceTemporarily;
break;
case ErrDescriptions::BannedDeviceAllTemporarily:
error_code = ErrCodes::BannedDeviceAllTemporarily;
break;
case ErrDescriptions::BannedDeviceInApplicationTemporarily:
error_code = ErrCodes::BannedDeviceInApplicationTemporarily;
break;
case ErrDescriptions::BannedDeviceInNexServiceTemporarily:
error_code = ErrCodes::BannedDeviceInNexServiceTemporarily;
break;
case ErrDescriptions::BannedDeviceInIndependentServiceTemporarily:
error_code = ErrCodes::BannedDeviceInIndependentServiceTemporarily;
break;
case ErrDescriptions::ServiceNotProvided:
error_code = ErrCodes::ServiceNotProvided;
break;
case ErrDescriptions::UnderMaintenance:
error_code = ErrCodes::UnderMaintenance;
break;
case ErrDescriptions::ServiceClosed:
error_code = ErrCodes::ServiceClosed;
break;
case ErrDescriptions::NintendoNetworkClosed:
error_code = ErrCodes::NintendoNetworkClosed;
break;
case ErrDescriptions::NotProvidedCountry:
error_code = ErrCodes::NotProvidedCountry;
break;
case ErrDescriptions::RestrictionError:
error_code = ErrCodes::RestrictionError;
break;
case ErrDescriptions::RestrictedByAge:
error_code = ErrCodes::RestrictedByAge;
break;
case ErrDescriptions::RestrictedByParentalControls:
error_code = ErrCodes::RestrictedByParentalControls;
break;
case ErrDescriptions::OnGameInternetCommunicationRestricted:
error_code = ErrCodes::OnGameInternetCommunicationRestricted;
break;
case ErrDescriptions::InternalServerError:
error_code = ErrCodes::InternalServerError;
break;
case ErrDescriptions::UnknownServerError:
error_code = ErrCodes::UnknownServerError;
break;
case ErrDescriptions::UnauthenticatedAfterSalvage:
error_code = ErrCodes::UnauthenticatedAfterSalvage;
break;
case ErrDescriptions::AuthenticationFailureUnknown:
error_code = ErrCodes::AuthenticationFailureUnknown;
break;
}
}
return error_code;
}
} // namespace Service::ACT

View File

@ -0,0 +1,613 @@
// Copyright 2024 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include "core/hle/service/service.h"
namespace Service::ACT {
namespace ErrDescriptions {
enum {
MySuccess = 0,
MailAddressNotConfirmed = 1,
// Library errors
LibraryError = 100,
NotInitialized = 101,
AlreadyInitialized = 102,
ErrDesc103 = 103,
ErrDesc104 = 104,
Busy = 111,
ErrDesc112 = 112,
NotImplemented = 191,
Deprecated = 192,
DevelopmentOnly = 193,
InvalidArgument = 200,
InvalidPointer = 201,
OutOfRange = 202,
InvalidSize = 203,
InvalidFormat = 204,
InvalidHandle = 205,
InvalidValue = 206,
InternalError = 300,
EndOfStream = 301,
FileError = 310,
FileNotFound = 311,
FileVersionMismatch = 312,
FileIOError = 313,
FileTypeMismatch = 314,
ErrDesc315 = 315,
OutOfResource = 330,
ShortOfBuffer = 331,
OutOfMemory = 340,
OutOfGlobalHeap = 341,
ErrDesc350 = 350,
ErrDesc351 = 351,
ErrDesc352 = 352,
ErrDesc360 = 360,
ErrDesc361 = 361,
ErrDesc362 = 362,
ErrDesc363 = 363,
// Account management errors
AccountManagementError = 400,
AccountNotFound = 401,
SlotsFull = 402,
AccountNotLoaded = 411,
AccountAlreadyLoaded = 412,
AccountLocked = 413,
NotNetworkAccount = 421,
NotLocalAccount = 422,
AccountNotCommited = 423,
ErrDesc431 = 431,
ErrDesc432 = 432,
ErrDesc433 = 433,
ErrDesc451 = 451,
AuthenticationError = 500,
// HTTP errors
HttpError = 501,
ErrDesc502 = 502,
ErrDesc503 = 503,
ErrDesc504 = 504,
ErrDesc505 = 505,
ErrDesc506 = 506,
ErrDesc507 = 507,
ErrDesc508 = 508,
ErrDesc509 = 509,
ErrDesc510 = 510,
ErrDesc511 = 511,
ErrDesc512 = 512,
ErrDesc513 = 513,
ErrDesc514 = 514,
ErrDesc515 = 515,
ErrDesc516 = 516,
ErrDesc517 = 517,
ErrDesc518 = 518,
ErrDesc519 = 519,
ErrDesc520 = 520,
ErrDesc521 = 521,
ErrDesc522 = 522,
ErrDesc523 = 523,
ErrDesc524 = 524,
ErrDesc525 = 525,
ErrDesc526 = 526,
ErrDesc527 = 527,
ErrDesc528 = 528,
ErrDesc529 = 529,
ErrDesc530 = 530,
ErrDesc531 = 531,
ErrDesc532 = 532,
ErrDesc533 = 533,
ErrDesc534 = 534,
ErrDesc535 = 535,
ErrDesc536 = 536,
ErrDesc537 = 537,
ErrDesc538 = 538,
ErrDesc539 = 539,
ErrDesc540 = 540,
ErrDesc541 = 541,
ErrDesc542 = 542,
ErrDesc543 = 543,
ErrDesc544 = 544,
ErrDesc545 = 545,
ErrDesc546 = 546,
ErrDesc547 = 547,
ErrDesc548 = 548,
ErrDesc549 = 549,
ErrDesc550 = 550,
ErrDesc551 = 551,
ErrDesc552 = 552,
ErrDesc553 = 553,
// Request errors
RequestError = 600,
BadFormatParameter = 601,
BadFormatRequest = 602,
RequestParameterMissing = 603,
WrongHttpMethod = 604,
// Response errors
ResponseError = 620,
BadFormatResponse = 621,
ResponseItemMissing = 622,
ResponseTooLarge = 623,
// Invalid parameter errors
InvalidCommonParameter = 650,
InvalidPlatformId = 651,
UnauthorizedDevice = 652,
InvalidSerialId = 653,
InvalidMacAddress = 654,
InvalidRegion = 655,
InvalidCountry = 656,
InvalidLanguage = 657,
UnauthorizedClient = 658,
DeviceIdEmpty = 659,
SerialIdEmpty = 660,
PlatformIdEmpty = 661,
InvalidUniqueId = 671,
InvalidClientId = 672,
InvalidClientKey = 673,
InvalidNexClientId = 681,
InvalidGameServerId = 682,
GameServerIdEnvironmentNotFound = 683,
GameServerIdUniqueIdNotLinked = 684,
ClientIdUniqueIdNotLinked = 685,
DeviceMismatch = 701,
CountryMismatch = 702,
EulaNotAccepted = 703,
// Update required errors
UpdateRequired = 710,
SystemUpdateRequired = 711,
ApplicationUpdateRequired = 712,
UnauthorizedRequest = 720,
RequestForbidden = 722,
// Resource not found errors
ResourceNotFound = 730,
PidNotFound = 731,
NexAccountNotFound = 732,
GenerateTokenFailure = 733,
RequestNotFound = 734,
MasterPinNotFound = 735,
MailTextNotFound = 736,
SendMailFailure = 737,
ApprovalIdNotFound = 738,
// EULA errors
InvalidEulaParameter = 740,
InvalidEulaCountry = 741,
InvalidEulaCountryAndVersion = 742,
EulaNotFound = 743,
// Not acceptable errors
PhraseNotAcceptable = 770,
AccountIdAlreadyExists = 771,
AccountIdNotAcceptable = 772,
AccountPasswordNotAcceptable = 773,
MiiNameNotAcceptable = 774,
MailAddressNotAcceptable = 775,
AccountIdFormatInvalid = 776,
AccountIdPasswordSame = 777,
AccountIdCharNotAcceptable = 778,
AccountIdSuccessiveSymbol = 779,
AccountIdSymbolPositionNotAcceptable = 780,
AccountIdTooManyDigit = 781,
AccountPasswordCharNotAcceptable = 782,
AccountPasswordTooFewCharTypes = 783,
AccountPasswordSuccessiveSameChar = 784,
MailAddressDomainNameNotAcceptable = 785,
MailAddressDomainNameNotResolved = 786,
ErrDesc787 = 787,
ReachedAssociationLimit = 791,
ReachedRegistrationLimit = 792,
CoppaNotAccepted = 793,
ParentalControlsRequired = 794,
MiiNotRegistered = 795,
DeviceEulaCountryMismatch = 796,
PendingMigration = 797,
// Wrong user input errors
WrongUserInput = 810,
WrongAccountPassword = 811,
WrongMailAddress = 812,
WrongAccountPasswordOrMailAddress = 813,
WrongConfirmationCode = 814,
WrongBirthDateOrMailAddress = 815,
WrongAccountMail = 816,
AccountAlreadyDeleted = 831,
AccountIdChanged = 832,
AuthenticationLocked = 833,
DeviceInactive = 834,
CoppaAgreementCanceled = 835,
DomainAccountAlreadyExists = 836,
AccountTokenExpired = 841,
InvalidAccountToken = 842,
AuthenticationRequired = 843,
ErrDesc844 = 844,
ConfirmationCodeExpired = 851,
MailAddressNotValidated = 861,
ExcessiveMailSendRequest = 862,
// Credit card errors
CreditCardError = 870,
CreditCardGeneralFailure = 871,
CreditCardDeclined = 872,
CreditCardBlacklisted = 873,
InvalidCreditCardNumber = 874,
InvalidCreditCardDate = 875,
InvalidCreditCardPin = 876,
InvalidPostalCode = 877,
InvalidLocation = 878,
CreditCardDateExpired = 879,
CreditCardNumberWrong = 880,
CreditCardPinWrong = 881,
// Ban errors
Banned = 900,
BannedAccount = 901,
BannedAccountAll = 902,
BannedAccountInApplication = 903,
BannedAccountInNexService = 904,
BannedAccountInIndependentService = 905,
BannedDevice = 911,
BannedDeviceAll = 912,
BannedDeviceInApplication = 913,
BannedDeviceInNexService = 914,
BannedDeviceInIndependentService = 915,
BannedAccountTemporarily = 921,
BannedAccountAllTemporarily = 922,
BannedAccountInApplicationTemporarily = 923,
BannedAccountInNexServiceTemporarily = 924,
BannedAccountInIndependentServiceTemporarily = 925,
BannedDeviceTemporarily = 931,
BannedDeviceAllTemporarily = 932,
BannedDeviceInApplicationTemporarily = 933,
BannedDeviceInNexServiceTemporarily = 934,
BannedDeviceInIndependentServiceTemporarily = 935,
// Service not provided errors
ServiceNotProvided = 950,
UnderMaintenance = 951,
ServiceClosed = 952,
NintendoNetworkClosed = 953,
NotProvidedCountry = 954,
// Restriction errors
RestrictionError = 970,
RestrictedByAge = 971,
RestrictedByParentalControls = 980,
OnGameInternetCommunicationRestricted = 981,
InternalServerError = 991,
UnknownServerError = 992,
UnauthenticatedAfterSalvage = 998,
AuthenticationFailureUnknown = 999,
};
}
namespace ErrCodes {
enum {
MySuccess = 220000, // 022-0000
MailAddressNotConfirmed = 220001, // 022-0001
// Library errors
LibraryError = 220500, // 022-0500
NotInitialized = 220501, // 022-0501
AlreadyInitialized = 220502, // 022-0502
ErrCode225103 = 225103, // 022-5103
ErrCode225104 = 225104, // 022-5104
Busy = 220511, // 022-0511
ErrCode225112 = 225112, // 022-5112
NotImplemented = 220591, // 022-0591
Deprecated = 220592, // 022-0592
DevelopmentOnly = 220593, // 022-0593
InvalidArgument = 220600, // 022-0600
InvalidPointer = 220601, // 022-0601
OutOfRange = 220602, // 022-0602
InvalidSize = 220603, // 022-0603
InvalidFormat = 220604, // 022-0604
InvalidHandle = 220605, // 022-0605
InvalidValue = 220606, // 022-0606
InternalError = 220700, // 022-0700
EndOfStream = 220701, // 022-0701
FileError = 220710, // 022-0710
FileNotFound = 220711, // 022-0711
FileVersionMismatch = 220712, // 022-0712
FileIOError = 220713, // 022-0713
FileTypeMismatch = 220714, // 022-0714
ErrCode225315 = 225315, // 022-5315
OutOfResource = 220730, // 022-0730
ShortOfBuffer = 220731, // 022-0731
OutOfMemory = 220740, // 022-0740
OutOfGlobalHeap = 220741, // 022-0741
ErrCode225350 = 225350, // 022-5350
ErrCode225351 = 225351, // 022-5351
ErrCode225352 = 225352, // 022-5352
ErrCode225360 = 225360, // 022-5360
ErrCode225361 = 225361, // 022-5361
ErrCode225362 = 225362, // 022-5362
ErrCode225363 = 225363, // 022-5363
// Account management errors
AccountManagementError = 221000, // 022-1000
AccountNotFound = 221001, // 022-1001
SlotsFull = 221002, // 022-1002
AccountNotLoaded = 221011, // 022-1011
AccountAlreadyLoaded = 221012, // 022-1012
AccountLocked = 221013, // 022-1013
NotNetworkAccount = 221021, // 022-1021
NotLocalAccount = 221022, // 022-1022
AccountCommited = 221023, // 022-1023
ErrCode225431 = 225431, // 022-5431
ErrCode225432 = 225432, // 022-5432
ErrCode225433 = 225433, // 022-5433
ErrCode221101 = 221101, // 022-1101
AuthenticationError = 222000, // 022-2000
// HTTP errors
HttpError = 222100, // 022-2100
ErrCode225502 = 225502, // 022-5502
ErrCode225503 = 225503, // 022-5503
ErrCode225504 = 225504, // 022-5504
ErrCode225505 = 225505, // 022-5505
ErrCode225506 = 225506, // 022-5506
ErrCode225507 = 225507, // 022-5507
ErrCode225508 = 225508, // 022-5508
ErrCode225509 = 225509, // 022-5509
ErrCode225510 = 225510, // 022-5510
ErrCode225511 = 225511, // 022-5511
ErrCode225512 = 225512, // 022-5512
ErrCode225513 = 225513, // 022-5513
ErrCode225514 = 225514, // 022-5514
ErrCode225515 = 225515, // 022-5515
ErrCode225516 = 225516, // 022-5516
ErrCode225517 = 225517, // 022-5517
ErrCode225518 = 225518, // 022-5518
ErrCode225519 = 225519, // 022-5519
ErrCode225520 = 225520, // 022-5520
ErrCode225521 = 225521, // 022-5521
ErrCode225522 = 225522, // 022-5522
ErrCode225523 = 225523, // 022-5523
ErrCode225524 = 225524, // 022-5524
ErrCode225525 = 225525, // 022-5525
ErrCode225526 = 225526, // 022-5526
ErrCode225527 = 225527, // 022-5527
ErrCode225528 = 225528, // 022-5528
ErrCode225529 = 225529, // 022-5529
ErrCode225530 = 225530, // 022-5530
ErrCode225531 = 225531, // 022-5531
ErrCode225532 = 225532, // 022-5532
ErrCode225533 = 225533, // 022-5533
ErrCode225534 = 225534, // 022-5534
ErrCode225535 = 225535, // 022-5535
ErrCode225536 = 225536, // 022-5536
ErrCode225537 = 225537, // 022-5537
ErrCode225538 = 225538, // 022-5538
ErrCode225539 = 225539, // 022-5539
ErrCode225540 = 225540, // 022-5540
ErrCode225541 = 225541, // 022-5541
ErrCode225542 = 225542, // 022-5542
ErrCode225543 = 225543, // 022-5543
ErrCode225544 = 225544, // 022-5544
ErrCode225545 = 225545, // 022-5545
ErrCode225546 = 225546, // 022-5546
ErrCode225547 = 225547, // 022-5547
ErrCode225548 = 225548, // 022-5548
ErrCode225549 = 225549, // 022-5549
ErrCode225550 = 225550, // 022-5550
ErrCode225551 = 225551, // 022-5551
ErrCode225552 = 225552, // 022-5552
ErrCode225553 = 225553, // 022-5553
// Request errors
RequestError = 222400, // 022-2400
BadFormatParameter = 222401, // 022-2401
BadFormatRequest = 222402, // 022-2402
RequestParameterMissing = 222403, // 022-2403
WrongHttpMethod = 222404, // 022-2404
// Response errors
ResponseError = 222420, // 022-2420
BadFormatResponse = 222421, // 022-2421
ResponseItemMissing = 222422, // 022-2422
ResponseTooLarge = 222423, // 022-2423
// Invalid parameter errors
InvalidCommonParameter = 222450, // 022-2450
InvalidPlatformId = 222451, // 022-2451
UnauthorizedDevice = 222452, // 022-2452
InvalidSerialId = 222453, // 022-2453
InvalidMacAddress = 222454, // 022-2454
InvalidRegion = 222455, // 022-2455
InvalidCountry = 222456, // 022-2456
InvalidLanguage = 222457, // 022-2457
UnauthorizedClient = 222458, // 022-2458
DeviceIdEmpty = 222459, // 022-2459
SerialIdEmpty = 222460, // 022-2460
PlatformIdEmpty = 222461, // 022-2461
InvalidUniqueId = 222471, // 022-2471
InvalidClientId = 222472, // 022-2472
InvalidClientKey = 222473, // 022-2473
InvalidNexClientId = 222481, // 022-2481
InvalidGameServerId = 222482, // 022-2482
GameServerIdEnvironmentNotFound = 222483, // 022-2483
GameServerIdUniqueIdNotLinked = 222484, // 022-2484
ClientIdUniqueIdNotLinked = 222485, // 022-2485
DeviceMismatch = 222501, // 022-2501
CountryMismatch = 222502, // 022-2502
EulaNotAccepted = 222503, // 022-2503
// Update required errors
UpdateRequired = 222510, // 022-2510
SystemUpdateRequired = 222511, // 022-2511
ApplicationUpdateRequired = 222512, // 022-2512
UnauthorizedRequest = 222520, // 022-2520
RequestForbidden = 222522, // 022-2522
// Resource not found errors
ResourceNotFound = 222530, // 022-2530
PidNotFound = 222531, // 022-2531
NexAccountNotFound = 222532, // 022-2532
GenerateTokenFailure = 222533, // 022-2533
RequestNotFound = 222534, // 022-2534
MasterPinNotFound = 222535, // 022-2535
MailTextNotFound = 222536, // 022-2536
SendMailFailure = 222537, // 022-2537
ApprovalIdNotFound = 222538, // 022-2538
// EULA errors
InvalidEulaParameter = 222540, // 022-2540
InvalidEulaCountry = 222541, // 022-2541
InvalidEulaCountryAndVersion = 222542, // 022-2542
EulaNotFound = 222543, // 022-2543
// Not acceptable errors
PhraseNotAcceptable = 222570, // 022-2570
AccountIdAlreadyExists = 222571, // 022-2571
AccountIdNotAcceptable = 222572, // 022-2572
AccountPasswordNotAcceptable = 222573, // 022-2573
MiiNameNotAcceptable = 222574, // 022-2574
MailAddressNotAcceptable = 222575, // 022-2575
AccountIdFormatInvalid = 222576, // 022-2576
AccountIdPasswordSame = 222577, // 022-2577
AccountIdCharNotAcceptable = 222578, // 022-2578
AccountIdSuccessiveSymbol = 222579, // 022-2579
AccountIdSymbolPositionNotAcceptable = 222580, // 022-2580
AccountIdTooManyDigit = 222581, // 022-2581
AccountPasswordCharNotAcceptable = 222582, // 022-2582
AccountPasswordTooFewCharTypes = 222583, // 022-2583
AccountPasswordSuccessiveSameChar = 222584, // 022-2584
MailAddressDomainNameNotAcceptable = 222585, // 022-2585
MailAddressDomainNameNotResolved = 222586, // 022-2586
ErrCode222587 = 222587, // 022-2587
ReachedAssociationLimit = 222591, // 022-2591
ReachedRegistrationLimit = 222592, // 022-2592
CoppaNotAccepted = 222593, // 022-2593
ParentalControlsRequired = 222594, // 022-2594
MiiNotRegistered = 222595, // 022-2595
DeviceEulaCountryMismatch = 222596, // 022-2596
PendingMigration = 222597, // 022-2597
// Wrong user input errors
WrongUserInput = 222610, // 022-2610
WrongAccountPassword = 222611, // 022-2611
WrongMailAddress = 222612, // 022-2612
WrongAccountPasswordOrMailAddress = 222613, // 022-2613
WrongConfirmationCode = 222614, // 022-2614
WrongBirthDateOrMailAddress = 222615, // 022-2615
WrongAccountMail = 222616, // 022-2616
AccountAlreadyDeleted = 222631, // 022-2631
AccountIdChanged = 222632, // 022-2632
AuthenticationLocked = 222633, // 022-2633
DeviceInactive = 222634, // 022-2634
CoppaAgreementCanceled = 222635, // 022-2635
DomainAccountAlreadyExists = 222636, // 022-2636
AccountTokenExpired = 222641, // 022-2641
InvalidAccountToken = 222642, // 022-2642
AuthenticationRequired = 222643, // 022-2643
ErrCode225844 = 225844, // 022-5844
ConfirmationCodeExpired = 222651, // 022-2651
MailAddressNotValidated = 222661, // 022-2661
ExcessiveMailSendRequest = 222662, // 022-2662
// Credit card errors
CreditCardError = 222670, // 022-2670
CreditCardGeneralFailure = 222671, // 022-2671
CreditCardDeclined = 222672, // 022-2672
CreditCardBlacklisted = 222673, // 022-2673
InvalidCreditCardNumber = 222674, // 022-2674
InvalidCreditCardDate = 222675, // 022-2675
InvalidCreditCardPin = 222676, // 022-2676
InvalidPostalCode = 222677, // 022-2677
InvalidLocation = 222678, // 022-2678
CreditCardDateExpired = 222679, // 022-2679
CreditCardNumberWrong = 222680, // 022-2680
CreditCardPinWrong = 222681, // 022-2681
// Ban errors
Banned = 222800, // 022-2800
BannedAccount = 222801, // 022-2801
BannedAccountAll = 222802, // 022-2802
BannedAccountInApplication = 222803, // 022-2803
BannedAccountInNexService = 222804, // 022-2804
BannedAccountInIndependentService = 222805, // 022-2805
BannedDevice = 222811, // 022-2811
BannedDeviceAll = 222812, // 022-2812
BannedDeviceInApplication = 222813, // 022-2813
BannedDeviceInNexService = 222814, // 022-2814
BannedDeviceInIndependentService = 222815, // 022-2815
BannedAccountTemporarily = 222821, // 022-2821
BannedAccountAllTemporarily = 222822, // 022-2822
BannedAccountInApplicationTemporarily = 222823, // 022-2823
BannedAccountInNexServiceTemporarily = 222824, // 022-2824
BannedAccountInIndependentServiceTemporarily = 222825, // 022-2825
BannedDeviceTemporarily = 222831, // 022-2831
BannedDeviceAllTemporarily = 222832, // 022-2832
BannedDeviceInApplicationTemporarily = 222833, // 022-2833
BannedDeviceInNexServiceTemporarily = 222834, // 022-2834
BannedDeviceInIndependentServiceTemporarily = 222835, // 022-2835
// Service not provided errors
ServiceNotProvided = 222880, // 022-2880
UnderMaintenance = 222881, // 022-2881
ServiceClosed = 222882, // 022-2882
NintendoNetworkClosed = 222883, // 022-2883
NotProvidedCountry = 222884, // 022-2884
// Restriction errors
RestrictionError = 222900, // 022-2900
RestrictedByAge = 222901, // 022-2901
RestrictedByParentalControls = 222910, // 022-2910
OnGameInternetCommunicationRestricted = 222911, // 022-2911
InternalServerError = 222931, // 022-2931
UnknownServerError = 222932, // 022-2932
UnauthenticatedAfterSalvage = 222998, // 022-2998
AuthenticationFailureUnknown = 222999, // 022-2999
Unknown = 229999, // 022-9999
};
}
/// Gets the ACT error code for the given result
u32 GetACTErrorCode(Result result);
} // namespace Service::ACT

View File

@ -11,10 +11,31 @@ ACT_U::ACT_U(std::shared_ptr<Module> act) : Module::Interface(std::move(act), "a
static const FunctionInfo functions[] = { static const FunctionInfo functions[] = {
// clang-format off // clang-format off
{0x0001, &ACT_U::Initialize, "Initialize"}, {0x0001, &ACT_U::Initialize, "Initialize"},
{0x0002, nullptr, "GetErrorCode"}, {0x0002, &ACT_U::GetErrorCode, "GetErrorCode"},
{0x0006, &ACT_U::GetAccountDataBlock, "GetAccountDataBlock"}, {0x0003, nullptr, "GetLastResponseCode"},
{0x0005, nullptr, "GetCommonInfo"},
{0x0006, &ACT_U::GetAccountInfo, "GetAccountInfo"},
{0x0007, nullptr, "GetResultAsync"},
{0x0008, nullptr, "GetMiiImageData"},
{0x0009, nullptr, "SetNfsPassword"},
{0x000B, nullptr, "AcquireEulaList"}, {0x000B, nullptr, "AcquireEulaList"},
{0x000C, nullptr, "AcquireTimeZoneList"},
{0x000D, nullptr, "GenerateUuid"}, {0x000D, nullptr, "GenerateUuid"},
{0x000F, nullptr, "FindSlotNoByUuid"},
{0x0010, nullptr, "SaveData"},
{0x0011, nullptr, "GetTransferableId"},
{0x0012, nullptr, "AcquireNexServiceToken"},
{0x0013, nullptr, "GetNexServiceToken"},
{0x0014, nullptr, "AcquireIndependentServiceToken"},
{0x0015, nullptr, "GetIndependentServiceToken"},
{0x0016, nullptr, "AcquireAccountInfo"},
{0x0017, nullptr, "AcquireAccountIdByPrincipalId"},
{0x0018, nullptr, "AcquirePrincipalIdByAccountId"},
{0x0019, nullptr, "AcquireMii"},
{0x001A, nullptr, "AcquireAccountInfoEx"},
{0x001D, nullptr, "InquireMailAddress"},
{0x001E, nullptr, "AcquireEula"},
{0x001F, nullptr, "AcquireEulaLanguageList"},
// clang-format on // clang-format on
}; };
RegisterHandlers(functions); RegisterHandlers(functions);

View File

@ -804,30 +804,137 @@ Module::Interface::~Interface() = default;
void Module::Interface::GetNumPrograms(Kernel::HLERequestContext& ctx) { void Module::Interface::GetNumPrograms(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx); IPC::RequestParser rp(ctx);
u32 media_type = rp.Pop<u8>(); u8 media_type = rp.Pop<u8>();
if (artic_client.get()) {
struct AsyncData {
u8 media_type;
ResultVal<s32> res;
};
auto async_data = std::make_shared<AsyncData>();
async_data->media_type = media_type;
ctx.RunAsync(
[this, async_data](Kernel::HLERequestContext& ctx) {
auto req = artic_client->NewRequest("AM_GetTitleCount");
req.AddParameterU8(async_data->media_type);
auto resp = artic_client->Send(req);
if (!resp.has_value() || !resp->Succeeded()) {
async_data->res = Result(-1);
return 0;
}
auto res = Result(static_cast<u32>(resp->GetMethodResult()));
if (res.IsError()) {
async_data->res = res;
return 0;
}
auto count = resp->GetResponseS32(0);
if (!count.has_value()) {
async_data->res = Result(-1);
return 0;
}
async_data->res = *count;
return 0;
},
[async_data](Kernel::HLERequestContext& ctx) {
IPC::RequestBuilder rb(ctx, 2, 0);
rb.Push(async_data->res.Code());
rb.Push<u32>(
static_cast<u32>(async_data->res.Succeeded() ? async_data->res.Unwrap() : 0));
},
true);
} else {
IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); IPC::RequestBuilder rb = rp.MakeBuilder(2, 0);
rb.Push(ResultSuccess); rb.Push(ResultSuccess);
rb.Push<u32>(static_cast<u32>(am->am_title_list[media_type].size())); rb.Push<u32>(static_cast<u32>(am->am_title_list[media_type].size()));
}
} }
void Module::Interface::FindDLCContentInfos(Kernel::HLERequestContext& ctx) { void Module::Interface::FindDLCContentInfos(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx);
IPC::RequestParser rp(ctx);
auto media_type = static_cast<Service::FS::MediaType>(rp.Pop<u8>()); auto media_type = static_cast<Service::FS::MediaType>(rp.Pop<u8>());
u64 title_id = rp.Pop<u64>(); u64 title_id = rp.Pop<u64>();
u32 content_count = rp.Pop<u32>(); u32 content_count = rp.Pop<u32>();
auto& content_requested_in = rp.PopMappedBuffer(); auto& content_requested_in = rp.PopMappedBuffer();
if (artic_client.get()) {
struct AsyncData {
u8 media_type;
u64 title_id;
std::vector<u16> content_requested;
Result res{0};
std::vector<u8> out;
Kernel::MappedBuffer* content_info_out;
};
auto async_data = std::make_shared<AsyncData>();
async_data->media_type = static_cast<u8>(media_type);
async_data->title_id = title_id;
async_data->content_requested.resize(content_count);
content_requested_in.Read(async_data->content_requested.data(), 0,
content_count * sizeof(u16));
async_data->content_info_out = &rp.PopMappedBuffer();
ctx.RunAsync(
[this, async_data](Kernel::HLERequestContext& ctx) {
auto req = artic_client->NewRequest("AMAPP_FindDLCContentInfos");
req.AddParameterU8(async_data->media_type);
req.AddParameterU64(async_data->title_id);
req.AddParameterBuffer(async_data->content_requested.data(),
async_data->content_requested.size() * sizeof(u16));
auto resp = artic_client->Send(req);
if (!resp.has_value() || !resp->Succeeded()) {
async_data->res = Result(-1);
return 0;
}
auto res = Result(static_cast<u32>(resp->GetMethodResult()));
if (res.IsError()) {
async_data->res = res;
return 0;
}
auto content_info = resp->GetResponseBuffer(0);
if (!content_info.has_value()) {
async_data->res = Result(-1);
return 0;
}
async_data->out.resize(content_info->second);
memcpy(async_data->out.data(), content_info->first, content_info->second);
return 0;
},
[async_data](Kernel::HLERequestContext& ctx) {
if (async_data->res.IsSuccess()) {
async_data->content_info_out->Write(async_data->out.data(), 0,
async_data->out.size());
}
IPC::RequestBuilder rb(ctx, 1, 0);
rb.Push(async_data->res);
},
true);
} else {
auto& content_info_out = rp.PopMappedBuffer(); auto& content_info_out = rp.PopMappedBuffer();
// Validate that only DLC TIDs are passed in // Validate that only DLC TIDs are passed in
u32 tid_high = static_cast<u32>(title_id >> 32); u32 tid_high = static_cast<u32>(title_id >> 32);
if (tid_high != TID_HIGH_DLC) { if (tid_high != TID_HIGH_DLC) {
IPC::RequestBuilder rb = rp.MakeBuilder(1, 4); IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
rb.Push(Result(ErrCodes::InvalidTIDInList, ErrorModule::AM, ErrorSummary::InvalidArgument, rb.Push(Result(ErrCodes::InvalidTIDInList, ErrorModule::AM,
ErrorLevel::Usage)); ErrorSummary::InvalidArgument, ErrorLevel::Usage));
rb.PushMappedBuffer(content_requested_in);
rb.PushMappedBuffer(content_info_out);
return; return;
} }
@ -846,10 +953,8 @@ void Module::Interface::FindDLCContentInfos(Kernel::HLERequestContext& ctx) {
"Attempted to get info for non-existent content index {:04x}.", "Attempted to get info for non-existent content index {:04x}.",
content_requested[i]); content_requested[i]);
IPC::RequestBuilder rb = rp.MakeBuilder(1, 4); IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
rb.Push<u32>(-1); // TODO(Steveice10): Find the right error code rb.Push<u32>(-1); // TODO(Steveice10): Find the right error code
rb.PushMappedBuffer(content_requested_in);
rb.PushMappedBuffer(content_info_out);
return; return;
} }
@ -861,7 +966,8 @@ void Module::Interface::FindDLCContentInfos(Kernel::HLERequestContext& ctx) {
content_info.ownership = content_info.ownership =
OWNERSHIP_OWNED; // TODO(Steveice10): Pull this from the ticket. OWNERSHIP_OWNED; // TODO(Steveice10): Pull this from the ticket.
if (FileUtil::Exists(GetTitleContentPath(media_type, title_id, content_requested[i]))) { if (FileUtil::Exists(
GetTitleContentPath(media_type, title_id, content_requested[i]))) {
content_info.ownership |= OWNERSHIP_DOWNLOADED; content_info.ownership |= OWNERSHIP_DOWNLOADED;
} }
@ -870,10 +976,9 @@ void Module::Interface::FindDLCContentInfos(Kernel::HLERequestContext& ctx) {
} }
} }
IPC::RequestBuilder rb = rp.MakeBuilder(1, 4); IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
rb.Push(ResultSuccess); rb.Push(ResultSuccess);
rb.PushMappedBuffer(content_requested_in); }
rb.PushMappedBuffer(content_info_out);
} }
void Module::Interface::ListDLCContentInfos(Kernel::HLERequestContext& ctx) { void Module::Interface::ListDLCContentInfos(Kernel::HLERequestContext& ctx) {
@ -883,16 +988,78 @@ void Module::Interface::ListDLCContentInfos(Kernel::HLERequestContext& ctx) {
auto media_type = static_cast<Service::FS::MediaType>(rp.Pop<u8>()); auto media_type = static_cast<Service::FS::MediaType>(rp.Pop<u8>());
u64 title_id = rp.Pop<u64>(); u64 title_id = rp.Pop<u64>();
u32 start_index = rp.Pop<u32>(); u32 start_index = rp.Pop<u32>();
if (artic_client.get()) {
struct AsyncData {
u8 media_type;
u64 title_id;
u32 content_count;
u32 start_index;
Result res{0};
std::vector<u8> out;
Kernel::MappedBuffer* content_info_out;
};
auto async_data = std::make_shared<AsyncData>();
async_data->media_type = static_cast<u8>(media_type);
async_data->title_id = title_id;
async_data->content_count = content_count;
async_data->start_index = start_index;
async_data->content_info_out = &rp.PopMappedBuffer();
ctx.RunAsync(
[this, async_data](Kernel::HLERequestContext& ctx) {
auto req = artic_client->NewRequest("AMAPP_ListDLCContentInfos");
req.AddParameterU32(async_data->content_count);
req.AddParameterU8(async_data->media_type);
req.AddParameterU64(async_data->title_id);
req.AddParameterU32(async_data->start_index);
auto resp = artic_client->Send(req);
if (!resp.has_value() || !resp->Succeeded()) {
async_data->res = Result(-1);
return 0;
}
auto res = Result(static_cast<u32>(resp->GetMethodResult()));
if (res.IsError()) {
async_data->res = res;
return 0;
}
auto content_info = resp->GetResponseBuffer(0);
if (!content_info.has_value()) {
async_data->res = Result(-1);
return 0;
}
async_data->out.resize(content_info->second);
memcpy(async_data->out.data(), content_info->first, content_info->second);
return 0;
},
[async_data](Kernel::HLERequestContext& ctx) {
if (async_data->res.IsSuccess()) {
async_data->content_info_out->Write(async_data->out.data(), 0,
async_data->out.size());
}
IPC::RequestBuilder rb(ctx, 2, 0);
rb.Push(async_data->res);
rb.Push<u32>(static_cast<u32>(async_data->out.size() / sizeof(ContentInfo)));
},
true);
} else {
auto& content_info_out = rp.PopMappedBuffer(); auto& content_info_out = rp.PopMappedBuffer();
// Validate that only DLC TIDs are passed in // Validate that only DLC TIDs are passed in
u32 tid_high = static_cast<u32>(title_id >> 32); u32 tid_high = static_cast<u32>(title_id >> 32);
if (tid_high != TID_HIGH_DLC) { if (tid_high != TID_HIGH_DLC) {
IPC::RequestBuilder rb = rp.MakeBuilder(2, 2); IPC::RequestBuilder rb = rp.MakeBuilder(2, 0);
rb.Push(Result(ErrCodes::InvalidTIDInList, ErrorModule::AM, ErrorSummary::InvalidArgument, rb.Push(Result(ErrCodes::InvalidTIDInList, ErrorModule::AM,
ErrorLevel::Usage)); ErrorSummary::InvalidArgument, ErrorLevel::Usage));
rb.Push<u32>(0); rb.Push<u32>(0);
rb.PushMappedBuffer(content_info_out);
return; return;
} }
@ -923,10 +1090,10 @@ void Module::Interface::ListDLCContentInfos(Kernel::HLERequestContext& ctx) {
} }
} }
IPC::RequestBuilder rb = rp.MakeBuilder(2, 2); IPC::RequestBuilder rb = rp.MakeBuilder(2, 0);
rb.Push(ResultSuccess); rb.Push(ResultSuccess);
rb.Push(copied); rb.Push(copied);
rb.PushMappedBuffer(content_info_out); }
} }
void Module::Interface::DeleteContents(Kernel::HLERequestContext& ctx) { void Module::Interface::DeleteContents(Kernel::HLERequestContext& ctx) {
@ -945,16 +1112,77 @@ void Module::Interface::DeleteContents(Kernel::HLERequestContext& ctx) {
void Module::Interface::GetProgramList(Kernel::HLERequestContext& ctx) { void Module::Interface::GetProgramList(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx); IPC::RequestParser rp(ctx);
u32 count = rp.Pop<u32>(); u32 count = rp.Pop<u32>();
u8 media_type = rp.Pop<u8>(); u8 media_type = rp.Pop<u8>();
if (artic_client.get()) {
struct AsyncData {
u32 count;
u8 media_type;
Result res{0};
std::vector<u8> out;
Kernel::MappedBuffer* title_ids_output;
};
auto async_data = std::make_shared<AsyncData>();
async_data->count = count;
async_data->media_type = media_type;
async_data->title_ids_output = &rp.PopMappedBuffer();
ctx.RunAsync(
[this, async_data](Kernel::HLERequestContext& ctx) {
auto req = artic_client->NewRequest("AM_GetTitleList");
req.AddParameterU32(async_data->count);
req.AddParameterU8(async_data->media_type);
auto resp = artic_client->Send(req);
if (!resp.has_value() || !resp->Succeeded()) {
async_data->res = Result(-1);
return 0;
}
auto res = Result(static_cast<u32>(resp->GetMethodResult()));
if (res.IsError()) {
async_data->res = res;
return 0;
}
async_data->res = res;
auto title_ids = resp->GetResponseBuffer(0);
if (!title_ids.has_value()) {
async_data->res = Result(-1);
return 0;
}
async_data->out.resize(title_ids->second);
memcpy(async_data->out.data(), title_ids->first, title_ids->second);
return 0;
},
[async_data](Kernel::HLERequestContext& ctx) {
if (!async_data->res.IsSuccess()) {
IPC::RequestBuilder rb(ctx, 2, 0);
rb.Push(async_data->res);
rb.Push<u32>(0);
} else {
async_data->title_ids_output->Write(async_data->out.data(), 0,
async_data->out.size());
IPC::RequestBuilder rb(ctx, 2, 0);
rb.Push(async_data->res);
rb.Push<u32>(static_cast<u32>(async_data->out.size() / sizeof(u64)));
}
},
true);
} else {
auto& title_ids_output = rp.PopMappedBuffer(); auto& title_ids_output = rp.PopMappedBuffer();
if (media_type > 2) { if (media_type > 2) {
IPC::RequestBuilder rb = rp.MakeBuilder(2, 2); IPC::RequestBuilder rb = rp.MakeBuilder(2, 0);
rb.Push<u32>(-1); // TODO(shinyquagsire23): Find the right error code rb.Push<u32>(-1); // TODO(shinyquagsire23): Find the right error code
rb.Push<u32>(0); rb.Push<u32>(0);
rb.PushMappedBuffer(title_ids_output);
return; return;
} }
@ -963,10 +1191,10 @@ void Module::Interface::GetProgramList(Kernel::HLERequestContext& ctx) {
title_ids_output.Write(am->am_title_list[media_type].data(), 0, copied * sizeof(u64)); title_ids_output.Write(am->am_title_list[media_type].data(), 0, copied * sizeof(u64));
IPC::RequestBuilder rb = rp.MakeBuilder(2, 2); IPC::RequestBuilder rb = rp.MakeBuilder(2, 0);
rb.Push(ResultSuccess); rb.Push(ResultSuccess);
rb.Push(copied); rb.Push(copied);
rb.PushMappedBuffer(title_ids_output); }
} }
Result GetTitleInfoFromList(std::span<const u64> title_id_list, Service::FS::MediaType media_type, Result GetTitleInfoFromList(std::span<const u64> title_id_list, Service::FS::MediaType media_type,
@ -996,11 +1224,88 @@ Result GetTitleInfoFromList(std::span<const u64> title_id_list, Service::FS::Med
return ResultSuccess; return ResultSuccess;
} }
void Module::Interface::GetProgramInfos(Kernel::HLERequestContext& ctx) { void Module::Interface::GetProgramInfosImpl(Kernel::HLERequestContext& ctx, bool ignore_platform) {
IPC::RequestParser rp(ctx); IPC::RequestParser rp(ctx);
auto media_type = static_cast<Service::FS::MediaType>(rp.Pop<u8>()); auto media_type = static_cast<Service::FS::MediaType>(rp.Pop<u8>());
u32 title_count = rp.Pop<u32>(); u32 title_count = rp.Pop<u32>();
if (artic_client.get()) {
struct AsyncData {
u8 media_type;
bool ignore_platform;
std::vector<u64> title_id_list;
Result res{0};
std::vector<u8> out;
Kernel::MappedBuffer* title_id_list_buffer;
Kernel::MappedBuffer* title_info_out;
};
auto async_data = std::make_shared<AsyncData>();
async_data->media_type = static_cast<u8>(media_type);
async_data->ignore_platform = ignore_platform;
async_data->title_id_list.resize(title_count);
async_data->title_id_list_buffer = &rp.PopMappedBuffer();
async_data->title_id_list_buffer->Read(async_data->title_id_list.data(), 0,
title_count * sizeof(u64));
async_data->title_info_out = &rp.PopMappedBuffer();
ctx.RunAsync(
[this, async_data](Kernel::HLERequestContext& ctx) {
auto req = artic_client->NewRequest("AM_GetTitleInfo");
req.AddParameterU8(async_data->media_type);
req.AddParameterBuffer(async_data->title_id_list.data(),
async_data->title_id_list.size() * sizeof(u64));
req.AddParameterU8(async_data->ignore_platform ? 1 : 0);
auto resp = artic_client->Send(req);
if (!resp.has_value() || !resp->Succeeded()) {
async_data->res = Result(-1);
return 0;
}
auto res = Result(static_cast<u32>(resp->GetMethodResult()));
if (res.IsError()) {
async_data->res = res;
return 0;
}
async_data->res = res;
auto title_infos = resp->GetResponseBuffer(0);
if (!title_infos.has_value()) {
async_data->res = Result(-1);
return 0;
}
async_data->out.resize(title_infos->second);
memcpy(async_data->out.data(), title_infos->first, title_infos->second);
return 0;
},
[async_data](Kernel::HLERequestContext& ctx) {
if (!async_data->res.IsSuccess()) {
IPC::RequestBuilder rb(ctx, 1, async_data->ignore_platform ? 0 : 4);
rb.Push(async_data->res);
if (!async_data->ignore_platform) {
rb.PushMappedBuffer(*async_data->title_id_list_buffer);
rb.PushMappedBuffer(*async_data->title_info_out);
}
} else {
async_data->title_info_out->Write(async_data->out.data(), 0,
async_data->out.size());
IPC::RequestBuilder rb(ctx, 1, async_data->ignore_platform ? 0 : 4);
rb.Push(async_data->res);
if (!async_data->ignore_platform) {
rb.PushMappedBuffer(*async_data->title_id_list_buffer);
rb.PushMappedBuffer(*async_data->title_info_out);
}
}
},
true);
} else {
auto& title_id_list_buffer = rp.PopMappedBuffer(); auto& title_id_list_buffer = rp.PopMappedBuffer();
auto& title_info_out = rp.PopMappedBuffer(); auto& title_info_out = rp.PopMappedBuffer();
@ -1009,14 +1314,21 @@ void Module::Interface::GetProgramInfos(Kernel::HLERequestContext& ctx) {
Result result = GetTitleInfoFromList(title_id_list, media_type, title_info_out); Result result = GetTitleInfoFromList(title_id_list, media_type, title_info_out);
IPC::RequestBuilder rb = rp.MakeBuilder(1, 4); IPC::RequestBuilder rb = rp.MakeBuilder(1, ignore_platform ? 0 : 4);
rb.Push(result); rb.Push(result);
if (!ignore_platform) {
rb.PushMappedBuffer(title_id_list_buffer); rb.PushMappedBuffer(title_id_list_buffer);
rb.PushMappedBuffer(title_info_out); rb.PushMappedBuffer(title_info_out);
}
}
}
void Module::Interface::GetProgramInfos(Kernel::HLERequestContext& ctx) {
GetProgramInfosImpl(ctx, false);
} }
void Module::Interface::GetProgramInfosIgnorePlatform(Kernel::HLERequestContext& ctx) { void Module::Interface::GetProgramInfosIgnorePlatform(Kernel::HLERequestContext& ctx) {
GetProgramInfos(ctx); GetProgramInfosImpl(ctx, true);
} }
void Module::Interface::DeleteUserProgram(Kernel::HLERequestContext& ctx) { void Module::Interface::DeleteUserProgram(Kernel::HLERequestContext& ctx) {
@ -1078,6 +1390,75 @@ void Module::Interface::GetDLCTitleInfos(Kernel::HLERequestContext& ctx) {
auto media_type = static_cast<Service::FS::MediaType>(rp.Pop<u8>()); auto media_type = static_cast<Service::FS::MediaType>(rp.Pop<u8>());
u32 title_count = rp.Pop<u32>(); u32 title_count = rp.Pop<u32>();
if (artic_client.get()) {
struct AsyncData {
u8 media_type;
std::vector<u64> title_id_list;
Result res{0};
std::vector<u8> out;
Kernel::MappedBuffer* title_id_list_buffer;
Kernel::MappedBuffer* title_info_out;
};
auto async_data = std::make_shared<AsyncData>();
async_data->media_type = static_cast<u8>(media_type);
async_data->title_id_list.resize(title_count);
async_data->title_id_list_buffer = &rp.PopMappedBuffer();
async_data->title_id_list_buffer->Read(async_data->title_id_list.data(), 0,
title_count * sizeof(u64));
async_data->title_info_out = &rp.PopMappedBuffer();
ctx.RunAsync(
[this, async_data](Kernel::HLERequestContext& ctx) {
auto req = artic_client->NewRequest("AMAPP_GetDLCTitleInfos");
req.AddParameterU8(async_data->media_type);
req.AddParameterBuffer(async_data->title_id_list.data(),
async_data->title_id_list.size() * sizeof(u64));
auto resp = artic_client->Send(req);
if (!resp.has_value() || !resp->Succeeded()) {
async_data->res = Result(-1);
return 0;
}
auto res = Result(static_cast<u32>(resp->GetMethodResult()));
if (res.IsError()) {
async_data->res = res;
return 0;
}
async_data->res = res;
auto title_infos = resp->GetResponseBuffer(0);
if (!title_infos.has_value()) {
async_data->res = Result(-1);
return 0;
}
async_data->out.resize(title_infos->second);
memcpy(async_data->out.data(), title_infos->first, title_infos->second);
return 0;
},
[async_data](Kernel::HLERequestContext& ctx) {
if (!async_data->res.IsSuccess()) {
IPC::RequestBuilder rb(ctx, 1, 4);
rb.Push(async_data->res);
rb.PushMappedBuffer(*async_data->title_id_list_buffer);
rb.PushMappedBuffer(*async_data->title_info_out);
} else {
async_data->title_info_out->Write(async_data->out.data(), 0,
async_data->out.size());
IPC::RequestBuilder rb(ctx, 1, 4);
rb.Push(async_data->res);
rb.PushMappedBuffer(*async_data->title_id_list_buffer);
rb.PushMappedBuffer(*async_data->title_info_out);
}
},
true);
} else {
auto& title_id_list_buffer = rp.PopMappedBuffer(); auto& title_id_list_buffer = rp.PopMappedBuffer();
auto& title_info_out = rp.PopMappedBuffer(); auto& title_info_out = rp.PopMappedBuffer();
@ -1104,6 +1485,7 @@ void Module::Interface::GetDLCTitleInfos(Kernel::HLERequestContext& ctx) {
rb.Push(result); rb.Push(result);
rb.PushMappedBuffer(title_id_list_buffer); rb.PushMappedBuffer(title_id_list_buffer);
rb.PushMappedBuffer(title_info_out); rb.PushMappedBuffer(title_info_out);
}
} }
void Module::Interface::GetPatchTitleInfos(Kernel::HLERequestContext& ctx) { void Module::Interface::GetPatchTitleInfos(Kernel::HLERequestContext& ctx) {
@ -1111,6 +1493,75 @@ void Module::Interface::GetPatchTitleInfos(Kernel::HLERequestContext& ctx) {
auto media_type = static_cast<Service::FS::MediaType>(rp.Pop<u8>()); auto media_type = static_cast<Service::FS::MediaType>(rp.Pop<u8>());
u32 title_count = rp.Pop<u32>(); u32 title_count = rp.Pop<u32>();
if (artic_client.get()) {
struct AsyncData {
u8 media_type;
std::vector<u64> title_id_list;
Result res{0};
std::vector<u8> out;
Kernel::MappedBuffer* title_id_list_buffer;
Kernel::MappedBuffer* title_info_out;
};
auto async_data = std::make_shared<AsyncData>();
async_data->media_type = static_cast<u8>(media_type);
async_data->title_id_list.resize(title_count);
async_data->title_id_list_buffer = &rp.PopMappedBuffer();
async_data->title_id_list_buffer->Read(async_data->title_id_list.data(), 0,
title_count * sizeof(u64));
async_data->title_info_out = &rp.PopMappedBuffer();
ctx.RunAsync(
[this, async_data](Kernel::HLERequestContext& ctx) {
auto req = artic_client->NewRequest("AMAPP_GetPatchTitleInfos");
req.AddParameterU8(async_data->media_type);
req.AddParameterBuffer(async_data->title_id_list.data(),
async_data->title_id_list.size() * sizeof(u64));
auto resp = artic_client->Send(req);
if (!resp.has_value() || !resp->Succeeded()) {
async_data->res = Result(-1);
return 0;
}
auto res = Result(static_cast<u32>(resp->GetMethodResult()));
if (res.IsError()) {
async_data->res = res;
return 0;
}
async_data->res = res;
auto title_infos = resp->GetResponseBuffer(0);
if (!title_infos.has_value()) {
async_data->res = Result(-1);
return 0;
}
async_data->out.resize(title_infos->second);
memcpy(async_data->out.data(), title_infos->first, title_infos->second);
return 0;
},
[async_data](Kernel::HLERequestContext& ctx) {
if (!async_data->res.IsSuccess()) {
IPC::RequestBuilder rb(ctx, 1, 4);
rb.Push(async_data->res);
rb.PushMappedBuffer(*async_data->title_id_list_buffer);
rb.PushMappedBuffer(*async_data->title_info_out);
} else {
async_data->title_info_out->Write(async_data->out.data(), 0,
async_data->out.size());
IPC::RequestBuilder rb(ctx, 1, 4);
rb.Push(async_data->res);
rb.PushMappedBuffer(*async_data->title_id_list_buffer);
rb.PushMappedBuffer(*async_data->title_info_out);
}
},
true);
} else {
auto& title_id_list_buffer = rp.PopMappedBuffer(); auto& title_id_list_buffer = rp.PopMappedBuffer();
auto& title_info_out = rp.PopMappedBuffer(); auto& title_info_out = rp.PopMappedBuffer();
@ -1137,6 +1588,7 @@ void Module::Interface::GetPatchTitleInfos(Kernel::HLERequestContext& ctx) {
rb.Push(result); rb.Push(result);
rb.PushMappedBuffer(title_id_list_buffer); rb.PushMappedBuffer(title_id_list_buffer);
rb.PushMappedBuffer(title_info_out); rb.PushMappedBuffer(title_info_out);
}
} }
void Module::Interface::ListDataTitleTicketInfos(Kernel::HLERequestContext& ctx) { void Module::Interface::ListDataTitleTicketInfos(Kernel::HLERequestContext& ctx) {
@ -1144,6 +1596,64 @@ void Module::Interface::ListDataTitleTicketInfos(Kernel::HLERequestContext& ctx)
u32 ticket_count = rp.Pop<u32>(); u32 ticket_count = rp.Pop<u32>();
u64 title_id = rp.Pop<u64>(); u64 title_id = rp.Pop<u64>();
u32 start_index = rp.Pop<u32>(); u32 start_index = rp.Pop<u32>();
if (artic_client.get()) {
struct AsyncData {
u64 title_id;
u32 ticket_count;
u32 start_index;
Result res{0};
std::vector<u8> out;
Kernel::MappedBuffer* ticket_info_out;
};
auto async_data = std::make_shared<AsyncData>();
async_data->title_id = title_id;
async_data->ticket_count = ticket_count;
async_data->start_index = start_index;
async_data->ticket_info_out = &rp.PopMappedBuffer();
ctx.RunAsync(
[this, async_data](Kernel::HLERequestContext& ctx) {
auto req = artic_client->NewRequest("AMAPP_ListDataTitleTicketInfos");
req.AddParameterU32(async_data->ticket_count);
req.AddParameterU64(async_data->title_id);
req.AddParameterU32(async_data->start_index);
auto resp = artic_client->Send(req);
if (!resp.has_value() || !resp->Succeeded()) {
async_data->res = Result(-1);
return 0;
}
auto res = Result(static_cast<u32>(resp->GetMethodResult()));
if (res.IsError()) {
async_data->res = res;
return 0;
}
auto content_info = resp->GetResponseBuffer(0);
if (!content_info.has_value()) {
async_data->res = Result(-1);
return 0;
}
async_data->out.resize(content_info->second);
memcpy(async_data->out.data(), content_info->first, content_info->second);
return 0;
},
[async_data](Kernel::HLERequestContext& ctx) {
if (async_data->res.IsSuccess()) {
async_data->ticket_info_out->Write(async_data->out.data(), 0,
async_data->out.size());
}
IPC::RequestBuilder rb(ctx, 2, 0);
rb.Push(async_data->res);
rb.Push<u32>(static_cast<u32>(async_data->out.size() / sizeof(TicketInfo)));
},
true);
} else {
auto& ticket_info_out = rp.PopMappedBuffer(); auto& ticket_info_out = rp.PopMappedBuffer();
std::size_t write_offset = 0; std::size_t write_offset = 0;
@ -1165,17 +1675,67 @@ void Module::Interface::ListDataTitleTicketInfos(Kernel::HLERequestContext& ctx)
LOG_WARNING(Service_AM, LOG_WARNING(Service_AM,
"(STUBBED) ticket_count=0x{:08X}, title_id=0x{:016x}, start_index=0x{:08X}", "(STUBBED) ticket_count=0x{:08X}, title_id=0x{:016x}, start_index=0x{:08X}",
ticket_count, title_id, start_index); ticket_count, title_id, start_index);
}
} }
void Module::Interface::GetDLCContentInfoCount(Kernel::HLERequestContext& ctx) { void Module::Interface::GetDLCContentInfoCount(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx); IPC::RequestParser rp(ctx);
auto media_type = static_cast<Service::FS::MediaType>(rp.Pop<u8>()); auto media_type = static_cast<Service::FS::MediaType>(rp.Pop<u8>());
u64 title_id = rp.Pop<u64>(); u64 title_id = rp.Pop<u64>();
if (artic_client.get()) {
struct AsyncData {
u8 media_type;
u64 title_id;
ResultVal<s32> res;
};
auto async_data = std::make_shared<AsyncData>();
async_data->media_type = static_cast<u8>(media_type);
async_data->title_id = title_id;
ctx.RunAsync(
[this, async_data](Kernel::HLERequestContext& ctx) {
auto req = artic_client->NewRequest("AMAPP_GetDLCContentInfoCount");
req.AddParameterU8(async_data->media_type);
req.AddParameterU64(async_data->title_id);
auto resp = artic_client->Send(req);
if (!resp.has_value() || !resp->Succeeded()) {
async_data->res = Result(-1);
return 0;
}
auto res = Result(static_cast<u32>(resp->GetMethodResult()));
if (res.IsError()) {
async_data->res = res;
return 0;
}
auto count = resp->GetResponseS32(0);
if (!count.has_value()) {
async_data->res = Result(-1);
return 0;
}
async_data->res = *count;
return 0;
},
[async_data](Kernel::HLERequestContext& ctx) {
IPC::RequestBuilder rb(ctx, 2, 0);
rb.Push(async_data->res.Code());
rb.Push<u32>(
static_cast<u32>(async_data->res.Succeeded() ? async_data->res.Unwrap() : 0));
},
true);
} else {
// Validate that only DLC TIDs are passed in // Validate that only DLC TIDs are passed in
u32 tid_high = static_cast<u32>(title_id >> 32); u32 tid_high = static_cast<u32>(title_id >> 32);
if (tid_high != TID_HIGH_DLC) { if (tid_high != TID_HIGH_DLC) {
IPC::RequestBuilder rb = rp.MakeBuilder(2, 2); IPC::RequestBuilder rb = rp.MakeBuilder(2, 0);
rb.Push(Result(ErrCodes::InvalidTID, ErrorModule::AM, ErrorSummary::InvalidArgument, rb.Push(Result(ErrCodes::InvalidTID, ErrorModule::AM, ErrorSummary::InvalidArgument,
ErrorLevel::Usage)); ErrorLevel::Usage));
rb.Push<u32>(0); rb.Push<u32>(0);
@ -1192,8 +1752,9 @@ void Module::Interface::GetDLCContentInfoCount(Kernel::HLERequestContext& ctx) {
rb.Push<u32>(static_cast<u32>(tmd.GetContentCount())); rb.Push<u32>(static_cast<u32>(tmd.GetContentCount()));
} else { } else {
rb.Push<u32>(1); // Number of content infos plus one rb.Push<u32>(1); // Number of content infos plus one
LOG_WARNING(Service_AM, "(STUBBED) called media_type={}, title_id=0x{:016x}", media_type, LOG_WARNING(Service_AM, "(STUBBED) called media_type={}, title_id=0x{:016x}",
title_id); media_type, title_id);
}
} }
} }

View File

@ -20,6 +20,7 @@
#include "core/hle/kernel/mutex.h" #include "core/hle/kernel/mutex.h"
#include "core/hle/result.h" #include "core/hle/result.h"
#include "core/hle/service/service.h" #include "core/hle/service/service.h"
#include "network/artic_base/artic_base_client.h"
namespace Core { namespace Core {
class System; class System;
@ -245,7 +246,13 @@ public:
return am; return am;
} }
void UseArticClient(std::shared_ptr<Network::ArticBase::Client>& client) {
artic_client = client;
}
protected: protected:
void GetProgramInfosImpl(Kernel::HLERequestContext& ctx, bool ignore_platform);
/** /**
* AM::GetNumPrograms service function * AM::GetNumPrograms service function
* Gets the number of installed titles in the requested media type * Gets the number of installed titles in the requested media type
@ -753,6 +760,9 @@ public:
protected: protected:
std::shared_ptr<Module> am; std::shared_ptr<Module> am;
// Placed on the interface level so that only am:net and am:app have it.
std::shared_ptr<Network::ArticBase::Client> artic_client = nullptr;
}; };
/** /**

View File

@ -544,6 +544,7 @@ void Y2R_U::StopConversion(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx); IPC::RequestParser rp(ctx);
if (is_busy_conversion) { if (is_busy_conversion) {
is_busy_conversion = false;
system.CoreTiming().RemoveEvent(completion_signal_event); system.CoreTiming().RemoveEvent(completion_signal_event);
} }

View File

@ -9,7 +9,7 @@
#include <boost/serialization/unique_ptr.hpp> #include <boost/serialization/unique_ptr.hpp>
#include <cryptopp/osrng.h> #include <cryptopp/osrng.h>
#include <cryptopp/sha.h> #include <cryptopp/sha.h>
#include <fmt/format.h> #include <fmt/ranges.h>
#include "common/archives.h" #include "common/archives.h"
#include "common/file_util.h" #include "common/file_util.h"
#include "common/logging/log.h" #include "common/logging/log.h"
@ -278,7 +278,7 @@ void Module::Interface::GetTransferableId(Kernel::HLERequestContext& ctx) {
std::array<u8, 12> buffer; std::array<u8, 12> buffer;
const Result result = const Result result =
cfg->GetConfigBlock(ConsoleUniqueID2BlockID, 8, AccessFlag::SystemRead, buffer.data()); cfg->GetConfigBlock(ConsoleUniqueID2BlockID, 8, AccessFlag::Global, buffer.data());
rb.Push(result); rb.Push(result);
if (result.IsSuccess()) { if (result.IsSuccess()) {
std::memcpy(&buffer[8], &app_id_salt, sizeof(u32)); std::memcpy(&buffer[8], &app_id_salt, sizeof(u32));
@ -502,11 +502,42 @@ ResultVal<void*> Module::GetConfigBlockPointer(u32 block_id, u32 size, AccessFla
} }
Result Module::GetConfigBlock(u32 block_id, u32 size, AccessFlag accesss_flag, void* output) { Result Module::GetConfigBlock(u32 block_id, u32 size, AccessFlag accesss_flag, void* output) {
bool get_from_artic =
block_id == ConsoleUniqueID2BlockID &&
(static_cast<u16>(accesss_flag) & static_cast<u16>(AccessFlag::UserRead)) != 0;
if (get_from_artic && artic_client.get()) {
auto req = artic_client->NewRequest("CFGU_GetConfigInfoBlk2");
req.AddParameterS32(block_id);
req.AddParameterU32(size);
auto resp = artic_client->Send(req);
if (!resp.has_value() || !resp->Succeeded())
return Result(-1);
auto res = Result(static_cast<u32>(resp->GetMethodResult()));
if (res.IsError())
return res;
auto buff = resp->GetResponseBuffer(0);
if (!buff.has_value())
return Result(-1);
size_t actually_read = buff->second;
if (actually_read > size)
return Result(-1);
memcpy(output, buff->first, actually_read);
return ResultSuccess;
} else {
void* pointer = nullptr; void* pointer = nullptr;
CASCADE_RESULT(pointer, GetConfigBlockPointer(block_id, size, accesss_flag)); CASCADE_RESULT(pointer, GetConfigBlockPointer(block_id, size, accesss_flag));
std::memcpy(output, pointer, size); std::memcpy(output, pointer, size);
return ResultSuccess; return ResultSuccess;
}
} }
Result Module::SetConfigBlock(u32 block_id, u32 size, AccessFlag accesss_flag, const void* input) { Result Module::SetConfigBlock(u32 block_id, u32 size, AccessFlag accesss_flag, const void* input) {

View File

@ -11,6 +11,7 @@
#include <utility> #include <utility>
#include "common/common_types.h" #include "common/common_types.h"
#include "core/hle/service/service.h" #include "core/hle/service/service.h"
#include "network/artic_base/artic_base_client.h"
namespace FileSys { namespace FileSys {
class ArchiveBackend; class ArchiveBackend;
@ -210,6 +211,10 @@ public:
std::shared_ptr<Module> GetModule() const; std::shared_ptr<Module> GetModule() const;
void UseArticClient(std::shared_ptr<Network::ArticBase::Client>& client) {
GetModule()->artic_client = client;
}
/** /**
* CFG::GetCountryCodeString service function * CFG::GetCountryCodeString service function
* Inputs: * Inputs:
@ -680,6 +685,8 @@ private:
bool preferred_region_chosen = false; bool preferred_region_chosen = false;
MCUData mcu_data{}; MCUData mcu_data{};
std::shared_ptr<Network::ArticBase::Client> artic_client = nullptr;
template <class Archive> template <class Archive>
void serialize(Archive& ar, const unsigned int); void serialize(Archive& ar, const unsigned int);
friend class boost::serialization::access; friend class boost::serialization::access;

View File

@ -298,18 +298,22 @@ Result ArchiveManager::DeleteSystemSaveData(u32 high, u32 low) {
return ResultSuccess; return ResultSuccess;
} }
Result ArchiveManager::CreateSystemSaveData(u32 high, u32 low) { Result ArchiveManager::CreateSystemSaveData(u32 high, u32 low, u32 total_size, u32 block_size,
// Construct the binary path to the archive first u32 number_directories, u32 number_files,
const FileSys::Path path = FileSys::ConstructSystemSaveDataBinaryPath(high, low); u32 number_directory_buckets, u32 number_file_buckets,
u8 duplicate_data) {
const std::string& nand_directory = FileUtil::GetUserPath(FileUtil::UserPath::NANDDir); auto archive = id_code_map.find(ArchiveIdCode::SystemSaveData);
const std::string base_path = FileSys::GetSystemSaveDataContainerPath(nand_directory);
const std::string systemsavedata_path = FileSys::GetSystemSaveDataPath(base_path, path); if (archive == id_code_map.end()) {
if (!FileUtil::CreateFullPath(systemsavedata_path)) { return UnimplementedFunction(ErrorModule::FS); // TODO(Subv): Find the right error
return ResultUnknown; // TODO(Subv): Find the right error code
} }
return ResultSuccess; auto sys_savedata = static_cast<FileSys::ArchiveFactory_SystemSaveData*>(archive->second.get());
return sys_savedata->FormatAsSysData(high, low, total_size, block_size, number_directories,
number_files, number_directory_buckets,
number_file_buckets, duplicate_data);
} }
ResultVal<ArchiveResource> ArchiveManager::GetArchiveResource(MediaType media_type) const { ResultVal<ArchiveResource> ArchiveManager::GetArchiveResource(MediaType media_type) const {
@ -454,6 +458,16 @@ void ArchiveManager::RegisterArticNCCH(std::shared_ptr<Network::ArticBase::Clien
reinterpret_cast<FileSys::ArchiveFactory_NCCH*>(itr->second.get())->RegisterArtic(client); reinterpret_cast<FileSys::ArchiveFactory_NCCH*>(itr->second.get())->RegisterArtic(client);
} }
void ArchiveManager::RegisterArticSystemSaveData(
std::shared_ptr<Network::ArticBase::Client>& client) {
auto itr = id_code_map.find(ArchiveIdCode::SystemSaveData);
if (itr == id_code_map.end() || itr->second.get() == nullptr) {
return;
}
reinterpret_cast<FileSys::ArchiveFactory_SystemSaveData*>(itr->second.get())
->RegisterArtic(client);
}
ArchiveManager::ArchiveManager(Core::System& system) : system(system) { ArchiveManager::ArchiveManager(Core::System& system) : system(system) {
RegisterArchiveTypes(); RegisterArchiveTypes();
} }

View File

@ -264,11 +264,12 @@ public:
/** /**
* Creates the SystemSaveData archive folder for the specified save data id * Creates the SystemSaveData archive folder for the specified save data id
* @param high The high word of the SystemSaveData archive to create
* @param low The low word of the SystemSaveData archive to create
* @return Result 0 on success or the corresponding code on error * @return Result 0 on success or the corresponding code on error
*/ */
Result CreateSystemSaveData(u32 high, u32 low); Result CreateSystemSaveData(u32 high, u32 low, u32 total_size, u32 block_size,
u32 number_directories, u32 number_files,
u32 number_directory_buckets, u32 number_file_buckets,
u8 duplicate_data);
/** /**
* Returns capacity and free space information about the given media type. * Returns capacity and free space information about the given media type.
@ -296,6 +297,8 @@ public:
void RegisterArticNCCH(std::shared_ptr<Network::ArticBase::Client>& client); void RegisterArticNCCH(std::shared_ptr<Network::ArticBase::Client>& client);
void RegisterArticSystemSaveData(std::shared_ptr<Network::ArticBase::Client>& client);
private: private:
Core::System& system; Core::System& system;

View File

@ -1026,7 +1026,9 @@ void FS_USER::CreateSystemSaveData(Kernel::HLERequestContext& ctx) {
file_buckets, duplicate); file_buckets, duplicate);
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
rb.Push(archives.CreateSystemSaveData(savedata_high, savedata_low)); rb.Push(archives.CreateSystemSaveData(savedata_high, savedata_low, total_size, block_size,
directories, files, directory_buckets, file_buckets,
duplicate ? 1 : 0));
} }
void FS_USER::CreateLegacySystemSaveData(Kernel::HLERequestContext& ctx) { void FS_USER::CreateLegacySystemSaveData(Kernel::HLERequestContext& ctx) {
@ -1048,7 +1050,8 @@ void FS_USER::CreateLegacySystemSaveData(Kernel::HLERequestContext& ctx) {
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
// With this command, the SystemSaveData always has save_high = 0 (Always created in the NAND) // With this command, the SystemSaveData always has save_high = 0 (Always created in the NAND)
rb.Push(archives.CreateSystemSaveData(0, savedata_id)); rb.Push(archives.CreateSystemSaveData(0, savedata_id, total_size, block_size, directories,
files, directory_buckets, file_buckets, duplicate));
} }
void FS_USER::InitializeWithSdkVersion(Kernel::HLERequestContext& ctx) { void FS_USER::InitializeWithSdkVersion(Kernel::HLERequestContext& ctx) {

View File

@ -71,7 +71,12 @@ struct CacheFlushCommand {
/// GSP command /// GSP command
struct Command { struct Command {
union {
BitField<0, 8, CommandId> id; BitField<0, 8, CommandId> id;
BitField<8, 8, u32> unknown1;
BitField<16, 8, u32> stop;
BitField<24, 8, u32> unknown2;
};
union { union {
DmaCommand dma_request; DmaCommand dma_request;
SubmitCmdListCommand submit_gpu_cmdlist; SubmitCmdListCommand submit_gpu_cmdlist;
@ -86,6 +91,8 @@ static_assert(sizeof(Command) == 0x20, "Command struct has incorrect size");
/// GSP shared memory GX command buffer header /// GSP shared memory GX command buffer header
struct CommandBuffer { struct CommandBuffer {
static constexpr u32 STATUS_STOPPED = 0x1;
static constexpr u32 STATUS_CMD_FAILED = 0x80;
union { union {
u32 hex; u32 hex;
@ -99,6 +106,11 @@ struct CommandBuffer {
// application when writing a command to shared memory, after increasing this value // application when writing a command to shared memory, after increasing this value
// TriggerCmdReqQueue is only used if this field is value 1. // TriggerCmdReqQueue is only used if this field is value 1.
BitField<8, 8, u32> number_commands; BitField<8, 8, u32> number_commands;
// When any of the following flags are set to 1, the GSP module stops processing the
// commands in the command buffer.
BitField<16, 8, u32> status;
BitField<24, 8, u32> should_stop;
}; };
u32 unk[7]; u32 unk[7];

View File

@ -9,6 +9,7 @@
#include <boost/serialization/shared_ptr.hpp> #include <boost/serialization/shared_ptr.hpp>
#include "common/archives.h" #include "common/archives.h"
#include "common/bit_field.h" #include "common/bit_field.h"
#include "common/settings.h"
#include "core/core.h" #include "core/core.h"
#include "core/hle/ipc_helpers.h" #include "core/hle/ipc_helpers.h"
#include "core/hle/kernel/shared_memory.h" #include "core/hle/kernel/shared_memory.h"
@ -408,21 +409,54 @@ void GSP_GPU::SetLcdForceBlack(Kernel::HLERequestContext& ctx) {
void GSP_GPU::TriggerCmdReqQueue(Kernel::HLERequestContext& ctx) { void GSP_GPU::TriggerCmdReqQueue(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx); IPC::RequestParser rp(ctx);
// Iterate through each command.
auto* command_buffer = GetCommandBuffer(active_thread_id); auto* command_buffer = GetCommandBuffer(active_thread_id);
auto& gpu = system.GPU(); auto& gpu = system.GPU();
for (u32 i = 0; i < command_buffer->number_commands; i++) {
gpu.Debugger().GXCommandProcessed(command_buffer->commands[i]);
// Decode and execute command bool requires_delay = false;
gpu.Execute(command_buffer->commands[i]);
// Indicates that command has completed while (command_buffer->number_commands) {
command_buffer->number_commands.Assign(command_buffer->number_commands - 1); if (command_buffer->should_stop) {
command_buffer->status.Assign(CommandBuffer::STATUS_STOPPED);
break;
}
if (command_buffer->status == CommandBuffer::STATUS_STOPPED) {
break;
} }
Command command = command_buffer->commands[command_buffer->index];
if (command.id == CommandId::SubmitCmdList && !requires_delay &&
Settings::values.delay_game_render_thread_us.GetValue() != 0) {
requires_delay = true;
}
// Decrease the number of commands remaining and increase the current index
command_buffer->number_commands.Assign(command_buffer->number_commands - 1);
command_buffer->index.Assign((command_buffer->index + 1) % 0xF);
gpu.Debugger().GXCommandProcessed(command);
// Decode and execute command
gpu.Execute(command);
if (command.stop) {
command_buffer->should_stop.Assign(1);
}
}
if (requires_delay) {
ctx.RunAsync(
[](Kernel::HLERequestContext& ctx) {
return Settings::values.delay_game_render_thread_us.GetValue() * 1000;
},
[](Kernel::HLERequestContext& ctx) {
IPC::RequestBuilder rb(ctx, 1, 0);
rb.Push(ResultSuccess);
},
false);
} else {
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
rb.Push(ResultSuccess); rb.Push(ResultSuccess);
}
} }
void GSP_GPU::ImportDisplayCaptureInfo(Kernel::HLERequestContext& ctx) { void GSP_GPU::ImportDisplayCaptureInfo(Kernel::HLERequestContext& ctx) {

View File

@ -20,6 +20,8 @@
#include "core/hle/service/hid/hid.h" #include "core/hle/service/hid/hid.h"
#include "core/hle/service/hid/hid_spvr.h" #include "core/hle/service/hid/hid_spvr.h"
#include "core/hle/service/hid/hid_user.h" #include "core/hle/service/hid/hid_user.h"
#include "core/hle/service/ir/ir_rst.h"
#include "core/hle/service/ir/ir_user.h"
#include "core/hle/service/service.h" #include "core/hle/service/service.h"
#include "core/movie.h" #include "core/movie.h"
@ -53,6 +55,32 @@ void Module::serialize(Archive& ar, const unsigned int file_version) {
} }
SERIALIZE_IMPL(Module) SERIALIZE_IMPL(Module)
ArticBaseController::ArticBaseController(
const std::shared_ptr<Network::ArticBase::Client>& client) {
udp_stream =
client->NewUDPStream("ArticController", sizeof(ArticBaseController::ControllerData),
std::chrono::milliseconds(2));
if (udp_stream.get()) {
udp_stream->Start();
}
}
ArticBaseController::ControllerData ArticBaseController::GetControllerData() {
if (udp_stream.get() && udp_stream->IsReady()) {
auto data = udp_stream->GetLastPacket();
if (data.size() == sizeof(ControllerData)) {
u32 id = *reinterpret_cast<u32*>(data.data());
if ((id - last_packet_id) < (std::numeric_limits<u32>::max() / 2)) {
last_packet_id = id;
memcpy(&last_controller_data, data.data(), data.size());
}
}
}
return last_controller_data;
}
constexpr float accelerometer_coef = 512.0f; // measured from hw test result constexpr float accelerometer_coef = 512.0f; // measured from hw test result
constexpr float gyroscope_coef = 14.375f; // got from hwtest GetGyroscopeLowRawToDpsCoefficient call constexpr float gyroscope_coef = 14.375f; // got from hwtest GetGyroscopeLowRawToDpsCoefficient call
@ -111,6 +139,60 @@ void Module::UpdatePadCallback(std::uintptr_t user_data, s64 cycles_late) {
LoadInputDevices(); LoadInputDevices();
using namespace Settings::NativeButton; using namespace Settings::NativeButton;
if (artic_controller.get() && artic_controller->IsReady()) {
constexpr u32 HID_VALID_KEYS = 0xF0003FFF;
constexpr u32 LIBCTRU_TOUCH_KEY = (1 << 20);
ArticBaseController::ControllerData data = artic_controller->GetControllerData();
state.hex = data.pad & HID_VALID_KEYS;
s16 circle_pad_x = data.c_pad_x;
s16 circle_pad_y = data.c_pad_y;
system.Movie().HandlePadAndCircleStatus(state, circle_pad_x, circle_pad_y);
mem->pad.current_state.hex = state.hex;
mem->pad.index = next_pad_index;
next_pad_index = (next_pad_index + 1) % mem->pad.entries.size();
// Get the previous Pad state
u32 last_entry_index = (mem->pad.index - 1) % mem->pad.entries.size();
PadState old_state = mem->pad.entries[last_entry_index].current_state;
// Compute bitmask with 1s for bits different from the old state
PadState changed = {{(state.hex ^ old_state.hex)}};
// Get the current Pad entry
PadDataEntry& pad_entry = mem->pad.entries[mem->pad.index];
// Update entry properties
pad_entry.current_state.hex = state.hex;
pad_entry.delta_additions.hex = changed.hex & state.hex;
pad_entry.delta_removals.hex = changed.hex & old_state.hex;
pad_entry.circle_pad_x = circle_pad_x;
pad_entry.circle_pad_y = circle_pad_y;
// If we just updated index 0, provide a new timestamp
if (mem->pad.index == 0) {
mem->pad.index_reset_ticks_previous = mem->pad.index_reset_ticks;
mem->pad.index_reset_ticks = (s64)system.CoreTiming().GetTicks();
}
mem->touch.index = next_touch_index;
next_touch_index = (next_touch_index + 1) % mem->touch.entries.size();
// Get the current touch entry
TouchDataEntry& touch_entry = mem->touch.entries[mem->touch.index];
bool pressed = (data.pad & LIBCTRU_TOUCH_KEY) != 0;
touch_entry.x = static_cast<u16>(data.touch_x);
touch_entry.y = static_cast<u16>(data.touch_y);
touch_entry.valid.Assign(pressed ? 1 : 0);
system.Movie().HandleTouchStatus(touch_entry);
} else {
state.a.Assign(buttons[A - BUTTON_HID_BEGIN]->GetStatus()); state.a.Assign(buttons[A - BUTTON_HID_BEGIN]->GetStatus());
state.b.Assign(buttons[B - BUTTON_HID_BEGIN]->GetStatus()); state.b.Assign(buttons[B - BUTTON_HID_BEGIN]->GetStatus());
state.x.Assign(buttons[X - BUTTON_HID_BEGIN]->GetStatus()); state.x.Assign(buttons[X - BUTTON_HID_BEGIN]->GetStatus());
@ -138,11 +220,11 @@ void Module::UpdatePadCallback(std::uintptr_t user_data, s64 cycles_late) {
// These are rounded rather than truncated on actual hardware // These are rounded rather than truncated on actual hardware
s16 circle_pad_new_x = static_cast<s16>(std::roundf(circle_pad_x_f * MAX_CIRCLEPAD_POS)); s16 circle_pad_new_x = static_cast<s16>(std::roundf(circle_pad_x_f * MAX_CIRCLEPAD_POS));
s16 circle_pad_new_y = static_cast<s16>(std::roundf(circle_pad_y_f * MAX_CIRCLEPAD_POS)); s16 circle_pad_new_y = static_cast<s16>(std::roundf(circle_pad_y_f * MAX_CIRCLEPAD_POS));
s16 circle_pad_x = s16 circle_pad_x = (circle_pad_new_x +
(circle_pad_new_x + std::accumulate(circle_pad_old_x.begin(), circle_pad_old_x.end(), 0)) / std::accumulate(circle_pad_old_x.begin(), circle_pad_old_x.end(), 0)) /
CIRCLE_PAD_AVERAGING; CIRCLE_PAD_AVERAGING;
s16 circle_pad_y = s16 circle_pad_y = (circle_pad_new_y +
(circle_pad_new_y + std::accumulate(circle_pad_old_y.begin(), circle_pad_old_y.end(), 0)) / std::accumulate(circle_pad_old_y.begin(), circle_pad_old_y.end(), 0)) /
CIRCLE_PAD_AVERAGING; CIRCLE_PAD_AVERAGING;
circle_pad_old_x.erase(circle_pad_old_x.begin()); circle_pad_old_x.erase(circle_pad_old_x.begin());
circle_pad_old_x.push_back(circle_pad_new_x); circle_pad_old_x.push_back(circle_pad_new_x);
@ -200,6 +282,7 @@ void Module::UpdatePadCallback(std::uintptr_t user_data, s64 cycles_late) {
touch_entry.valid.Assign(pressed ? 1 : 0); touch_entry.valid.Assign(pressed ? 1 : 0);
system.Movie().HandleTouchStatus(touch_entry); system.Movie().HandleTouchStatus(touch_entry);
}
// TODO(bunnei): We're not doing anything with offset 0xA8 + 0x18 of HID SharedMemory, which // TODO(bunnei): We're not doing anything with offset 0xA8 + 0x18 of HID SharedMemory, which
// supposedly is "Touch-screen entry, which contains the raw coordinate data prior to being // supposedly is "Touch-screen entry, which contains the raw coordinate data prior to being
@ -231,6 +314,16 @@ void Module::UpdateAccelerometerCallback(std::uintptr_t user_data, s64 cycles_la
mem->accelerometer.index = next_accelerometer_index; mem->accelerometer.index = next_accelerometer_index;
next_accelerometer_index = (next_accelerometer_index + 1) % mem->accelerometer.entries.size(); next_accelerometer_index = (next_accelerometer_index + 1) % mem->accelerometer.entries.size();
AccelerometerDataEntry& accelerometer_entry =
mem->accelerometer.entries[mem->accelerometer.index];
if (artic_controller.get() && artic_controller->IsReady()) {
ArticBaseController::ControllerData data = artic_controller->GetControllerData();
accelerometer_entry.x = data.accel_x;
accelerometer_entry.y = data.accel_y;
accelerometer_entry.z = data.accel_z;
} else {
Common::Vec3<float> accel; Common::Vec3<float> accel;
std::tie(accel, std::ignore) = motion_device->GetStatus(); std::tie(accel, std::ignore) = motion_device->GetStatus();
accel *= accelerometer_coef; accel *= accelerometer_coef;
@ -238,12 +331,10 @@ void Module::UpdateAccelerometerCallback(std::uintptr_t user_data, s64 cycles_la
// The time stretch formula should be like // The time stretch formula should be like
// stretched_vector = (raw_vector - gravity) * stretch_ratio + gravity // stretched_vector = (raw_vector - gravity) * stretch_ratio + gravity
AccelerometerDataEntry& accelerometer_entry =
mem->accelerometer.entries[mem->accelerometer.index];
accelerometer_entry.x = static_cast<s16>(accel.x); accelerometer_entry.x = static_cast<s16>(accel.x);
accelerometer_entry.y = static_cast<s16>(accel.y); accelerometer_entry.y = static_cast<s16>(accel.y);
accelerometer_entry.z = static_cast<s16>(accel.z); accelerometer_entry.z = static_cast<s16>(accel.z);
}
system.Movie().HandleAccelerometerStatus(accelerometer_entry); system.Movie().HandleAccelerometerStatus(accelerometer_entry);
@ -278,6 +369,13 @@ void Module::UpdateGyroscopeCallback(std::uintptr_t user_data, s64 cycles_late)
GyroscopeDataEntry& gyroscope_entry = mem->gyroscope.entries[mem->gyroscope.index]; GyroscopeDataEntry& gyroscope_entry = mem->gyroscope.entries[mem->gyroscope.index];
if (artic_controller.get() && artic_controller->IsReady()) {
ArticBaseController::ControllerData data = artic_controller->GetControllerData();
gyroscope_entry.x = data.gyro_x;
gyroscope_entry.y = data.gyro_y;
gyroscope_entry.z = data.gyro_z;
} else {
Common::Vec3<float> gyro; Common::Vec3<float> gyro;
std::tie(std::ignore, gyro) = motion_device->GetStatus(); std::tie(std::ignore, gyro) = motion_device->GetStatus();
double stretch = system.perf_stats->GetLastFrameTimeScale(); double stretch = system.perf_stats->GetLastFrameTimeScale();
@ -285,6 +383,7 @@ void Module::UpdateGyroscopeCallback(std::uintptr_t user_data, s64 cycles_late)
gyroscope_entry.x = static_cast<s16>(gyro.x); gyroscope_entry.x = static_cast<s16>(gyro.x);
gyroscope_entry.y = static_cast<s16>(gyro.y); gyroscope_entry.y = static_cast<s16>(gyro.y);
gyroscope_entry.z = static_cast<s16>(gyro.z); gyroscope_entry.z = static_cast<s16>(gyro.z);
}
system.Movie().HandleGyroscopeStatus(gyroscope_entry); system.Movie().HandleGyroscopeStatus(gyroscope_entry);
@ -316,6 +415,23 @@ void Module::Interface::GetIPCHandles(Kernel::HLERequestContext& ctx) {
void Module::Interface::EnableAccelerometer(Kernel::HLERequestContext& ctx) { void Module::Interface::EnableAccelerometer(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx); IPC::RequestParser rp(ctx);
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
auto& artic_client = GetModule()->artic_client;
if (artic_client.get()) {
auto req = artic_client->NewRequest("HIDUSER_EnableAccelerometer");
auto resp = artic_client->Send(req);
if (!resp.has_value()) {
rb.Push(ResultUnknown);
} else {
rb.Push(Result{static_cast<u32>(resp->GetMethodResult())});
}
} else {
rb.Push(ResultSuccess);
}
++hid->enable_accelerometer_count; ++hid->enable_accelerometer_count;
// Schedules the accelerometer update event if the accelerometer was just enabled // Schedules the accelerometer update event if the accelerometer was just enabled
@ -324,15 +440,29 @@ void Module::Interface::EnableAccelerometer(Kernel::HLERequestContext& ctx) {
hid->accelerometer_update_event); hid->accelerometer_update_event);
} }
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
rb.Push(ResultSuccess);
LOG_DEBUG(Service_HID, "called"); LOG_DEBUG(Service_HID, "called");
} }
void Module::Interface::DisableAccelerometer(Kernel::HLERequestContext& ctx) { void Module::Interface::DisableAccelerometer(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx); IPC::RequestParser rp(ctx);
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
auto& artic_client = GetModule()->artic_client;
if (artic_client.get()) {
auto req = artic_client->NewRequest("HIDUSER_DisableAccelerometer");
auto resp = artic_client->Send(req);
if (!resp.has_value()) {
rb.Push(ResultUnknown);
} else {
rb.Push(Result{static_cast<u32>(resp->GetMethodResult())});
}
} else {
rb.Push(ResultSuccess);
}
--hid->enable_accelerometer_count; --hid->enable_accelerometer_count;
// Unschedules the accelerometer update event if the accelerometer was just disabled // Unschedules the accelerometer update event if the accelerometer was just disabled
@ -340,15 +470,29 @@ void Module::Interface::DisableAccelerometer(Kernel::HLERequestContext& ctx) {
hid->system.CoreTiming().UnscheduleEvent(hid->accelerometer_update_event, 0); hid->system.CoreTiming().UnscheduleEvent(hid->accelerometer_update_event, 0);
} }
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
rb.Push(ResultSuccess);
LOG_DEBUG(Service_HID, "called"); LOG_DEBUG(Service_HID, "called");
} }
void Module::Interface::EnableGyroscopeLow(Kernel::HLERequestContext& ctx) { void Module::Interface::EnableGyroscopeLow(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx); IPC::RequestParser rp(ctx);
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
auto& artic_client = GetModule()->artic_client;
if (artic_client.get()) {
auto req = artic_client->NewRequest("HIDUSER_EnableGyroscope");
auto resp = artic_client->Send(req);
if (!resp.has_value()) {
rb.Push(ResultUnknown);
} else {
rb.Push(Result{static_cast<u32>(resp->GetMethodResult())});
}
} else {
rb.Push(ResultSuccess);
}
++hid->enable_gyroscope_count; ++hid->enable_gyroscope_count;
// Schedules the gyroscope update event if the gyroscope was just enabled // Schedules the gyroscope update event if the gyroscope was just enabled
@ -356,15 +500,29 @@ void Module::Interface::EnableGyroscopeLow(Kernel::HLERequestContext& ctx) {
hid->system.CoreTiming().ScheduleEvent(gyroscope_update_ticks, hid->gyroscope_update_event); hid->system.CoreTiming().ScheduleEvent(gyroscope_update_ticks, hid->gyroscope_update_event);
} }
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
rb.Push(ResultSuccess);
LOG_DEBUG(Service_HID, "called"); LOG_DEBUG(Service_HID, "called");
} }
void Module::Interface::DisableGyroscopeLow(Kernel::HLERequestContext& ctx) { void Module::Interface::DisableGyroscopeLow(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx); IPC::RequestParser rp(ctx);
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
auto& artic_client = GetModule()->artic_client;
if (artic_client.get()) {
auto req = artic_client->NewRequest("HIDUSER_DisableGyroscope");
auto resp = artic_client->Send(req);
if (!resp.has_value()) {
rb.Push(ResultUnknown);
} else {
rb.Push(Result{static_cast<u32>(resp->GetMethodResult())});
}
} else {
rb.Push(ResultSuccess);
}
--hid->enable_gyroscope_count; --hid->enable_gyroscope_count;
// Unschedules the gyroscope update event if the gyroscope was just disabled // Unschedules the gyroscope update event if the gyroscope was just disabled
@ -372,9 +530,6 @@ void Module::Interface::DisableGyroscopeLow(Kernel::HLERequestContext& ctx) {
hid->system.CoreTiming().UnscheduleEvent(hid->gyroscope_update_event, 0); hid->system.CoreTiming().UnscheduleEvent(hid->gyroscope_update_event, 0);
} }
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
rb.Push(ResultSuccess);
LOG_DEBUG(Service_HID, "called"); LOG_DEBUG(Service_HID, "called");
} }
@ -382,14 +537,78 @@ void Module::Interface::GetGyroscopeLowRawToDpsCoefficient(Kernel::HLERequestCon
IPC::RequestParser rp(ctx); IPC::RequestParser rp(ctx);
IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); IPC::RequestBuilder rb = rp.MakeBuilder(2, 0);
auto& artic_client = GetModule()->artic_client;
if (artic_client.get()) {
auto req = artic_client->NewRequest("HIDUSER_GetGyroRawToDpsCoef");
auto resp = artic_client->Send(req);
if (!resp.has_value()) {
rb.Push(ResultUnknown);
rb.Push(0.f);
return;
}
Result res = Result{static_cast<u32>(resp->GetMethodResult())};
if (res.IsError()) {
rb.Push(res);
rb.Push(0.f);
return;
}
auto coef = resp->GetResponseFloat(0);
if (!coef.has_value()) {
rb.Push(ResultUnknown);
rb.Push(0.f);
return;
}
rb.Push(res);
rb.Push(*coef);
} else {
rb.Push(ResultSuccess); rb.Push(ResultSuccess);
rb.Push(gyroscope_coef); rb.Push(gyroscope_coef);
}
} }
void Module::Interface::GetGyroscopeLowCalibrateParam(Kernel::HLERequestContext& ctx) { void Module::Interface::GetGyroscopeLowCalibrateParam(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx); IPC::RequestParser rp(ctx);
IPC::RequestBuilder rb = rp.MakeBuilder(6, 0); IPC::RequestBuilder rb = rp.MakeBuilder(6, 0);
auto& artic_client = GetModule()->artic_client;
if (artic_client.get()) {
GyroscopeCalibrateParam param;
auto req = artic_client->NewRequest("HIDUSER_GetGyroCalibrateParam");
auto resp = artic_client->Send(req);
if (!resp.has_value()) {
rb.Push(ResultUnknown);
rb.PushRaw(param);
return;
}
Result res = Result{static_cast<u32>(resp->GetMethodResult())};
if (res.IsError()) {
rb.Push(res);
rb.PushRaw(param);
return;
}
auto param_buf = resp->GetResponseBuffer(0);
if (!param_buf.has_value() || param_buf->second != sizeof(param)) {
rb.Push(ResultUnknown);
rb.PushRaw(param);
return;
}
memcpy(&param, param_buf->first, sizeof(param));
rb.Push(res);
rb.PushRaw(param);
} else {
rb.Push(ResultSuccess); rb.Push(ResultSuccess);
const s16 param_unit = 6700; // an approximate value taken from hw const s16 param_unit = 6700; // an approximate value taken from hw
@ -401,6 +620,7 @@ void Module::Interface::GetGyroscopeLowCalibrateParam(Kernel::HLERequestContext&
rb.PushRaw(param); rb.PushRaw(param);
LOG_WARNING(Service_HID, "(STUBBED) called"); LOG_WARNING(Service_HID, "(STUBBED) called");
}
} }
void Module::Interface::GetSoundVolume(Kernel::HLERequestContext& ctx) { void Module::Interface::GetSoundVolume(Kernel::HLERequestContext& ctx) {
@ -454,6 +674,24 @@ Module::Module(Core::System& system) : system(system) {
timing.ScheduleEvent(pad_update_ticks, pad_update_event); timing.ScheduleEvent(pad_update_ticks, pad_update_event);
} }
void Module::UseArticClient(const std::shared_ptr<Network::ArticBase::Client>& client) {
artic_client = client;
artic_controller = std::make_shared<ArticBaseController>(client);
if (!artic_controller->IsCreated()) {
artic_controller.reset();
} else {
auto ir_user = system.ServiceManager().GetService<Service::IR::IR_USER>("ir:USER");
if (ir_user.get()) {
ir_user->UseArticController(artic_controller);
}
auto ir_rst = system.ServiceManager().GetService<Service::IR::IR_RST>("ir:rst");
if (ir_rst.get()) {
ir_rst->UseArticController(artic_controller);
}
}
}
void Module::ReloadInputDevices() { void Module::ReloadInputDevices() {
is_device_reload_pending.store(true); is_device_reload_pending.store(true);
} }

View File

@ -14,13 +14,11 @@
#include "common/common_funcs.h" #include "common/common_funcs.h"
#include "common/common_types.h" #include "common/common_types.h"
#include "common/settings.h" #include "common/settings.h"
#include "core/core.h"
#include "core/core_timing.h" #include "core/core_timing.h"
#include "core/frontend/input.h" #include "core/frontend/input.h"
#include "core/hle/service/service.h" #include "core/hle/service/service.h"
#include "network/artic_base/artic_base_client.h"
namespace Core {
class System;
}
namespace Kernel { namespace Kernel {
class Event; class Event;
@ -199,6 +197,44 @@ struct DirectionState {
/// Translates analog stick axes to directions. This is exposed for ir_rst module to use. /// Translates analog stick axes to directions. This is exposed for ir_rst module to use.
DirectionState GetStickDirectionState(s16 circle_pad_x, s16 circle_pad_y); DirectionState GetStickDirectionState(s16 circle_pad_x, s16 circle_pad_y);
class ArticBaseController {
public:
struct ControllerData {
u32 index{};
u32 pad{};
s16 c_pad_x{};
s16 c_pad_y{};
u16 touch_x{};
u16 touch_y{};
s16 c_stick_x{};
s16 c_stick_y{};
s16 accel_x{};
s16 accel_y{};
s16 accel_z{};
s16 gyro_x{};
s16 gyro_y{};
s16 gyro_z{};
};
static_assert(sizeof(ControllerData) == 0x20, "Incorrect ControllerData size");
ArticBaseController(const std::shared_ptr<Network::ArticBase::Client>& client);
bool IsCreated() {
return udp_stream.get();
}
bool IsReady() {
return udp_stream.get() ? udp_stream->IsReady() : false;
}
ControllerData GetControllerData();
private:
std::shared_ptr<Network::ArticBase::Client::UDPStream> udp_stream;
u32 last_packet_id{};
ControllerData last_controller_data{};
};
class Module final { class Module final {
public: public:
explicit Module(Core::System& system); explicit Module(Core::System& system);
@ -296,6 +332,8 @@ public:
std::shared_ptr<Module> hid; std::shared_ptr<Module> hid;
}; };
void UseArticClient(const std::shared_ptr<Network::ArticBase::Client>& client);
void ReloadInputDevices(); void ReloadInputDevices();
const PadState& GetState() const; const PadState& GetState() const;
@ -355,6 +393,9 @@ private:
std::unique_ptr<Input::TouchDevice> touch_device; std::unique_ptr<Input::TouchDevice> touch_device;
std::unique_ptr<Input::TouchDevice> touch_btn_device; std::unique_ptr<Input::TouchDevice> touch_btn_device;
std::shared_ptr<ArticBaseController> artic_controller;
std::shared_ptr<Network::ArticBase::Client> artic_client;
template <class Archive> template <class Archive>
void serialize(Archive& ar, const unsigned int); void serialize(Archive& ar, const unsigned int);
friend class boost::serialization::access; friend class boost::serialization::access;

View File

@ -381,10 +381,10 @@ void Context::MakeRequestNonSSL(httplib::Request& request, const URLInfo& url_in
if (!client->send(request, response, error)) { if (!client->send(request, response, error)) {
LOG_ERROR(Service_HTTP, "Request failed: {}: {}", error, httplib::to_string(error)); LOG_ERROR(Service_HTTP, "Request failed: {}: {}", error, httplib::to_string(error));
state = RequestState::TimedOut; state = RequestState::Completed;
} else { } else {
LOG_DEBUG(Service_HTTP, "Request successful"); LOG_DEBUG(Service_HTTP, "Request successful");
state = RequestState::ReadyToDownloadContent; state = RequestState::ReceivingBody;
} }
} }
@ -439,10 +439,10 @@ void Context::MakeRequestSSL(httplib::Request& request, const URLInfo& url_info,
if (!client->send(request, response, error)) { if (!client->send(request, response, error)) {
LOG_ERROR(Service_HTTP, "Request failed: {}: {}", error, httplib::to_string(error)); LOG_ERROR(Service_HTTP, "Request failed: {}: {}", error, httplib::to_string(error));
state = RequestState::TimedOut; state = RequestState::Completed;
} else { } else {
LOG_DEBUG(Service_HTTP, "Request successful"); LOG_DEBUG(Service_HTTP, "Request successful");
state = RequestState::ReadyToDownloadContent; state = RequestState::ReceivingBody;
} }
} }
@ -696,6 +696,7 @@ void HTTP_C::ReceiveDataImpl(Kernel::HLERequestContext& ctx, bool timeout) {
http_context.current_copied_data, http_context.current_copied_data,
0, remaining_data); 0, remaining_data);
http_context.current_copied_data += remaining_data; http_context.current_copied_data += remaining_data;
http_context.state = RequestState::Completed;
rb.Push(ResultSuccess); rb.Push(ResultSuccess);
} else { } else {
async_data->buffer->Write(http_context.response.body.data() + async_data->buffer->Write(http_context.response.body.data() +

View File

@ -48,12 +48,29 @@ enum class RequestMethod : u8 {
constexpr u32 TotalRequestMethods = 8; constexpr u32 TotalRequestMethods = 8;
enum class RequestState : u8 { enum class RequestState : u8 {
NotStarted = 0x1, // Request has not started yet. /// Request has not started yet.
ConnectingToServer = 0x5, // Request in progress, connecting to server. NotStarted = 0x1,
SendingRequest = 0x6, // Request in progress, sending HTTP request.
ReceivingResponse = 0x7, // Request in progress, receiving HTTP response. /// Request in progress, connecting to server.
ReadyToDownloadContent = 0x8, // Ready to download the content. ConnectingToServer = 0x5,
TimedOut = 0xA, // Request timed out?
/// Request in progress, sending HTTP request.
SendingRequest = 0x6,
// Request in progress, receiving HTTP response and headers.
ReceivingResponse = 0x7,
/// Request in progress, receiving HTTP body. The HTTP module may
/// get stuck in this state if the internal receive buffer gets full.
/// Once the user calls ReceiveData it will get unstuck.
ReceivingBody = 0x8,
/// Request is finished and all data has been received. HTTP transitions
/// to the Completed state shortly afterwards after some cleanup.
Received = 0x9,
/// Request is completed.
Completed = 0xA,
}; };
enum class PostDataEncoding : u8 { enum class PostDataEncoding : u8 {

View File

@ -2,10 +2,11 @@
// Licensed under GPLv2 or any later version // Licensed under GPLv2 or any later version
// Refer to the license.txt file included. // Refer to the license.txt file included.
#include <fmt/format.h> #include <fmt/ranges.h>
#include "common/alignment.h" #include "common/alignment.h"
#include "common/settings.h" #include "common/settings.h"
#include "core/core_timing.h" #include "core/core_timing.h"
#include "core/hle/service/hid/hid.h"
#include "core/hle/service/ir/extra_hid.h" #include "core/hle/service/ir/extra_hid.h"
#include "core/movie.h" #include "core/movie.h"
@ -230,15 +231,38 @@ void ExtraHID::SendHIDStatus() {
if (is_device_reload_pending.exchange(false)) if (is_device_reload_pending.exchange(false))
LoadInputDevices(); LoadInputDevices();
constexpr u32 ZL_BUTTON = (1 << 14);
constexpr u32 ZR_BUTTON = (1 << 15);
constexpr int C_STICK_CENTER = 0x800; constexpr int C_STICK_CENTER = 0x800;
// TODO(wwylele): this value is not accurately measured. We currently assume that the axis can // TODO(wwylele): this value is not accurately measured. We currently assume that the axis can
// take values in the whole range of a 12-bit integer. // take values in the whole range of a 12-bit integer.
constexpr int C_STICK_RADIUS = 0x7FF; constexpr int C_STICK_RADIUS = 0x7FF;
ExtraHIDResponse response{};
if (artic_controller.get() && artic_controller->IsReady()) {
Service::HID::ArticBaseController::ControllerData data =
artic_controller->GetControllerData();
constexpr int MAX_CSTICK_RADIUS = 0x9C; // Max value for a c-stick radius
response.c_stick.header.Assign(static_cast<u8>(ResponseID::PollHID));
response.c_stick.c_stick_x.Assign(static_cast<u32>(
(static_cast<float>(data.c_stick_x) / MAX_CSTICK_RADIUS) * C_STICK_RADIUS +
C_STICK_CENTER));
response.c_stick.c_stick_y.Assign(static_cast<u32>(
(static_cast<float>(data.c_stick_y) / MAX_CSTICK_RADIUS) * C_STICK_RADIUS +
C_STICK_CENTER));
response.buttons.battery_level.Assign(0x1F);
response.buttons.zl_not_held.Assign((data.pad & ZL_BUTTON) == 0);
response.buttons.zr_not_held.Assign((data.pad & ZR_BUTTON) == 0);
response.buttons.r_not_held.Assign(1);
response.unknown = 0;
} else {
float x, y; float x, y;
std::tie(x, y) = c_stick->GetStatus(); std::tie(x, y) = c_stick->GetStatus();
ExtraHIDResponse response{};
response.c_stick.header.Assign(static_cast<u8>(ResponseID::PollHID)); response.c_stick.header.Assign(static_cast<u8>(ResponseID::PollHID));
response.c_stick.c_stick_x.Assign(static_cast<u32>(C_STICK_CENTER + C_STICK_RADIUS * x)); response.c_stick.c_stick_x.Assign(static_cast<u32>(C_STICK_CENTER + C_STICK_RADIUS * x));
response.c_stick.c_stick_y.Assign(static_cast<u32>(C_STICK_CENTER + C_STICK_RADIUS * y)); response.c_stick.c_stick_y.Assign(static_cast<u32>(C_STICK_CENTER + C_STICK_RADIUS * y));
@ -247,6 +271,7 @@ void ExtraHID::SendHIDStatus() {
response.buttons.zr_not_held.Assign(!zr->GetStatus()); response.buttons.zr_not_held.Assign(!zr->GetStatus());
response.buttons.r_not_held.Assign(1); response.buttons.r_not_held.Assign(1);
response.unknown = 0; response.unknown = 0;
}
movie.HandleExtraHidResponse(response); movie.HandleExtraHidResponse(response);

View File

@ -19,6 +19,10 @@ class Timing;
class Movie; class Movie;
} // namespace Core } // namespace Core
namespace Service::HID {
class ArticBaseController;
};
namespace Service::IR { namespace Service::IR {
struct ExtraHIDResponse { struct ExtraHIDResponse {
@ -54,6 +58,10 @@ public:
/// Requests input devices reload from current settings. Called when the input settings change. /// Requests input devices reload from current settings. Called when the input settings change.
void RequestInputDevicesReload(); void RequestInputDevicesReload();
void UseArticController(const std::shared_ptr<Service::HID::ArticBaseController>& ac) {
artic_controller = ac;
}
private: private:
void SendHIDStatus(); void SendHIDStatus();
void HandleConfigureHIDPollingRequest(std::span<const u8> request); void HandleConfigureHIDPollingRequest(std::span<const u8> request);
@ -70,6 +78,8 @@ private:
std::unique_ptr<Input::AnalogDevice> c_stick; std::unique_ptr<Input::AnalogDevice> c_stick;
std::atomic<bool> is_device_reload_pending; std::atomic<bool> is_device_reload_pending;
std::shared_ptr<Service::HID::ArticBaseController> artic_controller = nullptr;
template <class Archive> template <class Archive>
void serialize(Archive& ar, const unsigned int) { void serialize(Archive& ar, const unsigned int) {
ar& hid_period; ar& hid_period;

View File

@ -72,7 +72,22 @@ void IR_RST::UpdateCallback(std::uintptr_t user_data, s64 cycles_late) {
if (is_device_reload_pending.exchange(false)) if (is_device_reload_pending.exchange(false))
LoadInputDevices(); LoadInputDevices();
constexpr u32 VALID_EXTRAHID_KEYS = 0xF00C000;
PadState state; PadState state;
s16 c_stick_x, c_stick_y;
if (artic_controller.get() && artic_controller->IsReady()) {
Service::HID::ArticBaseController::ControllerData data =
artic_controller->GetControllerData();
state.hex = data.pad & VALID_EXTRAHID_KEYS;
c_stick_x = data.c_stick_x;
c_stick_y = data.c_stick_y;
system.Movie().HandleIrRst(state, c_stick_x, c_stick_y);
} else {
state.zl.Assign(zl_button->GetStatus()); state.zl.Assign(zl_button->GetStatus());
state.zr.Assign(zr_button->GetStatus()); state.zr.Assign(zr_button->GetStatus());
@ -80,8 +95,8 @@ void IR_RST::UpdateCallback(std::uintptr_t user_data, s64 cycles_late) {
float c_stick_x_f, c_stick_y_f; float c_stick_x_f, c_stick_y_f;
std::tie(c_stick_x_f, c_stick_y_f) = c_stick->GetStatus(); std::tie(c_stick_x_f, c_stick_y_f) = c_stick->GetStatus();
constexpr int MAX_CSTICK_RADIUS = 0x9C; // Max value for a c-stick radius constexpr int MAX_CSTICK_RADIUS = 0x9C; // Max value for a c-stick radius
s16 c_stick_x = static_cast<s16>(c_stick_x_f * MAX_CSTICK_RADIUS); c_stick_x = static_cast<s16>(c_stick_x_f * MAX_CSTICK_RADIUS);
s16 c_stick_y = static_cast<s16>(c_stick_y_f * MAX_CSTICK_RADIUS); c_stick_y = static_cast<s16>(c_stick_y_f * MAX_CSTICK_RADIUS);
system.Movie().HandleIrRst(state, c_stick_x, c_stick_y); system.Movie().HandleIrRst(state, c_stick_x, c_stick_y);
@ -92,6 +107,7 @@ void IR_RST::UpdateCallback(std::uintptr_t user_data, s64 cycles_late) {
state.c_stick_left.Assign(direction.left); state.c_stick_left.Assign(direction.left);
state.c_stick_right.Assign(direction.right); state.c_stick_right.Assign(direction.right);
} }
}
// TODO (wwylele): implement raw C-stick data for raw_c_stick = true // TODO (wwylele): implement raw C-stick data for raw_c_stick = true

View File

@ -21,6 +21,10 @@ namespace Core {
struct TimingEventType; struct TimingEventType;
}; };
namespace Service::HID {
class ArticBaseController;
};
namespace Service::IR { namespace Service::IR {
union PadState { union PadState {
@ -42,6 +46,10 @@ public:
~IR_RST(); ~IR_RST();
void ReloadInputDevices(); void ReloadInputDevices();
void UseArticController(const std::shared_ptr<Service::HID::ArticBaseController>& ac) {
artic_controller = ac;
}
private: private:
/** /**
* GetHandles service function * GetHandles service function
@ -88,6 +96,8 @@ private:
bool raw_c_stick{false}; bool raw_c_stick{false};
int update_period{0}; int update_period{0};
std::shared_ptr<Service::HID::ArticBaseController> artic_controller = nullptr;
template <class Archive> template <class Archive>
void serialize(Archive& ar, const unsigned int); void serialize(Archive& ar, const unsigned int);
friend class boost::serialization::access; friend class boost::serialization::access;

View File

@ -8,7 +8,7 @@
#include <boost/serialization/base_object.hpp> #include <boost/serialization/base_object.hpp>
#include <boost/serialization/shared_ptr.hpp> #include <boost/serialization/shared_ptr.hpp>
#include <boost/serialization/unique_ptr.hpp> #include <boost/serialization/unique_ptr.hpp>
#include <fmt/format.h> #include <fmt/ranges.h>
#include "common/archives.h" #include "common/archives.h"
#include "common/swap.h" #include "common/swap.h"
#include "core/core.h" #include "core/core.h"
@ -480,6 +480,12 @@ void IR_USER::ReloadInputDevices() {
extra_hid->RequestInputDevicesReload(); extra_hid->RequestInputDevicesReload();
} }
void IR_USER::UseArticController(const std::shared_ptr<Service::HID::ArticBaseController>& ac) {
if (extra_hid.get()) {
extra_hid->UseArticController(ac);
}
}
IRDevice::IRDevice(SendFunc send_func_) : send_func(send_func_) {} IRDevice::IRDevice(SendFunc send_func_) : send_func(send_func_) {}
IRDevice::~IRDevice() = default; IRDevice::~IRDevice() = default;

View File

@ -14,6 +14,10 @@ class Event;
class SharedMemory; class SharedMemory;
} // namespace Kernel } // namespace Kernel
namespace Service::HID {
class ArticBaseController;
};
namespace Service::IR { namespace Service::IR {
class BufferManager; class BufferManager;
@ -57,6 +61,8 @@ public:
void ReloadInputDevices(); void ReloadInputDevices();
void UseArticController(const std::shared_ptr<Service::HID::ArticBaseController>& ac);
private: private:
/** /**
* InitializeIrNopShared service function * InitializeIrNopShared service function

View File

@ -38,7 +38,7 @@ SERVICE_CONSTRUCT_IMPL(Service::PLGLDR::PLG_LDR)
namespace Service::PLGLDR { namespace Service::PLGLDR {
static const Kernel::CoreVersion plgldr_version = Kernel::CoreVersion(1, 0, 0); static const Kernel::CoreVersion plgldr_version = Kernel::CoreVersion(1, 0, 2);
PLG_LDR::PLG_LDR(Core::System& system_) : ServiceFramework{"plg:ldr", 1}, system(system_) { PLG_LDR::PLG_LDR(Core::System& system_) : ServiceFramework{"plg:ldr", 1}, system(system_) {
static const FunctionInfo functions[] = { static const FunctionInfo functions[] = {
@ -91,7 +91,11 @@ void PLG_LDR::serialize(Archive& ar, const unsigned int) {
SERIALIZE_IMPL(PLG_LDR) SERIALIZE_IMPL(PLG_LDR)
void PLG_LDR::OnProcessRun(Kernel::Process& process, Kernel::KernelSystem& kernel) { void PLG_LDR::OnProcessRun(Kernel::Process& process, Kernel::KernelSystem& kernel) {
if (!plgldr_context.is_enabled || plgldr_context.plugin_loaded) { constexpr u32 TITLE_ID_APP_MASK = 0xFFFFFFED;
constexpr u32 TITLE_ID_APP_VALUE = 0x04000000;
if (!plgldr_context.is_enabled || plgldr_context.plugin_loaded ||
(static_cast<u32>(process.codeset->program_id >> 32) & TITLE_ID_APP_MASK) !=
TITLE_ID_APP_VALUE) {
return; return;
} }
{ {

Some files were not shown because too many files have changed in this diff Show More