mirror of
https://github.com/Lime3DS/Lime3DS.git
synced 2026-07-09 17:24:49 -06:00
Compare commits
29 Commits
2126.0-alp
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0c19c33bd3 | ||
|
|
bda4671be6 | ||
|
|
c2dd5f3bea | ||
|
|
31e4ecea58 | ||
|
|
49ba2e8d5f | ||
|
|
b64040b5fa | ||
|
|
64a967b4a8 | ||
|
|
7e976e852c | ||
|
|
ad0dde0725 | ||
|
|
d61801ad76 | ||
|
|
9a0f0e3caf | ||
|
|
4f63caf5fc | ||
|
|
2a39345e4b | ||
|
|
209173fa0c | ||
|
|
8c4e8b77b2 | ||
|
|
fcb10fb899 | ||
|
|
77cb5cecd8 | ||
|
|
6b197e2c04 | ||
|
|
384913be70 | ||
|
|
307f558d28 | ||
|
|
d93adebb16 | ||
|
|
7c225c6abb | ||
|
|
93143bcb15 | ||
|
|
32fd435928 | ||
|
|
1d59c9a0fc | ||
|
|
9293767e1d | ||
|
|
c9d2593c2c | ||
|
|
9e43e451d1 | ||
|
|
0a269688a3 |
@ -32,6 +32,7 @@ foreach(KEY IN ITEMS
|
|||||||
"physical_device"
|
"physical_device"
|
||||||
"use_gles"
|
"use_gles"
|
||||||
"renderer_debug"
|
"renderer_debug"
|
||||||
|
"pica_debugging"
|
||||||
"dump_command_buffers"
|
"dump_command_buffers"
|
||||||
"spirv_shader_gen"
|
"spirv_shader_gen"
|
||||||
"disable_spirv_optimizer"
|
"disable_spirv_optimizer"
|
||||||
@ -41,6 +42,7 @@ foreach(KEY IN ITEMS
|
|||||||
"use_disk_shader_cache"
|
"use_disk_shader_cache"
|
||||||
"shaders_accurate_mul"
|
"shaders_accurate_mul"
|
||||||
"use_vsync"
|
"use_vsync"
|
||||||
|
"use_skip_duplicate_frames"
|
||||||
"use_display_refresh_rate_detection"
|
"use_display_refresh_rate_detection"
|
||||||
"use_shader_jit"
|
"use_shader_jit"
|
||||||
"resolution_factor"
|
"resolution_factor"
|
||||||
@ -239,6 +241,7 @@ if (ANDROID)
|
|||||||
"screen_orientation"
|
"screen_orientation"
|
||||||
"performance_overlay_position"
|
"performance_overlay_position"
|
||||||
"enable_secondary_display"
|
"enable_secondary_display"
|
||||||
|
"combo_button_buttons"
|
||||||
)
|
)
|
||||||
string(REPLACE "_" "_1" KEY_JNI_ESCAPED ${KEY})
|
string(REPLACE "_" "_1" KEY_JNI_ESCAPED ${KEY})
|
||||||
set(SETTING_KEY_LIST "${SETTING_KEY_LIST}\n\"${KEY}\",")
|
set(SETTING_KEY_LIST "${SETTING_KEY_LIST}\n\"${KEY}\",")
|
||||||
|
|||||||
299
dist/dumpkeys/DumpKeys.gm9
vendored
299
dist/dumpkeys/DumpKeys.gm9
vendored
@ -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
|
|
||||||
|
|
||||||
10
dist/dumpkeys/README.md
vendored
10
dist/dumpkeys/README.md
vendored
@ -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
2
externals/dynarmic
vendored
@ -1 +1 @@
|
|||||||
Subproject commit 526227eebe1efff3fb14dbf494b9c5b44c2e9c1f
|
Subproject commit e77b1ba0b7da7cbe93021b01a663acfe7c4dd516
|
||||||
@ -1,6 +1,7 @@
|
|||||||
<component name="InspectionProjectProfileManager">
|
<component name="InspectionProjectProfileManager">
|
||||||
<profile version="1.0">
|
<profile version="1.0">
|
||||||
<option name="myName" value="Project Default" />
|
<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="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="GrazieStyle" enabled="false" level="STYLE_SUGGESTION" enabled_by_default="false" />
|
||||||
<inspection_tool class="LanguageDetectionInspection" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
|
<inspection_tool class="LanguageDetectionInspection" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
|
||||||
|
|||||||
@ -12,5 +12,6 @@ enum class Hotkey(val button: Int) {
|
|||||||
QUICKSAVE(10005),
|
QUICKSAVE(10005),
|
||||||
QUICKLOAD(10006),
|
QUICKLOAD(10006),
|
||||||
TURBO_LIMIT(10007),
|
TURBO_LIMIT(10007),
|
||||||
ENABLE(10008)
|
ENABLE(10008),
|
||||||
|
COMBO_BUTTON(10009)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -14,6 +14,7 @@ import org.citra.citra_emu.R
|
|||||||
import org.citra.citra_emu.display.ScreenAdjustmentUtil
|
import org.citra.citra_emu.display.ScreenAdjustmentUtil
|
||||||
import org.citra.citra_emu.features.settings.model.Settings
|
import org.citra.citra_emu.features.settings.model.Settings
|
||||||
import org.citra.citra_emu.features.settings.model.view.InputBindingSetting
|
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.EmulationLifecycleUtil
|
||||||
import org.citra.citra_emu.utils.TurboHelper
|
import org.citra.citra_emu.utils.TurboHelper
|
||||||
|
|
||||||
@ -71,12 +72,17 @@ class HotkeyUtility(
|
|||||||
var handled = false
|
var handled = false
|
||||||
val buttonSet = InputBindingSetting.getButtonSet(keyEvent)
|
val buttonSet = InputBindingSetting.getButtonSet(keyEvent)
|
||||||
val thisKeyIsEnableButton = buttonSet.contains(Hotkey.ENABLE.button)
|
val thisKeyIsEnableButton = buttonSet.contains(Hotkey.ENABLE.button)
|
||||||
|
val thisKeyIsComboButton = buttonSet.contains(Hotkey.COMBO_BUTTON.button)
|
||||||
val thisKeyIsHotkey =
|
val thisKeyIsHotkey =
|
||||||
!thisKeyIsEnableButton && Hotkey.entries.any { buttonSet.contains(it.button) }
|
!thisKeyIsEnableButton && Hotkey.entries.any { buttonSet.contains(it.button) }
|
||||||
if (thisKeyIsEnableButton) {
|
if (thisKeyIsEnableButton) {
|
||||||
handled = true
|
handled = true
|
||||||
hotkeyIsEnabled = false
|
hotkeyIsEnabled = false
|
||||||
}
|
}
|
||||||
|
if (thisKeyIsComboButton) {
|
||||||
|
ComboHelper.comboActivate(NativeLibrary.ButtonState.RELEASED)
|
||||||
|
handled = true
|
||||||
|
}
|
||||||
|
|
||||||
for (button in buttonSet) {
|
for (button in buttonSet) {
|
||||||
// this is a hotkey button
|
// this is a hotkey button
|
||||||
@ -142,6 +148,10 @@ class HotkeyUtility(
|
|||||||
).show()
|
).show()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Hotkey.COMBO_BUTTON.button -> {
|
||||||
|
ComboHelper.comboActivate(NativeLibrary.ButtonState.PRESSED)
|
||||||
|
}
|
||||||
|
|
||||||
else -> {}
|
else -> {}
|
||||||
}
|
}
|
||||||
hotkeyIsPressed = true
|
hotkeyIsPressed = true
|
||||||
|
|||||||
@ -39,6 +39,7 @@ object SettingKeys {
|
|||||||
external fun use_disk_shader_cache(): String
|
external fun use_disk_shader_cache(): String
|
||||||
external fun shaders_accurate_mul(): String
|
external fun shaders_accurate_mul(): String
|
||||||
external fun use_vsync(): String
|
external fun use_vsync(): String
|
||||||
|
external fun use_skip_duplicate_frames(): String
|
||||||
external fun use_shader_jit(): String
|
external fun use_shader_jit(): String
|
||||||
external fun resolution_factor(): String
|
external fun resolution_factor(): String
|
||||||
external fun frame_limit(): String
|
external fun frame_limit(): String
|
||||||
@ -142,4 +143,5 @@ object SettingKeys {
|
|||||||
external fun screen_orientation(): String
|
external fun screen_orientation(): String
|
||||||
external fun performance_overlay_position(): String
|
external fun performance_overlay_position(): String
|
||||||
external fun enable_secondary_display(): String
|
external fun enable_secondary_display(): String
|
||||||
|
external fun combo_button_buttons(): String
|
||||||
}
|
}
|
||||||
|
|||||||
@ -97,6 +97,11 @@ enum class BooleanSetting(
|
|||||||
HW_SHADER(SettingKeys.use_hw_shader(), Settings.SECTION_RENDERER, true),
|
HW_SHADER(SettingKeys.use_hw_shader(), Settings.SECTION_RENDERER, true),
|
||||||
SHADER_JIT(SettingKeys.use_shader_jit(), Settings.SECTION_RENDERER, true),
|
SHADER_JIT(SettingKeys.use_shader_jit(), Settings.SECTION_RENDERER, true),
|
||||||
VSYNC(SettingKeys.use_vsync(), Settings.SECTION_RENDERER, false),
|
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),
|
USE_FRAME_LIMIT(SettingKeys.use_frame_limit(), Settings.SECTION_RENDERER, true),
|
||||||
DEBUG_RENDERER(SettingKeys.renderer_debug(), Settings.SECTION_DEBUG, false),
|
DEBUG_RENDERER(SettingKeys.renderer_debug(), Settings.SECTION_DEBUG, false),
|
||||||
DISABLE_RIGHT_EYE_RENDER(
|
DISABLE_RIGHT_EYE_RENDER(
|
||||||
@ -123,7 +128,7 @@ enum class BooleanSetting(
|
|||||||
SIMULATE_3DS_GPU_TIMINGS(
|
SIMULATE_3DS_GPU_TIMINGS(
|
||||||
SettingKeys.simulate_3ds_gpu_timings(),
|
SettingKeys.simulate_3ds_gpu_timings(),
|
||||||
Settings.SECTION_RENDERER,
|
Settings.SECTION_RENDERER,
|
||||||
true
|
false
|
||||||
);
|
);
|
||||||
|
|
||||||
override var boolean: Boolean = defaultValue
|
override var boolean: Boolean = defaultValue
|
||||||
|
|||||||
@ -18,6 +18,16 @@ enum class IntListSetting(
|
|||||||
Settings.SECTION_LAYOUT,
|
Settings.SECTION_LAYOUT,
|
||||||
listOf(0, 1, 2, 3, 4, 5),
|
listOf(0, 1, 2, 3, 4, 5),
|
||||||
canBeEmpty = false
|
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
|
private var backingList: List<Int> = defaultValue
|
||||||
|
|||||||
@ -142,6 +142,7 @@ class Settings {
|
|||||||
const val HOTKEY_QUICKSAVE = "hotkey_quickload"
|
const val HOTKEY_QUICKSAVE = "hotkey_quickload"
|
||||||
const val HOTKEY_QUICKLOAD = "hotkey_quickpause"
|
const val HOTKEY_QUICKLOAD = "hotkey_quickpause"
|
||||||
const val HOTKEY_TURBO_LIMIT = "hotkey_turbo_limit"
|
const val HOTKEY_TURBO_LIMIT = "hotkey_turbo_limit"
|
||||||
|
const val HOTKEY_BUTTON_COMBO = "hotkey_button_combo"
|
||||||
|
|
||||||
val buttonKeys = listOf(
|
val buttonKeys = listOf(
|
||||||
KEY_BUTTON_A,
|
KEY_BUTTON_A,
|
||||||
@ -209,7 +210,8 @@ class Settings {
|
|||||||
HOTKEY_PAUSE_OR_RESUME,
|
HOTKEY_PAUSE_OR_RESUME,
|
||||||
HOTKEY_QUICKSAVE,
|
HOTKEY_QUICKSAVE,
|
||||||
HOTKEY_QUICKLOAD,
|
HOTKEY_QUICKLOAD,
|
||||||
HOTKEY_TURBO_LIMIT
|
HOTKEY_TURBO_LIMIT,
|
||||||
|
HOTKEY_BUTTON_COMBO
|
||||||
)
|
)
|
||||||
val hotkeyTitles = listOf(
|
val hotkeyTitles = listOf(
|
||||||
R.string.controller_hotkey_enable_button,
|
R.string.controller_hotkey_enable_button,
|
||||||
@ -219,7 +221,8 @@ class Settings {
|
|||||||
R.string.emulation_toggle_pause,
|
R.string.emulation_toggle_pause,
|
||||||
R.string.emulation_quicksave,
|
R.string.emulation_quicksave,
|
||||||
R.string.emulation_quickload,
|
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
|
// TODO: Move these in with the other setting keys in GenerateSettingKeys.cmake
|
||||||
|
|||||||
@ -127,6 +127,7 @@ class InputBindingSetting(val abstractSetting: AbstractSetting, titleId: Int) :
|
|||||||
Settings.HOTKEY_QUICKSAVE -> Hotkey.QUICKSAVE.button
|
Settings.HOTKEY_QUICKSAVE -> Hotkey.QUICKSAVE.button
|
||||||
Settings.HOTKEY_QUICKLOAD -> Hotkey.QUICKLOAD.button
|
Settings.HOTKEY_QUICKLOAD -> Hotkey.QUICKLOAD.button
|
||||||
Settings.HOTKEY_TURBO_LIMIT -> Hotkey.TURBO_LIMIT.button
|
Settings.HOTKEY_TURBO_LIMIT -> Hotkey.TURBO_LIMIT.button
|
||||||
|
Settings.HOTKEY_BUTTON_COMBO -> Hotkey.COMBO_BUTTON.button
|
||||||
else -> -1
|
else -> -1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -799,6 +799,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
|
|||||||
|
|
||||||
private fun addControlsSettings(sl: ArrayList<SettingsItem>) {
|
private fun addControlsSettings(sl: ArrayList<SettingsItem>) {
|
||||||
settingsActivity.setToolbarTitle(settingsActivity.getString(R.string.preferences_controls))
|
settingsActivity.setToolbarTitle(settingsActivity.getString(R.string.preferences_controls))
|
||||||
|
|
||||||
sl.apply {
|
sl.apply {
|
||||||
add(
|
add(
|
||||||
RunnableSetting(
|
RunnableSetting(
|
||||||
@ -810,6 +811,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
|
|||||||
onLongClick = { settingsAdapter.onLongClickAutoMap() }
|
onLongClick = { settingsAdapter.onLongClickAutoMap() }
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
add(HeaderSetting(R.string.generic_buttons))
|
add(HeaderSetting(R.string.generic_buttons))
|
||||||
Settings.buttonKeys.forEachIndexed { i: Int, key: String ->
|
Settings.buttonKeys.forEachIndexed { i: Int, key: String ->
|
||||||
val button = getInputObject(key)
|
val button = getInputObject(key)
|
||||||
@ -860,6 +862,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
|
|||||||
val button = getInputObject(key)
|
val button = getInputObject(key)
|
||||||
add(InputBindingSetting(button, Settings.hotkeyTitles[i]))
|
add(InputBindingSetting(button, Settings.hotkeyTitles[i]))
|
||||||
}
|
}
|
||||||
|
|
||||||
add(HeaderSetting(R.string.miscellaneous))
|
add(HeaderSetting(R.string.miscellaneous))
|
||||||
add(
|
add(
|
||||||
SwitchSetting(
|
SwitchSetting(
|
||||||
@ -870,6 +873,18 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
|
|||||||
BooleanSetting.USE_ARTIC_BASE_CONTROLLER.defaultValue
|
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
|
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
|
// Disabled until custom texture implementation gets rewrite, current one overloads RAM
|
||||||
// and crashes Citra.
|
// and crashes Citra.
|
||||||
|
|||||||
@ -69,6 +69,7 @@ import org.citra.citra_emu.display.PortraitScreenLayout
|
|||||||
import org.citra.citra_emu.display.ScreenAdjustmentUtil
|
import org.citra.citra_emu.display.ScreenAdjustmentUtil
|
||||||
import org.citra.citra_emu.display.ScreenLayout
|
import org.citra.citra_emu.display.ScreenLayout
|
||||||
import org.citra.citra_emu.display.SecondaryDisplayLayout
|
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.BooleanSetting
|
||||||
import org.citra.citra_emu.features.settings.model.IntSetting
|
import org.citra.citra_emu.features.settings.model.IntSetting
|
||||||
import org.citra.citra_emu.features.settings.model.SettingsViewModel
|
import org.citra.citra_emu.features.settings.model.SettingsViewModel
|
||||||
@ -862,6 +863,16 @@ class EmulationFragment :
|
|||||||
true
|
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 -> {
|
R.id.menu_emulation_adjust_opacity -> {
|
||||||
showAdjustOpacityDialog()
|
showAdjustOpacityDialog()
|
||||||
true
|
true
|
||||||
@ -1241,13 +1252,13 @@ class EmulationFragment :
|
|||||||
|
|
||||||
private fun showToggleControlsDialog() {
|
private fun showToggleControlsDialog() {
|
||||||
val editor = preferences.edit()
|
val editor = preferences.edit()
|
||||||
val enabledButtons = BooleanArray(16)
|
val enabledButtons = BooleanArray(17)
|
||||||
enabledButtons.forEachIndexed { i: Int, _: Boolean ->
|
enabledButtons.forEachIndexed { i: Int, _: Boolean ->
|
||||||
// Buttons that are disabled by default
|
// Buttons that are disabled by default
|
||||||
var defaultValue = true
|
var defaultValue = true
|
||||||
when (i) {
|
when (i) {
|
||||||
// TODO: Remove these magic numbers
|
// 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)
|
enabledButtons[i] = preferences.getBoolean("buttonToggle$i", defaultValue)
|
||||||
}
|
}
|
||||||
@ -1282,18 +1293,25 @@ class EmulationFragment :
|
|||||||
val sliderBinding = DialogSliderBinding.inflate(layoutInflater)
|
val sliderBinding = DialogSliderBinding.inflate(layoutInflater)
|
||||||
|
|
||||||
sliderBinding.apply {
|
sliderBinding.apply {
|
||||||
slider.valueTo = 150f
|
val sliderStart = 50
|
||||||
slider.valueFrom = 0f
|
val sliderMin = 0
|
||||||
slider.value = preferences.getInt(target, 50).toFloat()
|
val sliderMax = 150
|
||||||
textValue.setText((slider.value + 50).toInt().toString())
|
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 {
|
textValue.addTextChangedListener(object : TextWatcher {
|
||||||
override fun afterTextChanged(s: Editable) {
|
override fun afterTextChanged(s: Editable) {
|
||||||
val value = s.toString().toIntOrNull()
|
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"
|
textInput.error = "Inappropriate Value"
|
||||||
} else {
|
} else {
|
||||||
textInput.error = null
|
textInput.error = null
|
||||||
slider.value = value.toFloat() - 50
|
slider.value = realValue.toFloat()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1306,8 +1324,10 @@ class EmulationFragment :
|
|||||||
progress: Float,
|
progress: Float,
|
||||||
_: Boolean
|
_: Boolean
|
||||||
->
|
->
|
||||||
if (textValue.text.toString() != (slider.value + 50).toInt().toString()) {
|
if (textValue.text.toString() !=
|
||||||
textValue.setText((slider.value + 50).toInt().toString())
|
(slider.value + sliderStart).toInt().toString()
|
||||||
|
) {
|
||||||
|
textValue.setText((slider.value + sliderStart).toInt().toString())
|
||||||
textValue.setSelection(textValue.length())
|
textValue.setSelection(textValue.length())
|
||||||
setControlScale(slider.value.toInt(), target)
|
setControlScale(slider.value.toInt(), target)
|
||||||
}
|
}
|
||||||
@ -1415,6 +1435,8 @@ class EmulationFragment :
|
|||||||
resetScale("controlScale-" + NativeLibrary.ButtonType.STICK_C)
|
resetScale("controlScale-" + NativeLibrary.ButtonType.STICK_C)
|
||||||
resetScale("controlScale-" + NativeLibrary.ButtonType.BUTTON_HOME)
|
resetScale("controlScale-" + NativeLibrary.ButtonType.BUTTON_HOME)
|
||||||
resetScale("controlScale-" + NativeLibrary.ButtonType.BUTTON_SWAP)
|
resetScale("controlScale-" + NativeLibrary.ButtonType.BUTTON_SWAP)
|
||||||
|
resetScale("controlScale-" + NativeLibrary.ButtonType.BUTTON_TURBO)
|
||||||
|
resetScale("controlScale-" + Hotkey.COMBO_BUTTON.button)
|
||||||
binding.surfaceInputOverlay.refreshControls()
|
binding.surfaceInputOverlay.refreshControls()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1442,10 +1464,11 @@ class EmulationFragment :
|
|||||||
.apply()
|
.apply()
|
||||||
|
|
||||||
val editor = preferences.edit()
|
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
|
var defaultValue = true
|
||||||
when (i) {
|
when (i) {
|
||||||
6, 7, 12, 13, 14, 15 -> defaultValue = false
|
6, 7, 12, 13, 14, 15, 16 -> defaultValue = false
|
||||||
}
|
}
|
||||||
editor.putBoolean("buttonToggle$i", defaultValue)
|
editor.putBoolean("buttonToggle$i", defaultValue)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -26,6 +26,8 @@ import kotlin.math.min
|
|||||||
import org.citra.citra_emu.CitraApplication
|
import org.citra.citra_emu.CitraApplication
|
||||||
import org.citra.citra_emu.NativeLibrary
|
import org.citra.citra_emu.NativeLibrary
|
||||||
import org.citra.citra_emu.R
|
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.EmulationMenuSettings
|
||||||
import org.citra.citra_emu.utils.TurboHelper
|
import org.citra.citra_emu.utils.TurboHelper
|
||||||
|
|
||||||
@ -184,6 +186,8 @@ class InputOverlay(context: Context?, attrs: AttributeSet?) :
|
|||||||
button.status == NativeLibrary.ButtonState.PRESSED
|
button.status == NativeLibrary.ButtonState.PRESSED
|
||||||
) {
|
) {
|
||||||
TurboHelper.toggleTurbo(true)
|
TurboHelper.toggleTurbo(true)
|
||||||
|
} else if (button.id == Hotkey.COMBO_BUTTON.button) {
|
||||||
|
ComboHelper.comboActivate(button.status)
|
||||||
}
|
}
|
||||||
|
|
||||||
NativeLibrary.onGamePadEvent(
|
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() {
|
fun refreshControls() {
|
||||||
@ -806,6 +822,14 @@ class InputOverlay(context: Context?, attrs: AttributeSet?) :
|
|||||||
NativeLibrary.ButtonType.BUTTON_TURBO.toString() + "-Y",
|
NativeLibrary.ButtonType.BUTTON_TURBO.toString() + "-Y",
|
||||||
resources.getInteger(R.integer.N3DS_BUTTON_TURBO_Y).toFloat() / 1000 * maxY
|
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()
|
.apply()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -957,6 +981,14 @@ class InputOverlay(context: Context?, attrs: AttributeSet?) :
|
|||||||
NativeLibrary.ButtonType.BUTTON_TURBO.toString() + portrait + "-Y",
|
NativeLibrary.ButtonType.BUTTON_TURBO.toString() + portrait + "-Y",
|
||||||
resources.getInteger(R.integer.N3DS_BUTTON_TURBO_PORTRAIT_Y).toFloat() / 1000 * maxY
|
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()
|
.apply()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -152,6 +152,7 @@ void Config::ReadValues() {
|
|||||||
ReadSetting("Renderer", Settings::values.resolution_factor);
|
ReadSetting("Renderer", Settings::values.resolution_factor);
|
||||||
ReadSetting("Renderer", Settings::values.use_disk_shader_cache);
|
ReadSetting("Renderer", Settings::values.use_disk_shader_cache);
|
||||||
ReadSetting("Renderer", Settings::values.use_vsync);
|
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_filter);
|
||||||
ReadSetting("Renderer", Settings::values.texture_sampling);
|
ReadSetting("Renderer", Settings::values.texture_sampling);
|
||||||
ReadSetting("Renderer", Settings::values.turbo_limit);
|
ReadSetting("Renderer", Settings::values.turbo_limit);
|
||||||
@ -314,6 +315,7 @@ void Config::ReadValues() {
|
|||||||
Settings::values.record_frame_times =
|
Settings::values.record_frame_times =
|
||||||
android_config->GetBoolean("Debugging", Settings::HKeys::record_frame_times.c_str(), false);
|
android_config->GetBoolean("Debugging", Settings::HKeys::record_frame_times.c_str(), false);
|
||||||
ReadSetting("Debugging", Settings::values.renderer_debug);
|
ReadSetting("Debugging", Settings::values.renderer_debug);
|
||||||
|
ReadSetting("Debugging", Settings::values.pica_debugging);
|
||||||
ReadSetting("Debugging", Settings::values.use_gdbstub);
|
ReadSetting("Debugging", Settings::values.use_gdbstub);
|
||||||
ReadSetting("Debugging", Settings::values.gdbstub_port);
|
ReadSetting("Debugging", Settings::values.gdbstub_port);
|
||||||
ReadSetting("Debugging", Settings::values.instant_debug_log);
|
ReadSetting("Debugging", Settings::values.instant_debug_log);
|
||||||
|
|||||||
@ -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)
|
# Use Artic Controller when connected to Artic Base Server. (Default 0)
|
||||||
)") DECLARE_KEY(use_artic_base_controller) BOOST_HANA_STRING(R"(
|
)") 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]
|
[Core]
|
||||||
# Whether to use the Just-In-Time (JIT) compiler for CPU emulation
|
# Whether to use the Just-In-Time (JIT) compiler for CPU emulation
|
||||||
# 0: Interpreter (slow), 1 (default): JIT (fast)
|
# 0: Interpreter (slow), 1 (default): JIT (fast)
|
||||||
@ -130,6 +133,10 @@ static const char* android_config_default_file_content = (BOOST_HANA_STRING(R"(
|
|||||||
# 0 (default): Off, 1: On
|
# 0 (default): Off, 1: On
|
||||||
)") DECLARE_KEY(use_vsync) BOOST_HANA_STRING(R"(
|
)") 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
|
# Reduce stuttering by storing and loading generated shaders to disk
|
||||||
# 0: Off, 1 (default. On)
|
# 0: Off, 1 (default. On)
|
||||||
)") DECLARE_KEY(use_disk_shader_cache) BOOST_HANA_STRING(R"(
|
)") 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
|
# 0 (default): Off, 1: On
|
||||||
)") DECLARE_KEY(renderer_debug) BOOST_HANA_STRING(R"(
|
)") 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
|
# 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.
|
# 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"(
|
)") DECLARE_KEY(instant_debug_log) BOOST_HANA_STRING(R"(
|
||||||
|
|||||||
@ -255,7 +255,13 @@ static Core::System::ResultStatus RunCitra(const std::string& filepath) {
|
|||||||
// Register microphone permission check
|
// Register microphone permission check
|
||||||
system.RegisterMicPermissionCheck(&CheckMicPermission);
|
system.RegisterMicPermissionCheck(&CheckMicPermission);
|
||||||
|
|
||||||
|
// No PICA debugging on Android
|
||||||
|
if (Settings::values.pica_debugging) {
|
||||||
Pica::g_debug_context = Pica::DebugContext::Construct();
|
Pica::g_debug_context = Pica::DebugContext::Construct();
|
||||||
|
} else {
|
||||||
|
Pica::g_debug_context.reset();
|
||||||
|
}
|
||||||
|
|
||||||
InputManager::Init();
|
InputManager::Init();
|
||||||
|
|
||||||
window->MakeCurrent();
|
window->MakeCurrent();
|
||||||
|
|||||||
32
src/android/app/src/main/res/drawable/button_combo.xml
Normal file
32
src/android/app/src/main/res/drawable/button_combo.xml
Normal 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>
|
||||||
@ -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>
|
||||||
@ -79,6 +79,12 @@
|
|||||||
<item
|
<item
|
||||||
android:id="@+id/menu_emulation_adjust_scale_button_swap"
|
android:id="@+id/menu_emulation_adjust_scale_button_swap"
|
||||||
android:title="@string/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>
|
</menu>
|
||||||
</item>
|
</item>
|
||||||
|
|
||||||
|
|||||||
@ -188,8 +188,37 @@
|
|||||||
<item>@string/button_home</item>
|
<item>@string/button_home</item>
|
||||||
<item>@string/button_swap</item>
|
<item>@string/button_swap</item>
|
||||||
<item>@string/button_turbo</item>
|
<item>@string/button_turbo</item>
|
||||||
|
<item>@string/button_combo</item>
|
||||||
</string-array>
|
</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">
|
<string-array name="cameraImageSourceNames">
|
||||||
<item>@string/blank</item>
|
<item>@string/blank</item>
|
||||||
<item>@string/still_image</item>
|
<item>@string/still_image</item>
|
||||||
|
|||||||
@ -35,6 +35,8 @@
|
|||||||
<integer name="N3DS_BUTTON_SWAP_Y">850</integer>
|
<integer name="N3DS_BUTTON_SWAP_Y">850</integer>
|
||||||
<integer name="N3DS_BUTTON_TURBO_X">630</integer>
|
<integer name="N3DS_BUTTON_TURBO_X">630</integer>
|
||||||
<integer name="N3DS_BUTTON_TURBO_Y">850</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 -->
|
<!-- Default N3DS portrait layout -->
|
||||||
<integer name="N3DS_BUTTON_A_PORTRAIT_X">810</integer>
|
<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_SWAP_PORTRAIT_Y">675</integer>
|
||||||
<integer name="N3DS_BUTTON_TURBO_PORTRAIT_X">453</integer>
|
<integer name="N3DS_BUTTON_TURBO_PORTRAIT_X">453</integer>
|
||||||
<integer name="N3DS_BUTTON_TURBO_PORTRAIT_Y">720</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>
|
</resources>
|
||||||
|
|||||||
@ -156,6 +156,7 @@
|
|||||||
<string name="button_home">HOME</string>
|
<string name="button_home">HOME</string>
|
||||||
<string name="button_swap">Swap Screens</string>
|
<string name="button_swap">Swap Screens</string>
|
||||||
<string name="button_turbo">Turbo</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_x" translatable="false">X</string>
|
||||||
<string name="button_y" translatable="false">Y</string>
|
<string name="button_y" translatable="false">Y</string>
|
||||||
<string name="button_l" translatable="false">L</string>
|
<string name="button_l" translatable="false">L</string>
|
||||||
@ -167,6 +168,8 @@
|
|||||||
<string name="turbo_limit_hotkey">Turbo Speed</string>
|
<string name="turbo_limit_hotkey">Turbo Speed</string>
|
||||||
<string name="turbo_enabled_toast">Turbo Speed Enabled</string>
|
<string name="turbo_enabled_toast">Turbo Speed Enabled</string>
|
||||||
<string name="turbo_disabled_toast">Turbo Speed Disabled</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 -->
|
<!-- System files strings -->
|
||||||
<string name="setup_system_files">System Files</string>
|
<string name="setup_system_files">System Files</string>
|
||||||
@ -271,6 +274,8 @@
|
|||||||
<string name="advanced">Advanced</string>
|
<string name="advanced">Advanced</string>
|
||||||
<string name="texture_sampling_name">Texture Sampling</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="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">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="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>
|
<string name="asynchronous_gpu">Enable Asynchronous GPU Emulation</string>
|
||||||
|
|||||||
@ -2,6 +2,7 @@
|
|||||||
// Licensed under GPLv2 or any later version
|
// Licensed under GPLv2 or any later version
|
||||||
// Refer to the license.txt file included.
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#include <cstring>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <cubeb/cubeb.h>
|
#include <cubeb/cubeb.h>
|
||||||
|
|||||||
@ -1,8 +1,9 @@
|
|||||||
// Copyright 2018 Citra Emulator Project
|
// Copyright Citra Emulator Project / Azahar Emulator Project
|
||||||
// Licensed under GPLv2 or any later version
|
// Licensed under GPLv2 or any later version
|
||||||
// Refer to the license.txt file included.
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
#include <cstdarg>
|
#include <cstdarg>
|
||||||
|
#include <cstring>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <cubeb/cubeb.h>
|
#include <cubeb/cubeb.h>
|
||||||
|
|||||||
@ -552,7 +552,7 @@ bool retro_load_game(const struct retro_game_info* info) {
|
|||||||
emu_instance->emu_window->UpdateLayout();
|
emu_instance->emu_window->UpdateLayout();
|
||||||
|
|
||||||
switch (Settings::values.graphics_api.GetValue()) {
|
switch (Settings::values.graphics_api.GetValue()) {
|
||||||
case Settings::GraphicsAPI::OpenGL:
|
case Settings::GraphicsAPI::OpenGL: {
|
||||||
#ifdef ENABLE_OPENGL
|
#ifdef ENABLE_OPENGL
|
||||||
LOG_INFO(Frontend, "Using OpenGL hw renderer");
|
LOG_INFO(Frontend, "Using OpenGL hw renderer");
|
||||||
LibRetro::SetHWSharedContext();
|
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);
|
LibRetro::SetFramebufferCallback(emu_instance->hw_render.get_current_framebuffer);
|
||||||
#endif
|
#endif
|
||||||
break;
|
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
|
#ifdef ENABLE_VULKAN
|
||||||
LOG_INFO(Frontend, "Using Vulkan hw renderer");
|
LOG_INFO(Frontend, "Using Vulkan hw renderer");
|
||||||
emu_instance->hw_render.context_type = RETRO_HW_CONTEXT_VULKAN;
|
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);
|
LibRetro::SetHWRenderContextNegotiationInterface((void**)&vk_negotiation);
|
||||||
#endif
|
#endif
|
||||||
break;
|
break;
|
||||||
case Settings::GraphicsAPI::Software:
|
}
|
||||||
|
case Settings::GraphicsAPI::Software: {
|
||||||
emu_instance->emu_window->CreateContext();
|
emu_instance->emu_window->CreateContext();
|
||||||
emu_instance->game_loaded = do_load_game();
|
emu_instance->game_loaded = do_load_game();
|
||||||
if (!emu_instance->game_loaded)
|
if (!emu_instance->game_loaded)
|
||||||
return false;
|
return false;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
uint64_t quirks =
|
uint64_t quirks =
|
||||||
RETRO_SERIALIZATION_QUIRK_CORE_VARIABLE_SIZE | RETRO_SERIALIZATION_QUIRK_MUST_INITIALIZE;
|
RETRO_SERIALIZATION_QUIRK_CORE_VARIABLE_SIZE | RETRO_SERIALIZATION_QUIRK_MUST_INITIALIZE;
|
||||||
|
|||||||
@ -126,6 +126,10 @@ public:
|
|||||||
return static_cast<u32>(frame_pool.size());
|
return static_cast<u32>(frame_pool.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
vk::Format GetSurfaceFormat() const noexcept {
|
||||||
|
return output_format;
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
/// Creates the render pass for LibRetro output
|
/// Creates the render pass for LibRetro output
|
||||||
vk::RenderPass CreateRenderpass();
|
vk::RenderPass CreateRenderpass();
|
||||||
|
|||||||
@ -299,8 +299,7 @@ GMainWindow::GMainWindow(Core::System& system_)
|
|||||||
if (i >= args.size() - 1 || args[i + 1].startsWith(QChar::fromLatin1('-'))) {
|
if (i >= args.size() - 1 || args[i + 1].startsWith(QChar::fromLatin1('-'))) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
Settings::values.use_gdbstub = true;
|
gdbport_from_arg = strtoul(args[++i].toLatin1(), NULL, 0);
|
||||||
Settings::values.gdbstub_port = strtoul(args[++i].toLatin1(), NULL, 0);
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -414,7 +413,11 @@ GMainWindow::GMainWindow(Core::System& system_)
|
|||||||
|
|
||||||
LoadTranslation();
|
LoadTranslation();
|
||||||
|
|
||||||
|
if (Settings::values.pica_debugging) {
|
||||||
Pica::g_debug_context = Pica::DebugContext::Construct();
|
Pica::g_debug_context = Pica::DebugContext::Construct();
|
||||||
|
} else {
|
||||||
|
Pica::g_debug_context.reset();
|
||||||
|
}
|
||||||
setAcceptDrops(true);
|
setAcceptDrops(true);
|
||||||
ui->setupUi(this);
|
ui->setupUi(this);
|
||||||
statusBar()->hide();
|
statusBar()->hide();
|
||||||
@ -706,8 +709,12 @@ void GMainWindow::InitializeWidgets() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void GMainWindow::InitializeDebugWidgets() {
|
void GMainWindow::InitializeDebugWidgets() {
|
||||||
|
if (Pica::g_debug_context) {
|
||||||
connect(ui->action_Create_Pica_Surface_Viewer, &QAction::triggered, this,
|
connect(ui->action_Create_Pica_Surface_Viewer, &QAction::triggered, this,
|
||||||
&GMainWindow::OnCreateGraphicsSurfaceViewer);
|
&GMainWindow::OnCreateGraphicsSurfaceViewer);
|
||||||
|
} else {
|
||||||
|
ui->action_Create_Pica_Surface_Viewer->setEnabled(false);
|
||||||
|
}
|
||||||
|
|
||||||
QMenu* debug_menu = ui->menu_View_Debugging;
|
QMenu* debug_menu = ui->menu_View_Debugging;
|
||||||
|
|
||||||
@ -726,6 +733,7 @@ void GMainWindow::InitializeDebugWidgets() {
|
|||||||
connect(this, &GMainWindow::EmulationStopping, registersWidget,
|
connect(this, &GMainWindow::EmulationStopping, registersWidget,
|
||||||
&RegistersWidget::OnEmulationStopping);
|
&RegistersWidget::OnEmulationStopping);
|
||||||
|
|
||||||
|
if (Pica::g_debug_context) {
|
||||||
graphicsWidget = new GPUCommandStreamWidget(system, this);
|
graphicsWidget = new GPUCommandStreamWidget(system, this);
|
||||||
addDockWidget(Qt::RightDockWidgetArea, graphicsWidget);
|
addDockWidget(Qt::RightDockWidgetArea, graphicsWidget);
|
||||||
graphicsWidget->hide();
|
graphicsWidget->hide();
|
||||||
@ -755,6 +763,7 @@ void GMainWindow::InitializeDebugWidgets() {
|
|||||||
&GraphicsTracingWidget::OnEmulationStarting);
|
&GraphicsTracingWidget::OnEmulationStarting);
|
||||||
connect(this, &GMainWindow::EmulationStopping, graphicsTracingWidget,
|
connect(this, &GMainWindow::EmulationStopping, graphicsTracingWidget,
|
||||||
&GraphicsTracingWidget::OnEmulationStopping);
|
&GraphicsTracingWidget::OnEmulationStopping);
|
||||||
|
}
|
||||||
|
|
||||||
waitTreeWidget = new WaitTreeWidget(system, this);
|
waitTreeWidget = new WaitTreeWidget(system, this);
|
||||||
addDockWidget(Qt::LeftDockWidgetArea, waitTreeWidget);
|
addDockWidget(Qt::LeftDockWidgetArea, waitTreeWidget);
|
||||||
@ -1516,6 +1525,13 @@ void GMainWindow::BootGame(const QString& filename) {
|
|||||||
system.RegisterAppLoaderEarly(loader);
|
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();
|
system.ApplySettings();
|
||||||
|
|
||||||
Settings::LogSettings();
|
Settings::LogSettings();
|
||||||
@ -1553,7 +1569,7 @@ void GMainWindow::BootGame(const QString& filename) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Register debug widgets
|
// Register debug widgets
|
||||||
if (graphicsWidget->isVisible()) {
|
if (graphicsWidget && graphicsWidget->isVisible()) {
|
||||||
graphicsWidget->Register();
|
graphicsWidget->Register();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1638,10 +1654,12 @@ void GMainWindow::ShutdownGame() {
|
|||||||
// breakpoint after (or before) RequestStop() is called, the emulation would never be able
|
// 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.
|
// 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
|
// 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();
|
Pica::g_debug_context->ClearBreakpoints();
|
||||||
|
}
|
||||||
|
|
||||||
// Unregister debug widgets
|
// Unregister debug widgets
|
||||||
if (graphicsWidget->isVisible()) {
|
if (graphicsWidget && graphicsWidget->isVisible()) {
|
||||||
graphicsWidget->Unregister();
|
graphicsWidget->Unregister();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2341,28 +2359,33 @@ void GMainWindow::OnMenuSetUpSystemFiles() {
|
|||||||
|
|
||||||
QRadioButton radio1(&dialog);
|
QRadioButton radio1(&dialog);
|
||||||
QRadioButton radio2(&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) {
|
if (!install_state.first) {
|
||||||
radio1.setChecked(true);
|
radio1.setChecked(true);
|
||||||
|
|
||||||
radio1.setText(tr("(\u2139\uFE0F) Old 3DS setup"));
|
radio1.setText(availableIcon + old3dsSetupString);
|
||||||
radio1.setToolTip(tr("Setup is possible."));
|
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.setToolTip(tr("Old 3DS setup is required first."));
|
||||||
radio2.setEnabled(false);
|
radio2.setEnabled(false);
|
||||||
} else {
|
} else {
|
||||||
radio1.setText(tr("(\u2705) Old 3DS setup"));
|
radio1.setText(installedIcon + old3dsSetupString);
|
||||||
radio1.setToolTip(tr("Setup completed."));
|
radio1.setToolTip(tr("Setup completed."));
|
||||||
|
|
||||||
if (!install_state.second) {
|
if (!install_state.second) {
|
||||||
radio2.setChecked(true);
|
radio2.setChecked(true);
|
||||||
|
|
||||||
radio2.setText(tr("(\u2139\uFE0F) New 3DS setup"));
|
radio2.setText(availableIcon + new3dsSetupString);
|
||||||
radio2.setToolTip(tr("Setup is possible."));
|
radio2.setToolTip(tr("Setup is possible."));
|
||||||
} else {
|
} else {
|
||||||
radio1.setChecked(true);
|
radio1.setChecked(true);
|
||||||
|
|
||||||
radio2.setText(tr("(\u2705) New 3DS setup"));
|
radio2.setText(installedIcon + new3dsSetupString);
|
||||||
radio2.setToolTip(tr("Setup completed."));
|
radio2.setToolTip(tr("Setup completed."));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -2564,6 +2587,7 @@ void GMainWindow::UninstallTitles(
|
|||||||
} else if (!future_watcher.isCanceled()) {
|
} else if (!future_watcher.isCanceled()) {
|
||||||
QMessageBox::information(this, tr("Azahar"),
|
QMessageBox::information(this, tr("Azahar"),
|
||||||
tr("Successfully uninstalled '%1'.").arg(first_name));
|
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,
|
QObject::connect(&app, &QGuiApplication::applicationStateChanged, &main_window,
|
||||||
&GMainWindow::OnAppFocusStateChanged);
|
&GMainWindow::OnAppFocusStateChanged);
|
||||||
|
|
||||||
|
// Process any pending events before executing the app (prevents freeze-on–boot on macOS)
|
||||||
|
app.processEvents();
|
||||||
|
|
||||||
int result = app.exec();
|
int result = app.exec();
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -151,6 +151,7 @@ signals:
|
|||||||
void InfoLEDColorChanged();
|
void InfoLEDColorChanged();
|
||||||
// Signal that tells widgets to update icons to use the current theme
|
// Signal that tells widgets to update icons to use the current theme
|
||||||
void UpdateThemedIcons();
|
void UpdateThemedIcons();
|
||||||
|
void InstalledTitlesChanged();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void InitializeWidgets();
|
void InitializeWidgets();
|
||||||
@ -406,23 +407,25 @@ private:
|
|||||||
// Whether game was paused due to stopping video dumping
|
// Whether game was paused due to stopping video dumping
|
||||||
bool game_paused_for_dumping = false;
|
bool game_paused_for_dumping = false;
|
||||||
|
|
||||||
|
int gdbport_from_arg = -1;
|
||||||
|
|
||||||
QString gl_renderer;
|
QString gl_renderer;
|
||||||
std::vector<QString> physical_devices;
|
std::vector<QString> physical_devices;
|
||||||
|
|
||||||
// Debugger panes
|
// Debugger panes
|
||||||
ProfilerWidget* profilerWidget;
|
ProfilerWidget* profilerWidget{};
|
||||||
#if MICROPROFILE_ENABLED
|
#if MICROPROFILE_ENABLED
|
||||||
MicroProfileDialog* microProfileDialog;
|
MicroProfileDialog* microProfileDialog{};
|
||||||
#endif
|
#endif
|
||||||
RegistersWidget* registersWidget;
|
RegistersWidget* registersWidget{};
|
||||||
GPUCommandStreamWidget* graphicsWidget;
|
GPUCommandStreamWidget* graphicsWidget{};
|
||||||
GPUCommandListWidget* graphicsCommandsWidget;
|
GPUCommandListWidget* graphicsCommandsWidget{};
|
||||||
GraphicsBreakPointsWidget* graphicsBreakpointsWidget;
|
GraphicsBreakPointsWidget* graphicsBreakpointsWidget{};
|
||||||
GraphicsVertexShaderWidget* graphicsVertexShaderWidget;
|
GraphicsVertexShaderWidget* graphicsVertexShaderWidget{};
|
||||||
GraphicsTracingWidget* graphicsTracingWidget;
|
GraphicsTracingWidget* graphicsTracingWidget{};
|
||||||
IPCRecorderWidget* ipcRecorderWidget;
|
IPCRecorderWidget* ipcRecorderWidget{};
|
||||||
LLEServiceModulesWidget* lleServiceModulesWidget;
|
LLEServiceModulesWidget* lleServiceModulesWidget{};
|
||||||
WaitTreeWidget* waitTreeWidget;
|
WaitTreeWidget* waitTreeWidget{};
|
||||||
|
|
||||||
QAction* actions_recent_files[max_recent_files_item];
|
QAction* actions_recent_files[max_recent_files_item];
|
||||||
std::array<QAction*, Core::SaveStateSlotCount> actions_load_state;
|
std::array<QAction*, Core::SaveStateSlotCount> actions_load_state;
|
||||||
|
|||||||
@ -513,6 +513,7 @@ void QtConfig::ReadDebuggingValues() {
|
|||||||
ReadBasicSetting(Settings::values.use_gdbstub);
|
ReadBasicSetting(Settings::values.use_gdbstub);
|
||||||
ReadBasicSetting(Settings::values.gdbstub_port);
|
ReadBasicSetting(Settings::values.gdbstub_port);
|
||||||
ReadBasicSetting(Settings::values.renderer_debug);
|
ReadBasicSetting(Settings::values.renderer_debug);
|
||||||
|
ReadBasicSetting(Settings::values.pica_debugging);
|
||||||
ReadBasicSetting(Settings::values.dump_command_buffers);
|
ReadBasicSetting(Settings::values.dump_command_buffers);
|
||||||
ReadBasicSetting(Settings::values.instant_debug_log);
|
ReadBasicSetting(Settings::values.instant_debug_log);
|
||||||
ReadBasicSetting(Settings::values.enable_rpc_server);
|
ReadBasicSetting(Settings::values.enable_rpc_server);
|
||||||
@ -714,6 +715,7 @@ void QtConfig::ReadRendererValues() {
|
|||||||
ReadGlobalSetting(Settings::values.shaders_accurate_mul);
|
ReadGlobalSetting(Settings::values.shaders_accurate_mul);
|
||||||
ReadGlobalSetting(Settings::values.use_disk_shader_cache);
|
ReadGlobalSetting(Settings::values.use_disk_shader_cache);
|
||||||
ReadGlobalSetting(Settings::values.use_vsync);
|
ReadGlobalSetting(Settings::values.use_vsync);
|
||||||
|
ReadGlobalSetting(Settings::values.use_skip_duplicate_frames);
|
||||||
ReadGlobalSetting(Settings::values.use_display_refresh_rate_detection);
|
ReadGlobalSetting(Settings::values.use_display_refresh_rate_detection);
|
||||||
ReadGlobalSetting(Settings::values.resolution_factor);
|
ReadGlobalSetting(Settings::values.resolution_factor);
|
||||||
ReadGlobalSetting(Settings::values.use_integer_scaling);
|
ReadGlobalSetting(Settings::values.use_integer_scaling);
|
||||||
@ -1103,6 +1105,7 @@ void QtConfig::SaveDebuggingValues() {
|
|||||||
WriteBasicSetting(Settings::values.use_gdbstub);
|
WriteBasicSetting(Settings::values.use_gdbstub);
|
||||||
WriteBasicSetting(Settings::values.gdbstub_port);
|
WriteBasicSetting(Settings::values.gdbstub_port);
|
||||||
WriteBasicSetting(Settings::values.renderer_debug);
|
WriteBasicSetting(Settings::values.renderer_debug);
|
||||||
|
WriteBasicSetting(Settings::values.pica_debugging);
|
||||||
WriteBasicSetting(Settings::values.instant_debug_log);
|
WriteBasicSetting(Settings::values.instant_debug_log);
|
||||||
WriteBasicSetting(Settings::values.enable_rpc_server);
|
WriteBasicSetting(Settings::values.enable_rpc_server);
|
||||||
WriteBasicSetting(Settings::values.toggle_unique_data_console_type);
|
WriteBasicSetting(Settings::values.toggle_unique_data_console_type);
|
||||||
@ -1263,6 +1266,7 @@ void QtConfig::SaveRendererValues() {
|
|||||||
WriteGlobalSetting(Settings::values.shaders_accurate_mul);
|
WriteGlobalSetting(Settings::values.shaders_accurate_mul);
|
||||||
WriteGlobalSetting(Settings::values.use_disk_shader_cache);
|
WriteGlobalSetting(Settings::values.use_disk_shader_cache);
|
||||||
WriteGlobalSetting(Settings::values.use_vsync);
|
WriteGlobalSetting(Settings::values.use_vsync);
|
||||||
|
WriteGlobalSetting(Settings::values.use_skip_duplicate_frames);
|
||||||
WriteGlobalSetting(Settings::values.use_display_refresh_rate_detection);
|
WriteGlobalSetting(Settings::values.use_display_refresh_rate_detection);
|
||||||
WriteGlobalSetting(Settings::values.resolution_factor);
|
WriteGlobalSetting(Settings::values.resolution_factor);
|
||||||
WriteGlobalSetting(Settings::values.use_integer_scaling);
|
WriteGlobalSetting(Settings::values.use_integer_scaling);
|
||||||
|
|||||||
@ -82,8 +82,14 @@ ConfigureDebug::ConfigureDebug(bool is_powered_on_, QWidget* parent)
|
|||||||
});
|
});
|
||||||
#endif
|
#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_cpu_jit->setEnabled(!is_powered_on);
|
||||||
ui->toggle_renderer_debug->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->toggle_dump_command_buffers->setEnabled(!is_powered_on);
|
||||||
ui->enable_rpc_server->setEnabled(!is_powered_on);
|
ui->enable_rpc_server->setEnabled(!is_powered_on);
|
||||||
ui->toggle_unique_data_console_type->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());
|
Settings::values.break_on_unmapped_memory_access.GetValue());
|
||||||
|
|
||||||
ui->toggle_renderer_debug->setChecked(Settings::values.renderer_debug.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());
|
ui->toggle_dump_command_buffers->setChecked(Settings::values.dump_command_buffers.GetValue());
|
||||||
|
|
||||||
if (!Settings::IsConfiguringGlobal()) {
|
if (!Settings::IsConfiguringGlobal()) {
|
||||||
@ -181,6 +188,7 @@ void ConfigureDebug::ApplyConfiguration() {
|
|||||||
Settings::values.break_on_unmapped_memory_access =
|
Settings::values.break_on_unmapped_memory_access =
|
||||||
ui->break_on_unmapped_memory_access->isChecked();
|
ui->break_on_unmapped_memory_access->isChecked();
|
||||||
Settings::values.renderer_debug = ui->toggle_renderer_debug->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.dump_command_buffers = ui->toggle_dump_command_buffers->isChecked();
|
||||||
Settings::values.instant_debug_log = ui->instant_debug_log->isChecked();
|
Settings::values.instant_debug_log = ui->instant_debug_log->isChecked();
|
||||||
|
|
||||||
|
|||||||
@ -251,6 +251,13 @@
|
|||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="5" column="0">
|
<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">
|
<widget class="QCheckBox" name="toggle_dump_command_buffers">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Dump command buffers</string>
|
<string>Dump command buffers</string>
|
||||||
|
|||||||
@ -144,6 +144,8 @@ void ConfigureGraphics::SetConfiguration() {
|
|||||||
ui->toggle_accurate_mul->setChecked(Settings::values.shaders_accurate_mul.GetValue());
|
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_disk_shader_cache->setChecked(Settings::values.use_disk_shader_cache.GetValue());
|
||||||
ui->toggle_vsync->setChecked(Settings::values.use_vsync.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->spirv_shader_gen->setChecked(Settings::values.spirv_shader_gen.GetValue());
|
||||||
ui->disable_spirv_optimizer->setChecked(Settings::values.disable_spirv_optimizer.GetValue());
|
ui->disable_spirv_optimizer->setChecked(Settings::values.disable_spirv_optimizer.GetValue());
|
||||||
ui->toggle_async_shaders->setChecked(Settings::values.async_shader_compilation.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);
|
ui->toggle_disk_shader_cache, use_disk_shader_cache);
|
||||||
ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_vsync, ui->toggle_vsync,
|
ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_vsync, ui->toggle_vsync,
|
||||||
use_vsync);
|
use_vsync);
|
||||||
|
ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_skip_duplicate_frames,
|
||||||
|
ui->toggle_skip_duplicate_frames,
|
||||||
|
use_skip_duplicate_frames);
|
||||||
ConfigurationShared::ApplyPerGameSetting(
|
ConfigurationShared::ApplyPerGameSetting(
|
||||||
&Settings::values.delay_game_render_thread_us, ui->delay_render_combo,
|
&Settings::values.delay_game_render_thread_us, ui->delay_render_combo,
|
||||||
[this](s32) { return ui->delay_render_slider->value(); });
|
[this](s32) { return ui->delay_render_slider->value(); });
|
||||||
@ -208,6 +213,9 @@ void ConfigureGraphics::SetupPerGameUI() {
|
|||||||
Settings::values.use_disk_shader_cache.UsingGlobal());
|
Settings::values.use_disk_shader_cache.UsingGlobal());
|
||||||
ui->toggle_vsync->setEnabled(ui->toggle_vsync->isEnabled() &&
|
ui->toggle_vsync->setEnabled(ui->toggle_vsync->isEnabled() &&
|
||||||
Settings::values.use_vsync.UsingGlobal());
|
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(
|
ui->toggle_async_shaders->setEnabled(
|
||||||
Settings::values.async_shader_compilation.UsingGlobal());
|
Settings::values.async_shader_compilation.UsingGlobal());
|
||||||
ui->widget_texture_sampling->setEnabled(Settings::values.texture_sampling.UsingGlobal());
|
ui->widget_texture_sampling->setEnabled(Settings::values.texture_sampling.UsingGlobal());
|
||||||
@ -250,6 +258,9 @@ void ConfigureGraphics::SetupPerGameUI() {
|
|||||||
use_disk_shader_cache);
|
use_disk_shader_cache);
|
||||||
ConfigurationShared::SetColoredTristate(ui->toggle_vsync, Settings::values.use_vsync,
|
ConfigurationShared::SetColoredTristate(ui->toggle_vsync, Settings::values.use_vsync,
|
||||||
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,
|
ConfigurationShared::SetColoredTristate(ui->toggle_async_shaders,
|
||||||
Settings::values.async_shader_compilation,
|
Settings::values.async_shader_compilation,
|
||||||
async_shader_compilation);
|
async_shader_compilation);
|
||||||
|
|||||||
@ -39,6 +39,7 @@ private:
|
|||||||
ConfigurationShared::CheckState shaders_accurate_mul;
|
ConfigurationShared::CheckState shaders_accurate_mul;
|
||||||
ConfigurationShared::CheckState use_disk_shader_cache;
|
ConfigurationShared::CheckState use_disk_shader_cache;
|
||||||
ConfigurationShared::CheckState use_vsync;
|
ConfigurationShared::CheckState use_vsync;
|
||||||
|
ConfigurationShared::CheckState use_skip_duplicate_frames;
|
||||||
ConfigurationShared::CheckState use_display_refresh_rate_detection;
|
ConfigurationShared::CheckState use_display_refresh_rate_detection;
|
||||||
ConfigurationShared::CheckState async_shader_compilation;
|
ConfigurationShared::CheckState async_shader_compilation;
|
||||||
ConfigurationShared::CheckState async_presentation;
|
ConfigurationShared::CheckState async_presentation;
|
||||||
|
|||||||
@ -7,7 +7,7 @@
|
|||||||
<x>0</x>
|
<x>0</x>
|
||||||
<y>0</y>
|
<y>0</y>
|
||||||
<width>400</width>
|
<width>400</width>
|
||||||
<height>509</height>
|
<height>559</height>
|
||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
<property name="minimumSize">
|
<property name="minimumSize">
|
||||||
@ -317,6 +317,16 @@
|
|||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QCheckBox" name="toggle_skip_duplicate_frames">
|
||||||
|
<property name="toolTip">
|
||||||
|
<string><html><head/><body><p>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.</p></body></html></string>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Skip Presenting Duplicate Frames</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<widget class="QCheckBox" name="toggle_display_refresh_rate_detection">
|
<widget class="QCheckBox" name="toggle_display_refresh_rate_detection">
|
||||||
<property name="toolTip">
|
<property name="toolTip">
|
||||||
|
|||||||
@ -379,6 +379,8 @@ GameList::GameList(PlayTime::PlayTimeManager& play_time_manager_, GMainWindow* p
|
|||||||
item_model->setSortRole(GameListItemPath::SortRole);
|
item_model->setSortRole(GameListItemPath::SortRole);
|
||||||
|
|
||||||
connect(main_window, &GMainWindow::UpdateThemedIcons, this, &GameList::OnUpdateThemedIcons);
|
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::activated, this, &GameList::ValidateEntry);
|
||||||
connect(tree_view, &QTreeView::customContextMenuRequested, this, &GameList::PopupContextMenu);
|
connect(tree_view, &QTreeView::customContextMenuRequested, this, &GameList::PopupContextMenu);
|
||||||
connect(tree_view, &QTreeView::expanded, this, &GameList::OnItemExpanded);
|
connect(tree_view, &QTreeView::expanded, this, &GameList::OnItemExpanded);
|
||||||
|
|||||||
@ -864,12 +864,16 @@ const std::string& GetExeDirectory() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
std::string AppDataRoamingDirectory() {
|
std::string AppDataRoamingDirectory() {
|
||||||
PWSTR pw_local_path = nullptr;
|
PWSTR path = nullptr;
|
||||||
// Only supported by Windows Vista or later
|
|
||||||
SHGetKnownFolderPath(FOLDERID_RoamingAppData, 0, nullptr, &pw_local_path);
|
const HRESULT hr =
|
||||||
std::string local_path = Common::UTF16ToUTF8(pw_local_path);
|
SHGetKnownFolderPath(FOLDERID_RoamingAppData, KF_FLAG_DEFAULT, nullptr, &path);
|
||||||
CoTaskMemFree(pw_local_path);
|
|
||||||
return local_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
|
#else
|
||||||
/**
|
/**
|
||||||
@ -1516,6 +1520,46 @@ std::size_t IOFile::WriteImpl(const void* data, std::size_t length, std::size_t
|
|||||||
#endif
|
#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) {
|
bool IOFile::Resize(u64 size) {
|
||||||
if (!IsOpen() || 0 !=
|
if (!IsOpen() || 0 !=
|
||||||
#if defined(HAVE_LIBRETRO_VFS)
|
#if defined(HAVE_LIBRETRO_VFS)
|
||||||
|
|||||||
@ -428,6 +428,27 @@ public:
|
|||||||
return WriteImpl(data.data(), data.size(), sizeof(T));
|
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 {
|
[[nodiscard]] virtual bool IsOpen() const {
|
||||||
return nullptr != m_file;
|
return nullptr != m_file;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -573,8 +573,10 @@ void Start() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void Stop() {
|
void Stop() {
|
||||||
|
if (logging_initialized) {
|
||||||
Impl::Stop();
|
Impl::Stop();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void DisableLoggingInTests() {
|
void DisableLoggingInTests() {
|
||||||
initialization_in_progress_suppress_logging = true;
|
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,
|
void FmtLogMessageImpl(Class log_class, Level log_level, const char* filename,
|
||||||
unsigned int line_num, const char* function, fmt::string_view format,
|
unsigned int line_num, const char* function, fmt::string_view format,
|
||||||
const fmt::format_args& args) {
|
const fmt::format_args& args) {
|
||||||
if (initialization_in_progress_suppress_logging) [[unlikely]] {
|
if (initialization_in_progress_suppress_logging && log_level < Level::Critical) [[unlikely]] {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -96,6 +96,7 @@ void LogSettings() {
|
|||||||
log_setting("Renderer_SpirvShaderGen", values.spirv_shader_gen.GetValue());
|
log_setting("Renderer_SpirvShaderGen", values.spirv_shader_gen.GetValue());
|
||||||
log_setting("Renderer_DisableSpirvOptimizer", values.disable_spirv_optimizer.GetValue());
|
log_setting("Renderer_DisableSpirvOptimizer", values.disable_spirv_optimizer.GetValue());
|
||||||
log_setting("Renderer_Debug", values.renderer_debug.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_UseHwShader", values.use_hw_shader.GetValue());
|
||||||
log_setting("Renderer_ShadersAccurateMul", values.shaders_accurate_mul.GetValue());
|
log_setting("Renderer_ShadersAccurateMul", values.shaders_accurate_mul.GetValue());
|
||||||
log_setting("Renderer_UseShaderJit", values.use_shader_jit.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_UseIntegerScaling", values.use_integer_scaling.GetValue());
|
||||||
log_setting("Renderer_FrameLimit", values.frame_limit.GetValue());
|
log_setting("Renderer_FrameLimit", values.frame_limit.GetValue());
|
||||||
log_setting("Renderer_VSyncNew", values.use_vsync.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_PostProcessingShader", values.pp_shader_name.GetValue());
|
||||||
log_setting("Renderer_FilterMode", values.filter_mode.GetValue());
|
log_setting("Renderer_FilterMode", values.filter_mode.GetValue());
|
||||||
log_setting("Renderer_TextureFilter", GetTextureFilterName(values.texture_filter.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.use_disk_shader_cache.SetGlobal(true);
|
||||||
values.shaders_accurate_mul.SetGlobal(true);
|
values.shaders_accurate_mul.SetGlobal(true);
|
||||||
values.use_vsync.SetGlobal(true);
|
values.use_vsync.SetGlobal(true);
|
||||||
|
values.use_skip_duplicate_frames.SetGlobal(true);
|
||||||
values.resolution_factor.SetGlobal(true);
|
values.resolution_factor.SetGlobal(true);
|
||||||
values.use_integer_scaling.SetGlobal(true);
|
values.use_integer_scaling.SetGlobal(true);
|
||||||
values.frame_limit.SetGlobal(true);
|
values.frame_limit.SetGlobal(true);
|
||||||
|
|||||||
@ -527,6 +527,7 @@ struct Values {
|
|||||||
SwitchableSetting<u32> physical_device{0, Keys::physical_device};
|
SwitchableSetting<u32> physical_device{0, Keys::physical_device};
|
||||||
Setting<bool> use_gles{false, Keys::use_gles};
|
Setting<bool> use_gles{false, Keys::use_gles};
|
||||||
Setting<bool> renderer_debug{false, Keys::renderer_debug};
|
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};
|
Setting<bool> dump_command_buffers{false, Keys::dump_command_buffers};
|
||||||
SwitchableSetting<bool> spirv_shader_gen{true, Keys::spirv_shader_gen};
|
SwitchableSetting<bool> spirv_shader_gen{true, Keys::spirv_shader_gen};
|
||||||
SwitchableSetting<bool> disable_spirv_optimizer{true, Keys::disable_spirv_optimizer};
|
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> async_presentation{true, Keys::async_presentation};
|
||||||
SwitchableSetting<bool> use_hw_shader{true, Keys::use_hw_shader};
|
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_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};
|
SwitchableSetting<bool> shaders_accurate_mul{true, Keys::shaders_accurate_mul};
|
||||||
#ifdef ANDROID // TODO: Fuck this -OS
|
#ifdef ANDROID // TODO: Fuck this -OS
|
||||||
SwitchableSetting<bool> use_vsync{false, Keys::use_vsync};
|
SwitchableSetting<bool> use_vsync{false, Keys::use_vsync};
|
||||||
@ -552,7 +554,7 @@ struct Values {
|
|||||||
Keys::texture_sampling};
|
Keys::texture_sampling};
|
||||||
SwitchableSetting<u16, true> delay_game_render_thread_us{0, 0, 65000,
|
SwitchableSetting<u16, true> delay_game_render_thread_us{0, 0, 65000,
|
||||||
Keys::delay_game_render_thread_us};
|
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<LayoutOption> layout_option{LayoutOption::Default, Keys::layout_option};
|
||||||
SwitchableSetting<bool> swap_screen{false, Keys::swap_screen};
|
SwitchableSetting<bool> swap_screen{false, Keys::swap_screen};
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
// Copyright 2022 Citra Emulator Project
|
// Copyright Citra Emulator Project / Azahar Emulator Project
|
||||||
// Licensed under GPLv2 or any later version
|
// Licensed under GPLv2 or any later version
|
||||||
// Refer to the license.txt file included.
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
@ -24,13 +24,13 @@ constexpr u32 GetMatchingBitsFromStringLiteral() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
template <u32 mask_>
|
template <u32 mask_>
|
||||||
constexpr u32 DepositBits(u32 val) {
|
constexpr u32 ExtractBits(u32 val) {
|
||||||
u32 mask = mask_;
|
u32 mask = mask_;
|
||||||
u32 res = 0;
|
u32 res = 0;
|
||||||
for (u32 bb = 1; mask; bb += bb) {
|
for (u32 bb = 1; mask; bb += bb) {
|
||||||
u32 neg_mask = 0 - mask;
|
const u32 lowest_bit = mask & (0 - mask);
|
||||||
if (val & bb)
|
if (val & lowest_bit)
|
||||||
res |= mask & neg_mask;
|
res |= bb;
|
||||||
mask &= mask - 1;
|
mask &= mask - 1;
|
||||||
}
|
}
|
||||||
return res;
|
return res;
|
||||||
@ -40,7 +40,7 @@ template <Common::StringLiteral haystack>
|
|||||||
struct MatcherArg {
|
struct MatcherArg {
|
||||||
template <Common::StringLiteral needle>
|
template <Common::StringLiteral needle>
|
||||||
u32 Get() {
|
u32 Get() {
|
||||||
return DepositBits<GetMatchingBitsFromStringLiteral<haystack, needle>()>(instruction);
|
return ExtractBits<GetMatchingBitsFromStringLiteral<haystack, needle>()>(instruction);
|
||||||
}
|
}
|
||||||
|
|
||||||
u32 instruction;
|
u32 instruction;
|
||||||
@ -52,39 +52,95 @@ struct Matcher {
|
|||||||
std::function<u64(u32)> fn;
|
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) {
|
u64 DataProcessing_imm(auto i) {
|
||||||
if (i.template Get<"d">() == 15) {
|
if (i.template Get<"d">() == 15) {
|
||||||
return 7;
|
return pipeline_flush_cycles;
|
||||||
}
|
}
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
u64 DataProcessing_reg(auto i) {
|
u64 DataProcessing_reg(auto i) {
|
||||||
if (i.template Get<"d">() == 15) {
|
if (i.template Get<"d">() == 15) {
|
||||||
return 7;
|
return pipeline_flush_cycles;
|
||||||
}
|
}
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
u64 DataProcessing_rsr(auto i) {
|
u64 DataProcessing_rsr(auto i) {
|
||||||
if (i.template Get<"d">() == 15) {
|
if (i.template Get<"d">() == 15) {
|
||||||
return 8;
|
return pipeline_flush_cycles + 1;
|
||||||
}
|
}
|
||||||
return 2;
|
return 2;
|
||||||
}
|
}
|
||||||
u64 LoadStoreSingle_imm(auto) {
|
// MOV (reg) has its own handler: a plain "MOV pc, lr" is a procedure return,
|
||||||
return 2;
|
// 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) {
|
return 1;
|
||||||
// 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 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) {
|
u64 LoadStoreMultiple(auto i) {
|
||||||
// TODO: Load PC
|
|
||||||
return 1 + std::popcount(i.template Get<"x">()) / 2;
|
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) \
|
#define INST(NAME, BS, CYCLES) \
|
||||||
Matcher{GetMatchingBitsFromStringLiteral<BS, "01">(), \
|
Matcher{GetMatchingBitsFromStringLiteral<BS, "01">(), \
|
||||||
@ -97,14 +153,27 @@ u64 LoadStoreMultiple(auto i) {
|
|||||||
const std::array arm_matchers{
|
const std::array arm_matchers{
|
||||||
// clang-format off
|
// clang-format off
|
||||||
|
|
||||||
// Branch instructions
|
// branch instructions
|
||||||
INST("BLX (imm)", "1111101hvvvvvvvvvvvvvvvvvvvvvvvv", 5) // v5
|
// direct branches and calls are BTAC/statically predicted (folded when
|
||||||
INST("BLX (reg)", "cccc000100101111111111110011mmmm", 6) // v5
|
// predicted; we charge the issue slot), BX lr is a return-stack-predicted
|
||||||
INST("B", "cccc1010vvvvvvvvvvvvvvvvvvvvvvvv", 4) // v1
|
// return; indirect branches through other registers depend on target
|
||||||
INST("BL", "cccc1011vvvvvvvvvvvvvvvvvvvvvvvv", 4) // v1
|
// stability
|
||||||
INST("BX", "cccc000100101111111111110001mmmm", 5) // v4T
|
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
|
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
|
// Coprocessor instructions
|
||||||
INST("CDP", "cccc1110ooooNNNNDDDDppppooo0MMMM", 1) // v2 (CDP2: v5)
|
INST("CDP", "cccc1110ooooNNNNDDDDppppooo0MMMM", 1) // v2 (CDP2: v5)
|
||||||
INST("LDC", "cccc110pudw1nnnnDDDDppppvvvvvvvv", 1) // v2 (LDC2: 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 (reg)", "cccc0000001Snnnnddddvvvvvrr0mmmm", DataProcessing_reg(i)) // v1
|
||||||
INST("EOR (rsr)", "cccc0000001Snnnnddddssss0rr1mmmm", DataProcessing_rsr(i)) // v1
|
INST("EOR (rsr)", "cccc0000001Snnnnddddssss0rr1mmmm", DataProcessing_rsr(i)) // v1
|
||||||
INST("MOV (imm)", "cccc0011101S0000ddddrrrrvvvvvvvv", DataProcessing_imm(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("MOV (rsr)", "cccc0001101S0000ddddssss0rr1mmmm", DataProcessing_rsr(i)) // v1
|
||||||
INST("MVN (imm)", "cccc0011111S0000ddddrrrrvvvvvvvv", DataProcessing_imm(i)) // v1
|
INST("MVN (imm)", "cccc0011111S0000ddddrrrrvvvvvvvv", DataProcessing_imm(i)) // v1
|
||||||
INST("MVN (reg)", "cccc0001111S0000ddddvvvvvrr0mmmm", DataProcessing_reg(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("STRBT (A2)", "----0110-110---------------0----", 1) // v1
|
||||||
INST("STRT (A1)", "----0100-010--------------------", 1) // v1
|
INST("STRT (A1)", "----0100-010--------------------", 1) // v1
|
||||||
INST("STRT (A2)", "----0110-010---------------0----", 1) // v1
|
INST("STRT (A2)", "----0110-010---------------0----", 1) // v1
|
||||||
INST("LDR (lit)", "cccc0101u0011111ttttvvvvvvvvvvvv", LoadStoreSingle_imm(i)) // v1
|
INST("LDR (lit)", "cccc0101u0011111ttttvvvvvvvvvvvv", Load_lit(i)) // v1
|
||||||
INST("LDR (imm)", "cccc010pu0w1nnnnttttvvvvvvvvvvvv", LoadStoreSingle_imm(i)) // v1
|
INST("LDR (imm)", "cccc010pu0w1nnnnttttvvvvvvvvvvvv", Load_imm(i)) // v1
|
||||||
INST("LDR (reg)", "cccc011pu0w1nnnnttttvvvvvrr0mmmm", LoadStoreSingle_reg(i)) // v1
|
INST("LDR (reg)", "cccc011pu0w1nnnnttttvvvvvrr0mmmm", Load_reg(i)) // v1
|
||||||
INST("LDRB (lit)", "cccc0101u1011111ttttvvvvvvvvvvvv", LoadStoreSingle_imm(i)) // v1
|
INST("LDRB (lit)", "cccc0101u1011111ttttvvvvvvvvvvvv", Load_lit(i)) // v1
|
||||||
INST("LDRB (imm)", "cccc010pu1w1nnnnttttvvvvvvvvvvvv", LoadStoreSingle_imm(i)) // v1
|
INST("LDRB (imm)", "cccc010pu1w1nnnnttttvvvvvvvvvvvv", Load_imm(i)) // v1
|
||||||
INST("LDRB (reg)", "cccc011pu1w1nnnnttttvvvvvrr0mmmm", LoadStoreSingle_reg(i)) // v1
|
INST("LDRB (reg)", "cccc011pu1w1nnnnttttvvvvvrr0mmmm", Load_reg(i)) // v1
|
||||||
INST("LDRD (lit)", "cccc0001u1001111ttttvvvv1101vvvv", LoadStoreSingle_imm(i)) // v5E
|
INST("LDRD (lit)", "cccc0001u1001111ttttvvvv1101vvvv", 1) // v5E
|
||||||
INST("LDRD (imm)", "cccc000pu1w0nnnnttttvvvv1101vvvv", LoadStoreSingle_imm(i)) // v5E
|
INST("LDRD (imm)", "cccc000pu1w0nnnnttttvvvv1101vvvv", 1) // v5E
|
||||||
INST("LDRD (reg)", "cccc000pu0w0nnnntttt00001101mmmm", LoadStoreSingle_reg(i)) // v5E
|
INST("LDRD (reg)", "cccc000pu0w0nnnntttt00001101mmmm", 1) // v5E
|
||||||
INST("LDRH (lit)", "cccc000pu1w11111ttttvvvv1011vvvv", LoadStoreSingle_imm(i)) // v4
|
INST("LDRH (lit)", "cccc000pu1w11111ttttvvvv1011vvvv", Load_reg_noshift(i)) // v4
|
||||||
INST("LDRH (imm)", "cccc000pu1w1nnnnttttvvvv1011vvvv", LoadStoreSingle_imm(i)) // v4
|
INST("LDRH (imm)", "cccc000pu1w1nnnnttttvvvv1011vvvv", Load_reg_noshift(i)) // v4
|
||||||
INST("LDRH (reg)", "cccc000pu0w1nnnntttt00001011mmmm", LoadStoreSingle_reg(i)) // v4
|
INST("LDRH (reg)", "cccc000pu0w1nnnntttt00001011mmmm", Load_reg_noshift(i)) // v4
|
||||||
INST("LDRSB (lit)", "cccc0001u1011111ttttvvvv1101vvvv", LoadStoreSingle_imm(i)) // v4
|
INST("LDRSB (lit)", "cccc0001u1011111ttttvvvv1101vvvv", Load_reg_noshift(i)) // v4
|
||||||
INST("LDRSB (imm)", "cccc000pu1w1nnnnttttvvvv1101vvvv", LoadStoreSingle_imm(i)) // v4
|
INST("LDRSB (imm)", "cccc000pu1w1nnnnttttvvvv1101vvvv", Load_reg_noshift(i)) // v4
|
||||||
INST("LDRSB (reg)", "cccc000pu0w1nnnntttt00001101mmmm", LoadStoreSingle_reg(i)) // v4
|
INST("LDRSB (reg)", "cccc000pu0w1nnnntttt00001101mmmm", Load_reg_noshift(i)) // v4
|
||||||
INST("LDRSH (lit)", "cccc0001u1011111ttttvvvv1111vvvv", LoadStoreSingle_imm(i)) // v4
|
INST("LDRSH (lit)", "cccc0001u1011111ttttvvvv1111vvvv", Load_reg_noshift(i)) // v4
|
||||||
INST("LDRSH (imm)", "cccc000pu1w1nnnnttttvvvv1111vvvv", LoadStoreSingle_imm(i)) // v4
|
INST("LDRSH (imm)", "cccc000pu1w1nnnnttttvvvv1111vvvv", Load_reg_noshift(i)) // v4
|
||||||
INST("LDRSH (reg)", "cccc000pu0w1nnnntttt00001111mmmm", LoadStoreSingle_reg(i)) // v4
|
INST("LDRSH (reg)", "cccc000pu0w1nnnntttt00001111mmmm", Load_reg_noshift(i)) // v4
|
||||||
INST("STR (imm)", "cccc010pu0w0nnnnttttvvvvvvvvvvvv", LoadStoreSingle_imm(i)) // v1
|
INST("STR (imm)", "cccc010pu0w0nnnnttttvvvvvvvvvvvv", Store_imm(i)) // v1
|
||||||
INST("STR (reg)", "cccc011pu0w0nnnnttttvvvvvrr0mmmm", LoadStoreSingle_reg(i)) // v1
|
INST("STR (reg)", "cccc011pu0w0nnnnttttvvvvvrr0mmmm", Store_reg(i)) // v1
|
||||||
INST("STRB (imm)", "cccc010pu1w0nnnnttttvvvvvvvvvvvv", LoadStoreSingle_imm(i)) // v1
|
INST("STRB (imm)", "cccc010pu1w0nnnnttttvvvvvvvvvvvv", Store_imm(i)) // v1
|
||||||
INST("STRB (reg)", "cccc011pu1w0nnnnttttvvvvvrr0mmmm", LoadStoreSingle_reg(i)) // v1
|
INST("STRB (reg)", "cccc011pu1w0nnnnttttvvvvvrr0mmmm", Store_reg(i)) // v1
|
||||||
INST("STRD (imm)", "cccc000pu1w0nnnnttttvvvv1111vvvv", LoadStoreSingle_imm(i)) // v5E
|
INST("STRD (imm)", "cccc000pu1w0nnnnttttvvvv1111vvvv", 1) // v5E
|
||||||
INST("STRD (reg)", "cccc000pu0w0nnnntttt00001111mmmm", LoadStoreSingle_reg(i)) // v5E
|
INST("STRD (reg)", "cccc000pu0w0nnnntttt00001111mmmm", 1) // v5E
|
||||||
INST("STRH (imm)", "cccc000pu1w0nnnnttttvvvv1011vvvv", LoadStoreSingle_imm(i)) // v4
|
INST("STRH (imm)", "cccc000pu1w0nnnnttttvvvv1011vvvv", Store_reg_noshift(i)) // v4
|
||||||
INST("STRH (reg)", "cccc000pu0w0nnnntttt00001011mmmm", LoadStoreSingle_reg(i)) // v4
|
INST("STRH (reg)", "cccc000pu0w0nnnntttt00001011mmmm", Store_reg_noshift(i)) // v4
|
||||||
|
|
||||||
// Load/Store Multiple instructions
|
// Load/Store Multiple instructions
|
||||||
INST("LDM", "cccc100010w1nnnnxxxxxxxxxxxxxxxx", LoadStoreMultiple(i)) // v1
|
INST("LDM", "cccc100010w1nnnnxxxxxxxxxxxxxxxx", LoadStoreMultiple(i)) // v1
|
||||||
@ -370,7 +439,7 @@ const std::array arm_matchers{
|
|||||||
// clang-format on
|
// clang-format on
|
||||||
};
|
};
|
||||||
|
|
||||||
const std::array thumb_matchers{
|
const std::array thumb16_matchers{
|
||||||
// clang-format off
|
// clang-format off
|
||||||
|
|
||||||
// Shift (immediate) add, subtract, move and compare instructions
|
// Shift (immediate) add, subtract, move and compare instructions
|
||||||
@ -400,7 +469,7 @@ const std::array thumb_matchers{
|
|||||||
INST("CMP (reg, T1)", "0100001010mmmnnn", 1)
|
INST("CMP (reg, T1)", "0100001010mmmnnn", 1)
|
||||||
INST("CMN (reg)", "0100001011mmmnnn", 1)
|
INST("CMN (reg)", "0100001011mmmnnn", 1)
|
||||||
INST("ORR (reg)", "0100001100mmmddd", 1)
|
INST("ORR (reg)", "0100001100mmmddd", 1)
|
||||||
INST("MUL (reg)", "0100001101nnnddd", 1)
|
INST("MUL (reg)", "0100001101nnnddd", 2)
|
||||||
INST("BIC (reg)", "0100001110mmmddd", 1)
|
INST("BIC (reg)", "0100001110mmmddd", 1)
|
||||||
INST("MVN (reg)", "0100001111mmmddd", 1)
|
INST("MVN (reg)", "0100001111mmmddd", 1)
|
||||||
|
|
||||||
@ -410,23 +479,23 @@ const std::array thumb_matchers{
|
|||||||
INST("MOV (reg)", "01000110Dmmmmddd", 1) // v4T, Low regs: v6
|
INST("MOV (reg)", "01000110Dmmmmddd", 1) // v4T, Low regs: v6
|
||||||
|
|
||||||
// Store/Load single data item instructions
|
// Store/Load single data item instructions
|
||||||
INST("LDR (literal)", "01001tttvvvvvvvv", 2)
|
INST("LDR (literal)", "01001tttvvvvvvvv", 1)
|
||||||
INST("STR (reg)", "0101000mmmnnnttt", 2)
|
INST("STR (reg)", "0101000mmmnnnttt", 1)
|
||||||
INST("STRH (reg)", "0101001mmmnnnttt", 2)
|
INST("STRH (reg)", "0101001mmmnnnttt", 1)
|
||||||
INST("STRB (reg)", "0101010mmmnnnttt", 2)
|
INST("STRB (reg)", "0101010mmmnnnttt", 1)
|
||||||
INST("LDRSB (reg)", "0101011mmmnnnttt", 2)
|
INST("LDRSB (reg)", "0101011mmmnnnttt", 1)
|
||||||
INST("LDR (reg)", "0101100mmmnnnttt", 2)
|
INST("LDR (reg)", "0101100mmmnnnttt", 1)
|
||||||
INST("LDRH (reg)", "0101101mmmnnnttt", 2)
|
INST("LDRH (reg)", "0101101mmmnnnttt", 1)
|
||||||
INST("LDRB (reg)", "0101110mmmnnnttt", 2)
|
INST("LDRB (reg)", "0101110mmmnnnttt", 1)
|
||||||
INST("LDRSH (reg)", "0101111mmmnnnttt", 2)
|
INST("LDRSH (reg)", "0101111mmmnnnttt", 1)
|
||||||
INST("STR (imm, T1)", "01100vvvvvnnnttt", 2)
|
INST("STR (imm, T1)", "01100vvvvvnnnttt", 1)
|
||||||
INST("LDR (imm, T1)", "01101vvvvvnnnttt", 2)
|
INST("LDR (imm, T1)", "01101vvvvvnnnttt", 1)
|
||||||
INST("STRB (imm)", "01110vvvvvnnnttt", 2)
|
INST("STRB (imm)", "01110vvvvvnnnttt", 1)
|
||||||
INST("LDRB (imm)", "01111vvvvvnnnttt", 2)
|
INST("LDRB (imm)", "01111vvvvvnnnttt", 1)
|
||||||
INST("STRH (imm)", "10000vvvvvnnnttt", 2)
|
INST("STRH (imm)", "10000vvvvvnnnttt", 1)
|
||||||
INST("LDRH (imm)", "10001vvvvvnnnttt", 2)
|
INST("LDRH (imm)", "10001vvvvvnnnttt", 1)
|
||||||
INST("STR (imm, T2)", "10010tttvvvvvvvv", 2)
|
INST("STR (imm, T2)", "10010tttvvvvvvvv", 1)
|
||||||
INST("LDR (imm, T2)", "10011tttvvvvvvvv", 2)
|
INST("LDR (imm, T2)", "10011tttvvvvvvvv", 1)
|
||||||
|
|
||||||
// Generate relative address instructions
|
// Generate relative address instructions
|
||||||
INST("ADR", "10100dddvvvvvvvv", 1)
|
INST("ADR", "10100dddvvvvvvvv", 1)
|
||||||
@ -456,14 +525,22 @@ const std::array thumb_matchers{
|
|||||||
INST("LDMIA", "11001nnnxxxxxxxx", LoadStoreMultiple(i))
|
INST("LDMIA", "11001nnnxxxxxxxx", LoadStoreMultiple(i))
|
||||||
|
|
||||||
// Branch instructions
|
// Branch instructions
|
||||||
INST("BX", "010001110mmmm000", 5) // v4T
|
INST("BX", "010001110mmmm000", (i.template Get<"m">() == 14 ? 1 : indirect_branch_cycles)) // v4T
|
||||||
INST("BLX (reg)", "010001111mmmm000", 6) // v5T
|
INST("BLX (reg)", "010001111mmmm000", 2) // v5T
|
||||||
INST("UDF", "11011110--------", 8)
|
INST("UDF", "11011110--------", 8)
|
||||||
INST("SVC", "11011111xxxxxxxx", 8)
|
INST("SVC", "11011111xxxxxxxx", 8)
|
||||||
INST("B (T1)", "1101ccccvvvvvvvv", 4)
|
INST("B (T1)", "1101ccccvvvvvvvv", 1)
|
||||||
INST("B (T2)", "11100vvvvvvvvvvv", 4)
|
INST("B (T2)", "11100vvvvvvvvvvv", 1)
|
||||||
INST("BL (imm)", "11110Svvvvvvvvvv11j1jvvvvvvvvvvv", 4) // v4T
|
|
||||||
INST("BLX (imm)", "11110Svvvvvvvvvv11j0jvvvvvvvvvvv", 5) // v5T
|
// 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
|
// clang-format on
|
||||||
};
|
};
|
||||||
@ -473,14 +550,30 @@ const std::array thumb_matchers{
|
|||||||
namespace Core {
|
namespace Core {
|
||||||
|
|
||||||
u64 TicksForInstruction(bool is_thumb, u32 instruction) {
|
u64 TicksForInstruction(bool is_thumb, u32 instruction) {
|
||||||
if (is_thumb) {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto matches_instruction = [instruction](const auto& matcher) {
|
const auto matches_instruction = [instruction](const auto& matcher) {
|
||||||
return (instruction & matcher.mask) == matcher.expect;
|
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);
|
auto iter = std::find_if(arm_matchers.begin(), arm_matchers.end(), matches_instruction);
|
||||||
if (iter != arm_matchers.end()) {
|
if (iter != arm_matchers.end()) {
|
||||||
return iter->fn(instruction);
|
return iter->fn(instruction);
|
||||||
|
|||||||
@ -762,8 +762,13 @@ void System::Reset() {
|
|||||||
|
|
||||||
void System::ApplySettings() {
|
void System::ApplySettings() {
|
||||||
#ifdef ENABLE_GDBSTUB
|
#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::SetServerPort(Settings::values.gdbstub_port.GetValue());
|
||||||
GDBStub::ToggleServer(Settings::values.use_gdbstub.GetValue());
|
GDBStub::ToggleServer(Settings::values.use_gdbstub.GetValue());
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
if (gpu) {
|
if (gpu) {
|
||||||
|
|||||||
@ -421,6 +421,10 @@ public:
|
|||||||
|
|
||||||
void DebugUnscheduleAllThreadsFromFrontend(bool unschedule);
|
void DebugUnscheduleAllThreadsFromFrontend(bool unschedule);
|
||||||
|
|
||||||
|
void SetGDBPortOverride(int port) {
|
||||||
|
override_gdb_port = port;
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
/**
|
/**
|
||||||
* Initialize the emulated system.
|
* Initialize the emulated system.
|
||||||
@ -531,6 +535,7 @@ private:
|
|||||||
std::function<void()> info_led_color_changed;
|
std::function<void()> info_led_color_changed;
|
||||||
|
|
||||||
bool debug_next_process;
|
bool debug_next_process;
|
||||||
|
int override_gdb_port = -1;
|
||||||
|
|
||||||
friend class boost::serialization::access;
|
friend class boost::serialization::access;
|
||||||
template <typename Archive>
|
template <typename Archive>
|
||||||
|
|||||||
@ -444,6 +444,10 @@ std::string GetTitlePath(Service::FS::MediaType media_type, u64 tid);
|
|||||||
*/
|
*/
|
||||||
std::string GetMediaTitlePath(Service::FS::MediaType media_type);
|
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.
|
* Uninstalls the specified title.
|
||||||
* @param media_type the storage medium the title is installed to
|
* @param media_type the storage medium the title is installed to
|
||||||
|
|||||||
@ -105,7 +105,7 @@ static constexpr std::array<AppletTitleData, NumApplets> applet_titleids = {{
|
|||||||
// TODO(Subv): Fill in the rest of the 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");
|
ASSERT_MSG(id != AppletId::None, "Invalid applet id");
|
||||||
|
|
||||||
auto itr = std::find_if(applet_titleids.begin(), applet_titleids.end(),
|
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) {
|
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) {
|
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) {
|
AppletManager::AppletSlot AppletManager::GetAppletSlotFromId(AppletId id) {
|
||||||
@ -272,8 +274,9 @@ void AppletManager::CancelAndSendParameter(const MessageParameter& parameter) {
|
|||||||
parameter.sender_id);
|
parameter.sender_id);
|
||||||
|
|
||||||
if (parameter.buffer.size() >= sizeof(CaptureBufferInfo)) {
|
if (parameter.buffer.size() >= sizeof(CaptureBufferInfo)) {
|
||||||
SetCaptureInfo(parameter.buffer);
|
SetCaptureInfoSuspendedApp(parameter.buffer);
|
||||||
CaptureFrameBuffers();
|
CaptureFrameBuffers();
|
||||||
|
TransferCapturedFramebuffers();
|
||||||
}
|
}
|
||||||
|
|
||||||
next_parameter->sender_id = parameter.destination_id;
|
next_parameter->sender_id = parameter.destination_id;
|
||||||
@ -359,6 +362,15 @@ bool AppletManager::CancelParameter(bool check_sender, AppletId sender_appid, bo
|
|||||||
return cancellation_success;
|
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(
|
ResultVal<AppletManager::GetLockHandleResult> AppletManager::GetLockHandle(
|
||||||
AppletAttributes attributes) {
|
AppletAttributes attributes) {
|
||||||
auto corrected_attributes = attributes;
|
auto corrected_attributes = attributes;
|
||||||
@ -605,8 +617,6 @@ Result AppletManager::PrepareToStartLibraryApplet(AppletId applet_id) {
|
|||||||
last_library_launcher_slot = active_slot;
|
last_library_launcher_slot = active_slot;
|
||||||
last_prepared_library_applet = applet_id;
|
last_prepared_library_applet = applet_id;
|
||||||
|
|
||||||
capture_buffer_info.reset();
|
|
||||||
|
|
||||||
if (Settings::values.lle_applets) {
|
if (Settings::values.lle_applets) {
|
||||||
bool is_setup = system.GetAppLoader().DoingInitialSetup();
|
bool is_setup = system.GetAppLoader().DoingInitialSetup();
|
||||||
auto cfg = Service::CFG::GetModule(system);
|
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);
|
LOG_DEBUG(Service_APT, "Creating HLE applet {:03X} with parent {:03X}", applet_id, parent);
|
||||||
return CreateHLEApplet(applet_id, parent, false);
|
return CreateHLEApplet(applet_id, parent, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
capture_info.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
Result AppletManager::PreloadLibraryApplet(AppletId applet_id) {
|
Result AppletManager::PreloadLibraryApplet(AppletId applet_id) {
|
||||||
@ -726,6 +738,14 @@ Result AppletManager::CloseLibraryApplet(std::shared_ptr<Kernel::Object> object,
|
|||||||
SendParameter(param);
|
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;
|
return ResultSuccess;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -960,7 +980,7 @@ Result AppletManager::PrepareToJumpToHomeMenu() {
|
|||||||
|
|
||||||
last_jump_to_home_slot = active_slot;
|
last_jump_to_home_slot = active_slot;
|
||||||
|
|
||||||
capture_buffer_info.reset();
|
capture_info.reset();
|
||||||
|
|
||||||
if (last_jump_to_home_slot == AppletSlot::Application) {
|
if (last_jump_to_home_slot == AppletSlot::Application) {
|
||||||
EnsureHomeMenuLoaded();
|
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_title_id = title_id;
|
||||||
app_start_parameters->next_media_type = media_type;
|
app_start_parameters->next_media_type = media_type;
|
||||||
|
|
||||||
capture_buffer_info.reset();
|
capture_info.reset();
|
||||||
|
|
||||||
app_jump_parameters.Invalidate();
|
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,
|
static void CaptureFrameBuffer(Core::System& system, u32 capture_offset, std::span<u8> dst,
|
||||||
DisplayBufferMode mode) {
|
VAddr src, u32 height, DisplayBufferMode mode) {
|
||||||
const auto bpp = GetDisplayBufferModePixelSize(mode);
|
const auto bpp = GetDisplayBufferModePixelSize(mode);
|
||||||
if (bpp == 0) {
|
if (bpp == 0) {
|
||||||
return;
|
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,
|
system.Memory().RasterizerFlushVirtualRegion(src, GSP::FRAMEBUFFER_WIDTH * height * bpp,
|
||||||
Memory::FlushMode::Flush);
|
Memory::FlushMode::Flush);
|
||||||
|
|
||||||
// Address in VRAM that APT copies framebuffer captures to.
|
// APT captures the framebuffer to a copy on its own bss.
|
||||||
constexpr VAddr screen_capture_base_vaddr = Memory::VRAM_VADDR + 0x500000;
|
u8* dst_ptr = dst.data() + capture_offset;
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto src_ptr = system.Memory().GetPointer(src);
|
const auto src_ptr = system.Memory().GetPointer(src);
|
||||||
if (!src_ptr) {
|
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) +
|
const auto dst_offset = VideoCore::GetMortonOffset(x, y, bpp) +
|
||||||
(y & ~7) * GSP::FRAMEBUFFER_WIDTH_POW2 * bpp;
|
(y & ~7) * GSP::FRAMEBUFFER_WIDTH_POW2 * bpp;
|
||||||
const auto src_offset = bpp * (GSP::FRAMEBUFFER_WIDTH * y + x);
|
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);
|
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() {
|
void AppletManager::CaptureFrameBuffers() {
|
||||||
CaptureFrameBuffer(system, capture_info->bottom_screen_left_offset,
|
CaptureFrameBuffer(system, capture_info_suspended_app->bottom_screen_left_offset,
|
||||||
GSP::FRAMEBUFFER_SAVE_AREA_BOTTOM, GSP::BOTTOM_FRAMEBUFFER_HEIGHT,
|
framebuffer_backup, GSP::FRAMEBUFFER_SAVE_AREA_BOTTOM,
|
||||||
capture_info->bottom_screen_format);
|
GSP::BOTTOM_FRAMEBUFFER_HEIGHT,
|
||||||
CaptureFrameBuffer(system, capture_info->top_screen_left_offset,
|
capture_info_suspended_app->bottom_screen_format);
|
||||||
GSP::FRAMEBUFFER_SAVE_AREA_TOP_LEFT, GSP::TOP_FRAMEBUFFER_HEIGHT,
|
CaptureFrameBuffer(system, capture_info_suspended_app->top_screen_left_offset,
|
||||||
capture_info->top_screen_format);
|
framebuffer_backup, GSP::FRAMEBUFFER_SAVE_AREA_TOP_LEFT,
|
||||||
if (capture_info->is_3d) {
|
GSP::TOP_FRAMEBUFFER_HEIGHT, capture_info_suspended_app->top_screen_format);
|
||||||
CaptureFrameBuffer(system, capture_info->top_screen_right_offset,
|
if (capture_info_suspended_app->is_3d) {
|
||||||
GSP::FRAMEBUFFER_SAVE_AREA_TOP_RIGHT, GSP::TOP_FRAMEBUFFER_HEIGHT,
|
CaptureFrameBuffer(system, capture_info_suspended_app->top_screen_right_offset,
|
||||||
capture_info->top_screen_format);
|
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() {
|
void AppletManager::LoadInputDevices() {
|
||||||
home_button = Input::CreateDevice<Input::ButtonDevice>(
|
home_button = Input::CreateDevice<Input::ButtonDevice>(
|
||||||
Settings::values.current_input_profile.buttons[Settings::NativeButton::Home]);
|
Settings::values.current_input_profile.buttons[Settings::NativeButton::Home]);
|
||||||
|
|||||||
@ -101,8 +101,11 @@ enum class AppletId : u32 {
|
|||||||
Mint2 = 0x407,
|
Mint2 = 0x407,
|
||||||
Extrapad2 = 0x408,
|
Extrapad2 = 0x408,
|
||||||
Memolib2 = 0x409,
|
Memolib2 = 0x409,
|
||||||
|
TypeMask = 0xF00,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
u64 GetTitleIdForApplet(AppletId id, u32 region_value);
|
||||||
|
|
||||||
/// Application Old/New 3DS target platforms
|
/// Application Old/New 3DS target platforms
|
||||||
enum class TargetPlatform : u8 {
|
enum class TargetPlatform : u8 {
|
||||||
Old3ds = 0,
|
Old3ds = 0,
|
||||||
@ -288,6 +291,8 @@ public:
|
|||||||
bool CancelParameter(bool check_sender, AppletId sender_appid, bool check_receiver,
|
bool CancelParameter(bool check_sender, AppletId sender_appid, bool check_receiver,
|
||||||
AppletId receiver_appid);
|
AppletId receiver_appid);
|
||||||
|
|
||||||
|
void MapProgramIdForDebug(AppletId app_id, u64 title_id, FS::MediaType media_type);
|
||||||
|
|
||||||
struct GetLockHandleResult {
|
struct GetLockHandleResult {
|
||||||
AppletAttributes corrected_attributes;
|
AppletAttributes corrected_attributes;
|
||||||
u32 state;
|
u32 state;
|
||||||
@ -362,35 +367,36 @@ public:
|
|||||||
deliver_arg = std::move(arg);
|
deliver_arg = std::move(arg);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<u8> GetCaptureInfo() {
|
std::vector<u8> GetCaptureInfoSuspendedApp() {
|
||||||
std::vector<u8> buffer;
|
std::vector<u8> buffer;
|
||||||
if (capture_info) {
|
if (capture_info_suspended_app) {
|
||||||
buffer.resize(sizeof(CaptureBufferInfo));
|
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;
|
return buffer;
|
||||||
}
|
}
|
||||||
void SetCaptureInfo(std::vector<u8> buffer) {
|
void SetCaptureInfoSuspendedApp(std::vector<u8> buffer) {
|
||||||
ASSERT_MSG(buffer.size() >= sizeof(CaptureBufferInfo), "CaptureBufferInfo is too small.");
|
ASSERT_MSG(buffer.size() >= sizeof(CaptureBufferInfo), "CaptureBufferInfo is too small.");
|
||||||
|
|
||||||
capture_info.emplace();
|
capture_info_suspended_app.emplace();
|
||||||
std::memcpy(&capture_info.get(), buffer.data(), sizeof(CaptureBufferInfo));
|
std::memcpy(&capture_info_suspended_app.get(), buffer.data(), sizeof(CaptureBufferInfo));
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<u8> ReceiveCaptureBufferInfo() {
|
std::vector<u8> ReceiveCaptureBufferInfo() {
|
||||||
std::vector<u8> buffer;
|
std::vector<u8> buffer;
|
||||||
if (capture_buffer_info) {
|
if (capture_info) {
|
||||||
buffer.resize(sizeof(CaptureBufferInfo));
|
buffer.resize(sizeof(CaptureBufferInfo));
|
||||||
std::memcpy(buffer.data(), &capture_buffer_info.get(), sizeof(CaptureBufferInfo));
|
std::memcpy(buffer.data(), &capture_info.get(), sizeof(CaptureBufferInfo));
|
||||||
capture_buffer_info.reset();
|
capture_info.reset();
|
||||||
}
|
}
|
||||||
return buffer;
|
return buffer;
|
||||||
}
|
}
|
||||||
void SendCaptureBufferInfo(std::vector<u8> buffer) {
|
void SendCaptureBufferInfo(std::vector<u8> buffer) {
|
||||||
ASSERT_MSG(buffer.size() >= sizeof(CaptureBufferInfo), "CaptureBufferInfo is too small.");
|
ASSERT_MSG(buffer.size() >= sizeof(CaptureBufferInfo), "CaptureBufferInfo is too small.");
|
||||||
|
|
||||||
capture_buffer_info.emplace();
|
capture_info.emplace();
|
||||||
std::memcpy(&capture_buffer_info.get(), buffer.data(), sizeof(CaptureBufferInfo));
|
std::memcpy(&capture_info.get(), buffer.data(), sizeof(CaptureBufferInfo));
|
||||||
}
|
}
|
||||||
|
|
||||||
Result PrepareToStartApplication(u64 title_id, FS::MediaType media_type);
|
Result PrepareToStartApplication(u64 title_id, FS::MediaType media_type);
|
||||||
@ -446,8 +452,11 @@ private:
|
|||||||
boost::optional<SysMenuArg> sys_menu_arg{};
|
boost::optional<SysMenuArg> sys_menu_arg{};
|
||||||
u64 home_menu_tid_to_start{};
|
u64 home_menu_tid_to_start{};
|
||||||
|
|
||||||
|
boost::optional<CaptureBufferInfo> capture_info_suspended_app;
|
||||||
boost::optional<CaptureBufferInfo> capture_info;
|
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;
|
static constexpr std::size_t NumAppletSlot = 4;
|
||||||
|
|
||||||
@ -550,6 +559,7 @@ private:
|
|||||||
void EnsureHomeMenuLoaded();
|
void EnsureHomeMenuLoaded();
|
||||||
|
|
||||||
void CaptureFrameBuffers();
|
void CaptureFrameBuffers();
|
||||||
|
void TransferCapturedFramebuffers();
|
||||||
|
|
||||||
Result CreateHLEApplet(AppletId id, AppletId parent, bool preload);
|
Result CreateHLEApplet(AppletId id, AppletId parent, bool preload);
|
||||||
void HLEAppletUpdateEvent(std::uintptr_t user_data, s64 cycles_late);
|
void HLEAppletUpdateEvent(std::uintptr_t user_data, s64 cycles_late);
|
||||||
@ -566,8 +576,9 @@ private:
|
|||||||
ar & deliver_arg;
|
ar & deliver_arg;
|
||||||
ar & sys_menu_arg;
|
ar & sys_menu_arg;
|
||||||
ar & home_menu_tid_to_start;
|
ar & home_menu_tid_to_start;
|
||||||
|
ar & capture_info_suspended_app;
|
||||||
ar & capture_info;
|
ar & capture_info;
|
||||||
ar & capture_buffer_info;
|
ar & framebuffer_backup;
|
||||||
ar & active_slot;
|
ar & active_slot;
|
||||||
ar & last_library_launcher_slot;
|
ar & last_library_launcher_slot;
|
||||||
ar & last_prepared_library_applet;
|
ar & last_prepared_library_applet;
|
||||||
@ -579,7 +590,6 @@ private:
|
|||||||
ar & application_close_target;
|
ar & application_close_target;
|
||||||
ar & new_3ds_mode_blocked;
|
ar & new_3ds_mode_blocked;
|
||||||
ar & lock;
|
ar & lock;
|
||||||
ar & capture_info;
|
|
||||||
ar & applet_slots;
|
ar & applet_slots;
|
||||||
ar & library_applet_closing_command;
|
ar & library_applet_closing_command;
|
||||||
ar & next_app_mediatype;
|
ar & next_app_mediatype;
|
||||||
|
|||||||
@ -574,6 +574,28 @@ void Module::APTInterface::CancelParameter(Kernel::HLERequestContext& ctx) {
|
|||||||
receiver_appid));
|
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) {
|
void Module::APTInterface::PrepareToDoApplicationJump(Kernel::HLERequestContext& ctx) {
|
||||||
IPC::RequestParser rp(ctx);
|
IPC::RequestParser rp(ctx);
|
||||||
auto flags = rp.PopEnum<ApplicationJumpFlags>();
|
auto flags = rp.PopEnum<ApplicationJumpFlags>();
|
||||||
@ -1101,7 +1123,7 @@ void Module::APTInterface::GetCaptureInfo(Kernel::HLERequestContext& ctx) {
|
|||||||
|
|
||||||
LOG_DEBUG(Service_APT, "called");
|
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);
|
auto real_size = std::min(static_cast<u32>(screen_capture_buffer.size()), size);
|
||||||
screen_capture_buffer.resize(size);
|
screen_capture_buffer.resize(size);
|
||||||
|
|
||||||
@ -1410,6 +1432,58 @@ void Module::APTInterface::Reboot(Kernel::HLERequestContext& ctx) {
|
|||||||
rb.Push(ResultSuccess);
|
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) {
|
void Module::APTInterface::HardwareResetAsync(Kernel::HLERequestContext& ctx) {
|
||||||
IPC::RequestParser rp(ctx);
|
IPC::RequestParser rp(ctx);
|
||||||
|
|
||||||
|
|||||||
@ -376,6 +376,8 @@ public:
|
|||||||
*/
|
*/
|
||||||
void CancelParameter(Kernel::HLERequestContext& ctx);
|
void CancelParameter(Kernel::HLERequestContext& ctx);
|
||||||
|
|
||||||
|
void MapProgramIdForDebug(Kernel::HLERequestContext& ctx);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* APT::PrepareToStartApplication service function. When the input title-info programID is
|
* 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
|
* 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 Reboot(Kernel::HLERequestContext& ctx);
|
||||||
|
|
||||||
|
void GetAppletProgramInfo(Kernel::HLERequestContext& ctx);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* APT::HardwareResetAsync service function.
|
* APT::HardwareResetAsync service function.
|
||||||
* Outputs:
|
* Outputs:
|
||||||
|
|||||||
@ -27,7 +27,7 @@ APT_A::APT_A(std::shared_ptr<Module> apt)
|
|||||||
{0x000E, &APT_A::GlanceParameter, "GlanceParameter"},
|
{0x000E, &APT_A::GlanceParameter, "GlanceParameter"},
|
||||||
{0x000F, &APT_A::CancelParameter, "CancelParameter"},
|
{0x000F, &APT_A::CancelParameter, "CancelParameter"},
|
||||||
{0x0010, nullptr, "DebugFunc"},
|
{0x0010, nullptr, "DebugFunc"},
|
||||||
{0x0011, nullptr, "MapProgramIdForDebug"},
|
{0x0011, &APT_A::MapProgramIdForDebug, "MapProgramIdForDebug"},
|
||||||
{0x0012, nullptr, "SetHomeMenuAppletIdForDebug"},
|
{0x0012, nullptr, "SetHomeMenuAppletIdForDebug"},
|
||||||
{0x0013, nullptr, "GetPreparationState"},
|
{0x0013, nullptr, "GetPreparationState"},
|
||||||
{0x0014, nullptr, "SetPreparationState"},
|
{0x0014, nullptr, "SetPreparationState"},
|
||||||
@ -87,7 +87,7 @@ APT_A::APT_A(std::shared_ptr<Module> apt)
|
|||||||
{0x004A, &APT_A::GetCaptureInfo, "GetCaptureInfo"},
|
{0x004A, &APT_A::GetCaptureInfo, "GetCaptureInfo"},
|
||||||
{0x004B, &APT_A::AppletUtility, "AppletUtility"},
|
{0x004B, &APT_A::AppletUtility, "AppletUtility"},
|
||||||
{0x004C, nullptr, "SetFatalErrDispMode"},
|
{0x004C, nullptr, "SetFatalErrDispMode"},
|
||||||
{0x004D, nullptr, "GetAppletProgramInfo"},
|
{0x004D, &APT_A::GetAppletProgramInfo, "GetAppletProgramInfo"},
|
||||||
{0x004E, &APT_A::HardwareResetAsync, "HardwareResetAsync"},
|
{0x004E, &APT_A::HardwareResetAsync, "HardwareResetAsync"},
|
||||||
{0x004F, &APT_A::SetAppCpuTimeLimit, "SetAppCpuTimeLimit"},
|
{0x004F, &APT_A::SetAppCpuTimeLimit, "SetAppCpuTimeLimit"},
|
||||||
{0x0050, &APT_A::GetAppCpuTimeLimit, "GetAppCpuTimeLimit"},
|
{0x0050, &APT_A::GetAppCpuTimeLimit, "GetAppCpuTimeLimit"},
|
||||||
|
|||||||
@ -27,7 +27,7 @@ APT_S::APT_S(std::shared_ptr<Module> apt)
|
|||||||
{0x000E, &APT_S::GlanceParameter, "GlanceParameter"},
|
{0x000E, &APT_S::GlanceParameter, "GlanceParameter"},
|
||||||
{0x000F, &APT_S::CancelParameter, "CancelParameter"},
|
{0x000F, &APT_S::CancelParameter, "CancelParameter"},
|
||||||
{0x0010, nullptr, "DebugFunc"},
|
{0x0010, nullptr, "DebugFunc"},
|
||||||
{0x0011, nullptr, "MapProgramIdForDebug"},
|
{0x0011, &APT_S::MapProgramIdForDebug, "MapProgramIdForDebug"},
|
||||||
{0x0012, nullptr, "SetHomeMenuAppletIdForDebug"},
|
{0x0012, nullptr, "SetHomeMenuAppletIdForDebug"},
|
||||||
{0x0013, nullptr, "GetPreparationState"},
|
{0x0013, nullptr, "GetPreparationState"},
|
||||||
{0x0014, nullptr, "SetPreparationState"},
|
{0x0014, nullptr, "SetPreparationState"},
|
||||||
@ -87,7 +87,7 @@ APT_S::APT_S(std::shared_ptr<Module> apt)
|
|||||||
{0x004A, &APT_S::GetCaptureInfo, "GetCaptureInfo"},
|
{0x004A, &APT_S::GetCaptureInfo, "GetCaptureInfo"},
|
||||||
{0x004B, &APT_S::AppletUtility, "AppletUtility"},
|
{0x004B, &APT_S::AppletUtility, "AppletUtility"},
|
||||||
{0x004C, nullptr, "SetFatalErrDispMode"},
|
{0x004C, nullptr, "SetFatalErrDispMode"},
|
||||||
{0x004D, nullptr, "GetAppletProgramInfo"},
|
{0x004D, &APT_S::GetAppletProgramInfo, "GetAppletProgramInfo"},
|
||||||
{0x004E, &APT_S::HardwareResetAsync, "HardwareResetAsync"},
|
{0x004E, &APT_S::HardwareResetAsync, "HardwareResetAsync"},
|
||||||
{0x004F, &APT_S::SetAppCpuTimeLimit, "SetAppCpuTimeLimit"},
|
{0x004F, &APT_S::SetAppCpuTimeLimit, "SetAppCpuTimeLimit"},
|
||||||
{0x0050, &APT_S::GetAppCpuTimeLimit, "GetAppCpuTimeLimit"},
|
{0x0050, &APT_S::GetAppCpuTimeLimit, "GetAppCpuTimeLimit"},
|
||||||
|
|||||||
@ -27,7 +27,7 @@ APT_U::APT_U(std::shared_ptr<Module> apt)
|
|||||||
{0x000E, &APT_U::GlanceParameter, "GlanceParameter"},
|
{0x000E, &APT_U::GlanceParameter, "GlanceParameter"},
|
||||||
{0x000F, &APT_U::CancelParameter, "CancelParameter"},
|
{0x000F, &APT_U::CancelParameter, "CancelParameter"},
|
||||||
{0x0010, nullptr, "DebugFunc"},
|
{0x0010, nullptr, "DebugFunc"},
|
||||||
{0x0011, nullptr, "MapProgramIdForDebug"},
|
{0x0011, &APT_U::MapProgramIdForDebug, "MapProgramIdForDebug"},
|
||||||
{0x0012, nullptr, "SetHomeMenuAppletIdForDebug"},
|
{0x0012, nullptr, "SetHomeMenuAppletIdForDebug"},
|
||||||
{0x0013, nullptr, "GetPreparationState"},
|
{0x0013, nullptr, "GetPreparationState"},
|
||||||
{0x0014, nullptr, "SetPreparationState"},
|
{0x0014, nullptr, "SetPreparationState"},
|
||||||
@ -87,7 +87,7 @@ APT_U::APT_U(std::shared_ptr<Module> apt)
|
|||||||
{0x004A, &APT_U::GetCaptureInfo, "GetCaptureInfo"},
|
{0x004A, &APT_U::GetCaptureInfo, "GetCaptureInfo"},
|
||||||
{0x004B, &APT_U::AppletUtility, "AppletUtility"},
|
{0x004B, &APT_U::AppletUtility, "AppletUtility"},
|
||||||
{0x004C, nullptr, "SetFatalErrDispMode"},
|
{0x004C, nullptr, "SetFatalErrDispMode"},
|
||||||
{0x004D, nullptr, "GetAppletProgramInfo"},
|
{0x004D, &APT_U::GetAppletProgramInfo, "GetAppletProgramInfo"},
|
||||||
{0x004E, &APT_U::HardwareResetAsync, "HardwareResetAsync"},
|
{0x004E, &APT_U::HardwareResetAsync, "HardwareResetAsync"},
|
||||||
{0x004F, &APT_U::SetAppCpuTimeLimit, "SetAppCpuTimeLimit"},
|
{0x004F, &APT_U::SetAppCpuTimeLimit, "SetAppCpuTimeLimit"},
|
||||||
{0x0050, &APT_U::GetAppCpuTimeLimit, "GetAppCpuTimeLimit"},
|
{0x0050, &APT_U::GetAppCpuTimeLimit, "GetAppCpuTimeLimit"},
|
||||||
|
|||||||
@ -602,6 +602,43 @@ void Module::Interface::GetModelNintendo2DS(Kernel::HLERequestContext& ctx) {
|
|||||||
rb.Push(model != Service::CFG::NINTENDO_2DS);
|
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) {
|
void Module::Interface::GetConfig(Kernel::HLERequestContext& ctx) {
|
||||||
IPC::RequestParser rp(ctx);
|
IPC::RequestParser rp(ctx);
|
||||||
u32 size = rp.Pop<u32>();
|
u32 size = rp.Pop<u32>();
|
||||||
|
|||||||
@ -152,6 +152,15 @@ struct ConsoleCountryInfo {
|
|||||||
std::array<u8, 2> unknown; ///< Unknown data
|
std::array<u8, 2> unknown; ///< Unknown data
|
||||||
u8 state_code; ///< The state or province code.
|
u8 state_code; ///< The state or province code.
|
||||||
u8 country_code; ///< The country code of the console
|
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");
|
static_assert(sizeof(ConsoleCountryInfo) == 4, "ConsoleCountryInfo must be exactly 4 bytes");
|
||||||
|
|
||||||
@ -315,6 +324,8 @@ public:
|
|||||||
*/
|
*/
|
||||||
void GetModelNintendo2DS(Kernel::HLERequestContext& ctx);
|
void GetModelNintendo2DS(Kernel::HLERequestContext& ctx);
|
||||||
|
|
||||||
|
void TranslateCountryInfo(Kernel::HLERequestContext& ctx);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* CFG::GetConfig service function
|
* CFG::GetConfig service function
|
||||||
* Inputs:
|
* Inputs:
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
// Copyright 2014 Citra Emulator Project
|
// Copyright Citra Emulator Project / Azahar Emulator Project
|
||||||
// Licensed under GPLv2 or any later version
|
// Licensed under GPLv2 or any later version
|
||||||
// Refer to the license.txt file included.
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
@ -20,7 +20,7 @@ CFG_I::CFG_I(std::shared_ptr<Module> cfg) : Module::Interface(std::move(cfg), "c
|
|||||||
{0x0005, &CFG_I::GetSystemModel, "GetSystemModel"},
|
{0x0005, &CFG_I::GetSystemModel, "GetSystemModel"},
|
||||||
{0x0006, &CFG_I::GetModelNintendo2DS, "GetModelNintendo2DS"},
|
{0x0006, &CFG_I::GetModelNintendo2DS, "GetModelNintendo2DS"},
|
||||||
{0x0007, nullptr, "WriteToFirstByteCfgSavegame"},
|
{0x0007, nullptr, "WriteToFirstByteCfgSavegame"},
|
||||||
{0x0008, nullptr, "TranslateCountryInfo"},
|
{0x0008, &CFG_I::TranslateCountryInfo, "TranslateCountryInfo"},
|
||||||
{0x0009, &CFG_I::GetCountryCodeString, "GetCountryCodeString"},
|
{0x0009, &CFG_I::GetCountryCodeString, "GetCountryCodeString"},
|
||||||
{0x000A, &CFG_I::GetCountryCodeID, "GetCountryCodeID"},
|
{0x000A, &CFG_I::GetCountryCodeID, "GetCountryCodeID"},
|
||||||
{0x000B, nullptr, "IsFangateSupported"},
|
{0x000B, nullptr, "IsFangateSupported"},
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
// Copyright 2015 Citra Emulator Project
|
// Copyright Citra Emulator Project / Azahar Emulator Project
|
||||||
// Licensed under GPLv2 or any later version
|
// Licensed under GPLv2 or any later version
|
||||||
// Refer to the license.txt file included.
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
@ -20,7 +20,7 @@ CFG_S::CFG_S(std::shared_ptr<Module> cfg) : Module::Interface(std::move(cfg), "c
|
|||||||
{0x0005, &CFG_S::GetSystemModel, "GetSystemModel"},
|
{0x0005, &CFG_S::GetSystemModel, "GetSystemModel"},
|
||||||
{0x0006, &CFG_S::GetModelNintendo2DS, "GetModelNintendo2DS"},
|
{0x0006, &CFG_S::GetModelNintendo2DS, "GetModelNintendo2DS"},
|
||||||
{0x0007, nullptr, "WriteToFirstByteCfgSavegame"},
|
{0x0007, nullptr, "WriteToFirstByteCfgSavegame"},
|
||||||
{0x0008, nullptr, "TranslateCountryInfo"},
|
{0x0008, &CFG_S::TranslateCountryInfo, "TranslateCountryInfo"},
|
||||||
{0x0009, &CFG_S::GetCountryCodeString, "GetCountryCodeString"},
|
{0x0009, &CFG_S::GetCountryCodeString, "GetCountryCodeString"},
|
||||||
{0x000A, &CFG_S::GetCountryCodeID, "GetCountryCodeID"},
|
{0x000A, &CFG_S::GetCountryCodeID, "GetCountryCodeID"},
|
||||||
{0x000B, nullptr, "IsFangateSupported"},
|
{0x000B, nullptr, "IsFangateSupported"},
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
// Copyright 2014 Citra Emulator Project
|
// Copyright Citra Emulator Project / Azahar Emulator Project
|
||||||
// Licensed under GPLv2 or any later version
|
// Licensed under GPLv2 or any later version
|
||||||
// Refer to the license.txt file included.
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
@ -20,7 +20,7 @@ CFG_U::CFG_U(std::shared_ptr<Module> cfg) : Module::Interface(std::move(cfg), "c
|
|||||||
{0x0005, &CFG_U::GetSystemModel, "GetSystemModel"},
|
{0x0005, &CFG_U::GetSystemModel, "GetSystemModel"},
|
||||||
{0x0006, &CFG_U::GetModelNintendo2DS, "GetModelNintendo2DS"},
|
{0x0006, &CFG_U::GetModelNintendo2DS, "GetModelNintendo2DS"},
|
||||||
{0x0007, nullptr, "WriteToFirstByteCfgSavegame"},
|
{0x0007, nullptr, "WriteToFirstByteCfgSavegame"},
|
||||||
{0x0008, nullptr, "TranslateCountryInfo"},
|
{0x0008, &CFG_U::TranslateCountryInfo, "TranslateCountryInfo"},
|
||||||
{0x0009, &CFG_U::GetCountryCodeString, "GetCountryCodeString"},
|
{0x0009, &CFG_U::GetCountryCodeString, "GetCountryCodeString"},
|
||||||
{0x000A, &CFG_U::GetCountryCodeID, "GetCountryCodeID"},
|
{0x000A, &CFG_U::GetCountryCodeID, "GetCountryCodeID"},
|
||||||
{0x000B, nullptr, "IsFangateSupported"},
|
{0x000B, nullptr, "IsFangateSupported"},
|
||||||
|
|||||||
@ -39,10 +39,13 @@ namespace ErrCodes {
|
|||||||
enum {
|
enum {
|
||||||
// TODO(purpasmart): Check if this name fits its actual usage
|
// TODO(purpasmart): Check if this name fits its actual usage
|
||||||
OutofRangeOrMisalignedAddress = 513,
|
OutofRangeOrMisalignedAddress = 513,
|
||||||
|
NoSaveVRAMSysAreaPerm = 518,
|
||||||
FirstInitialization = 519,
|
FirstInitialization = 519,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
constexpr Result ResultNoSaveVRAMSysAreaPerm(ErrCodes::NoSaveVRAMSysAreaPerm, ErrorModule::GX,
|
||||||
|
ErrorSummary::NothingHappened, ErrorLevel::Permanent);
|
||||||
constexpr Result ResultFirstInitialization(ErrCodes::FirstInitialization, ErrorModule::GX,
|
constexpr Result ResultFirstInitialization(ErrCodes::FirstInitialization, ErrorModule::GX,
|
||||||
ErrorSummary::Success, ErrorLevel::Success);
|
ErrorSummary::Success, ErrorLevel::Success);
|
||||||
constexpr Result ResultRegsOutOfRangeOrMisaligned(ErrCodes::OutofRangeOrMisalignedAddress,
|
constexpr Result ResultRegsOutOfRangeOrMisaligned(ErrCodes::OutofRangeOrMisalignedAddress,
|
||||||
@ -71,13 +74,34 @@ CommandBuffer* GSP_GPU::GetCommandBuffer(u32 thread_id) {
|
|||||||
return reinterpret_cast<CommandBuffer*>(ptr);
|
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");
|
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
|
// For each thread there are two FrameBufferUpdate fields
|
||||||
const u32 offset = 0x200 + (2 * thread_id + screen_index) * sizeof(FrameBufferUpdate);
|
const u32 offset = 0x200 + (2 * thread_id + screen_index) * sizeof(FrameBufferUpdate);
|
||||||
u8* ptr = shared_memory->GetPointer(offset);
|
FrameBufferUpdate* screen =
|
||||||
return reinterpret_cast<FrameBufferUpdate*>(ptr);
|
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) {
|
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) {
|
void GSP_GPU::ClientDisconnected(std::shared_ptr<Kernel::ServerSession> server_session) {
|
||||||
const SessionData* session_data = GetSessionData(server_session);
|
SessionData* session_data = GetSessionData(server_session);
|
||||||
if (active_thread_id == session_data->thread_id) {
|
session_data->interrupt_event = nullptr;
|
||||||
|
session_data->registered = false;
|
||||||
|
if (thread_id_with_rights == session_data->thread_id) {
|
||||||
ReleaseRight(session_data);
|
ReleaseRight(session_data);
|
||||||
}
|
}
|
||||||
|
if (True(session_data->relay_queue_flags & RelayEventQueueFlags::AllowSaveVramSysArea)) {
|
||||||
|
vram_backup_handler.ResetState();
|
||||||
|
}
|
||||||
SessionRequestHandler::ClientDisconnected(server_session);
|
SessionRequestHandler::ClientDisconnected(server_session);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -190,6 +219,41 @@ static Result WriteHWRegsWithMask(u32 base_address, u32 size_in_bytes, std::span
|
|||||||
return ResultSuccess;
|
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) {
|
void GSP_GPU::WriteHWRegs(Kernel::HLERequestContext& ctx) {
|
||||||
IPC::RequestParser rp(ctx);
|
IPC::RequestParser rp(ctx);
|
||||||
const u32 reg_addr = rp.Pop<u32>();
|
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) {
|
void GSP_GPU::RegisterInterruptRelayQueue(Kernel::HLERequestContext& ctx) {
|
||||||
IPC::RequestParser rp(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>();
|
auto interrupt_event = rp.PopObject<Kernel::Event>();
|
||||||
ASSERT_MSG(interrupt_event, "handle is not valid!");
|
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());
|
SessionData* session_data = GetSessionData(ctx.Session());
|
||||||
session_data->interrupt_event = std::move(interrupt_event);
|
session_data->interrupt_event = std::move(interrupt_event);
|
||||||
session_data->registered = true;
|
session_data->registered = true;
|
||||||
|
session_data->relay_queue_flags = relay_queue_flags;
|
||||||
|
|
||||||
IPC::RequestBuilder rb = rp.MakeBuilder(2, 2);
|
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.Push(session_data->thread_id);
|
||||||
rb.PushCopyObjects(shared_memory);
|
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) {
|
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
|
// Update framebuffer information if requested
|
||||||
const s32 screen_id = (interrupt_id == InterruptId::PDC0) ? 0 : 1;
|
const s32 screen_id = (interrupt_id == InterruptId::PDC0) ? 0 : 1;
|
||||||
|
|
||||||
auto* info = GetFrameBufferInfo(thread_id, screen_id);
|
auto info = GetFrameBufferInfo(thread_id, screen_id);
|
||||||
if (info->is_dirty) {
|
if (info.first || force_buffer_swap[screen_id]) {
|
||||||
system.GPU().SetBufferSwap(screen_id, info->framebuffer_info[info->index]);
|
force_buffer_swap[screen_id] = false;
|
||||||
info->is_dirty.Assign(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 {
|
} 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.
|
// 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;
|
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) {
|
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) {
|
void GSP_GPU::TriggerCmdReqQueue(Kernel::HLERequestContext& ctx) {
|
||||||
IPC::RequestParser rp(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();
|
auto& gpu = system.GPU();
|
||||||
|
|
||||||
bool requires_delay = false;
|
bool requires_delay = false;
|
||||||
@ -628,17 +708,18 @@ void GSP_GPU::ImportDisplayCaptureInfo(Kernel::HLERequestContext& ctx) {
|
|||||||
|
|
||||||
LOG_DEBUG(Service_GSP, "called");
|
LOG_DEBUG(Service_GSP, "called");
|
||||||
|
|
||||||
if (active_thread_id == std::numeric_limits<u32>::max()) {
|
auto top_screen_info = GetFrameBufferInfo(thread_id_with_rights, 0);
|
||||||
LOG_WARNING(Service_GSP, "Called without an active thread.");
|
auto bottom_screen_info = GetFrameBufferInfo(thread_id_with_rights, 1);
|
||||||
|
|
||||||
// TODO: Find the right error code.
|
if (top_screen_info.first) {
|
||||||
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
|
force_buffer_swap[0] = true;
|
||||||
rb.Push(-1);
|
}
|
||||||
return;
|
if (bottom_screen_info.first) {
|
||||||
|
force_buffer_swap[1] = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
FrameBufferUpdate* top_screen = GetFrameBufferInfo(active_thread_id, 0);
|
auto top_screen = top_screen_info.second;
|
||||||
FrameBufferUpdate* bottom_screen = GetFrameBufferInfo(active_thread_id, 1);
|
auto bottom_screen = bottom_screen_info.second;
|
||||||
|
|
||||||
struct CaptureInfoEntry {
|
struct CaptureInfoEntry {
|
||||||
u32_le address_left;
|
u32_le address_left;
|
||||||
@ -649,16 +730,15 @@ void GSP_GPU::ImportDisplayCaptureInfo(Kernel::HLERequestContext& ctx) {
|
|||||||
|
|
||||||
CaptureInfoEntry top_entry, bottom_entry;
|
CaptureInfoEntry top_entry, bottom_entry;
|
||||||
// Top Screen
|
// Top Screen
|
||||||
top_entry.address_left = top_screen->framebuffer_info[top_screen->index].address_left;
|
top_entry.address_left = top_screen->address_left;
|
||||||
top_entry.address_right = top_screen->framebuffer_info[top_screen->index].address_right;
|
top_entry.address_right = top_screen->address_right;
|
||||||
top_entry.format = top_screen->framebuffer_info[top_screen->index].format;
|
top_entry.format = top_screen->format;
|
||||||
top_entry.stride = top_screen->framebuffer_info[top_screen->index].stride;
|
top_entry.stride = top_screen->stride;
|
||||||
// Bottom Screen
|
// Bottom Screen
|
||||||
bottom_entry.address_left = bottom_screen->framebuffer_info[bottom_screen->index].address_left;
|
bottom_entry.address_left = bottom_screen->address_left;
|
||||||
bottom_entry.address_right =
|
bottom_entry.address_right = bottom_screen->address_right;
|
||||||
bottom_screen->framebuffer_info[bottom_screen->index].address_right;
|
bottom_entry.format = bottom_screen->format;
|
||||||
bottom_entry.format = bottom_screen->framebuffer_info[bottom_screen->index].format;
|
bottom_entry.stride = bottom_screen->stride;
|
||||||
bottom_entry.stride = bottom_screen->framebuffer_info[bottom_screen->index].stride;
|
|
||||||
|
|
||||||
IPC::RequestBuilder rb = rp.MakeBuilder(9, 0);
|
IPC::RequestBuilder rb = rp.MakeBuilder(9, 0);
|
||||||
rb.Push(ResultSuccess);
|
rb.Push(ResultSuccess);
|
||||||
@ -722,26 +802,19 @@ void GSP_GPU::SaveVramSysArea(Kernel::HLERequestContext& ctx) {
|
|||||||
// Invalid values
|
// Invalid values
|
||||||
0, 0, 0};
|
0, 0, 0};
|
||||||
|
|
||||||
if (active_thread_id == std::numeric_limits<u32>::max()) {
|
const auto session_data = GetSessionData(ctx.Session());
|
||||||
LOG_WARNING(Service_GSP, "Called without an active thread.");
|
if (False(session_data->relay_queue_flags & RelayEventQueueFlags::AllowSaveVramSysArea)) {
|
||||||
|
|
||||||
// TODO: Find the right error code.
|
|
||||||
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
|
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
|
||||||
rb.Push(-1);
|
rb.Push(ResultNoSaveVRAMSysAreaPerm);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
system.Memory().RasterizerFlushVirtualRegion(Memory::VRAM_VADDR, Memory::VRAM_SIZE,
|
vram_backup_handler.SaveVRAMWithState(VramBackupHandler::State::STATE_0);
|
||||||
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);
|
|
||||||
|
|
||||||
auto top_screen = GetFrameBufferInfo(active_thread_id, 0);
|
auto top_screen = GetFrameBufferInfo(thread_id_with_rights, 0).second;
|
||||||
if (top_screen) {
|
if (top_screen) {
|
||||||
u8 bytes_per_pixel =
|
u8 bytes_per_pixel = bpp_per_format[top_screen->GetPixelFormat()];
|
||||||
bpp_per_format[top_screen->framebuffer_info[top_screen->index].GetPixelFormat()];
|
const auto& top_fb = *top_screen;
|
||||||
const auto top_fb = top_screen->framebuffer_info[top_screen->index];
|
|
||||||
if (top_fb.address_left && bytes_per_pixel != 0 && bytes_per_pixel != 4) {
|
if (top_fb.address_left && bytes_per_pixel != 0 && bytes_per_pixel != 4) {
|
||||||
CopyFrameBuffer(system, FRAMEBUFFER_SAVE_AREA_TOP_LEFT, top_fb.address_left,
|
CopyFrameBuffer(system, FRAMEBUFFER_SAVE_AREA_TOP_LEFT, top_fb.address_left,
|
||||||
FRAMEBUFFER_WIDTH * bytes_per_pixel, top_fb.stride,
|
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);
|
FRAMEBUFFER_WIDTH * bytes_per_pixel, TOP_FRAMEBUFFER_HEIGHT);
|
||||||
}
|
}
|
||||||
|
|
||||||
FrameBufferInfo fb_info = top_screen->framebuffer_info[top_screen->index];
|
top_screen->address_left = FRAMEBUFFER_SAVE_AREA_TOP_LEFT;
|
||||||
|
top_screen->address_right = FRAMEBUFFER_SAVE_AREA_TOP_RIGHT;
|
||||||
fb_info.address_left = FRAMEBUFFER_SAVE_AREA_TOP_LEFT;
|
top_screen->stride = FRAMEBUFFER_WIDTH * bytes_per_pixel;
|
||||||
fb_info.address_right = FRAMEBUFFER_SAVE_AREA_TOP_RIGHT;
|
force_buffer_swap[0] = true;
|
||||||
fb_info.stride = FRAMEBUFFER_WIDTH * bytes_per_pixel;
|
|
||||||
system.GPU().SetBufferSwap(0, fb_info);
|
|
||||||
} else {
|
} else {
|
||||||
LOG_WARNING(Service_GSP, "No top screen bound, skipping capture.");
|
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) {
|
if (bottom_screen) {
|
||||||
u8 bytes_per_pixel =
|
u8 bytes_per_pixel = bpp_per_format[bottom_screen->GetPixelFormat()];
|
||||||
bpp_per_format[bottom_screen->framebuffer_info[bottom_screen->index].GetPixelFormat()];
|
const auto& bottom_fb = *bottom_screen;
|
||||||
const auto bottom_fb = bottom_screen->framebuffer_info[bottom_screen->index];
|
|
||||||
if (bottom_fb.address_left && bytes_per_pixel != 0 && bytes_per_pixel != 4) {
|
if (bottom_fb.address_left && bytes_per_pixel != 0 && bytes_per_pixel != 4) {
|
||||||
CopyFrameBuffer(system, FRAMEBUFFER_SAVE_AREA_BOTTOM, bottom_fb.address_left,
|
CopyFrameBuffer(system, FRAMEBUFFER_SAVE_AREA_BOTTOM, bottom_fb.address_left,
|
||||||
FRAMEBUFFER_WIDTH * bytes_per_pixel, bottom_fb.stride,
|
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,
|
ClearFramebuffer(system, FRAMEBUFFER_SAVE_AREA_BOTTOM,
|
||||||
FRAMEBUFFER_WIDTH * bytes_per_pixel, BOTTOM_FRAMEBUFFER_HEIGHT);
|
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;
|
bottom_screen->address_left = FRAMEBUFFER_SAVE_AREA_BOTTOM;
|
||||||
fb_info.stride = FRAMEBUFFER_WIDTH * bytes_per_pixel;
|
bottom_screen->stride = FRAMEBUFFER_WIDTH * bytes_per_pixel;
|
||||||
system.GPU().SetBufferSwap(1, fb_info);
|
force_buffer_swap[1] = true;
|
||||||
} else {
|
} else {
|
||||||
LOG_WARNING(Service_GSP, "No bottom screen bound, skipping capture.");
|
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);
|
// Wait for VBlank
|
||||||
rb.Push(ResultSuccess);
|
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) {
|
void GSP_GPU::RestoreVramSysArea(Kernel::HLERequestContext& ctx) {
|
||||||
@ -805,31 +877,35 @@ void GSP_GPU::RestoreVramSysArea(Kernel::HLERequestContext& ctx) {
|
|||||||
|
|
||||||
LOG_DEBUG(Service_GSP, "called");
|
LOG_DEBUG(Service_GSP, "called");
|
||||||
|
|
||||||
if (saved_vram) {
|
const auto session_data = GetSessionData(ctx.Session());
|
||||||
auto vram = system.Memory().GetPointer(Memory::VRAM_VADDR);
|
if (False(session_data->relay_queue_flags & RelayEventQueueFlags::AllowSaveVramSysArea)) {
|
||||||
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 (?).
|
|
||||||
|
|
||||||
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
|
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,
|
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().vs_setup.requires_fixup = requires_shader_fixup;
|
||||||
gpu.PicaCore().gs_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,
|
return {ErrorDescription::AlreadyDone, ErrorModule::GX, ErrorSummary::Success,
|
||||||
ErrorLevel::Success};
|
ErrorLevel::Success};
|
||||||
}
|
}
|
||||||
@ -870,14 +946,14 @@ Result GSP_GPU::AcquireGpuRight(const Kernel::HLERequestContext& ctx,
|
|||||||
|
|
||||||
if (blocking) {
|
if (blocking) {
|
||||||
// TODO: The thread should be put to sleep until acquired.
|
// 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.");
|
"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,
|
return {ErrorDescription::Busy, ErrorModule::GX, ErrorSummary::WouldBlock,
|
||||||
ErrorLevel::Status};
|
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;
|
active_client_thread_id = ctx.ClientThread()->thread_id;
|
||||||
return ResultSuccess;
|
return ResultSuccess;
|
||||||
}
|
}
|
||||||
@ -904,16 +980,17 @@ void GSP_GPU::AcquireRight(Kernel::HLERequestContext& ctx) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void GSP_GPU::ReleaseRight(const SessionData* session_data) {
|
void GSP_GPU::ReleaseRight(const SessionData* session_data) {
|
||||||
ASSERT_MSG(active_thread_id == session_data->thread_id,
|
if (thread_id_with_rights == session_data->thread_id) {
|
||||||
"Wrong thread tried to release GPU right");
|
thread_id_with_rights = std::numeric_limits<u32>::max();
|
||||||
active_thread_id = std::numeric_limits<u32>::max();
|
|
||||||
active_client_thread_id = std::numeric_limits<u32>::max();
|
active_client_thread_id = std::numeric_limits<u32>::max();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void GSP_GPU::ReleaseRight(Kernel::HLERequestContext& ctx) {
|
void GSP_GPU::ReleaseRight(Kernel::HLERequestContext& ctx) {
|
||||||
IPC::RequestParser rp(ctx);
|
IPC::RequestParser rp(ctx);
|
||||||
|
|
||||||
const SessionData* session_data = GetSessionData(ctx.Session());
|
const SessionData* session_data = GetSessionData(ctx.Session());
|
||||||
|
// Success even if wrong thread calls this function.
|
||||||
ReleaseRight(session_data);
|
ReleaseRight(session_data);
|
||||||
|
|
||||||
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
|
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
|
||||||
@ -976,14 +1053,16 @@ void GSP_GPU::serialize(Archive& ar, const unsigned int) {
|
|||||||
DEBUG_SERIALIZATION_POINT;
|
DEBUG_SERIALIZATION_POINT;
|
||||||
ar& boost::serialization::base_object<Kernel::SessionRequestHandler>(*this);
|
ar& boost::serialization::base_object<Kernel::SessionRequestHandler>(*this);
|
||||||
ar & shared_memory;
|
ar & shared_memory;
|
||||||
ar & active_thread_id;
|
ar & thread_id_with_rights;
|
||||||
ar & active_client_thread_id;
|
ar & active_client_thread_id;
|
||||||
ar & first_initialization;
|
ar & first_initialization;
|
||||||
ar & used_thread_ids;
|
ar & used_thread_ids;
|
||||||
ar & saved_vram;
|
|
||||||
ar & delay_texture_copy_completion;
|
ar & delay_texture_copy_completion;
|
||||||
ar & pending_interrupts;
|
ar & pending_interrupts;
|
||||||
ar & perf_recorder;
|
ar & perf_recorder;
|
||||||
|
ar & cached_framebuffer_infos;
|
||||||
|
ar & force_buffer_swap;
|
||||||
|
ar & vram_backup_handler;
|
||||||
}
|
}
|
||||||
SERIALIZE_IMPL(GSP_GPU)
|
SERIALIZE_IMPL(GSP_GPU)
|
||||||
|
|
||||||
@ -1051,6 +1130,8 @@ void SessionData::serialize(Archive& ar, const unsigned int) {
|
|||||||
ar & interrupt_event;
|
ar & interrupt_event;
|
||||||
ar & thread_id;
|
ar & thread_id;
|
||||||
ar & registered;
|
ar & registered;
|
||||||
|
ar & relay_queue_flags;
|
||||||
|
ar & on_vblank_event;
|
||||||
}
|
}
|
||||||
SERIALIZE_IMPL(SessionData)
|
SERIALIZE_IMPL(SessionData)
|
||||||
|
|
||||||
@ -1067,4 +1148,64 @@ SessionData::~SessionData() {
|
|||||||
gsp->used_thread_ids[thread_id] = false;
|
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
|
} // namespace Service::GSP
|
||||||
|
|
||||||
|
SERIALIZE_EXPORT_IMPL(Service::GSP::GSP_GPU::ThreadCallback)
|
||||||
|
|||||||
@ -18,6 +18,7 @@
|
|||||||
#include "core/hle/service/gsp/gsp_command.h"
|
#include "core/hle/service/gsp/gsp_command.h"
|
||||||
#include "core/hle/service/gsp/gsp_interrupt.h"
|
#include "core/hle/service/gsp/gsp_interrupt.h"
|
||||||
#include "core/hle/service/service.h"
|
#include "core/hle/service/service.h"
|
||||||
|
#include "core/memory.h"
|
||||||
|
|
||||||
namespace Core {
|
namespace Core {
|
||||||
class System;
|
class System;
|
||||||
@ -45,6 +46,17 @@ struct FrameBufferInfo {
|
|||||||
u32 GetPixelFormat() {
|
u32 GetPixelFormat() {
|
||||||
return format & PIXEL_FORMAT_MASK;
|
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");
|
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,
|
static_assert(offsetof(FrameBufferUpdate, framebuffer_info[1]) == 0x20,
|
||||||
"FrameBufferInfo element has incorrect alignment");
|
"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 = 240;
|
||||||
constexpr u32 FRAMEBUFFER_WIDTH_POW2 = 256;
|
constexpr u32 FRAMEBUFFER_WIDTH_POW2 = 256;
|
||||||
constexpr u32 TOP_FRAMEBUFFER_HEIGHT = 400;
|
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_TOP_RIGHT = Memory::VRAM_VADDR + 0x2B9800;
|
||||||
constexpr VAddr FRAMEBUFFER_SAVE_AREA_BOTTOM = Memory::VRAM_VADDR + 0x4C7800;
|
constexpr VAddr FRAMEBUFFER_SAVE_AREA_BOTTOM = Memory::VRAM_VADDR + 0x4C7800;
|
||||||
|
|
||||||
|
constexpr size_t VRAM_BACKUP_BANK_SIZE = 0x80000;
|
||||||
|
|
||||||
class GSP_GPU;
|
class GSP_GPU;
|
||||||
|
|
||||||
class SessionData : public Kernel::SessionRequestHandler::SessionDataBase {
|
class SessionData : public Kernel::SessionRequestHandler::SessionDataBase {
|
||||||
@ -88,6 +116,10 @@ public:
|
|||||||
u32 thread_id;
|
u32 thread_id;
|
||||||
/// Whether RegisterInterruptRelayQueue was called for this session
|
/// Whether RegisterInterruptRelayQueue was called for this session
|
||||||
bool registered = false;
|
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:
|
private:
|
||||||
template <class Archive>
|
template <class Archive>
|
||||||
@ -95,6 +127,68 @@ private:
|
|||||||
friend class boost::serialization::access;
|
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> {
|
class GSP_GPU final : public ServiceFramework<GSP_GPU, SessionData> {
|
||||||
public:
|
public:
|
||||||
explicit GSP_GPU(Core::System& system);
|
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
|
* @param thread_id GSP thread id of the process that accesses the structure that we are
|
||||||
* requesting.
|
* requesting.
|
||||||
* @param screen_index Index of the screen we are requesting (Top = 0, Bottom = 1).
|
* @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
|
/// Gets a pointer to a thread command buffer in GSP shared memory
|
||||||
CommandBuffer* GetCommandBuffer(u32 thread_id);
|
CommandBuffer* GetCommandBuffer(u32 thread_id);
|
||||||
@ -128,7 +223,7 @@ public:
|
|||||||
* Retreives the ID of the thread with GPU rights.
|
* Retreives the ID of the thread with GPU rights.
|
||||||
*/
|
*/
|
||||||
u32 GetActiveThreadId() {
|
u32 GetActiveThreadId() {
|
||||||
return active_thread_id;
|
return thread_id_with_rights;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -138,6 +233,8 @@ public:
|
|||||||
return active_client_thread_id;
|
return active_client_thread_id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class ThreadCallback;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
/**
|
/**
|
||||||
* Signals that the specified interrupt type has occurred to userland code for the specified GSP
|
* 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;
|
std::shared_ptr<Kernel::SharedMemory> shared_memory;
|
||||||
|
|
||||||
/// Thread id that currently has GPU rights or std::numeric_limits<u32>::max() if none.
|
/// 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
|
/// Thread id of the client thread that has GPU rights
|
||||||
u32 active_client_thread_id = std::numeric_limits<u32>::max();
|
u32 active_client_thread_id = std::numeric_limits<u32>::max();
|
||||||
|
|
||||||
bool first_initialization = true;
|
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.
|
/// Maximum number of threads that can be registered at the same time in the GSP module.
|
||||||
static constexpr u32 MaxGSPThreads = 4;
|
static constexpr u32 MaxGSPThreads = 4;
|
||||||
|
|
||||||
@ -527,6 +621,11 @@ private:
|
|||||||
|
|
||||||
Core::TimingEventType* SignalInterruptEventType = nullptr;
|
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;
|
friend class SessionData;
|
||||||
|
|
||||||
template <class Archive>
|
template <class Archive>
|
||||||
@ -539,3 +638,4 @@ private:
|
|||||||
BOOST_CLASS_EXPORT_KEY(Service::GSP::SessionData)
|
BOOST_CLASS_EXPORT_KEY(Service::GSP::SessionData)
|
||||||
BOOST_CLASS_EXPORT_KEY(Service::GSP::GSP_GPU)
|
BOOST_CLASS_EXPORT_KEY(Service::GSP::GSP_GPU)
|
||||||
SERVICE_CONSTRUCT(Service::GSP::GSP_GPU)
|
SERVICE_CONSTRUCT(Service::GSP::GSP_GPU)
|
||||||
|
BOOST_CLASS_EXPORT_KEY(Service::GSP::GSP_GPU::ThreadCallback)
|
||||||
|
|||||||
@ -11,6 +11,7 @@
|
|||||||
#include <fmt/format.h>
|
#include <fmt/format.h>
|
||||||
#include "common/archives.h"
|
#include "common/archives.h"
|
||||||
#include "common/assert.h"
|
#include "common/assert.h"
|
||||||
|
#include "common/file_util.h"
|
||||||
#include "common/scope_exit.h"
|
#include "common/scope_exit.h"
|
||||||
#include "common/string_util.h"
|
#include "common/string_util.h"
|
||||||
#include "core/core.h"
|
#include "core/core.h"
|
||||||
@ -281,7 +282,7 @@ std::string Context::ParseMultipartFormData() {
|
|||||||
void Context::MakeRequest() {
|
void Context::MakeRequest() {
|
||||||
ASSERT(state == RequestState::NotStarted);
|
ASSERT(state == RequestState::NotStarted);
|
||||||
|
|
||||||
state = RequestState::ConnectingToServer;
|
state = RequestState::SendingRequest;
|
||||||
|
|
||||||
static const std::unordered_map<RequestMethod, std::string> request_method_strings{
|
static const std::unordered_map<RequestMethod, std::string> request_method_strings{
|
||||||
{RequestMethod::Get, "GET"}, {RequestMethod::Post, "POST"},
|
{RequestMethod::Get, "GET"}, {RequestMethod::Post, "POST"},
|
||||||
@ -297,6 +298,9 @@ void Context::MakeRequest() {
|
|||||||
request.method = request_method_strings.at(method);
|
request.method = request_method_strings.at(method);
|
||||||
request.path = url_info.path;
|
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 {
|
request.progress = [this](u64 current, u64 total) -> bool {
|
||||||
// TODO(B3N30): Is there a state that shows response header are available
|
// TODO(B3N30): Is there a state that shows response header are available
|
||||||
current_download_size_bytes = current;
|
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) {
|
bool Context::ContentProvider(size_t offset, size_t length, httplib::DataSink& sink) {
|
||||||
state = RequestState::SendingRequest;
|
|
||||||
|
|
||||||
if (!post_data_raw.empty()) {
|
if (!post_data_raw.empty()) {
|
||||||
sink.write(post_data_raw.data() + offset, length);
|
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) {
|
bool Context::ChunkedContentProvider(size_t offset, httplib::DataSink& sink) {
|
||||||
state = RequestState::SendingRequest;
|
|
||||||
|
|
||||||
finish_post_data.Wait();
|
finish_post_data.Wait();
|
||||||
|
|
||||||
switch (post_data_type) {
|
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].socket_buffer_size = 0;
|
||||||
contexts[context_counter].handle = context_counter;
|
contexts[context_counter].handle = context_counter;
|
||||||
contexts[context_counter].session_id = session_data->session_id;
|
contexts[context_counter].session_id = session_data->session_id;
|
||||||
|
contexts[context_counter].url_replacer = &url_replacer;
|
||||||
|
|
||||||
session_data->num_http_contexts++;
|
session_data->num_http_contexts++;
|
||||||
|
|
||||||
@ -858,13 +859,25 @@ void HTTP_C::GetRequestState(Kernel::HLERequestContext& ctx) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
LOG_DEBUG(Service_HTTP, "called, context_handle={}", context_handle);
|
|
||||||
|
|
||||||
Context& http_context = GetContext(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);
|
IPC::RequestBuilder rb = rp.MakeBuilder(2, 0);
|
||||||
rb.Push(ResultSuccess);
|
rb.Push(ResultSuccess);
|
||||||
rb.PushEnum<RequestState>(http_context.state);
|
rb.PushEnum<RequestState>(state);
|
||||||
}
|
}
|
||||||
|
|
||||||
void HTTP_C::AddRequestHeader(Kernel::HLERequestContext& ctx) {
|
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.finish_post_data.Set();
|
||||||
http_context.post_pending_request = false;
|
|
||||||
|
|
||||||
http_context.current_copied_data = 0;
|
http_context.current_copied_data = 0;
|
||||||
http_context.request_future =
|
http_context.request_future =
|
||||||
@ -2003,6 +2015,61 @@ void HTTP_C::Finalize(Kernel::HLERequestContext& ctx) {
|
|||||||
LOG_WARNING(Service_HTTP, "(STUBBED) called");
|
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) {
|
void HTTP_C::GetDownloadSizeState(Kernel::HLERequestContext& ctx) {
|
||||||
IPC::RequestParser rp(ctx);
|
IPC::RequestParser rp(ctx);
|
||||||
const Context::Handle context_handle = rp.Pop<u32>();
|
const Context::Handle context_handle = rp.Pop<u32>();
|
||||||
@ -2171,6 +2238,96 @@ void HTTP_C::DecryptClCertA() {
|
|||||||
ClCertA.init = true;
|
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) {
|
HTTP_C::HTTP_C() : ServiceFramework("http:C", 32) {
|
||||||
static const FunctionInfo functions[] = {
|
static const FunctionInfo functions[] = {
|
||||||
// clang-format off
|
// clang-format off
|
||||||
@ -2231,6 +2388,9 @@ HTTP_C::HTTP_C() : ServiceFramework("http:C", 32) {
|
|||||||
{0x0037, &HTTP_C::SetKeepAlive, "SetKeepAlive"},
|
{0x0037, &HTTP_C::SetKeepAlive, "SetKeepAlive"},
|
||||||
{0x0038, &HTTP_C::SetPostDataTypeSize, "SetPostDataTypeSize"},
|
{0x0038, &HTTP_C::SetPostDataTypeSize, "SetPostDataTypeSize"},
|
||||||
{0x0039, &HTTP_C::Finalize, "Finalize"},
|
{0x0039, &HTTP_C::Finalize, "Finalize"},
|
||||||
|
// Custom
|
||||||
|
{0x0C00, &HTTP_C::RegisterURLReplacement, "RegisterURLReplacement"},
|
||||||
|
{0x0C01, &HTTP_C::UnregisterURLReplacement, "UnregisterURLReplacement"},
|
||||||
// clang-format on
|
// clang-format on
|
||||||
};
|
};
|
||||||
RegisterHandlers(functions);
|
RegisterHandlers(functions);
|
||||||
|
|||||||
@ -11,6 +11,7 @@
|
|||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <boost/optional.hpp>
|
#include <boost/optional.hpp>
|
||||||
|
#include <boost/regex.hpp>
|
||||||
#include <boost/serialization/optional.hpp>
|
#include <boost/serialization/optional.hpp>
|
||||||
#include <boost/serialization/shared_ptr.hpp>
|
#include <boost/serialization/shared_ptr.hpp>
|
||||||
#include <boost/serialization/string.hpp>
|
#include <boost/serialization/string.hpp>
|
||||||
@ -55,6 +56,8 @@ enum class RequestState : u8 {
|
|||||||
ConnectingToServer = 0x5,
|
ConnectingToServer = 0x5,
|
||||||
|
|
||||||
/// Request in progress, sending HTTP request.
|
/// Request in progress, sending HTTP request.
|
||||||
|
/// HTTPC stays in this state when there is POST
|
||||||
|
/// data pending.
|
||||||
SendingRequest = 0x6,
|
SendingRequest = 0x6,
|
||||||
|
|
||||||
// Request in progress, receiving HTTP response and headers.
|
// Request in progress, receiving HTTP response and headers.
|
||||||
@ -160,6 +163,28 @@ struct ClCertAData {
|
|||||||
bool init = false;
|
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.
|
/// Represents an HTTP context.
|
||||||
class Context final {
|
class Context final {
|
||||||
public:
|
public:
|
||||||
@ -274,6 +299,7 @@ public:
|
|||||||
u32 socket_buffer_size;
|
u32 socket_buffer_size;
|
||||||
std::vector<RequestHeader> headers;
|
std::vector<RequestHeader> headers;
|
||||||
const ClCertAData* clcert_data;
|
const ClCertAData* clcert_data;
|
||||||
|
const URLReplacer* url_replacer;
|
||||||
bool post_data_added = false;
|
bool post_data_added = false;
|
||||||
bool post_pending_request = false;
|
bool post_pending_request = false;
|
||||||
Params post_data;
|
Params post_data;
|
||||||
@ -864,6 +890,10 @@ private:
|
|||||||
*/
|
*/
|
||||||
void Finalize(Kernel::HLERequestContext& ctx);
|
void Finalize(Kernel::HLERequestContext& ctx);
|
||||||
|
|
||||||
|
void RegisterURLReplacement(Kernel::HLERequestContext& ctx);
|
||||||
|
|
||||||
|
void UnregisterURLReplacement(Kernel::HLERequestContext& ctx);
|
||||||
|
|
||||||
[[nodiscard]] SessionData* EnsureSessionInitialized(Kernel::HLERequestContext& ctx,
|
[[nodiscard]] SessionData* EnsureSessionInitialized(Kernel::HLERequestContext& ctx,
|
||||||
IPC::RequestParser rp);
|
IPC::RequestParser rp);
|
||||||
|
|
||||||
@ -898,6 +928,8 @@ private:
|
|||||||
|
|
||||||
ClCertAData ClCertA;
|
ClCertAData ClCertA;
|
||||||
|
|
||||||
|
URLReplacer url_replacer;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
template <class Archive>
|
template <class Archive>
|
||||||
void serialize(Archive& ar, const unsigned int) {
|
void serialize(Archive& ar, const unsigned int) {
|
||||||
|
|||||||
@ -29,6 +29,8 @@ constexpr std::size_t IgnoreFrames = 5;
|
|||||||
|
|
||||||
namespace Core {
|
namespace Core {
|
||||||
|
|
||||||
|
bool PerfStats::game_frames_updated = true;
|
||||||
|
|
||||||
PerfStats::PerfStats(u64 title_id) : title_id(title_id) {}
|
PerfStats::PerfStats(u64 title_id) : title_id(title_id) {}
|
||||||
|
|
||||||
PerfStats::~PerfStats() {
|
PerfStats::~PerfStats() {
|
||||||
@ -109,6 +111,7 @@ void PerfStats::EndGameFrame() {
|
|||||||
std::scoped_lock lock{object_mutex};
|
std::scoped_lock lock{object_mutex};
|
||||||
|
|
||||||
game_frames += 1;
|
game_frames += 1;
|
||||||
|
PerfStats::game_frames_updated = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
double PerfStats::GetMeanFrametime() const {
|
double PerfStats::GetMeanFrametime() const {
|
||||||
|
|||||||
@ -120,6 +120,8 @@ public:
|
|||||||
artic_events.Set(event, set);
|
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:
|
private:
|
||||||
mutable std::mutex object_mutex;
|
mutable std::mutex object_mutex;
|
||||||
|
|||||||
@ -371,10 +371,6 @@ const Pica::PicaCore& GPU::PicaCore() const {
|
|||||||
return impl->pica;
|
return impl->pica;
|
||||||
}
|
}
|
||||||
|
|
||||||
Pica::DebugContext& GPU::DebugContext() {
|
|
||||||
return *Pica::g_debug_context;
|
|
||||||
}
|
|
||||||
|
|
||||||
GraphicsDebugger& GPU::Debugger() {
|
GraphicsDebugger& GPU::Debugger() {
|
||||||
return impl->gpu_debugger;
|
return impl->gpu_debugger;
|
||||||
}
|
}
|
||||||
@ -490,13 +486,13 @@ void GPU::MemoryTransfer() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void GPU::VBlankCallback(std::uintptr_t user_data, s64 cycles_late) {
|
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
|
// Signal to GSP that GPU interrupt has occurred
|
||||||
impl->signal_interrupt(Service::GSP::InterruptId::PDC0, 0);
|
impl->signal_interrupt(Service::GSP::InterruptId::PDC0, 0);
|
||||||
impl->signal_interrupt(Service::GSP::InterruptId::PDC1, 0);
|
impl->signal_interrupt(Service::GSP::InterruptId::PDC1, 0);
|
||||||
|
|
||||||
|
// Present renderered frame.
|
||||||
|
impl->renderer->SwapBuffers();
|
||||||
|
|
||||||
// Reschedule recurrent event
|
// Reschedule recurrent event
|
||||||
impl->timing.ScheduleEvent(FRAME_TICKS - cycles_late, impl->vblank_event);
|
impl->timing.ScheduleEvent(FRAME_TICKS - cycles_late, impl->vblank_event);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -84,9 +84,6 @@ public:
|
|||||||
/// Returns an immutable reference to the PICA GPU.
|
/// Returns an immutable reference to the PICA GPU.
|
||||||
[[nodiscard]] const Pica::PicaCore& PicaCore() const;
|
[[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.
|
/// Returns a mutable reference to the GSP command debugger.
|
||||||
[[nodiscard]] GraphicsDebugger& Debugger();
|
[[nodiscard]] GraphicsDebugger& Debugger();
|
||||||
|
|
||||||
|
|||||||
@ -71,12 +71,28 @@ RasterizerCache<T>::RasterizerCache(Memory::MemorySystem& memory_,
|
|||||||
auto& null_surface = slot_surfaces[NULL_SURFACE_ID];
|
auto& null_surface = slot_surfaces[NULL_SURFACE_ID];
|
||||||
runtime.ClearTexture(null_surface, {
|
runtime.ClearTexture(null_surface, {
|
||||||
.texture_level = 0,
|
.texture_level = 0,
|
||||||
|
.texture_layer = 0,
|
||||||
.texture_rect = null_surface.GetScaledRect(),
|
.texture_rect = null_surface.GetScaledRect(),
|
||||||
.value =
|
.value =
|
||||||
{
|
{
|
||||||
.color = {0.f, 0.f, 0.f, 0.f},
|
.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>
|
template <class T>
|
||||||
@ -1151,14 +1167,14 @@ void RasterizerCache<T>::DownloadFillSurface(Surface& surface, SurfaceInterval i
|
|||||||
const u32 flush_end = boost::icl::last_next(interval);
|
const u32 flush_end = boost::icl::last_next(interval);
|
||||||
ASSERT(flush_start >= surface.addr && flush_end <= surface.end);
|
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]] {
|
if (!dest_ptr) [[unlikely]] {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const u32 start_offset = flush_start - surface.addr;
|
const u32 start_offset = flush_start - surface.addr;
|
||||||
const u32 download_size =
|
const u32 end_offset =
|
||||||
std::clamp(flush_end - flush_start, 0u, static_cast<u32>(dest_ptr.GetSize()));
|
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 coarse_start_offset = start_offset - (start_offset % surface.fill_size);
|
||||||
const u32 backup_bytes = 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);
|
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::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) {
|
if (backup_bytes) {
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
// Copyright 2023 Citra Emulator Project
|
// Copyright Citra Emulator Project / Azahar Emulator Project
|
||||||
// Licensed under GPLv2 or any later version
|
// Licensed under GPLv2 or any later version
|
||||||
// Refer to the license.txt file included.
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
@ -30,6 +30,7 @@ union ClearValue {
|
|||||||
|
|
||||||
struct TextureClear {
|
struct TextureClear {
|
||||||
u32 texture_level;
|
u32 texture_level;
|
||||||
|
u32 texture_layer;
|
||||||
Common::Rectangle<u32> texture_rect;
|
Common::Rectangle<u32> texture_rect;
|
||||||
ClearValue value;
|
ClearValue value;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -88,6 +88,7 @@ bool BlitHelper::ConvertDS24S8ToRGBA8(Surface& source, Surface& dest,
|
|||||||
SCOPE_EXIT({ prev_state.Apply(); });
|
SCOPE_EXIT({ prev_state.Apply(); });
|
||||||
|
|
||||||
state.texture_units[0].texture_2d = source.Handle();
|
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[0].sampler = 0;
|
||||||
state.texture_units[1].sampler = 0;
|
state.texture_units[1].sampler = 0;
|
||||||
|
|
||||||
@ -103,6 +104,7 @@ bool BlitHelper::ConvertDS24S8ToRGBA8(Surface& source, Surface& dest,
|
|||||||
temp_tex.Release();
|
temp_tex.Release();
|
||||||
temp_tex.Create();
|
temp_tex.Create();
|
||||||
state.texture_units[1].texture_2d = temp_tex.handle;
|
state.texture_units[1].texture_2d = temp_tex.handle;
|
||||||
|
state.texture_units[1].target = GL_TEXTURE_2D;
|
||||||
state.Apply();
|
state.Apply();
|
||||||
glActiveTexture(GL_TEXTURE1);
|
glActiveTexture(GL_TEXTURE1);
|
||||||
glTexStorage2D(GL_TEXTURE_2D, 1, GL_DEPTH24_STENCIL8, temp_extent.width,
|
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);
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
||||||
}
|
}
|
||||||
state.texture_units[1].texture_2d = temp_tex.handle;
|
state.texture_units[1].texture_2d = temp_tex.handle;
|
||||||
|
state.texture_units[1].target = GL_TEXTURE_2D;
|
||||||
state.Apply();
|
state.Apply();
|
||||||
|
|
||||||
glActiveTexture(GL_TEXTURE1);
|
glActiveTexture(GL_TEXTURE1);
|
||||||
@ -145,6 +148,7 @@ bool BlitHelper::ConvertRGBA4ToRGB5A1(Surface& source, Surface& dest,
|
|||||||
SCOPE_EXIT({ prev_state.Apply(); });
|
SCOPE_EXIT({ prev_state.Apply(); });
|
||||||
|
|
||||||
state.texture_units[0].texture_2d = source.Handle();
|
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,
|
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};
|
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.fbo.Create();
|
||||||
texture.tex.Create();
|
texture.tex.Create();
|
||||||
state.texture_units[1].texture_2d = texture.tex.handle;
|
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.draw.draw_framebuffer = texture.fbo.handle;
|
||||||
state.Apply();
|
state.Apply();
|
||||||
glActiveTexture(GL_TEXTURE1);
|
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[1].texture_2d = LUMAD.tex.handle;
|
||||||
state.texture_units[2].texture_2d = XY.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
|
// gradient x pass
|
||||||
Draw(gradient_x_program, XY.tex.handle, XY.fbo.handle, 0, temp_rect);
|
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();
|
const OpenGLState prev_state = OpenGLState::GetCurState();
|
||||||
SCOPE_EXIT({ prev_state.Apply(); });
|
SCOPE_EXIT({ prev_state.Apply(); });
|
||||||
state.texture_units[0].texture_2d = surface.Handle(0);
|
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);
|
SetParams(bicubic_program, surface.RealExtent(false), blit.src_rect);
|
||||||
Draw(bicubic_program, surface.Handle(), draw_fbo.handle, blit.dst_level, blit.dst_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();
|
const OpenGLState prev_state = OpenGLState::GetCurState();
|
||||||
SCOPE_EXIT({ prev_state.Apply(); });
|
SCOPE_EXIT({ prev_state.Apply(); });
|
||||||
state.texture_units[0].texture_2d = surface.Handle(0);
|
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);
|
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);
|
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();
|
const OpenGLState prev_state = OpenGLState::GetCurState();
|
||||||
SCOPE_EXIT({ prev_state.Apply(); });
|
SCOPE_EXIT({ prev_state.Apply(); });
|
||||||
state.texture_units[0].texture_2d = surface.Handle(0);
|
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));
|
glProgramUniform1f(xbrz_program.handle, 2, static_cast<GLfloat>(surface.res_scale));
|
||||||
SetParams(xbrz_program, surface.RealExtent(false), blit.src_rect);
|
SetParams(xbrz_program, surface.RealExtent(false), blit.src_rect);
|
||||||
Draw(xbrz_program, surface.Handle(), draw_fbo.handle, blit.dst_level, blit.dst_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();
|
const OpenGLState prev_state = OpenGLState::GetCurState();
|
||||||
SCOPE_EXIT({ prev_state.Apply(); });
|
SCOPE_EXIT({ prev_state.Apply(); });
|
||||||
state.texture_units[0].texture_2d = surface.Handle(0);
|
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);
|
SetParams(mmpx_program, surface.RealExtent(false), blit.src_rect);
|
||||||
Draw(mmpx_program, surface.Handle(), draw_fbo.handle, blit.dst_level, blit.dst_rect);
|
Draw(mmpx_program, surface.Handle(), draw_fbo.handle, blit.dst_level, blit.dst_rect);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -717,6 +717,7 @@ void RasterizerOpenGL::SyncTextureUnits(const Framebuffer* framebuffer) {
|
|||||||
if (!IsFeedbackLoop(texture_index, framebuffer, surface)) {
|
if (!IsFeedbackLoop(texture_index, framebuffer, surface)) {
|
||||||
BindMaterial(texture_index, surface);
|
BindMaterial(texture_index, surface);
|
||||||
state.texture_units[texture_index].texture_2d = surface.Handle();
|
state.texture_units[texture_index].texture_2d = surface.Handle();
|
||||||
|
state.texture_units[texture_index].target = GL_TEXTURE_2D;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -222,8 +222,9 @@ bool TextureRuntime::ClearTextureWithoutFbo(Surface& surface,
|
|||||||
UNREACHABLE_MSG("Unknown surface type {}", surface.type);
|
UNREACHABLE_MSG("Unknown surface type {}", surface.type);
|
||||||
}
|
}
|
||||||
glClearTexSubImage(surface.Handle(), clear.texture_level, clear.texture_rect.left,
|
glClearTexSubImage(surface.Handle(), clear.texture_level, clear.texture_rect.left,
|
||||||
clear.texture_rect.bottom, 0, clear.texture_rect.GetWidth(),
|
clear.texture_rect.bottom, clear.texture_layer,
|
||||||
clear.texture_rect.GetHeight(), 1, format, type, &clear.value);
|
clear.texture_rect.GetWidth(), clear.texture_rect.GetHeight(), 1, format,
|
||||||
|
type, &clear.value);
|
||||||
return true;
|
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.draw.draw_framebuffer = draw_fbos[FboIndex(surface.type)].handle;
|
||||||
state.Apply();
|
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) {
|
switch (surface.type) {
|
||||||
case SurfaceType::Color:
|
case SurfaceType::Color:
|
||||||
@ -326,6 +327,7 @@ void TextureRuntime::GenerateMipmaps(Surface& surface) {
|
|||||||
|
|
||||||
const auto generate = [&](u32 index) {
|
const auto generate = [&](u32 index) {
|
||||||
state.texture_units[0].texture_2d = surface.Handle(index);
|
state.texture_units[0].texture_2d = surface.Handle(index);
|
||||||
|
state.texture_units[0].target = GL_TEXTURE_2D;
|
||||||
state.Apply();
|
state.Apply();
|
||||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, surface.levels - 1);
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, surface.levels - 1);
|
||||||
glGenerateMipmap(GL_TEXTURE_2D);
|
glGenerateMipmap(GL_TEXTURE_2D);
|
||||||
@ -554,6 +556,7 @@ bool Surface::DownloadWithoutFbo(const VideoCore::BufferTextureCopy& download,
|
|||||||
// that only support up to 4.3
|
// that only support up to 4.3
|
||||||
OpenGLState state = OpenGLState::GetCurState();
|
OpenGLState state = OpenGLState::GetCurState();
|
||||||
state.texture_units[0].texture_2d = Handle(0);
|
state.texture_units[0].texture_2d = Handle(0);
|
||||||
|
state.texture_units[0].target = GL_TEXTURE_2D;
|
||||||
state.Apply();
|
state.Apply();
|
||||||
|
|
||||||
glGetTexImage(GL_TEXTURE_2D, download.texture_level, tuple.format, tuple.type,
|
glGetTexImage(GL_TEXTURE_2D, download.texture_level, tuple.format, tuple.type,
|
||||||
|
|||||||
@ -93,11 +93,26 @@ void RendererOpenGL::SwapBuffers() {
|
|||||||
// Maintain the rasterizer's state as a priority
|
// Maintain the rasterizer's state as a priority
|
||||||
OpenGLState prev_state = OpenGLState::GetCurState();
|
OpenGLState prev_state = OpenGLState::GetCurState();
|
||||||
state.Apply();
|
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();
|
render_window.SetupFramebuffer();
|
||||||
|
|
||||||
PrepareRendertarget();
|
PrepareRendertarget();
|
||||||
RenderScreenshot();
|
RenderScreenshot();
|
||||||
|
isSecondaryWindow = false;
|
||||||
#ifdef HAVE_LIBRETRO
|
#ifdef HAVE_LIBRETRO
|
||||||
DrawScreens(render_window.GetFramebufferLayout(), false);
|
DrawScreens(render_window.GetFramebufferLayout(), false);
|
||||||
render_window.SwapBuffers();
|
render_window.SwapBuffers();
|
||||||
@ -110,6 +125,7 @@ void RendererOpenGL::SwapBuffers() {
|
|||||||
// it means we have a second display
|
// it means we have a second display
|
||||||
if (secondary_window) {
|
if (secondary_window) {
|
||||||
const auto& secondary_layout = secondary_window->GetFramebufferLayout();
|
const auto& secondary_layout = secondary_window->GetFramebufferLayout();
|
||||||
|
isSecondaryWindow = true;
|
||||||
RenderToMailbox(secondary_layout, secondary_window->mailbox, false);
|
RenderToMailbox(secondary_layout, secondary_window->mailbox, false);
|
||||||
secondary_window->PollEvents();
|
secondary_window->PollEvents();
|
||||||
}
|
}
|
||||||
@ -117,6 +133,7 @@ void RendererOpenGL::SwapBuffers() {
|
|||||||
if (Settings::values.layout_option.GetValue() == Settings::LayoutOption::SeparateWindows) {
|
if (Settings::values.layout_option.GetValue() == Settings::LayoutOption::SeparateWindows) {
|
||||||
ASSERT(secondary_window);
|
ASSERT(secondary_window);
|
||||||
const auto& secondary_layout = secondary_window->GetFramebufferLayout();
|
const auto& secondary_layout = secondary_window->GetFramebufferLayout();
|
||||||
|
isSecondaryWindow = true;
|
||||||
RenderToMailbox(secondary_layout, secondary_window->mailbox, false);
|
RenderToMailbox(secondary_layout, secondary_window->mailbox, false);
|
||||||
secondary_window->PollEvents();
|
secondary_window->PollEvents();
|
||||||
}
|
}
|
||||||
@ -196,7 +213,8 @@ void RendererOpenGL::PrepareRendertarget() {
|
|||||||
void RendererOpenGL::RenderToMailbox(const Layout::FramebufferLayout& layout,
|
void RendererOpenGL::RenderToMailbox(const Layout::FramebufferLayout& layout,
|
||||||
std::unique_ptr<Frontend::TextureMailbox>& mailbox,
|
std::unique_ptr<Frontend::TextureMailbox>& mailbox,
|
||||||
bool flipped) {
|
bool flipped) {
|
||||||
|
if (!Settings::values.use_skip_duplicate_frames.GetValue() ||
|
||||||
|
Core::PerfStats::game_frames_updated) {
|
||||||
Frontend::Frame* frame;
|
Frontend::Frame* frame;
|
||||||
{
|
{
|
||||||
MICROPROFILE_SCOPE(OpenGL_WaitPresent);
|
MICROPROFILE_SCOPE(OpenGL_WaitPresent);
|
||||||
@ -241,6 +259,11 @@ void RendererOpenGL::RenderToMailbox(const Layout::FramebufferLayout& layout,
|
|||||||
glFlush();
|
glFlush();
|
||||||
mailbox->ReleaseRenderFrame(frame);
|
mailbox->ReleaseRenderFrame(frame);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ((secondaryWindowEnabled && isSecondaryWindow) || (!secondaryWindowEnabled)) {
|
||||||
|
Core::PerfStats::game_frames_updated = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -112,6 +112,8 @@ private:
|
|||||||
GLuint attrib_tex_coord;
|
GLuint attrib_tex_coord;
|
||||||
|
|
||||||
FrameDumperOpenGL frame_dumper;
|
FrameDumperOpenGL frame_dumper;
|
||||||
|
bool isSecondaryWindow;
|
||||||
|
bool secondaryWindowEnabled;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace OpenGL
|
} // namespace OpenGL
|
||||||
|
|||||||
@ -24,7 +24,6 @@
|
|||||||
#include "video_core/host_shaders/vulkan_cursor_vert.h"
|
#include "video_core/host_shaders/vulkan_cursor_vert.h"
|
||||||
|
|
||||||
#include <vk_mem_alloc.h>
|
#include <vk_mem_alloc.h>
|
||||||
|
|
||||||
#if defined(__APPLE__) && !defined(HAVE_LIBRETRO)
|
#if defined(__APPLE__) && !defined(HAVE_LIBRETRO)
|
||||||
#include "common/apple_utils.h"
|
#include "common/apple_utils.h"
|
||||||
#endif
|
#endif
|
||||||
@ -237,6 +236,8 @@ void RendererVulkan::PrepareDraw(Frame* frame, const Layout::FramebufferLayout&
|
|||||||
|
|
||||||
void RendererVulkan::RenderToWindow(PresentWindow& window, const Layout::FramebufferLayout& layout,
|
void RendererVulkan::RenderToWindow(PresentWindow& window, const Layout::FramebufferLayout& layout,
|
||||||
bool flipped) {
|
bool flipped) {
|
||||||
|
if (!Settings::values.use_skip_duplicate_frames.GetValue() ||
|
||||||
|
Core::PerfStats::game_frames_updated) {
|
||||||
Frame* frame = window.GetRenderFrame();
|
Frame* frame = window.GetRenderFrame();
|
||||||
|
|
||||||
if (layout.width != frame->width || layout.height != frame->height) {
|
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);
|
DrawScreens(frame, layout, flipped);
|
||||||
scheduler.Flush(frame->render_ready);
|
scheduler.Flush(frame->render_ready);
|
||||||
|
|
||||||
window.Present(frame);
|
window.Present(frame);
|
||||||
|
if ((secondaryWindowEnabled && isSecondaryWindow) || (!secondaryWindowEnabled)) {
|
||||||
|
Core::PerfStats::game_frames_updated = false;
|
||||||
|
screenRendered = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void RendererVulkan::LoadFBToScreenInfo(const Pica::FramebufferConfig& framebuffer,
|
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) {
|
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 = {
|
const vk::ClearColorValue clear_color = {
|
||||||
.float32 =
|
.float32 =
|
||||||
std::array{
|
std::array{
|
||||||
@ -1111,9 +1121,28 @@ void RendererVulkan::DrawCursor(const Layout::FramebufferLayout& layout) {
|
|||||||
|
|
||||||
void RendererVulkan::SwapBuffers() {
|
void RendererVulkan::SwapBuffers() {
|
||||||
system.perf_stats->StartSwap();
|
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();
|
const Layout::FramebufferLayout& layout = render_window.GetFramebufferLayout();
|
||||||
PrepareRendertarget();
|
PrepareRendertarget();
|
||||||
RenderScreenshot();
|
RenderScreenshot();
|
||||||
|
isSecondaryWindow = false;
|
||||||
RenderToWindow(main_present_window, layout, false);
|
RenderToWindow(main_present_window, layout, false);
|
||||||
#ifndef ANDROID
|
#ifndef ANDROID
|
||||||
if (Settings::values.layout_option.GetValue() == Settings::LayoutOption::SeparateWindows) {
|
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_present_window_ptr = std::make_unique<PresentWindow>(
|
||||||
*secondary_window, instance, scheduler, IsLowRefreshRate());
|
*secondary_window, instance, scheduler, IsLowRefreshRate());
|
||||||
}
|
}
|
||||||
|
isSecondaryWindow = true;
|
||||||
RenderToWindow(*secondary_present_window_ptr, secondary_layout, false);
|
RenderToWindow(*secondary_present_window_ptr, secondary_layout, false);
|
||||||
secondary_window->PollEvents();
|
secondary_window->PollEvents();
|
||||||
}
|
}
|
||||||
@ -1135,10 +1165,14 @@ void RendererVulkan::SwapBuffers() {
|
|||||||
secondary_present_window_ptr = std::make_unique<PresentWindow>(
|
secondary_present_window_ptr = std::make_unique<PresentWindow>(
|
||||||
*secondary_window, instance, scheduler, IsLowRefreshRate());
|
*secondary_window, instance, scheduler, IsLowRefreshRate());
|
||||||
}
|
}
|
||||||
|
isSecondaryWindow = true;
|
||||||
RenderToWindow(*secondary_present_window_ptr, secondary_layout, false);
|
RenderToWindow(*secondary_present_window_ptr, secondary_layout, false);
|
||||||
secondary_window->PollEvents();
|
secondary_window->PollEvents();
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
if (!screenRendered) {
|
||||||
|
scheduler.Finish();
|
||||||
|
}
|
||||||
|
|
||||||
system.perf_stats->EndSwap();
|
system.perf_stats->EndSwap();
|
||||||
rasterizer.TickFrame();
|
rasterizer.TickFrame();
|
||||||
@ -1268,6 +1302,15 @@ void RendererVulkan::RenderScreenshotWithStagingCopy() {
|
|||||||
// Copy backing image data to the QImage screenshot buffer
|
// Copy backing image data to the QImage screenshot buffer
|
||||||
std::memcpy(settings.screenshot_bits, alloc_info.pMappedData, staging_buffer_info.size);
|
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
|
// Destroy allocated resources
|
||||||
vmaDestroyBuffer(instance.GetAllocator(), staging_buffer, allocation);
|
vmaDestroyBuffer(instance.GetAllocator(), staging_buffer, allocation);
|
||||||
vmaDestroyImage(instance.GetAllocator(), frame.image, frame.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
|
// Ensure the copy is fully completed before saving the screenshot
|
||||||
scheduler.Finish();
|
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
|
// Image data has been copied directly to host memory
|
||||||
device.destroyFramebuffer(frame.framebuffer);
|
device.destroyFramebuffer(frame.framebuffer);
|
||||||
device.destroyImageView(frame.image_view);
|
device.destroyImageView(frame.image_view);
|
||||||
|
|||||||
@ -134,7 +134,6 @@ private:
|
|||||||
DescriptorUpdateQueue update_queue;
|
DescriptorUpdateQueue update_queue;
|
||||||
RasterizerVulkan rasterizer;
|
RasterizerVulkan rasterizer;
|
||||||
std::unique_ptr<PresentWindow> secondary_present_window_ptr;
|
std::unique_ptr<PresentWindow> secondary_present_window_ptr;
|
||||||
|
|
||||||
DescriptorHeap present_heap;
|
DescriptorHeap present_heap;
|
||||||
vk::UniquePipelineLayout present_pipeline_layout;
|
vk::UniquePipelineLayout present_pipeline_layout;
|
||||||
std::array<vk::Pipeline, PRESENT_PIPELINES> present_pipelines;
|
std::array<vk::Pipeline, PRESENT_PIPELINES> present_pipelines;
|
||||||
@ -151,6 +150,9 @@ private:
|
|||||||
vk::ShaderModule cursor_fragment_shader{};
|
vk::ShaderModule cursor_fragment_shader{};
|
||||||
vk::Pipeline cursor_pipeline{};
|
vk::Pipeline cursor_pipeline{};
|
||||||
vk::UniquePipelineLayout cursor_pipeline_layout{};
|
vk::UniquePipelineLayout cursor_pipeline_layout{};
|
||||||
|
bool isSecondaryWindow;
|
||||||
|
bool secondaryWindowEnabled;
|
||||||
|
bool screenRendered;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Vulkan
|
} // namespace Vulkan
|
||||||
|
|||||||
@ -63,6 +63,10 @@ public:
|
|||||||
return swapchain.GetImageCount();
|
return swapchain.GetImageCount();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
vk::Format GetSurfaceFormat() const noexcept {
|
||||||
|
return swapchain.GetSurfaceFormat().format;
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void PresentThread(std::stop_token token);
|
void PresentThread(std::stop_token token);
|
||||||
|
|
||||||
|
|||||||
@ -350,8 +350,8 @@ bool TextureRuntime::ClearTexture(Surface& surface, const VideoCore::TextureClea
|
|||||||
.aspectMask = params.aspect,
|
.aspectMask = params.aspect,
|
||||||
.baseMipLevel = clear.texture_level,
|
.baseMipLevel = clear.texture_level,
|
||||||
.levelCount = 1,
|
.levelCount = 1,
|
||||||
.baseArrayLayer = 0,
|
.baseArrayLayer = clear.texture_layer,
|
||||||
.layerCount = VK_REMAINING_ARRAY_LAYERS,
|
.layerCount = 1,
|
||||||
};
|
};
|
||||||
|
|
||||||
const vk::ImageMemoryBarrier pre_barrier = {
|
const vk::ImageMemoryBarrier pre_barrier = {
|
||||||
@ -436,7 +436,8 @@ void TextureRuntime::ClearTextureWithRenderpass(Surface& surface,
|
|||||||
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||||
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||||
.image = params.src_image,
|
.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 = {
|
const vk::ImageMemoryBarrier post_barrier = {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user