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
# 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)
# Bundle required QT wayland libraries
export EXTRA_QT_PLUGINS="waylandcompositor"
export EXTRA_PLATFORM_PLUGINS="libqwayland-egl.so;libqwayland-generic.so"
else
# For the linux-fresh verification target, verify compilation without PCH as well.
export EXTRA_CMAKE_FLAGS=(-DCITRA_USE_PRECOMPILED_HEADERS=OFF)

View File

@ -235,7 +235,7 @@ find_package(Threads REQUIRED)
if (ENABLE_QT)
if (NOT USE_SYSTEM_QT)
download_qt(6.6.0)
download_qt(6.7.2)
endif()
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
.Os
.Sh NAME
@ -12,6 +12,20 @@
.Bl -tag -width Ds
.It Fl g Ar port , Fl Fl gdbport Ar 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
Shows syntax help and exits
.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)
# _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()
if (CITRA_WARNINGS_AS_ERRORS)

View File

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

View File

@ -527,12 +527,28 @@ object NativeLibrary {
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 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)
/**

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.utils.ControllerMappingHelper
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.EmulationMenuSettings
import org.citra.citra_emu.utils.ThemeUtil
@ -47,7 +46,6 @@ import org.citra.citra_emu.viewmodel.EmulationViewModel
class EmulationActivity : AppCompatActivity() {
private val preferences: SharedPreferences
get() = PreferenceManager.getDefaultSharedPreferences(CitraApplication.appContext)
private var foregroundService: Intent? = null
var isActivityRecreated = false
private val settingsViewModel: SettingsViewModel by viewModels()
@ -66,7 +64,7 @@ class EmulationActivity : AppCompatActivity() {
binding = ActivityEmulationBinding.inflate(layoutInflater)
screenAdjustmentUtil = ScreenAdjustmentUtil(windowManager, settingsViewModel.settings)
hotkeyUtility = HotkeyUtility(screenAdjustmentUtil)
hotkeyUtility = HotkeyUtility(screenAdjustmentUtil, this)
setContentView(binding.root)
val navHostFragment =
@ -85,10 +83,6 @@ class EmulationActivity : AppCompatActivity() {
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() })
}
@ -112,7 +106,6 @@ class EmulationActivity : AppCompatActivity() {
override fun onDestroy() {
EmulationLifecycleUtil.clear()
stopForegroundService(this)
super.onDestroy()
}
@ -186,8 +179,7 @@ class EmulationActivity : AppCompatActivity() {
return false
}
val button =
preferences.getInt(InputBindingSetting.getInputButtonKey(event.keyCode), event.keyCode)
val button = preferences.getInt(InputBindingSetting.getInputButtonKey(event), event.scanCode)
val action: Int = when (event.action) {
KeyEvent.ACTION_DOWN -> {
// On some devices, the back gesture / button press is not intercepted by androidx
@ -453,12 +445,4 @@ class EmulationActivity : AppCompatActivity() {
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 {
View.VISIBLE
}
binding.textGameId.visibility = if (game.titleId == 0L) {
View.GONE
} else {
View.VISIBLE
}
binding.textGameTitle.text = game.title
binding.textCompany.text = game.company
binding.textGameId.text = String.format("ID: %016X", game.titleId)
binding.textFilename.text = game.filename
val backgroundColorId =
@ -181,6 +187,9 @@ class GameAdapter(private val activity: AppCompatActivity) :
binding.textCompany.ellipsize = TextUtils.TruncateAt.MARQUEE
binding.textCompany.isSelected = true
binding.textGameId.ellipsize = TextUtils.TruncateAt.MARQUEE
binding.textGameId.isSelected = true
binding.textFilename.ellipsize = TextUtils.TruncateAt.MARQUEE
binding.textFilename.isSelected = true
},

View File

@ -8,5 +8,7 @@ enum class Hotkey(val button: Int) {
SWAP_SCREEN(10001),
CYCLE_LAYOUT(10002),
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
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.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 }
@ -18,6 +22,23 @@ class HotkeyUtility(private val screenAdjustmentUtil: ScreenAdjustmentUtil) {
Hotkey.CYCLE_LAYOUT.button -> screenAdjustmentUtil.cycleLayouts()
Hotkey.CLOSE_GAME.button -> EmulationLifecycleUtil.closeGame()
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 -> {}
}
return true

View File

@ -13,7 +13,8 @@ enum class BooleanSetting(
ASYNC_SHADERS("async_shader_compilation", Settings.SECTION_RENDERER, false),
PLUGIN_LOADER("plugin_loader", Settings.SECTION_SYSTEM, false),
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
@ -33,7 +34,8 @@ enum class BooleanSetting(
companion object {
private val NOT_RUNTIME_EDITABLE = listOf(
PLUGIN_LOADER,
ALLOW_PLUGIN_LOADER
ALLOW_PLUGIN_LOADER,
ASYNC_SHADERS
)
fun from(key: String): BooleanSetting? =

View File

@ -19,6 +19,7 @@ enum class IntSetting(
RESOLUTION_FACTOR("resolution_factor", Settings.SECTION_RENDERER, 1),
STEREOSCOPIC_3D_MODE("render_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_X_SHIFT("cardboard_x_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),
DEBUG_RENDERER("renderer_debug", Settings.SECTION_DEBUG, 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
@ -68,7 +71,8 @@ enum class IntSetting(
DEBUG_RENDERER,
CPU_JIT,
ASYNC_CUSTOM_LOADING,
AUDIO_INPUT_TYPE
AUDIO_INPUT_TYPE,
USE_ARTIC_BASE_CONTROLLER
)
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_CLOSE_GAME = "hotkey_close_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(
KEY_BUTTON_A,
@ -163,14 +165,28 @@ class Settings {
KEY_CSTICK_AXIS_VERTICAL,
KEY_CSTICK_AXIS_HORIZONTAL
)
val dPadKeys = listOf(
val dPadAxisKeys = listOf(
KEY_DPAD_AXIS_VERTICAL,
KEY_DPAD_AXIS_HORIZONTAL
)
val dPadButtonKeys = listOf(
KEY_BUTTON_UP,
KEY_BUTTON_DOWN,
KEY_BUTTON_LEFT,
KEY_BUTTON_RIGHT
)
val axisTitles = listOf(
R.string.controller_axis_vertical,
R.string.controller_axis_vertical,
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(
KEY_BUTTON_L,
KEY_BUTTON_R,
@ -187,13 +203,17 @@ class Settings {
HOTKEY_SCREEN_SWAP,
HOTKEY_CYCLE_LAYOUT,
HOTKEY_CLOSE_GAME,
HOTKEY_PAUSE_OR_RESUME
HOTKEY_PAUSE_OR_RESUME,
HOTKEY_QUICKSAVE,
HOTKEY_QUICKlOAD
)
val hotkeyTitles = listOf(
R.string.emulation_swap_screens,
R.string.emulation_cycle_landscape_layouts,
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"

View File

@ -4,6 +4,6 @@
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
}

View File

@ -78,7 +78,6 @@ class InputBindingSetting(
else -> false
}
/**
* 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.
@ -128,11 +127,12 @@ class InputBindingSetting(
Settings.KEY_BUTTON_DOWN -> NativeLibrary.ButtonType.DPAD_DOWN
Settings.KEY_BUTTON_LEFT -> NativeLibrary.ButtonType.DPAD_LEFT
Settings.KEY_BUTTON_RIGHT -> NativeLibrary.ButtonType.DPAD_RIGHT
Settings.HOTKEY_SCREEN_SWAP -> Hotkey.SWAP_SCREEN.button
Settings.HOTKEY_CYCLE_LAYOUT -> Hotkey.CYCLE_LAYOUT.button
Settings.HOTKEY_CLOSE_GAME -> Hotkey.CLOSE_GAME.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
}
@ -222,8 +222,10 @@ class InputBindingSetting(
Toast.makeText(context, R.string.input_message_analog_only, Toast.LENGTH_LONG).show()
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
}
@ -283,9 +285,17 @@ class InputBindingSetting(
/**
* 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}"
/**
* 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.
*/
@ -301,5 +311,23 @@ class InputBindingSetting(
*/
fun getInputAxisOrientationKey(axis: Int): String =
"${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()
val controllerKeys = Settings.buttonKeys + Settings.circlePadKeys + Settings.cStickKeys +
Settings.dPadKeys + Settings.triggerKeys
Settings.dPadButtonKeys + Settings.dPadAxisKeys + Settings.triggerKeys
val editor =
PreferenceManager.getDefaultSharedPreferences(CitraApplication.appContext).edit()
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(
RunnableSetting(
R.string.console_id,
@ -609,11 +621,16 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
add(InputBindingSetting(button, Settings.axisTitles[i]))
}
add(HeaderSetting(R.string.controller_dpad))
Settings.dPadKeys.forEachIndexed { i: Int, key: String ->
add(HeaderSetting(R.string.controller_dpad_axis,R.string.controller_dpad_axis_description))
Settings.dPadAxisKeys.forEachIndexed { i: Int, key: String ->
val button = getInputObject(key)
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))
Settings.triggerKeys.forEachIndexed { i: Int, key: String ->
@ -626,6 +643,16 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
val button = getInputObject(key)
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
)
)
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(
@ -964,6 +1003,15 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
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) {
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) {

View File

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

View File

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

View File

@ -128,6 +128,8 @@ void Config::ReadValues() {
static_cast<u16>(sdl2_config->GetInteger("Controls", "udp_input_port",
InputCommon::CemuhookUDP::DEFAULT_PORT));
ReadSetting("Controls", Settings::values.use_artic_base_controller);
// Core
ReadSetting("Core", Settings::values.use_cpu_jit);
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_green);
ReadSetting("Renderer", Settings::values.bg_blue);
ReadSetting("Renderer", Settings::values.delay_game_render_thread_us);
// Layout
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.plugin_loader_enabled);
ReadSetting("System", Settings::values.allow_plugin_loader);
ReadSetting("System", Settings::values.steps_per_hour);
// Camera
using namespace Service::CAM;
@ -259,6 +263,7 @@ void Config::ReadValues() {
ReadSetting("Debugging", Settings::values.renderer_debug);
ReadSetting("Debugging", Settings::values.use_gdbstub);
ReadSetting("Debugging", Settings::values.gdbstub_port);
ReadSetting("Debugging", Settings::values.instant_debug_log);
for (const auto& service_module : Service::service_module_map) {
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)
udp_pad_index=
# Use Artic Controller when connected to Artic Base Server. (Default 0)
use_artic_base_controller=
[Core]
# Whether to use the Just-In-Time (JIT) compiler for CPU emulation
# 0: Interpreter (slow), 1 (default): JIT (fast)
@ -175,6 +178,10 @@ anaglyph_shader_name =
# 0: Nearest, 1 (default): Linear
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 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
@ -300,6 +307,10 @@ init_ticks_type =
# Defaults to 0.
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.
# You can also set if homebrew apps are allowed to enable the plugin loader
plugin_loader =
@ -350,6 +361,10 @@ renderer_debug =
use_gdbstub=false
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"
[WebService]

View File

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

View File

@ -17,7 +17,7 @@ public:
~EmuWindow_Android();
/// 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)
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) {
s_surf = ANativeWindow_fromSurface(env, surf);
bool notify = false;
if (window) {
window->OnSurfaceChanged(s_surf);
notify = window->OnSurfaceChanged(s_surf);
}
auto& system = Core::System::GetInstance();
if (system.IsPoweredOn()) {
if (notify && system.IsPoweredOn()) {
system.GPU().Renderer().NotifySurfaceChanged();
}
@ -308,10 +309,12 @@ void Java_org_citra_citra_1emu_NativeLibrary_surfaceChanged(JNIEnv* env,
void Java_org_citra_citra_1emu_NativeLibrary_surfaceDestroyed([[maybe_unused]] JNIEnv* env,
[[maybe_unused]] jobject obj) {
ANativeWindow_release(s_surf);
s_surf = nullptr;
if (window) {
window->OnSurfaceChanged(s_surf);
if (s_surf != nullptr) {
ANativeWindow_release(s_surf);
s_surf = nullptr;
if (window) {
window->OnSurfaceChanged(s_surf);
}
}
}

View File

@ -22,8 +22,8 @@
<ImageView
android:id="@+id/image_game_screen"
android:layout_width="56dp"
android:layout_height="56dp"
android:layout_width="75dp"
android:layout_height="75dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
@ -63,6 +63,18 @@
android:requiresFadingEdge="horizontal"
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
android:id="@+id/text_filename"
style="@style/TextAppearance.Material3.BodySmall"

View File

@ -1,16 +1,33 @@
<?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"
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"
style="@style/TextAppearance.Material3.TitleSmall"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="start|center_vertical"
android:paddingHorizontal="@dimen/spacing_large"
android:paddingVertical="16dp"
android:focusable="false"
android:clickable="false"
android:textAlignment="viewStart"
android:textColor="?attr/colorPrimary"
android:textStyle="bold"
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_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="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>

View File

@ -108,8 +108,16 @@
<string name="controller_triggers">Triggers</string>
<string name="controller_trigger">Trigger</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_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_description">Press or move an input.</string>
<string name="input_binding">Input Binding</string>
@ -182,6 +190,8 @@
<string name="birthday_day">Day</string>
<string name="country">Country</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="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>
@ -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">Connect to Artic Base</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>

View File

@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
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 =
static_cast<u16>(sdl2_config->GetInteger("Controls", "udp_input_port",
InputCommon::CemuhookUDP::DEFAULT_PORT));
ReadSetting("Controls", Settings::values.use_artic_base_controller);
// Core
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.texture_filter);
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.render_3d);
@ -230,6 +232,7 @@ void Config::ReadValues() {
ReadSetting("System", Settings::values.init_ticks_override);
ReadSetting("System", Settings::values.plugin_loader_enabled);
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";
@ -321,6 +324,7 @@ void Config::ReadValues() {
ReadSetting("Debugging", Settings::values.renderer_debug);
ReadSetting("Debugging", Settings::values.use_gdbstub);
ReadSetting("Debugging", Settings::values.gdbstub_port);
ReadSetting("Debugging", Settings::values.instant_debug_log);
for (const auto& service_module : Service::service_module_map) {
bool use_lle = sdl2_config->GetBoolean("Debugging", "LLE\\" + service_module.name, false);

View File

@ -319,6 +319,10 @@ init_ticks_type =
# Defaults to 0.
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]
# Which camera engine to use for the right outer camera
# blank (default): a dummy camera that always returns black image

View File

@ -327,6 +327,8 @@ void Config::ReadCameraValues() {
void Config::ReadControlValues() {
qt_config->beginGroup(QStringLiteral("Controls"));
ReadBasicSetting(Settings::values.use_artic_base_controller);
int num_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.renderer_debug);
ReadBasicSetting(Settings::values.dump_command_buffers);
ReadBasicSetting(Settings::values.instant_debug_log);
qt_config->beginGroup(QStringLiteral("LLE"));
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_sampling);
ReadGlobalSetting(Settings::values.delay_game_render_thread_us);
if (global) {
ReadBasicSetting(Settings::values.use_shader_jit);
}
@ -708,6 +713,7 @@ void Config::ReadSystemValues() {
ReadBasicSetting(Settings::values.init_time_offset);
ReadBasicSetting(Settings::values.init_ticks_type);
ReadBasicSetting(Settings::values.init_ticks_override);
ReadBasicSetting(Settings::values.steps_per_hour);
ReadBasicSetting(Settings::values.plugin_loader_enabled);
ReadBasicSetting(Settings::values.allow_plugin_loader);
}
@ -922,6 +928,8 @@ void Config::SaveCameraValues() {
void Config::SaveControlValues() {
qt_config->beginGroup(QStringLiteral("Controls"));
WriteBasicSetting(Settings::values.use_artic_base_controller);
WriteSetting(QStringLiteral("profile"), Settings::values.current_input_profile_index, 0);
qt_config->beginWriteArray(QStringLiteral("profiles"));
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.gdbstub_port);
WriteBasicSetting(Settings::values.renderer_debug);
WriteBasicSetting(Settings::values.instant_debug_log);
qt_config->beginGroup(QStringLiteral("LLE"));
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_sampling);
WriteGlobalSetting(Settings::values.delay_game_render_thread_us);
if (global) {
WriteSetting(QStringLiteral("use_shader_jit"), Settings::values.use_shader_jit.GetValue(),
true);
@ -1209,6 +1220,7 @@ void Config::SaveSystemValues() {
WriteBasicSetting(Settings::values.init_time_offset);
WriteBasicSetting(Settings::values.init_ticks_type);
WriteBasicSetting(Settings::values.init_ticks_override);
WriteBasicSetting(Settings::values.steps_per_hour);
WriteBasicSetting(Settings::values.plugin_loader_enabled);
WriteBasicSetting(Settings::values.allow_plugin_loader);
}

View File

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

View File

@ -5,6 +5,7 @@
#include <QCheckBox>
#include <QMessageBox>
#include <QTableWidgetItem>
#include <QtGlobal>
#include "configure_cheats.h"
#include "core/cheats/cheat_base.h"
#include "core/cheats/cheats.h"
@ -60,7 +61,11 @@ void ConfigureCheats::LoadCheats() {
i, 2, new QTableWidgetItem(QString::fromStdString(cheats[i]->GetType())));
enabled->setProperty("row", static_cast<int>(i));
#if QT_VERSION < QT_VERSION_CHECK(6, 7, 0)
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()));
ui->clock_display_label->setText(
QStringLiteral("%1%").arg(Settings::values.cpu_clock_percentage.GetValue()));
ui->instant_debug_log->setChecked(Settings::values.instant_debug_log.GetValue());
}
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.renderer_debug = ui->toggle_renderer_debug->isChecked();
Settings::values.dump_command_buffers = ui->toggle_dump_command_buffers->isChecked();
Settings::values.instant_debug_log = ui->instant_debug_log->isChecked();
ConfigurationShared::ApplyPerGameSetting(
&Settings::values.cpu_clock_percentage, ui->clock_speed_combo,

View File

@ -86,7 +86,7 @@
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<layout class="QHBoxLayout" name="horizontalLayout1">
<item>
<widget class="QLabel" name="label_3">
<property name="text">
@ -100,7 +100,7 @@
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<layout class="QHBoxLayout" name="horizontalLayout2">
<item>
<widget class="QCheckBox" name="toggle_console">
<property name="text">
@ -117,6 +117,16 @@
</item>
</layout>
</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>
</widget>
</item>
@ -125,7 +135,7 @@
<property name="title">
<string>CPU</string>
</property>
<layout class="QGridLayout" name="gridLayout_2">
<layout class="QGridLayout" name="clock_speed_GLayout">
<item row="1" column="0">
<widget class="QWidget" name="clock_speed_widget" native="true">
<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()},
general_tab{std::make_unique<ConfigureGeneral>(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)},
graphics_tab{
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
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 =
qobject_cast<QStandardItemModel*>(ui->graphics_api_combo->model());
#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,
&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();
}
ConfigureGraphics::~ConfigureGraphics() = default;
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()) {
ConfigurationShared::SetHighlight(ui->graphics_api_group,
!Settings::values.graphics_api.UsingGlobal());
@ -101,6 +118,16 @@ void ConfigureGraphics::SetConfiguration() {
&Settings::values.texture_sampling);
ConfigurationShared::SetHighlight(ui->widget_texture_sampling,
!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 {
ui->graphics_api_combo->setCurrentIndex(
static_cast<int>(Settings::values.graphics_api.GetValue()));
@ -144,6 +171,9 @@ void ConfigureGraphics::ApplyConfiguration() {
ui->toggle_disk_shader_cache, use_disk_shader_cache);
ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_vsync_new, ui->toggle_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()) {
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->graphics_api_combo->setEnabled(Settings::values.graphics_api.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;
}
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);
ConfigurationShared::SetColoredComboBox(

View File

@ -307,6 +307,83 @@
</property>
</widget>
</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>
</widget>
</item>

View File

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

View File

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

View File

@ -841,6 +841,13 @@
</item>
</layout>
</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>
</item>
</layout>

View File

@ -151,7 +151,14 @@ void ConfigurePerGame::LoadConfiguration() {
ui->display_title_id->setText(
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;
if (loader->ReadTitle(title) == Loader::ResultStatus::Success)

View File

@ -328,6 +328,8 @@ void ConfigureSystem::SetConfiguration() {
ui->edit_init_ticks_value->setText(
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);
ReadSystemSettings();
@ -460,6 +462,8 @@ void ConfigureSystem::ApplyConfiguration() {
Settings::values.init_ticks_override =
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_days = ui->edit_init_time_offset_days->value() * 86400;
@ -631,8 +635,10 @@ void ConfigureSystem::SetupPerGameUI() {
ui->label_language->setVisible(false);
ui->label_country->setVisible(false);
ui->label_play_coins->setVisible(false);
ui->label_steps_per_hour->setVisible(false);
ui->edit_username->setVisible(false);
ui->spinBox_play_coins->setVisible(false);
ui->spinBox_steps_per_hour->setVisible(false);
ui->combo_birthday->setVisible(false);
ui->combo_birthmonth->setVisible(false);
ui->combo_init_clock->setVisible(false);

File diff suppressed because it is too large Load Diff

View File

@ -5,6 +5,7 @@
#include <QBrush>
#include <QString>
#include <QTreeWidgetItem>
#include <QtGlobal>
#include <fmt/format.h>
#include "citra_qt/debugger/ipc/record_dialog.h"
#include "citra_qt/debugger/ipc/recorder.h"
@ -22,8 +23,13 @@ IPCRecorderWidget::IPCRecorderWidget(Core::System& system_, QWidget* parent)
ui->setupUi(this);
qRegisterMetaType<IPCDebugger::RequestRecord>();
#if QT_VERSION < QT_VERSION_CHECK(6, 7, 0)
connect(ui->enabled, &QCheckBox::stateChanged, this,
[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->filter, &QLineEdit::textChanged, this, &IPCRecorderWidget::ApplyFilterToAll);
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_DICT, QT_TR_NOOP("dictionary")},
{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{{
@ -39,7 +39,7 @@ static const std::unordered_map<AVOptionType, const char*> TypeDescriptionMap{{
{AV_OPT_TYPE_DICT,
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_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}

View File

@ -974,7 +974,7 @@ void GMainWindow::UpdateMenuState() {
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) {
ui->action_Pause->setText(tr("&Continue"));
@ -1216,7 +1216,10 @@ bool GMainWindow::LoadROM(const QString& filename) {
case Core::System::ResultStatus::ErrorArticDisconnected:
QMessageBox::critical(
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;
default:
QMessageBox::critical(
@ -1238,6 +1241,10 @@ bool GMainWindow::LoadROM(const QString& filename) {
}
void GMainWindow::BootGame(const QString& filename) {
if (emu_thread) {
ShutdownGame();
}
const bool is_artic = filename.startsWith(QString::fromStdString("articbase://"));
if (!is_artic && filename.endsWith(QStringLiteral(".cia"))) {
@ -2412,33 +2419,47 @@ void GMainWindow::OnSaveMovie() {
}
void GMainWindow::OnCaptureScreenshot() {
if (!emu_thread || !emu_thread->IsRunning()) [[unlikely]] {
if (!emu_thread) [[unlikely]] {
return;
}
OnPauseGame();
std::string path = UISettings::values.screenshot_path.GetValue();
if (!FileUtil::IsDirectory(path)) {
if (!FileUtil::CreateFullPath(path)) {
QMessageBox::information(this, tr("Invalid Screenshot Directory"),
tr("Cannot create specified screenshot directory. Screenshot "
"path is set back to its default value."));
path = FileUtil::GetUserPath(FileUtil::UserPath::UserDir);
path.append("screenshots/");
UISettings::values.screenshot_path = path;
};
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();
}
std::string path = UISettings::values.screenshot_path.GetValue();
if (!FileUtil::IsDirectory(path)) {
if (!FileUtil::CreateFullPath(path)) {
QMessageBox::information(
this, tr("Invalid Screenshot Directory"),
tr("Cannot create specified screenshot directory. Screenshot "
"path is set back to its default value."));
path = FileUtil::GetUserPath(FileUtil::UserPath::UserDir);
path.append("screenshots/");
UISettings::values.screenshot_path = path;
};
}
static QRegularExpression expr(QStringLiteral("[\\/:?\"<>|]"));
const std::string filename = game_title.remove(expr).toStdString();
const std::string timestamp = QDateTime::currentDateTime()
.toString(QStringLiteral("dd.MM.yy_hh.mm.ss.z"))
.toStdString();
path.append(fmt::format("/{}_{}.png", filename, timestamp));
auto* const screenshot_window =
secondary_window->HasFocus() ? secondary_window : render_window;
screenshot_window->CaptureScreenshot(
UISettings::values.screenshot_resolution_factor.GetValue(),
QString::fromStdString(path));
OnStartGame();
}
static QRegularExpression expr(QStringLiteral("[\\/:?\"<>|]"));
const std::string filename = game_title.remove(expr).toStdString();
const std::string timestamp =
QDateTime::currentDateTime().toString(QStringLiteral("dd.MM.yy_hh.mm.ss.z")).toStdString();
path.append(fmt::format("/{}_{}.png", filename, timestamp));
auto* const screenshot_window = secondary_window->HasFocus() ? secondary_window : render_window;
screenshot_window->CaptureScreenshot(UISettings::values.screenshot_resolution_factor.GetValue(),
QString::fromStdString(path));
OnStartGame();
}
void GMainWindow::OnDumpVideo() {
@ -2626,10 +2647,12 @@ void GMainWindow::UpdateStatusBar() {
const bool 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);
static const std::array<std::pair<Core::PerfStats::PerfArticEventBits, QString>, 4>
static const std::array<std::pair<Core::PerfStats::PerfArticEventBits, QString>, 5>
perf_events = {
std::make_pair(Core::PerfStats::PerfArticEventBits::ARTIC_SHARED_EXT_DATA,
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,
tr("(Accessing BossExtData)")),
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;
} else if (result == Core::System::ResultStatus::ErrorArticDisconnected) {
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;
can_continue = false;
} else {
@ -3068,8 +3093,8 @@ void GMainWindow::LoadTranslation() {
bool loaded;
if (UISettings::values.language.isEmpty()) {
// If the selected language is empty, use system locale
loaded = translator.load(QLocale(), {}, {}, QStringLiteral(":/languages/"));
// Use the system's default locale
loaded = translator.load(QLocale::system(), {}, {}, QStringLiteral(":/languages/"));
} else {
// Otherwise load from the specified file
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)};
}
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) {
using namespace oaknut;
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) {
using namespace oaknut;
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
#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_get_class_func avcodec_get_class;
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_parameters_from_context_func avcodec_parameters_from_context;
avcodec_receive_frame_func avcodec_receive_frame;
@ -232,6 +235,9 @@ static bool LoadAVCodec() {
LOAD_SYMBOL(avcodec, avcodec_free_context);
LOAD_SYMBOL(avcodec, avcodec_get_class);
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_parameters_from_context);
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 const AVClass* (*avcodec_get_class_func)();
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_parameters_from_context_func)(AVCodecParameters* par, const AVCodecContext*);
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_get_class_func avcodec_get_class;
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_parameters_from_context_func avcodec_parameters_from_context;
extern avcodec_receive_frame_func avcodec_receive_frame;

View File

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

View File

@ -12,9 +12,9 @@
#if FMT_VERSION >= 80100
template <typename T>
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>
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(
static_cast<std::underlying_type_t<T>>(value), ctx);
}

View File

@ -83,6 +83,7 @@ void LogSettings() {
LOG_INFO(Config, "Citra Configuration:");
log_setting("Core_UseCpuJit", values.use_cpu_jit.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_GraphicsAPI", GetGraphicsAPIName(values.graphics_api.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_TextureSampling",
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_Factor3d", values.factor_3d.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_UseGdbstub", values.use_gdbstub.GetValue());
log_setting("Debugging_GdbstubPort", values.gdbstub_port.GetValue());
log_setting("Debugging_InstantDebugLog", values.instant_debug_log.GetValue());
}
bool IsConfiguringGlobal() {
@ -192,6 +195,7 @@ void RestoreGlobalState(bool is_powered_on) {
values.frame_limit.SetGlobal(true);
values.texture_filter.SetGlobal(true);
values.texture_sampling.SetGlobal(true);
values.delay_game_render_thread_us.SetGlobal(true);
values.layout_option.SetGlobal(true);
values.swap_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
std::vector<InputProfile> input_profiles; ///< The list of input profiles
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"};
@ -447,6 +448,7 @@ struct Values {
Setting<s64> init_ticks_override{0, "init_ticks_override"};
Setting<bool> plugin_loader_enabled{false, "plugin_loader"};
Setting<bool> allow_plugin_loader{true, "allow_plugin_loader"};
Setting<u16> steps_per_hour{0, "steps_per_hour"};
// Renderer
SwitchableSetting<GraphicsAPI, true> graphics_api {
@ -479,6 +481,8 @@ struct Values {
SwitchableSetting<TextureFilter> texture_filter{TextureFilter::None, "texture_filter"};
SwitchableSetting<TextureSampling> texture_sampling{TextureSampling::GameControlled,
"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<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> use_gdbstub{false, "use_gdbstub"};
Setting<u16> gdbstub_port{24689, "gdbstub_port"};
Setting<bool> instant_debug_log{false, "instant_debug_log"};
// Miscellaneous
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/act/act.cpp
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.h
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;
// 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 pixel_format_opt = FFmpeg::av_dict_get(options, "pixel_format", nullptr, 0);
if (pixel_format_opt) {
sw_pixel_format = FFmpeg::av_get_pix_fmt(pixel_format_opt->value);
} else if (codec->pix_fmts) {
sw_pixel_format = GetPixelFormat(codec_context.get(), codec->pix_fmts);
} else if (ret >= 0 && pix_fmts) {
sw_pixel_format = GetPixelFormat(codec_context.get(), pix_fmts);
} else {
sw_pixel_format = AV_PIX_FMT_YUV420P;
}
@ -285,11 +294,20 @@ void FFmpegVideoStream::ProcessFrame(VideoFrame& frame) {
}
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;
for (int j = 0;; ++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;
}
}
@ -303,7 +321,7 @@ bool FFmpegVideoStream::InitHWContext(const AVCodec* codec) {
continue;
}
codec_context->pix_fmt = codec->pix_fmts[i];
codec_context->pix_fmt = pix_fmts[i];
// Create HW device context
AVBufferRef* hw_device_context;
@ -351,7 +369,7 @@ bool FFmpegVideoStream::InitHWContext(const AVCodec* codec) {
AVHWFramesContext* hw_frames_context =
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->width = codec_context->width;
hw_frames_context->height = codec_context->height;
@ -455,6 +473,7 @@ bool FFmpegAudioStream::Init(FFmpegMuxer& muxer) {
}
frame_count = 0;
int ret;
// Initialize audio codec
const AVCodec* codec =
@ -468,16 +487,33 @@ bool FFmpegAudioStream::Init(FFmpegMuxer& muxer) {
// Configure audio codec context
codec_context->codec_type = AVMEDIA_TYPE_AUDIO;
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 {
codec_context->sample_fmt = AV_SAMPLE_FMT_S16P;
}
if (codec->supported_samplerates) {
codec_context->sample_rate = codec->supported_samplerates[0];
#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(61, 13, 100) // lavc 61.13.100
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
const int* ptr = codec->supported_samplerates;
const int* ptr = supported_samplerates;
while ((*ptr)) {
if ((*ptr) == 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: {
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);
}
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);
}
auto req = client->NewRequest("FSFILE_Read");
size_t read_amount = 0;
while (read_amount != length) {
size_t to_read =
std::min<size_t>(client->GetServerRequestMaxSize() - 0x100, length - read_amount);
req.AddParameterS32(file_handle);
req.AddParameterU64(offset);
req.AddParameterU32(static_cast<u32>(length));
auto req = client->NewRequest("FSFILE_Read");
req.AddParameterS32(file_handle);
req.AddParameterS64(static_cast<s64>(offset + read_amount));
req.AddParameterS32(static_cast<s32>(to_read));
auto resp = client->Send(req);
if (!resp.has_value() || !resp->Succeeded())
return Result(-1);
auto resp = client->Send(req);
auto res = ArticArchive::RespResult(resp);
if (res.IsError())
return res;
auto res = Result(static_cast<u32>(resp->GetMethodResult()));
if (res.IsError())
return res;
auto read_buf = resp->GetResponseBuffer(0);
if (!read_buf || read_buf->second > length) {
return std::size_t(0);
auto read_buff = resp->GetResponseBuffer(0);
size_t actually_read = 0;
if (read_buff.has_value()) {
actually_read = read_buff->second;
memcpy(buffer + read_amount, read_buff->first, actually_read);
}
read_amount += actually_read;
if (actually_read != to_read)
break;
}
memcpy(buffer, read_buf->first, read_buf->second);
return read_buf->second;
return read_amount;
}
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) {
return cache->Write(file_handle, offset, length, buffer, flags);
} else {
auto req = client->NewRequest("FSFILE_Write");
size_t written_amount = 0;
while (written_amount != length) {
size_t to_write = std::min<size_t>(client->GetServerRequestMaxSize() - 0x100,
length - written_amount);
req.AddParameterS32(file_handle);
req.AddParameterU64(offset);
req.AddParameterU32(static_cast<u32>(length));
req.AddParameterU32(flags);
req.AddParameterBuffer(buffer, length);
auto req = client->NewRequest("FSFILE_Write");
req.AddParameterS32(file_handle);
req.AddParameterS64(static_cast<s64>(offset + written_amount));
req.AddParameterS32(static_cast<s32>(to_write));
req.AddParameterS32(static_cast<s32>(flags));
req.AddParameterBuffer(buffer + written_amount, to_write);
auto resp = client->Send(req);
if (!resp.has_value() || !resp->Succeeded())
return Result(-1);
auto resp = client->Send(req);
auto res = ArticArchive::RespResult(resp);
if (res.IsError())
return res;
auto res = Result(static_cast<u32>(resp->GetMethodResult()));
if (res.IsError())
return res;
auto writen_buf = resp->GetResponseS32(0);
if (!writen_buf) {
return std::size_t(0);
auto actually_written_opt = resp->GetResponseS32(0);
if (!actually_written_opt.has_value())
return Result(-1);
size_t actually_written = static_cast<size_t>(actually_written_opt.value());
written_amount += actually_written;
if (actually_written != to_write)
break;
}
return std::size_t(*writen_buf);
return written_amount;
}
}

View File

@ -10,6 +10,7 @@
#include "common/archives.h"
#include "common/common_types.h"
#include "common/file_util.h"
#include "core/file_sys/archive_artic.h"
#include "core/file_sys/archive_systemsavedata.h"
#include "core/file_sys/errors.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)
: 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,
u64 program_id) {
std::string fullpath = GetSystemSaveDataPath(base_path, path);
if (!FileUtil::Exists(fullpath)) {
// TODO(Subv): Check error code, this one is probably wrong
return ResultNotFound;
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);
if (!FileUtil::Exists(fullpath)) {
// TODO(Subv): Check error code, this one is probably wrong
return ResultNotFound;
}
return std::make_unique<SaveDataArchive>(fullpath);
}
return std::make_unique<SaveDataArchive>(fullpath);
}
Result ArchiveFactory_SystemSaveData::Format(const Path& path,
const FileSys::ArchiveFormatInfo& format_info,
u64 program_id, u32 directory_buckets,
u32 file_buckets) {
std::string fullpath = GetSystemSaveDataPath(base_path, path);
FileUtil::DeleteDirRecursively(fullpath);
FileUtil::CreateFullPath(fullpath);
return ResultSuccess;
const std::vector<u8> vec_data = path.AsBinary();
u32 save_low;
u32 save_high;
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,
@ -79,4 +101,45 @@ ResultVal<ArchiveFormatInfo> ArchiveFactory_SystemSaveData::GetFormatInfo(const
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

View File

@ -10,12 +10,15 @@
#include <boost/serialization/string.hpp>
#include "common/common_types.h"
#include "core/file_sys/archive_backend.h"
#include "core/file_sys/artic_cache.h"
#include "core/hle/result.h"
#include "core/hle/service/fs/archive.h"
#include "network/artic_base/artic_base_client.h"
namespace FileSys {
/// File system interface to the SystemSaveData archive
class ArchiveFactory_SystemSaveData final : public ArchiveFactory {
class ArchiveFactory_SystemSaveData final : public ArchiveFactory, public ArticCacheProvider {
public:
explicit ArchiveFactory_SystemSaveData(const std::string& mount_point);
@ -24,13 +27,31 @@ public:
u32 directory_buckets, u32 file_buckets) 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 {
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:
std::string base_path;
std::shared_ptr<Network::ArticBase::Client> artic_client = nullptr;
ArchiveFactory_SystemSaveData() = default;
template <class Archive>
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;
auto read_buff = resp->GetResponseBuffer(0);
if (!read_buff.has_value())
return Result(-1);
size_t actually_read = read_buff->second;
size_t actually_read = 0;
if (read_buff.has_value()) {
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;
if (actually_read != to_read)
break;

View File

@ -179,6 +179,8 @@ Loader::ResultStatus FileSys::Plugin3GXLoader::Map(
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.
// Calculate the sizes of the different memory regions
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);
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());
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
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());
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
auto vma_heap = process.vm_manager.MapBackingMemory(
_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());
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<2, 2, u32_le> memory_region_size;
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;
u32_le exe_load_checksum;
u32_le builtin_load_exe_args[4];

View File

@ -369,6 +369,16 @@ enum class ControlProcessOP {
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> {
public:
SVC(Core::System& system);
@ -460,7 +470,8 @@ private:
Result InvalidateEntireInstructionCache();
u32 ConvertVaToPa(u32 addr);
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 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,
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 =
kernel.GetCurrentProcess()->handle_table.Get<Process>(dst_process_handle);
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;
}
// TODO(PabloMK7) Fix-up this svc.
// Only linear memory supported
auto vma = src_process->vm_manager.FindVMA(src_address);
R_UNLESS(vma != src_process->vm_manager.vma_map.end() &&
vma->second.type == VMAType::BackingMemory &&
vma->second.meminfo_state == MemoryState::Continuous,
vma->second.type == VMAType::BackingMemory,
ResultInvalidAddress);
const u32 offset = src_address - vma->second.base;
@ -2038,7 +2065,7 @@ Result SVC::MapProcessMemoryEx(Handle dst_process_handle, u32 dst_address,
dst_address,
memory.GetFCRAMRef(vma->second.backing_memory.GetPtr() + offset -
kernel.memory.GetFCRAMPointer(0)),
size, Kernel::MemoryState::Continuous);
size, map_as_private ? MemoryState::Private : MemoryState::Shared);
if (!vma_res.Succeeded()) {
return ResultInvalidAddressState;
@ -2060,8 +2087,7 @@ Result SVC::UnmapProcessMemoryEx(Handle process, u32 dst_address, u32 size) {
// Only linear memory supported
auto vma = dst_process->vm_manager.FindVMA(dst_address);
R_UNLESS(vma != dst_process->vm_manager.vma_map.end() &&
vma->second.type == VMAType::BackingMemory &&
vma->second.meminfo_state == MemoryState::Continuous,
vma->second.type == VMAType::BackingMemory,
ResultInvalidAddress);
dst_process->vm_manager.UnmapRange(dst_address, size);

View File

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

View File

@ -38,9 +38,19 @@ public:
void Initialize(Kernel::HLERequestContext& ctx);
/**
* ACT::GetAccountDataBlock service function.
* ACT::GetErrorCode service function.
* 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
* 3 : Block ID
* 4 : Output Buffer Mapping Translation Header ((Size << 4) | 0xC)
@ -48,7 +58,7 @@ public:
* Outputs:
* 1 : Result of function, 0 on success, otherwise error code
*/
void GetAccountDataBlock(Kernel::HLERequestContext& ctx);
void GetAccountInfo(Kernel::HLERequestContext& ctx);
};
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
// clang-format off
{0x0001, &ACT_A::Initialize, "Initialize"},
{0x0002, nullptr, "GetErrorCode"},
{0x0006, &ACT_A::GetAccountDataBlock, "GetAccountDataBlock"},
{0x0002, &ACT_A::GetErrorCode, "GetErrorCode"},
{0x0003, nullptr, "GetLastResponseCode"},
{0x0005, nullptr, "GetCommonInfo"},
{0x0006, &ACT_A::GetAccountInfo, "GetAccountInfo"},
{0x0007, nullptr, "GetResultAsync"},
{0x0008, nullptr, "GetMiiImageData"},
{0x0009, nullptr, "SetNfsPassword"},
{0x000B, nullptr, "AcquireEulaList"},
{0x000C, nullptr, "AcquireTimeZoneList"},
{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
{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"},
{0x0414, nullptr, "InquireAccountIdAvailability"},
{0x0415, nullptr, "BindToNewServerAccount"},
{0x0416, nullptr, "BindToExistentServerAccount"},
{0x0417, nullptr, "InquireBindingToExistentServerAccount"},
{0x041A, nullptr, "AcquireAccountTokenEx"},
{0x041B, nullptr, "AgreeEula"},
{0x041C, nullptr, "SyncAccountInfo"},
{0x041E, nullptr, "UpdateAccountPassword"},
{0x041F, nullptr, "ReissueAccountPassword"},
{0x0420, nullptr, "SetAccountPasswordInput"},
{0x0421, nullptr, "UploadMii"},
{0x0423, nullptr, "ValidateMailAddress"},
{0x0423, nullptr, "SendConfirmationMail"},
{0x0428, nullptr, "ApproveByCreditCard"},
{0x0428, nullptr, "SendCoppaCodeMail"},
{0x042F, nullptr, "UpdateAccountInfoEx"},
{0x0430, nullptr, "UpdateAccountMailAddress"},
{0x0435, nullptr, "DeleteServerAccount"},
// clang-format on
};
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[] = {
// clang-format off
{0x0001, &ACT_U::Initialize, "Initialize"},
{0x0002, nullptr, "GetErrorCode"},
{0x0006, &ACT_U::GetAccountDataBlock, "GetAccountDataBlock"},
{0x0002, &ACT_U::GetErrorCode, "GetErrorCode"},
{0x0003, nullptr, "GetLastResponseCode"},
{0x0005, nullptr, "GetCommonInfo"},
{0x0006, &ACT_U::GetAccountInfo, "GetAccountInfo"},
{0x0007, nullptr, "GetResultAsync"},
{0x0008, nullptr, "GetMiiImageData"},
{0x0009, nullptr, "SetNfsPassword"},
{0x000B, nullptr, "AcquireEulaList"},
{0x000C, nullptr, "AcquireTimeZoneList"},
{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
};
RegisterHandlers(functions);

File diff suppressed because it is too large Load Diff

View File

@ -20,6 +20,7 @@
#include "core/hle/kernel/mutex.h"
#include "core/hle/result.h"
#include "core/hle/service/service.h"
#include "network/artic_base/artic_base_client.h"
namespace Core {
class System;
@ -245,7 +246,13 @@ public:
return am;
}
void UseArticClient(std::shared_ptr<Network::ArticBase::Client>& client) {
artic_client = client;
}
protected:
void GetProgramInfosImpl(Kernel::HLERequestContext& ctx, bool ignore_platform);
/**
* AM::GetNumPrograms service function
* Gets the number of installed titles in the requested media type
@ -753,6 +760,9 @@ public:
protected:
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);
if (is_busy_conversion) {
is_busy_conversion = false;
system.CoreTiming().RemoveEvent(completion_signal_event);
}

View File

@ -9,7 +9,7 @@
#include <boost/serialization/unique_ptr.hpp>
#include <cryptopp/osrng.h>
#include <cryptopp/sha.h>
#include <fmt/format.h>
#include <fmt/ranges.h>
#include "common/archives.h"
#include "common/file_util.h"
#include "common/logging/log.h"
@ -278,7 +278,7 @@ void Module::Interface::GetTransferableId(Kernel::HLERequestContext& ctx) {
std::array<u8, 12> buffer;
const Result result =
cfg->GetConfigBlock(ConsoleUniqueID2BlockID, 8, AccessFlag::SystemRead, buffer.data());
cfg->GetConfigBlock(ConsoleUniqueID2BlockID, 8, AccessFlag::Global, buffer.data());
rb.Push(result);
if (result.IsSuccess()) {
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) {
void* pointer = nullptr;
CASCADE_RESULT(pointer, GetConfigBlockPointer(block_id, size, accesss_flag));
std::memcpy(output, pointer, size);
bool get_from_artic =
block_id == ConsoleUniqueID2BlockID &&
(static_cast<u16>(accesss_flag) & static_cast<u16>(AccessFlag::UserRead)) != 0;
return ResultSuccess;
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;
CASCADE_RESULT(pointer, GetConfigBlockPointer(block_id, size, accesss_flag));
std::memcpy(output, pointer, size);
return ResultSuccess;
}
}
Result Module::SetConfigBlock(u32 block_id, u32 size, AccessFlag accesss_flag, const void* input) {

View File

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

View File

@ -298,18 +298,22 @@ Result ArchiveManager::DeleteSystemSaveData(u32 high, u32 low) {
return ResultSuccess;
}
Result ArchiveManager::CreateSystemSaveData(u32 high, u32 low) {
// Construct the binary path to the archive first
const FileSys::Path path = FileSys::ConstructSystemSaveDataBinaryPath(high, low);
Result ArchiveManager::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) {
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
auto archive = id_code_map.find(ArchiveIdCode::SystemSaveData);
if (archive == id_code_map.end()) {
return UnimplementedFunction(ErrorModule::FS); // TODO(Subv): Find the right error
}
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 {
@ -454,6 +458,16 @@ void ArchiveManager::RegisterArticNCCH(std::shared_ptr<Network::ArticBase::Clien
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) {
RegisterArchiveTypes();
}

View File

@ -264,11 +264,12 @@ public:
/**
* 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
*/
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.
@ -296,6 +297,8 @@ public:
void RegisterArticNCCH(std::shared_ptr<Network::ArticBase::Client>& client);
void RegisterArticSystemSaveData(std::shared_ptr<Network::ArticBase::Client>& client);
private:
Core::System& system;

View File

@ -1026,7 +1026,9 @@ void FS_USER::CreateSystemSaveData(Kernel::HLERequestContext& ctx) {
file_buckets, duplicate);
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) {
@ -1048,7 +1050,8 @@ void FS_USER::CreateLegacySystemSaveData(Kernel::HLERequestContext& ctx) {
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
// 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) {

View File

@ -71,7 +71,12 @@ struct CacheFlushCommand {
/// GSP command
struct Command {
BitField<0, 8, CommandId> id;
union {
BitField<0, 8, CommandId> id;
BitField<8, 8, u32> unknown1;
BitField<16, 8, u32> stop;
BitField<24, 8, u32> unknown2;
};
union {
DmaCommand dma_request;
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
struct CommandBuffer {
static constexpr u32 STATUS_STOPPED = 0x1;
static constexpr u32 STATUS_CMD_FAILED = 0x80;
union {
u32 hex;
@ -99,6 +106,11 @@ struct CommandBuffer {
// application when writing a command to shared memory, after increasing this value
// TriggerCmdReqQueue is only used if this field is value 1.
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];

View File

@ -9,6 +9,7 @@
#include <boost/serialization/shared_ptr.hpp>
#include "common/archives.h"
#include "common/bit_field.h"
#include "common/settings.h"
#include "core/core.h"
#include "core/hle/ipc_helpers.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) {
IPC::RequestParser rp(ctx);
// Iterate through each command.
auto* command_buffer = GetCommandBuffer(active_thread_id);
auto& gpu = system.GPU();
for (u32 i = 0; i < command_buffer->number_commands; i++) {
gpu.Debugger().GXCommandProcessed(command_buffer->commands[i]);
bool requires_delay = false;
while (command_buffer->number_commands) {
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_buffer->commands[i]);
gpu.Execute(command);
// Indicates that command has completed
command_buffer->number_commands.Assign(command_buffer->number_commands - 1);
if (command.stop) {
command_buffer->should_stop.Assign(1);
}
}
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
rb.Push(ResultSuccess);
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);
rb.Push(ResultSuccess);
}
}
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_spvr.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/movie.h"
@ -53,6 +55,32 @@ void Module::serialize(Archive& ar, const unsigned int file_version) {
}
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 gyroscope_coef = 14.375f; // got from hwtest GetGyroscopeLowRawToDpsCoefficient call
@ -111,96 +139,151 @@ void Module::UpdatePadCallback(std::uintptr_t user_data, s64 cycles_late) {
LoadInputDevices();
using namespace Settings::NativeButton;
state.a.Assign(buttons[A - BUTTON_HID_BEGIN]->GetStatus());
state.b.Assign(buttons[B - BUTTON_HID_BEGIN]->GetStatus());
state.x.Assign(buttons[X - BUTTON_HID_BEGIN]->GetStatus());
state.y.Assign(buttons[Y - BUTTON_HID_BEGIN]->GetStatus());
state.right.Assign(buttons[Right - BUTTON_HID_BEGIN]->GetStatus());
state.left.Assign(buttons[Left - BUTTON_HID_BEGIN]->GetStatus());
state.up.Assign(buttons[Up - BUTTON_HID_BEGIN]->GetStatus());
state.down.Assign(buttons[Down - BUTTON_HID_BEGIN]->GetStatus());
state.l.Assign(buttons[L - BUTTON_HID_BEGIN]->GetStatus());
state.r.Assign(buttons[R - BUTTON_HID_BEGIN]->GetStatus());
state.start.Assign(buttons[Start - BUTTON_HID_BEGIN]->GetStatus());
state.select.Assign(buttons[Select - BUTTON_HID_BEGIN]->GetStatus());
state.debug.Assign(buttons[Debug - BUTTON_HID_BEGIN]->GetStatus());
state.gpio14.Assign(buttons[Gpio14 - BUTTON_HID_BEGIN]->GetStatus());
// Get current circle pad position and update circle pad direction
float circle_pad_x_f, circle_pad_y_f;
std::tie(circle_pad_x_f, circle_pad_y_f) = circle_pad->GetStatus();
if (artic_controller.get() && artic_controller->IsReady()) {
constexpr u32 HID_VALID_KEYS = 0xF0003FFF;
constexpr u32 LIBCTRU_TOUCH_KEY = (1 << 20);
// xperia64: 0x9A seems to be the calibrated limit of the circle pad
// Verified by using Input Redirector with very large-value digital inputs
// on the circle pad and calibrating using the system settings application
constexpr int MAX_CIRCLEPAD_POS = 0x9A; // Max value for a circle pad position
ArticBaseController::ControllerData data = artic_controller->GetControllerData();
// 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_y = static_cast<s16>(std::roundf(circle_pad_y_f * MAX_CIRCLEPAD_POS));
s16 circle_pad_x =
(circle_pad_new_x + std::accumulate(circle_pad_old_x.begin(), circle_pad_old_x.end(), 0)) /
CIRCLE_PAD_AVERAGING;
s16 circle_pad_y =
(circle_pad_new_y + std::accumulate(circle_pad_old_y.begin(), circle_pad_old_y.end(), 0)) /
CIRCLE_PAD_AVERAGING;
circle_pad_old_x.erase(circle_pad_old_x.begin());
circle_pad_old_x.push_back(circle_pad_new_x);
circle_pad_old_y.erase(circle_pad_old_y.begin());
circle_pad_old_y.push_back(circle_pad_new_y);
state.hex = data.pad & HID_VALID_KEYS;
system.Movie().HandlePadAndCircleStatus(state, circle_pad_x, circle_pad_y);
s16 circle_pad_x = data.c_pad_x;
s16 circle_pad_y = data.c_pad_y;
const DirectionState direction = GetStickDirectionState(circle_pad_x, circle_pad_y);
state.circle_up.Assign(direction.up);
state.circle_down.Assign(direction.down);
state.circle_left.Assign(direction.left);
state.circle_right.Assign(direction.right);
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();
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;
// 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)}};
// 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];
// 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;
// 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();
// 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.b.Assign(buttons[B - BUTTON_HID_BEGIN]->GetStatus());
state.x.Assign(buttons[X - BUTTON_HID_BEGIN]->GetStatus());
state.y.Assign(buttons[Y - BUTTON_HID_BEGIN]->GetStatus());
state.right.Assign(buttons[Right - BUTTON_HID_BEGIN]->GetStatus());
state.left.Assign(buttons[Left - BUTTON_HID_BEGIN]->GetStatus());
state.up.Assign(buttons[Up - BUTTON_HID_BEGIN]->GetStatus());
state.down.Assign(buttons[Down - BUTTON_HID_BEGIN]->GetStatus());
state.l.Assign(buttons[L - BUTTON_HID_BEGIN]->GetStatus());
state.r.Assign(buttons[R - BUTTON_HID_BEGIN]->GetStatus());
state.start.Assign(buttons[Start - BUTTON_HID_BEGIN]->GetStatus());
state.select.Assign(buttons[Select - BUTTON_HID_BEGIN]->GetStatus());
state.debug.Assign(buttons[Debug - BUTTON_HID_BEGIN]->GetStatus());
state.gpio14.Assign(buttons[Gpio14 - BUTTON_HID_BEGIN]->GetStatus());
// Get current circle pad position and update circle pad direction
float circle_pad_x_f, circle_pad_y_f;
std::tie(circle_pad_x_f, circle_pad_y_f) = circle_pad->GetStatus();
// xperia64: 0x9A seems to be the calibrated limit of the circle pad
// Verified by using Input Redirector with very large-value digital inputs
// on the circle pad and calibrating using the system settings application
constexpr int MAX_CIRCLEPAD_POS = 0x9A; // Max value for a circle pad position
// 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_y = static_cast<s16>(std::roundf(circle_pad_y_f * MAX_CIRCLEPAD_POS));
s16 circle_pad_x = (circle_pad_new_x +
std::accumulate(circle_pad_old_x.begin(), circle_pad_old_x.end(), 0)) /
CIRCLE_PAD_AVERAGING;
s16 circle_pad_y = (circle_pad_new_y +
std::accumulate(circle_pad_old_y.begin(), circle_pad_old_y.end(), 0)) /
CIRCLE_PAD_AVERAGING;
circle_pad_old_x.erase(circle_pad_old_x.begin());
circle_pad_old_x.push_back(circle_pad_new_x);
circle_pad_old_y.erase(circle_pad_old_y.begin());
circle_pad_old_y.push_back(circle_pad_new_y);
system.Movie().HandlePadAndCircleStatus(state, circle_pad_x, circle_pad_y);
const DirectionState direction = GetStickDirectionState(circle_pad_x, circle_pad_y);
state.circle_up.Assign(direction.up);
state.circle_down.Assign(direction.down);
state.circle_left.Assign(direction.left);
state.circle_right.Assign(direction.right);
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 = false;
float x, y;
std::tie(x, y, pressed) = touch_device->GetStatus();
if (!pressed && touch_btn_device) {
std::tie(x, y, pressed) = touch_btn_device->GetStatus();
}
touch_entry.x = static_cast<u16>(x * Core::kScreenBottomWidth);
touch_entry.y = static_cast<u16>(y * Core::kScreenBottomHeight);
touch_entry.valid.Assign(pressed ? 1 : 0);
system.Movie().HandleTouchStatus(touch_entry);
}
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 = false;
float x, y;
std::tie(x, y, pressed) = touch_device->GetStatus();
if (!pressed && touch_btn_device) {
std::tie(x, y, pressed) = touch_btn_device->GetStatus();
}
touch_entry.x = static_cast<u16>(x * Core::kScreenBottomWidth);
touch_entry.y = static_cast<u16>(y * Core::kScreenBottomHeight);
touch_entry.valid.Assign(pressed ? 1 : 0);
system.Movie().HandleTouchStatus(touch_entry);
// 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
// converted to pixel coordinates." (http://3dbrew.org/wiki/HID_Shared_Memory#Offset_0xA8).
@ -231,19 +314,27 @@ void Module::UpdateAccelerometerCallback(std::uintptr_t user_data, s64 cycles_la
mem->accelerometer.index = next_accelerometer_index;
next_accelerometer_index = (next_accelerometer_index + 1) % mem->accelerometer.entries.size();
Common::Vec3<float> accel;
std::tie(accel, std::ignore) = motion_device->GetStatus();
accel *= accelerometer_coef;
// TODO(wwylele): do a time stretch like the one in UpdateGyroscopeCallback
// The time stretch formula should be like
// 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.y = static_cast<s16>(accel.y);
accelerometer_entry.z = static_cast<s16>(accel.z);
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;
std::tie(accel, std::ignore) = motion_device->GetStatus();
accel *= accelerometer_coef;
// TODO(wwylele): do a time stretch like the one in UpdateGyroscopeCallback
// The time stretch formula should be like
// stretched_vector = (raw_vector - gravity) * stretch_ratio + gravity
accelerometer_entry.x = static_cast<s16>(accel.x);
accelerometer_entry.y = static_cast<s16>(accel.y);
accelerometer_entry.z = static_cast<s16>(accel.z);
}
system.Movie().HandleAccelerometerStatus(accelerometer_entry);
@ -278,13 +369,21 @@ void Module::UpdateGyroscopeCallback(std::uintptr_t user_data, s64 cycles_late)
GyroscopeDataEntry& gyroscope_entry = mem->gyroscope.entries[mem->gyroscope.index];
Common::Vec3<float> gyro;
std::tie(std::ignore, gyro) = motion_device->GetStatus();
double stretch = system.perf_stats->GetLastFrameTimeScale();
gyro *= gyroscope_coef * static_cast<float>(stretch);
gyroscope_entry.x = static_cast<s16>(gyro.x);
gyroscope_entry.y = static_cast<s16>(gyro.y);
gyroscope_entry.z = static_cast<s16>(gyro.z);
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;
std::tie(std::ignore, gyro) = motion_device->GetStatus();
double stretch = system.perf_stats->GetLastFrameTimeScale();
gyro *= gyroscope_coef * static_cast<float>(stretch);
gyroscope_entry.x = static_cast<s16>(gyro.x);
gyroscope_entry.y = static_cast<s16>(gyro.y);
gyroscope_entry.z = static_cast<s16>(gyro.z);
}
system.Movie().HandleGyroscopeStatus(gyroscope_entry);
@ -316,6 +415,23 @@ void Module::Interface::GetIPCHandles(Kernel::HLERequestContext& ctx) {
void Module::Interface::EnableAccelerometer(Kernel::HLERequestContext& 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;
// 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);
}
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
rb.Push(ResultSuccess);
LOG_DEBUG(Service_HID, "called");
}
void Module::Interface::DisableAccelerometer(Kernel::HLERequestContext& 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;
// 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);
}
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
rb.Push(ResultSuccess);
LOG_DEBUG(Service_HID, "called");
}
void Module::Interface::EnableGyroscopeLow(Kernel::HLERequestContext& 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;
// 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);
}
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
rb.Push(ResultSuccess);
LOG_DEBUG(Service_HID, "called");
}
void Module::Interface::DisableGyroscopeLow(Kernel::HLERequestContext& 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;
// 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);
}
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
rb.Push(ResultSuccess);
LOG_DEBUG(Service_HID, "called");
}
@ -382,25 +537,90 @@ void Module::Interface::GetGyroscopeLowRawToDpsCoefficient(Kernel::HLERequestCon
IPC::RequestParser rp(ctx);
IPC::RequestBuilder rb = rp.MakeBuilder(2, 0);
rb.Push(ResultSuccess);
rb.Push(gyroscope_coef);
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(gyroscope_coef);
}
}
void Module::Interface::GetGyroscopeLowCalibrateParam(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx);
IPC::RequestBuilder rb = rp.MakeBuilder(6, 0);
rb.Push(ResultSuccess);
const s16 param_unit = 6700; // an approximate value taken from hw
GyroscopeCalibrateParam param = {
{0, param_unit, -param_unit},
{0, param_unit, -param_unit},
{0, param_unit, -param_unit},
};
rb.PushRaw(param);
auto& artic_client = GetModule()->artic_client;
if (artic_client.get()) {
GyroscopeCalibrateParam param;
LOG_WARNING(Service_HID, "(STUBBED) called");
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);
const s16 param_unit = 6700; // an approximate value taken from hw
GyroscopeCalibrateParam param = {
{0, param_unit, -param_unit},
{0, param_unit, -param_unit},
{0, param_unit, -param_unit},
};
rb.PushRaw(param);
LOG_WARNING(Service_HID, "(STUBBED) called");
}
}
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);
}
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() {
is_device_reload_pending.store(true);
}

View File

@ -14,13 +14,11 @@
#include "common/common_funcs.h"
#include "common/common_types.h"
#include "common/settings.h"
#include "core/core.h"
#include "core/core_timing.h"
#include "core/frontend/input.h"
#include "core/hle/service/service.h"
namespace Core {
class System;
}
#include "network/artic_base/artic_base_client.h"
namespace Kernel {
class Event;
@ -199,6 +197,44 @@ struct DirectionState {
/// 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);
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 {
public:
explicit Module(Core::System& system);
@ -296,6 +332,8 @@ public:
std::shared_ptr<Module> hid;
};
void UseArticClient(const std::shared_ptr<Network::ArticBase::Client>& client);
void ReloadInputDevices();
const PadState& GetState() const;
@ -355,6 +393,9 @@ private:
std::unique_ptr<Input::TouchDevice> touch_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>
void serialize(Archive& ar, const unsigned int);
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)) {
LOG_ERROR(Service_HTTP, "Request failed: {}: {}", error, httplib::to_string(error));
state = RequestState::TimedOut;
state = RequestState::Completed;
} else {
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)) {
LOG_ERROR(Service_HTTP, "Request failed: {}: {}", error, httplib::to_string(error));
state = RequestState::TimedOut;
state = RequestState::Completed;
} else {
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,
0, remaining_data);
http_context.current_copied_data += remaining_data;
http_context.state = RequestState::Completed;
rb.Push(ResultSuccess);
} else {
async_data->buffer->Write(http_context.response.body.data() +

View File

@ -48,12 +48,29 @@ enum class RequestMethod : u8 {
constexpr u32 TotalRequestMethods = 8;
enum class RequestState : u8 {
NotStarted = 0x1, // Request has not started yet.
ConnectingToServer = 0x5, // Request in progress, connecting to server.
SendingRequest = 0x6, // Request in progress, sending HTTP request.
ReceivingResponse = 0x7, // Request in progress, receiving HTTP response.
ReadyToDownloadContent = 0x8, // Ready to download the content.
TimedOut = 0xA, // Request timed out?
/// Request has not started yet.
NotStarted = 0x1,
/// Request in progress, connecting to server.
ConnectingToServer = 0x5,
/// 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 {

View File

@ -2,10 +2,11 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <fmt/format.h>
#include <fmt/ranges.h>
#include "common/alignment.h"
#include "common/settings.h"
#include "core/core_timing.h"
#include "core/hle/service/hid/hid.h"
#include "core/hle/service/ir/extra_hid.h"
#include "core/movie.h"
@ -230,23 +231,47 @@ void ExtraHID::SendHIDStatus() {
if (is_device_reload_pending.exchange(false))
LoadInputDevices();
constexpr u32 ZL_BUTTON = (1 << 14);
constexpr u32 ZR_BUTTON = (1 << 15);
constexpr int C_STICK_CENTER = 0x800;
// 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.
constexpr int C_STICK_RADIUS = 0x7FF;
float x, y;
std::tie(x, y) = c_stick->GetStatus();
ExtraHIDResponse response{};
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_y.Assign(static_cast<u32>(C_STICK_CENTER + C_STICK_RADIUS * y));
response.buttons.battery_level.Assign(0x1F);
response.buttons.zl_not_held.Assign(!zl->GetStatus());
response.buttons.zr_not_held.Assign(!zr->GetStatus());
response.buttons.r_not_held.Assign(1);
response.unknown = 0;
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;
std::tie(x, y) = c_stick->GetStatus();
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_y.Assign(static_cast<u32>(C_STICK_CENTER + C_STICK_RADIUS * y));
response.buttons.battery_level.Assign(0x1F);
response.buttons.zl_not_held.Assign(!zl->GetStatus());
response.buttons.zr_not_held.Assign(!zr->GetStatus());
response.buttons.r_not_held.Assign(1);
response.unknown = 0;
}
movie.HandleExtraHidResponse(response);

View File

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

View File

@ -72,25 +72,41 @@ void IR_RST::UpdateCallback(std::uintptr_t user_data, s64 cycles_late) {
if (is_device_reload_pending.exchange(false))
LoadInputDevices();
constexpr u32 VALID_EXTRAHID_KEYS = 0xF00C000;
PadState state;
state.zl.Assign(zl_button->GetStatus());
state.zr.Assign(zr_button->GetStatus());
s16 c_stick_x, c_stick_y;
// Get current c-stick position and update c-stick direction
float c_stick_x_f, c_stick_y_f;
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
s16 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);
if (artic_controller.get() && artic_controller->IsReady()) {
Service::HID::ArticBaseController::ControllerData data =
artic_controller->GetControllerData();
system.Movie().HandleIrRst(state, c_stick_x, c_stick_y);
state.hex = data.pad & VALID_EXTRAHID_KEYS;
if (!raw_c_stick) {
const HID::DirectionState direction = HID::GetStickDirectionState(c_stick_x, c_stick_y);
state.c_stick_up.Assign(direction.up);
state.c_stick_down.Assign(direction.down);
state.c_stick_left.Assign(direction.left);
state.c_stick_right.Assign(direction.right);
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.zr.Assign(zr_button->GetStatus());
// Get current c-stick position and update c-stick direction
float c_stick_x_f, c_stick_y_f;
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
c_stick_x = static_cast<s16>(c_stick_x_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);
if (!raw_c_stick) {
const HID::DirectionState direction = HID::GetStickDirectionState(c_stick_x, c_stick_y);
state.c_stick_up.Assign(direction.up);
state.c_stick_down.Assign(direction.down);
state.c_stick_left.Assign(direction.left);
state.c_stick_right.Assign(direction.right);
}
}
// TODO (wwylele): implement raw C-stick data for raw_c_stick = true

View File

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

View File

@ -8,7 +8,7 @@
#include <boost/serialization/base_object.hpp>
#include <boost/serialization/shared_ptr.hpp>
#include <boost/serialization/unique_ptr.hpp>
#include <fmt/format.h>
#include <fmt/ranges.h>
#include "common/archives.h"
#include "common/swap.h"
#include "core/core.h"
@ -480,6 +480,12 @@ void IR_USER::ReloadInputDevices() {
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() = default;

View File

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

View File

@ -38,7 +38,7 @@ SERVICE_CONSTRUCT_IMPL(Service::PLGLDR::PLG_LDR)
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_) {
static const FunctionInfo functions[] = {
@ -91,7 +91,11 @@ void PLG_LDR::serialize(Archive& ar, const unsigned int) {
SERIALIZE_IMPL(PLG_LDR)
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;
}
{

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