Compare commits

...

29 Commits

Author SHA1 Message Date
Ryan McClelland
0c19c33bd3
libretro: scope Vulkan case to fix MSVC C2360 (#2267)
MSVC with /permissive- /WX rejects the switch in retro_load_game:
the static vk_negotiation initializer in the Vulkan case is crossed
by the next case label (C2360, initialization skipped by case). Wrap
the Vulkan case body in a block so its scope ends before that label,
and add a comment noting why the braces are required there. The other
cases are wrapped in blocks too for visual consistency, though only
the Vulkan case strictly needs it. GCC/clang and the MXE cross-build
accept the original; this only affects native MSVC builds. No
behaviour change.
2026-07-09 14:44:37 +02:00
PabloMK7
bda4671be6
core: gsp: General improvements to GSP service accuracy (#2273) 2026-07-09 14:19:36 +02:00
PabloMK7
c2dd5f3bea
core: Change default value of simulate GPU timings to false (#2274) 2026-07-09 13:58:07 +02:00
PabloMK7
31e4ecea58 core: apt: Restore captured framebuffers on libapplet closure 2026-07-09 09:07:02 +02:00
PabloMK7
49ba2e8d5f core: apt: Implement GetAppletProgramInfo 2026-07-09 09:07:02 +02:00
PabloMK7
b64040b5fa
video_core: Fix fill surface downloading (#2270) 2026-07-08 23:44:03 +02:00
ApfelTeeSaft
64a967b4a8
core/arm: model ARM11 MPCore timing per TRM (#2262)
* feat: make per instruction cycle table optional

makes the nov 2022 change regarding the instruction cycle table thing toggleable inside Debug->CPU Settings.

* core/arm: model ARM11 MPCore timing per TRM

This Commit also reverts my initial "reimplementation / revertion" of the legacy "1 tick : 1 instruction" cpu cycling.

- Fix a bug related to the Get<field> helper. It performed a parallel bit deposit where an extract was required

- Branch prediction stuff

- Charge now loads/stores their 1-cycle issue cost instead of flat interlock penalty

Add VFP11 costs (was VFP -> decay to 1 cycle generic entry)

- Implement Thumb dispatch

* Revert accidental push of diagnosig logic.

* Fix license and format

---------

Co-authored-by: PabloMK7 <hackyglitch2@gmail.com>
2026-07-08 20:23:22 +02:00
OpenSauce04
7e976e852c android: Flag AndroidLintUseKtx lint as style suggestion instead of warning 2026-07-08 15:24:01 +01:00
PabloMK7
ad0dde0725
common: Detect and print AppData folder get failures (#2259) 2026-07-05 00:00:57 +02:00
PabloMK7
d61801ad76
qt: gdb: Do not change GDB settings when the gdbport arg is used (#2257) 2026-07-05 00:00:32 +02:00
OpenSauce04
9a0f0e3caf citra_qt.cpp: Deduplicate 3DS system file setup dialog strings 2026-07-04 18:43:23 +01:00
OpenSauce04
4f63caf5fc android: Fix misplaced Inappropriate Scale messages + Fix crash with inappropriate values 2026-07-04 18:35:18 +01:00
OpenSauce04
2a39345e4b android: Fix Turbo button not appearing in Adjust Scale menu 2026-07-04 18:35:18 +01:00
ADAS2024
209173fa0c
[Android] Add Combo Button (#1244)
* Base starting commit for Feature Impl

* Base commit 2 (off of closed PR, impl details of the actual button will likely be different.)

* Added Combo Button Bool setting to use for later implementation.

* Added Combo Button Submenu and associated strings. Need to implement button displaying for user to interact with.

* Added layout file for combo settings for potential impl

* Removed combo settings as it wasn't liked. Starting Multi Choice impl

* Added Multi Choice Impl. Need to Refactor OnClick for MultiChoice and debug Impl when adding combo button.

* Change List<> in Multi Files to be MutableSet<> to allow adds while prevent potential duplicates. Finished primary multi choice backend impl.

* used correct lisencing procedure for multi files

* Added code in arrays.xml to display the buttons.
This code has the buttons display but clicking on them crashes the app. Will debug further.
Also replaced setSelectedValues with add and remove SelectedValue for clarity purposes.

* Deleted Int Combo Value array in arrays.xml as it was not needed. Able to Click multiple buttons at once but unchecking them causes app to crash. (within the MultiChoice OnClick for SettingsAdapter.kt)

Also debating whether to remove the normal MultiChoice addition as it wasn't added.

* Attempt to deal with merge conflicts

* Moved OnSettingChange after I add or remove selected vals to fix crashing issue.

* Displayed combo button in both landscape and portrait

* WIP on Button Impl, made combo button setting run time editable

* Changed the combo_key string into button_combo, added functionality (impl could be better), added combo key as an input bindable setting within the buttons tab

* Moved Combo Settings into just controls instead of having its own submenu

* Made ComboHelper code more readable and added the key setting to be toggleable in game

* Edited Layout integers of combo button. Made Combo Button display properly in normal portrait mode.

* Code Cleanup and Start of Reimplementation based off #1430

* Progress commit for better impl, need to have the pop up menu show up to select buttons

* Code cleanup and bug fix to properly map values.

* Deleted Multi String Setting, added basic error handling, added comment explaining hardcoded size

* Changed Combo Button to be a HotKey, changed impl to be default multi choice, and removed StringMultiChoice details

* Remove Commented Code

* Changed ComboHelper.kt to use Native Button ints, removed rest of initial implementation traces

* Commented out debug logging, added back missing TODO statement regarding magic numbers

* Cleanup

* Changed Combo Key to Combo Button in kotlin code (will need to test)

* Trying to integrate combo button as a SettingsKey setting. Running into issues with malformed/missing key

* latest changes (added setting to default ini). Still debugging file issue

* added possible fix to touchscreen latency issue, will need to test

* Reverted MultiChoice code to original, removed unused strings and variables and ensured consistent naming, fixed latency issues with button,

* reverted int list setting change as its no longer needed.

* `combo_buttons` --> `combo_button_buttons`

* default_ini.h: Adjust ini comment for combo_button_buttons

* Fix "Reset Overlay" not affecting combo button

* Better center default position of combo button in portrait layout

* `combo_button_options` --> `combo_button_settings`

Better reflects string content

* Last-minute minor string nitpicks

---------

Co-authored-by: OpenSauce <opensauce04@gmail.com>
2026-07-04 15:11:06 +01:00
KojoZero
8c4e8b77b2
Add Skip Presenting Duplicate Frames Feature (#1867)
* added basic skip duplicate frames implementation

* fix secondary display and attempt to fix lcd color fill
2026-07-03 12:37:27 +02:00
PabloMK7
fcb10fb899
core: Make PICA debugging a toggleable feature (#2254) 2026-07-03 11:00:56 +02:00
PabloMK7
77cb5cecd8 Remove unintended leftover scripts 2026-07-02 21:40:19 +02:00
Malachi
6b197e2c04
renderer_vulkan: Fix screenshot color channels on RGBA swapchain formats (#2170) 2026-07-02 09:40:33 +02:00
Filipe Fortunato
384913be70
video_core: vulkan: Fix crash on color fill before framebuffer setup (#2241) 2026-07-01 20:32:38 +02:00
Andrey Kurlin
307f558d28
Refresh game list after uninstalling installed titles (#2237)
Signed-off-by: Andrey Kurlin <superkurlin2013@yandex.ru>
2026-07-01 20:20:56 +02:00
Wunk
d93adebb16
renderer_gl: Fix OpenGL texture unit state tracking (#2242)
* video_core: Implement texture-layer clears

Allow specific texture-layers to be targetted for clears rather than just defaulting to the first layer, or clearing all layers after in the case of Vulkan. Allows specific faces of a cubemap to be cleared.

* video_core: Initialize `NULL_SURFACE_CUBE_ID`

Properly initialize the `NULL_SURFACE_CUBE_ID` texture to ensure that all 6 of its faces are black-transparent.

* renderer_gl: Add explicit texture-unit target state tracking

The `.target` field can be either a `GL_TEXTURE_2D` or a
`GL_TEXTURE_CUBE_MAP` and must be explicitly set when assigning the
`.texture_2d` or there is a risk of leaking a previous texture state
into the current one in the case that a texture-cube was binded into
slot 0 and it becomes a texture-2d sometime after.
2026-07-01 17:25:03 +02:00
OpenSauce04
7c225c6abb qt: Fix macOS freeze-on-boot caused by unprocessed events
As far as I can tell the line added here really shouldn't be necessary and wasn't until a relatively recent change in either Qt or macOS. The exact reason behind the original issue is unclear, but by educated guess it could have been caused by some kind of race condition within Qt?
2026-06-29 14:34:22 +01:00
asyync1024
93143bcb15
audio_core: Add '#include <cstring>' to cubeb_sink and cubeb_input.cpp (#2232)
* audio_core: Add '#include <cstring>' to cubeb_sink and cubeb_input.cpp

This fixes a build failure encountered when compiling: https://pastebin.com/JQ4mbhyu

* Fix: License header
2026-06-24 20:54:06 +02:00
PabloMK7
32fd435928 core: Add HLE HTTP URL replacement functionality 2026-06-24 17:43:04 +02:00
PabloMK7
1d59c9a0fc core: Fix HTTPC behaviour when post data is pending 2026-06-24 17:43:04 +02:00
PabloMK7
9293767e1d
dynarmic: Update to latest commit (#2236) 2026-06-24 16:11:31 +02:00
Daniel López Guimaraes
c9d2593c2c
apt: Properly implement app ID checks (#2225)
The IsSystemAppletId and IsApplicationAppletId checks were prone to
collisions due to the way they were implemented. For example, the
application app ID would pass IsSystemAppletId since 0x300 & 0x100 != 0

To account for masking collisions, add a generic `TypeMask` enum value
used to mask the app ID type correctly, and then match the masked value
with the app ID we're looking for.

Fixes issues with system applets that use objects from the caller
applications, since the edge case for sysapplet parameters being sent to
applications was also passing the other way around.
2026-06-19 23:56:06 +02:00
PabloMK7
9e43e451d1
core: Implement APT:MapProgramIdForDebug (#2224) 2026-06-19 09:00:33 +02:00
PabloMK7
0a269688a3
core: Implement CFG:TranslateCountryInfo (#2223) 2026-06-19 09:00:08 +02:00
78 changed files with 1646 additions and 716 deletions

View File

@ -32,6 +32,7 @@ foreach(KEY IN ITEMS
"physical_device"
"use_gles"
"renderer_debug"
"pica_debugging"
"dump_command_buffers"
"spirv_shader_gen"
"disable_spirv_optimizer"
@ -41,6 +42,7 @@ foreach(KEY IN ITEMS
"use_disk_shader_cache"
"shaders_accurate_mul"
"use_vsync"
"use_skip_duplicate_frames"
"use_display_refresh_rate_detection"
"use_shader_jit"
"resolution_factor"
@ -239,6 +241,7 @@ if (ANDROID)
"screen_orientation"
"performance_overlay_position"
"enable_secondary_display"
"combo_button_buttons"
)
string(REPLACE "_" "_1" KEY_JNI_ESCAPED ${KEY})
set(SETTING_KEY_LIST "${SETTING_KEY_LIST}\n\"${KEY}\",")

View File

@ -1,299 +0,0 @@
set PREVIEW_MODE "Key Dumper\n \nWorking..."
# Boot9
set BOOT9_BIN "M:/boot9.bin"
fget $[BOOT9_BIN]@D9D0:10 KEYX_2C
set KEYX_2D $[KEYX_2C]
set KEYX_2E $[KEYX_2C]
set KEYX_2F $[KEYX_2C]
fget $[BOOT9_BIN]@D9E0:10 KEYX_30
set KEYX_31 $[KEYX_30]
set KEYX_32 $[KEYX_30]
set KEYX_33 $[KEYX_30]
fget $[BOOT9_BIN]@D9F0:10 KEYX_34
set KEYX_35 $[KEYX_34]
set KEYX_36 $[KEYX_34]
set KEYX_37 $[KEYX_34]
fget $[BOOT9_BIN]@DA00:10 KEYX_38
set KEYX_39 $[KEYX_38]
set KEYX_3A $[KEYX_38]
set KEYX_3B $[KEYX_38]
fget $[BOOT9_BIN]@DA10:10 KEYX_3C
fget $[BOOT9_BIN]@DA20:10 KEYX_3D
fget $[BOOT9_BIN]@DA30:10 KEYX_3E
fget $[BOOT9_BIN]@DA40:10 KEYX_3F
fget $[BOOT9_BIN]@DA50:10 KEYY_04
fget $[BOOT9_BIN]@DA60:10 KEYY_05
fget $[BOOT9_BIN]@DA70:10 KEYY_06
fget $[BOOT9_BIN]@DA80:10 KEYY_07
fget $[BOOT9_BIN]@DA90:10 KEYY_08
fget $[BOOT9_BIN]@DAA0:10 KEYY_09
fget $[BOOT9_BIN]@DAB0:10 KEYY_0A
fget $[BOOT9_BIN]@DAC0:10 KEYY_0B
fget $[BOOT9_BIN]@DAD0:10 KEYN_0D
fget $[BOOT9_BIN]@DBA0:10 KEYN_2D
fget $[BOOT9_BIN]@DBB0:10 KEYN_32
fget $[BOOT9_BIN]@DBC0:10 KEYN_36
fget $[BOOT9_BIN]@DBD0:10 KEYN_38
# NATIVE_FIRM
if chk $[ONTYPE] "N3DS"
if not find 1:/title/00040138/20000002/content/????????.app NATIVE_FIRM_APP
echo "New 3DS NATIVE_FIRM not found."
goto Exit
end
set EXPECTED_NATIVE_FIRM_VER "1F00"
set KEYY_2E_OFFSET "66504"
set KEYY_39_NFC_OFFSET "66524"
set COMMON_0_OFFSET "6CE51"
set COMMON_1_OFFSET "6CE65"
set COMMON_2_OFFSET "6CE79"
set COMMON_3_OFFSET "6CE8D"
set COMMON_4_OFFSET "6CEA1"
set COMMON_5_OFFSET "6CEB5"
else
if not find 1:/title/00040138/00000002/content/????????.app NATIVE_FIRM_APP
echo "Old 3DS NATIVE_FIRM not found."
goto Exit
end
set EXPECTED_NATIVE_FIRM_VER "1F00"
set KEYY_2E_OFFSET "66488"
set KEYY_39_NFC_OFFSET "664A8"
set COMMON_0_OFFSET "6CDC1"
set COMMON_1_OFFSET "6CDD5"
set COMMON_2_OFFSET "6CDE9"
set COMMON_3_OFFSET "6CDFD"
set COMMON_4_OFFSET "6CE11"
set COMMON_5_OFFSET "6CE25"
end
set NATIVE_FIRM_EXTHEADER "G:/extheader.bin"
set NATIVE_FIRM_ENCRYPTED "G:/exefs/.firm"
set NATIVE_FIRM_DECRYPTED "$[GM9OUT]/native.firm"
set PROCESS9_CODE "G:/0004013000003000.Process9/exefs/.code"
imgmount $[NATIVE_FIRM_APP]
fget $[NATIVE_FIRM_EXTHEADER]@E:2 NATIVE_FIRM_VER
if not chk $[NATIVE_FIRM_VER] $[EXPECTED_NATIVE_FIRM_VER]
echo "Unsupported NATIVE_FIRM version.\nThis script requires the latest 3DS firmware.\n\nExpected $[EXPECTED_NATIVE_FIRM_VER], got $[NATIVE_FIRM_VER]"
goto Exit
end
cp -w $[NATIVE_FIRM_ENCRYPTED] $[NATIVE_FIRM_DECRYPTED]
decrypt $[NATIVE_FIRM_DECRYPTED]
imgumount
imgmount $[NATIVE_FIRM_DECRYPTED]
fget $[PROCESS9_CODE]@$[KEYY_2E_OFFSET]:10 KEYY_2E
set KEYY_31 $[KEYY_2E]
set KEYY_39_DLP $[KEYY_2E]
fget $[PROCESS9_CODE]@$[KEYY_39_NFC_OFFSET]:10 KEYY_39_NFC
fget $[PROCESS9_CODE]@$[COMMON_0_OFFSET]:10 COMMON_0
fget $[PROCESS9_CODE]@$[COMMON_1_OFFSET]:10 COMMON_1
fget $[PROCESS9_CODE]@$[COMMON_2_OFFSET]:10 COMMON_2
fget $[PROCESS9_CODE]@$[COMMON_3_OFFSET]:10 COMMON_3
fget $[PROCESS9_CODE]@$[COMMON_4_OFFSET]:10 COMMON_4
fget $[PROCESS9_CODE]@$[COMMON_5_OFFSET]:10 COMMON_5
imgumount
rm -o -s $[NATIVE_FIRM_DECRYPTED]
# NFC
if chk $[ONTYPE] "N3DS"
if not find 1:/title/00040130/20004002/content/????????.app NFC_APP
echo "New 3DS NFC not found."
goto Exit
end
set EXPECTED_NFC_VER "0700"
set NFC_PHRASE_0_OFFSET "355EE"
set NFC_SEED_0_OFFSET "355FC"
set NFC_HMAC_KEY_0_OFFSET "3560A"
set NFC_PHRASE_1_OFFSET "3561A"
set NFC_SEED_1_OFFSET "35628"
set NFC_HMAC_KEY_1_OFFSET "35638"
set NFC_IV_OFFSET "35648"
else
if not find 1:/title/00040130/00004002/content/????????.app NFC_APP
echo "Old 3DS NFC not found."
goto Exit
end
set EXPECTED_NFC_VER "0800"
set NFC_PHRASE_0_OFFSET "17382"
set NFC_SEED_0_OFFSET "17390"
set NFC_HMAC_KEY_0_OFFSET "1739E"
set NFC_PHRASE_1_OFFSET "173AE"
set NFC_SEED_1_OFFSET "173BC"
set NFC_HMAC_KEY_1_OFFSET "173CC"
set NFC_IV_OFFSET "173DC"
end
set NFC_EXTHEADER "G:/extheader.bin"
set NFC_CODE "$[GM9OUT]/nfc_code.bin"
imgmount $[NFC_APP]
fget $[NFC_EXTHEADER]@E:2 NFC_VER
if not chk $[NFC_VER] $[EXPECTED_NFC_VER]
echo "Unsupported NFC module version.\nThis script requires the latest 3DS firmware.\n\nExpected $[EXPECTED_NFC_VER], got $[NFC_VER]"
goto Exit
end
imgumount
extrcode $[NFC_APP] $[NFC_CODE]
fget $[NFC_CODE]@$[NFC_PHRASE_0_OFFSET]:E NFC_PHRASE_0
fget $[NFC_CODE]@$[NFC_SEED_0_OFFSET]:E NFC_SEED_0
fget $[NFC_CODE]@$[NFC_HMAC_KEY_0_OFFSET]:10 NFC_HMAC_KEY_0
fget $[NFC_CODE]@$[NFC_PHRASE_1_OFFSET]:E NFC_PHRASE_1
fget $[NFC_CODE]@$[NFC_SEED_1_OFFSET]:10 NFC_SEED_1
fget $[NFC_CODE]@$[NFC_HMAC_KEY_1_OFFSET]:10 NFC_HMAC_KEY_1
fget $[NFC_CODE]@$[NFC_IV_OFFSET]:10 NFC_IV
rm -o -s $[NFC_CODE]
# GodMode9 Key Database
set KEY_DB "V:/aeskeydb.bin"
set KEY_DB_18X "K:/slot0x18KeyX.ret.bin"
set KEY_DB_19X "K:/slot0x19KeyX.ret.bin"
set KEY_DB_1AX "K:/slot0x1AKeyX.ret.bin"
set KEY_DB_1BX "K:/slot0x1BKeyX.ret.bin"
set KEY_DB_1CX "K:/slot0x1CKeyX.ret.bin"
set KEY_DB_1DX "K:/slot0x1DKeyX.ret.bin"
set KEY_DB_1EX "K:/slot0x1EKeyX.ret.bin"
set KEY_DB_1FX "K:/slot0x1FKeyX.ret.bin"
set KEY_DB_25X "K:/slot0x25KeyX.ret.bin"
set KEY_DB_24Y "K:/slot0x24KeyY.bin"
set KEY_DB_2FY "K:/slot0x2FKeyY.ret.bin"
imgmount $[KEY_DB]
fget $[KEY_DB_18X]@0:10 KEYX_18
fget $[KEY_DB_19X]@0:10 KEYX_19
fget $[KEY_DB_1AX]@0:10 KEYX_1A
fget $[KEY_DB_1BX]@0:10 KEYX_1B
fget $[KEY_DB_1CX]@0:10 KEYX_1C
fget $[KEY_DB_1DX]@0:10 KEYX_1D
fget $[KEY_DB_1EX]@0:10 KEYX_1E
fget $[KEY_DB_1FX]@0:10 KEYX_1F
fget $[KEY_DB_25X]@0:10 KEYX_25
fget $[KEY_DB_24Y]@0:10 KEYY_24
fget $[KEY_DB_2FY]@0:10 KEYY_2F
imgumount
# Write Keys To File
set OUT "0:/gm9/aes_keys.txt"
dumptxt $[OUT] "# KeyX"
dumptxt -p $[OUT] ""
dumptxt -p $[OUT] "slot0x18KeyX=$[KEYX_18]"
dumptxt -p $[OUT] "slot0x19KeyX=$[KEYX_19]"
dumptxt -p $[OUT] "slot0x1AKeyX=$[KEYX_1A]"
dumptxt -p $[OUT] "slot0x1BKeyX=$[KEYX_1B]"
dumptxt -p $[OUT] "slot0x1CKeyX=$[KEYX_1C]"
dumptxt -p $[OUT] "slot0x1DKeyX=$[KEYX_1D]"
dumptxt -p $[OUT] "slot0x1EKeyX=$[KEYX_1E]"
dumptxt -p $[OUT] "slot0x1FKeyX=$[KEYX_1F]"
dumptxt -p $[OUT] "slot0x25KeyX=$[KEYX_25]"
dumptxt -p $[OUT] "slot0x2CKeyX=$[KEYX_2C]"
dumptxt -p $[OUT] "slot0x2DKeyX=$[KEYX_2D]"
dumptxt -p $[OUT] "slot0x2EKeyX=$[KEYX_2E]"
dumptxt -p $[OUT] "slot0x2FKeyX=$[KEYX_2F]"
dumptxt -p $[OUT] "slot0x30KeyX=$[KEYX_30]"
dumptxt -p $[OUT] "slot0x31KeyX=$[KEYX_31]"
dumptxt -p $[OUT] "slot0x32KeyX=$[KEYX_32]"
dumptxt -p $[OUT] "slot0x33KeyX=$[KEYX_33]"
dumptxt -p $[OUT] "slot0x34KeyX=$[KEYX_34]"
dumptxt -p $[OUT] "slot0x35KeyX=$[KEYX_35]"
dumptxt -p $[OUT] "slot0x36KeyX=$[KEYX_36]"
dumptxt -p $[OUT] "slot0x37KeyX=$[KEYX_37]"
dumptxt -p $[OUT] "slot0x38KeyX=$[KEYX_38]"
dumptxt -p $[OUT] "slot0x39KeyX=$[KEYX_39]"
dumptxt -p $[OUT] "slot0x3AKeyX=$[KEYX_3A]"
dumptxt -p $[OUT] "slot0x3BKeyX=$[KEYX_3B]"
dumptxt -p $[OUT] "slot0x3CKeyX=$[KEYX_3C]"
dumptxt -p $[OUT] "slot0x3DKeyX=$[KEYX_3D]"
dumptxt -p $[OUT] "slot0x3EKeyX=$[KEYX_3E]"
dumptxt -p $[OUT] "slot0x3FKeyX=$[KEYX_3F]"
dumptxt -p $[OUT] ""
dumptxt -p $[OUT] "# KeyY"
dumptxt -p $[OUT] ""
dumptxt -p $[OUT] "slot0x04KeyY=$[KEYY_04]"
dumptxt -p $[OUT] "slot0x05KeyY=$[KEYY_05]"
dumptxt -p $[OUT] "slot0x06KeyY=$[KEYY_06]"
dumptxt -p $[OUT] "slot0x07KeyY=$[KEYY_07]"
dumptxt -p $[OUT] "slot0x08KeyY=$[KEYY_08]"
dumptxt -p $[OUT] "slot0x09KeyY=$[KEYY_09]"
dumptxt -p $[OUT] "slot0x0AKeyY=$[KEYY_0A]"
dumptxt -p $[OUT] "slot0x0BKeyY=$[KEYY_0B]"
dumptxt -p $[OUT] "slot0x24KeyY=$[KEYY_24]"
dumptxt -p $[OUT] "slot0x2EKeyY=$[KEYY_2E]"
dumptxt -p $[OUT] "slot0x2FKeyY=$[KEYY_2F]"
dumptxt -p $[OUT] "slot0x31KeyY=$[KEYY_31]"
dumptxt -p $[OUT] ""
dumptxt -p $[OUT] "# DLP/NFC KeyY (slot 0x39)"
dumptxt -p $[OUT] ""
dumptxt -p $[OUT] "dlpKeyY=$[KEYY_39_DLP]"
dumptxt -p $[OUT] "nfcKeyY=$[KEYY_39_NFC]"
dumptxt -p $[OUT] ""
dumptxt -p $[OUT] "# Ticket Common KeyY (slot 0x3D)"
dumptxt -p $[OUT] ""
dumptxt -p $[OUT] "common0=$[COMMON_0]"
dumptxt -p $[OUT] "common1=$[COMMON_1]"
dumptxt -p $[OUT] "common2=$[COMMON_2]"
dumptxt -p $[OUT] "common3=$[COMMON_3]"
dumptxt -p $[OUT] "common4=$[COMMON_4]"
dumptxt -p $[OUT] "common5=$[COMMON_5]"
dumptxt -p $[OUT] ""
dumptxt -p $[OUT] "# KeyN"
dumptxt -p $[OUT] ""
dumptxt -p $[OUT] "slot0x0DKeyN=$[KEYN_0D]"
dumptxt -p $[OUT] "slot0x2DKeyN=$[KEYN_2D]"
dumptxt -p $[OUT] "slot0x32KeyN=$[KEYN_32]"
dumptxt -p $[OUT] "slot0x36KeyN=$[KEYN_36]"
dumptxt -p $[OUT] "slot0x38KeyN=$[KEYN_38]"
dumptxt -p $[OUT] ""
dumptxt -p $[OUT] "# NFC Secrets"
dumptxt -p $[OUT] ""
dumptxt -p $[OUT] "nfcSecret0Phrase=$[NFC_PHRASE_0]"
dumptxt -p $[OUT] "nfcSecret0Seed=$[NFC_SEED_0]"
dumptxt -p $[OUT] "nfcSecret0HmacKey=$[NFC_HMAC_KEY_0]"
dumptxt -p $[OUT] "nfcSecret1Phrase=$[NFC_PHRASE_1]"
dumptxt -p $[OUT] "nfcSecret1Seed=$[NFC_SEED_1]"
dumptxt -p $[OUT] "nfcSecret1HmacKey=$[NFC_HMAC_KEY_1]"
dumptxt -p $[OUT] "nfcIv=$[NFC_IV]"
# Dump seeddb.bin as well
set SEEDDB_IN "0:/gm9/out/seeddb.bin"
set SEEDDB_OUT "0:/gm9/seeddb.bin"
sdump -w seeddb.bin
cp -w $[SEEDDB_IN] $[SEEDDB_OUT]
@Exit

View File

@ -1,10 +0,0 @@
# DumpKeys
This is a GodMode9 script that dumps all the keys and other related secrets that Azahar needs from a real 3DS.
Usage:
1. Copy "DumpKeys.gm9" into the "gm9/scripts/" directory on your SD card.
2. Launch GodMode9, press the HOME button, select Scripts, and select "DumpKeys" from the list of scripts that appears.
3. Wait for the script to complete and return you to the GodMode9 main menu.
4. Power off your system and copy the "gm9/aes_keys.txt" and "gm9/seeddb.bin" files off of your SD card into "(Azahar directory)/sysdata/".

2
externals/dynarmic vendored

@ -1 +1 @@
Subproject commit 526227eebe1efff3fb14dbf494b9c5b44c2e9c1f
Subproject commit e77b1ba0b7da7cbe93021b01a663acfe7c4dd516

View File

@ -1,6 +1,7 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="AndroidLintUseKtx" enabled="true" level="STYLE_SUGGESTION" enabled_by_default="true" editorAttributes="TEXT_STYLE_SUGGESTION" />
<inspection_tool class="GrazieInspection" enabled="false" level="GRAMMAR_ERROR" enabled_by_default="false" />
<inspection_tool class="GrazieStyle" enabled="false" level="STYLE_SUGGESTION" enabled_by_default="false" />
<inspection_tool class="LanguageDetectionInspection" enabled="false" level="WEAK WARNING" enabled_by_default="false" />

View File

@ -12,5 +12,6 @@ enum class Hotkey(val button: Int) {
QUICKSAVE(10005),
QUICKLOAD(10006),
TURBO_LIMIT(10007),
ENABLE(10008)
ENABLE(10008),
COMBO_BUTTON(10009)
}

View File

@ -14,6 +14,7 @@ import org.citra.citra_emu.R
import org.citra.citra_emu.display.ScreenAdjustmentUtil
import org.citra.citra_emu.features.settings.model.Settings
import org.citra.citra_emu.features.settings.model.view.InputBindingSetting
import org.citra.citra_emu.utils.ComboHelper
import org.citra.citra_emu.utils.EmulationLifecycleUtil
import org.citra.citra_emu.utils.TurboHelper
@ -71,12 +72,17 @@ class HotkeyUtility(
var handled = false
val buttonSet = InputBindingSetting.getButtonSet(keyEvent)
val thisKeyIsEnableButton = buttonSet.contains(Hotkey.ENABLE.button)
val thisKeyIsComboButton = buttonSet.contains(Hotkey.COMBO_BUTTON.button)
val thisKeyIsHotkey =
!thisKeyIsEnableButton && Hotkey.entries.any { buttonSet.contains(it.button) }
if (thisKeyIsEnableButton) {
handled = true
hotkeyIsEnabled = false
}
if (thisKeyIsComboButton) {
ComboHelper.comboActivate(NativeLibrary.ButtonState.RELEASED)
handled = true
}
for (button in buttonSet) {
// this is a hotkey button
@ -142,6 +148,10 @@ class HotkeyUtility(
).show()
}
Hotkey.COMBO_BUTTON.button -> {
ComboHelper.comboActivate(NativeLibrary.ButtonState.PRESSED)
}
else -> {}
}
hotkeyIsPressed = true

View File

@ -39,6 +39,7 @@ object SettingKeys {
external fun use_disk_shader_cache(): String
external fun shaders_accurate_mul(): String
external fun use_vsync(): String
external fun use_skip_duplicate_frames(): String
external fun use_shader_jit(): String
external fun resolution_factor(): String
external fun frame_limit(): String
@ -142,4 +143,5 @@ object SettingKeys {
external fun screen_orientation(): String
external fun performance_overlay_position(): String
external fun enable_secondary_display(): String
external fun combo_button_buttons(): String
}

View File

@ -97,6 +97,11 @@ enum class BooleanSetting(
HW_SHADER(SettingKeys.use_hw_shader(), Settings.SECTION_RENDERER, true),
SHADER_JIT(SettingKeys.use_shader_jit(), Settings.SECTION_RENDERER, true),
VSYNC(SettingKeys.use_vsync(), Settings.SECTION_RENDERER, false),
USE_SKIP_DUPLICATE_FRAMES(
SettingKeys.use_skip_duplicate_frames(),
Settings.SECTION_RENDERER,
false
),
USE_FRAME_LIMIT(SettingKeys.use_frame_limit(), Settings.SECTION_RENDERER, true),
DEBUG_RENDERER(SettingKeys.renderer_debug(), Settings.SECTION_DEBUG, false),
DISABLE_RIGHT_EYE_RENDER(
@ -123,7 +128,7 @@ enum class BooleanSetting(
SIMULATE_3DS_GPU_TIMINGS(
SettingKeys.simulate_3ds_gpu_timings(),
Settings.SECTION_RENDERER,
true
false
);
override var boolean: Boolean = defaultValue

View File

@ -18,6 +18,16 @@ enum class IntListSetting(
Settings.SECTION_LAYOUT,
listOf(0, 1, 2, 3, 4, 5),
canBeEmpty = false
),
/**
* Combo Buttons will be given options on MultiChoice setting initialization. This just makes selections empty by default.
*/
COMBO_BUTTON_BUTTONS(
SettingKeys.combo_button_buttons(),
Settings.SECTION_CONTROLS,
listOf(),
canBeEmpty = true
);
private var backingList: List<Int> = defaultValue

View File

@ -142,6 +142,7 @@ class Settings {
const val HOTKEY_QUICKSAVE = "hotkey_quickload"
const val HOTKEY_QUICKLOAD = "hotkey_quickpause"
const val HOTKEY_TURBO_LIMIT = "hotkey_turbo_limit"
const val HOTKEY_BUTTON_COMBO = "hotkey_button_combo"
val buttonKeys = listOf(
KEY_BUTTON_A,
@ -209,7 +210,8 @@ class Settings {
HOTKEY_PAUSE_OR_RESUME,
HOTKEY_QUICKSAVE,
HOTKEY_QUICKLOAD,
HOTKEY_TURBO_LIMIT
HOTKEY_TURBO_LIMIT,
HOTKEY_BUTTON_COMBO
)
val hotkeyTitles = listOf(
R.string.controller_hotkey_enable_button,
@ -219,7 +221,8 @@ class Settings {
R.string.emulation_toggle_pause,
R.string.emulation_quicksave,
R.string.emulation_quickload,
R.string.turbo_limit_hotkey
R.string.turbo_limit_hotkey,
R.string.button_combo
)
// TODO: Move these in with the other setting keys in GenerateSettingKeys.cmake

View File

@ -127,6 +127,7 @@ class InputBindingSetting(val abstractSetting: AbstractSetting, titleId: Int) :
Settings.HOTKEY_QUICKSAVE -> Hotkey.QUICKSAVE.button
Settings.HOTKEY_QUICKLOAD -> Hotkey.QUICKLOAD.button
Settings.HOTKEY_TURBO_LIMIT -> Hotkey.TURBO_LIMIT.button
Settings.HOTKEY_BUTTON_COMBO -> Hotkey.COMBO_BUTTON.button
else -> -1
}

View File

@ -799,6 +799,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
private fun addControlsSettings(sl: ArrayList<SettingsItem>) {
settingsActivity.setToolbarTitle(settingsActivity.getString(R.string.preferences_controls))
sl.apply {
add(
RunnableSetting(
@ -810,6 +811,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
onLongClick = { settingsAdapter.onLongClickAutoMap() }
)
)
add(HeaderSetting(R.string.generic_buttons))
Settings.buttonKeys.forEachIndexed { i: Int, key: String ->
val button = getInputObject(key)
@ -860,6 +862,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
val button = getInputObject(key)
add(InputBindingSetting(button, Settings.hotkeyTitles[i]))
}
add(HeaderSetting(R.string.miscellaneous))
add(
SwitchSetting(
@ -870,6 +873,18 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
BooleanSetting.USE_ARTIC_BASE_CONTROLLER.defaultValue
)
)
add(
MultiChoiceSetting(
IntListSetting.COMBO_BUTTON_BUTTONS,
R.string.combo_button_settings,
R.string.combo_button_settings_description,
R.array.comboOptions,
R.array.comboOptionValues,
IntListSetting.COMBO_BUTTON_BUTTONS.key,
IntListSetting.COMBO_BUTTON_BUTTONS.defaultValue
)
)
}
}
@ -1146,6 +1161,15 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
IntSetting.TEXTURE_SAMPLING.defaultValue
)
)
add(
SwitchSetting(
BooleanSetting.USE_SKIP_DUPLICATE_FRAMES,
R.string.use_skip_duplicate_frames,
R.string.use_skip_duplicate_frames_description,
BooleanSetting.USE_SKIP_DUPLICATE_FRAMES.key,
BooleanSetting.USE_SKIP_DUPLICATE_FRAMES.defaultValue
)
)
// Disabled until custom texture implementation gets rewrite, current one overloads RAM
// and crashes Citra.

View File

@ -69,6 +69,7 @@ import org.citra.citra_emu.display.PortraitScreenLayout
import org.citra.citra_emu.display.ScreenAdjustmentUtil
import org.citra.citra_emu.display.ScreenLayout
import org.citra.citra_emu.display.SecondaryDisplayLayout
import org.citra.citra_emu.features.hotkeys.Hotkey
import org.citra.citra_emu.features.settings.model.BooleanSetting
import org.citra.citra_emu.features.settings.model.IntSetting
import org.citra.citra_emu.features.settings.model.SettingsViewModel
@ -862,6 +863,16 @@ class EmulationFragment :
true
}
R.id.menu_emulation_adjust_scale_button_turbo -> {
showAdjustScaleDialog("controlScale-" + NativeLibrary.ButtonType.BUTTON_TURBO)
true
}
R.id.menu_emulation_adjust_scale_button_combo -> {
showAdjustScaleDialog("controlScale-" + Hotkey.COMBO_BUTTON.button)
true
}
R.id.menu_emulation_adjust_opacity -> {
showAdjustOpacityDialog()
true
@ -1241,13 +1252,13 @@ class EmulationFragment :
private fun showToggleControlsDialog() {
val editor = preferences.edit()
val enabledButtons = BooleanArray(16)
val enabledButtons = BooleanArray(17)
enabledButtons.forEachIndexed { i: Int, _: Boolean ->
// Buttons that are disabled by default
var defaultValue = true
when (i) {
// TODO: Remove these magic numbers
6, 7, 12, 13, 14, 15 -> defaultValue = false
6, 7, 12, 13, 14, 15, 16 -> defaultValue = false
}
enabledButtons[i] = preferences.getBoolean("buttonToggle$i", defaultValue)
}
@ -1282,18 +1293,25 @@ class EmulationFragment :
val sliderBinding = DialogSliderBinding.inflate(layoutInflater)
sliderBinding.apply {
slider.valueTo = 150f
slider.valueFrom = 0f
slider.value = preferences.getInt(target, 50).toFloat()
textValue.setText((slider.value + 50).toInt().toString())
val sliderStart = 50
val sliderMin = 0
val sliderMax = 150
slider.valueFrom = sliderMin.toFloat()
slider.valueTo = sliderMax.toFloat()
slider.value = preferences.getInt(target, sliderStart)
.toFloat()
.coerceIn(slider.valueFrom..slider.valueTo)
@SuppressLint("SetTextI18n")
textValue.setText((slider.value + sliderStart).toInt().toString())
textValue.addTextChangedListener(object : TextWatcher {
override fun afterTextChanged(s: Editable) {
val value = s.toString().toIntOrNull()
if (value == null || value < 50 || value > 150) {
val realValue = value?.minus(sliderStart)
if (realValue == null || realValue < sliderMin || realValue > sliderMax) {
textInput.error = "Inappropriate Value"
} else {
textInput.error = null
slider.value = value.toFloat() - 50
slider.value = realValue.toFloat()
}
}
@ -1306,8 +1324,10 @@ class EmulationFragment :
progress: Float,
_: Boolean
->
if (textValue.text.toString() != (slider.value + 50).toInt().toString()) {
textValue.setText((slider.value + 50).toInt().toString())
if (textValue.text.toString() !=
(slider.value + sliderStart).toInt().toString()
) {
textValue.setText((slider.value + sliderStart).toInt().toString())
textValue.setSelection(textValue.length())
setControlScale(slider.value.toInt(), target)
}
@ -1415,6 +1435,8 @@ class EmulationFragment :
resetScale("controlScale-" + NativeLibrary.ButtonType.STICK_C)
resetScale("controlScale-" + NativeLibrary.ButtonType.BUTTON_HOME)
resetScale("controlScale-" + NativeLibrary.ButtonType.BUTTON_SWAP)
resetScale("controlScale-" + NativeLibrary.ButtonType.BUTTON_TURBO)
resetScale("controlScale-" + Hotkey.COMBO_BUTTON.button)
binding.surfaceInputOverlay.refreshControls()
}
@ -1442,10 +1464,11 @@ class EmulationFragment :
.apply()
val editor = preferences.edit()
for (i in 0 until 16) {
// TODO: This code sucks balls. We need to do this differently. -OS
for (i in 0 until 17) {
var defaultValue = true
when (i) {
6, 7, 12, 13, 14, 15 -> defaultValue = false
6, 7, 12, 13, 14, 15, 16 -> defaultValue = false
}
editor.putBoolean("buttonToggle$i", defaultValue)
}

View File

@ -26,6 +26,8 @@ import kotlin.math.min
import org.citra.citra_emu.CitraApplication
import org.citra.citra_emu.NativeLibrary
import org.citra.citra_emu.R
import org.citra.citra_emu.features.hotkeys.Hotkey
import org.citra.citra_emu.utils.ComboHelper
import org.citra.citra_emu.utils.EmulationMenuSettings
import org.citra.citra_emu.utils.TurboHelper
@ -184,6 +186,8 @@ class InputOverlay(context: Context?, attrs: AttributeSet?) :
button.status == NativeLibrary.ButtonState.PRESSED
) {
TurboHelper.toggleTurbo(true)
} else if (button.id == Hotkey.COMBO_BUTTON.button) {
ComboHelper.comboActivate(button.status)
}
NativeLibrary.onGamePadEvent(
@ -593,6 +597,18 @@ class InputOverlay(context: Context?, attrs: AttributeSet?) :
)
)
}
if (preferences.getBoolean("buttonToggle16", false)) {
overlayButtons.add(
initializeOverlayButton(
context,
R.drawable.button_combo,
R.drawable.button_combo_pressed,
Hotkey.COMBO_BUTTON.button,
orientation
)
)
}
}
fun refreshControls() {
@ -806,6 +822,14 @@ class InputOverlay(context: Context?, attrs: AttributeSet?) :
NativeLibrary.ButtonType.BUTTON_TURBO.toString() + "-Y",
resources.getInteger(R.integer.N3DS_BUTTON_TURBO_Y).toFloat() / 1000 * maxY
)
.putFloat(
Hotkey.COMBO_BUTTON.button.toString() + "-X",
resources.getInteger(R.integer.N3DS_BUTTON_COMBO_X).toFloat() / 1000 * maxX
)
.putFloat(
Hotkey.COMBO_BUTTON.button.toString() + "-Y",
resources.getInteger(R.integer.N3DS_BUTTON_COMBO_Y).toFloat() / 1000 * maxY
)
.apply()
}
@ -957,6 +981,14 @@ class InputOverlay(context: Context?, attrs: AttributeSet?) :
NativeLibrary.ButtonType.BUTTON_TURBO.toString() + portrait + "-Y",
resources.getInteger(R.integer.N3DS_BUTTON_TURBO_PORTRAIT_Y).toFloat() / 1000 * maxY
)
.putFloat(
Hotkey.COMBO_BUTTON.button.toString() + portrait + "-X",
resources.getInteger(R.integer.N3DS_BUTTON_COMBO_PORTRAIT_X).toFloat() / 1000 * maxX
)
.putFloat(
Hotkey.COMBO_BUTTON.button.toString() + portrait + "-Y",
resources.getInteger(R.integer.N3DS_BUTTON_COMBO_PORTRAIT_Y).toFloat() / 1000 * maxY
)
.apply()
}

View File

@ -0,0 +1,26 @@
// Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
package org.citra.citra_emu.utils
import org.citra.citra_emu.NativeLibrary
import org.citra.citra_emu.features.settings.model.IntListSetting
object ComboHelper {
fun comboActivate(buttonStatus: Int) {
val comboArray = IntListSetting.COMBO_BUTTON_BUTTONS.list
for (nativeButton in comboArray) {
if (nativeButton == -1) {
// We don't want to parse any bad inputs here so we continue loop
continue
} else {
NativeLibrary.onGamePadEvent(
NativeLibrary.TOUCHSCREEN_DEVICE,
nativeButton,
buttonStatus
)
}
}
}
}

View File

@ -152,6 +152,7 @@ void Config::ReadValues() {
ReadSetting("Renderer", Settings::values.resolution_factor);
ReadSetting("Renderer", Settings::values.use_disk_shader_cache);
ReadSetting("Renderer", Settings::values.use_vsync);
ReadSetting("Renderer", Settings::values.use_skip_duplicate_frames);
ReadSetting("Renderer", Settings::values.texture_filter);
ReadSetting("Renderer", Settings::values.texture_sampling);
ReadSetting("Renderer", Settings::values.turbo_limit);
@ -314,6 +315,7 @@ void Config::ReadValues() {
Settings::values.record_frame_times =
android_config->GetBoolean("Debugging", Settings::HKeys::record_frame_times.c_str(), false);
ReadSetting("Debugging", Settings::values.renderer_debug);
ReadSetting("Debugging", Settings::values.pica_debugging);
ReadSetting("Debugging", Settings::values.use_gdbstub);
ReadSetting("Debugging", Settings::values.gdbstub_port);
ReadSetting("Debugging", Settings::values.instant_debug_log);

View File

@ -80,6 +80,9 @@ static const char* android_config_default_file_content = (BOOST_HANA_STRING(R"(
# Use Artic Controller when connected to Artic Base Server. (Default 0)
)") DECLARE_KEY(use_artic_base_controller) BOOST_HANA_STRING(R"(
# List of buttons which will be triggered by the combo button. (Default [] or empty)
)") DECLARE_KEY(combo_button_buttons) BOOST_HANA_STRING(R"(
[Core]
# Whether to use the Just-In-Time (JIT) compiler for CPU emulation
# 0: Interpreter (slow), 1 (default): JIT (fast)
@ -130,6 +133,10 @@ static const char* android_config_default_file_content = (BOOST_HANA_STRING(R"(
# 0 (default): Off, 1: On
)") DECLARE_KEY(use_vsync) BOOST_HANA_STRING(R"(
# Skips display of duplicated frames in 30 fps games
# 0: Off, 1 (default): On
)") DECLARE_KEY(use_skip_duplicate_frames) BOOST_HANA_STRING(R"(
# Reduce stuttering by storing and loading generated shaders to disk
# 0: Off, 1 (default. On)
)") DECLARE_KEY(use_disk_shader_cache) BOOST_HANA_STRING(R"(
@ -546,6 +553,10 @@ static const char* android_config_default_file_content = (BOOST_HANA_STRING(R"(
# 0 (default): Off, 1: On
)") DECLARE_KEY(renderer_debug) BOOST_HANA_STRING(R"(
# Whether to enable PICA200 debugging (does nothing on Android)
# 0 (default): Off, 1: On
)") DECLARE_KEY(pica_debugging) BOOST_HANA_STRING(R"(
# Flush log output on every message
# Immediately commits the debug log to file. Use this if Azahar crashes and the log output is being cut.
)") DECLARE_KEY(instant_debug_log) BOOST_HANA_STRING(R"(

View File

@ -255,7 +255,13 @@ static Core::System::ResultStatus RunCitra(const std::string& filepath) {
// Register microphone permission check
system.RegisterMicPermissionCheck(&CheckMicPermission);
// No PICA debugging on Android
if (Settings::values.pica_debugging) {
Pica::g_debug_context = Pica::DebugContext::Construct();
} else {
Pica::g_debug_context.reset();
}
InputManager::Init();
window->MakeCurrent();

View File

@ -0,0 +1,32 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="100dp"
android:height="100dp"
android:viewportWidth="99.27"
android:viewportHeight="99.27">
<!-- Outer circle -->
<path
android:fillAlpha="0.5"
android:fillColor="#eaeaea"
android:pathData="M49.64,49.64m-49.64,0a49.64,49.64 0,1 1,99.28 0a49.64,49.64 0,1 1,-99.28 0"
android:strokeAlpha="0.5" />
<!-- Centered blocky "C" icon -->
<path
android:fillAlpha="0.75"
android:fillColor="#FF000000"
android:pathData="M42,30
H62
V40
H47
V60
H62
V70
H42
V60
H37
V40
H42
Z"
android:strokeAlpha="0.75" />
</vector>

View File

@ -0,0 +1,28 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="100dp"
android:height="100dp"
android:viewportWidth="99.27"
android:viewportHeight="99.27">
<path
android:fillAlpha="0.5"
android:fillColor="#151515"
android:pathData="M49.64,49.64m-49.64,0a49.64,49.64 0,1 1,99.28 0a49.64,49.64 0,1 1,-99.28 0"
android:strokeAlpha="0.5" />
<path
android:fillAlpha="0.75"
android:fillColor="#fff"
android:pathData="M42,30
H62
V40
H47
V60
H62
V70
H42
V60
H37
V40
H42
Z"
android:strokeAlpha="0.75" />
</vector>

View File

@ -79,6 +79,12 @@
<item
android:id="@+id/menu_emulation_adjust_scale_button_swap"
android:title="@string/button_swap" />
<item
android:id="@+id/menu_emulation_adjust_scale_button_turbo"
android:title="@string/button_turbo" />
<item
android:id="@+id/menu_emulation_adjust_scale_button_combo"
android:title="@string/button_combo" />
</menu>
</item>

View File

@ -188,8 +188,37 @@
<item>@string/button_home</item>
<item>@string/button_swap</item>
<item>@string/button_turbo</item>
<item>@string/button_combo</item>
</string-array>
<string-array name="comboOptions">
<item>@string/button_a</item>
<item>@string/button_b</item>
<item>@string/button_x</item>
<item>@string/button_y</item>
<item>@string/button_l</item>
<item>@string/button_r</item>
<item>@string/button_zl</item>
<item>@string/button_zr</item>
<item>@string/button_start</item>
<item>@string/button_select</item>
</string-array>
<!-- Ints refer to button values in NativeLibrary -->
<integer-array name="comboOptionValues">
<item>700</item>
<item>701</item>
<item>702</item>
<item>703</item>
<item>773</item>
<item>774</item>
<item>707</item>
<item>708</item>
<item>704</item>
<item>705</item>
</integer-array>
<string-array name="cameraImageSourceNames">
<item>@string/blank</item>
<item>@string/still_image</item>

View File

@ -35,6 +35,8 @@
<integer name="N3DS_BUTTON_SWAP_Y">850</integer>
<integer name="N3DS_BUTTON_TURBO_X">630</integer>
<integer name="N3DS_BUTTON_TURBO_Y">850</integer>
<integer name="N3DS_BUTTON_COMBO_X">740</integer>
<integer name="N3DS_BUTTON_COMBO_Y">480</integer>
<!-- Default N3DS portrait layout -->
<integer name="N3DS_BUTTON_A_PORTRAIT_X">810</integer>
@ -69,5 +71,7 @@
<integer name="N3DS_BUTTON_SWAP_PORTRAIT_Y">675</integer>
<integer name="N3DS_BUTTON_TURBO_PORTRAIT_X">453</integer>
<integer name="N3DS_BUTTON_TURBO_PORTRAIT_Y">720</integer>
<integer name="N3DS_BUTTON_COMBO_PORTRAIT_X">450</integer>
<integer name="N3DS_BUTTON_COMBO_PORTRAIT_Y">925</integer>
</resources>

View File

@ -156,6 +156,7 @@
<string name="button_home">HOME</string>
<string name="button_swap">Swap Screens</string>
<string name="button_turbo">Turbo</string>
<string name="button_combo">Combo Button</string>
<string name="button_x" translatable="false">X</string>
<string name="button_y" translatable="false">Y</string>
<string name="button_l" translatable="false">L</string>
@ -167,6 +168,8 @@
<string name="turbo_limit_hotkey">Turbo Speed</string>
<string name="turbo_enabled_toast">Turbo Speed Enabled</string>
<string name="turbo_disabled_toast">Turbo Speed Disabled</string>
<string name="combo_button_settings">Configure Combo Button</string>
<string name="combo_button_settings_description">Select which buttons are triggered by the Combo Button.</string>
<!-- System files strings -->
<string name="setup_system_files">System Files</string>
@ -271,6 +274,8 @@
<string name="advanced">Advanced</string>
<string name="texture_sampling_name">Texture Sampling</string>
<string name="texture_sampling_description">Overrides the sampling filter used by games. This can be useful in certain cases with poorly behaved games when upscaling. If unsure, set this to Game Controlled.</string>
<string name="use_skip_duplicate_frames">Skip Presenting Duplicate Frames</string>
<string name="use_skip_duplicate_frames_description">Skips the presentation of frames that are not unique. It also allows external frame generation tools to work correctly with 30fps games</string>
<string name="shaders_accurate_mul">Accurate Multiplication</string>
<string name="shaders_accurate_mul_description">Uses more accurate multiplication in hardware shaders, which may fix some graphical bugs. When enabled, performance will be reduced.</string>
<string name="asynchronous_gpu">Enable Asynchronous GPU Emulation</string>

View File

@ -2,6 +2,7 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <cstring>
#include <utility>
#include <vector>
#include <cubeb/cubeb.h>

View File

@ -1,8 +1,9 @@
// Copyright 2018 Citra Emulator Project
// Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <cstdarg>
#include <cstring>
#include <mutex>
#include <vector>
#include <cubeb/cubeb.h>

View File

@ -552,7 +552,7 @@ bool retro_load_game(const struct retro_game_info* info) {
emu_instance->emu_window->UpdateLayout();
switch (Settings::values.graphics_api.GetValue()) {
case Settings::GraphicsAPI::OpenGL:
case Settings::GraphicsAPI::OpenGL: {
#ifdef ENABLE_OPENGL
LOG_INFO(Frontend, "Using OpenGL hw renderer");
LibRetro::SetHWSharedContext();
@ -576,7 +576,12 @@ bool retro_load_game(const struct retro_game_info* info) {
LibRetro::SetFramebufferCallback(emu_instance->hw_render.get_current_framebuffer);
#endif
break;
case Settings::GraphicsAPI::Vulkan:
}
case Settings::GraphicsAPI::Vulkan: {
// These braces are required (not only for consistency): vk_negotiation
// below is declared with an initializer, so without an explicit scope
// the following case label would jump past that initialization. MSVC
// rejects that as error C2360 under /permissive- /WX.
#ifdef ENABLE_VULKAN
LOG_INFO(Frontend, "Using Vulkan hw renderer");
emu_instance->hw_render.context_type = RETRO_HW_CONTEXT_VULKAN;
@ -601,13 +606,15 @@ bool retro_load_game(const struct retro_game_info* info) {
LibRetro::SetHWRenderContextNegotiationInterface((void**)&vk_negotiation);
#endif
break;
case Settings::GraphicsAPI::Software:
}
case Settings::GraphicsAPI::Software: {
emu_instance->emu_window->CreateContext();
emu_instance->game_loaded = do_load_game();
if (!emu_instance->game_loaded)
return false;
break;
}
}
uint64_t quirks =
RETRO_SERIALIZATION_QUIRK_CORE_VARIABLE_SIZE | RETRO_SERIALIZATION_QUIRK_MUST_INITIALIZE;

View File

@ -126,6 +126,10 @@ public:
return static_cast<u32>(frame_pool.size());
}
vk::Format GetSurfaceFormat() const noexcept {
return output_format;
}
private:
/// Creates the render pass for LibRetro output
vk::RenderPass CreateRenderpass();

View File

@ -299,8 +299,7 @@ GMainWindow::GMainWindow(Core::System& system_)
if (i >= args.size() - 1 || args[i + 1].startsWith(QChar::fromLatin1('-'))) {
continue;
}
Settings::values.use_gdbstub = true;
Settings::values.gdbstub_port = strtoul(args[++i].toLatin1(), NULL, 0);
gdbport_from_arg = strtoul(args[++i].toLatin1(), NULL, 0);
continue;
}
@ -414,7 +413,11 @@ GMainWindow::GMainWindow(Core::System& system_)
LoadTranslation();
if (Settings::values.pica_debugging) {
Pica::g_debug_context = Pica::DebugContext::Construct();
} else {
Pica::g_debug_context.reset();
}
setAcceptDrops(true);
ui->setupUi(this);
statusBar()->hide();
@ -706,8 +709,12 @@ void GMainWindow::InitializeWidgets() {
}
void GMainWindow::InitializeDebugWidgets() {
if (Pica::g_debug_context) {
connect(ui->action_Create_Pica_Surface_Viewer, &QAction::triggered, this,
&GMainWindow::OnCreateGraphicsSurfaceViewer);
} else {
ui->action_Create_Pica_Surface_Viewer->setEnabled(false);
}
QMenu* debug_menu = ui->menu_View_Debugging;
@ -726,6 +733,7 @@ void GMainWindow::InitializeDebugWidgets() {
connect(this, &GMainWindow::EmulationStopping, registersWidget,
&RegistersWidget::OnEmulationStopping);
if (Pica::g_debug_context) {
graphicsWidget = new GPUCommandStreamWidget(system, this);
addDockWidget(Qt::RightDockWidgetArea, graphicsWidget);
graphicsWidget->hide();
@ -755,6 +763,7 @@ void GMainWindow::InitializeDebugWidgets() {
&GraphicsTracingWidget::OnEmulationStarting);
connect(this, &GMainWindow::EmulationStopping, graphicsTracingWidget,
&GraphicsTracingWidget::OnEmulationStopping);
}
waitTreeWidget = new WaitTreeWidget(system, this);
addDockWidget(Qt::LeftDockWidgetArea, waitTreeWidget);
@ -1516,6 +1525,13 @@ void GMainWindow::BootGame(const QString& filename) {
system.RegisterAppLoaderEarly(loader);
}
// Override GDB settings if emulator was launched with
// GDB port option.
if (gdbport_from_arg != -1) {
system.SetGDBPortOverride(gdbport_from_arg);
system.SetDebugNextProcessFlag();
}
system.ApplySettings();
Settings::LogSettings();
@ -1553,7 +1569,7 @@ void GMainWindow::BootGame(const QString& filename) {
}
// Register debug widgets
if (graphicsWidget->isVisible()) {
if (graphicsWidget && graphicsWidget->isVisible()) {
graphicsWidget->Register();
}
@ -1638,10 +1654,12 @@ void GMainWindow::ShutdownGame() {
// breakpoint after (or before) RequestStop() is called, the emulation would never be able
// to continue out to the main loop and terminate. Thus wait() would hang forever.
// TODO(bunnei): This function is not thread safe, but it's being used as if it were
if (Pica::g_debug_context) {
Pica::g_debug_context->ClearBreakpoints();
}
// Unregister debug widgets
if (graphicsWidget->isVisible()) {
if (graphicsWidget && graphicsWidget->isVisible()) {
graphicsWidget->Unregister();
}
@ -2341,28 +2359,33 @@ void GMainWindow::OnMenuSetUpSystemFiles() {
QRadioButton radio1(&dialog);
QRadioButton radio2(&dialog);
QString new3dsSetupString = tr("Old 3DS setup");
QString old3dsSetupString = tr("New 3DS setup");
QString availableIcon = QStringLiteral("(\u2139\uFE0F) ");
QString unavailableIcon = QStringLiteral("(\u26A0) ");
QString installedIcon = QStringLiteral("(\u2705) ");
if (!install_state.first) {
radio1.setChecked(true);
radio1.setText(tr("(\u2139\uFE0F) Old 3DS setup"));
radio1.setText(availableIcon + old3dsSetupString);
radio1.setToolTip(tr("Setup is possible."));
radio2.setText(tr("(\u26A0) New 3DS setup"));
radio2.setText(unavailableIcon + new3dsSetupString);
radio2.setToolTip(tr("Old 3DS setup is required first."));
radio2.setEnabled(false);
} else {
radio1.setText(tr("(\u2705) Old 3DS setup"));
radio1.setText(installedIcon + old3dsSetupString);
radio1.setToolTip(tr("Setup completed."));
if (!install_state.second) {
radio2.setChecked(true);
radio2.setText(tr("(\u2139\uFE0F) New 3DS setup"));
radio2.setText(availableIcon + new3dsSetupString);
radio2.setToolTip(tr("Setup is possible."));
} else {
radio1.setChecked(true);
radio2.setText(tr("(\u2705) New 3DS setup"));
radio2.setText(installedIcon + new3dsSetupString);
radio2.setToolTip(tr("Setup completed."));
}
}
@ -2564,6 +2587,7 @@ void GMainWindow::UninstallTitles(
} else if (!future_watcher.isCanceled()) {
QMessageBox::information(this, tr("Azahar"),
tr("Successfully uninstalled '%1'.").arg(first_name));
emit InstalledTitlesChanged();
}
}
@ -4456,6 +4480,9 @@ int LaunchQtFrontend(int argc, char* argv[]) {
QObject::connect(&app, &QGuiApplication::applicationStateChanged, &main_window,
&GMainWindow::OnAppFocusStateChanged);
// Process any pending events before executing the app (prevents freeze-onboot on macOS)
app.processEvents();
int result = app.exec();
return result;
}

View File

@ -151,6 +151,7 @@ signals:
void InfoLEDColorChanged();
// Signal that tells widgets to update icons to use the current theme
void UpdateThemedIcons();
void InstalledTitlesChanged();
private:
void InitializeWidgets();
@ -406,23 +407,25 @@ private:
// Whether game was paused due to stopping video dumping
bool game_paused_for_dumping = false;
int gdbport_from_arg = -1;
QString gl_renderer;
std::vector<QString> physical_devices;
// Debugger panes
ProfilerWidget* profilerWidget;
ProfilerWidget* profilerWidget{};
#if MICROPROFILE_ENABLED
MicroProfileDialog* microProfileDialog;
MicroProfileDialog* microProfileDialog{};
#endif
RegistersWidget* registersWidget;
GPUCommandStreamWidget* graphicsWidget;
GPUCommandListWidget* graphicsCommandsWidget;
GraphicsBreakPointsWidget* graphicsBreakpointsWidget;
GraphicsVertexShaderWidget* graphicsVertexShaderWidget;
GraphicsTracingWidget* graphicsTracingWidget;
IPCRecorderWidget* ipcRecorderWidget;
LLEServiceModulesWidget* lleServiceModulesWidget;
WaitTreeWidget* waitTreeWidget;
RegistersWidget* registersWidget{};
GPUCommandStreamWidget* graphicsWidget{};
GPUCommandListWidget* graphicsCommandsWidget{};
GraphicsBreakPointsWidget* graphicsBreakpointsWidget{};
GraphicsVertexShaderWidget* graphicsVertexShaderWidget{};
GraphicsTracingWidget* graphicsTracingWidget{};
IPCRecorderWidget* ipcRecorderWidget{};
LLEServiceModulesWidget* lleServiceModulesWidget{};
WaitTreeWidget* waitTreeWidget{};
QAction* actions_recent_files[max_recent_files_item];
std::array<QAction*, Core::SaveStateSlotCount> actions_load_state;

View File

@ -513,6 +513,7 @@ void QtConfig::ReadDebuggingValues() {
ReadBasicSetting(Settings::values.use_gdbstub);
ReadBasicSetting(Settings::values.gdbstub_port);
ReadBasicSetting(Settings::values.renderer_debug);
ReadBasicSetting(Settings::values.pica_debugging);
ReadBasicSetting(Settings::values.dump_command_buffers);
ReadBasicSetting(Settings::values.instant_debug_log);
ReadBasicSetting(Settings::values.enable_rpc_server);
@ -714,6 +715,7 @@ void QtConfig::ReadRendererValues() {
ReadGlobalSetting(Settings::values.shaders_accurate_mul);
ReadGlobalSetting(Settings::values.use_disk_shader_cache);
ReadGlobalSetting(Settings::values.use_vsync);
ReadGlobalSetting(Settings::values.use_skip_duplicate_frames);
ReadGlobalSetting(Settings::values.use_display_refresh_rate_detection);
ReadGlobalSetting(Settings::values.resolution_factor);
ReadGlobalSetting(Settings::values.use_integer_scaling);
@ -1103,6 +1105,7 @@ void QtConfig::SaveDebuggingValues() {
WriteBasicSetting(Settings::values.use_gdbstub);
WriteBasicSetting(Settings::values.gdbstub_port);
WriteBasicSetting(Settings::values.renderer_debug);
WriteBasicSetting(Settings::values.pica_debugging);
WriteBasicSetting(Settings::values.instant_debug_log);
WriteBasicSetting(Settings::values.enable_rpc_server);
WriteBasicSetting(Settings::values.toggle_unique_data_console_type);
@ -1263,6 +1266,7 @@ void QtConfig::SaveRendererValues() {
WriteGlobalSetting(Settings::values.shaders_accurate_mul);
WriteGlobalSetting(Settings::values.use_disk_shader_cache);
WriteGlobalSetting(Settings::values.use_vsync);
WriteGlobalSetting(Settings::values.use_skip_duplicate_frames);
WriteGlobalSetting(Settings::values.use_display_refresh_rate_detection);
WriteGlobalSetting(Settings::values.resolution_factor);
WriteGlobalSetting(Settings::values.use_integer_scaling);

View File

@ -82,8 +82,14 @@ ConfigureDebug::ConfigureDebug(bool is_powered_on_, QWidget* parent)
});
#endif
connect(ui->toggle_pica_debugging, &QCheckBox::clicked, this, [this](bool checked) {
QMessageBox::information(this, tr("Relaunch Required"),
tr("Please relaunch Azahar for this setting to take effect."));
});
ui->toggle_cpu_jit->setEnabled(!is_powered_on);
ui->toggle_renderer_debug->setEnabled(!is_powered_on);
ui->toggle_pica_debugging->setEnabled(!is_powered_on);
ui->toggle_dump_command_buffers->setEnabled(!is_powered_on);
ui->enable_rpc_server->setEnabled(!is_powered_on);
ui->toggle_unique_data_console_type->setEnabled(!is_powered_on);
@ -135,6 +141,7 @@ void ConfigureDebug::SetConfiguration() {
Settings::values.break_on_unmapped_memory_access.GetValue());
ui->toggle_renderer_debug->setChecked(Settings::values.renderer_debug.GetValue());
ui->toggle_pica_debugging->setChecked(Settings::values.pica_debugging.GetValue());
ui->toggle_dump_command_buffers->setChecked(Settings::values.dump_command_buffers.GetValue());
if (!Settings::IsConfiguringGlobal()) {
@ -181,6 +188,7 @@ void ConfigureDebug::ApplyConfiguration() {
Settings::values.break_on_unmapped_memory_access =
ui->break_on_unmapped_memory_access->isChecked();
Settings::values.renderer_debug = ui->toggle_renderer_debug->isChecked();
Settings::values.pica_debugging = ui->toggle_pica_debugging->isChecked();
Settings::values.dump_command_buffers = ui->toggle_dump_command_buffers->isChecked();
Settings::values.instant_debug_log = ui->instant_debug_log->isChecked();

View File

@ -251,6 +251,13 @@
</widget>
</item>
<item row="5" column="0">
<widget class="QCheckBox" name="toggle_pica_debugging">
<property name="text">
<string>Enable PICA200 debugging</string>
</property>
</widget>
</item>
<item row="6" column="0">
<widget class="QCheckBox" name="toggle_dump_command_buffers">
<property name="text">
<string>Dump command buffers</string>

View File

@ -144,6 +144,8 @@ void ConfigureGraphics::SetConfiguration() {
ui->toggle_accurate_mul->setChecked(Settings::values.shaders_accurate_mul.GetValue());
ui->toggle_disk_shader_cache->setChecked(Settings::values.use_disk_shader_cache.GetValue());
ui->toggle_vsync->setChecked(Settings::values.use_vsync.GetValue());
ui->toggle_skip_duplicate_frames->setChecked(
Settings::values.use_skip_duplicate_frames.GetValue());
ui->spirv_shader_gen->setChecked(Settings::values.spirv_shader_gen.GetValue());
ui->disable_spirv_optimizer->setChecked(Settings::values.disable_spirv_optimizer.GetValue());
ui->toggle_async_shaders->setChecked(Settings::values.async_shader_compilation.GetValue());
@ -180,6 +182,9 @@ void ConfigureGraphics::ApplyConfiguration() {
ui->toggle_disk_shader_cache, use_disk_shader_cache);
ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_vsync, ui->toggle_vsync,
use_vsync);
ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_skip_duplicate_frames,
ui->toggle_skip_duplicate_frames,
use_skip_duplicate_frames);
ConfigurationShared::ApplyPerGameSetting(
&Settings::values.delay_game_render_thread_us, ui->delay_render_combo,
[this](s32) { return ui->delay_render_slider->value(); });
@ -208,6 +213,9 @@ void ConfigureGraphics::SetupPerGameUI() {
Settings::values.use_disk_shader_cache.UsingGlobal());
ui->toggle_vsync->setEnabled(ui->toggle_vsync->isEnabled() &&
Settings::values.use_vsync.UsingGlobal());
ui->toggle_skip_duplicate_frames->setEnabled(
ui->toggle_skip_duplicate_frames->isEnabled() &&
Settings::values.use_skip_duplicate_frames.UsingGlobal());
ui->toggle_async_shaders->setEnabled(
Settings::values.async_shader_compilation.UsingGlobal());
ui->widget_texture_sampling->setEnabled(Settings::values.texture_sampling.UsingGlobal());
@ -250,6 +258,9 @@ void ConfigureGraphics::SetupPerGameUI() {
use_disk_shader_cache);
ConfigurationShared::SetColoredTristate(ui->toggle_vsync, Settings::values.use_vsync,
use_vsync);
ConfigurationShared::SetColoredTristate(ui->toggle_skip_duplicate_frames,
Settings::values.use_skip_duplicate_frames,
use_skip_duplicate_frames);
ConfigurationShared::SetColoredTristate(ui->toggle_async_shaders,
Settings::values.async_shader_compilation,
async_shader_compilation);

View File

@ -39,6 +39,7 @@ private:
ConfigurationShared::CheckState shaders_accurate_mul;
ConfigurationShared::CheckState use_disk_shader_cache;
ConfigurationShared::CheckState use_vsync;
ConfigurationShared::CheckState use_skip_duplicate_frames;
ConfigurationShared::CheckState use_display_refresh_rate_detection;
ConfigurationShared::CheckState async_shader_compilation;
ConfigurationShared::CheckState async_presentation;

View File

@ -7,7 +7,7 @@
<x>0</x>
<y>0</y>
<width>400</width>
<height>509</height>
<height>559</height>
</rect>
</property>
<property name="minimumSize">
@ -317,6 +317,16 @@
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="toggle_skip_duplicate_frames">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;This detects and skips the presentation of frames that are not unique. It also allows external frame generation tools to work correctly with 30fps games. Works in OpenGL and Vulkan.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Skip Presenting Duplicate Frames</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="toggle_display_refresh_rate_detection">
<property name="toolTip">

View File

@ -379,6 +379,8 @@ GameList::GameList(PlayTime::PlayTimeManager& play_time_manager_, GMainWindow* p
item_model->setSortRole(GameListItemPath::SortRole);
connect(main_window, &GMainWindow::UpdateThemedIcons, this, &GameList::OnUpdateThemedIcons);
connect(main_window, &GMainWindow::InstalledTitlesChanged, this,
&GameList::RefreshGameDirectory);
connect(tree_view, &QTreeView::activated, this, &GameList::ValidateEntry);
connect(tree_view, &QTreeView::customContextMenuRequested, this, &GameList::PopupContextMenu);
connect(tree_view, &QTreeView::expanded, this, &GameList::OnItemExpanded);

View File

@ -864,12 +864,16 @@ const std::string& GetExeDirectory() {
}
std::string AppDataRoamingDirectory() {
PWSTR pw_local_path = nullptr;
// Only supported by Windows Vista or later
SHGetKnownFolderPath(FOLDERID_RoamingAppData, 0, nullptr, &pw_local_path);
std::string local_path = Common::UTF16ToUTF8(pw_local_path);
CoTaskMemFree(pw_local_path);
return local_path;
PWSTR path = nullptr;
const HRESULT hr =
SHGetKnownFolderPath(FOLDERID_RoamingAppData, KF_FLAG_DEFAULT, nullptr, &path);
ASSERT_MSG(SUCCEEDED(hr) && path != nullptr, "Failed to get AppData directory: {:X}", hr);
std::string result = Common::UTF16ToUTF8(path);
CoTaskMemFree(path);
return result;
}
#else
/**
@ -1516,6 +1520,46 @@ std::size_t IOFile::WriteImpl(const void* data, std::size_t length, std::size_t
#endif
}
bool IOFile::ReadLine(std::string& line) {
line.clear();
char ch;
bool read_anything = false;
while (true) {
const std::size_t read = ReadImpl(&ch, sizeof(ch), 1);
if (read != sizeof(ch)) {
return read_anything;
}
read_anything = true;
if (ch == '\n') {
return true;
}
// Always convert to UNIX style
if (ch != '\r') {
line.push_back(ch);
}
}
}
size_t IOFile::WriteLine(const std::string_view line) {
const size_t written_line = WriteImpl(line.data(), line.size(), 1);
if (written_line != line.size()) {
return written_line;
}
char nl = '\n';
const size_t written_nl = WriteImpl(&nl, sizeof(nl), 1);
if (written_nl != sizeof(nl)) {
return written_nl;
}
return written_line + written_nl;
}
bool IOFile::Resize(u64 size) {
if (!IsOpen() || 0 !=
#if defined(HAVE_LIBRETRO_VFS)

View File

@ -428,6 +428,27 @@ public:
return WriteImpl(data.data(), data.size(), sizeof(T));
}
/**
* Reads the file line by line, returning true if data
* was read and false when reaching the end of file.
*
* @param line The output string to write the read data to
*
* @returns Whether the line was read or not
*/
bool ReadLine(std::string& line);
/**
* Writes the specified line to the file
* automatically appending a newline
* character to it.
*
* @param line The input string to write
*
* @returns Count of bytes written, including the newline.
*/
size_t WriteLine(const std::string_view line);
[[nodiscard]] virtual bool IsOpen() const {
return nullptr != m_file;
}

View File

@ -573,8 +573,10 @@ void Start() {
}
void Stop() {
if (logging_initialized) {
Impl::Stop();
}
}
void DisableLoggingInTests() {
initialization_in_progress_suppress_logging = true;
@ -595,7 +597,7 @@ void SetColorConsoleBackendEnabled(bool enabled) {
void FmtLogMessageImpl(Class log_class, Level log_level, const char* filename,
unsigned int line_num, const char* function, fmt::string_view format,
const fmt::format_args& args) {
if (initialization_in_progress_suppress_logging) [[unlikely]] {
if (initialization_in_progress_suppress_logging && log_level < Level::Critical) [[unlikely]] {
return;
}

View File

@ -96,6 +96,7 @@ void LogSettings() {
log_setting("Renderer_SpirvShaderGen", values.spirv_shader_gen.GetValue());
log_setting("Renderer_DisableSpirvOptimizer", values.disable_spirv_optimizer.GetValue());
log_setting("Renderer_Debug", values.renderer_debug.GetValue());
log_setting("Renderer_PicaDebugging", values.pica_debugging.GetValue());
log_setting("Renderer_UseHwShader", values.use_hw_shader.GetValue());
log_setting("Renderer_ShadersAccurateMul", values.shaders_accurate_mul.GetValue());
log_setting("Renderer_UseShaderJit", values.use_shader_jit.GetValue());
@ -103,6 +104,7 @@ void LogSettings() {
log_setting("Renderer_UseIntegerScaling", values.use_integer_scaling.GetValue());
log_setting("Renderer_FrameLimit", values.frame_limit.GetValue());
log_setting("Renderer_VSyncNew", values.use_vsync.GetValue());
log_setting("Renderer_SkipDuplicateFrames", values.use_skip_duplicate_frames.GetValue());
log_setting("Renderer_PostProcessingShader", values.pp_shader_name.GetValue());
log_setting("Renderer_FilterMode", values.filter_mode.GetValue());
log_setting("Renderer_TextureFilter", GetTextureFilterName(values.texture_filter.GetValue()));
@ -215,6 +217,7 @@ void RestoreGlobalState(bool is_powered_on) {
values.use_disk_shader_cache.SetGlobal(true);
values.shaders_accurate_mul.SetGlobal(true);
values.use_vsync.SetGlobal(true);
values.use_skip_duplicate_frames.SetGlobal(true);
values.resolution_factor.SetGlobal(true);
values.use_integer_scaling.SetGlobal(true);
values.frame_limit.SetGlobal(true);

View File

@ -527,6 +527,7 @@ struct Values {
SwitchableSetting<u32> physical_device{0, Keys::physical_device};
Setting<bool> use_gles{false, Keys::use_gles};
Setting<bool> renderer_debug{false, Keys::renderer_debug};
Setting<bool> pica_debugging{false, Keys::pica_debugging};
Setting<bool> dump_command_buffers{false, Keys::dump_command_buffers};
SwitchableSetting<bool> spirv_shader_gen{true, Keys::spirv_shader_gen};
SwitchableSetting<bool> disable_spirv_optimizer{true, Keys::disable_spirv_optimizer};
@ -534,6 +535,7 @@ struct Values {
SwitchableSetting<bool> async_presentation{true, Keys::async_presentation};
SwitchableSetting<bool> use_hw_shader{true, Keys::use_hw_shader};
SwitchableSetting<bool> use_disk_shader_cache{true, Keys::use_disk_shader_cache};
SwitchableSetting<bool> use_skip_duplicate_frames{true, Keys::use_skip_duplicate_frames};
SwitchableSetting<bool> shaders_accurate_mul{true, Keys::shaders_accurate_mul};
#ifdef ANDROID // TODO: Fuck this -OS
SwitchableSetting<bool> use_vsync{false, Keys::use_vsync};
@ -552,7 +554,7 @@ struct Values {
Keys::texture_sampling};
SwitchableSetting<u16, true> delay_game_render_thread_us{0, 0, 65000,
Keys::delay_game_render_thread_us};
SwitchableSetting<bool> simulate_3ds_gpu_timings{true, Keys::simulate_3ds_gpu_timings};
SwitchableSetting<bool> simulate_3ds_gpu_timings{false, Keys::simulate_3ds_gpu_timings};
SwitchableSetting<LayoutOption> layout_option{LayoutOption::Default, Keys::layout_option};
SwitchableSetting<bool> swap_screen{false, Keys::swap_screen};

View File

@ -1,4 +1,4 @@
// Copyright 2022 Citra Emulator Project
// Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
@ -24,13 +24,13 @@ constexpr u32 GetMatchingBitsFromStringLiteral() {
}
template <u32 mask_>
constexpr u32 DepositBits(u32 val) {
constexpr u32 ExtractBits(u32 val) {
u32 mask = mask_;
u32 res = 0;
for (u32 bb = 1; mask; bb += bb) {
u32 neg_mask = 0 - mask;
if (val & bb)
res |= mask & neg_mask;
const u32 lowest_bit = mask & (0 - mask);
if (val & lowest_bit)
res |= bb;
mask &= mask - 1;
}
return res;
@ -40,7 +40,7 @@ template <Common::StringLiteral haystack>
struct MatcherArg {
template <Common::StringLiteral needle>
u32 Get() {
return DepositBits<GetMatchingBitsFromStringLiteral<haystack, needle>()>(instruction);
return ExtractBits<GetMatchingBitsFromStringLiteral<haystack, needle>()>(instruction);
}
u32 instruction;
@ -52,39 +52,95 @@ struct Matcher {
std::function<u64(u32)> fn;
};
// pipeline flush cost for a PC write that program flow prediction cannot handle
// (ARM DDI 0211: refilling the 8-stage pipeline costs 5 to 7 cycles)
constexpr u64 pipeline_flush_cycles = 7;
// Indirect branches through a register other than lr hit the BTAC only when the
// target is stable; charge the average of a fold and a flush
constexpr u64 indirect_branch_cycles = 4;
u64 DataProcessing_imm(auto i) {
if (i.template Get<"d">() == 15) {
return 7;
return pipeline_flush_cycles;
}
return 1;
}
u64 DataProcessing_reg(auto i) {
if (i.template Get<"d">() == 15) {
return 7;
return pipeline_flush_cycles;
}
return 1;
}
u64 DataProcessing_rsr(auto i) {
if (i.template Get<"d">() == 15) {
return 8;
return pipeline_flush_cycles + 1;
}
return 2;
}
u64 LoadStoreSingle_imm(auto) {
return 2;
// MOV (reg) has its own handler: a plain "MOV pc, lr" is a procedure return,
// predicted by the return stack
u64 MovReg(auto i) {
if (i.template Get<"d">() == 15) {
const bool is_return =
i.template Get<"m">() == 14 && i.template Get<"v">() == 0 && i.template Get<"r">() == 0;
return is_return ? 1 : pipeline_flush_cycles;
}
u64 LoadStoreSingle_reg(auto i) {
// TODO: Load PC
if (i.template Get<"u">() == 1 && i.template Get<"r">() == 0 &&
(i.template Get<"v">() == 0 || i.template Get<"v">() == 2)) {
return 2;
return 1;
}
return 4;
// Loads: 1 issue cycle, loads into PC are branches: "LDR pc, [sp...]" is a
// return-stack-predicted procedure return; anything else (jump tables) flushes
u64 Load_imm(auto i) {
if (i.template Get<"t">() == 15) {
return i.template Get<"n">() == 13 ? 1 : pipeline_flush_cycles - 1;
}
return 1;
}
u64 Load_lit(auto i) {
if (i.template Get<"t">() == 15) {
return pipeline_flush_cycles - 1;
}
return 1;
}
u64 Load_reg(auto i) {
if (i.template Get<"t">() == 15) {
return pipeline_flush_cycles - 1;
}
// Scaled register offset requires an extra cycle for the offset shift
return (i.template Get<"v">() == 0 && i.template Get<"r">() == 0) ? 1 : 2;
}
u64 Load_reg_noshift(auto i) {
if (i.template Get<"t">() == 15) {
return pipeline_flush_cycles - 1;
}
return 1;
}
u64 Store_imm(auto) {
return 1;
}
u64 Store_reg(auto i) {
return (i.template Get<"v">() == 0 && i.template Get<"r">() == 0) ? 1 : 2;
}
u64 Store_reg_noshift(auto) {
return 1;
}
// Two registers per cycle over the 64-bit data path, a pop including PC is a
// return-stack-predicted procedure return, so no branch penalty is added
u64 LoadStoreMultiple(auto i) {
// TODO: Load PC
return 1 + std::popcount(i.template Get<"x">()) / 2;
}
// VLDR/VSTR (p=1, w=0): single transfer, 1 cycle. VLDM/VSTM/VPUSH/VPOP: the imm8
// field counts words; two words transfer per cycle
u64 VfpLoadStore(auto i) {
if (i.template Get<"p">() == 0 && i.template Get<"u">() == 0) {
// MCRR/MRRC to the VFP (VMOV to/from a core register pair) share this
// encoding space (P=U=0 is not a valid VFP load/store addressing mode)
return 2;
}
if (i.template Get<"p">() == 1 && i.template Get<"w">() == 0) {
return 1;
}
return 1 + i.template Get<"v">() / 2;
}
#define INST(NAME, BS, CYCLES) \
Matcher{GetMatchingBitsFromStringLiteral<BS, "01">(), \
@ -97,14 +153,27 @@ u64 LoadStoreMultiple(auto i) {
const std::array arm_matchers{
// clang-format off
// Branch instructions
INST("BLX (imm)", "1111101hvvvvvvvvvvvvvvvvvvvvvvvv", 5) // v5
INST("BLX (reg)", "cccc000100101111111111110011mmmm", 6) // v5
INST("B", "cccc1010vvvvvvvvvvvvvvvvvvvvvvvv", 4) // v1
INST("BL", "cccc1011vvvvvvvvvvvvvvvvvvvvvvvv", 4) // v1
INST("BX", "cccc000100101111111111110001mmmm", 5) // v4T
// branch instructions
// direct branches and calls are BTAC/statically predicted (folded when
// predicted; we charge the issue slot), BX lr is a return-stack-predicted
// return; indirect branches through other registers depend on target
// stability
INST("BLX (imm)", "1111101hvvvvvvvvvvvvvvvvvvvvvvvv", 1) // v5
INST("BLX (reg)", "cccc000100101111111111110011mmmm", 2) // v5
INST("B", "cccc1010vvvvvvvvvvvvvvvvvvvvvvvv", 1) // v1
INST("BL", "cccc1011vvvvvvvvvvvvvvvvvvvvvvvv", 1) // v1
INST("BX", "cccc000100101111111111110001mmmm", (i.template Get<"m">() == 14 ? 1 : indirect_branch_cycles)) // v4T
INST("BXJ", "cccc000100101111111111110010mmmm", 1) // v5J
// floating-point (VFP11) instructions
// must precede the generic coprocessor entries below, Bit 8 selects double
// precision. Divide/sqrt block the non-pipelined DS unit for their full
// throughput; other data-processing operations are pipelined
INST("VDIV", "cccc11101-00--------101z-0-0----", (i.template Get<"z">() ? 29 : 15)) // VFPv2
INST("VSQRT", "cccc11101-110001----101z11-0----", (i.template Get<"z">() ? 29 : 15)) // VFPv2
INST("VFP DP op", "cccc1110------------101z---0----", (i.template Get<"z">() ? 2 : 1)) // VFPv2
INST("VFP load/store", "cccc110pu-w---------101-vvvvvvvv", VfpLoadStore(i)) // VFPv2
// Coprocessor instructions
INST("CDP", "cccc1110ooooNNNNDDDDppppooo0MMMM", 1) // v2 (CDP2: v5)
INST("LDC", "cccc110pudw1nnnnDDDDppppvvvvvvvv", 1) // v2 (LDC2: v5)
@ -137,7 +206,7 @@ const std::array arm_matchers{
INST("EOR (reg)", "cccc0000001Snnnnddddvvvvvrr0mmmm", DataProcessing_reg(i)) // v1
INST("EOR (rsr)", "cccc0000001Snnnnddddssss0rr1mmmm", DataProcessing_rsr(i)) // v1
INST("MOV (imm)", "cccc0011101S0000ddddrrrrvvvvvvvv", DataProcessing_imm(i)) // v1
INST("MOV (reg)", "cccc0001101S0000ddddvvvvvrr0mmmm", DataProcessing_reg(i)) // v1
INST("MOV (reg)", "cccc0001101S0000ddddvvvvvrr0mmmm", MovReg(i)) // v1
INST("MOV (rsr)", "cccc0001101S0000ddddssss0rr1mmmm", DataProcessing_rsr(i)) // v1
INST("MVN (imm)", "cccc0011111S0000ddddrrrrvvvvvvvv", DataProcessing_imm(i)) // v1
INST("MVN (reg)", "cccc0001111S0000ddddvvvvvrr0mmmm", DataProcessing_reg(i)) // v1
@ -213,32 +282,32 @@ const std::array arm_matchers{
INST("STRBT (A2)", "----0110-110---------------0----", 1) // v1
INST("STRT (A1)", "----0100-010--------------------", 1) // v1
INST("STRT (A2)", "----0110-010---------------0----", 1) // v1
INST("LDR (lit)", "cccc0101u0011111ttttvvvvvvvvvvvv", LoadStoreSingle_imm(i)) // v1
INST("LDR (imm)", "cccc010pu0w1nnnnttttvvvvvvvvvvvv", LoadStoreSingle_imm(i)) // v1
INST("LDR (reg)", "cccc011pu0w1nnnnttttvvvvvrr0mmmm", LoadStoreSingle_reg(i)) // v1
INST("LDRB (lit)", "cccc0101u1011111ttttvvvvvvvvvvvv", LoadStoreSingle_imm(i)) // v1
INST("LDRB (imm)", "cccc010pu1w1nnnnttttvvvvvvvvvvvv", LoadStoreSingle_imm(i)) // v1
INST("LDRB (reg)", "cccc011pu1w1nnnnttttvvvvvrr0mmmm", LoadStoreSingle_reg(i)) // v1
INST("LDRD (lit)", "cccc0001u1001111ttttvvvv1101vvvv", LoadStoreSingle_imm(i)) // v5E
INST("LDRD (imm)", "cccc000pu1w0nnnnttttvvvv1101vvvv", LoadStoreSingle_imm(i)) // v5E
INST("LDRD (reg)", "cccc000pu0w0nnnntttt00001101mmmm", LoadStoreSingle_reg(i)) // v5E
INST("LDRH (lit)", "cccc000pu1w11111ttttvvvv1011vvvv", LoadStoreSingle_imm(i)) // v4
INST("LDRH (imm)", "cccc000pu1w1nnnnttttvvvv1011vvvv", LoadStoreSingle_imm(i)) // v4
INST("LDRH (reg)", "cccc000pu0w1nnnntttt00001011mmmm", LoadStoreSingle_reg(i)) // v4
INST("LDRSB (lit)", "cccc0001u1011111ttttvvvv1101vvvv", LoadStoreSingle_imm(i)) // v4
INST("LDRSB (imm)", "cccc000pu1w1nnnnttttvvvv1101vvvv", LoadStoreSingle_imm(i)) // v4
INST("LDRSB (reg)", "cccc000pu0w1nnnntttt00001101mmmm", LoadStoreSingle_reg(i)) // v4
INST("LDRSH (lit)", "cccc0001u1011111ttttvvvv1111vvvv", LoadStoreSingle_imm(i)) // v4
INST("LDRSH (imm)", "cccc000pu1w1nnnnttttvvvv1111vvvv", LoadStoreSingle_imm(i)) // v4
INST("LDRSH (reg)", "cccc000pu0w1nnnntttt00001111mmmm", LoadStoreSingle_reg(i)) // v4
INST("STR (imm)", "cccc010pu0w0nnnnttttvvvvvvvvvvvv", LoadStoreSingle_imm(i)) // v1
INST("STR (reg)", "cccc011pu0w0nnnnttttvvvvvrr0mmmm", LoadStoreSingle_reg(i)) // v1
INST("STRB (imm)", "cccc010pu1w0nnnnttttvvvvvvvvvvvv", LoadStoreSingle_imm(i)) // v1
INST("STRB (reg)", "cccc011pu1w0nnnnttttvvvvvrr0mmmm", LoadStoreSingle_reg(i)) // v1
INST("STRD (imm)", "cccc000pu1w0nnnnttttvvvv1111vvvv", LoadStoreSingle_imm(i)) // v5E
INST("STRD (reg)", "cccc000pu0w0nnnntttt00001111mmmm", LoadStoreSingle_reg(i)) // v5E
INST("STRH (imm)", "cccc000pu1w0nnnnttttvvvv1011vvvv", LoadStoreSingle_imm(i)) // v4
INST("STRH (reg)", "cccc000pu0w0nnnntttt00001011mmmm", LoadStoreSingle_reg(i)) // v4
INST("LDR (lit)", "cccc0101u0011111ttttvvvvvvvvvvvv", Load_lit(i)) // v1
INST("LDR (imm)", "cccc010pu0w1nnnnttttvvvvvvvvvvvv", Load_imm(i)) // v1
INST("LDR (reg)", "cccc011pu0w1nnnnttttvvvvvrr0mmmm", Load_reg(i)) // v1
INST("LDRB (lit)", "cccc0101u1011111ttttvvvvvvvvvvvv", Load_lit(i)) // v1
INST("LDRB (imm)", "cccc010pu1w1nnnnttttvvvvvvvvvvvv", Load_imm(i)) // v1
INST("LDRB (reg)", "cccc011pu1w1nnnnttttvvvvvrr0mmmm", Load_reg(i)) // v1
INST("LDRD (lit)", "cccc0001u1001111ttttvvvv1101vvvv", 1) // v5E
INST("LDRD (imm)", "cccc000pu1w0nnnnttttvvvv1101vvvv", 1) // v5E
INST("LDRD (reg)", "cccc000pu0w0nnnntttt00001101mmmm", 1) // v5E
INST("LDRH (lit)", "cccc000pu1w11111ttttvvvv1011vvvv", Load_reg_noshift(i)) // v4
INST("LDRH (imm)", "cccc000pu1w1nnnnttttvvvv1011vvvv", Load_reg_noshift(i)) // v4
INST("LDRH (reg)", "cccc000pu0w1nnnntttt00001011mmmm", Load_reg_noshift(i)) // v4
INST("LDRSB (lit)", "cccc0001u1011111ttttvvvv1101vvvv", Load_reg_noshift(i)) // v4
INST("LDRSB (imm)", "cccc000pu1w1nnnnttttvvvv1101vvvv", Load_reg_noshift(i)) // v4
INST("LDRSB (reg)", "cccc000pu0w1nnnntttt00001101mmmm", Load_reg_noshift(i)) // v4
INST("LDRSH (lit)", "cccc0001u1011111ttttvvvv1111vvvv", Load_reg_noshift(i)) // v4
INST("LDRSH (imm)", "cccc000pu1w1nnnnttttvvvv1111vvvv", Load_reg_noshift(i)) // v4
INST("LDRSH (reg)", "cccc000pu0w1nnnntttt00001111mmmm", Load_reg_noshift(i)) // v4
INST("STR (imm)", "cccc010pu0w0nnnnttttvvvvvvvvvvvv", Store_imm(i)) // v1
INST("STR (reg)", "cccc011pu0w0nnnnttttvvvvvrr0mmmm", Store_reg(i)) // v1
INST("STRB (imm)", "cccc010pu1w0nnnnttttvvvvvvvvvvvv", Store_imm(i)) // v1
INST("STRB (reg)", "cccc011pu1w0nnnnttttvvvvvrr0mmmm", Store_reg(i)) // v1
INST("STRD (imm)", "cccc000pu1w0nnnnttttvvvv1111vvvv", 1) // v5E
INST("STRD (reg)", "cccc000pu0w0nnnntttt00001111mmmm", 1) // v5E
INST("STRH (imm)", "cccc000pu1w0nnnnttttvvvv1011vvvv", Store_reg_noshift(i)) // v4
INST("STRH (reg)", "cccc000pu0w0nnnntttt00001011mmmm", Store_reg_noshift(i)) // v4
// Load/Store Multiple instructions
INST("LDM", "cccc100010w1nnnnxxxxxxxxxxxxxxxx", LoadStoreMultiple(i)) // v1
@ -370,7 +439,7 @@ const std::array arm_matchers{
// clang-format on
};
const std::array thumb_matchers{
const std::array thumb16_matchers{
// clang-format off
// Shift (immediate) add, subtract, move and compare instructions
@ -400,7 +469,7 @@ const std::array thumb_matchers{
INST("CMP (reg, T1)", "0100001010mmmnnn", 1)
INST("CMN (reg)", "0100001011mmmnnn", 1)
INST("ORR (reg)", "0100001100mmmddd", 1)
INST("MUL (reg)", "0100001101nnnddd", 1)
INST("MUL (reg)", "0100001101nnnddd", 2)
INST("BIC (reg)", "0100001110mmmddd", 1)
INST("MVN (reg)", "0100001111mmmddd", 1)
@ -410,23 +479,23 @@ const std::array thumb_matchers{
INST("MOV (reg)", "01000110Dmmmmddd", 1) // v4T, Low regs: v6
// Store/Load single data item instructions
INST("LDR (literal)", "01001tttvvvvvvvv", 2)
INST("STR (reg)", "0101000mmmnnnttt", 2)
INST("STRH (reg)", "0101001mmmnnnttt", 2)
INST("STRB (reg)", "0101010mmmnnnttt", 2)
INST("LDRSB (reg)", "0101011mmmnnnttt", 2)
INST("LDR (reg)", "0101100mmmnnnttt", 2)
INST("LDRH (reg)", "0101101mmmnnnttt", 2)
INST("LDRB (reg)", "0101110mmmnnnttt", 2)
INST("LDRSH (reg)", "0101111mmmnnnttt", 2)
INST("STR (imm, T1)", "01100vvvvvnnnttt", 2)
INST("LDR (imm, T1)", "01101vvvvvnnnttt", 2)
INST("STRB (imm)", "01110vvvvvnnnttt", 2)
INST("LDRB (imm)", "01111vvvvvnnnttt", 2)
INST("STRH (imm)", "10000vvvvvnnnttt", 2)
INST("LDRH (imm)", "10001vvvvvnnnttt", 2)
INST("STR (imm, T2)", "10010tttvvvvvvvv", 2)
INST("LDR (imm, T2)", "10011tttvvvvvvvv", 2)
INST("LDR (literal)", "01001tttvvvvvvvv", 1)
INST("STR (reg)", "0101000mmmnnnttt", 1)
INST("STRH (reg)", "0101001mmmnnnttt", 1)
INST("STRB (reg)", "0101010mmmnnnttt", 1)
INST("LDRSB (reg)", "0101011mmmnnnttt", 1)
INST("LDR (reg)", "0101100mmmnnnttt", 1)
INST("LDRH (reg)", "0101101mmmnnnttt", 1)
INST("LDRB (reg)", "0101110mmmnnnttt", 1)
INST("LDRSH (reg)", "0101111mmmnnnttt", 1)
INST("STR (imm, T1)", "01100vvvvvnnnttt", 1)
INST("LDR (imm, T1)", "01101vvvvvnnnttt", 1)
INST("STRB (imm)", "01110vvvvvnnnttt", 1)
INST("LDRB (imm)", "01111vvvvvnnnttt", 1)
INST("STRH (imm)", "10000vvvvvnnnttt", 1)
INST("LDRH (imm)", "10001vvvvvnnnttt", 1)
INST("STR (imm, T2)", "10010tttvvvvvvvv", 1)
INST("LDR (imm, T2)", "10011tttvvvvvvvv", 1)
// Generate relative address instructions
INST("ADR", "10100dddvvvvvvvv", 1)
@ -456,14 +525,22 @@ const std::array thumb_matchers{
INST("LDMIA", "11001nnnxxxxxxxx", LoadStoreMultiple(i))
// Branch instructions
INST("BX", "010001110mmmm000", 5) // v4T
INST("BLX (reg)", "010001111mmmm000", 6) // v5T
INST("BX", "010001110mmmm000", (i.template Get<"m">() == 14 ? 1 : indirect_branch_cycles)) // v4T
INST("BLX (reg)", "010001111mmmm000", 2) // v5T
INST("UDF", "11011110--------", 8)
INST("SVC", "11011111xxxxxxxx", 8)
INST("B (T1)", "1101ccccvvvvvvvv", 4)
INST("B (T2)", "11100vvvvvvvvvvv", 4)
INST("BL (imm)", "11110Svvvvvvvvvv11j1jvvvvvvvvvvv", 4) // v4T
INST("BLX (imm)", "11110Svvvvvvvvvv11j0jvvvvvvvvvvv", 5) // v5T
INST("B (T1)", "1101ccccvvvvvvvv", 1)
INST("B (T2)", "11100vvvvvvvvvvv", 1)
// clang-format on
};
const std::array thumb32_matchers{
// clang-format off
// Branch instructions (the only 32-bit encodings on ARMv6: BL/BLX prefix pairs)
INST("BL (imm)", "11110Svvvvvvvvvv11j1jvvvvvvvvvvv", 1) // v4T
INST("BLX (imm)", "11110Svvvvvvvvvv11j0jvvvvvvvvvvv", 1) // v5T
// clang-format on
};
@ -473,14 +550,30 @@ const std::array thumb_matchers{
namespace Core {
u64 TicksForInstruction(bool is_thumb, u32 instruction) {
if (is_thumb) {
return 1;
}
const auto matches_instruction = [instruction](const auto& matcher) {
return (instruction & matcher.mask) == matcher.expect;
};
if (is_thumb) {
// dynarmic passes 16-bit Thumb instructions zero-extended and 32-bit ones
// (BL/BLX prefix+suffix pairs on ARMv6) with the first halfword in the
// upper 16 bits
if ((instruction >> 16) != 0) {
auto iter =
std::find_if(thumb32_matchers.begin(), thumb32_matchers.end(), matches_instruction);
if (iter != thumb32_matchers.end()) {
return iter->fn(instruction);
}
return 1;
}
auto iter =
std::find_if(thumb16_matchers.begin(), thumb16_matchers.end(), matches_instruction);
if (iter != thumb16_matchers.end()) {
return iter->fn(instruction);
}
return 1;
}
auto iter = std::find_if(arm_matchers.begin(), arm_matchers.end(), matches_instruction);
if (iter != arm_matchers.end()) {
return iter->fn(instruction);

View File

@ -762,8 +762,13 @@ void System::Reset() {
void System::ApplySettings() {
#ifdef ENABLE_GDBSTUB
if (override_gdb_port != -1) {
GDBStub::SetServerPort(override_gdb_port);
GDBStub::ToggleServer(true);
} else {
GDBStub::SetServerPort(Settings::values.gdbstub_port.GetValue());
GDBStub::ToggleServer(Settings::values.use_gdbstub.GetValue());
}
#endif
if (gpu) {

View File

@ -421,6 +421,10 @@ public:
void DebugUnscheduleAllThreadsFromFrontend(bool unschedule);
void SetGDBPortOverride(int port) {
override_gdb_port = port;
}
private:
/**
* Initialize the emulated system.
@ -531,6 +535,7 @@ private:
std::function<void()> info_led_color_changed;
bool debug_next_process;
int override_gdb_port = -1;
friend class boost::serialization::access;
template <typename Archive>

View File

@ -444,6 +444,10 @@ std::string GetTitlePath(Service::FS::MediaType media_type, u64 tid);
*/
std::string GetMediaTitlePath(Service::FS::MediaType media_type);
Result GetTitleInfoFromList(Core::System& system, std::span<const u64> title_id_list,
Service::FS::MediaType media_type,
std::vector<TitleInfo>& title_info_out);
/**
* Uninstalls the specified title.
* @param media_type the storage medium the title is installed to

View File

@ -105,7 +105,7 @@ static constexpr std::array<AppletTitleData, NumApplets> applet_titleids = {{
// TODO(Subv): Fill in the rest of the titleids
}};
static u64 GetTitleIdForApplet(AppletId id, u32 region_value) {
u64 GetTitleIdForApplet(AppletId id, u32 region_value) {
ASSERT_MSG(id != AppletId::None, "Invalid applet id");
auto itr = std::find_if(applet_titleids.begin(), applet_titleids.end(),
@ -154,11 +154,13 @@ static u64 ConvertTitleID(Core::System& system, u64 base_title_id) {
}
static bool IsSystemAppletId(AppletId applet_id) {
return (static_cast<u32>(applet_id) & static_cast<u32>(AppletId::AnySystemApplet)) != 0;
return (static_cast<u32>(applet_id) & static_cast<u32>(AppletId::TypeMask)) ==
static_cast<u32>(AppletId::AnySystemApplet);
}
static bool IsApplicationAppletId(AppletId applet_id) {
return (static_cast<u32>(applet_id) & static_cast<u32>(AppletId::Application)) != 0;
return (static_cast<u32>(applet_id) & static_cast<u32>(AppletId::TypeMask)) ==
static_cast<u32>(AppletId::Application);
}
AppletManager::AppletSlot AppletManager::GetAppletSlotFromId(AppletId id) {
@ -272,8 +274,9 @@ void AppletManager::CancelAndSendParameter(const MessageParameter& parameter) {
parameter.sender_id);
if (parameter.buffer.size() >= sizeof(CaptureBufferInfo)) {
SetCaptureInfo(parameter.buffer);
SetCaptureInfoSuspendedApp(parameter.buffer);
CaptureFrameBuffers();
TransferCapturedFramebuffers();
}
next_parameter->sender_id = parameter.destination_id;
@ -359,6 +362,15 @@ bool AppletManager::CancelParameter(bool check_sender, AppletId sender_appid, bo
return cancellation_success;
}
void AppletManager::MapProgramIdForDebug(AppletId app_id, u64 title_id, FS::MediaType media_type) {
auto slot = GetAppletSlotFromId(app_id);
if (slot != AppletSlot::Error) {
auto applet_slot = GetAppletSlot(slot);
applet_slot->title_id = title_id;
applet_slot->media_type = media_type;
}
}
ResultVal<AppletManager::GetLockHandleResult> AppletManager::GetLockHandle(
AppletAttributes attributes) {
auto corrected_attributes = attributes;
@ -605,8 +617,6 @@ Result AppletManager::PrepareToStartLibraryApplet(AppletId applet_id) {
last_library_launcher_slot = active_slot;
last_prepared_library_applet = applet_id;
capture_buffer_info.reset();
if (Settings::values.lle_applets) {
bool is_setup = system.GetAppLoader().DoingInitialSetup();
auto cfg = Service::CFG::GetModule(system);
@ -627,6 +637,8 @@ Result AppletManager::PrepareToStartLibraryApplet(AppletId applet_id) {
LOG_DEBUG(Service_APT, "Creating HLE applet {:03X} with parent {:03X}", applet_id, parent);
return CreateHLEApplet(applet_id, parent, false);
}
capture_info.reset();
}
Result AppletManager::PreloadLibraryApplet(AppletId applet_id) {
@ -726,6 +738,14 @@ Result AppletManager::CloseLibraryApplet(std::shared_ptr<Kernel::Object> object,
SendParameter(param);
}
// TODO: This doesn't work correctly when opening the
// theme shop and an error showwing, because for
// some reason the theme shop is an application.
if (last_library_launcher_slot == AppletSlot::SystemApplet &&
GetAppletSlotFromId(AppletId::Application) != AppletSlot::Error) {
TransferCapturedFramebuffers();
}
return ResultSuccess;
}
@ -960,7 +980,7 @@ Result AppletManager::PrepareToJumpToHomeMenu() {
last_jump_to_home_slot = active_slot;
capture_buffer_info.reset();
capture_info.reset();
if (last_jump_to_home_slot == AppletSlot::Application) {
EnsureHomeMenuLoaded();
@ -1454,7 +1474,7 @@ Result AppletManager::PrepareToStartApplication(u64 title_id, FS::MediaType medi
app_start_parameters->next_title_id = title_id;
app_start_parameters->next_media_type = media_type;
capture_buffer_info.reset();
capture_info.reset();
app_jump_parameters.Invalidate();
@ -1621,8 +1641,8 @@ static u32 GetDisplayBufferModePixelSize(DisplayBufferMode mode) {
}
}
static void CaptureFrameBuffer(Core::System& system, u32 capture_offset, VAddr src, u32 height,
DisplayBufferMode mode) {
static void CaptureFrameBuffer(Core::System& system, u32 capture_offset, std::span<u8> dst,
VAddr src, u32 height, DisplayBufferMode mode) {
const auto bpp = GetDisplayBufferModePixelSize(mode);
if (bpp == 0) {
return;
@ -1631,15 +1651,8 @@ static void CaptureFrameBuffer(Core::System& system, u32 capture_offset, VAddr s
system.Memory().RasterizerFlushVirtualRegion(src, GSP::FRAMEBUFFER_WIDTH * height * bpp,
Memory::FlushMode::Flush);
// Address in VRAM that APT copies framebuffer captures to.
constexpr VAddr screen_capture_base_vaddr = Memory::VRAM_VADDR + 0x500000;
const auto dst_vaddr = screen_capture_base_vaddr + capture_offset;
auto dst_ptr = system.Memory().GetPointer(dst_vaddr);
if (!dst_ptr) {
LOG_ERROR(Service_APT,
"Could not retrieve framebuffer capture destination buffer, skipping screen.");
return;
}
// APT captures the framebuffer to a copy on its own bss.
u8* dst_ptr = dst.data() + capture_offset;
const auto src_ptr = system.Memory().GetPointer(src);
if (!src_ptr) {
@ -1653,28 +1666,40 @@ static void CaptureFrameBuffer(Core::System& system, u32 capture_offset, VAddr s
const auto dst_offset = VideoCore::GetMortonOffset(x, y, bpp) +
(y & ~7) * GSP::FRAMEBUFFER_WIDTH_POW2 * bpp;
const auto src_offset = bpp * (GSP::FRAMEBUFFER_WIDTH * y + x);
ASSERT(capture_offset + dst_offset + bpp <= dst.size());
std::memcpy(dst_ptr + dst_offset, src_ptr + src_offset, bpp);
}
}
system.Memory().RasterizerFlushVirtualRegion(
dst_vaddr, GSP::FRAMEBUFFER_WIDTH_POW2 * height * bpp, Memory::FlushMode::Invalidate);
}
void AppletManager::CaptureFrameBuffers() {
CaptureFrameBuffer(system, capture_info->bottom_screen_left_offset,
GSP::FRAMEBUFFER_SAVE_AREA_BOTTOM, GSP::BOTTOM_FRAMEBUFFER_HEIGHT,
capture_info->bottom_screen_format);
CaptureFrameBuffer(system, capture_info->top_screen_left_offset,
GSP::FRAMEBUFFER_SAVE_AREA_TOP_LEFT, GSP::TOP_FRAMEBUFFER_HEIGHT,
capture_info->top_screen_format);
if (capture_info->is_3d) {
CaptureFrameBuffer(system, capture_info->top_screen_right_offset,
GSP::FRAMEBUFFER_SAVE_AREA_TOP_RIGHT, GSP::TOP_FRAMEBUFFER_HEIGHT,
capture_info->top_screen_format);
CaptureFrameBuffer(system, capture_info_suspended_app->bottom_screen_left_offset,
framebuffer_backup, GSP::FRAMEBUFFER_SAVE_AREA_BOTTOM,
GSP::BOTTOM_FRAMEBUFFER_HEIGHT,
capture_info_suspended_app->bottom_screen_format);
CaptureFrameBuffer(system, capture_info_suspended_app->top_screen_left_offset,
framebuffer_backup, GSP::FRAMEBUFFER_SAVE_AREA_TOP_LEFT,
GSP::TOP_FRAMEBUFFER_HEIGHT, capture_info_suspended_app->top_screen_format);
if (capture_info_suspended_app->is_3d) {
CaptureFrameBuffer(system, capture_info_suspended_app->top_screen_right_offset,
framebuffer_backup, GSP::FRAMEBUFFER_SAVE_AREA_TOP_RIGHT,
GSP::TOP_FRAMEBUFFER_HEIGHT,
capture_info_suspended_app->top_screen_format);
}
}
void AppletManager::TransferCapturedFramebuffers() {
constexpr u32 VRAM_TRANSFER_OFFSET = 0x500000;
const VAddr dst_vaddr = Memory::VRAM_VADDR + VRAM_TRANSFER_OFFSET;
auto dst_ptr = system.Memory().GetPointer(dst_vaddr);
memcpy(dst_ptr, framebuffer_backup.data(), framebuffer_backup.size());
system.Memory().RasterizerFlushVirtualRegion(dst_vaddr, framebuffer_backup.size(),
Memory::FlushMode::Invalidate);
}
void AppletManager::LoadInputDevices() {
home_button = Input::CreateDevice<Input::ButtonDevice>(
Settings::values.current_input_profile.buttons[Settings::NativeButton::Home]);

View File

@ -101,8 +101,11 @@ enum class AppletId : u32 {
Mint2 = 0x407,
Extrapad2 = 0x408,
Memolib2 = 0x409,
TypeMask = 0xF00,
};
u64 GetTitleIdForApplet(AppletId id, u32 region_value);
/// Application Old/New 3DS target platforms
enum class TargetPlatform : u8 {
Old3ds = 0,
@ -288,6 +291,8 @@ public:
bool CancelParameter(bool check_sender, AppletId sender_appid, bool check_receiver,
AppletId receiver_appid);
void MapProgramIdForDebug(AppletId app_id, u64 title_id, FS::MediaType media_type);
struct GetLockHandleResult {
AppletAttributes corrected_attributes;
u32 state;
@ -362,35 +367,36 @@ public:
deliver_arg = std::move(arg);
}
std::vector<u8> GetCaptureInfo() {
std::vector<u8> GetCaptureInfoSuspendedApp() {
std::vector<u8> buffer;
if (capture_info) {
if (capture_info_suspended_app) {
buffer.resize(sizeof(CaptureBufferInfo));
std::memcpy(buffer.data(), &capture_info.get(), sizeof(CaptureBufferInfo));
std::memcpy(buffer.data(), &capture_info_suspended_app.get(),
sizeof(CaptureBufferInfo));
}
return buffer;
}
void SetCaptureInfo(std::vector<u8> buffer) {
void SetCaptureInfoSuspendedApp(std::vector<u8> buffer) {
ASSERT_MSG(buffer.size() >= sizeof(CaptureBufferInfo), "CaptureBufferInfo is too small.");
capture_info.emplace();
std::memcpy(&capture_info.get(), buffer.data(), sizeof(CaptureBufferInfo));
capture_info_suspended_app.emplace();
std::memcpy(&capture_info_suspended_app.get(), buffer.data(), sizeof(CaptureBufferInfo));
}
std::vector<u8> ReceiveCaptureBufferInfo() {
std::vector<u8> buffer;
if (capture_buffer_info) {
if (capture_info) {
buffer.resize(sizeof(CaptureBufferInfo));
std::memcpy(buffer.data(), &capture_buffer_info.get(), sizeof(CaptureBufferInfo));
capture_buffer_info.reset();
std::memcpy(buffer.data(), &capture_info.get(), sizeof(CaptureBufferInfo));
capture_info.reset();
}
return buffer;
}
void SendCaptureBufferInfo(std::vector<u8> buffer) {
ASSERT_MSG(buffer.size() >= sizeof(CaptureBufferInfo), "CaptureBufferInfo is too small.");
capture_buffer_info.emplace();
std::memcpy(&capture_buffer_info.get(), buffer.data(), sizeof(CaptureBufferInfo));
capture_info.emplace();
std::memcpy(&capture_info.get(), buffer.data(), sizeof(CaptureBufferInfo));
}
Result PrepareToStartApplication(u64 title_id, FS::MediaType media_type);
@ -446,8 +452,11 @@ private:
boost::optional<SysMenuArg> sys_menu_arg{};
u64 home_menu_tid_to_start{};
boost::optional<CaptureBufferInfo> capture_info_suspended_app;
boost::optional<CaptureBufferInfo> capture_info;
boost::optional<CaptureBufferInfo> capture_buffer_info;
static constexpr size_t FRAMEBUFFER_BACKUP_SIZE = 0xE7000;
std::vector<u8> framebuffer_backup = std::vector<u8>(FRAMEBUFFER_BACKUP_SIZE);
static constexpr std::size_t NumAppletSlot = 4;
@ -550,6 +559,7 @@ private:
void EnsureHomeMenuLoaded();
void CaptureFrameBuffers();
void TransferCapturedFramebuffers();
Result CreateHLEApplet(AppletId id, AppletId parent, bool preload);
void HLEAppletUpdateEvent(std::uintptr_t user_data, s64 cycles_late);
@ -566,8 +576,9 @@ private:
ar & deliver_arg;
ar & sys_menu_arg;
ar & home_menu_tid_to_start;
ar & capture_info_suspended_app;
ar & capture_info;
ar & capture_buffer_info;
ar & framebuffer_backup;
ar & active_slot;
ar & last_library_launcher_slot;
ar & last_prepared_library_applet;
@ -579,7 +590,6 @@ private:
ar & application_close_target;
ar & new_3ds_mode_blocked;
ar & lock;
ar & capture_info;
ar & applet_slots;
ar & library_applet_closing_command;
ar & next_app_mediatype;

View File

@ -574,6 +574,28 @@ void Module::APTInterface::CancelParameter(Kernel::HLERequestContext& ctx) {
receiver_appid));
}
void Module::APTInterface::MapProgramIdForDebug(Kernel::HLERequestContext& ctx) {
// This function got stubbed at some point and no longer
// does anything on real hardware. It is implemented here
// so that emulated applications can take advantage of it.
IPC::RequestParser rp(ctx);
const auto app_id = rp.PopEnum<AppletId>();
u64 title_id = rp.Pop<u64>();
// NOTE: Real hardware forces the media type to nand.
// To allow better control of this service call,
// treat the highest byte of the title ID as the
// media type instead.
FS::MediaType media_type = static_cast<FS::MediaType>(title_id >> 56);
title_id &= ~0xFF00000000000000ULL;
apt->applet_manager->MapProgramIdForDebug(app_id, title_id, media_type);
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
rb.Push(ResultSuccess); // No error
}
void Module::APTInterface::PrepareToDoApplicationJump(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx);
auto flags = rp.PopEnum<ApplicationJumpFlags>();
@ -1101,7 +1123,7 @@ void Module::APTInterface::GetCaptureInfo(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_APT, "called");
auto screen_capture_buffer = apt->applet_manager->GetCaptureInfo();
auto screen_capture_buffer = apt->applet_manager->GetCaptureInfoSuspendedApp();
auto real_size = std::min(static_cast<u32>(screen_capture_buffer.size()), size);
screen_capture_buffer.resize(size);
@ -1410,6 +1432,58 @@ void Module::APTInterface::Reboot(Kernel::HLERequestContext& ctx) {
rb.Push(ResultSuccess);
}
void Module::APTInterface::GetAppletProgramInfo(Kernel::HLERequestContext& ctx) {
union {
u32 raw;
BitField<0, 1, u32> mediatype_nand;
BitField<1, 1, u32> mediatype_sdmc;
BitField<2, 1, u32> mediatype_gamecard;
BitField<4, 1, u32> is_appid;
BitField<5, 1, u32> not_appid_must_set;
BitField<8, 1, u32> is_system_app;
} flags;
IPC::RequestParser rp(ctx);
u32 program_id = rp.Pop<u32>();
flags.raw = rp.Pop<u32>();
IPC::RequestBuilder rb = rp.MakeBuilder(2, 0);
u64 title_id;
if (flags.is_appid) {
auto cfg = Service::CFG::GetModule(apt->system);
title_id =
GetTitleIdForApplet(static_cast<AppletId>(program_id), cfg->GetRegionValue(true));
} else {
if (!flags.not_appid_must_set) {
rb.Push(ResultUnknown); // TODO: Find proper error code
rb.Push(0);
return;
} else {
u32 tid_high = flags.is_system_app ? 0x00040010 : 0x00040000;
title_id = static_cast<u64>(tid_high) << 32 | program_id;
}
}
std::vector<Service::AM::TitleInfo> info;
Result res = ResultUnknown; // TODO: Find proper error code
if (res.IsError() && flags.mediatype_nand) {
res = Service::AM::GetTitleInfoFromList(apt->system, std::span<u64>(&title_id, 1),
FS::MediaType::NAND, info);
}
if (res.IsError() && flags.mediatype_sdmc) {
res = Service::AM::GetTitleInfoFromList(apt->system, std::span<u64>(&title_id, 1),
FS::MediaType::SDMC, info);
}
if (res.IsError() && flags.mediatype_gamecard) {
res = Service::AM::GetTitleInfoFromList(apt->system, std::span<u64>(&title_id, 1),
FS::MediaType::GameCard, info);
}
rb.Push(res);
rb.Push(res.IsSuccess() ? info[0].version : 0);
}
void Module::APTInterface::HardwareResetAsync(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx);

View File

@ -376,6 +376,8 @@ public:
*/
void CancelParameter(Kernel::HLERequestContext& ctx);
void MapProgramIdForDebug(Kernel::HLERequestContext& ctx);
/**
* APT::PrepareToStartApplication service function. When the input title-info programID is
* zero, NS will load the actual program ID via AMNet:GetTitleIDList. After doing some
@ -1007,6 +1009,8 @@ public:
*/
void Reboot(Kernel::HLERequestContext& ctx);
void GetAppletProgramInfo(Kernel::HLERequestContext& ctx);
/**
* APT::HardwareResetAsync service function.
* Outputs:

View File

@ -27,7 +27,7 @@ APT_A::APT_A(std::shared_ptr<Module> apt)
{0x000E, &APT_A::GlanceParameter, "GlanceParameter"},
{0x000F, &APT_A::CancelParameter, "CancelParameter"},
{0x0010, nullptr, "DebugFunc"},
{0x0011, nullptr, "MapProgramIdForDebug"},
{0x0011, &APT_A::MapProgramIdForDebug, "MapProgramIdForDebug"},
{0x0012, nullptr, "SetHomeMenuAppletIdForDebug"},
{0x0013, nullptr, "GetPreparationState"},
{0x0014, nullptr, "SetPreparationState"},
@ -87,7 +87,7 @@ APT_A::APT_A(std::shared_ptr<Module> apt)
{0x004A, &APT_A::GetCaptureInfo, "GetCaptureInfo"},
{0x004B, &APT_A::AppletUtility, "AppletUtility"},
{0x004C, nullptr, "SetFatalErrDispMode"},
{0x004D, nullptr, "GetAppletProgramInfo"},
{0x004D, &APT_A::GetAppletProgramInfo, "GetAppletProgramInfo"},
{0x004E, &APT_A::HardwareResetAsync, "HardwareResetAsync"},
{0x004F, &APT_A::SetAppCpuTimeLimit, "SetAppCpuTimeLimit"},
{0x0050, &APT_A::GetAppCpuTimeLimit, "GetAppCpuTimeLimit"},

View File

@ -27,7 +27,7 @@ APT_S::APT_S(std::shared_ptr<Module> apt)
{0x000E, &APT_S::GlanceParameter, "GlanceParameter"},
{0x000F, &APT_S::CancelParameter, "CancelParameter"},
{0x0010, nullptr, "DebugFunc"},
{0x0011, nullptr, "MapProgramIdForDebug"},
{0x0011, &APT_S::MapProgramIdForDebug, "MapProgramIdForDebug"},
{0x0012, nullptr, "SetHomeMenuAppletIdForDebug"},
{0x0013, nullptr, "GetPreparationState"},
{0x0014, nullptr, "SetPreparationState"},
@ -87,7 +87,7 @@ APT_S::APT_S(std::shared_ptr<Module> apt)
{0x004A, &APT_S::GetCaptureInfo, "GetCaptureInfo"},
{0x004B, &APT_S::AppletUtility, "AppletUtility"},
{0x004C, nullptr, "SetFatalErrDispMode"},
{0x004D, nullptr, "GetAppletProgramInfo"},
{0x004D, &APT_S::GetAppletProgramInfo, "GetAppletProgramInfo"},
{0x004E, &APT_S::HardwareResetAsync, "HardwareResetAsync"},
{0x004F, &APT_S::SetAppCpuTimeLimit, "SetAppCpuTimeLimit"},
{0x0050, &APT_S::GetAppCpuTimeLimit, "GetAppCpuTimeLimit"},

View File

@ -27,7 +27,7 @@ APT_U::APT_U(std::shared_ptr<Module> apt)
{0x000E, &APT_U::GlanceParameter, "GlanceParameter"},
{0x000F, &APT_U::CancelParameter, "CancelParameter"},
{0x0010, nullptr, "DebugFunc"},
{0x0011, nullptr, "MapProgramIdForDebug"},
{0x0011, &APT_U::MapProgramIdForDebug, "MapProgramIdForDebug"},
{0x0012, nullptr, "SetHomeMenuAppletIdForDebug"},
{0x0013, nullptr, "GetPreparationState"},
{0x0014, nullptr, "SetPreparationState"},
@ -87,7 +87,7 @@ APT_U::APT_U(std::shared_ptr<Module> apt)
{0x004A, &APT_U::GetCaptureInfo, "GetCaptureInfo"},
{0x004B, &APT_U::AppletUtility, "AppletUtility"},
{0x004C, nullptr, "SetFatalErrDispMode"},
{0x004D, nullptr, "GetAppletProgramInfo"},
{0x004D, &APT_U::GetAppletProgramInfo, "GetAppletProgramInfo"},
{0x004E, &APT_U::HardwareResetAsync, "HardwareResetAsync"},
{0x004F, &APT_U::SetAppCpuTimeLimit, "SetAppCpuTimeLimit"},
{0x0050, &APT_U::GetAppCpuTimeLimit, "GetAppCpuTimeLimit"},

View File

@ -602,6 +602,43 @@ void Module::Interface::GetModelNintendo2DS(Kernel::HLERequestContext& ctx) {
rb.Push(model != Service::CFG::NINTENDO_2DS);
}
void Module::Interface::TranslateCountryInfo(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx);
ConsoleCountryInfo country_info = rp.PopRaw<ConsoleCountryInfo>();
u8 translate_direction = rp.Pop<u8>();
// Translation table, left is version A and right is version B.
static constexpr std::array<std::pair<ConsoleCountryInfo, ConsoleCountryInfo>, 5> translations =
{{
{{{0x00, 0x00}, 0x03, 0x6E}, {{0x00, 0x00}, 0x04, 0x6E}},
{{{0x00, 0x00}, 0x04, 0x6E}, {{0x00, 0x00}, 0x05, 0x6E}},
{{{0x00, 0x00}, 0x05, 0x6E}, {{0x00, 0x00}, 0x06, 0x6E}},
{{{0x00, 0x00}, 0x06, 0x6E}, {{0x00, 0x00}, 0x07, 0x6E}},
{{{0x00, 0x00}, 0x07, 0x6E}, {{0x00, 0x00}, 0x03, 0x6E}},
}};
ConsoleCountryInfo final_info = country_info;
if (translate_direction == 0) {
for (const auto& [vA, vB] : translations) {
if (country_info == vB) {
final_info = vA;
break;
}
}
} else if (translate_direction == 1) {
for (const auto& [vA, vB] : translations) {
if (country_info == vA) {
final_info = vB;
break;
}
}
}
IPC::RequestBuilder rb = rp.MakeBuilder(2, 0);
rb.Push(ResultSuccess);
rb.PushRaw<ConsoleCountryInfo>(final_info);
}
void Module::Interface::GetConfig(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx);
u32 size = rp.Pop<u32>();

View File

@ -152,6 +152,15 @@ struct ConsoleCountryInfo {
std::array<u8, 2> unknown; ///< Unknown data
u8 state_code; ///< The state or province code.
u8 country_code; ///< The country code of the console
bool operator==(const ConsoleCountryInfo& other) const {
return unknown == other.unknown && state_code == other.state_code &&
country_code == other.country_code;
}
bool operator!=(const ConsoleCountryInfo& other) const {
return !(*this == other);
}
};
static_assert(sizeof(ConsoleCountryInfo) == 4, "ConsoleCountryInfo must be exactly 4 bytes");
@ -315,6 +324,8 @@ public:
*/
void GetModelNintendo2DS(Kernel::HLERequestContext& ctx);
void TranslateCountryInfo(Kernel::HLERequestContext& ctx);
/**
* CFG::GetConfig service function
* Inputs:

View File

@ -1,4 +1,4 @@
// Copyright 2014 Citra Emulator Project
// Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
@ -20,7 +20,7 @@ CFG_I::CFG_I(std::shared_ptr<Module> cfg) : Module::Interface(std::move(cfg), "c
{0x0005, &CFG_I::GetSystemModel, "GetSystemModel"},
{0x0006, &CFG_I::GetModelNintendo2DS, "GetModelNintendo2DS"},
{0x0007, nullptr, "WriteToFirstByteCfgSavegame"},
{0x0008, nullptr, "TranslateCountryInfo"},
{0x0008, &CFG_I::TranslateCountryInfo, "TranslateCountryInfo"},
{0x0009, &CFG_I::GetCountryCodeString, "GetCountryCodeString"},
{0x000A, &CFG_I::GetCountryCodeID, "GetCountryCodeID"},
{0x000B, nullptr, "IsFangateSupported"},

View File

@ -1,4 +1,4 @@
// Copyright 2015 Citra Emulator Project
// Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
@ -20,7 +20,7 @@ CFG_S::CFG_S(std::shared_ptr<Module> cfg) : Module::Interface(std::move(cfg), "c
{0x0005, &CFG_S::GetSystemModel, "GetSystemModel"},
{0x0006, &CFG_S::GetModelNintendo2DS, "GetModelNintendo2DS"},
{0x0007, nullptr, "WriteToFirstByteCfgSavegame"},
{0x0008, nullptr, "TranslateCountryInfo"},
{0x0008, &CFG_S::TranslateCountryInfo, "TranslateCountryInfo"},
{0x0009, &CFG_S::GetCountryCodeString, "GetCountryCodeString"},
{0x000A, &CFG_S::GetCountryCodeID, "GetCountryCodeID"},
{0x000B, nullptr, "IsFangateSupported"},

View File

@ -1,4 +1,4 @@
// Copyright 2014 Citra Emulator Project
// Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
@ -20,7 +20,7 @@ CFG_U::CFG_U(std::shared_ptr<Module> cfg) : Module::Interface(std::move(cfg), "c
{0x0005, &CFG_U::GetSystemModel, "GetSystemModel"},
{0x0006, &CFG_U::GetModelNintendo2DS, "GetModelNintendo2DS"},
{0x0007, nullptr, "WriteToFirstByteCfgSavegame"},
{0x0008, nullptr, "TranslateCountryInfo"},
{0x0008, &CFG_U::TranslateCountryInfo, "TranslateCountryInfo"},
{0x0009, &CFG_U::GetCountryCodeString, "GetCountryCodeString"},
{0x000A, &CFG_U::GetCountryCodeID, "GetCountryCodeID"},
{0x000B, nullptr, "IsFangateSupported"},

View File

@ -39,10 +39,13 @@ namespace ErrCodes {
enum {
// TODO(purpasmart): Check if this name fits its actual usage
OutofRangeOrMisalignedAddress = 513,
NoSaveVRAMSysAreaPerm = 518,
FirstInitialization = 519,
};
}
constexpr Result ResultNoSaveVRAMSysAreaPerm(ErrCodes::NoSaveVRAMSysAreaPerm, ErrorModule::GX,
ErrorSummary::NothingHappened, ErrorLevel::Permanent);
constexpr Result ResultFirstInitialization(ErrCodes::FirstInitialization, ErrorModule::GX,
ErrorSummary::Success, ErrorLevel::Success);
constexpr Result ResultRegsOutOfRangeOrMisaligned(ErrCodes::OutofRangeOrMisalignedAddress,
@ -71,13 +74,34 @@ CommandBuffer* GSP_GPU::GetCommandBuffer(u32 thread_id) {
return reinterpret_cast<CommandBuffer*>(ptr);
}
FrameBufferUpdate* GSP_GPU::GetFrameBufferInfo(u32 thread_id, u32 screen_index) {
std::pair<bool, FrameBufferInfo*> GSP_GPU::GetFrameBufferInfo(u32 thread_id, u32 screen_index,
bool force_update) {
DEBUG_ASSERT_MSG(screen_index < 2, "Invalid screen index");
bool is_dirty = false;
// Matches real HW behaviour of a cached copy of
// the framebuffer info being maintained that
// is only updated with the dirty flag every time
// the FB info is accessed.
//
// If the thread ID is invalid, the last cached
// data is used.
if (thread_id != std::numeric_limits<u32>::max()) {
// For each thread there are two FrameBufferUpdate fields
const u32 offset = 0x200 + (2 * thread_id + screen_index) * sizeof(FrameBufferUpdate);
u8* ptr = shared_memory->GetPointer(offset);
return reinterpret_cast<FrameBufferUpdate*>(ptr);
FrameBufferUpdate* screen =
reinterpret_cast<FrameBufferUpdate*>(shared_memory->GetPointer(offset));
if (screen->is_dirty || force_update) {
cached_framebuffer_infos[screen_index] = screen->framebuffer_info[screen->index];
screen->is_dirty.Assign(false);
is_dirty = true;
}
}
return {is_dirty, &cached_framebuffer_infos[screen_index]};
}
InterruptRelayQueue* GSP_GPU::GetInterruptRelayQueue(u32 thread_id) {
@ -86,10 +110,15 @@ InterruptRelayQueue* GSP_GPU::GetInterruptRelayQueue(u32 thread_id) {
}
void GSP_GPU::ClientDisconnected(std::shared_ptr<Kernel::ServerSession> server_session) {
const SessionData* session_data = GetSessionData(server_session);
if (active_thread_id == session_data->thread_id) {
SessionData* session_data = GetSessionData(server_session);
session_data->interrupt_event = nullptr;
session_data->registered = false;
if (thread_id_with_rights == session_data->thread_id) {
ReleaseRight(session_data);
}
if (True(session_data->relay_queue_flags & RelayEventQueueFlags::AllowSaveVramSysArea)) {
vram_backup_handler.ResetState();
}
SessionRequestHandler::ClientDisconnected(server_session);
}
@ -190,6 +219,41 @@ static Result WriteHWRegsWithMask(u32 base_address, u32 size_in_bytes, std::span
return ResultSuccess;
}
class GSP_GPU::ThreadCallback : public Kernel::HLERequestContext::WakeupCallback {
public:
enum class Type : u32 {
SaveVRAMSysArea,
RestoreVRAMSysArea,
};
explicit ThreadCallback(Core::System& system_, std::string name_, Type type_)
: system(system_), name(name_), type(type_) {}
void WakeUp(std::shared_ptr<Kernel::Thread> thread, Kernel::HLERequestContext& ctx,
Kernel::ThreadWakeupReason reason) {
if (type == Type::SaveVRAMSysArea || type == Type::RestoreVRAMSysArea) {
IPC::RequestBuilder rb = IPC::RequestBuilder(ctx, 1, 0);
rb.Push(ResultSuccess);
}
}
private:
Core::System& system;
std::string name;
Type type;
ThreadCallback() : system(Core::Global<Core::System>()) {}
template <class Archive>
void serialize(Archive& ar, const unsigned int) {
ar& boost::serialization::base_object<Kernel::HLERequestContext::WakeupCallback>(*this);
ar & name;
ar & type;
}
friend class boost::serialization::access;
};
void GSP_GPU::WriteHWRegs(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx);
const u32 reg_addr = rp.Pop<u32>();
@ -315,7 +379,8 @@ void GSP_GPU::GetPerfLog(Kernel::HLERequestContext& ctx) {
void GSP_GPU::RegisterInterruptRelayQueue(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx);
u32 flags = rp.Pop<u32>();
auto relay_queue_flags = static_cast<RelayEventQueueFlags>(rp.Pop<u32>());
auto interrupt_event = rp.PopObject<Kernel::Event>();
ASSERT_MSG(interrupt_event, "handle is not valid!");
@ -325,6 +390,7 @@ void GSP_GPU::RegisterInterruptRelayQueue(Kernel::HLERequestContext& ctx) {
SessionData* session_data = GetSessionData(ctx.Session());
session_data->interrupt_event = std::move(interrupt_event);
session_data->registered = true;
session_data->relay_queue_flags = relay_queue_flags;
IPC::RequestBuilder rb = rp.MakeBuilder(2, 2);
@ -339,7 +405,7 @@ void GSP_GPU::RegisterInterruptRelayQueue(Kernel::HLERequestContext& ctx) {
rb.Push(session_data->thread_id);
rb.PushCopyObjects(shared_memory);
LOG_DEBUG(Service_GSP, "called, flags=0x{:08X}", flags);
LOG_DEBUG(Service_GSP, "called, relay_queue_flags=0x{:08X}", relay_queue_flags);
}
void GSP_GPU::UnregisterInterruptRelayQueue(Kernel::HLERequestContext& ctx) {
@ -521,10 +587,17 @@ void Service::GSP::GSP_GPU::ProcessPendingInterruptImpl(InterruptId interrupt_id
// Update framebuffer information if requested
const s32 screen_id = (interrupt_id == InterruptId::PDC0) ? 0 : 1;
auto* info = GetFrameBufferInfo(thread_id, screen_id);
if (info->is_dirty) {
system.GPU().SetBufferSwap(screen_id, info->framebuffer_info[info->index]);
info->is_dirty.Assign(false);
auto info = GetFrameBufferInfo(thread_id, screen_id);
if (info.first || force_buffer_swap[screen_id]) {
force_buffer_swap[screen_id] = false;
system.GPU().SetBufferSwap(screen_id, *info.second);
}
// If it's the bottom screen interrupt and a HLE event is pending,
// signal it and reset.
if (interrupt_id == InterruptId::PDC1 && session_data->on_vblank_event) {
session_data->on_vblank_event->Signal();
session_data->on_vblank_event = nullptr;
}
} else {
@ -549,11 +622,11 @@ void GSP_GPU::SignalInterrupt(InterruptId interrupt_id, u64 wait_delay_ns) {
}
// For normal interrupts, don't do anything if no process has acquired the GPU right.
if (active_thread_id == std::numeric_limits<u32>::max()) {
if (thread_id_with_rights == std::numeric_limits<u32>::max()) {
return;
}
SignalInterruptForThread(interrupt_id, active_thread_id, wait_delay_ns);
SignalInterruptForThread(interrupt_id, thread_id_with_rights, wait_delay_ns);
}
void GSP_GPU::SetLcdForceBlack(Kernel::HLERequestContext& ctx) {
@ -571,7 +644,14 @@ void GSP_GPU::SetLcdForceBlack(Kernel::HLERequestContext& ctx) {
void GSP_GPU::TriggerCmdReqQueue(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx);
auto* command_buffer = GetCommandBuffer(active_thread_id);
if (thread_id_with_rights == std::numeric_limits<u32>::max()) {
// Even if the active thread ID is not set,
// the function always succeeds.
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
rb.Push(ResultSuccess);
}
auto* command_buffer = GetCommandBuffer(thread_id_with_rights);
auto& gpu = system.GPU();
bool requires_delay = false;
@ -628,17 +708,18 @@ void GSP_GPU::ImportDisplayCaptureInfo(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_GSP, "called");
if (active_thread_id == std::numeric_limits<u32>::max()) {
LOG_WARNING(Service_GSP, "Called without an active thread.");
auto top_screen_info = GetFrameBufferInfo(thread_id_with_rights, 0);
auto bottom_screen_info = GetFrameBufferInfo(thread_id_with_rights, 1);
// TODO: Find the right error code.
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
rb.Push(-1);
return;
if (top_screen_info.first) {
force_buffer_swap[0] = true;
}
if (bottom_screen_info.first) {
force_buffer_swap[1] = true;
}
FrameBufferUpdate* top_screen = GetFrameBufferInfo(active_thread_id, 0);
FrameBufferUpdate* bottom_screen = GetFrameBufferInfo(active_thread_id, 1);
auto top_screen = top_screen_info.second;
auto bottom_screen = bottom_screen_info.second;
struct CaptureInfoEntry {
u32_le address_left;
@ -649,16 +730,15 @@ void GSP_GPU::ImportDisplayCaptureInfo(Kernel::HLERequestContext& ctx) {
CaptureInfoEntry top_entry, bottom_entry;
// Top Screen
top_entry.address_left = top_screen->framebuffer_info[top_screen->index].address_left;
top_entry.address_right = top_screen->framebuffer_info[top_screen->index].address_right;
top_entry.format = top_screen->framebuffer_info[top_screen->index].format;
top_entry.stride = top_screen->framebuffer_info[top_screen->index].stride;
top_entry.address_left = top_screen->address_left;
top_entry.address_right = top_screen->address_right;
top_entry.format = top_screen->format;
top_entry.stride = top_screen->stride;
// Bottom Screen
bottom_entry.address_left = bottom_screen->framebuffer_info[bottom_screen->index].address_left;
bottom_entry.address_right =
bottom_screen->framebuffer_info[bottom_screen->index].address_right;
bottom_entry.format = bottom_screen->framebuffer_info[bottom_screen->index].format;
bottom_entry.stride = bottom_screen->framebuffer_info[bottom_screen->index].stride;
bottom_entry.address_left = bottom_screen->address_left;
bottom_entry.address_right = bottom_screen->address_right;
bottom_entry.format = bottom_screen->format;
bottom_entry.stride = bottom_screen->stride;
IPC::RequestBuilder rb = rp.MakeBuilder(9, 0);
rb.Push(ResultSuccess);
@ -722,26 +802,19 @@ void GSP_GPU::SaveVramSysArea(Kernel::HLERequestContext& ctx) {
// Invalid values
0, 0, 0};
if (active_thread_id == std::numeric_limits<u32>::max()) {
LOG_WARNING(Service_GSP, "Called without an active thread.");
// TODO: Find the right error code.
const auto session_data = GetSessionData(ctx.Session());
if (False(session_data->relay_queue_flags & RelayEventQueueFlags::AllowSaveVramSysArea)) {
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
rb.Push(-1);
rb.Push(ResultNoSaveVRAMSysAreaPerm);
return;
}
system.Memory().RasterizerFlushVirtualRegion(Memory::VRAM_VADDR, Memory::VRAM_SIZE,
Memory::FlushMode::Flush);
const auto vram = system.Memory().GetPointer(Memory::VRAM_VADDR);
saved_vram.emplace(std::vector<u8>(Memory::VRAM_SIZE));
std::memcpy(saved_vram.get().data(), vram, Memory::VRAM_SIZE);
vram_backup_handler.SaveVRAMWithState(VramBackupHandler::State::STATE_0);
auto top_screen = GetFrameBufferInfo(active_thread_id, 0);
auto top_screen = GetFrameBufferInfo(thread_id_with_rights, 0).second;
if (top_screen) {
u8 bytes_per_pixel =
bpp_per_format[top_screen->framebuffer_info[top_screen->index].GetPixelFormat()];
const auto top_fb = top_screen->framebuffer_info[top_screen->index];
u8 bytes_per_pixel = bpp_per_format[top_screen->GetPixelFormat()];
const auto& top_fb = *top_screen;
if (top_fb.address_left && bytes_per_pixel != 0 && bytes_per_pixel != 4) {
CopyFrameBuffer(system, FRAMEBUFFER_SAVE_AREA_TOP_LEFT, top_fb.address_left,
FRAMEBUFFER_WIDTH * bytes_per_pixel, top_fb.stride,
@ -761,21 +834,18 @@ void GSP_GPU::SaveVramSysArea(Kernel::HLERequestContext& ctx) {
FRAMEBUFFER_WIDTH * bytes_per_pixel, TOP_FRAMEBUFFER_HEIGHT);
}
FrameBufferInfo fb_info = top_screen->framebuffer_info[top_screen->index];
fb_info.address_left = FRAMEBUFFER_SAVE_AREA_TOP_LEFT;
fb_info.address_right = FRAMEBUFFER_SAVE_AREA_TOP_RIGHT;
fb_info.stride = FRAMEBUFFER_WIDTH * bytes_per_pixel;
system.GPU().SetBufferSwap(0, fb_info);
top_screen->address_left = FRAMEBUFFER_SAVE_AREA_TOP_LEFT;
top_screen->address_right = FRAMEBUFFER_SAVE_AREA_TOP_RIGHT;
top_screen->stride = FRAMEBUFFER_WIDTH * bytes_per_pixel;
force_buffer_swap[0] = true;
} else {
LOG_WARNING(Service_GSP, "No top screen bound, skipping capture.");
}
auto bottom_screen = GetFrameBufferInfo(active_thread_id, 1);
auto bottom_screen = GetFrameBufferInfo(thread_id_with_rights, 1).second;
if (bottom_screen) {
u8 bytes_per_pixel =
bpp_per_format[bottom_screen->framebuffer_info[bottom_screen->index].GetPixelFormat()];
const auto bottom_fb = bottom_screen->framebuffer_info[bottom_screen->index];
u8 bytes_per_pixel = bpp_per_format[bottom_screen->GetPixelFormat()];
const auto& bottom_fb = *bottom_screen;
if (bottom_fb.address_left && bytes_per_pixel != 0 && bytes_per_pixel != 4) {
CopyFrameBuffer(system, FRAMEBUFFER_SAVE_AREA_BOTTOM, bottom_fb.address_left,
FRAMEBUFFER_WIDTH * bytes_per_pixel, bottom_fb.stride,
@ -785,19 +855,21 @@ void GSP_GPU::SaveVramSysArea(Kernel::HLERequestContext& ctx) {
ClearFramebuffer(system, FRAMEBUFFER_SAVE_AREA_BOTTOM,
FRAMEBUFFER_WIDTH * bytes_per_pixel, BOTTOM_FRAMEBUFFER_HEIGHT);
}
FrameBufferInfo fb_info = bottom_screen->framebuffer_info[bottom_screen->index];
fb_info.address_left = FRAMEBUFFER_SAVE_AREA_BOTTOM;
fb_info.stride = FRAMEBUFFER_WIDTH * bytes_per_pixel;
system.GPU().SetBufferSwap(1, fb_info);
bottom_screen->address_left = FRAMEBUFFER_SAVE_AREA_BOTTOM;
bottom_screen->stride = FRAMEBUFFER_WIDTH * bytes_per_pixel;
force_buffer_swap[1] = true;
} else {
LOG_WARNING(Service_GSP, "No bottom screen bound, skipping capture.");
}
// Real GSP waits for VBlank here, but we don't need it (?).
vram_backup_handler.SaveVRAMWithState(VramBackupHandler::State::STATE_1);
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
rb.Push(ResultSuccess);
// Wait for VBlank
auto vblank_callback = std::make_shared<ThreadCallback>(system, "GSP_GPU::SaveVRAMSysArea",
ThreadCallback::Type::SaveVRAMSysArea);
session_data->on_vblank_event = ctx.SleepClientThread(
"GSP_GPU::SaveVRAMSysArea", std::chrono::nanoseconds(-1), vblank_callback);
}
void GSP_GPU::RestoreVramSysArea(Kernel::HLERequestContext& ctx) {
@ -805,31 +877,35 @@ void GSP_GPU::RestoreVramSysArea(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_GSP, "called");
if (saved_vram) {
auto vram = system.Memory().GetPointer(Memory::VRAM_VADDR);
std::memcpy(vram, saved_vram.get().data(), Memory::VRAM_SIZE);
system.Memory().RasterizerFlushVirtualRegion(Memory::VRAM_VADDR, Memory::VRAM_SIZE,
Memory::FlushMode::Invalidate);
}
auto top_screen = GetFrameBufferInfo(active_thread_id, 0);
if (top_screen) {
system.GPU().SetBufferSwap(0, top_screen->framebuffer_info[top_screen->index]);
} else {
LOG_WARNING(Service_GSP, "No top screen bound, skipping restore.");
}
auto bottom_screen = GetFrameBufferInfo(active_thread_id, 1);
if (bottom_screen) {
system.GPU().SetBufferSwap(1, bottom_screen->framebuffer_info[top_screen->index]);
} else {
LOG_WARNING(Service_GSP, "No bottom screen bound, skipping restore.");
}
// Real GSP waits for VBlank here, but we don't need it (?).
const auto session_data = GetSessionData(ctx.Session());
if (False(session_data->relay_queue_flags & RelayEventQueueFlags::AllowSaveVramSysArea)) {
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
rb.Push(ResultSuccess);
rb.Push(ResultNoSaveVRAMSysAreaPerm);
return;
}
vram_backup_handler.RestoreVRAMWithState(VramBackupHandler::State::STATE_1);
auto top_screen = GetFrameBufferInfo(thread_id_with_rights, 0, true).second;
if (!top_screen) {
LOG_WARNING(Service_GSP, "No top screen bound, skipping restore.");
} else {
force_buffer_swap[0] = true;
}
auto bottom_screen = GetFrameBufferInfo(thread_id_with_rights, 1, true).second;
if (!bottom_screen) {
LOG_WARNING(Service_GSP, "No bottom screen bound, skipping restore.");
} else {
force_buffer_swap[1] = true;
}
vram_backup_handler.RestoreVRAMWithState(VramBackupHandler::State::STATE_0);
auto vblank_callback = std::make_shared<ThreadCallback>(
system, "GSP_GPU::RestoreVramSysArea", ThreadCallback::Type::RestoreVRAMSysArea);
session_data->on_vblank_event = ctx.SleepClientThread(
"GSP_GPU::RestoreVramSysArea", std::chrono::nanoseconds(-1), vblank_callback);
}
Result GSP_GPU::AcquireGpuRight(const Kernel::HLERequestContext& ctx,
@ -861,7 +937,7 @@ Result GSP_GPU::AcquireGpuRight(const Kernel::HLERequestContext& ctx,
gpu.PicaCore().vs_setup.requires_fixup = requires_shader_fixup;
gpu.PicaCore().gs_setup.requires_fixup = requires_shader_fixup;
if (active_thread_id == session_data->thread_id) {
if (thread_id_with_rights == session_data->thread_id) {
return {ErrorDescription::AlreadyDone, ErrorModule::GX, ErrorSummary::Success,
ErrorLevel::Success};
}
@ -870,14 +946,14 @@ Result GSP_GPU::AcquireGpuRight(const Kernel::HLERequestContext& ctx,
if (blocking) {
// TODO: The thread should be put to sleep until acquired.
ASSERT_MSG(active_thread_id == std::numeric_limits<u32>::max(),
ASSERT_MSG(thread_id_with_rights == std::numeric_limits<u32>::max(),
"Sleeping for GPU right is not yet supported.");
} else if (active_thread_id != std::numeric_limits<u32>::max()) {
} else if (thread_id_with_rights != std::numeric_limits<u32>::max()) {
return {ErrorDescription::Busy, ErrorModule::GX, ErrorSummary::WouldBlock,
ErrorLevel::Status};
}
active_thread_id = session_data->thread_id;
thread_id_with_rights = session_data->thread_id;
active_client_thread_id = ctx.ClientThread()->thread_id;
return ResultSuccess;
}
@ -904,16 +980,17 @@ void GSP_GPU::AcquireRight(Kernel::HLERequestContext& ctx) {
}
void GSP_GPU::ReleaseRight(const SessionData* session_data) {
ASSERT_MSG(active_thread_id == session_data->thread_id,
"Wrong thread tried to release GPU right");
active_thread_id = std::numeric_limits<u32>::max();
if (thread_id_with_rights == session_data->thread_id) {
thread_id_with_rights = std::numeric_limits<u32>::max();
active_client_thread_id = std::numeric_limits<u32>::max();
}
}
void GSP_GPU::ReleaseRight(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx);
const SessionData* session_data = GetSessionData(ctx.Session());
// Success even if wrong thread calls this function.
ReleaseRight(session_data);
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
@ -976,14 +1053,16 @@ void GSP_GPU::serialize(Archive& ar, const unsigned int) {
DEBUG_SERIALIZATION_POINT;
ar& boost::serialization::base_object<Kernel::SessionRequestHandler>(*this);
ar & shared_memory;
ar & active_thread_id;
ar & thread_id_with_rights;
ar & active_client_thread_id;
ar & first_initialization;
ar & used_thread_ids;
ar & saved_vram;
ar & delay_texture_copy_completion;
ar & pending_interrupts;
ar & perf_recorder;
ar & cached_framebuffer_infos;
ar & force_buffer_swap;
ar & vram_backup_handler;
}
SERIALIZE_IMPL(GSP_GPU)
@ -1051,6 +1130,8 @@ void SessionData::serialize(Archive& ar, const unsigned int) {
ar & interrupt_event;
ar & thread_id;
ar & registered;
ar & relay_queue_flags;
ar & on_vblank_event;
}
SERIALIZE_IMPL(SessionData)
@ -1067,4 +1148,64 @@ SessionData::~SessionData() {
gsp->used_thread_ids[thread_id] = false;
}
void VramBackupHandler::SaveVRAMWithState(State state) {
VRAMBankBackupMask mask{};
if (state == State::STATE_0 && curr_state == State::STATE_0) {
mask = VRAMBankBackupMask::MASK_1;
curr_state = State::STATE_1;
} else if (state == State::STATE_1 && curr_state == State::STATE_1) {
mask = VRAMBankBackupMask::MASK_2;
curr_state = State::STATE_2;
}
for (size_t i = 0; i < BANK_COUNT; i++) {
if (True(mask & mask_per_bank[i])) {
SaveVRAMBank(i);
}
}
}
void VramBackupHandler::RestoreVRAMWithState(State state) {
VRAMBankBackupMask mask{};
if (state == State::STATE_0 && curr_state == State::STATE_1) {
mask = VRAMBankBackupMask::MASK_1;
curr_state = State::STATE_0;
} else if (state == State::STATE_1 && curr_state == State::STATE_2) {
mask = VRAMBankBackupMask::MASK_2;
curr_state = State::STATE_1;
}
for (size_t i = 0; i < BANK_COUNT; i++) {
if (True(mask & mask_per_bank[i])) {
RestoreVRAMBank(i);
}
}
}
void VramBackupHandler::SaveVRAMBank(size_t bank_id) {
ASSERT(bank_id < BANK_COUNT);
VAddr bank_vaddr = Memory::VRAM_VADDR + bank_id * VRAM_BACKUP_BANK_SIZE;
system.Memory().RasterizerFlushVirtualRegion(bank_vaddr, VRAM_BACKUP_BANK_SIZE,
Memory::FlushMode::Flush);
u8* data = system.Memory().GetPointer(bank_vaddr);
memcpy(vram_backup[bank_id].data(), data, VRAM_BACKUP_BANK_SIZE);
}
void VramBackupHandler::RestoreVRAMBank(size_t bank_id) {
ASSERT(bank_id < BANK_COUNT);
VAddr bank_vaddr = Memory::VRAM_VADDR + bank_id * VRAM_BACKUP_BANK_SIZE;
u8* data = system.Memory().GetPointer(bank_vaddr);
memcpy(data, vram_backup[bank_id].data(), VRAM_BACKUP_BANK_SIZE);
system.Memory().RasterizerFlushVirtualRegion(bank_vaddr, VRAM_BACKUP_BANK_SIZE,
Memory::FlushMode::Invalidate);
}
} // namespace Service::GSP
SERIALIZE_EXPORT_IMPL(Service::GSP::GSP_GPU::ThreadCallback)

View File

@ -18,6 +18,7 @@
#include "core/hle/service/gsp/gsp_command.h"
#include "core/hle/service/gsp/gsp_interrupt.h"
#include "core/hle/service/service.h"
#include "core/memory.h"
namespace Core {
class System;
@ -45,6 +46,17 @@ struct FrameBufferInfo {
u32 GetPixelFormat() {
return format & PIXEL_FORMAT_MASK;
}
template <class Archive>
void serialize(Archive& ar, const unsigned int) {
ar & active_fb;
ar & address_left;
ar & address_right;
ar & stride;
ar & format;
ar & shown_fb;
ar & unknown;
}
};
static_assert(sizeof(FrameBufferInfo) == 0x1c, "Struct has incorrect size");
@ -61,6 +73,20 @@ static_assert(sizeof(FrameBufferUpdate) == 0x40, "Struct has incorrect size");
static_assert(offsetof(FrameBufferUpdate, framebuffer_info[1]) == 0x20,
"FrameBufferInfo element has incorrect alignment");
enum RelayEventQueueFlags : u32 {
AllowSaveVramSysArea = (1 << 0),
};
DECLARE_ENUM_FLAG_OPERATORS(RelayEventQueueFlags)
// TODO(PabloMK7): Fill this in when the purpose of
// each bit is determined.
enum class VRAMBankBackupMask : u8 {
MASK_0 = (1 << 0),
MASK_1 = (1 << 1),
MASK_2 = (1 << 2),
};
DECLARE_ENUM_FLAG_OPERATORS(VRAMBankBackupMask)
constexpr u32 FRAMEBUFFER_WIDTH = 240;
constexpr u32 FRAMEBUFFER_WIDTH_POW2 = 256;
constexpr u32 TOP_FRAMEBUFFER_HEIGHT = 400;
@ -72,6 +98,8 @@ constexpr VAddr FRAMEBUFFER_SAVE_AREA_TOP_LEFT = Memory::VRAM_VADDR + 0x273000;
constexpr VAddr FRAMEBUFFER_SAVE_AREA_TOP_RIGHT = Memory::VRAM_VADDR + 0x2B9800;
constexpr VAddr FRAMEBUFFER_SAVE_AREA_BOTTOM = Memory::VRAM_VADDR + 0x4C7800;
constexpr size_t VRAM_BACKUP_BANK_SIZE = 0x80000;
class GSP_GPU;
class SessionData : public Kernel::SessionRequestHandler::SessionDataBase {
@ -88,6 +116,10 @@ public:
u32 thread_id;
/// Whether RegisterInterruptRelayQueue was called for this session
bool registered = false;
/// Current relay queue flags
RelayEventQueueFlags relay_queue_flags{};
/// Event to be signaled on VBlank
std::shared_ptr<Kernel::Event> on_vblank_event{};
private:
template <class Archive>
@ -95,6 +127,68 @@ private:
friend class boost::serialization::access;
};
class VramBackupHandler {
public:
VramBackupHandler() : system(Core::Global<Core::System>()) {
for (auto& v : vram_backup) {
v.resize(VRAM_BACKUP_BANK_SIZE);
}
}
// TODO(PabloMK7): Figure out what each state means
enum class State : u32 { STATE_0, STATE_1, STATE_2 };
void SaveVRAMWithState(State state);
void RestoreVRAMWithState(State state);
void ResetState() {
curr_state = State::STATE_0;
}
private:
Core::System& system;
void SaveVRAMBank(size_t bank_id);
void RestoreVRAMBank(size_t bank_id);
static constexpr u32 BANK_COUNT = Memory::VRAM_SIZE / VRAM_BACKUP_BANK_SIZE;
// A vector is needed because otherwise a stack overflow happens
// in the boost serialization code.
std::array<std::vector<u8>, BANK_COUNT> vram_backup{};
// TODO(PabloMK7): Figure out what this means, it's taken from
// GSP decompilation.
static constexpr std::array<VRAMBankBackupMask, BANK_COUNT> mask_per_bank = {
VRAMBankBackupMask::MASK_0, // 0x1F000000 - 0x1F07FFFF
VRAMBankBackupMask::MASK_0, // 0x1F080000 - 0x1F0FFFFF
VRAMBankBackupMask::MASK_0, // 0x1F100000 - 0x1F17FFFF
VRAMBankBackupMask::MASK_0 | VRAMBankBackupMask::MASK_1, // 0x1F180000 - 0x1F1FFFFF
VRAMBankBackupMask::MASK_0 | VRAMBankBackupMask::MASK_1, // 0x1F200000 - 0x1F27FFFF
VRAMBankBackupMask::MASK_0 | VRAMBankBackupMask::MASK_1, // 0x1F280000 - 0x1F2FFFFF
VRAMBankBackupMask::MASK_0, // 0x1F300000 - 0x1F37FFFF
VRAMBankBackupMask::MASK_0 | VRAMBankBackupMask::MASK_2, // 0x1F380000 - 0x1F3FFFFF
VRAMBankBackupMask::MASK_0 | VRAMBankBackupMask::MASK_2, // 0x1F400000 - 0x1F47FFFF
VRAMBankBackupMask::MASK_0 | VRAMBankBackupMask::MASK_1, // 0x1F480000 - 0x1F4FFFFF
VRAMBankBackupMask::MASK_0 | VRAMBankBackupMask::MASK_1, // 0x1F500000 - 0x1F57FFFF
VRAMBankBackupMask::MASK_0 | VRAMBankBackupMask::MASK_1, // 0x1F580000 - 0x1F5FFFFF
};
State curr_state = State::STATE_0;
template <class Archive>
void serialize(Archive& ar, const unsigned int) {
for (size_t i = 0; i < BANK_COUNT; i++) {
ar& vram_backup[i];
}
ar & curr_state;
}
friend class boost::serialization::access;
};
class GSP_GPU final : public ServiceFramework<GSP_GPU, SessionData> {
public:
explicit GSP_GPU(Core::System& system);
@ -114,9 +208,10 @@ public:
* @param thread_id GSP thread id of the process that accesses the structure that we are
* requesting.
* @param screen_index Index of the screen we are requesting (Top = 0, Bottom = 1).
* @returns FramebufferUpdate Information about the specified framebuffer.
* @returns A pair containing whether the FB info is new and the framebuffer info itself.
*/
FrameBufferUpdate* GetFrameBufferInfo(u32 thread_id, u32 screen_index);
std::pair<bool, FrameBufferInfo*> GetFrameBufferInfo(u32 thread_id, u32 screen_index,
bool force_update = false);
/// Gets a pointer to a thread command buffer in GSP shared memory
CommandBuffer* GetCommandBuffer(u32 thread_id);
@ -128,7 +223,7 @@ public:
* Retreives the ID of the thread with GPU rights.
*/
u32 GetActiveThreadId() {
return active_thread_id;
return thread_id_with_rights;
}
/**
@ -138,6 +233,8 @@ public:
return active_client_thread_id;
}
class ThreadCallback;
private:
/**
* Signals that the specified interrupt type has occurred to userland code for the specified GSP
@ -399,16 +496,13 @@ private:
std::shared_ptr<Kernel::SharedMemory> shared_memory;
/// Thread id that currently has GPU rights or std::numeric_limits<u32>::max() if none.
u32 active_thread_id = std::numeric_limits<u32>::max();
u32 thread_id_with_rights = std::numeric_limits<u32>::max();
/// Thread id of the client thread that has GPU rights
u32 active_client_thread_id = std::numeric_limits<u32>::max();
bool first_initialization = true;
/// VRAM copy saved using SaveVramSysArea.
boost::optional<std::vector<u8>> saved_vram;
/// Maximum number of threads that can be registered at the same time in the GSP module.
static constexpr u32 MaxGSPThreads = 4;
@ -527,6 +621,11 @@ private:
Core::TimingEventType* SignalInterruptEventType = nullptr;
std::array<FrameBufferInfo, 2> cached_framebuffer_infos;
std::array<bool, 2> force_buffer_swap{};
VramBackupHandler vram_backup_handler;
friend class SessionData;
template <class Archive>
@ -539,3 +638,4 @@ private:
BOOST_CLASS_EXPORT_KEY(Service::GSP::SessionData)
BOOST_CLASS_EXPORT_KEY(Service::GSP::GSP_GPU)
SERVICE_CONSTRUCT(Service::GSP::GSP_GPU)
BOOST_CLASS_EXPORT_KEY(Service::GSP::GSP_GPU::ThreadCallback)

View File

@ -11,6 +11,7 @@
#include <fmt/format.h>
#include "common/archives.h"
#include "common/assert.h"
#include "common/file_util.h"
#include "common/scope_exit.h"
#include "common/string_util.h"
#include "core/core.h"
@ -281,7 +282,7 @@ std::string Context::ParseMultipartFormData() {
void Context::MakeRequest() {
ASSERT(state == RequestState::NotStarted);
state = RequestState::ConnectingToServer;
state = RequestState::SendingRequest;
static const std::unordered_map<RequestMethod, std::string> request_method_strings{
{RequestMethod::Get, "GET"}, {RequestMethod::Post, "POST"},
@ -297,6 +298,9 @@ void Context::MakeRequest() {
request.method = request_method_strings.at(method);
request.path = url_info.path;
// Apply URL replacements if any
url_info.host = url_replacer->Apply(url_info.host);
request.progress = [this](u64 current, u64 total) -> bool {
// TODO(B3N30): Is there a state that shows response header are available
current_download_size_bytes = current;
@ -450,8 +454,6 @@ void Context::MakeRequestSSL(httplib::Request& request, const URLInfo& url_info,
}
bool Context::ContentProvider(size_t offset, size_t length, httplib::DataSink& sink) {
state = RequestState::SendingRequest;
if (!post_data_raw.empty()) {
sink.write(post_data_raw.data() + offset, length);
}
@ -462,8 +464,6 @@ bool Context::ContentProvider(size_t offset, size_t length, httplib::DataSink& s
}
bool Context::ChunkedContentProvider(size_t offset, httplib::DataSink& sink) {
state = RequestState::SendingRequest;
finish_post_data.Wait();
switch (post_data_type) {
@ -788,6 +788,7 @@ void HTTP_C::CreateContext(Kernel::HLERequestContext& ctx) {
contexts[context_counter].socket_buffer_size = 0;
contexts[context_counter].handle = context_counter;
contexts[context_counter].session_id = session_data->session_id;
contexts[context_counter].url_replacer = &url_replacer;
session_data->num_http_contexts++;
@ -858,13 +859,25 @@ void HTTP_C::GetRequestState(Kernel::HLERequestContext& ctx) {
return;
}
LOG_DEBUG(Service_HTTP, "called, context_handle={}", context_handle);
Context& http_context = GetContext(context_handle);
RequestState state = http_context.state;
// When POST data is pending to be set, HTTPC stays in the SendingRequest
// state until NotifyFinishSendPostData is called. Most likely HTTPC
// already started the HTTP request at this point, has send the headers
// and is waiting for the client to set the post body to send.
// We cannot do that with httplib so instead fake the state to SendingRequest
// if post data is pending. TODO(PabloMK7): Fix if we get a more
// flexible HTTP library.
if (state == RequestState::NotStarted && http_context.post_pending_request) {
state = RequestState::SendingRequest;
}
LOG_DEBUG(Service_HTTP, "called, context_handle={} state={}", context_handle, state);
IPC::RequestBuilder rb = rp.MakeBuilder(2, 0);
rb.Push(ResultSuccess);
rb.PushEnum<RequestState>(http_context.state);
rb.PushEnum<RequestState>(state);
}
void HTTP_C::AddRequestHeader(Kernel::HLERequestContext& ctx) {
@ -1400,7 +1413,6 @@ void HTTP_C::NotifyFinishSendPostData(Kernel::HLERequestContext& ctx) {
}
http_context.finish_post_data.Set();
http_context.post_pending_request = false;
http_context.current_copied_data = 0;
http_context.request_future =
@ -2003,6 +2015,61 @@ void HTTP_C::Finalize(Kernel::HLERequestContext& ctx) {
LOG_WARNING(Service_HTTP, "(STUBBED) called");
}
void HTTP_C::RegisterURLReplacement(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx);
const u32 pattern_size = rp.Pop<u32>();
const u32 replacement_size = rp.Pop<u32>();
const std::vector<u8>& pattern_buf = rp.PopStaticBuffer();
const std::vector<u8>& replacement_buf = rp.PopStaticBuffer();
std::string pattern(reinterpret_cast<const char*>(pattern_buf.data()),
std::min(static_cast<size_t>(pattern_size), pattern_buf.size()));
std::string replacement(
reinterpret_cast<const char*>(replacement_buf.data()),
std::min(static_cast<size_t>(replacement_size), replacement_buf.size()));
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
if (url_replacer.HasRule(pattern)) {
rb.Push(Result{ErrorDescription::AlreadyExists, ErrorModule::HTTP,
ErrorSummary::InvalidArgument, ErrorLevel::Status});
return;
}
Result res = url_replacer.AddRule(pattern, replacement)
? ResultSuccess
: Result{ErrorDescription::InvalidCombination, ErrorModule::HTTP,
ErrorSummary::InvalidArgument, ErrorLevel::Status};
if (res.IsSuccess()) {
res = url_replacer.Save() ? res
: Result{ErrorDescription::OutOfMemory, ErrorModule::HTTP,
ErrorSummary::Internal, ErrorLevel::Permanent};
}
rb.Push(res);
}
void HTTP_C::UnregisterURLReplacement(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx);
const u32 pattern_size = rp.Pop<u32>();
const std::vector<u8>& pattern_buf = rp.PopStaticBuffer();
std::string pattern(reinterpret_cast<const char*>(pattern_buf.data()),
std::min(static_cast<size_t>(pattern_size), pattern_buf.size()));
bool deleted = url_replacer.DeleteRule(pattern);
Result res = deleted ? ResultSuccess
: Result{ErrorDescription::NotFound, ErrorModule::HTTP,
ErrorSummary::NotFound, ErrorLevel::Info};
if (deleted) {
url_replacer.Save();
}
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
rb.Push(res);
}
void HTTP_C::GetDownloadSizeState(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx);
const Context::Handle context_handle = rp.Pop<u32>();
@ -2171,6 +2238,96 @@ void HTTP_C::DecryptClCertA() {
ClCertA.init = true;
}
URLReplacer::URLReplacer() {
const std::string path{fmt::format("{}/http_hle_replace_rules.txt",
FileUtil::GetUserPath(FileUtil::UserPath::SysDataDir))};
FileUtil::IOFile f(path, "rb");
if (!f.IsOpen()) {
return;
}
std::string pattern;
std::string replacement;
while (f.ReadLine(pattern) && f.ReadLine(replacement)) {
try {
rules.push_back(Rule{
.regex = boost::regex(pattern),
.pattern = pattern,
.replacement = replacement,
});
} catch (const boost::regex_error& e) {
LOG_ERROR(Service_HTTP, "Failed to load HTTP HLE replacement pattern \"{}\": {}",
pattern, e.what());
}
}
}
bool URLReplacer::HasRule(const std::string& pattern) {
for (const auto& rule : rules) {
if (rule.pattern == pattern) {
return true;
}
}
return false;
}
bool URLReplacer::AddRule(const std::string& pattern, const std::string& replacement) {
try {
rules.push_back(Rule{
.regex = boost::regex(pattern),
.pattern = pattern,
.replacement = replacement,
});
} catch (const boost::regex_error& e) {
return false;
}
return true;
}
bool URLReplacer::DeleteRule(const std::string& pattern) {
const auto old_size = rules.size();
std::erase_if(rules, [&](const Rule& rule) { return rule.pattern == pattern; });
return rules.size() != old_size;
}
std::string URLReplacer::Apply(const std::string& url) const {
std::string result = url;
for (const auto& rule : rules) {
if (boost::regex_search(result, rule.regex)) {
result = boost::regex_replace(result, rule.regex, rule.replacement,
boost::match_default | boost::format_all);
LOG_WARNING(Service_HTTP, "rule \"{}\" has replaced URL \"{}\" to \"{}\"", rule.pattern,
url, result);
break;
}
}
return result;
}
bool URLReplacer::Save() {
const std::string path{fmt::format("{}/http_hle_replace_rules.txt",
FileUtil::GetUserPath(FileUtil::UserPath::SysDataDir))};
FileUtil::IOFile f(path, "wb");
for (const auto& rule : rules) {
if ((f.WriteLine(rule.pattern) != rule.pattern.size() + 1) ||
(f.WriteLine(rule.replacement) != rule.replacement.size() + 1)) {
LOG_ERROR(Service_HTTP, "failed to write URL replacement rules");
f.Close();
FileUtil::Delete(path);
return false;
}
}
return true;
}
HTTP_C::HTTP_C() : ServiceFramework("http:C", 32) {
static const FunctionInfo functions[] = {
// clang-format off
@ -2231,6 +2388,9 @@ HTTP_C::HTTP_C() : ServiceFramework("http:C", 32) {
{0x0037, &HTTP_C::SetKeepAlive, "SetKeepAlive"},
{0x0038, &HTTP_C::SetPostDataTypeSize, "SetPostDataTypeSize"},
{0x0039, &HTTP_C::Finalize, "Finalize"},
// Custom
{0x0C00, &HTTP_C::RegisterURLReplacement, "RegisterURLReplacement"},
{0x0C01, &HTTP_C::UnregisterURLReplacement, "UnregisterURLReplacement"},
// clang-format on
};
RegisterHandlers(functions);

View File

@ -11,6 +11,7 @@
#include <unordered_map>
#include <vector>
#include <boost/optional.hpp>
#include <boost/regex.hpp>
#include <boost/serialization/optional.hpp>
#include <boost/serialization/shared_ptr.hpp>
#include <boost/serialization/string.hpp>
@ -55,6 +56,8 @@ enum class RequestState : u8 {
ConnectingToServer = 0x5,
/// Request in progress, sending HTTP request.
/// HTTPC stays in this state when there is POST
/// data pending.
SendingRequest = 0x6,
// Request in progress, receiving HTTP response and headers.
@ -160,6 +163,28 @@ struct ClCertAData {
bool init = false;
};
class URLReplacer {
private:
struct Rule {
boost::regex regex;
std::string pattern;
std::string replacement;
};
std::vector<Rule> rules;
public:
URLReplacer();
bool HasRule(const std::string& pattern);
bool AddRule(const std::string& pattern, const std::string& replacement);
bool DeleteRule(const std::string& pattern);
std::string Apply(const std::string& url) const;
bool Save();
};
/// Represents an HTTP context.
class Context final {
public:
@ -274,6 +299,7 @@ public:
u32 socket_buffer_size;
std::vector<RequestHeader> headers;
const ClCertAData* clcert_data;
const URLReplacer* url_replacer;
bool post_data_added = false;
bool post_pending_request = false;
Params post_data;
@ -864,6 +890,10 @@ private:
*/
void Finalize(Kernel::HLERequestContext& ctx);
void RegisterURLReplacement(Kernel::HLERequestContext& ctx);
void UnregisterURLReplacement(Kernel::HLERequestContext& ctx);
[[nodiscard]] SessionData* EnsureSessionInitialized(Kernel::HLERequestContext& ctx,
IPC::RequestParser rp);
@ -898,6 +928,8 @@ private:
ClCertAData ClCertA;
URLReplacer url_replacer;
private:
template <class Archive>
void serialize(Archive& ar, const unsigned int) {

View File

@ -29,6 +29,8 @@ constexpr std::size_t IgnoreFrames = 5;
namespace Core {
bool PerfStats::game_frames_updated = true;
PerfStats::PerfStats(u64 title_id) : title_id(title_id) {}
PerfStats::~PerfStats() {
@ -109,6 +111,7 @@ void PerfStats::EndGameFrame() {
std::scoped_lock lock{object_mutex};
game_frames += 1;
PerfStats::game_frames_updated = true;
}
double PerfStats::GetMeanFrametime() const {

View File

@ -120,6 +120,8 @@ public:
artic_events.Set(event, set);
}
}
/// Boolean representing whether game_frames has been updated since last time it was presented
static bool game_frames_updated;
private:
mutable std::mutex object_mutex;

View File

@ -371,10 +371,6 @@ const Pica::PicaCore& GPU::PicaCore() const {
return impl->pica;
}
Pica::DebugContext& GPU::DebugContext() {
return *Pica::g_debug_context;
}
GraphicsDebugger& GPU::Debugger() {
return impl->gpu_debugger;
}
@ -490,13 +486,13 @@ void GPU::MemoryTransfer() {
}
void GPU::VBlankCallback(std::uintptr_t user_data, s64 cycles_late) {
// Present renderered frame.
impl->renderer->SwapBuffers();
// Signal to GSP that GPU interrupt has occurred
impl->signal_interrupt(Service::GSP::InterruptId::PDC0, 0);
impl->signal_interrupt(Service::GSP::InterruptId::PDC1, 0);
// Present renderered frame.
impl->renderer->SwapBuffers();
// Reschedule recurrent event
impl->timing.ScheduleEvent(FRAME_TICKS - cycles_late, impl->vblank_event);
}

View File

@ -84,9 +84,6 @@ public:
/// Returns an immutable reference to the PICA GPU.
[[nodiscard]] const Pica::PicaCore& PicaCore() const;
/// Returns a mutable reference to the pica debugging context.
[[nodiscard]] Pica::DebugContext& DebugContext();
/// Returns a mutable reference to the GSP command debugger.
[[nodiscard]] GraphicsDebugger& Debugger();

View File

@ -71,12 +71,28 @@ RasterizerCache<T>::RasterizerCache(Memory::MemorySystem& memory_,
auto& null_surface = slot_surfaces[NULL_SURFACE_ID];
runtime.ClearTexture(null_surface, {
.texture_level = 0,
.texture_layer = 0,
.texture_rect = null_surface.GetScaledRect(),
.value =
{
.color = {0.f, 0.f, 0.f, 0.f},
},
});
auto& null_surface_cube = slot_surfaces[NULL_SURFACE_CUBE_ID];
// Clear all cubemap faces
for (u32 cube_layer_index = 0; cube_layer_index < 6; ++cube_layer_index) {
runtime.ClearTexture(null_surface_cube,
{
.texture_level = 0,
.texture_layer = cube_layer_index,
.texture_rect = null_surface_cube.GetScaledRect(),
.value =
{
.color = {0.f, 0.f, 0.f, 0.f},
},
});
}
}
template <class T>
@ -1151,14 +1167,14 @@ void RasterizerCache<T>::DownloadFillSurface(Surface& surface, SurfaceInterval i
const u32 flush_end = boost::icl::last_next(interval);
ASSERT(flush_start >= surface.addr && flush_end <= surface.end);
MemoryRef dest_ptr = memory.GetPhysicalRef(flush_start);
MemoryRef dest_ptr = memory.GetPhysicalRef(surface.addr);
if (!dest_ptr) [[unlikely]] {
return;
}
const u32 start_offset = flush_start - surface.addr;
const u32 download_size =
std::clamp(flush_end - flush_start, 0u, static_cast<u32>(dest_ptr.GetSize()));
const u32 end_offset =
std::clamp(flush_end - surface.addr, 0u, static_cast<u32>(dest_ptr.GetSize()));
const u32 coarse_start_offset = start_offset - (start_offset % surface.fill_size);
const u32 backup_bytes = start_offset % surface.fill_size;
@ -1167,9 +1183,9 @@ void RasterizerCache<T>::DownloadFillSurface(Surface& surface, SurfaceInterval i
std::memcpy(backup_data.data(), &dest_ptr[coarse_start_offset], backup_bytes);
}
for (u32 offset = coarse_start_offset; offset < download_size; offset += surface.fill_size) {
for (u32 offset = coarse_start_offset; offset < end_offset; offset += surface.fill_size) {
std::memcpy(&dest_ptr[offset], &surface.fill_data[0],
std::min(surface.fill_size, download_size - offset));
std::min(surface.fill_size, end_offset - offset));
}
if (backup_bytes) {

View File

@ -1,4 +1,4 @@
// Copyright 2023 Citra Emulator Project
// Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
@ -30,6 +30,7 @@ union ClearValue {
struct TextureClear {
u32 texture_level;
u32 texture_layer;
Common::Rectangle<u32> texture_rect;
ClearValue value;
};

View File

@ -88,6 +88,7 @@ bool BlitHelper::ConvertDS24S8ToRGBA8(Surface& source, Surface& dest,
SCOPE_EXIT({ prev_state.Apply(); });
state.texture_units[0].texture_2d = source.Handle();
state.texture_units[0].target = GL_TEXTURE_2D;
state.texture_units[0].sampler = 0;
state.texture_units[1].sampler = 0;
@ -103,6 +104,7 @@ bool BlitHelper::ConvertDS24S8ToRGBA8(Surface& source, Surface& dest,
temp_tex.Release();
temp_tex.Create();
state.texture_units[1].texture_2d = temp_tex.handle;
state.texture_units[1].target = GL_TEXTURE_2D;
state.Apply();
glActiveTexture(GL_TEXTURE1);
glTexStorage2D(GL_TEXTURE_2D, 1, GL_DEPTH24_STENCIL8, temp_extent.width,
@ -111,6 +113,7 @@ bool BlitHelper::ConvertDS24S8ToRGBA8(Surface& source, Surface& dest,
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
}
state.texture_units[1].texture_2d = temp_tex.handle;
state.texture_units[1].target = GL_TEXTURE_2D;
state.Apply();
glActiveTexture(GL_TEXTURE1);
@ -145,6 +148,7 @@ bool BlitHelper::ConvertRGBA4ToRGB5A1(Surface& source, Surface& dest,
SCOPE_EXIT({ prev_state.Apply(); });
state.texture_units[0].texture_2d = source.Handle();
state.texture_units[0].target = GL_TEXTURE_2D;
const Common::Rectangle src_rect{copy.src_offset.x, copy.src_offset.y + copy.extent.height,
copy.src_offset.x + copy.extent.width, copy.src_offset.x};
@ -206,6 +210,7 @@ void BlitHelper::FilterAnime4K(Surface& surface, const VideoCore::TextureBlit& b
texture.fbo.Create();
texture.tex.Create();
state.texture_units[1].texture_2d = texture.tex.handle;
state.texture_units[1].target = GL_TEXTURE_2D;
state.draw.draw_framebuffer = texture.fbo.handle;
state.Apply();
glActiveTexture(GL_TEXTURE1);
@ -228,6 +233,10 @@ void BlitHelper::FilterAnime4K(Surface& surface, const VideoCore::TextureBlit& b
state.texture_units[1].texture_2d = LUMAD.tex.handle;
state.texture_units[2].texture_2d = XY.tex.handle;
state.texture_units[0].target = GL_TEXTURE_2D;
state.texture_units[1].target = GL_TEXTURE_2D;
state.texture_units[2].target = GL_TEXTURE_2D;
// gradient x pass
Draw(gradient_x_program, XY.tex.handle, XY.fbo.handle, 0, temp_rect);
@ -249,6 +258,7 @@ void BlitHelper::FilterBicubic(Surface& surface, const VideoCore::TextureBlit& b
const OpenGLState prev_state = OpenGLState::GetCurState();
SCOPE_EXIT({ prev_state.Apply(); });
state.texture_units[0].texture_2d = surface.Handle(0);
state.texture_units[0].target = GL_TEXTURE_2D;
SetParams(bicubic_program, surface.RealExtent(false), blit.src_rect);
Draw(bicubic_program, surface.Handle(), draw_fbo.handle, blit.dst_level, blit.dst_rect);
}
@ -257,6 +267,7 @@ void BlitHelper::FilterScaleForce(Surface& surface, const VideoCore::TextureBlit
const OpenGLState prev_state = OpenGLState::GetCurState();
SCOPE_EXIT({ prev_state.Apply(); });
state.texture_units[0].texture_2d = surface.Handle(0);
state.texture_units[0].target = GL_TEXTURE_2D;
SetParams(scale_force_program, surface.RealExtent(false), blit.src_rect);
Draw(scale_force_program, surface.Handle(), draw_fbo.handle, blit.dst_level, blit.dst_rect);
}
@ -265,6 +276,7 @@ void BlitHelper::FilterXbrz(Surface& surface, const VideoCore::TextureBlit& blit
const OpenGLState prev_state = OpenGLState::GetCurState();
SCOPE_EXIT({ prev_state.Apply(); });
state.texture_units[0].texture_2d = surface.Handle(0);
state.texture_units[0].target = GL_TEXTURE_2D;
glProgramUniform1f(xbrz_program.handle, 2, static_cast<GLfloat>(surface.res_scale));
SetParams(xbrz_program, surface.RealExtent(false), blit.src_rect);
Draw(xbrz_program, surface.Handle(), draw_fbo.handle, blit.dst_level, blit.dst_rect);
@ -274,6 +286,7 @@ void BlitHelper::FilterMMPX(Surface& surface, const VideoCore::TextureBlit& blit
const OpenGLState prev_state = OpenGLState::GetCurState();
SCOPE_EXIT({ prev_state.Apply(); });
state.texture_units[0].texture_2d = surface.Handle(0);
state.texture_units[0].target = GL_TEXTURE_2D;
SetParams(mmpx_program, surface.RealExtent(false), blit.src_rect);
Draw(mmpx_program, surface.Handle(), draw_fbo.handle, blit.dst_level, blit.dst_rect);
}

View File

@ -717,6 +717,7 @@ void RasterizerOpenGL::SyncTextureUnits(const Framebuffer* framebuffer) {
if (!IsFeedbackLoop(texture_index, framebuffer, surface)) {
BindMaterial(texture_index, surface);
state.texture_units[texture_index].texture_2d = surface.Handle();
state.texture_units[texture_index].target = GL_TEXTURE_2D;
}
}

View File

@ -222,8 +222,9 @@ bool TextureRuntime::ClearTextureWithoutFbo(Surface& surface,
UNREACHABLE_MSG("Unknown surface type {}", surface.type);
}
glClearTexSubImage(surface.Handle(), clear.texture_level, clear.texture_rect.left,
clear.texture_rect.bottom, 0, clear.texture_rect.GetWidth(),
clear.texture_rect.GetHeight(), 1, format, type, &clear.value);
clear.texture_rect.bottom, clear.texture_layer,
clear.texture_rect.GetWidth(), clear.texture_rect.GetHeight(), 1, format,
type, &clear.value);
return true;
}
@ -245,7 +246,7 @@ void TextureRuntime::ClearTexture(Surface& surface, const VideoCore::TextureClea
state.draw.draw_framebuffer = draw_fbos[FboIndex(surface.type)].handle;
state.Apply();
surface.Attach(GL_DRAW_FRAMEBUFFER, clear.texture_level, 0);
surface.Attach(GL_DRAW_FRAMEBUFFER, clear.texture_level, clear.texture_layer);
switch (surface.type) {
case SurfaceType::Color:
@ -326,6 +327,7 @@ void TextureRuntime::GenerateMipmaps(Surface& surface) {
const auto generate = [&](u32 index) {
state.texture_units[0].texture_2d = surface.Handle(index);
state.texture_units[0].target = GL_TEXTURE_2D;
state.Apply();
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, surface.levels - 1);
glGenerateMipmap(GL_TEXTURE_2D);
@ -554,6 +556,7 @@ bool Surface::DownloadWithoutFbo(const VideoCore::BufferTextureCopy& download,
// that only support up to 4.3
OpenGLState state = OpenGLState::GetCurState();
state.texture_units[0].texture_2d = Handle(0);
state.texture_units[0].target = GL_TEXTURE_2D;
state.Apply();
glGetTexImage(GL_TEXTURE_2D, download.texture_level, tuple.format, tuple.type,

View File

@ -93,11 +93,26 @@ void RendererOpenGL::SwapBuffers() {
// Maintain the rasterizer's state as a priority
OpenGLState prev_state = OpenGLState::GetCurState();
state.Apply();
#ifdef ANDROID
if (secondary_window) {
secondaryWindowEnabled = true;
} else {
secondaryWindowEnabled = false;
}
#else
if (Settings::values.layout_option.GetValue() == Settings::LayoutOption::SeparateWindows) {
ASSERT(secondary_window);
secondaryWindowEnabled = true;
} else {
secondaryWindowEnabled = false;
}
#endif
render_window.SetupFramebuffer();
PrepareRendertarget();
RenderScreenshot();
isSecondaryWindow = false;
#ifdef HAVE_LIBRETRO
DrawScreens(render_window.GetFramebufferLayout(), false);
render_window.SwapBuffers();
@ -110,6 +125,7 @@ void RendererOpenGL::SwapBuffers() {
// it means we have a second display
if (secondary_window) {
const auto& secondary_layout = secondary_window->GetFramebufferLayout();
isSecondaryWindow = true;
RenderToMailbox(secondary_layout, secondary_window->mailbox, false);
secondary_window->PollEvents();
}
@ -117,6 +133,7 @@ void RendererOpenGL::SwapBuffers() {
if (Settings::values.layout_option.GetValue() == Settings::LayoutOption::SeparateWindows) {
ASSERT(secondary_window);
const auto& secondary_layout = secondary_window->GetFramebufferLayout();
isSecondaryWindow = true;
RenderToMailbox(secondary_layout, secondary_window->mailbox, false);
secondary_window->PollEvents();
}
@ -196,7 +213,8 @@ void RendererOpenGL::PrepareRendertarget() {
void RendererOpenGL::RenderToMailbox(const Layout::FramebufferLayout& layout,
std::unique_ptr<Frontend::TextureMailbox>& mailbox,
bool flipped) {
if (!Settings::values.use_skip_duplicate_frames.GetValue() ||
Core::PerfStats::game_frames_updated) {
Frontend::Frame* frame;
{
MICROPROFILE_SCOPE(OpenGL_WaitPresent);
@ -241,6 +259,11 @@ void RendererOpenGL::RenderToMailbox(const Layout::FramebufferLayout& layout,
glFlush();
mailbox->ReleaseRenderFrame(frame);
}
if ((secondaryWindowEnabled && isSecondaryWindow) || (!secondaryWindowEnabled)) {
Core::PerfStats::game_frames_updated = false;
}
}
}
/**

View File

@ -112,6 +112,8 @@ private:
GLuint attrib_tex_coord;
FrameDumperOpenGL frame_dumper;
bool isSecondaryWindow;
bool secondaryWindowEnabled;
};
} // namespace OpenGL

View File

@ -24,7 +24,6 @@
#include "video_core/host_shaders/vulkan_cursor_vert.h"
#include <vk_mem_alloc.h>
#if defined(__APPLE__) && !defined(HAVE_LIBRETRO)
#include "common/apple_utils.h"
#endif
@ -237,6 +236,8 @@ void RendererVulkan::PrepareDraw(Frame* frame, const Layout::FramebufferLayout&
void RendererVulkan::RenderToWindow(PresentWindow& window, const Layout::FramebufferLayout& layout,
bool flipped) {
if (!Settings::values.use_skip_duplicate_frames.GetValue() ||
Core::PerfStats::game_frames_updated) {
Frame* frame = window.GetRenderFrame();
if (layout.width != frame->width || layout.height != frame->height) {
@ -252,8 +253,12 @@ void RendererVulkan::RenderToWindow(PresentWindow& window, const Layout::Framebu
DrawScreens(frame, layout, flipped);
scheduler.Flush(frame->render_ready);
window.Present(frame);
if ((secondaryWindowEnabled && isSecondaryWindow) || (!secondaryWindowEnabled)) {
Core::PerfStats::game_frames_updated = false;
screenRendered = true;
}
}
}
void RendererVulkan::LoadFBToScreenInfo(const Pica::FramebufferConfig& framebuffer,
@ -660,6 +665,11 @@ void RendererVulkan::ConfigureFramebufferTexture(TextureInfo& texture,
}
void RendererVulkan::FillScreen(Common::Vec3<u8> color, const TextureInfo& texture) {
// When loading some 3GX extensions, FillScreen may be called before texture image is available
if (!texture.image) {
return;
}
const vk::ClearColorValue clear_color = {
.float32 =
std::array{
@ -1111,9 +1121,28 @@ void RendererVulkan::DrawCursor(const Layout::FramebufferLayout& layout) {
void RendererVulkan::SwapBuffers() {
system.perf_stats->StartSwap();
screenRendered = false;
#ifndef ANDROID
if (Settings::values.layout_option.GetValue() == Settings::LayoutOption::SeparateWindows) {
ASSERT(secondary_window);
secondaryWindowEnabled = true;
} else {
secondaryWindowEnabled = false;
}
#endif
#ifdef ANDROID
if (secondary_window) {
secondaryWindowEnabled = true;
} else {
secondaryWindowEnabled = false;
}
#endif
const Layout::FramebufferLayout& layout = render_window.GetFramebufferLayout();
PrepareRendertarget();
RenderScreenshot();
isSecondaryWindow = false;
RenderToWindow(main_present_window, layout, false);
#ifndef ANDROID
if (Settings::values.layout_option.GetValue() == Settings::LayoutOption::SeparateWindows) {
@ -1123,6 +1152,7 @@ void RendererVulkan::SwapBuffers() {
secondary_present_window_ptr = std::make_unique<PresentWindow>(
*secondary_window, instance, scheduler, IsLowRefreshRate());
}
isSecondaryWindow = true;
RenderToWindow(*secondary_present_window_ptr, secondary_layout, false);
secondary_window->PollEvents();
}
@ -1135,10 +1165,14 @@ void RendererVulkan::SwapBuffers() {
secondary_present_window_ptr = std::make_unique<PresentWindow>(
*secondary_window, instance, scheduler, IsLowRefreshRate());
}
isSecondaryWindow = true;
RenderToWindow(*secondary_present_window_ptr, secondary_layout, false);
secondary_window->PollEvents();
}
#endif
if (!screenRendered) {
scheduler.Finish();
}
system.perf_stats->EndSwap();
rasterizer.TickFrame();
@ -1268,6 +1302,15 @@ void RendererVulkan::RenderScreenshotWithStagingCopy() {
// Copy backing image data to the QImage screenshot buffer
std::memcpy(settings.screenshot_bits, alloc_info.pMappedData, staging_buffer_info.size);
// QImage::Format_RGB32 expects BGRA byte order. If the swapchain format is RGBA,
// swap R and B channels so the screenshot colors are correct.
if (main_present_window.GetSurfaceFormat() == vk::Format::eR8G8B8A8Unorm) {
u8* pixels = static_cast<u8*>(settings.screenshot_bits);
for (u32 i = 0; i < width * height; i++) {
std::swap(pixels[i * 4 + 0], pixels[i * 4 + 2]);
}
}
// Destroy allocated resources
vmaDestroyBuffer(instance.GetAllocator(), staging_buffer, allocation);
vmaDestroyImage(instance.GetAllocator(), frame.image, frame.allocation);
@ -1423,6 +1466,15 @@ bool RendererVulkan::TryRenderScreenshotWithHostMemory() {
// Ensure the copy is fully completed before saving the screenshot
scheduler.Finish();
// QImage::Format_RGB32 expects BGRA byte order. If the swapchain format is RGBA,
// swap R and B channels so the screenshot colors are correct.
if (main_present_window.GetSurfaceFormat() == vk::Format::eR8G8B8A8Unorm) {
u8* pixels = static_cast<u8*>(settings.screenshot_bits);
for (u32 i = 0; i < width * height; i++) {
std::swap(pixels[i * 4 + 0], pixels[i * 4 + 2]);
}
}
// Image data has been copied directly to host memory
device.destroyFramebuffer(frame.framebuffer);
device.destroyImageView(frame.image_view);

View File

@ -134,7 +134,6 @@ private:
DescriptorUpdateQueue update_queue;
RasterizerVulkan rasterizer;
std::unique_ptr<PresentWindow> secondary_present_window_ptr;
DescriptorHeap present_heap;
vk::UniquePipelineLayout present_pipeline_layout;
std::array<vk::Pipeline, PRESENT_PIPELINES> present_pipelines;
@ -151,6 +150,9 @@ private:
vk::ShaderModule cursor_fragment_shader{};
vk::Pipeline cursor_pipeline{};
vk::UniquePipelineLayout cursor_pipeline_layout{};
bool isSecondaryWindow;
bool secondaryWindowEnabled;
bool screenRendered;
};
} // namespace Vulkan

View File

@ -63,6 +63,10 @@ public:
return swapchain.GetImageCount();
}
vk::Format GetSurfaceFormat() const noexcept {
return swapchain.GetSurfaceFormat().format;
}
private:
void PresentThread(std::stop_token token);

View File

@ -350,8 +350,8 @@ bool TextureRuntime::ClearTexture(Surface& surface, const VideoCore::TextureClea
.aspectMask = params.aspect,
.baseMipLevel = clear.texture_level,
.levelCount = 1,
.baseArrayLayer = 0,
.layerCount = VK_REMAINING_ARRAY_LAYERS,
.baseArrayLayer = clear.texture_layer,
.layerCount = 1,
};
const vk::ImageMemoryBarrier pre_barrier = {
@ -436,7 +436,8 @@ void TextureRuntime::ClearTextureWithRenderpass(Surface& surface,
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.image = params.src_image,
.subresourceRange = MakeSubresourceRange(params.aspect, clear.texture_level),
.subresourceRange =
MakeSubresourceRange(params.aspect, clear.texture_level, 1, clear.texture_layer),
};
const vk::ImageMemoryBarrier post_barrier = {