Compare commits

..

17 Commits

Author SHA1 Message Date
lizzie f307a0331d 2026-09-23 07:42:01
Signed-off-by: lizzie <lizzie@eden-emu.dev>
2026-09-23 07:42:01 +00:00
lizzie cb85810ffd 2026-09-23 02:07:39
Signed-off-by: lizzie <lizzie@eden-emu.dev>
2026-09-23 02:07:39 +00:00
lizzie 8ddd0f3cc4 2026-09-23 01:28:59
Signed-off-by: lizzie <lizzie@eden-emu.dev>
2026-09-23 01:28:59 +00:00
lizzie c52a7be00f 2026-09-23 01:18:14
Signed-off-by: lizzie <lizzie@eden-emu.dev>
2026-09-23 01:18:14 +00:00
lizzie 37dc82d6d7 2026-09-22 04:33:30
Signed-off-by: lizzie <lizzie@eden-emu.dev>
2026-09-23 00:53:50 +00:00
lizzie b6a7c809eb 2026-09-22 04:25:09
Signed-off-by: lizzie <lizzie@eden-emu.dev>
2026-09-23 00:53:50 +00:00
lizzie d2b43a2356 2026-09-21 02:08:29
Signed-off-by: lizzie <lizzie@eden-emu.dev>
2026-09-23 00:53:50 +00:00
lizzie 6d508f3eaf 2026-09-21 02:07:30
Signed-off-by: lizzie <lizzie@eden-emu.dev>
2026-09-23 00:53:50 +00:00
lizzie 0871e16706 Fix license headers 2026-09-23 00:53:50 +00:00
lizzie 38816c4f31 Fix license headers 2026-09-23 00:53:50 +00:00
lizzie 251dc590dd 2026-09-21 00:39:13
Signed-off-by: lizzie <lizzie@eden-emu.dev>
2026-09-23 00:53:50 +00:00
lizzie 3eb20d4d45 Fix license headers 2026-09-23 00:53:50 +00:00
lizzie fdf816c1ee 2026-09-20 13:20:40
Signed-off-by: lizzie <lizzie@eden-emu.dev>
2026-09-23 00:53:50 +00:00
lizzie 88bfc04887 2026-09-20 13:02:52
Signed-off-by: lizzie <lizzie@eden-emu.dev>
2026-09-23 00:53:50 +00:00
lizzie 44e6f76c4c 2026-09-20 12:52:02
Signed-off-by: lizzie <lizzie@eden-emu.dev>
2026-09-23 00:53:50 +00:00
lizzie b9ab300b97 Fix license headers 2026-09-23 00:53:49 +00:00
lizzie 376dc541ea 2026-09-20 07:03:36
Signed-off-by: lizzie <lizzie@eden-emu.dev>
2026-09-23 00:53:49 +00:00
123 changed files with 1699 additions and 1472 deletions
-5
View File
@@ -333,11 +333,6 @@
"repo": "herumi/xbyak", "repo": "herumi/xbyak",
"version": "v7.40.1" "version": "v7.40.1"
}, },
"zbic": {
"hash": "fbe2f37986377d7f0d96ae3224c80b5971df6e8b6961f68061f1975ebb2e8cb78f07b90f014b007be4023dbf5513581504e7f7d61a5237cb2a8a7a61ae11e482",
"repo": "kinnay/zbic",
"version": "11b08f2712264bbed731545085cbd9702096ceb7"
},
"zlib": { "zlib": {
"hash": "16fea4df307a68cf0035858abe2fd550250618a97590e202037acd18a666f57afc10f8836cbbd472d54a0e76539d0e558cb26f059d53de52ff90634bbf4f47d4", "hash": "16fea4df307a68cf0035858abe2fd550250618a97590e202037acd18a666f57afc10f8836cbbd472d54a0e76539d0e558cb26f059d53de52ff90634bbf4f47d4",
"min_version": "1.2", "min_version": "1.2",
+21 -33
View File
@@ -1,44 +1,32 @@
# User Handbook - Command Line # User Handbook - Command Line
There are two main applications, an SDL-based app (`eden-cli`) and a Qt based app (`eden`); both accept the same command line arguments. There are two main applications, an SDL-based app (`eden-cli`) and a Qt based app (`eden`); both accept command line arguments.
## eden
- `./eden <path>`: Running with a single argument and nothing else, will make the emulator look for the given file and load it, this behavior is similar to `eden-cli`; allows dragging and dropping games into the application. - `./eden <path>`: Running with a single argument and nothing else, will make the emulator look for the given file and load it, this behavior is similar to `eden-cli`; allows dragging and dropping games into the application.
- `--debug/-d`: Enter debug mode, allow gdb stub at port `1234` - `-g <path>`: Alternate way to specify what to load, overrides. However let it be noted that arguments that use `-` will be treated as options/ignored, if your game, for some reason, starts with `-`, in order to safely handle it you may need to specify it as an argument.
- `--config/-c`: Specify alternate configuration file. - `-f`: Use fullscreen.
- `--fullscreen/-f`: Use fullscreen. - `-u <number>`: Select the index of the user to load as.
- `--help/-h`: Display help. - `-input-profile <name>`: Specifies input profile name to use (for player #0 only).
- `--game/-g <path>`: Alternate way to specify what to load, overrides. However let it be noted that arguments that use `-` will be treated as options/ignored, if your game, for some reason, starts with `-`, in order to safely handle it you may need to specify it as an argument.
- `--multiplayer/-m`: Specify multiplayer options.
- `--program/-p`: Specify the program arguments to pass (optional).
- `--user/-u <number>`: Specify the user index.
- `--version/-v`: Display version and quit.
- `--input-profile/-i <name>`: Specifies input profile name to use (for player #0 only).
- `--null-render/-n`: Forces the usage of the "Null" render backend irrespective of settings.
- `--filter/-x`: Sets the debug log filter irrespective of settings.
- `--singlecore/-s`: Forces single-core regardless of settings.
Only the Qt frontend supports the following arguments:
- `-qlaunch`: Launch QLaunch. - `-qlaunch`: Launch QLaunch.
- `-hlaunch`: Launch homebrew launcher `nx-hbloader`. - `-hlaunch`: Launch homebrew launcher `nx-hbloader`.
- Requires a copy of Atmosphere to be extracted onto `sdmc`. - Requires a copy of Atmosphere to be extracted onto `sdmc`.
- This is a shorthand for `<eden folder>/sdmc/atmosphere/hbl.nsp`. - This is a shorthand for `<eden folder>/sdmc/atmosphere/hbl.nsp`.
- `-setup`: Launch setup applet. - `-setup`: Launch setup applet.
`eden` (provided with `--room`), and `eden-room` supports the following options as well: ## eden-cli
- `-n/--room-name`: The name of the room. - `--debug/-d`: Enter debug mode, allow gdb stub at port `1234`
- `-d/--room-description`: The room description. - `--config/-c`: Specify alternate configuration file.
- `-s/--bind-address`: The bind address for the room. - `--fullscreen/-f`: Set fullscreen.
- `-p/--port`: The port used for the room. - `--help/-h`: Display help.
- `-m/--max-members`: The maximum number of players for this room. - `--game/-g`: Specify the game to run.
- `-w/--password`: The password for the room. - `--multiplayer/-m`: Specify multiplayer options.
- `-g/--preferred-game`: The preferred game for this room. - `--program/-p`: Specify the program arguments to pass (optional).
- `-i/--preferred-game-id`: The preferred game-id for this room. - `--user/-u`: Specify the user index.
- `-u/--username`: The username used for announce. - `--version/-v`: Display version and quit.
- `-t/--token`: The token used for announce. - `--input-profile/-i`: Specifies input profile name to use (for player #0 only).
- `-a/--web-api-url`: yuzu Web API url. - `--null-render/-n`: Forces the usage of the "Null" render backend irrespective of settings.
- `-b/--ban-list-file`: The file for storing the room ban list. - `--filter/-x`: Sets the debug log filter irrespective of settings.
- `-l/--log-file`: The file for storing the room log. - `--singlecore/-s`: Forces single-core regardless of settings.
- `-h/--help`: Display this help and exit.
- `-v/--version`: Output version information and exit.
-5
View File
@@ -48,11 +48,6 @@ if (NOT TARGET stb::headers)
add_library(stb::headers ALIAS stb) add_library(stb::headers ALIAS stb)
endif() endif()
AddJsonPackage(NAME zbic DOWNLOAD_ONLY)
set(ZBIC_INCLUDE_DIR
"${zbic_SOURCE_DIR}/src"
PARENT_SCOPE)
# ItaniumDemangle (Windows only) # ItaniumDemangle (Windows only)
if (WIN32 AND NOT TARGET LLVM::Demangle) if (WIN32 AND NOT TARGET LLVM::Demangle)
add_library(demangle demangle/ItaniumDemangle.cpp) add_library(demangle demangle/ItaniumDemangle.cpp)
@@ -30,6 +30,7 @@ enum class BooleanSetting(override val key: String) : AbstractBooleanSetting {
RENDERER_REACTIVE_FLUSHING("use_reactive_flushing"), RENDERER_REACTIVE_FLUSHING("use_reactive_flushing"),
ENABLE_BUFFER_HISTORY("enable_buffer_history"), ENABLE_BUFFER_HISTORY("enable_buffer_history"),
USE_OPTIMIZED_VERTEX_BUFFERS("use_optimized_vertex_buffers"), USE_OPTIMIZED_VERTEX_BUFFERS("use_optimized_vertex_buffers"),
ENABLE_GPU_BUFFER_READBACK("enable_gpu_buffer_readback"),
SYNC_MEMORY_OPERATIONS("sync_memory_operations"), SYNC_MEMORY_OPERATIONS("sync_memory_operations"),
BUFFER_REORDER_DISABLE("disable_buffer_reorder"), BUFFER_REORDER_DISABLE("disable_buffer_reorder"),
RENDERER_DEBUG("debug"), RENDERER_DEBUG("debug"),
@@ -908,6 +908,13 @@ abstract class SettingsItem(
descriptionId = R.string.enable_buffer_history_description descriptionId = R.string.enable_buffer_history_description
) )
) )
put(
SwitchSetting(
BooleanSetting.ENABLE_GPU_BUFFER_READBACK,
titleId = R.string.enable_gpu_buffer_readback,
descriptionId = R.string.enable_gpu_buffer_readback_description
)
)
put( put(
SwitchSetting( SwitchSetting(
BooleanSetting.USE_OPTIMIZED_VERTEX_BUFFERS, BooleanSetting.USE_OPTIMIZED_VERTEX_BUFFERS,
@@ -549,6 +549,7 @@ class SettingsFragmentPresenter(
add(BooleanSetting.RENDERER_FORCE_MAX_CLOCK.key) add(BooleanSetting.RENDERER_FORCE_MAX_CLOCK.key)
add(BooleanSetting.RENDERER_REACTIVE_FLUSHING.key) add(BooleanSetting.RENDERER_REACTIVE_FLUSHING.key)
add(BooleanSetting.ENABLE_BUFFER_HISTORY.key) add(BooleanSetting.ENABLE_BUFFER_HISTORY.key)
add(BooleanSetting.ENABLE_GPU_BUFFER_READBACK.key)
add(BooleanSetting.USE_OPTIMIZED_VERTEX_BUFFERS.key) add(BooleanSetting.USE_OPTIMIZED_VERTEX_BUFFERS.key)
add(HeaderSetting(R.string.hacks)) add(HeaderSetting(R.string.hacks))
@@ -580,6 +580,8 @@
<string name="renderer_reactive_flushing_description">يحسن دقة العرض في بعض الألعاب على حساب الأداء.</string> <string name="renderer_reactive_flushing_description">يحسن دقة العرض في بعض الألعاب على حساب الأداء.</string>
<string name="enable_buffer_history">تمكين سجل التخزين المؤقت</string> <string name="enable_buffer_history">تمكين سجل التخزين المؤقت</string>
<string name="enable_buffer_history_description">يُتيح هذا الخيار الوصول إلى حالات التخزين المؤقت السابقة. وقد يُحسّن جودة العرض وثبات الأداء في بعض الألعاب.</string> <string name="enable_buffer_history_description">يُتيح هذا الخيار الوصول إلى حالات التخزين المؤقت السابقة. وقد يُحسّن جودة العرض وثبات الأداء في بعض الألعاب.</string>
<string name="enable_gpu_buffer_readback">تفعيل قراءة مخزن وحدة معالجة الرسومات</string>
<string name="enable_gpu_buffer_readback_description">يحافظ هذا النظام على بيانات المخزن المؤقت المُعدّلة بواسطة وحدة معالجة الرسومات عن طريق قراءتها مرة أخرى قبل التحميل. تتطلب بعض الألعاب ذلك لعرض بعض التأثيرات بشكل صحيح. قد يُسبب ذلك مشاكل إذا لم يتمكن الجهاز من التعامل مع عبء العمل الإضافي.</string>
<string name="use_optimized_vertex_buffers">مخازن الرؤوس المُحسّنة</string> <string name="use_optimized_vertex_buffers">مخازن الرؤوس المُحسّنة</string>
<string name="use_optimized_vertex_buffers_description">يُتيح ربطًا مُحسَّنًا لمخازن الرؤوس لتحسين الأداء. يتطلب برامج تشغيل Mesa 26.0+ Turnip/ برامج تشغيل QCOM. قد يتعطل على برامج تشغيل Turnip القديمة (25.3 وما دون).</string> <string name="use_optimized_vertex_buffers_description">يُتيح ربطًا مُحسَّنًا لمخازن الرؤوس لتحسين الأداء. يتطلب برامج تشغيل Mesa 26.0+ Turnip/ برامج تشغيل QCOM. قد يتعطل على برامج تشغيل Turnip القديمة (25.3 وما دون).</string>
@@ -1097,6 +1099,7 @@
<string name="gpu_fence_behavior_immediate">فوري</string> <string name="gpu_fence_behavior_immediate">فوري</string>
<string name="gpu_fence_behavior_balanced">متوازن</string> <string name="gpu_fence_behavior_balanced">متوازن</string>
<string name="gpu_fence_behavior_accurate">دقيق</string> <string name="gpu_fence_behavior_accurate">دقيق</string>
<string name="gpu_fence_behavior_strict">صارم</string>
<string name="vram_usage_conservative">محافظ</string> <string name="vram_usage_conservative">محافظ</string>
<string name="vram_usage_aggressive">عدواني</string> <string name="vram_usage_aggressive">عدواني</string>
@@ -967,6 +967,7 @@ Wirklich fortfahren?</string>
<string name="gpu_fence_behavior_immediate">Direkt</string> <string name="gpu_fence_behavior_immediate">Direkt</string>
<string name="gpu_fence_behavior_balanced">Ausgewogen</string> <string name="gpu_fence_behavior_balanced">Ausgewogen</string>
<string name="gpu_fence_behavior_accurate">Genau</string> <string name="gpu_fence_behavior_accurate">Genau</string>
<string name="gpu_fence_behavior_strict">Strikt</string>
<string name="vram_usage_conservative">Konservativ</string> <string name="vram_usage_conservative">Konservativ</string>
<string name="vram_usage_aggressive">Aggressiv</string> <string name="vram_usage_aggressive">Aggressiv</string>
@@ -524,6 +524,8 @@
<string name="renderer_reactive_flushing_description">Mejora la precisión de renderizado en algunos juegos, pero reduce el rendimiento.</string> <string name="renderer_reactive_flushing_description">Mejora la precisión de renderizado en algunos juegos, pero reduce el rendimiento.</string>
<string name="enable_buffer_history">Activar el historial del búfer</string> <string name="enable_buffer_history">Activar el historial del búfer</string>
<string name="enable_buffer_history_description">Permite el acceso al estado del búfer anterior. Esta opción puede mejorar la calidad de renderizado y la consistencia en el rendimiento de algunos juegos.</string> <string name="enable_buffer_history_description">Permite el acceso al estado del búfer anterior. Esta opción puede mejorar la calidad de renderizado y la consistencia en el rendimiento de algunos juegos.</string>
<string name="enable_gpu_buffer_readback">Activar la lectura del buffer de la GPU</string>
<string name="enable_gpu_buffer_readback_description">Conserva los datos del búfer modificados por la GPU leyéndolos antes de subirlos.\nAlgunos juegos requieren esto para renderizar correctamente ciertos efectos.\nPuede causar problemas si el hardware no puede soportar la carga de trabajo adicional.</string>
<string name="use_optimized_vertex_buffers">Búferes de vértices optimizados</string> <string name="use_optimized_vertex_buffers">Búferes de vértices optimizados</string>
<string name="use_optimized_vertex_buffers_description">Permite la optimización del enlace del búfer de vértices para un mejor rendimiento. Requiere controladores Mesa 26.0+ Turnip/ controladores QCOM. Fallará con controladores Turnip más antiguos (versión 25.3 o inferior).</string> <string name="use_optimized_vertex_buffers_description">Permite la optimización del enlace del búfer de vértices para un mejor rendimiento. Requiere controladores Mesa 26.0+ Turnip/ controladores QCOM. Fallará con controladores Turnip más antiguos (versión 25.3 o inferior).</string>
@@ -1036,6 +1038,7 @@
<string name="gpu_fence_behavior_immediate">Inmediato</string> <string name="gpu_fence_behavior_immediate">Inmediato</string>
<string name="gpu_fence_behavior_balanced">Equilibrado</string> <string name="gpu_fence_behavior_balanced">Equilibrado</string>
<string name="gpu_fence_behavior_accurate">Preciso</string> <string name="gpu_fence_behavior_accurate">Preciso</string>
<string name="gpu_fence_behavior_strict">Estricto</string>
<string name="vram_usage_conservative">Conservador</string> <string name="vram_usage_conservative">Conservador</string>
<string name="vram_usage_aggressive">Agresivo</string> <string name="vram_usage_aggressive">Agresivo</string>
@@ -569,6 +569,8 @@
<string name="renderer_reactive_flushing_description">Повышение точности рендеринга в некоторых играх за счет снижения производительности.</string> <string name="renderer_reactive_flushing_description">Повышение точности рендеринга в некоторых играх за счет снижения производительности.</string>
<string name="enable_buffer_history">Включить историю буфера</string> <string name="enable_buffer_history">Включить историю буфера</string>
<string name="enable_buffer_history_description">Позволяет обращаться к предыдущим состояниям буфера. Эта опция может повысить качество рендеринга и стабильность производительности в некоторых играх.</string> <string name="enable_buffer_history_description">Позволяет обращаться к предыдущим состояниям буфера. Эта опция может повысить качество рендеринга и стабильность производительности в некоторых играх.</string>
<string name="enable_gpu_buffer_readback">Включить обратное чтение буфера ГПУ</string>
<string name="enable_gpu_buffer_readback_description">Сохраняет измененные ГПУ данные буфера путем чтения их обратно перед выгрузками. Некоторые игры требуют этого, чтобы рендерить определенные эффекты правильно. Может вызывать проблемы если оборудование не может обработать дополнительную рабочую нагрузку.</string>
<string name="use_optimized_vertex_buffers">Оптимизированные вершинные буферы</string> <string name="use_optimized_vertex_buffers">Оптимизированные вершинные буферы</string>
<string name="use_optimized_vertex_buffers_description">Включает оптимизированную привязку вершинного буфера для повышения производительности. Требует Mesa Turnip 26.0+ / QCOM. Приводит к вылету на старых версиях драйверов Turnip (25.3 и ниже).</string> <string name="use_optimized_vertex_buffers_description">Включает оптимизированную привязку вершинного буфера для повышения производительности. Требует Mesa Turnip 26.0+ / QCOM. Приводит к вылету на старых версиях драйверов Turnip (25.3 и ниже).</string>
@@ -1086,6 +1088,7 @@
<string name="gpu_fence_behavior_immediate">Мгновенный</string> <string name="gpu_fence_behavior_immediate">Мгновенный</string>
<string name="gpu_fence_behavior_balanced">Сбалансированный</string> <string name="gpu_fence_behavior_balanced">Сбалансированный</string>
<string name="gpu_fence_behavior_accurate">Точный</string> <string name="gpu_fence_behavior_accurate">Точный</string>
<string name="gpu_fence_behavior_strict">Строгий</string>
<string name="vram_usage_conservative">Консервативный</string> <string name="vram_usage_conservative">Консервативный</string>
<string name="vram_usage_aggressive">Агрессивный</string> <string name="vram_usage_aggressive">Агрессивный</string>
@@ -570,6 +570,8 @@
<string name="renderer_reactive_flushing_description">通过牺牲性能来提升某些游戏的渲染精度。</string> <string name="renderer_reactive_flushing_description">通过牺牲性能来提升某些游戏的渲染精度。</string>
<string name="enable_buffer_history">启用缓冲区历史</string> <string name="enable_buffer_history">启用缓冲区历史</string>
<string name="enable_buffer_history_description">启用对先前缓冲区状态的访问。此选项可在某些游戏中提升渲染质量并保持性能的一致性。</string> <string name="enable_buffer_history_description">启用对先前缓冲区状态的访问。此选项可在某些游戏中提升渲染质量并保持性能的一致性。</string>
<string name="enable_gpu_buffer_readback">启用 GPU 缓冲区回读</string>
<string name="enable_gpu_buffer_readback_description">在上传前回读经由 GPU 修改过的缓冲区数据,以将其保留。一些游戏会用到这项设定以正确渲染某些效果。如果硬件无法处理额外的工作负载,则可能会导致问题。</string>
<string name="use_optimized_vertex_buffers">优化顶点缓冲区</string> <string name="use_optimized_vertex_buffers">优化顶点缓冲区</string>
<string name="use_optimized_vertex_buffers_description">启用经过优化的顶点缓冲区绑定以提升性能。需要 Mesa 26.0 及以上版本的 Turnip 或 QCOM 驱动程序。若使用较旧版本的 Turnip 驱动 (25.3 及以下版本) 则会导致崩溃。</string> <string name="use_optimized_vertex_buffers_description">启用经过优化的顶点缓冲区绑定以提升性能。需要 Mesa 26.0 及以上版本的 Turnip 或 QCOM 驱动程序。若使用较旧版本的 Turnip 驱动 (25.3 及以下版本) 则会导致崩溃。</string>
@@ -1087,6 +1089,7 @@
<string name="gpu_fence_behavior_immediate">即时</string> <string name="gpu_fence_behavior_immediate">即时</string>
<string name="gpu_fence_behavior_balanced">均衡</string> <string name="gpu_fence_behavior_balanced">均衡</string>
<string name="gpu_fence_behavior_accurate">精确</string> <string name="gpu_fence_behavior_accurate">精确</string>
<string name="gpu_fence_behavior_strict">严格</string>
<string name="vram_usage_conservative">保守式</string> <string name="vram_usage_conservative">保守式</string>
<string name="vram_usage_aggressive">主动式</string> <string name="vram_usage_aggressive">主动式</string>
@@ -561,6 +561,8 @@
<string name="renderer_reactive_flushing_description">犧牲效能,以改善部分遊戲的轉譯準確度</string> <string name="renderer_reactive_flushing_description">犧牲效能,以改善部分遊戲的轉譯準確度</string>
<string name="enable_buffer_history">啟用緩衝區歷史</string> <string name="enable_buffer_history">啟用緩衝區歷史</string>
<string name="enable_buffer_history_description">允許存取先前的緩衝區狀態。此選項可能會改善部分遊戲的渲染品質與效能穩定性</string> <string name="enable_buffer_history_description">允許存取先前的緩衝區狀態。此選項可能會改善部分遊戲的渲染品質與效能穩定性</string>
<string name="enable_gpu_buffer_readback">啟用 GPU 緩衝區讀回</string>
<string name="enable_gpu_buffer_readback_description">透過在上傳之前先將 GPU 修改過的緩衝區資料讀回來保存資料,部分遊戲需要啟用此功能才能正常渲染遊戲特效。如果硬體無法負荷可能會導致錯誤</string>
<string name="use_optimized_vertex_buffers">最佳化頂點緩衝區</string> <string name="use_optimized_vertex_buffers">最佳化頂點緩衝區</string>
<string name="use_optimized_vertex_buffers_description">啟用最佳化的頂點緩衝區綁定。需要安裝 Mesa 26.0+ Turnip drivers/Qualcomm drivers。使用舊版 Turnip drivers 會導致當機 (25.3版和更低的版本)</string> <string name="use_optimized_vertex_buffers_description">啟用最佳化的頂點緩衝區綁定。需要安裝 Mesa 26.0+ Turnip drivers/Qualcomm drivers。使用舊版 Turnip drivers 會導致當機 (25.3版和更低的版本)</string>
@@ -558,6 +558,7 @@
<item>@string/gpu_fence_behavior_immediate</item> <item>@string/gpu_fence_behavior_immediate</item>
<item>@string/gpu_fence_behavior_balanced</item> <item>@string/gpu_fence_behavior_balanced</item>
<item>@string/gpu_fence_behavior_accurate</item> <item>@string/gpu_fence_behavior_accurate</item>
<item>@string/gpu_fence_behavior_strict</item>
</string-array> </string-array>
<integer-array name="gpuFenceBehaviorValues"> <integer-array name="gpuFenceBehaviorValues">
<item>0</item> <item>0</item>
@@ -586,6 +586,8 @@
<string name="renderer_reactive_flushing_description">Improves rendering accuracy in some games at the cost of performance.</string> <string name="renderer_reactive_flushing_description">Improves rendering accuracy in some games at the cost of performance.</string>
<string name="enable_buffer_history">Enable buffer history</string> <string name="enable_buffer_history">Enable buffer history</string>
<string name="enable_buffer_history_description">Enables access to previous buffer states. This option may improve rendering quality and performance consistency in some games.</string> <string name="enable_buffer_history_description">Enables access to previous buffer states. This option may improve rendering quality and performance consistency in some games.</string>
<string name="enable_gpu_buffer_readback">Enable GPU Buffer Readback</string>
<string name="enable_gpu_buffer_readback_description">Preserves GPU-modified buffer data by reading it back before uploads. Some games require this to render certain effects properly. May cause issues if the hardware cannot handle the additional workload.</string>
<string name="use_optimized_vertex_buffers">Optimized Vertex Buffers</string> <string name="use_optimized_vertex_buffers">Optimized Vertex Buffers</string>
<string name="use_optimized_vertex_buffers_description">Enables optimized vertex buffer binding for improved performance. Requires Mesa 26.0+ Turnip drivers/ QCOM drivers. Will crash on older Turnip drivers (25.3 and below).</string> <string name="use_optimized_vertex_buffers_description">Enables optimized vertex buffer binding for improved performance. Requires Mesa 26.0+ Turnip drivers/ QCOM drivers. Will crash on older Turnip drivers (25.3 and below).</string>
@@ -1136,6 +1138,7 @@
<string name="gpu_fence_behavior_immediate">Immediate</string> <string name="gpu_fence_behavior_immediate">Immediate</string>
<string name="gpu_fence_behavior_balanced">Balanced</string> <string name="gpu_fence_behavior_balanced">Balanced</string>
<string name="gpu_fence_behavior_accurate">Accurate</string> <string name="gpu_fence_behavior_accurate">Accurate</string>
<string name="gpu_fence_behavior_strict">Strict</string>
<!-- ASTC Decoding Method Choices --> <!-- ASTC Decoding Method Choices -->
<string name="accelerate_astc_cpu" translatable="false">CPU</string> <string name="accelerate_astc_cpu" translatable="false">CPU</string>
+6 -1
View File
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project // SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
@@ -19,6 +19,11 @@ ADSP::ADSP(Core::System& system, Sink::Sink& sink) {
} }
} }
void ADSP::Shutdown() {
audio_renderer->Stop();
opus_decoder->Shutdown();
}
AudioRenderer::AudioRenderer& ADSP::AudioRenderer() { AudioRenderer::AudioRenderer& ADSP::AudioRenderer() {
return *audio_renderer; return *audio_renderer;
} }
+2 -1
View File
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project // SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
@@ -45,6 +45,7 @@ public:
explicit ADSP(Core::System& system, Sink::Sink& sink); explicit ADSP(Core::System& system, Sink::Sink& sink);
~ADSP() = default; ~ADSP() = default;
void Shutdown();
AudioRenderer::AudioRenderer& AudioRenderer(); AudioRenderer::AudioRenderer& AudioRenderer();
OpusDecoder::OpusDecoder& OpusDecoder(); OpusDecoder::OpusDecoder& OpusDecoder();
@@ -34,26 +34,19 @@ void AudioRenderer::Start() {
mailbox.Send(Direction::DSP, Message::InitializeOK); mailbox.Send(Direction::DSP, Message::InitializeOK);
if (mailbox.Receive(Direction::Host) != Message::InitializeOK) { if (mailbox.Receive(Direction::Host) != Message::InitializeOK) {
LOG_ERROR(Service_Audio, "Host Audio Renderer -- Failed to receive shutdown " LOG_ERROR(Service_Audio, "Host Audio Renderer -- Failed to receive shutdown message response from ADSP!");
"message response from ADSP!");
return;
} }
running = true;
} }
void AudioRenderer::Stop() { void AudioRenderer::Stop() {
if (!running) { if (main_thread.joinable()) {
return; main_thread.request_stop();
mailbox.Send(Direction::DSP, Message::Shutdown);
if (mailbox.Receive(Direction::Host, main_thread.get_stop_token()) != Message::Shutdown) {
LOG_ERROR(Service_Audio, "Host Audio Renderer -- Failed to receive shutdown message response from ADSP!");
}
main_thread.join();
} }
mailbox.Send(Direction::DSP, Message::Shutdown);
if (mailbox.Receive(Direction::Host) != Message::Shutdown) {
LOG_ERROR(Service_Audio, "Host Audio Renderer -- Failed to receive shutdown "
"message response from ADSP!");
}
main_thread.request_stop();
main_thread.join();
for (auto& stream : streams) { for (auto& stream : streams) {
if (stream) { if (stream) {
stream->Stop(); stream->Stop();
@@ -61,7 +54,6 @@ void AudioRenderer::Stop() {
stream = nullptr; stream = nullptr;
} }
} }
running = false;
} }
void AudioRenderer::Signal() { void AudioRenderer::Signal() {
@@ -69,13 +61,12 @@ void AudioRenderer::Signal() {
Send(Direction::DSP, Message::Render); Send(Direction::DSP, Message::Render);
} }
void AudioRenderer::Wait() { void AudioRenderer::Wait(std::stop_token stop_token) {
auto msg = Receive(Direction::Host); auto msg = mailbox.Receive(Direction::Host, stop_token);
if (msg != Message::RenderResponse) { if (msg != Message::RenderResponse) {
LOG_ERROR(Service_Audio, LOG_ERROR(Service_Audio,
"Did not receive the expected render response from the AudioRenderer! Expected " "Did not receive the expected render response from the AudioRenderer! Expected "
"{}, got {}", "{}, got {}", Message::RenderResponse, msg);
Message::RenderResponse, msg);
} }
PostDSPClearCommandBuffer(); PostDSPClearCommandBuffer();
} }
@@ -147,7 +138,9 @@ void AudioRenderer::Main(std::stop_token stop_token) {
constexpr u64 max_process_time{2'304'000ULL}; constexpr u64 max_process_time{2'304'000ULL};
while (!stop_token.stop_requested()) { while (!stop_token.stop_requested()) {
auto msg{mailbox.Receive(Direction::DSP)}; auto msg = mailbox.Receive(Direction::DSP, stop_token);
if (stop_token.stop_requested())
break;
switch (msg) { switch (msg) {
case Message::Shutdown: case Message::Shutdown:
mailbox.Send(Direction::Host, Message::Shutdown); mailbox.Send(Direction::Host, Message::Shutdown);
@@ -63,13 +63,15 @@ public:
*/ */
void Start(); void Start();
void NotifyShutdown();
/** /**
* Stop the AudioRenderer. * Stop the AudioRenderer.
*/ */
void Stop(); void Stop();
void Signal(); void Signal();
void Wait(); void Wait(std::stop_token stop_token);
void Send(Direction dir, u32 message); void Send(Direction dir, u32 message);
u32 Receive(Direction dir); u32 Receive(Direction dir);
@@ -102,8 +104,6 @@ private:
Mailbox mailbox; Mailbox mailbox;
/// Main thread /// Main thread
std::jthread main_thread{}; std::jthread main_thread{};
/// The current state
std::atomic<bool> running{};
/// Shared memory of input command buffers, set by host, read by DSP /// Shared memory of input command buffers, set by host, read by DSP
std::array<CommandBuffer, MaxRendererSessions> command_buffers{}; std::array<CommandBuffer, MaxRendererSessions> command_buffers{};
/// The command lists to process /// The command lists to process
+33 -23
View File
@@ -41,19 +41,16 @@ OpusDecoder::OpusDecoder(Core::System& system_) : system{system_} {
} }
OpusDecoder::~OpusDecoder() { OpusDecoder::~OpusDecoder() {
if (!running) { if (main_thread.joinable()) {
// Shutdown the thread
Send(Direction::DSP, Message::Shutdown);
main_thread.request_stop();
auto msg = Receive(Direction::Host, main_thread.get_stop_token());
ASSERT_MSG(msg == Message::ShutdownOK, "Expected Opus shutdown code {}, got {}", Message::ShutdownOK, msg);
main_thread.join();
} else {
init_thread.request_stop(); init_thread.request_stop();
return;
} }
// Shutdown the thread
Send(Direction::DSP, Message::Shutdown);
auto msg = Receive(Direction::Host);
ASSERT_MSG(msg == Message::ShutdownOK, "Expected Opus shutdown code {}, got {}",
Message::ShutdownOK, msg);
main_thread.request_stop();
main_thread.join();
running = false;
} }
void OpusDecoder::Send(Direction dir, u32 message) { void OpusDecoder::Send(Direction dir, u32 message) {
@@ -73,7 +70,6 @@ void OpusDecoder::Init(std::stop_token stop_token) {
return; return;
} }
main_thread = std::jthread([this](std::stop_token st) { Main(st); }); main_thread = std::jthread([this](std::stop_token st) { Main(st); });
running = true;
Send(Direction::Host, Message::StartOK); Send(Direction::Host, Message::StartOK);
} }
@@ -82,12 +78,15 @@ void OpusDecoder::Main(std::stop_token stop_token) {
while (!stop_token.stop_requested()) { while (!stop_token.stop_requested()) {
auto msg = Receive(Direction::DSP, stop_token); auto msg = Receive(Direction::DSP, stop_token);
if (stop_token.stop_requested())
break;
switch (msg) { switch (msg) {
case Shutdown: case Message::Shutdown:
Send(Direction::Host, Message::ShutdownOK); Send(Direction::Host, Message::ShutdownOK);
return; return;
case GetWorkBufferSize: { case Message::GetWorkBufferSize: {
auto channel_count = static_cast<s32>(shared_memory->host_send_data[0]); auto channel_count = static_cast<s32>(shared_memory->host_send_data[0]);
ASSERT(IsValidChannelCount(channel_count)); ASSERT(IsValidChannelCount(channel_count));
@@ -96,7 +95,7 @@ void OpusDecoder::Main(std::stop_token stop_token) {
Send(Direction::Host, Message::GetWorkBufferSizeOK); Send(Direction::Host, Message::GetWorkBufferSizeOK);
} break; } break;
case InitializeDecodeObject: { case Message::InitializeDecodeObject: {
auto buffer = shared_memory->host_send_data[0]; auto buffer = shared_memory->host_send_data[0];
auto buffer_size = shared_memory->host_send_data[1]; auto buffer_size = shared_memory->host_send_data[1];
auto sample_rate = static_cast<s32>(shared_memory->host_send_data[2]); auto sample_rate = static_cast<s32>(shared_memory->host_send_data[2]);
@@ -113,7 +112,7 @@ void OpusDecoder::Main(std::stop_token stop_token) {
Send(Direction::Host, Message::InitializeDecodeObjectOK); Send(Direction::Host, Message::InitializeDecodeObjectOK);
} break; } break;
case ShutdownDecodeObject: { case Message::ShutdownDecodeObject: {
auto buffer = shared_memory->host_send_data[0]; auto buffer = shared_memory->host_send_data[0];
[[maybe_unused]] auto buffer_size = shared_memory->host_send_data[1]; [[maybe_unused]] auto buffer_size = shared_memory->host_send_data[1];
@@ -123,7 +122,7 @@ void OpusDecoder::Main(std::stop_token stop_token) {
Send(Direction::Host, Message::ShutdownDecodeObjectOK); Send(Direction::Host, Message::ShutdownDecodeObjectOK);
} break; } break;
case DecodeInterleaved: { case Message::DecodeInterleaved: {
auto start_time = system.CoreTiming().GetGlobalTimeUs(); auto start_time = system.CoreTiming().GetGlobalTimeUs();
auto buffer = shared_memory->host_send_data[0]; auto buffer = shared_memory->host_send_data[0];
@@ -161,19 +160,19 @@ void OpusDecoder::Main(std::stop_token stop_token) {
Send(Direction::Host, Message::DecodeInterleavedOK); Send(Direction::Host, Message::DecodeInterleavedOK);
} break; } break;
case MapMemory: { case Message::MapMemory: {
[[maybe_unused]] auto buffer = shared_memory->host_send_data[0]; [[maybe_unused]] auto buffer = shared_memory->host_send_data[0];
[[maybe_unused]] auto buffer_size = shared_memory->host_send_data[1]; [[maybe_unused]] auto buffer_size = shared_memory->host_send_data[1];
Send(Direction::Host, Message::MapMemoryOK); Send(Direction::Host, Message::MapMemoryOK);
} break; } break;
case UnmapMemory: { case Message::UnmapMemory: {
[[maybe_unused]] auto buffer = shared_memory->host_send_data[0]; [[maybe_unused]] auto buffer = shared_memory->host_send_data[0];
[[maybe_unused]] auto buffer_size = shared_memory->host_send_data[1]; [[maybe_unused]] auto buffer_size = shared_memory->host_send_data[1];
Send(Direction::Host, Message::UnmapMemoryOK); Send(Direction::Host, Message::UnmapMemoryOK);
} break; } break;
case GetWorkBufferSizeForMultiStream: { case Message::GetWorkBufferSizeForMultiStream: {
auto total_stream_count = static_cast<s32>(shared_memory->host_send_data[0]); auto total_stream_count = static_cast<s32>(shared_memory->host_send_data[0]);
auto stereo_stream_count = static_cast<s32>(shared_memory->host_send_data[1]); auto stereo_stream_count = static_cast<s32>(shared_memory->host_send_data[1]);
@@ -184,7 +183,7 @@ void OpusDecoder::Main(std::stop_token stop_token) {
Send(Direction::Host, Message::GetWorkBufferSizeForMultiStreamOK); Send(Direction::Host, Message::GetWorkBufferSizeForMultiStreamOK);
} break; } break;
case InitializeMultiStreamDecodeObject: { case Message::InitializeMultiStreamDecodeObject: {
auto buffer = shared_memory->host_send_data[0]; auto buffer = shared_memory->host_send_data[0];
auto buffer_size = shared_memory->host_send_data[1]; auto buffer_size = shared_memory->host_send_data[1];
auto sample_rate = static_cast<s32>(shared_memory->host_send_data[2]); auto sample_rate = static_cast<s32>(shared_memory->host_send_data[2]);
@@ -211,7 +210,7 @@ void OpusDecoder::Main(std::stop_token stop_token) {
Send(Direction::Host, Message::InitializeMultiStreamDecodeObjectOK); Send(Direction::Host, Message::InitializeMultiStreamDecodeObjectOK);
} break; } break;
case ShutdownMultiStreamDecodeObject: { case Message::ShutdownMultiStreamDecodeObject: {
auto buffer = shared_memory->host_send_data[0]; auto buffer = shared_memory->host_send_data[0];
[[maybe_unused]] auto buffer_size = shared_memory->host_send_data[1]; [[maybe_unused]] auto buffer_size = shared_memory->host_send_data[1];
@@ -221,7 +220,7 @@ void OpusDecoder::Main(std::stop_token stop_token) {
Send(Direction::Host, Message::ShutdownMultiStreamDecodeObjectOK); Send(Direction::Host, Message::ShutdownMultiStreamDecodeObjectOK);
} break; } break;
case DecodeInterleavedForMultiStream: { case Message::DecodeInterleavedForMultiStream: {
auto start_time = system.CoreTiming().GetGlobalTimeUs(); auto start_time = system.CoreTiming().GetGlobalTimeUs();
auto buffer = shared_memory->host_send_data[0]; auto buffer = shared_memory->host_send_data[0];
@@ -266,4 +265,15 @@ void OpusDecoder::Main(std::stop_token stop_token) {
} }
} }
void OpusDecoder::Shutdown() {
if (init_thread.joinable()) {
init_thread.request_stop();
init_thread.join(); //must explicitly wait for init to join
}
if (main_thread.joinable()) {
main_thread.request_stop();
Send(Direction::DSP, Message::Shutdown);
}
}
} // namespace AudioCore::ADSP::OpusDecoder } // namespace AudioCore::ADSP::OpusDecoder
+3 -3
View File
@@ -57,7 +57,7 @@ public:
~OpusDecoder(); ~OpusDecoder();
bool IsRunning() const noexcept { bool IsRunning() const noexcept {
return running; return main_thread.joinable();
} }
void Send(Direction dir, u32 message); void Send(Direction dir, u32 message);
@@ -67,6 +67,8 @@ public:
shared_memory = &shared_memory_; shared_memory = &shared_memory_;
} }
void Shutdown();
private: private:
/** /**
* Initializing thread, launched at audio_core boot to avoid blocking the main emu boot thread. * Initializing thread, launched at audio_core boot to avoid blocking the main emu boot thread.
@@ -85,8 +87,6 @@ private:
std::jthread init_thread{}; std::jthread init_thread{};
/// Main thread /// Main thread
std::jthread main_thread{}; std::jthread main_thread{};
/// The current state
bool running{};
/// Structure shared with the host, input data set by the host before sending a mailbox message, /// Structure shared with the host, input data set by the host before sending a mailbox message,
/// and the responses are written back by the OpusDecoder. /// and the responses are written back by the OpusDecoder.
SharedMemory* shared_memory{}; SharedMemory* shared_memory{};
+1
View File
@@ -33,6 +33,7 @@ void AudioCore::CreateSinks() {
void AudioCore::Shutdown() { void AudioCore::Shutdown() {
audio_manager->Shutdown(); audio_manager->Shutdown();
adsp->Shutdown();
} }
AudioManager& AudioCore::GetAudioManager() { AudioManager& AudioCore::GetAudioManager() {
+2 -2
View File
@@ -17,7 +17,7 @@ AudioManager::AudioManager() {
std::unique_lock l{events.GetAudioEventLock()}; std::unique_lock l{events.GetAudioEventLock()};
events.ClearEvents(); events.ClearEvents();
while (!stop_token.stop_requested()) { while (!stop_token.stop_requested()) {
const auto timed_out{events.Wait(l, std::chrono::seconds(2))}; const auto timed_out = events.Wait(l, std::chrono::seconds{2});
if (events.CheckAudioEventSet(Event::Type::Max)) { if (events.CheckAudioEventSet(Event::Type::Max)) {
break; break;
} }
@@ -35,9 +35,9 @@ AudioManager::AudioManager() {
} }
void AudioManager::Shutdown() { void AudioManager::Shutdown() {
events.SetAudioEvent(Event::Type::Max, true);
if (thread.joinable()) { if (thread.joinable()) {
thread.request_stop(); thread.request_stop();
events.SetAudioEvent(Event::Type::Max, true);
thread.join(); thread.join();
} }
} }
+1 -3
View File
@@ -39,9 +39,7 @@ class AudioManager {
public: public:
explicit AudioManager(); explicit AudioManager();
/** /// @brief Shutdown the audio manager.
* Shutdown the audio manager.
*/
void Shutdown(); void Shutdown();
/** /**
@@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
@@ -35,7 +38,7 @@ void CircularBufferSinkInfo::Update(BehaviorInfo::ErrorInfo& error_info, OutStat
auto current_params{reinterpret_cast<CircularBufferInParameter*>(parameter.data())}; auto current_params{reinterpret_cast<CircularBufferInParameter*>(parameter.data())};
auto current_state{reinterpret_cast<CircularBufferState*>(state.data())}; auto current_state{reinterpret_cast<CircularBufferState*>(state.data())};
if (in_use == buffer_params->in_use && !buffer_unmapped) { if (in_use == bool(buffer_params->in_use) && !buffer_unmapped) {
error_info.error_code = ResultSuccess; error_info.error_code = ResultSuccess;
error_info.address = CpuAddr(0); error_info.address = CpuAddr(0);
out_status.writeOffset = current_state->last_pos2; out_status.writeOffset = current_state->last_pos2;
+19 -20
View File
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project // SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
@@ -31,10 +31,10 @@ public:
}; };
struct DeviceInParameter { struct DeviceInParameter {
/* 0x000 */ char name[0x100]; /* 0x000 */ u8 name[0x100];
/* 0x100 */ u32 input_count; /* 0x100 */ u32 input_count;
/* 0x104 */ std::array<s8, MaxChannels> inputs; /* 0x104 */ std::array<s8, MaxChannels> inputs;
/* 0x10A */ char unk10A[0x1]; /* 0x10A */ u8 unk10A[0x1];
/* 0x10B */ bool downmix_enabled; /* 0x10B */ bool downmix_enabled;
/* 0x10C */ std::array<f32, 4> downmix_coeff; /* 0x10C */ std::array<f32, 4> downmix_coeff;
}; };
@@ -43,7 +43,7 @@ public:
struct DeviceState { struct DeviceState {
/* 0x00 */ UpsamplerInfo* upsampler_info; /* 0x00 */ UpsamplerInfo* upsampler_info;
/* 0x08 */ std::array<Common::FixedPoint<16, 16>, 4> downmix_coeff; /* 0x08 */ std::array<Common::FixedPoint<16, 16>, 4> downmix_coeff;
/* 0x18 */ char unk18[0x18]; /* 0x18 */ u8 unk18[0x18];
}; };
static_assert(sizeof(DeviceState) == 0x30, "DeviceState has the wrong size!"); static_assert(sizeof(DeviceState) == 0x30, "DeviceState has the wrong size!");
@@ -55,8 +55,8 @@ public:
/* 0x14 */ u32 previous_pos; /* 0x14 */ u32 previous_pos;
/* 0x18 */ SampleFormat format; /* 0x18 */ SampleFormat format;
/* 0x1C */ std::array<s8, MaxChannels> inputs; /* 0x1C */ std::array<s8, MaxChannels> inputs;
/* 0x22 */ bool in_use; /* 0x22 */ u8 in_use;
/* 0x23 */ char unk23[0x5]; /* 0x23 */ u8 unk23[0x5];
}; };
static_assert(sizeof(CircularBufferInParameter) == 0x28, static_assert(sizeof(CircularBufferInParameter) == 0x28,
"CircularBufferInParameter has the wrong size!"); "CircularBufferInParameter has the wrong size!");
@@ -65,16 +65,16 @@ public:
/* 0x00 */ u32 last_pos2; /* 0x00 */ u32 last_pos2;
/* 0x04 */ s32 current_pos; /* 0x04 */ s32 current_pos;
/* 0x08 */ u32 last_pos; /* 0x08 */ u32 last_pos;
/* 0x0C */ char unk0C[0x4]; /* 0x0C */ u8 unk0C[0x4];
/* 0x10 */ AddressInfo address_info; /* 0x10 */ AddressInfo address_info;
}; };
static_assert(sizeof(CircularBufferState) == 0x30, "CircularBufferState has the wrong size!"); static_assert(sizeof(CircularBufferState) == 0x30, "CircularBufferState has the wrong size!");
struct InParameter { struct InParameter {
/* 0x000 */ Type type; /* 0x000 */ Type type;
/* 0x001 */ bool in_use; /* 0x001 */ u8 in_use;
/* 0x004 */ u32 node_id; /* 0x004 */ u32 node_id;
/* 0x008 */ char unk08[0x18]; /* 0x008 */ u8 unk08[0x18];
union { union {
/* 0x020 */ DeviceInParameter device; /* 0x020 */ DeviceInParameter device;
/* 0x020 */ CircularBufferInParameter circular_buffer; /* 0x020 */ CircularBufferInParameter circular_buffer;
@@ -84,7 +84,7 @@ public:
struct OutStatus { struct OutStatus {
/* 0x00 */ u32 writeOffset; /* 0x00 */ u32 writeOffset;
/* 0x04 */ char unk04[0x1C]; /* 0x04 */ u8 unk04[0x1C];
}; // size == 0x20 }; // size == 0x20
static_assert(sizeof(OutStatus) == 0x20, "SinkInfoBase::OutStatus has the wrong size!"); static_assert(sizeof(OutStatus) == 0x20, "SinkInfoBase::OutStatus has the wrong size!");
@@ -162,19 +162,18 @@ public:
u8* GetParameter(); u8* GetParameter();
protected: protected:
/// Type of this sink
Type type{Type::Invalid};
/// Is this sink in use?
bool in_use{};
/// Is this sink's buffer unmapped? Circular only
bool buffer_unmapped{};
/// Node id for this sink
u32 node_id{};
/// State buffer for this sink /// State buffer for this sink
std::array<u8, (std::max)(sizeof(DeviceState), sizeof(CircularBufferState))> state{}; std::array<u8, (std::max)(sizeof(DeviceState), sizeof(CircularBufferState))> state{};
/// Parameter buffer for this sink /// Parameter buffer for this sink
std::array<u8, (std::max)(sizeof(DeviceInParameter), sizeof(CircularBufferInParameter))> std::array<u8, (std::max)(sizeof(DeviceInParameter), sizeof(CircularBufferInParameter))> parameter{};
parameter{}; /// Type of this sink
Type type{Type::Invalid};
/// Node id for this sink
u32 node_id{};
/// Is this sink in use?
bool in_use : 1 = false;
/// Is this sink's buffer unmapped? Circular only
bool buffer_unmapped : 1 = false;
}; };
} // namespace AudioCore::Renderer } // namespace AudioCore::Renderer
+14 -14
View File
@@ -100,8 +100,9 @@ u64 System::GetWorkBufferSize(const AudioRendererParameterInternal& params) {
} }
System::System(Core::System& core_, Kernel::KEvent* adsp_rendered_event_) System::System(Core::System& core_, Kernel::KEvent* adsp_rendered_event_)
: core{core_}, audio_renderer{core.AudioCore().ADSP().AudioRenderer()}, : core{core_}
adsp_rendered_event{adsp_rendered_event_} {} , adsp_rendered_event{adsp_rendered_event_}
{}
Result System::Initialize(const AudioRendererParameterInternal& params, Result System::Initialize(const AudioRendererParameterInternal& params,
Kernel::KTransferMemory* transfer_memory, u64 transfer_memory_size, Kernel::KTransferMemory* transfer_memory, u64 transfer_memory_size,
@@ -406,7 +407,7 @@ void System::Finalize() {
return; return;
} }
if (active) { if (IsActive()) {
Stop(); Stop();
} }
@@ -433,14 +434,12 @@ void System::Start() {
std::scoped_lock l{lock}; std::scoped_lock l{lock};
frames_elapsed = 0; frames_elapsed = 0;
state = State::Started; state = State::Started;
active = true;
} }
void System::Stop() { void System::Stop() {
{ {
std::scoped_lock l{lock}; std::scoped_lock l{lock};
state = State::Stopped; state = State::Stopped;
active = false;
} }
if (execution_mode == ExecutionMode::Auto) { if (execution_mode == ExecutionMode::Auto) {
@@ -480,7 +479,7 @@ Result System::Update(std::span<const u8> input, std::span<u8> performance, std:
return result; return result;
} }
result = info_updater.UpdateEffects(effect_context, active, memory_pool_workbuffer, result = info_updater.UpdateEffects(effect_context, IsActive(), memory_pool_workbuffer,
memory_pool_count); memory_pool_count);
if (result.IsError()) { if (result.IsError()) {
LOG_ERROR(Service_Audio, "Failed to update Effects!"); LOG_ERROR(Service_Audio, "Failed to update Effects!");
@@ -582,16 +581,16 @@ u32 System::GetRenderingDevice() const {
} }
bool System::IsActive() const { bool System::IsActive() const {
return active; return state == State::Started;
} }
void System::SendCommandToDsp() { void System::SendCommandToDsp() {
std::scoped_lock l{lock}; std::scoped_lock l{lock};
if (initialized) { if (initialized) {
if (active) { if (IsActive()) {
terminate_event.Reset(); terminate_event.Reset();
const auto remaining_command_count{audio_renderer.GetRemainCommandCount(session_id)}; const auto remaining_command_count = core.AudioCore().ADSP().AudioRenderer().GetRemainCommandCount(session_id);
u64 command_size{0}; u64 command_size{0};
if (remaining_command_count) { if (remaining_command_count) {
@@ -618,16 +617,17 @@ void System::SendCommandToDsp() {
auto time_limit{ auto time_limit{
static_cast<u64>((time_limit_percent / 100) * 2'880'000.0 * static_cast<u64>((time_limit_percent / 100) * 2'880'000.0 *
(static_cast<f32>(render_time_limit_percent) / 100.0f))}; (static_cast<f32>(render_time_limit_percent) / 100.0f))};
audio_renderer.SetCommandBuffer(session_id, translated_addr, command_size, time_limit, core.AudioCore().ADSP().AudioRenderer().SetCommandBuffer(session_id,
applet_resource_user_id, process_handle, translated_addr, command_size,
reset_command_buffers); time_limit, applet_resource_user_id,
process_handle, reset_command_buffers);
reset_command_buffers = false; reset_command_buffers = false;
command_buffer_size = command_size; command_buffer_size = command_size;
if (remaining_command_count == 0) { if (remaining_command_count == 0) {
adsp_rendered_event->Signal(core.Kernel()); adsp_rendered_event->Signal(core.Kernel());
} }
} else { } else {
audio_renderer.ClearRemainCommandCount(session_id); core.AudioCore().ADSP().AudioRenderer().ClearRemainCommandCount(session_id);
terminate_event.Set(); terminate_event.Set();
} }
} }
@@ -738,7 +738,7 @@ u64 System::GenerateCommand(std::span<u8> in_command_buffer,
const auto end_time{core.CoreTiming().GetGlobalTimeNs().count()}; const auto end_time{core.CoreTiming().GetGlobalTimeNs().count()};
total_ticks_elapsed += end_time - start_time; total_ticks_elapsed += end_time - start_time;
num_command_lists_generated++; num_command_lists_generated++;
render_start_tick = audio_renderer.GetRenderingStartTick(session_id); render_start_tick = core.AudioCore().ADSP().AudioRenderer().GetRenderingStartTick(session_id);
frames_elapsed++; frames_elapsed++;
return command_buffer.size; return command_buffer.size;
-2
View File
@@ -219,8 +219,6 @@ public:
private: private:
/// Core system /// Core system
Core::System& core; Core::System& core;
/// Reference to the ADSP's AudioRenderer for communication
::AudioCore::ADSP::AudioRenderer::AudioRenderer& audio_renderer;
/// Is this system initialized? /// Is this system initialized?
bool initialized{}; bool initialized{};
/// Is this system currently active? /// Is this system currently active?
+26 -29
View File
@@ -11,73 +11,70 @@
#include "audio_core/renderer/system_manager.h" #include "audio_core/renderer/system_manager.h"
#include "common/thread.h" #include "common/thread.h"
#include "core/core.h" #include "core/core.h"
#include "core/hle/kernel/kernel.h"
#include "core/hle/kernel/k_scheduler.h"
#include "core/core_timing.h" #include "core/core_timing.h"
namespace AudioCore::Renderer { namespace AudioCore::Renderer {
SystemManager::SystemManager(Core::System& core_) SystemManager::SystemManager(Core::System& core_)
: core{core_}, audio_renderer{core.AudioCore().ADSP().AudioRenderer()} {} : core{core_}
{
}
SystemManager::~SystemManager() { SystemManager::~SystemManager() {
Stop(); Stop();
} }
void SystemManager::InitializeUnsafe() { void SystemManager::InitializeUnsafe() {
if (!active) { if (!thread.joinable()) {
active = true; core.AudioCore().ADSP().AudioRenderer().Start();
audio_renderer.Start(); thread = core.Kernel().RunOnHostCoreThread("AudioRenderSystemManager", [this]() {
thread = std::jthread([this](std::stop_token stop_token) { auto& audio_renderer = core.AudioCore().ADSP().AudioRenderer();
Common::SetCurrentThreadName("AudioRenderSystemManager"); auto const stop_token = thread.get_stop_token();
Common::SetCurrentThreadPriority(Common::ThreadPriority::High); while (!stop_token.stop_requested()) {
while (active && !stop_token.stop_requested()) {
{ {
std::scoped_lock l{mutex1}; std::scoped_lock lk{mutex};
for (auto system : systems) for (auto system : systems)
system->SendCommandToDsp(); system->SendCommandToDsp();
} }
audio_renderer.Signal(); audio_renderer.Signal();
audio_renderer.Wait(); audio_renderer.Wait(stop_token);
} }
}); });
} }
} }
void SystemManager::Stop() { void SystemManager::Stop() {
if (active) { core.AudioCore().ADSP().AudioRenderer().Stop();
active = false; if (thread.joinable()) {
thread.request_stop(); thread.request_stop();
thread.join(); thread.join();
audio_renderer.Stop();
} }
} }
bool SystemManager::Add(System& system_) { bool SystemManager::Add(System& system_) {
std::scoped_lock l2{mutex2}; std::scoped_lock lk{mutex};
if (systems.size() + 1 > MaxRendererSessions) { if (systems.size() >= MaxRendererSessions) {
LOG_ERROR(Service_Audio, "Maximum AudioRenderer Systems active, cannot add more!"); LOG_ERROR(Service_Audio, "Maximum AudioRenderer Systems active, cannot add more!");
return false; return false;
} }
{
std::scoped_lock l{mutex1};
if (systems.empty())
InitializeUnsafe();
}
systems.push_back(&system_); systems.push_back(&system_);
if (!systems.empty()) {
InitializeUnsafe();
}
return true; return true;
} }
bool SystemManager::Remove(System& system_) { bool SystemManager::Remove(System& system_) {
std::scoped_lock l2{mutex2}; std::scoped_lock lk{mutex};
{ if (systems.remove(&system_) == 0) {
std::scoped_lock l{mutex1}; LOG_ERROR(Service_Audio, "Failed to remove a render system, it was not found in the list!");
if (systems.remove(&system_) == 0) { return false;
LOG_ERROR(Service_Audio, "Failed to remove a render system, it was not found in the list!");
return false;
}
} }
if (systems.empty()) {
if (systems.empty())
Stop(); Stop();
}
return true; return true;
} }
+2 -8
View File
@@ -75,14 +75,8 @@ private:
std::list<System*> systems{}; std::list<System*> systems{};
/// Main worker thread for generating command lists /// Main worker thread for generating command lists
std::jthread thread; std::jthread thread;
/// Mutex for the systems /// Mutex for the reading and adding/removing systems
std::mutex mutex1{}; std::mutex mutex{};
/// Mutex for adding/removing systems
std::mutex mutex2{};
/// Is the system manager thread active?
std::atomic<bool> active{};
/// Reference to the ADSP's AudioRenderer for communication
::AudioCore::ADSP::AudioRenderer::AudioRenderer& audio_renderer;
}; };
} // namespace AudioCore::Renderer } // namespace AudioCore::Renderer
+2 -6
View File
@@ -62,6 +62,7 @@ add_library(
fs/fs_util.h fs/fs_util.h
fs/path_util.cpp fs/path_util.cpp
fs/path_util.h fs/path_util.h
hash.h
heap_tracker.cpp heap_tracker.cpp
heap_tracker.h heap_tracker.h
hex_util.cpp hex_util.cpp
@@ -75,6 +76,7 @@ add_library(
logging.h logging.h
lz4_compression.cpp lz4_compression.cpp
lz4_compression.h lz4_compression.h
make_unique_for_overwrite.h
math_util.h math_util.h
memory_detect.cpp memory_detect.cpp
memory_detect.h memory_detect.h
@@ -136,8 +138,6 @@ add_library(
uuid.cpp uuid.cpp
uuid.h uuid.h
vector_math.h vector_math.h
zbic_compression.cpp
zbic_compression.h
zstd_compression.cpp zstd_compression.cpp
zstd_compression.h zstd_compression.h
fs/ryujinx_compat.h fs/ryujinx_compat.cpp fs/ryujinx_compat.h fs/ryujinx_compat.cpp
@@ -148,10 +148,6 @@ add_library(
net/net.h net/net.cpp net/net.h net/net.cpp
container/unordered_map.h container/unordered_set.h) container/unordered_map.h container/unordered_set.h)
set_source_files_properties(zbic_compression.cpp PROPERTIES
INCLUDE_DIRECTORIES "${ZBIC_INCLUDE_DIR}"
COMPILE_OPTIONS "$<$<CXX_COMPILER_ID:Clang,GNU>:-Wno-unused-function;-Wno-missing-declarations;-Wno-shadow>")
if(WIN32) if(WIN32)
target_sources(common PRIVATE windows/timer_resolution.cpp target_sources(common PRIVATE windows/timer_resolution.cpp
windows/timer_resolution.h) windows/timer_resolution.h)
+251 -252
View File
@@ -12,94 +12,93 @@
#include "common/android/multiplayer/multiplayer.h" #include "common/android/multiplayer/multiplayer.h"
#include <network/network.h> #include <network/network.h>
static struct {
JavaVM *java_vm;
jclass native_library_class;
jclass disk_cache_progress_class;
jclass load_callback_stage_class;
jclass game_dir_class;
jmethodID game_dir_constructor;
jmethodID exit_emulation_activity;
jmethodID disk_cache_load_progress;
jmethodID on_emulation_started;
jmethodID on_emulation_stopped;
jmethodID on_program_changed;
jmethodID copy_to_storage;
jmethodID file_exists;
jmethodID file_extension;
jclass game_class; static JavaVM *s_java_vm;
jmethodID game_constructor; static jclass s_native_library_class;
jfieldID game_title_field; static jclass s_disk_cache_progress_class;
jfieldID game_path_field; static jclass s_load_callback_stage_class;
jfieldID game_program_id_field; static jclass s_game_dir_class;
jfieldID game_developer_field; static jmethodID s_game_dir_constructor;
jfieldID game_version_field; static jmethodID s_exit_emulation_activity;
jfieldID game_is_homebrew_field; static jmethodID s_disk_cache_load_progress;
static jmethodID s_on_emulation_started;
static jmethodID s_on_emulation_stopped;
static jmethodID s_on_program_changed;
static jmethodID s_copy_to_storage;
static jmethodID s_file_exists;
static jmethodID s_file_extension;
jclass string_class; static jclass s_game_class;
jclass pair_class; static jmethodID s_game_constructor;
jmethodID pair_constructor; static jfieldID s_game_title_field;
jfieldID pair_first_field; static jfieldID s_game_path_field;
jfieldID pair_second_field; static jfieldID s_game_program_id_field;
static jfieldID s_game_developer_field;
static jfieldID s_game_version_field;
static jfieldID s_game_is_homebrew_field;
jclass overlay_control_data_class; static jclass s_string_class;
jmethodID overlay_control_data_constructor; static jclass s_pair_class;
jfieldID overlay_control_data_id_field; static jmethodID s_pair_constructor;
jfieldID overlay_control_data_enabled_field; static jfieldID s_pair_first_field;
jfieldID overlay_control_data_individual_scale_field; static jfieldID s_pair_second_field;
jfieldID overlay_control_data_landscape_position_field;
jfieldID overlay_control_data_portrait_position_field;
jfieldID overlay_control_data_foldable_position_field;
jclass patch_class; static jclass s_overlay_control_data_class;
jmethodID patch_constructor; static jmethodID s_overlay_control_data_constructor;
jfieldID patch_enabled_field; static jfieldID s_overlay_control_data_id_field;
jfieldID patch_name_field; static jfieldID s_overlay_control_data_enabled_field;
jfieldID patch_version_field; static jfieldID s_overlay_control_data_individual_scale_field;
jfieldID patch_type_field; static jfieldID s_overlay_control_data_landscape_position_field;
jfieldID patch_program_id_field; static jfieldID s_overlay_control_data_portrait_position_field;
jfieldID patch_title_id_field; static jfieldID s_overlay_control_data_foldable_position_field;
jclass double_class; static jclass s_patch_class;
jmethodID double_constructor; static jmethodID s_patch_constructor;
jmethodID double_value_method; static jfieldID s_patch_enabled_field;
static jfieldID s_patch_name_field;
static jfieldID s_patch_version_field;
static jfieldID s_patch_type_field;
static jfieldID s_patch_program_id_field;
static jfieldID s_patch_title_id_field;
jclass integer_class; static jclass s_double_class;
jmethodID integer_constructor; static jmethodID s_double_constructor;
jmethodID integer_value_method; static jmethodID s_double_value_method;
jclass boolean_class; static jclass s_integer_class;
jmethodID boolean_constructor; static jmethodID s_integer_constructor;
jmethodID boolean_value_method; static jmethodID s_integer_value_method;
jclass player_input_class; static jclass s_boolean_class;
jmethodID player_input_constructor; static jmethodID s_boolean_constructor;
jfieldID player_input_connected_field; static jmethodID s_boolean_value_method;
jfieldID player_input_buttons_field;
jfieldID player_input_analogs_field;
jfieldID player_input_motions_field;
jfieldID player_input_vibration_enabled_field;
jfieldID player_input_vibration_strength_field;
jfieldID player_input_body_color_left_field;
jfieldID player_input_body_color_right_field;
jfieldID player_input_button_color_left_field;
jfieldID player_input_button_color_right_field;
jfieldID player_input_profile_name_field;
jfieldID player_input_use_system_vibrator_field;
jclass yuzu_input_device_interface; static jclass s_player_input_class;
jmethodID yuzu_input_device_get_name; static jmethodID s_player_input_constructor;
jmethodID yuzu_input_device_get_guid; static jfieldID s_player_input_connected_field;
jmethodID yuzu_input_device_get_port; static jfieldID s_player_input_buttons_field;
jmethodID yuzu_input_device_get_supports_vibration; static jfieldID s_player_input_analogs_field;
jmethodID yuzu_input_device_vibrate; static jfieldID s_player_input_motions_field;
jmethodID yuzu_input_device_get_axes; static jfieldID s_player_input_vibration_enabled_field;
jmethodID yuzu_input_device_has_keys; static jfieldID s_player_input_vibration_strength_field;
static jfieldID s_player_input_body_color_left_field;
static jfieldID s_player_input_body_color_right_field;
static jfieldID s_player_input_button_color_left_field;
static jfieldID s_player_input_button_color_right_field;
static jfieldID s_player_input_profile_name_field;
static jfieldID s_player_input_use_system_vibrator_field;
jmethodID add_netplay_message; static jclass s_yuzu_input_device_interface;
jmethodID clear_chat; static jmethodID s_yuzu_input_device_get_name;
} state; static jmethodID s_yuzu_input_device_get_guid;
static jmethodID s_yuzu_input_device_get_port;
static jmethodID s_yuzu_input_device_get_supports_vibration;
static jmethodID s_yuzu_input_device_vibrate;
static jmethodID s_yuzu_input_device_get_axes;
static jmethodID s_yuzu_input_device_has_keys;
static jmethodID s_add_netplay_message;
static jmethodID s_clear_chat;
static constexpr jint JNI_VERSION = JNI_VERSION_1_6; static constexpr jint JNI_VERSION = JNI_VERSION_1_6;
@@ -107,14 +106,14 @@ namespace Common::Android {
JNIEnv *GetEnvForThread() { JNIEnv *GetEnvForThread() {
thread_local static struct OwnedEnv { thread_local static struct OwnedEnv {
OwnedEnv() { OwnedEnv() {
status = state.java_vm->GetEnv(reinterpret_cast<void **>(&env), JNI_VERSION_1_6); status = s_java_vm->GetEnv(reinterpret_cast<void **>(&env), JNI_VERSION_1_6);
if (status == JNI_EDETACHED) if (status == JNI_EDETACHED)
state.java_vm->AttachCurrentThread(&env, nullptr); s_java_vm->AttachCurrentThread(&env, nullptr);
} }
~OwnedEnv() { ~OwnedEnv() {
if (status == JNI_EDETACHED) if (status == JNI_EDETACHED)
state.java_vm->DetachCurrentThread(); s_java_vm->DetachCurrentThread();
} }
int status; int status;
@@ -124,303 +123,303 @@ namespace Common::Android {
} }
jclass GetNativeLibraryClass() { jclass GetNativeLibraryClass() {
return state.native_library_class; return s_native_library_class;
} }
jclass GetDiskCacheProgressClass() { jclass GetDiskCacheProgressClass() {
return state.disk_cache_progress_class; return s_disk_cache_progress_class;
} }
jclass GetDiskCacheLoadCallbackStageClass() { jclass GetDiskCacheLoadCallbackStageClass() {
return state.load_callback_stage_class; return s_load_callback_stage_class;
} }
jclass GetGameDirClass() { jclass GetGameDirClass() {
return state.game_dir_class; return s_game_dir_class;
} }
jmethodID GetGameDirConstructor() { jmethodID GetGameDirConstructor() {
return state.game_dir_constructor; return s_game_dir_constructor;
} }
jmethodID GetExitEmulationActivity() { jmethodID GetExitEmulationActivity() {
return state.exit_emulation_activity; return s_exit_emulation_activity;
} }
jmethodID GetDiskCacheLoadProgress() { jmethodID GetDiskCacheLoadProgress() {
return state.disk_cache_load_progress; return s_disk_cache_load_progress;
} }
jmethodID GetCopyToStorage() { jmethodID GetCopyToStorage() {
return state.copy_to_storage; return s_copy_to_storage;
} }
jmethodID GetFileExists() { jmethodID GetFileExists() {
return state.file_exists; return s_file_exists;
} }
jmethodID GetFileExtension() { jmethodID GetFileExtension() {
return state.file_extension; return s_file_extension;
} }
jmethodID GetOnEmulationStarted() { jmethodID GetOnEmulationStarted() {
return state.on_emulation_started; return s_on_emulation_started;
} }
jmethodID GetOnEmulationStopped() { jmethodID GetOnEmulationStopped() {
return state.on_emulation_stopped; return s_on_emulation_stopped;
} }
jmethodID GetOnProgramChanged() { jmethodID GetOnProgramChanged() {
return state.on_program_changed; return s_on_program_changed;
} }
jclass GetGameClass() { jclass GetGameClass() {
return state.game_class; return s_game_class;
} }
jmethodID GetGameConstructor() { jmethodID GetGameConstructor() {
return state.game_constructor; return s_game_constructor;
} }
jfieldID GetGameTitleField() { jfieldID GetGameTitleField() {
return state.game_title_field; return s_game_title_field;
} }
jfieldID GetGamePathField() { jfieldID GetGamePathField() {
return state.game_path_field; return s_game_path_field;
} }
jfieldID GetGameProgramIdField() { jfieldID GetGameProgramIdField() {
return state.game_program_id_field; return s_game_program_id_field;
} }
jfieldID GetGameDeveloperField() { jfieldID GetGameDeveloperField() {
return state.game_developer_field; return s_game_developer_field;
} }
jfieldID GetGameVersionField() { jfieldID GetGameVersionField() {
return state.game_version_field; return s_game_version_field;
} }
jfieldID GetGameIsHomebrewField() { jfieldID GetGameIsHomebrewField() {
return state.game_is_homebrew_field; return s_game_is_homebrew_field;
} }
jclass GetStringClass() { jclass GetStringClass() {
return state.string_class; return s_string_class;
} }
jclass GetPairClass() { jclass GetPairClass() {
return state.pair_class; return s_pair_class;
} }
jmethodID GetPairConstructor() { jmethodID GetPairConstructor() {
return state.pair_constructor; return s_pair_constructor;
} }
jfieldID GetPairFirstField() { jfieldID GetPairFirstField() {
return state.pair_first_field; return s_pair_first_field;
} }
jfieldID GetPairSecondField() { jfieldID GetPairSecondField() {
return state.pair_second_field; return s_pair_second_field;
} }
jclass GetOverlayControlDataClass() { jclass GetOverlayControlDataClass() {
return state.overlay_control_data_class; return s_overlay_control_data_class;
} }
jmethodID GetOverlayControlDataConstructor() { jmethodID GetOverlayControlDataConstructor() {
return state.overlay_control_data_constructor; return s_overlay_control_data_constructor;
} }
jfieldID GetOverlayControlDataIdField() { jfieldID GetOverlayControlDataIdField() {
return state.overlay_control_data_id_field; return s_overlay_control_data_id_field;
} }
jfieldID GetOverlayControlDataEnabledField() { jfieldID GetOverlayControlDataEnabledField() {
return state.overlay_control_data_enabled_field; return s_overlay_control_data_enabled_field;
} }
jfieldID GetOverlayControlDataIndividualScaleField() { jfieldID GetOverlayControlDataIndividualScaleField() {
return state.overlay_control_data_individual_scale_field; return s_overlay_control_data_individual_scale_field;
} }
jfieldID GetOverlayControlDataLandscapePositionField() { jfieldID GetOverlayControlDataLandscapePositionField() {
return state.overlay_control_data_landscape_position_field; return s_overlay_control_data_landscape_position_field;
} }
jfieldID GetOverlayControlDataPortraitPositionField() { jfieldID GetOverlayControlDataPortraitPositionField() {
return state.overlay_control_data_portrait_position_field; return s_overlay_control_data_portrait_position_field;
} }
jfieldID GetOverlayControlDataFoldablePositionField() { jfieldID GetOverlayControlDataFoldablePositionField() {
return state.overlay_control_data_foldable_position_field; return s_overlay_control_data_foldable_position_field;
} }
jclass GetPatchClass() { jclass GetPatchClass() {
return state.patch_class; return s_patch_class;
} }
jmethodID GetPatchConstructor() { jmethodID GetPatchConstructor() {
return state.patch_constructor; return s_patch_constructor;
} }
jfieldID GetPatchEnabledField() { jfieldID GetPatchEnabledField() {
return state.patch_enabled_field; return s_patch_enabled_field;
} }
jfieldID GetPatchNameField() { jfieldID GetPatchNameField() {
return state.patch_name_field; return s_patch_name_field;
} }
jfieldID GetPatchVersionField() { jfieldID GetPatchVersionField() {
return state.patch_version_field; return s_patch_version_field;
} }
jfieldID GetPatchTypeField() { jfieldID GetPatchTypeField() {
return state.patch_type_field; return s_patch_type_field;
} }
jfieldID GetPatchProgramIdField() { jfieldID GetPatchProgramIdField() {
return state.patch_program_id_field; return s_patch_program_id_field;
} }
jfieldID GetPatchTitleIdField() { jfieldID GetPatchTitleIdField() {
return state.patch_title_id_field; return s_patch_title_id_field;
} }
jclass GetDoubleClass() { jclass GetDoubleClass() {
return state.double_class; return s_double_class;
} }
jmethodID GetDoubleConstructor() { jmethodID GetDoubleConstructor() {
return state.double_constructor; return s_double_constructor;
} }
jmethodID GetDoubleValueMethod() { jmethodID GetDoubleValueMethod() {
return state.double_value_method; return s_double_value_method;
} }
jclass GetIntegerClass() { jclass GetIntegerClass() {
return state.integer_class; return s_integer_class;
} }
jmethodID GetIntegerConstructor() { jmethodID GetIntegerConstructor() {
return state.integer_constructor; return s_integer_constructor;
} }
jmethodID GetIntegerValueMethod() { jmethodID GetIntegerValueMethod() {
return state.integer_value_method; return s_integer_value_method;
} }
jclass GetBooleanClass() { jclass GetBooleanClass() {
return state.boolean_class; return s_boolean_class;
} }
jmethodID GetBooleanConstructor() { jmethodID GetBooleanConstructor() {
return state.boolean_constructor; return s_boolean_constructor;
} }
jmethodID GetBooleanValueMethod() { jmethodID GetBooleanValueMethod() {
return state.boolean_value_method; return s_boolean_value_method;
} }
jclass GetPlayerInputClass() { jclass GetPlayerInputClass() {
return state.player_input_class; return s_player_input_class;
} }
jmethodID GetPlayerInputConstructor() { jmethodID GetPlayerInputConstructor() {
return state.player_input_constructor; return s_player_input_constructor;
} }
jfieldID GetPlayerInputConnectedField() { jfieldID GetPlayerInputConnectedField() {
return state.player_input_connected_field; return s_player_input_connected_field;
} }
jfieldID GetPlayerInputButtonsField() { jfieldID GetPlayerInputButtonsField() {
return state.player_input_buttons_field; return s_player_input_buttons_field;
} }
jfieldID GetPlayerInputAnalogsField() { jfieldID GetPlayerInputAnalogsField() {
return state.player_input_analogs_field; return s_player_input_analogs_field;
} }
jfieldID GetPlayerInputMotionsField() { jfieldID GetPlayerInputMotionsField() {
return state.player_input_motions_field; return s_player_input_motions_field;
} }
jfieldID GetPlayerInputVibrationEnabledField() { jfieldID GetPlayerInputVibrationEnabledField() {
return state.player_input_vibration_enabled_field; return s_player_input_vibration_enabled_field;
} }
jfieldID GetPlayerInputVibrationStrengthField() { jfieldID GetPlayerInputVibrationStrengthField() {
return state.player_input_vibration_strength_field; return s_player_input_vibration_strength_field;
} }
jfieldID GetPlayerInputBodyColorLeftField() { jfieldID GetPlayerInputBodyColorLeftField() {
return state.player_input_body_color_left_field; return s_player_input_body_color_left_field;
} }
jfieldID GetPlayerInputBodyColorRightField() { jfieldID GetPlayerInputBodyColorRightField() {
return state.player_input_body_color_right_field; return s_player_input_body_color_right_field;
} }
jfieldID GetPlayerInputButtonColorLeftField() { jfieldID GetPlayerInputButtonColorLeftField() {
return state.player_input_button_color_left_field; return s_player_input_button_color_left_field;
} }
jfieldID GetPlayerInputButtonColorRightField() { jfieldID GetPlayerInputButtonColorRightField() {
return state.player_input_button_color_right_field; return s_player_input_button_color_right_field;
} }
jfieldID GetPlayerInputProfileNameField() { jfieldID GetPlayerInputProfileNameField() {
return state.player_input_profile_name_field; return s_player_input_profile_name_field;
} }
jfieldID GetPlayerInputUseSystemVibratorField() { jfieldID GetPlayerInputUseSystemVibratorField() {
return state.player_input_use_system_vibrator_field; return s_player_input_use_system_vibrator_field;
} }
jclass GetYuzuInputDeviceInterface() { jclass GetYuzuInputDeviceInterface() {
return state.yuzu_input_device_interface; return s_yuzu_input_device_interface;
} }
jmethodID GetYuzuDeviceGetName() { jmethodID GetYuzuDeviceGetName() {
return state.yuzu_input_device_get_name; return s_yuzu_input_device_get_name;
} }
jmethodID GetYuzuDeviceGetGUID() { jmethodID GetYuzuDeviceGetGUID() {
return state.yuzu_input_device_get_guid; return s_yuzu_input_device_get_guid;
} }
jmethodID GetYuzuDeviceGetPort() { jmethodID GetYuzuDeviceGetPort() {
return state.yuzu_input_device_get_port; return s_yuzu_input_device_get_port;
} }
jmethodID GetYuzuDeviceGetSupportsVibration() { jmethodID GetYuzuDeviceGetSupportsVibration() {
return state.yuzu_input_device_get_supports_vibration; return s_yuzu_input_device_get_supports_vibration;
} }
jmethodID GetYuzuDeviceVibrate() { jmethodID GetYuzuDeviceVibrate() {
return state.yuzu_input_device_vibrate; return s_yuzu_input_device_vibrate;
} }
jmethodID GetYuzuDeviceGetAxes() { jmethodID GetYuzuDeviceGetAxes() {
return state.yuzu_input_device_get_axes; return s_yuzu_input_device_get_axes;
} }
jmethodID GetYuzuDeviceHasKeys() { jmethodID GetYuzuDeviceHasKeys() {
return state.yuzu_input_device_has_keys; return s_yuzu_input_device_has_keys;
} }
jmethodID GetAddNetPlayMessage() { jmethodID GetAddNetPlayMessage() {
return state.add_netplay_message; return s_add_netplay_message;
} }
jmethodID ClearChat() { jmethodID ClearChat() {
return state.clear_chat; return s_clear_chat;
} }
#ifdef __cplusplus #ifdef __cplusplus
@@ -437,20 +436,20 @@ namespace Common::Android {
// UnInitialize Android Storage // UnInitialize Android Storage
Common::FS::Android::UnRegisterCallbacks(); Common::FS::Android::UnRegisterCallbacks();
env->DeleteGlobalRef(state.native_library_class); env->DeleteGlobalRef(s_native_library_class);
env->DeleteGlobalRef(state.disk_cache_progress_class); env->DeleteGlobalRef(s_disk_cache_progress_class);
env->DeleteGlobalRef(state.load_callback_stage_class); env->DeleteGlobalRef(s_load_callback_stage_class);
env->DeleteGlobalRef(state.game_dir_class); env->DeleteGlobalRef(s_game_dir_class);
env->DeleteGlobalRef(state.game_class); env->DeleteGlobalRef(s_game_class);
env->DeleteGlobalRef(state.string_class); env->DeleteGlobalRef(s_string_class);
env->DeleteGlobalRef(state.pair_class); env->DeleteGlobalRef(s_pair_class);
env->DeleteGlobalRef(state.overlay_control_data_class); env->DeleteGlobalRef(s_overlay_control_data_class);
env->DeleteGlobalRef(state.patch_class); env->DeleteGlobalRef(s_patch_class);
env->DeleteGlobalRef(state.double_class); env->DeleteGlobalRef(s_double_class);
env->DeleteGlobalRef(state.integer_class); env->DeleteGlobalRef(s_integer_class);
env->DeleteGlobalRef(state.boolean_class); env->DeleteGlobalRef(s_boolean_class);
env->DeleteGlobalRef(state.player_input_class); env->DeleteGlobalRef(s_player_input_class);
env->DeleteGlobalRef(state.yuzu_input_device_interface); env->DeleteGlobalRef(s_yuzu_input_device_interface);
// UnInitialize applets // UnInitialize applets
SoftwareKeyboard::CleanupJNI(env); SoftwareKeyboard::CleanupJNI(env);
@@ -464,7 +463,7 @@ namespace Common::Android {
#endif #endif
void Initialize(JavaVM* vm, JNIEnv *env) { void Initialize(JavaVM* vm, JNIEnv *env) {
state.java_vm = vm; s_java_vm = vm;
InitFFmpegOnLoad(vm); InitFFmpegOnLoad(vm);
if (env->ExceptionCheck()) { if (env->ExceptionCheck()) {
@@ -473,169 +472,169 @@ void Initialize(JavaVM* vm, JNIEnv *env) {
// Initialize Java classes // Initialize Java classes
const jclass native_library_class = env->FindClass("org/yuzu/yuzu_emu/NativeLibrary"); const jclass native_library_class = env->FindClass("org/yuzu/yuzu_emu/NativeLibrary");
state.native_library_class = reinterpret_cast<jclass>(env->NewGlobalRef(native_library_class)); s_native_library_class = reinterpret_cast<jclass>(env->NewGlobalRef(native_library_class));
state.disk_cache_progress_class = reinterpret_cast<jclass>(env->NewGlobalRef( s_disk_cache_progress_class = reinterpret_cast<jclass>(env->NewGlobalRef(
env->FindClass("org/yuzu/yuzu_emu/disk_shader_cache/DiskShaderCacheProgress"))); env->FindClass("org/yuzu/yuzu_emu/disk_shader_cache/DiskShaderCacheProgress")));
state.load_callback_stage_class = reinterpret_cast<jclass>(env->NewGlobalRef(env->FindClass( s_load_callback_stage_class = reinterpret_cast<jclass>(env->NewGlobalRef(env->FindClass(
"org/yuzu/yuzu_emu/disk_shader_cache/DiskShaderCacheProgress$LoadCallbackStage"))); "org/yuzu/yuzu_emu/disk_shader_cache/DiskShaderCacheProgress$LoadCallbackStage")));
const jclass game_dir_class = env->FindClass("org/yuzu/yuzu_emu/model/GameDir"); const jclass game_dir_class = env->FindClass("org/yuzu/yuzu_emu/model/GameDir");
state.game_dir_class = reinterpret_cast<jclass>(env->NewGlobalRef(game_dir_class)); s_game_dir_class = reinterpret_cast<jclass>(env->NewGlobalRef(game_dir_class));
state.game_dir_constructor = env->GetMethodID(game_dir_class, "<init>", s_game_dir_constructor = env->GetMethodID(game_dir_class, "<init>",
"(Ljava/lang/String;Z)V"); "(Ljava/lang/String;Z)V");
env->DeleteLocalRef(game_dir_class); env->DeleteLocalRef(game_dir_class);
// Initialize methods // Initialize methods
state.exit_emulation_activity = s_exit_emulation_activity =
env->GetStaticMethodID(state.native_library_class, "exitEmulationActivity", "(I)V"); env->GetStaticMethodID(s_native_library_class, "exitEmulationActivity", "(I)V");
state.disk_cache_load_progress = s_disk_cache_load_progress =
env->GetStaticMethodID(state.disk_cache_progress_class, "loadProgress", "(III)V"); env->GetStaticMethodID(s_disk_cache_progress_class, "loadProgress", "(III)V");
state.copy_to_storage = env->GetStaticMethodID(state.native_library_class, "copyFileToStorage", s_copy_to_storage = env->GetStaticMethodID(s_native_library_class, "copyFileToStorage",
"(Ljava/lang/String;Ljava/lang/String;)Z"); "(Ljava/lang/String;Ljava/lang/String;)Z");
state.file_exists = env->GetStaticMethodID(state.native_library_class, "exists", s_file_exists = env->GetStaticMethodID(s_native_library_class, "exists",
"(Ljava/lang/String;)Z"); "(Ljava/lang/String;)Z");
state.file_extension = env->GetStaticMethodID(state.native_library_class, "getFileExtension", s_file_extension = env->GetStaticMethodID(s_native_library_class, "getFileExtension",
"(Ljava/lang/String;)Ljava/lang/String;"); "(Ljava/lang/String;)Ljava/lang/String;");
state.on_emulation_started = s_on_emulation_started =
env->GetStaticMethodID(state.native_library_class, "onEmulationStarted", "()V"); env->GetStaticMethodID(s_native_library_class, "onEmulationStarted", "()V");
state.on_emulation_stopped = s_on_emulation_stopped =
env->GetStaticMethodID(state.native_library_class, "onEmulationStopped", "(I)V"); env->GetStaticMethodID(s_native_library_class, "onEmulationStopped", "(I)V");
state.on_program_changed = s_on_program_changed =
env->GetStaticMethodID(state.native_library_class, "onProgramChanged", "(I)V"); env->GetStaticMethodID(s_native_library_class, "onProgramChanged", "(I)V");
const jclass game_class = env->FindClass("org/yuzu/yuzu_emu/model/Game"); const jclass game_class = env->FindClass("org/yuzu/yuzu_emu/model/Game");
state.game_class = reinterpret_cast<jclass>(env->NewGlobalRef(game_class)); s_game_class = reinterpret_cast<jclass>(env->NewGlobalRef(game_class));
state.game_constructor = env->GetMethodID(game_class, "<init>", s_game_constructor = env->GetMethodID(game_class, "<init>",
"(Ljava/lang/String;Ljava/lang/String;Ljava/lang/" "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/"
"String;Ljava/lang/String;Ljava/lang/String;Z)V"); "String;Ljava/lang/String;Ljava/lang/String;Z)V");
state.game_title_field = env->GetFieldID(game_class, "title", "Ljava/lang/String;"); s_game_title_field = env->GetFieldID(game_class, "title", "Ljava/lang/String;");
state.game_path_field = env->GetFieldID(game_class, "path", "Ljava/lang/String;"); s_game_path_field = env->GetFieldID(game_class, "path", "Ljava/lang/String;");
state.game_program_id_field = env->GetFieldID(game_class, "programId", "Ljava/lang/String;"); s_game_program_id_field = env->GetFieldID(game_class, "programId", "Ljava/lang/String;");
state.game_developer_field = env->GetFieldID(game_class, "developer", "Ljava/lang/String;"); s_game_developer_field = env->GetFieldID(game_class, "developer", "Ljava/lang/String;");
state.game_version_field = env->GetFieldID(game_class, "version", "Ljava/lang/String;"); s_game_version_field = env->GetFieldID(game_class, "version", "Ljava/lang/String;");
state.game_is_homebrew_field = env->GetFieldID(game_class, "isHomebrew", "Z"); s_game_is_homebrew_field = env->GetFieldID(game_class, "isHomebrew", "Z");
env->DeleteLocalRef(game_class); env->DeleteLocalRef(game_class);
const jclass string_class = env->FindClass("java/lang/String"); const jclass string_class = env->FindClass("java/lang/String");
state.string_class = reinterpret_cast<jclass>(env->NewGlobalRef(string_class)); s_string_class = reinterpret_cast<jclass>(env->NewGlobalRef(string_class));
env->DeleteLocalRef(string_class); env->DeleteLocalRef(string_class);
const jclass pair_class = env->FindClass("kotlin/Pair"); const jclass pair_class = env->FindClass("kotlin/Pair");
state.pair_class = reinterpret_cast<jclass>(env->NewGlobalRef(pair_class)); s_pair_class = reinterpret_cast<jclass>(env->NewGlobalRef(pair_class));
state.pair_constructor = s_pair_constructor =
env->GetMethodID(pair_class, "<init>", "(Ljava/lang/Object;Ljava/lang/Object;)V"); env->GetMethodID(pair_class, "<init>", "(Ljava/lang/Object;Ljava/lang/Object;)V");
state.pair_first_field = env->GetFieldID(pair_class, "first", "Ljava/lang/Object;"); s_pair_first_field = env->GetFieldID(pair_class, "first", "Ljava/lang/Object;");
state.pair_second_field = env->GetFieldID(pair_class, "second", "Ljava/lang/Object;"); s_pair_second_field = env->GetFieldID(pair_class, "second", "Ljava/lang/Object;");
env->DeleteLocalRef(pair_class); env->DeleteLocalRef(pair_class);
const jclass overlay_control_data_class = const jclass overlay_control_data_class =
env->FindClass("org/yuzu/yuzu_emu/overlay/model/OverlayControlData"); env->FindClass("org/yuzu/yuzu_emu/overlay/model/OverlayControlData");
state.overlay_control_data_class = s_overlay_control_data_class =
reinterpret_cast<jclass>(env->NewGlobalRef(overlay_control_data_class)); reinterpret_cast<jclass>(env->NewGlobalRef(overlay_control_data_class));
state.overlay_control_data_constructor = s_overlay_control_data_constructor =
env->GetMethodID(overlay_control_data_class, "<init>", env->GetMethodID(overlay_control_data_class, "<init>",
"(Ljava/lang/String;ZLkotlin/Pair;Lkotlin/Pair;Lkotlin/Pair;F)V"); "(Ljava/lang/String;ZLkotlin/Pair;Lkotlin/Pair;Lkotlin/Pair;F)V");
state.overlay_control_data_id_field = s_overlay_control_data_id_field =
env->GetFieldID(overlay_control_data_class, "id", "Ljava/lang/String;"); env->GetFieldID(overlay_control_data_class, "id", "Ljava/lang/String;");
state.overlay_control_data_enabled_field = s_overlay_control_data_enabled_field =
env->GetFieldID(overlay_control_data_class, "enabled", "Z"); env->GetFieldID(overlay_control_data_class, "enabled", "Z");
state.overlay_control_data_landscape_position_field = s_overlay_control_data_landscape_position_field =
env->GetFieldID(overlay_control_data_class, "landscapePosition", "Lkotlin/Pair;"); env->GetFieldID(overlay_control_data_class, "landscapePosition", "Lkotlin/Pair;");
state.overlay_control_data_portrait_position_field = s_overlay_control_data_portrait_position_field =
env->GetFieldID(overlay_control_data_class, "portraitPosition", "Lkotlin/Pair;"); env->GetFieldID(overlay_control_data_class, "portraitPosition", "Lkotlin/Pair;");
state.overlay_control_data_foldable_position_field = s_overlay_control_data_foldable_position_field =
env->GetFieldID(overlay_control_data_class, "foldablePosition", "Lkotlin/Pair;"); env->GetFieldID(overlay_control_data_class, "foldablePosition", "Lkotlin/Pair;");
state.overlay_control_data_individual_scale_field = s_overlay_control_data_individual_scale_field =
env->GetFieldID(overlay_control_data_class, "individualScale", "F"); env->GetFieldID(overlay_control_data_class, "individualScale", "F");
env->DeleteLocalRef(overlay_control_data_class); env->DeleteLocalRef(overlay_control_data_class);
const jclass patch_class = env->FindClass("org/yuzu/yuzu_emu/model/Patch"); const jclass patch_class = env->FindClass("org/yuzu/yuzu_emu/model/Patch");
state.patch_class = reinterpret_cast<jclass>(env->NewGlobalRef(patch_class)); s_patch_class = reinterpret_cast<jclass>(env->NewGlobalRef(patch_class));
state.patch_constructor = env->GetMethodID( s_patch_constructor = env->GetMethodID(
patch_class, "<init>", patch_class, "<init>",
"(ZLjava/lang/String;Ljava/lang/String;ILjava/lang/String;Ljava/lang/String;JI)V"); "(ZLjava/lang/String;Ljava/lang/String;ILjava/lang/String;Ljava/lang/String;JI)V");
state.patch_enabled_field = env->GetFieldID(patch_class, "enabled", "Z"); s_patch_enabled_field = env->GetFieldID(patch_class, "enabled", "Z");
state.patch_name_field = env->GetFieldID(patch_class, "name", "Ljava/lang/String;"); s_patch_name_field = env->GetFieldID(patch_class, "name", "Ljava/lang/String;");
state.patch_version_field = env->GetFieldID(patch_class, "version", "Ljava/lang/String;"); s_patch_version_field = env->GetFieldID(patch_class, "version", "Ljava/lang/String;");
state.patch_type_field = env->GetFieldID(patch_class, "type", "I"); s_patch_type_field = env->GetFieldID(patch_class, "type", "I");
state.patch_program_id_field = env->GetFieldID(patch_class, "programId", "Ljava/lang/String;"); s_patch_program_id_field = env->GetFieldID(patch_class, "programId", "Ljava/lang/String;");
state.patch_title_id_field = env->GetFieldID(patch_class, "titleId", "Ljava/lang/String;"); s_patch_title_id_field = env->GetFieldID(patch_class, "titleId", "Ljava/lang/String;");
env->DeleteLocalRef(patch_class); env->DeleteLocalRef(patch_class);
const jclass double_class = env->FindClass("java/lang/Double"); const jclass double_class = env->FindClass("java/lang/Double");
state.double_class = reinterpret_cast<jclass>(env->NewGlobalRef(double_class)); s_double_class = reinterpret_cast<jclass>(env->NewGlobalRef(double_class));
state.double_constructor = env->GetMethodID(double_class, "<init>", "(D)V"); s_double_constructor = env->GetMethodID(double_class, "<init>", "(D)V");
state.double_value_method = env->GetMethodID(double_class, "doubleValue", "()D"); s_double_value_method = env->GetMethodID(double_class, "doubleValue", "()D");
env->DeleteLocalRef(double_class); env->DeleteLocalRef(double_class);
const jclass int_class = env->FindClass("java/lang/Integer"); const jclass int_class = env->FindClass("java/lang/Integer");
state.integer_class = reinterpret_cast<jclass>(env->NewGlobalRef(int_class)); s_integer_class = reinterpret_cast<jclass>(env->NewGlobalRef(int_class));
state.integer_constructor = env->GetMethodID(int_class, "<init>", "(I)V"); s_integer_constructor = env->GetMethodID(int_class, "<init>", "(I)V");
state.integer_value_method = env->GetMethodID(int_class, "intValue", "()I"); s_integer_value_method = env->GetMethodID(int_class, "intValue", "()I");
env->DeleteLocalRef(int_class); env->DeleteLocalRef(int_class);
const jclass boolean_class = env->FindClass("java/lang/Boolean"); const jclass boolean_class = env->FindClass("java/lang/Boolean");
state.boolean_class = reinterpret_cast<jclass>(env->NewGlobalRef(boolean_class)); s_boolean_class = reinterpret_cast<jclass>(env->NewGlobalRef(boolean_class));
state.boolean_constructor = env->GetMethodID(boolean_class, "<init>", "(Z)V"); s_boolean_constructor = env->GetMethodID(boolean_class, "<init>", "(Z)V");
state.boolean_value_method = env->GetMethodID(boolean_class, "booleanValue", "()Z"); s_boolean_value_method = env->GetMethodID(boolean_class, "booleanValue", "()Z");
env->DeleteLocalRef(boolean_class); env->DeleteLocalRef(boolean_class);
const jclass player_input_class = const jclass player_input_class =
env->FindClass("org/yuzu/yuzu_emu/features/input/model/PlayerInput"); env->FindClass("org/yuzu/yuzu_emu/features/input/model/PlayerInput");
state.player_input_class = reinterpret_cast<jclass>(env->NewGlobalRef(player_input_class)); s_player_input_class = reinterpret_cast<jclass>(env->NewGlobalRef(player_input_class));
state.player_input_constructor = env->GetMethodID( s_player_input_constructor = env->GetMethodID(
player_input_class, "<init>", player_input_class, "<init>",
"(Z[Ljava/lang/String;[Ljava/lang/String;[Ljava/lang/String;ZIJJJJLjava/lang/String;Z)V"); "(Z[Ljava/lang/String;[Ljava/lang/String;[Ljava/lang/String;ZIJJJJLjava/lang/String;Z)V");
state.player_input_connected_field = env->GetFieldID(player_input_class, "connected", "Z"); s_player_input_connected_field = env->GetFieldID(player_input_class, "connected", "Z");
state.player_input_buttons_field = s_player_input_buttons_field =
env->GetFieldID(player_input_class, "buttons", "[Ljava/lang/String;"); env->GetFieldID(player_input_class, "buttons", "[Ljava/lang/String;");
state.player_input_analogs_field = s_player_input_analogs_field =
env->GetFieldID(player_input_class, "analogs", "[Ljava/lang/String;"); env->GetFieldID(player_input_class, "analogs", "[Ljava/lang/String;");
state.player_input_motions_field = s_player_input_motions_field =
env->GetFieldID(player_input_class, "motions", "[Ljava/lang/String;"); env->GetFieldID(player_input_class, "motions", "[Ljava/lang/String;");
state.player_input_vibration_enabled_field = s_player_input_vibration_enabled_field =
env->GetFieldID(player_input_class, "vibrationEnabled", "Z"); env->GetFieldID(player_input_class, "vibrationEnabled", "Z");
state.player_input_vibration_strength_field = s_player_input_vibration_strength_field =
env->GetFieldID(player_input_class, "vibrationStrength", "I"); env->GetFieldID(player_input_class, "vibrationStrength", "I");
state.player_input_body_color_left_field = s_player_input_body_color_left_field =
env->GetFieldID(player_input_class, "bodyColorLeft", "J"); env->GetFieldID(player_input_class, "bodyColorLeft", "J");
state.player_input_body_color_right_field = s_player_input_body_color_right_field =
env->GetFieldID(player_input_class, "bodyColorRight", "J"); env->GetFieldID(player_input_class, "bodyColorRight", "J");
state.player_input_button_color_left_field = s_player_input_button_color_left_field =
env->GetFieldID(player_input_class, "buttonColorLeft", "J"); env->GetFieldID(player_input_class, "buttonColorLeft", "J");
state.player_input_button_color_right_field = s_player_input_button_color_right_field =
env->GetFieldID(player_input_class, "buttonColorRight", "J"); env->GetFieldID(player_input_class, "buttonColorRight", "J");
state.player_input_profile_name_field = s_player_input_profile_name_field =
env->GetFieldID(player_input_class, "profileName", "Ljava/lang/String;"); env->GetFieldID(player_input_class, "profileName", "Ljava/lang/String;");
state.player_input_use_system_vibrator_field = s_player_input_use_system_vibrator_field =
env->GetFieldID(player_input_class, "useSystemVibrator", "Z"); env->GetFieldID(player_input_class, "useSystemVibrator", "Z");
env->DeleteLocalRef(player_input_class); env->DeleteLocalRef(player_input_class);
const jclass yuzu_input_device_interface = const jclass yuzu_input_device_interface =
env->FindClass("org/yuzu/yuzu_emu/features/input/YuzuInputDevice"); env->FindClass("org/yuzu/yuzu_emu/features/input/YuzuInputDevice");
state.yuzu_input_device_interface = s_yuzu_input_device_interface =
reinterpret_cast<jclass>(env->NewGlobalRef(yuzu_input_device_interface)); reinterpret_cast<jclass>(env->NewGlobalRef(yuzu_input_device_interface));
state.yuzu_input_device_get_name = s_yuzu_input_device_get_name =
env->GetMethodID(yuzu_input_device_interface, "getName", "()Ljava/lang/String;"); env->GetMethodID(yuzu_input_device_interface, "getName", "()Ljava/lang/String;");
state.yuzu_input_device_get_guid = s_yuzu_input_device_get_guid =
env->GetMethodID(yuzu_input_device_interface, "getGUID", "()Ljava/lang/String;"); env->GetMethodID(yuzu_input_device_interface, "getGUID", "()Ljava/lang/String;");
state.yuzu_input_device_get_port = env->GetMethodID(yuzu_input_device_interface, "getPort", s_yuzu_input_device_get_port = env->GetMethodID(yuzu_input_device_interface, "getPort",
"()I"); "()I");
state.yuzu_input_device_get_supports_vibration = s_yuzu_input_device_get_supports_vibration =
env->GetMethodID(yuzu_input_device_interface, "getSupportsVibration", "()Z"); env->GetMethodID(yuzu_input_device_interface, "getSupportsVibration", "()Z");
state.yuzu_input_device_vibrate = env->GetMethodID(yuzu_input_device_interface, "vibrate", s_yuzu_input_device_vibrate = env->GetMethodID(yuzu_input_device_interface, "vibrate",
"(F)V"); "(F)V");
state.yuzu_input_device_get_axes = s_yuzu_input_device_get_axes =
env->GetMethodID(yuzu_input_device_interface, "getAxes", "()[Ljava/lang/Integer;"); env->GetMethodID(yuzu_input_device_interface, "getAxes", "()[Ljava/lang/Integer;");
state.yuzu_input_device_has_keys = s_yuzu_input_device_has_keys =
env->GetMethodID(yuzu_input_device_interface, "hasKeys", "([I)[Z"); env->GetMethodID(yuzu_input_device_interface, "hasKeys", "([I)[Z");
env->DeleteLocalRef(yuzu_input_device_interface); env->DeleteLocalRef(yuzu_input_device_interface);
state.add_netplay_message = env->GetStaticMethodID(state.native_library_class, "addNetPlayMessage", s_add_netplay_message = env->GetStaticMethodID(s_native_library_class, "addNetPlayMessage",
"(ILjava/lang/String;)V"); "(ILjava/lang/String;)V");
state.clear_chat = env->GetStaticMethodID(state.native_library_class, "clearChat", "()V"); s_clear_chat = env->GetStaticMethodID(s_native_library_class, "clearChat", "()V");
// Initialize Android Storage // Initialize Android Storage
Common::FS::Android::RegisterCallbacks(env, state.native_library_class); Common::FS::Android::RegisterCallbacks(env, s_native_library_class);
// Initialize applets // Initialize applets
Common::Android::SoftwareKeyboard::InitJNI(env); Common::Android::SoftwareKeyboard::InitJNI(env);
+18 -4
View File
@@ -22,22 +22,36 @@ template <typename T>
return std::size_t(sizeof(T) * CHAR_BIT); return std::size_t(sizeof(T) * CHAR_BIT);
} }
template<typename T>
requires std::is_integral_v<T>
[[nodiscard]] constexpr u32 MostSignificantBit(const T value) {
return u32(sizeof(T) * CHAR_BIT - 1 - std::countl_zero(value));
}
template<typename T> template<typename T>
requires std::is_integral_v<T> requires std::is_integral_v<T>
[[nodiscard]] constexpr T Log2Floor(const T value) { [[nodiscard]] constexpr T Log2Floor(const T value) {
return std::bit_width(value) - 1; return T(MostSignificantBit<T>(value));
} }
template<typename T> template<typename T>
requires std::is_integral_v<T> requires std::is_integral_v<T>
[[nodiscard]] constexpr T Log2Ceil(const T value) { [[nodiscard]] constexpr T Log2Ceil(const T value) {
return std::bit_width(value - 1); const T log2_f = Log2Floor<T>(value);
return T(log2_f + T((value ^ (T(1ULL) << log2_f)) != T(0ULL)));
}
template <typename T>
requires std::is_integral_v<T>
[[nodiscard]] T NextPow2(T value) {
return T(1ULL << (sizeof(T) * CHAR_BIT - std::countl_zero(value - 1U)));
} }
template <size_t bit_index, typename T> template <size_t bit_index, typename T>
requires (std::is_integral_v<T> && bit_index < BitSize<T>()) requires std::is_integral_v<T>
[[nodiscard]] constexpr bool Bit(const T value) { [[nodiscard]] constexpr bool Bit(const T value) {
return (T(value >> bit_index) & T(1)) == T(1); static_assert(bit_index < BitSize<T>(), "bit_index must be smaller than size of T");
return ((value >> bit_index) & T(1)) == T(1);
} }
} // namespace Common } // namespace Common
+6 -6
View File
@@ -109,12 +109,6 @@ private:
if (read_index == producer.index.load(std::memory_order::acquire)) { if (read_index == producer.index.load(std::memory_order::acquire)) {
return false; return false;
} }
} else if constexpr (Mode == PopMode::Wait) {
// Wait until the queue is not empty.
std::unique_lock lock{consumer.cv_mutex};
consumer.cv.wait(lock, [this, read_index] {
return read_index != producer.index.load(std::memory_order::acquire);
});
} else if constexpr (Mode == PopMode::WaitWithStopToken) { } else if constexpr (Mode == PopMode::WaitWithStopToken) {
// Wait until the queue is not empty. // Wait until the queue is not empty.
std::unique_lock lock{consumer.cv_mutex}; std::unique_lock lock{consumer.cv_mutex};
@@ -124,6 +118,12 @@ private:
if (stop_token.stop_requested()) { if (stop_token.stop_requested()) {
return false; return false;
} }
} else if constexpr (Mode == PopMode::Wait) {
// Wait until the queue is not empty.
std::unique_lock lock{consumer.cv_mutex};
consumer.cv.wait(lock, [this, read_index] {
return read_index != producer.index.load(std::memory_order::acquire);
});
} else { } else {
static_assert(Mode < PopMode::Count, "Invalid PopMode."); static_assert(Mode < PopMode::Count, "Invalid PopMode.");
} }
+13 -5
View File
@@ -25,18 +25,26 @@ template <typename T>
requires std::is_unsigned_v<T> requires std::is_unsigned_v<T>
inline std::size_t HashValue(T val) { inline std::size_t HashValue(T val) {
const unsigned int size_t_bits = std::numeric_limits<std::size_t>::digits; const unsigned int size_t_bits = std::numeric_limits<std::size_t>::digits;
const unsigned int length = (std::numeric_limits<T>::digits - 1) / static_cast<unsigned int>(size_t_bits); const unsigned int length =
(std::numeric_limits<T>::digits - 1) / static_cast<unsigned int>(size_t_bits);
std::size_t seed = 0; std::size_t seed = 0;
for (unsigned int i = length * size_t_bits; i > 0; i -= size_t_bits)
seed ^= std::size_t(val >> i) + (seed << 6) + (seed >> 2); for (unsigned int i = length * size_t_bits; i > 0; i -= size_t_bits) {
return seed ^= std::size_t(val) + (seed << 6) + (seed >> 2); seed ^= static_cast<size_t>(val >> i) + (seed << 6) + (seed >> 2);
}
seed ^= static_cast<size_t>(val) + (seed << 6) + (seed >> 2);
return seed;
} }
template <size_t Bits> template <size_t Bits>
struct HashCombineImpl { struct HashCombineImpl {
template <typename T> template <typename T>
static inline T fn(T seed, T value) { static inline T fn(T seed, T value) {
return seed ^= value + 0x9e3779b9 + (seed << 6) + (seed >> 2); seed ^= value + 0x9e3779b9 + (seed << 6) + (seed >> 2);
return seed;
} }
}; };
+2 -5
View File
@@ -1,6 +1,3 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
@@ -15,14 +12,14 @@ namespace Common {
template <typename N, typename D> template <typename N, typename D>
requires std::is_integral_v<N> && std::is_unsigned_v<D> requires std::is_integral_v<N> && std::is_unsigned_v<D>
[[nodiscard]] constexpr N DivCeil(N number, D divisor) { [[nodiscard]] constexpr N DivCeil(N number, D divisor) {
return N((D(number) + divisor - 1) / divisor); return static_cast<N>((static_cast<D>(number) + divisor - 1) / divisor);
} }
/// Ceiled integer division with logarithmic divisor in base 2 /// Ceiled integer division with logarithmic divisor in base 2
template <typename N, typename D> template <typename N, typename D>
requires std::is_integral_v<N> && std::is_unsigned_v<D> requires std::is_integral_v<N> && std::is_unsigned_v<D>
[[nodiscard]] constexpr N DivCeilLog2(N value, D alignment_log2) { [[nodiscard]] constexpr N DivCeilLog2(N value, D alignment_log2) {
return N((D(value) + (D(1) << alignment_log2) - 1) >> alignment_log2); return static_cast<N>((static_cast<D>(value) + (D(1) << alignment_log2) - 1) >> alignment_log2);
} }
} // namespace Common } // namespace Common
+28
View File
@@ -0,0 +1,28 @@
// SPDX-FileCopyrightText: 2015 Citra Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <cstddef>
#include <utility>
#include <boost/functional/hash.hpp>
namespace Common {
struct PairHash {
template <class T1, class T2>
std::size_t operator()(const std::pair<T1, T2>& pair) const noexcept {
std::size_t seed = std::hash<T1>()(pair.first);
boost::hash_combine(seed, std::hash<T2>()(pair.second));
return seed;
}
};
template <typename T>
struct IdentityHash {
[[nodiscard]] size_t operator()(T value) const noexcept {
return static_cast<size_t>(value);
}
};
} // namespace Common
+16 -10
View File
@@ -1,6 +1,3 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2013 Dolphin Emulator Project // SPDX-FileCopyrightText: 2013 Dolphin Emulator Project
// SPDX-FileCopyrightText: 2014 Citra Emulator Project // SPDX-FileCopyrightText: 2014 Citra Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
@@ -19,12 +16,14 @@ namespace Common {
[[nodiscard]] constexpr u8 ToHexNibble(char c) { [[nodiscard]] constexpr u8 ToHexNibble(char c) {
if (c >= 65 && c <= 70) { if (c >= 65 && c <= 70) {
return u8(c - 55); return static_cast<u8>(c - 55);
} }
if (c >= 97 && c <= 102) { if (c >= 97 && c <= 102) {
return u8(c - 87); return static_cast<u8>(c - 87);
} }
return u8(c - 48);
return static_cast<u8>(c - 48);
} }
[[nodiscard]] std::vector<u8> HexStringToVector(std::string_view str, bool little_endian); [[nodiscard]] std::vector<u8> HexStringToVector(std::string_view str, bool little_endian);
@@ -32,28 +31,35 @@ namespace Common {
template <std::size_t Size, bool le = false> template <std::size_t Size, bool le = false>
[[nodiscard]] constexpr std::array<u8, Size> HexStringToArray(std::string_view str) { [[nodiscard]] constexpr std::array<u8, Size> HexStringToArray(std::string_view str) {
ASSERT_MSG(Size * 2 <= str.size(), "Invalid string size"); ASSERT_MSG(Size * 2 <= str.size(), "Invalid string size");
std::array<u8, Size> out{}; std::array<u8, Size> out{};
if constexpr (le) { if constexpr (le) {
for (std::size_t i = 2 * Size - 2; i <= 2 * Size; i -= 2) { for (std::size_t i = 2 * Size - 2; i <= 2 * Size; i -= 2) {
out[i / 2] = u8((ToHexNibble(str[i]) << 4) | ToHexNibble(str[i + 1])); out[i / 2] = static_cast<u8>((ToHexNibble(str[i]) << 4) | ToHexNibble(str[i + 1]));
} }
} else { } else {
for (std::size_t i = 0; i < 2 * Size; i += 2) { for (std::size_t i = 0; i < 2 * Size; i += 2) {
out[i / 2] = u8((ToHexNibble(str[i]) << 4) | ToHexNibble(str[i + 1])); out[i / 2] = static_cast<u8>((ToHexNibble(str[i]) << 4) | ToHexNibble(str[i + 1]));
} }
} }
return out; return out;
} }
template <typename ContiguousContainer> template <typename ContiguousContainer>
requires std::is_same_v<typename ContiguousContainer::value_type, u8>
[[nodiscard]] std::string HexToString(const ContiguousContainer& data, bool upper = true) { [[nodiscard]] std::string HexToString(const ContiguousContainer& data, bool upper = true) {
static_assert(std::is_same_v<typename ContiguousContainer::value_type, u8>,
"Underlying type within the contiguous container must be u8.");
constexpr std::size_t pad_width = 2; constexpr std::size_t pad_width = 2;
std::string out; std::string out;
out.reserve(std::size(data) * pad_width); out.reserve(std::size(data) * pad_width);
const auto format_str = fmt::runtime(upper ? "{:02X}" : "{:02x}"); const auto format_str = fmt::runtime(upper ? "{:02X}" : "{:02x}");
for (const u8 c : data) for (const u8 c : data) {
out += fmt::format(format_str, c); out += fmt::format(format_str, c);
}
return out; return out;
} }
+6 -10
View File
@@ -10,7 +10,6 @@
#include <cstdlib> #include <cstdlib>
#include <regex> #include <regex>
#include <thread> #include <thread>
#include <boost/algorithm/string/predicate.hpp>
#if defined(__ANDROID__) #if defined(__ANDROID__)
#include <android/log.h> #include <android/log.h>
@@ -96,10 +95,9 @@ std::string FormatLogMessage(const Entry& entry) noexcept {
template <typename It> template <typename It>
Level GetLevelByName(const It begin, const It end) { Level GetLevelByName(const It begin, const It end) {
std::string_view const sv{begin, end};
for (u32 i = 0; i < u32(Level::Count); ++i) { for (u32 i = 0; i < u32(Level::Count); ++i) {
auto const level_name = GetLevelName(Level(i)); const char* level_name = GetLevelName(Level(i));
if (boost::iequals(sv, level_name)) if (Common::ComparePartialString(begin, end, level_name))
return Level(i); return Level(i);
} }
return Level::Count; return Level::Count;
@@ -107,10 +105,9 @@ Level GetLevelByName(const It begin, const It end) {
template <typename It> template <typename It>
Class GetClassByName(const It begin, const It end) { Class GetClassByName(const It begin, const It end) {
std::string_view const sv{begin, end};
for (u32 i = 0; i < u32(Class::Count); ++i) { for (u32 i = 0; i < u32(Class::Count); ++i) {
auto const level_name = GetLogClassName(Class(i)); const char* level_name = GetLogClassName(Class(i));
if (boost::iequals(sv, level_name)) if (Common::ComparePartialString(begin, end, level_name))
return Class(i); return Class(i);
} }
return Class::Count; return Class::Count;
@@ -123,13 +120,12 @@ bool ParseFilterRule(Filter& instance, Iterator begin, Iterator end) {
LOG_ERROR(Log, "Invalid log filter. Must specify a log level after `:`: {}", std::string(begin, end)); LOG_ERROR(Log, "Invalid log filter. Must specify a log level after `:`: {}", std::string(begin, end));
return false; return false;
} }
auto const sv = std::string_view{begin, level_separator}; const Level level = GetLevelByName(level_separator + 1, end);
auto const level = GetLevelByName(level_separator + 1, end);
if (level == Level::Count) { if (level == Level::Count) {
LOG_ERROR(Log, "Unknown log level in filter: {}", std::string(begin, end)); LOG_ERROR(Log, "Unknown log level in filter: {}", std::string(begin, end));
return false; return false;
} }
if (boost::iequals(sv, "*")) { if (Common::ComparePartialString(begin, level_separator, "*")) {
instance.class_levels.fill(level); instance.class_levels.fill(level);
return true; return true;
} }
+27
View File
@@ -0,0 +1,27 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <memory>
#include <type_traits>
namespace Common {
template <class T>
requires(!std::is_array_v<T>)
std::unique_ptr<T> make_unique_for_overwrite() {
return std::unique_ptr<T>(new T);
}
template <class T>
requires std::is_unbounded_array_v<T>
std::unique_ptr<T> make_unique_for_overwrite(std::size_t n) {
return std::unique_ptr<T>(new std::remove_extent_t<T>[n]);
}
template <class T, class... Args>
requires std::is_bounded_array_v<T>
void make_unique_for_overwrite(Args&&...) = delete;
} // namespace Common
+18 -20
View File
@@ -8,9 +8,6 @@
#include <stdexcept> #include <stdexcept>
#include <utility> #include <utility>
#include <vector> #include <vector>
#include <boost/algorithm/string/classification.hpp>
#include <boost/algorithm/string/replace.hpp>
#include <boost/algorithm/string/split.hpp>
#include "common/logging.h" #include "common/logging.h"
#include "common/param_package.h" #include "common/param_package.h"
@@ -18,16 +15,17 @@
namespace Common { namespace Common {
constexpr auto KEY_VALUE_SEPARATOR = ":"; constexpr char KEY_VALUE_SEPARATOR = ':';
constexpr auto PARAM_SEPARATOR = ","; constexpr char PARAM_SEPARATOR = ',';
constexpr auto ESCAPE_CHARACTER = "$";
constexpr auto KEY_VALUE_SEPARATOR_ESCAPE = "$0"; constexpr char ESCAPE_CHARACTER = '$';
constexpr auto PARAM_SEPARATOR_ESCAPE = "$1"; constexpr char KEY_VALUE_SEPARATOR_ESCAPE[] = "$0";
constexpr auto ESCAPE_CHARACTER_ESCAPE = "$2"; constexpr char PARAM_SEPARATOR_ESCAPE[] = "$1";
constexpr char ESCAPE_CHARACTER_ESCAPE[] = "$2";
/// A placeholder for empty param packages to avoid empty strings /// A placeholder for empty param packages to avoid empty strings
/// (they may be recognized as "not set" by some frontend libraries like qt) /// (they may be recognized as "not set" by some frontend libraries like qt)
constexpr auto EMPTY_PLACEHOLDER = "[empty]"; constexpr char EMPTY_PLACEHOLDER[] = "[empty]";
ParamPackage::ParamPackage(const std::string& serialized) { ParamPackage::ParamPackage(const std::string& serialized) {
if (serialized == EMPTY_PLACEHOLDER) { if (serialized == EMPTY_PLACEHOLDER) {
@@ -35,20 +33,20 @@ ParamPackage::ParamPackage(const std::string& serialized) {
} }
std::vector<std::string> pairs; std::vector<std::string> pairs;
boost::split(pairs, serialized, boost::is_any_of(PARAM_SEPARATOR)); Common::SplitString(serialized, PARAM_SEPARATOR, pairs);
for (const std::string& pair : pairs) { for (const std::string& pair : pairs) {
std::vector<std::string> key_value; std::vector<std::string> key_value;
boost::split(key_value, pair, boost::is_any_of(KEY_VALUE_SEPARATOR)); Common::SplitString(pair, KEY_VALUE_SEPARATOR, key_value);
if (key_value.size() != 2) { if (key_value.size() != 2) {
LOG_ERROR(Common, "invalid key pair {}", pair); LOG_ERROR(Common, "invalid key pair {}", pair);
continue; continue;
} }
for (auto& part : key_value) { for (std::string& part : key_value) {
boost::replace_all(part, KEY_VALUE_SEPARATOR_ESCAPE, KEY_VALUE_SEPARATOR); part = Common::ReplaceAll(part, KEY_VALUE_SEPARATOR_ESCAPE, {KEY_VALUE_SEPARATOR});
boost::replace_all(part, PARAM_SEPARATOR_ESCAPE, PARAM_SEPARATOR); part = Common::ReplaceAll(part, PARAM_SEPARATOR_ESCAPE, {PARAM_SEPARATOR});
boost::replace_all(part, ESCAPE_CHARACTER_ESCAPE, ESCAPE_CHARACTER); part = Common::ReplaceAll(part, ESCAPE_CHARACTER_ESCAPE, {ESCAPE_CHARACTER});
} }
Set(key_value[0], std::move(key_value[1])); Set(key_value[0], std::move(key_value[1]));
@@ -65,10 +63,10 @@ std::string ParamPackage::Serialize() const {
for (const auto& pair : data) { for (const auto& pair : data) {
std::array<std::string, 2> key_value{{pair.first, pair.second}}; std::array<std::string, 2> key_value{{pair.first, pair.second}};
for (auto& part : key_value) { for (std::string& part : key_value) {
boost::replace_all(part, ESCAPE_CHARACTER, ESCAPE_CHARACTER_ESCAPE); part = Common::ReplaceAll(part, {ESCAPE_CHARACTER}, ESCAPE_CHARACTER_ESCAPE);
boost::replace_all(part, PARAM_SEPARATOR, PARAM_SEPARATOR_ESCAPE); part = Common::ReplaceAll(part, {PARAM_SEPARATOR}, PARAM_SEPARATOR_ESCAPE);
boost::replace_all(part, KEY_VALUE_SEPARATOR, KEY_VALUE_SEPARATOR_ESCAPE); part = Common::ReplaceAll(part, {KEY_VALUE_SEPARATOR}, KEY_VALUE_SEPARATOR_ESCAPE);
} }
result += key_value[0] + KEY_VALUE_SEPARATOR + key_value[1] + PARAM_SEPARATOR; result += key_value[0] + KEY_VALUE_SEPARATOR + key_value[1] + PARAM_SEPARATOR;
} }
+6 -6
View File
@@ -8,7 +8,8 @@
#include <iterator> #include <iterator>
#include <cstring> #include <cstring>
#include <memory>
#include "common/make_unique_for_overwrite.h"
namespace Common { namespace Common {
@@ -37,9 +38,8 @@ public:
ScratchBuffer() = default; ScratchBuffer() = default;
explicit ScratchBuffer(size_type initial_capacity) explicit ScratchBuffer(size_type initial_capacity)
: last_requested_size{initial_capacity} : last_requested_size{initial_capacity}, buffer_capacity{initial_capacity},
, buffer_capacity{initial_capacity} buffer{Common::make_unique_for_overwrite<T[]>(initial_capacity)} {}
, buffer{std::make_unique_for_overwrite<T[]>(initial_capacity)} {}
~ScratchBuffer() = default; ~ScratchBuffer() = default;
ScratchBuffer(const ScratchBuffer&) = delete; ScratchBuffer(const ScratchBuffer&) = delete;
@@ -64,7 +64,7 @@ public:
/// The previously held data will remain intact. /// The previously held data will remain intact.
void resize(size_type size) { void resize(size_type size) {
if (size > buffer_capacity) { if (size > buffer_capacity) {
auto new_buffer = std::make_unique_for_overwrite<T[]>(size); auto new_buffer = Common::make_unique_for_overwrite<T[]>(size);
std::memcpy(new_buffer.get(), buffer.get(), buffer_capacity * sizeof(T)); std::memcpy(new_buffer.get(), buffer.get(), buffer_capacity * sizeof(T));
buffer = std::move(new_buffer); buffer = std::move(new_buffer);
buffer_capacity = size; buffer_capacity = size;
@@ -77,7 +77,7 @@ public:
void resize_destructive(size_type size) { void resize_destructive(size_type size) {
if (size > buffer_capacity) { if (size > buffer_capacity) {
buffer_capacity = size; buffer_capacity = size;
buffer = std::make_unique_for_overwrite<T[]>(buffer_capacity); buffer = Common::make_unique_for_overwrite<T[]>(buffer_capacity);
} }
last_requested_size = size; last_requested_size = size;
} }
+4
View File
@@ -178,6 +178,10 @@ bool IsGPUFenceBehaviorAccurate() {
return values.gpu_fence_behavior.GetValue() == GpuFenceBehavior::Accurate; return values.gpu_fence_behavior.GetValue() == GpuFenceBehavior::Accurate;
} }
bool IsGPUFenceBehaviorStrict() {
return values.gpu_fence_behavior.GetValue() == GpuFenceBehavior::Strict;
}
bool IsFastmemEnabled() { bool IsFastmemEnabled() {
if (values.cpu_accuracy.GetValue() == Settings::CpuAccuracy::Debugging) if (values.cpu_accuracy.GetValue() == Settings::CpuAccuracy::Debugging)
return bool(values.cpuopt_fastmem); return bool(values.cpuopt_fastmem);
+9 -1
View File
@@ -525,7 +525,7 @@ struct Values {
SwitchableSetting<GpuFenceBehavior, true> gpu_fence_behavior{linkage, SwitchableSetting<GpuFenceBehavior, true> gpu_fence_behavior{linkage,
GpuFenceBehavior::Default, GpuFenceBehavior::Default,
GpuFenceBehavior::Default, GpuFenceBehavior::Default,
GpuFenceBehavior::Accurate, GpuFenceBehavior::Strict,
"gpu_fence_behavior", "gpu_fence_behavior",
Category::RendererAdvanced, Category::RendererAdvanced,
Specialization::Default, Specialization::Default,
@@ -655,6 +655,13 @@ struct Values {
SwitchableSetting<bool> rescale_hack{linkage, false, "rescale_hack", SwitchableSetting<bool> rescale_hack{linkage, false, "rescale_hack",
Category::RendererHacks}; Category::RendererHacks};
SwitchableSetting<bool> enable_gpu_buffer_readback{linkage,
false,
"enable_gpu_buffer_readback",
Category::RendererAdvanced,
Specialization::Default,
true,
true};
SwitchableSetting<bool> use_asynchronous_shaders{linkage, false, "use_asynchronous_shaders", SwitchableSetting<bool> use_asynchronous_shaders{linkage, false, "use_asynchronous_shaders",
Category::RendererHacks}; Category::RendererHacks};
@@ -972,6 +979,7 @@ bool IsDMALevelSafe();
bool IsGPUFenceBehaviorDefault(); bool IsGPUFenceBehaviorDefault();
bool IsGPUFenceBehaviorBalanced(); bool IsGPUFenceBehaviorBalanced();
bool IsGPUFenceBehaviorAccurate(); bool IsGPUFenceBehaviorAccurate();
bool IsGPUFenceBehaviorStrict();
bool IsFastmemEnabled(); bool IsFastmemEnabled();
void SetNceEnabled(bool is_64bit); void SetNceEnabled(bool is_64bit);
+1 -1
View File
@@ -137,7 +137,7 @@ ENUM(VramUsageMode, Conservative, Aggressive);
ENUM(RendererBackend, OpenGL_GLSL, Vulkan, Null, OpenGL_GLASM, OpenGL_SPIRV); ENUM(RendererBackend, OpenGL_GLSL, Vulkan, Null, OpenGL_GLASM, OpenGL_SPIRV);
ENUM(GpuAccuracy, Low, High); ENUM(GpuAccuracy, Low, High);
ENUM(DmaAccuracy, Default, Unsafe, Safe); ENUM(DmaAccuracy, Default, Unsafe, Safe);
ENUM(GpuFenceBehavior, Default, Immediate, Balanced, Accurate); ENUM(GpuFenceBehavior, Default, Immediate, Balanced, Accurate, Strict);
ENUM(CpuBackend, Dynarmic, Nce); ENUM(CpuBackend, Dynarmic, Nce);
ENUM(CpuAccuracy, Auto, Accurate, Unsafe, Paranoid, Debugging); ENUM(CpuAccuracy, Auto, Accurate, Unsafe, Paranoid, Debugging);
ENUM(CpuClock, Normal, Boost, Overclock) ENUM(CpuClock, Normal, Boost, Overclock)
+15 -46
View File
@@ -6,7 +6,6 @@
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
#ifdef _WIN32 #ifdef _WIN32
#include <algorithm>
#include <windows.h> #include <windows.h>
#include <mutex> #include <mutex>
#else #else
@@ -21,36 +20,32 @@ namespace Common {
#ifdef _WIN32 #ifdef _WIN32
static std::vector<std::pair<u64, u64>> vector_regions {}; static std::vector<std::pair<u64, u64>> vector_regions {};
static std::mutex vector_regions_mutex {};
// Workaround for handling non-commited memory accessed by Dynarmic; usually result of an error // Workaround for handling non-commited memory accessed by Dynarmic; usually result of an error
static LONG WINAPI FakePageFaultHandler(PEXCEPTION_POINTERS info) { static LONG WINAPI FakePageFaultHandler(PEXCEPTION_POINTERS info) {
DWORD code = info->ExceptionRecord->ExceptionCode; DWORD code = info->ExceptionRecord->ExceptionCode;
u64 exception_addr = reinterpret_cast<u64>(info->ExceptionRecord->ExceptionInformation[1]); u64 exception_addr = reinterpret_cast<u64>(info->ExceptionRecord->ExceptionAddress);
if (code != EXCEPTION_ACCESS_VIOLATION || info->ExceptionRecord->ExceptionInformation[0] == 1) { if (code != EXCEPTION_ACCESS_VIOLATION) {
// Not our problem // Not our problem
return EXCEPTION_CONTINUE_SEARCH; return EXCEPTION_CONTINUE_SEARCH;
} }
u64 addr = 0, addr2 = 0; u64 addr = 0, addr2 = 0;
{ for (auto region: vector_regions) {
std::lock_guard lock(vector_regions_mutex); auto addr_shifted = exception_addr >> HostPageBits;
for (auto region: vector_regions) { if (region.first <= addr_shifted && addr_shifted <= region.second) {
auto addr_shifted = exception_addr >> HostPageBits; addr = addr_shifted;
if (region.first <= addr_shifted && addr_shifted <= region.second) { }
addr = addr_shifted;
}
// Page-boundary accesses // Page-boundary accesses
if (auto addr_ = (exception_addr + 0x40) >> HostPageBits; addr_ != addr_shifted && region.first <= addr_ && addr_ <= region.second) { if (auto addr_ = (exception_addr + 0x40) >> HostPageBits; addr_ != addr_shifted && region.first <= addr_ && addr_ <= region.second) {
addr2 = addr_; addr2 = addr_;
} }
if (addr != 0 || addr2 != 0) { if (addr != 0 || addr2 != 0) {
break; break;
}
} }
} }
@@ -82,16 +77,8 @@ bool CommitVectorPage(uintptr_t addr, bool write) noexcept {
auto res = VirtualQuery(reinterpret_cast<void*>(addr), &info, sizeof(info)); auto res = VirtualQuery(reinterpret_cast<void*>(addr), &info, sizeof(info));
if (res == 0) { if (res == 0) {
LOG_CRITICAL(HW_Memory, "Failed to query large buffer region at {:#x} with error {}, will try committing anyway", addr, GetLastError()); LOG_CRITICAL(HW_Memory, "Failed to query large buffer region at {:#x} with error {}, will try committing anyway", addr, GetLastError());
} else if (info.State == MEM_COMMIT) {
DWORD old_protect {};
auto perm = write ? PAGE_READWRITE : PAGE_READONLY;
if (!VirtualProtect(reinterpret_cast<void*>(addr), HostPageSize, perm, &old_protect)) {
LOG_ERROR(HW_Memory, "Failed to change permissions of large buffer region at {:#x}, error {}", addr, GetLastError());
return false;
}
return true;
} else if (info.State != MEM_RESERVE) { } else if (info.State != MEM_RESERVE) {
LOG_ERROR(HW_Memory, "Tried to commit an unreserved large buffer region at {:#x} that is not mapped (state {:#x})", addr, info.State); LOG_ERROR(HW_Memory, "Tried to commit an unreserved large buffer region at {:#x} that is not mapped or is already committed (state {:#x})", addr, info.State);
return false; return false;
} }
@@ -109,21 +96,6 @@ bool CommitVectorPage(uintptr_t addr, bool write) noexcept {
#ifndef MAP_NOCORE #ifndef MAP_NOCORE
#define MAP_NOCORE 0 #define MAP_NOCORE 0
#endif #endif
#ifndef MADV_FREE
#define MADV_FREE MADV_DONTNEED
#endif
void DecommitVectorPage(uintptr_t base) noexcept {
#if defined(_WIN32)
VirtualFree(reinterpret_cast<LPVOID>(base), HostPageSize, MEM_DECOMMIT);
#elif defined(__linux__)
// Linux's MADV_DONTNEED zeros out pages for us
madvise(reinterpret_cast<void*>(base), HostPageSize, MADV_DONTNEED);
#else
madvise(reinterpret_cast<void*>(base), HostPageSize, MADV_FREE);
std::memset(reinterpret_cast<void*>(base), 0, HostPageSize);
#endif
}
void* AllocateMemoryPages(std::size_t size) noexcept { void* AllocateMemoryPages(std::size_t size) noexcept {
if (auto page = HostPageSize; size % page != 0) { if (auto page = HostPageSize; size % page != 0) {
@@ -136,8 +108,7 @@ void* AllocateMemoryPages(std::size_t size) noexcept {
void* base = VirtualAlloc(nullptr, size, MEM_RESERVE, PAGE_READWRITE); void* base = VirtualAlloc(nullptr, size, MEM_RESERVE, PAGE_READWRITE);
if (base != nullptr) { if (base != nullptr) {
std::lock_guard lock(vector_regions_mutex); vector_regions.emplace_back(reinterpret_cast<u64>(base), reinterpret_cast<u64>(base) + size);
vector_regions.emplace_back(reinterpret_cast<u64>(base) >> HostPageBits, (reinterpret_cast<u64>(base) + size) >> HostPageBits);
static std::once_flag flag; static std::once_flag flag;
std::call_once(flag, []() { AddVectoredExceptionHandler(1, FakePageFaultHandler); }); std::call_once(flag, []() { AddVectoredExceptionHandler(1, FakePageFaultHandler); });
@@ -163,8 +134,6 @@ void FreeMemoryPages(void* base, [[maybe_unused]] std::size_t size) noexcept {
if (!base) if (!base)
return; return;
#ifdef _WIN32 #ifdef _WIN32
std::lock_guard lock(vector_regions_mutex);
std::erase_if(vector_regions, [base](const auto& r) {return r.first == reinterpret_cast<u64>(base); });
ASSERT(VirtualFree(base, 0, MEM_RELEASE)); ASSERT(VirtualFree(base, 0, MEM_RELEASE));
#else #else
ASSERT(munmap(base, size) == 0); ASSERT(munmap(base, size) == 0);
+14 -33
View File
@@ -28,14 +28,13 @@ constexpr u64 HostPageBits = 12;
constexpr u64 HostPageMask = ~(HostPageSize - 1); constexpr u64 HostPageMask = ~(HostPageSize - 1);
bool CommitVectorPage(uintptr_t addr, bool write) noexcept; bool CommitVectorPage(uintptr_t addr, bool write) noexcept;
#else #else
inline const u64 HostPageSize = sysconf(_SC_PAGESIZE); const u64 HostPageSize = sysconf(_SC_PAGESIZE);
inline const u64 HostPageBits = std::countr_zero(HostPageSize); const u64 HostPageBits = std::countr_zero(HostPageSize);
inline const u64 HostPageMask = ~(HostPageSize - 1); const u64 HostPageMask = ~(HostPageSize - 1);
#endif #endif
void* AllocateMemoryPages(std::size_t size) noexcept; void* AllocateMemoryPages(std::size_t size) noexcept;
void FreeMemoryPages(void* base, std::size_t size) noexcept; void FreeMemoryPages(void* base, std::size_t size) noexcept;
void DecommitVectorPage(uintptr_t base) noexcept;
/// A large page-aligned buffer that has optimized memory usage for zero-writes. /// A large page-aligned buffer that has optimized memory usage for zero-writes.
template <typename T> template <typename T>
@@ -81,8 +80,8 @@ public:
UNREACHABLE_MSG("Out of bounds RW access on SparseLargeVector @ {}", index); UNREACHABLE_MSG("Out of bounds RW access on SparseLargeVector @ {}", index);
} }
if (!IsCommittedPage(index) && !CommitPage(index)) { if (!IsCommittedPage(index)) {
UNREACHABLE_MSG("Cannot access SparseLargeVector index {} with RW permission", index); CommitPage(index);
} }
return base_ptr[index]; return base_ptr[index];
} }
@@ -103,8 +102,9 @@ public:
LOG_CRITICAL(Common_Memory, "Out of bounds write on SparseLargeVector @ {}", index); LOG_CRITICAL(Common_Memory, "Out of bounds write on SparseLargeVector @ {}", index);
return; return;
} }
if (IsCommittedPage(index) || CommitPage(index)) if (!IsCommittedPage(index))
base_ptr[index] = value; CommitPage(index);
base_ptr[index] = value;
} }
void ZeroRegion(std::size_t start, std::size_t end_) noexcept { void ZeroRegion(std::size_t start, std::size_t end_) noexcept {
@@ -114,7 +114,7 @@ public:
const u64 end_page = AlignUp(base, HostPageSize); const u64 end_page = AlignUp(base, HostPageSize);
const u64 first_size = (std::min)(end_page, end) - base; const u64 first_size = (std::min)(end_page, end) - base;
if (IsCommittedPage(start)) { if (IsCommittedPage(start / sizeof(T))) {
std::memset(reinterpret_cast<void*>(base), 0, first_size); std::memset(reinterpret_cast<void*>(base), 0, first_size);
} }
@@ -124,16 +124,11 @@ public:
base = end_page; base = end_page;
for (u64 page = base; page < end; page += HostPageSize) { for (u64 page = base; page < end; page += HostPageSize) {
auto index = (page - reinterpret_cast<u64>(base_ptr)) / sizeof(T); if (!IsCommittedPage((page - reinterpret_cast<u64>(base_ptr)) / sizeof(T))) {
if (!IsCommittedPage(index)) {
continue; continue;
} }
if (end - page >= HostPageSize) { std::memset(reinterpret_cast<void*>(page), 0, (std::min)( HostPageSize, end - page));
DecommitPage(index);
} else {
std::memset(reinterpret_cast<void*>(page), 0, end - page);
}
} }
} }
@@ -176,30 +171,16 @@ private:
return (val >> (page & 63)) & 1; return (val >> (page & 63)) & 1;
} }
constexpr bool CommitPage(std::size_t index) noexcept { constexpr void CommitPage(std::size_t index) noexcept {
auto page_index = (index * sizeof(T)) >> HostPageBits; auto page_index = (index * sizeof(T)) >> HostPageBits;
auto page = reinterpret_cast<uintptr_t>(base_ptr + index) & HostPageMask; auto page = reinterpret_cast<uintptr_t>(base_ptr + index) & HostPageMask;
#if defined(_WIN32) #if defined(_WIN32)
if (!CommitVectorPage(page, true)) { CommitVectorPage(page, true);
return false;
}
#else #else
if (mprotect(reinterpret_cast<void*>(page), HostPageSize, PROT_READ | PROT_WRITE) != 0) { mprotect(reinterpret_cast<void*>(page), HostPageSize, PROT_READ | PROT_WRITE);
LOG_ERROR(Common_Memory, "Failed to commit large buffer region at index {}, error {}", index, strerror(errno));
return false;
}
#endif #endif
committed_pages[page_index >> 6].fetch_or(1ULL << (page_index & 63), std::memory_order_release); committed_pages[page_index >> 6].fetch_or(1ULL << (page_index & 63), std::memory_order_release);
return true;
}
constexpr void DecommitPage(std::size_t index) noexcept {
auto page_index = (index * sizeof(T)) >> HostPageBits;
auto page = reinterpret_cast<uintptr_t>(base_ptr + index) & HostPageMask;
committed_pages[page_index >> 6].fetch_and(~(1ULL << (page_index & 63)), std::memory_order_release);
DecommitVectorPage(page);
} }
std::size_t alloc_size{}; std::size_t alloc_size{};
+51
View File
@@ -24,6 +24,22 @@
namespace Common { namespace Common {
/// Make a string lowercase
std::string ToLower(const std::string_view sv) {
std::string str{sv};
std::transform(str.begin(), str.end(), str.begin(),
[](auto const c) { return char(std::tolower(c)); });
return str;
}
/// Make a string uppercase
std::string ToUpper(const std::string_view sv) {
std::string str{sv};
std::transform(str.begin(), str.end(), str.begin(),
[](auto const c) { return char(std::toupper(c)); });
return str;
}
bool SplitPath(const std::string& full_path, std::string* _pPath, std::string* _pFilename, bool SplitPath(const std::string& full_path, std::string* _pPath, std::string* _pFilename,
std::string* _pExtension) { std::string* _pExtension) {
if (full_path.empty()) if (full_path.empty())
@@ -64,6 +80,41 @@ bool SplitPath(const std::string& full_path, std::string* _pPath, std::string* _
return true; return true;
} }
void SplitString(const std::string& str, const char delim, std::vector<std::string>& output) {
std::istringstream iss(str);
output.resize(1);
while (std::getline(iss, *output.rbegin(), delim)) {
output.emplace_back();
}
output.pop_back();
}
std::string TabsToSpaces(int tab_size, std::string in) {
std::size_t i = 0;
while ((i = in.find('\t')) != std::string::npos) {
in.replace(i, 1, tab_size, ' ');
}
return in;
}
std::string ReplaceAll(std::string result, const std::string& src, const std::string& dest) {
std::size_t pos = 0;
if (src == dest)
return result;
while ((pos = result.find(src, pos)) != std::string::npos) {
result.replace(pos, src.size(), dest);
pos += dest.length();
}
return result;
}
std::string UTF16ToUTF8(std::u16string_view input) { std::string UTF16ToUTF8(std::u16string_view input) {
std::wstring_convert<std::codecvt_utf8_utf16<char16_t>, char16_t> convert; std::wstring_convert<std::codecvt_utf8_utf16<char16_t>, char16_t> convert;
return convert.to_bytes(input.data(), input.data() + input.size()); return convert.to_bytes(input.data(), input.data() + input.size());
+56 -4
View File
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2013 Dolphin Emulator Project // SPDX-FileCopyrightText: 2013 Dolphin Emulator Project
@@ -16,6 +16,12 @@
namespace Common { namespace Common {
/// Make a string lowercase
[[nodiscard]] std::string ToLower(const std::string_view sv);
/// Make a string uppercase
[[nodiscard]] std::string ToUpper(const std::string_view sv);
[[nodiscard]] inline std::string StringFromBuffer(std::span<const u8> data) noexcept { [[nodiscard]] inline std::string StringFromBuffer(std::span<const u8> data) noexcept {
return std::string(data.begin(), std::find(data.begin(), data.end(), '\0')); return std::string(data.begin(), std::find(data.begin(), data.end(), '\0'));
} }
@@ -23,8 +29,37 @@ namespace Common {
return std::string(data.begin(), std::find(data.begin(), data.end(), '\0')); return std::string(data.begin(), std::find(data.begin(), data.end(), '\0'));
} }
/// Turns " hej " into "hej". Also handles tabs.
[[nodiscard]] inline std::string StripSpaces(const std::string_view str) noexcept {
const std::size_t s = str.find_first_not_of(" \t\r\n");
if (str.npos != s)
return std::string{str.substr(s, str.find_last_not_of(" \t\r\n") - s + 1)};
return {};
}
/// "\"hello\"" is turned to "hello"
/// This one assumes that the string has already been space stripped in both
/// ends, as done by StripSpaces above, for example.
[[nodiscard]] inline std::string StripQuotes(const std::string_view s) noexcept {
if (s.size() && '\"' == s[0] && '\"' == *s.rbegin())
return std::string{s.substr(1, s.size() - 2)};
return std::string{s};
}
[[nodiscard]] inline std::string StringFromBool(bool value) noexcept {
return value ? "True" : "False";
}
[[nodiscard]] std::string TabsToSpaces(int tab_size, std::string in);
void SplitString(const std::string& str, char delim, std::vector<std::string>& output);
// "C:/Windows/winhelp.exe" to "C:/Windows/", "winhelp", ".exe" // "C:/Windows/winhelp.exe" to "C:/Windows/", "winhelp", ".exe"
bool SplitPath(const std::string& full_path, std::string* _pPath, std::string* _pFilename, std::string* _pExtension); bool SplitPath(const std::string& full_path, std::string* _pPath, std::string* _pFilename,
std::string* _pExtension);
[[nodiscard]] std::string ReplaceAll(std::string result, const std::string& src,
const std::string& dest);
[[nodiscard]] std::string UTF16ToUTF8(std::u16string_view input); [[nodiscard]] std::string UTF16ToUTF8(std::u16string_view input);
[[nodiscard]] std::u16string UTF8ToUTF16(std::string_view input); [[nodiscard]] std::u16string UTF8ToUTF16(std::string_view input);
@@ -38,13 +73,30 @@ bool SplitPath(const std::string& full_path, std::string* _pPath, std::string* _
[[nodiscard]] std::u16string U16StringFromBuffer(const u16* input, std::size_t length); [[nodiscard]] std::u16string U16StringFromBuffer(const u16* input, std::size_t length);
/**
* Compares the string defined by the range [`begin`, `end`) to the null-terminated C-string
* `other` for equality.
*/
template <typename InIt>
[[nodiscard]] inline bool ComparePartialString(InIt begin, InIt end, const char* other) noexcept {
for (; begin != end && *other != '\0'; ++begin, ++other) {
if (*begin != *other) {
return false;
}
}
// Only return true if both strings finished at the same point
return (begin == end) == (*other == '\0');
}
/// Creates a std::string from a fixed-size NUL-terminated char buffer. If the buffer isn't /// Creates a std::string from a fixed-size NUL-terminated char buffer. If the buffer isn't
/// NUL-terminated then the string ends at max_len characters. /// NUL-terminated then the string ends at max_len characters.
[[nodiscard]] std::string StringFromFixedZeroTerminatedBuffer(std::string_view buffer, std::size_t max_len); [[nodiscard]] std::string StringFromFixedZeroTerminatedBuffer(std::string_view buffer,
std::size_t max_len);
/// Creates a UTF-16 std::u16string from a fixed-size NUL-terminated char buffer. If the buffer isn't /// Creates a UTF-16 std::u16string from a fixed-size NUL-terminated char buffer. If the buffer isn't
/// null-terminated, then the string ends at the greatest multiple of two less then or equal to /// null-terminated, then the string ends at the greatest multiple of two less then or equal to
/// max_len_bytes. /// max_len_bytes.
[[nodiscard]] std::u16string UTF16StringFromFixedZeroTerminatedBuffer(std::u16string_view buffer, std::size_t max_len); [[nodiscard]] std::u16string UTF16StringFromFixedZeroTerminatedBuffer(std::u16string_view buffer,
std::size_t max_len);
} // namespace Common } // namespace Common
-46
View File
@@ -1,46 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#include <cstring>
#include "common/zbic_compression.h"
#define ZSTD_ZBIC_SUPPORT 1
#define ZSTDLIB_VISIBLE static
#define ZSTDLIB_HIDDEN static
#define ZSTDERRORLIB_VISIBLE static
#define ZSTDERRORLIB_HIDDEN static
#undef ZSTD_MULTITHREAD
#if defined(__ANDROID__)
#undef _GNU_SOURCE
#endif
#include "zstd.h"
#define g_ZSTD_threading_useless_symbol g_ZSTD_zbic_threading_useless_symbol
#include "zstd.c"
#undef g_ZSTD_threading_useless_symbol
namespace Common::Compression {
bool IsZBIC(std::span<const u8> src) {
if (src.size() < sizeof(u32)) {
return false;
}
u32 magic = 0;
std::memcpy(&magic, src.data(), sizeof(u32));
return magic == ZSTD_MAGICNUMBER; // 0x4349425A ("ZBIC")
}
int DecompressDataZBIC(std::span<u8> dst, std::span<const u8> src) {
if (dst.empty() || src.empty()) {
return -1;
}
const size_t res = ZSTD_decompress(dst.data(), dst.size(), src.data(), src.size());
if (ZSTD_isError(res)) {
return -1;
}
return static_cast<int>(res);
}
} // namespace Common::Compression
-15
View File
@@ -1,15 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
#include <span>
#include "common/common_types.h"
namespace Common::Compression {
[[nodiscard]] bool IsZBIC(std::span<const u8> src);
[[nodiscard]] int DecompressDataZBIC(std::span<u8> dst, std::span<const u8> src);
} // namespace Common::Compression
-5
View File
@@ -23,8 +23,6 @@ add_library(core STATIC
core_timing.h core_timing.h
cpu_manager.cpp cpu_manager.cpp
cpu_manager.h cpu_manager.h
launch_params.cpp
launch_params.h
crypto/aes_util.cpp crypto/aes_util.cpp
crypto/aes_util.h crypto/aes_util.h
crypto/ctr_encryption_layer.cpp crypto/ctr_encryption_layer.cpp
@@ -1205,9 +1203,6 @@ endif()
target_include_directories(core PRIVATE ${OPUS_INCLUDE_DIRS}) target_include_directories(core PRIVATE ${OPUS_INCLUDE_DIRS})
target_link_libraries(core PUBLIC common PRIVATE audio_core hid_core network video_core nx_tzdb tz) target_link_libraries(core PUBLIC common PRIVATE audio_core hid_core network video_core nx_tzdb tz)
if (MSVC)
target_link_libraries(core PRIVATE getopt)
endif()
if (BOOST_NO_HEADERS) if (BOOST_NO_HEADERS)
target_link_libraries(core PUBLIC Boost::container Boost::heap Boost::asio Boost::process Boost::crc) target_link_libraries(core PUBLIC Boost::container Boost::heap Boost::asio Boost::process Boost::crc)
+1
View File
@@ -13,6 +13,7 @@
#include <dynarmic/interface/A64/a64.h> #include <dynarmic/interface/A64/a64.h>
#include <dynarmic/interface/code_page.h> #include <dynarmic/interface/code_page.h>
#include "common/common_types.h" #include "common/common_types.h"
#include "common/hash.h"
#include "core/arm/arm_interface.h" #include "core/arm/arm_interface.h"
#include "core/arm/dynarmic/dynarmic_exclusive_monitor.h" #include "core/arm/dynarmic/dynarmic_exclusive_monitor.h"
#include "dynarmic/interface/A64/config.h" #include "dynarmic/interface/A64/config.h"
+45 -23
View File
@@ -110,8 +110,13 @@ FileSys::VirtualFile GetGameFileFromPath(const FileSys::VirtualFilesystem& vfs,
struct System::Impl { struct System::Impl {
explicit Impl(System& system) explicit Impl(System& system)
: kernel{system}, fs_controller{system}, hid_core{kernel}, cpu_manager{system}, : kernel{system}
reporter{system}, applet_manager{system}, frontend_applets{system}, profile_manager{} {} , fs_controller{system}
, hid_core{kernel}
, cpu_manager{system}
, reporter{system}
, profile_manager{}
{}
u64 program_id; u64 program_id;
@@ -124,6 +129,12 @@ struct System::Impl {
core_timing.SetMulticore(is_multicore); core_timing.SetMulticore(is_multicore);
core_timing.Initialize([&system]() { system.RegisterHostThread(); }); core_timing.Initialize([&system]() { system.RegisterHostThread(); });
applet_manager.emplace(system);
frontend_applets.emplace(system);
apm_controller.emplace(core_timing);
arp_manager.emplace();
profile_manager.emplace();
// Create a default fs if one doesn't already exist. // Create a default fs if one doesn't already exist.
if (virtual_filesystem == nullptr) { if (virtual_filesystem == nullptr) {
virtual_filesystem = std::make_shared<FileSys::RealVfsFilesystem>(); virtual_filesystem = std::make_shared<FileSys::RealVfsFilesystem>();
@@ -133,7 +144,7 @@ struct System::Impl {
} }
// Create default implementations of applets if one is not provided. // Create default implementations of applets if one is not provided.
frontend_applets.SetDefaultAppletsIfMissing(); frontend_applets->SetDefaultAppletsIfMissing();
auto const is_async_gpu = Settings::values.use_asynchronous_gpu_emulation.GetValue(); auto const is_async_gpu = Settings::values.use_asynchronous_gpu_emulation.GetValue();
@@ -376,7 +387,7 @@ struct System::Impl {
// Register with applet manager // Register with applet manager
// All threads are started, begin main process execution, now that we're in the clear // All threads are started, begin main process execution, now that we're in the clear
applet_manager.CreateAndInsertByFrontendAppletParameters(std::move(process), params); applet_manager->CreateAndInsertByFrontendAppletParameters(std::move(process), params);
if (Settings::values.gamecard_inserted) { if (Settings::values.gamecard_inserted) {
if (Settings::values.gamecard_current_game) { if (Settings::values.gamecard_current_game) {
@@ -428,13 +439,27 @@ struct System::Impl {
core_timing.SyncPause(false); core_timing.SyncPause(false);
Network::CancelPendingSocketOperations(); Network::CancelPendingSocketOperations();
kernel.SuspendEmulation(true); kernel.SuspendEmulation(true);
kernel.CloseServices();
kernel.ShutdownCores(); kernel.ShutdownCores();
// Notify services helpers of shutdown
audio_core->Shutdown();
// Wait for threads/services to join
kernel.CloseServices();
// service shutdown
services.reset(); services.reset();
service_manager.reset(); service_manager.reset();
frontend_applets.reset();
applet_manager.reset();
apm_controller.reset();
arp_manager.reset();
profile_manager.reset();
fs_controller.Reset(); fs_controller.Reset();
cheat_engine.reset(); cheat_engine.reset();
core_timing.ClearPendingEvents(); core_timing.ClearPendingEvents();
core_timing.Reset();
app_loader.reset(); app_loader.reset();
audio_core.reset(); audio_core.reset();
gpu_core.reset(); gpu_core.reset();
@@ -451,9 +476,6 @@ struct System::Impl {
room_member->SendGameInfo(game_info); room_member->SendGameInfo(game_info);
} }
// Reset all glue registrations
arp_manager.ResetAll();
LOG_DEBUG(Core, "Shutdown OK"); LOG_DEBUG(Core, "Shutdown OK");
} }
@@ -482,13 +504,13 @@ struct System::Impl {
CpuManager cpu_manager; CpuManager cpu_manager;
Reporter reporter; Reporter reporter;
/// Applets /// Applets
Service::AM::AppletManager applet_manager; std::optional<Service::AM::AppletManager> applet_manager;
Service::AM::Frontend::FrontendAppletHolder frontend_applets; std::optional<Service::AM::Frontend::FrontendAppletHolder> frontend_applets;
/// APM (Performance) services /// APM (Performance) services
Service::APM::Controller apm_controller{core_timing}; std::optional<Service::APM::Controller> apm_controller;
/// Service State /// Service State
Service::Glue::ARPManager arp_manager; std::optional<Service::Glue::ARPManager> arp_manager;
Service::Account::ProfileManager profile_manager; std::optional<Service::Account::ProfileManager> profile_manager;
/// Network instance /// Network instance
Network::NetworkInstance network_instance; Network::NetworkInstance network_instance;
Core::SpeedLimiter speed_limiter; Core::SpeedLimiter speed_limiter;
@@ -815,19 +837,19 @@ void System::RegisterCheatList(const std::vector<Memory::CheatEntry>& list,
} }
void System::SetFrontendAppletSet(Service::AM::Frontend::FrontendAppletSet&& set) { void System::SetFrontendAppletSet(Service::AM::Frontend::FrontendAppletSet&& set) {
impl->frontend_applets.SetFrontendAppletSet(std::move(set)); impl->frontend_applets->SetFrontendAppletSet(std::move(set));
} }
Service::AM::Frontend::FrontendAppletHolder& System::GetFrontendAppletHolder() { Service::AM::Frontend::FrontendAppletHolder& System::GetFrontendAppletHolder() {
return impl->frontend_applets; return *impl->frontend_applets;
} }
const Service::AM::Frontend::FrontendAppletHolder& System::GetFrontendAppletHolder() const { const Service::AM::Frontend::FrontendAppletHolder& System::GetFrontendAppletHolder() const {
return impl->frontend_applets; return *impl->frontend_applets;
} }
Service::AM::AppletManager& System::GetAppletManager() { Service::AM::AppletManager& System::GetAppletManager() {
return impl->applet_manager; return *impl->applet_manager;
} }
void System::SetContentProvider(std::unique_ptr<FileSys::ContentProviderUnion> provider) { void System::SetContentProvider(std::unique_ptr<FileSys::ContentProviderUnion> provider) {
@@ -868,27 +890,27 @@ const Reporter& System::GetReporter() const {
} }
Service::Glue::ARPManager& System::GetARPManager() { Service::Glue::ARPManager& System::GetARPManager() {
return impl->arp_manager; return *impl->arp_manager;
} }
const Service::Glue::ARPManager& System::GetARPManager() const { const Service::Glue::ARPManager& System::GetARPManager() const {
return impl->arp_manager; return *impl->arp_manager;
} }
Service::APM::Controller& System::GetAPMController() { Service::APM::Controller& System::GetAPMController() {
return impl->apm_controller; return *impl->apm_controller;
} }
const Service::APM::Controller& System::GetAPMController() const { const Service::APM::Controller& System::GetAPMController() const {
return impl->apm_controller; return *impl->apm_controller;
} }
Service::Account::ProfileManager& System::GetProfileManager() { Service::Account::ProfileManager& System::GetProfileManager() {
return impl->profile_manager; return *impl->profile_manager;
} }
const Service::Account::ProfileManager& System::GetProfileManager() const { const Service::Account::ProfileManager& System::GetProfileManager() const {
return impl->profile_manager; return *impl->profile_manager;
} }
void System::SetExitLocked(bool locked) { void System::SetExitLocked(bool locked) {
+2 -2
View File
@@ -304,10 +304,10 @@ std::optional<s64> CoreTiming::Advance() {
void CoreTiming::Reset() { void CoreTiming::Reset() {
paused = true; paused = true;
pause_event.Set();
event.Set();
if (timer_thread.joinable()) { if (timer_thread.joinable()) {
timer_thread.request_stop(); timer_thread.request_stop();
pause_event.Set();
event.Set();
timer_thread.join(); timer_thread.join();
} }
has_started = false; has_started = false;
+19 -12
View File
@@ -168,7 +168,7 @@ void CpuManager::ShutdownThread(Kernel::KernelCore& kernel) {
UNREACHABLE(); UNREACHABLE();
} }
void CpuManager::RunThread(std::stop_token stop_token, std::size_t core) { void CpuManager::RunThread(std::stop_token token, std::size_t core) {
/// Initialization /// Initialization
system.RegisterCoreThread(core); system.RegisterCoreThread(core);
std::string name = is_multicore ? ("CPUCore_" + std::to_string(core)) : std::string{"CPUThread"}; std::string name = is_multicore ? ("CPUCore_" + std::to_string(core)) : std::string{"CPUThread"};
@@ -178,19 +178,26 @@ void CpuManager::RunThread(std::stop_token stop_token, std::size_t core) {
auto& data = core_data[core]; auto& data = core_data[core];
data.host_context = Common::Fiber::ThreadToFiber(); data.host_context = Common::Fiber::ThreadToFiber();
// Cleanup
SCOPE_EXIT {
data.host_context->Exit();
};
// Running // Running
gpu_barrier->arrive_and_wait(); if (!gpu_barrier->Sync(token)) {
if (!stop_token.stop_requested()) { return;
if (!is_async_gpu && !is_multicore) {
system.GPU().ObtainContext();
}
auto& kernel = system.Kernel();
auto& scheduler = *kernel.CurrentScheduler();
auto* thread = scheduler.GetSchedulerCurrentThread();
Kernel::SetCurrentThread(kernel, thread);
Common::Fiber::YieldTo(data.host_context, *thread->GetHostContext());
} }
data.host_context->Exit();
if (!is_async_gpu && !is_multicore) {
system.GPU().ObtainContext();
}
auto& kernel = system.Kernel();
auto& scheduler = *kernel.CurrentScheduler();
auto* thread = scheduler.GetSchedulerCurrentThread();
Kernel::SetCurrentThread(kernel, thread);
Common::Fiber::YieldTo(data.host_context, *thread->GetHostContext());
} }
} // namespace Core } // namespace Core
+2 -3
View File
@@ -8,7 +8,6 @@
#include <array> #include <array>
#include <atomic> #include <atomic>
#include <barrier>
#include <functional> #include <functional>
#include <memory> #include <memory>
#include <thread> #include <thread>
@@ -53,7 +52,7 @@ public:
} }
void OnGpuReady() { void OnGpuReady() {
gpu_barrier->arrive_and_wait(); gpu_barrier->Sync();
} }
void Initialize(); void Initialize();
@@ -96,7 +95,7 @@ private:
static constexpr std::size_t max_cycle_runs = 5; static constexpr std::size_t max_cycle_runs = 5;
std::optional<std::barrier<>> gpu_barrier{}; std::optional<Common::Barrier> gpu_barrier{};
struct CoreData { struct CoreData {
std::shared_ptr<Common::Fiber> host_context; std::shared_ptr<Common::Fiber> host_context;
std::jthread host_thread; std::jthread host_thread;
+1 -2
View File
@@ -14,7 +14,6 @@
#include <tuple> #include <tuple>
#include <vector> #include <vector>
#include <boost/algorithm/string/case_conv.hpp>
#include <openssl/evp.h> #include <openssl/evp.h>
#include "common/fs/file.h" #include "common/fs/file.h"
@@ -623,7 +622,7 @@ void KeyManager::LoadFromFile(const std::filesystem::path& file_path, bool is_ti
Key128 key = Common::HexStringToArray<16>(out[1]); Key128 key = Common::HexStringToArray<16>(out[1]);
s128_keys[{S128KeyType::Titlekey, rights_id[1], rights_id[0]}] = key; s128_keys[{S128KeyType::Titlekey, rights_id[1], rights_id[0]}] = key;
} else { } else {
boost::algorithm::to_lower(out[0]); out[0] = Common::ToLower(out[0]);
if (const auto iter128 = Find128ByName(out[0]); iter128 != s128_file_id.end()) { if (const auto iter128 = Find128ByName(out[0]); iter128 != s128_file_id.end()) {
const auto& index = iter128->second; const auto& index = iter128->second;
const Key128 key = Common::HexStringToArray<16>(out[1]); const Key128 key = Common::HexStringToArray<16>(out[1]);
+5 -3
View File
@@ -6,7 +6,6 @@
#include <array> #include <array>
#include <cstring> #include <cstring>
#include <boost/algorithm/string/case_conv.hpp>
#include "common/common_funcs.h" #include "common/common_funcs.h"
#include "common/common_types.h" #include "common/common_types.h"
#include "common/hex_util.h" #include "common/hex_util.h"
@@ -42,13 +41,16 @@ static_assert(sizeof(Package2Header) == 0x200, "Package2Header has incorrect siz
const u8 PartitionDataManager::MAX_KEYBLOB_SOURCE_HASH = 32; const u8 PartitionDataManager::MAX_KEYBLOB_SOURCE_HASH = 32;
static FileSys::VirtualFile FindFileInDirWithNames(const FileSys::VirtualDir& dir, const std::string& name) { static FileSys::VirtualFile FindFileInDirWithNames(const FileSys::VirtualDir& dir,
const auto upper = boost::algorithm::to_upper_copy(name); const std::string& name) {
const auto upper = Common::ToUpper(name);
for (const auto& fname : {name, name + ".bin", upper, upper + ".BIN"}) { for (const auto& fname : {name, name + ".bin", upper, upper + ".BIN"}) {
if (dir->GetFile(fname) != nullptr) { if (dir->GetFile(fname) != nullptr) {
return dir->GetFile(fname); return dir->GetFile(fname);
} }
} }
return nullptr; return nullptr;
} }
+3 -2
View File
@@ -8,7 +8,6 @@
#include <array> #include <array>
#include <cstddef> #include <cstddef>
#include <cstring> #include <cstring>
#include <boost/algorithm/string/case_conv.hpp>
#include "common/assert.h" #include "common/assert.h"
#include "common/hex_util.h" #include "common/hex_util.h"
@@ -74,10 +73,12 @@ VirtualDir FindSubdirectoryCaseless(const VirtualDir dir, std::string_view name)
#else #else
const auto subdirs = dir->GetSubdirectories(); const auto subdirs = dir->GetSubdirectories();
for (const auto& subdir : subdirs) { for (const auto& subdir : subdirs) {
if (name == boost::algorithm::to_lower_copy(subdir->GetName())) { std::string dir_name = Common::ToLower(subdir->GetName());
if (dir_name == name) {
return subdir; return subdir;
} }
} }
return nullptr; return nullptr;
#endif #endif
} }
+4 -4
View File
@@ -8,7 +8,6 @@
#include <limits> #include <limits>
#include <random> #include <random>
#include <regex> #include <regex>
#include <boost/algorithm/string/case_conv.hpp>
#include <openssl/evp.h> #include <openssl/evp.h>
#include "common/assert.h" #include "common/assert.h"
#include "common/fs/path_util.h" #include "common/fs/path_util.h"
@@ -1415,10 +1414,11 @@ void ExternalContentProvider::ScanDirectory(const VirtualDir& dir) {
continue; continue;
} }
const auto ext = boost::to_lower_copy(filename.substr(dot_pos + 1)); const auto extension = Common::ToLower(filename.substr(dot_pos + 1));
if (ext == "nsp") {
if (extension == "nsp") {
ProcessNSP(file); ProcessNSP(file);
} else if (ext == "xci") { } else if (extension == "xci") {
ProcessXCI(file); ProcessXCI(file);
} }
} }
+4 -3
View File
@@ -109,7 +109,8 @@ VirtualFile RealVfsFilesystem::OpenFileFromEntry(std::string_view path_, std::op
auto reference = std::make_unique<FileReference>(); auto reference = std::make_unique<FileReference>();
this->InsertReferenceIntoListLocked(*reference); this->InsertReferenceIntoListLocked(*reference);
auto file = std::make_shared<RealVfsFile>(*this, std::move(reference), path, perms, size, std::move(parent_path)); auto file = std::shared_ptr<RealVfsFile>(
new RealVfsFile(*this, std::move(reference), path, perms, size, std::move(parent_path)));
cache[path] = file; cache[path] = file;
return file; return file;
@@ -176,7 +177,7 @@ bool RealVfsFilesystem::DeleteFile(std::string_view path_) {
VirtualDir RealVfsFilesystem::OpenDirectory(std::string_view path_, OpenMode perms) { VirtualDir RealVfsFilesystem::OpenDirectory(std::string_view path_, OpenMode perms) {
const auto path = FS::SanitizePath(path_, FS::DirectorySeparator::PlatformDefault); const auto path = FS::SanitizePath(path_, FS::DirectorySeparator::PlatformDefault);
return std::make_shared<RealVfsDirectory>(*this, path, perms); return std::shared_ptr<RealVfsDirectory>(new RealVfsDirectory(*this, path, perms));
} }
VirtualDir RealVfsFilesystem::CreateDirectory(std::string_view path_, OpenMode perms) { VirtualDir RealVfsFilesystem::CreateDirectory(std::string_view path_, OpenMode perms) {
@@ -184,7 +185,7 @@ VirtualDir RealVfsFilesystem::CreateDirectory(std::string_view path_, OpenMode p
if (!FS::CreateDirs(path)) { if (!FS::CreateDirs(path)) {
return nullptr; return nullptr;
} }
return std::make_shared<RealVfsDirectory>(*this, path, perms); return std::shared_ptr<RealVfsDirectory>(new RealVfsDirectory(*this, path, perms));
} }
VirtualDir RealVfsFilesystem::CopyDirectory(std::string_view old_path_, VirtualDir RealVfsFilesystem::CopyDirectory(std::string_view old_path_,
+6 -5
View File
@@ -82,9 +82,6 @@ class RealVfsFile : public VfsFile {
friend class RealVfsFilesystem; friend class RealVfsFilesystem;
public: public:
RealVfsFile(RealVfsFilesystem& base, std::unique_ptr<FileReference> reference,
const std::string& path, OpenMode perms = OpenMode::Read,
std::optional<u64> size = {}, std::optional<std::string> parent_path = {});
~RealVfsFile() override; ~RealVfsFile() override;
std::string GetName() const override; std::string GetName() const override;
@@ -98,6 +95,9 @@ public:
bool Rename(std::string_view name) override; bool Rename(std::string_view name) override;
private: private:
RealVfsFile(RealVfsFilesystem& base, std::unique_ptr<FileReference> reference,
const std::string& path, OpenMode perms = OpenMode::Read,
std::optional<u64> size = {}, std::optional<std::string> parent_path = {});
RealVfsFilesystem& base; RealVfsFilesystem& base;
std::unique_ptr<FileReference> reference; std::unique_ptr<FileReference> reference;
@@ -113,8 +113,6 @@ class RealVfsDirectory : public VfsDirectory {
friend class RealVfsFilesystem; friend class RealVfsFilesystem;
public: public:
RealVfsDirectory(RealVfsFilesystem& base, const std::string& path,
OpenMode perms = OpenMode::Read);
~RealVfsDirectory() override; ~RealVfsDirectory() override;
VirtualFile GetFileRelative(std::string_view relative_path) const override; VirtualFile GetFileRelative(std::string_view relative_path) const override;
@@ -140,6 +138,9 @@ public:
std::map<std::string, VfsEntryType, std::less<>> GetEntries() const override; std::map<std::string, VfsEntryType, std::less<>> GetEntries() const override;
private: private:
RealVfsDirectory(RealVfsFilesystem& base, const std::string& path,
OpenMode perms = OpenMode::Read);
template <typename T, typename R> template <typename T, typename R>
std::vector<std::shared_ptr<R>> IterateEntries() const; std::vector<std::shared_ptr<R>> IterateEntries() const;
+2 -3
View File
@@ -9,7 +9,6 @@
#include <regex> #include <regex>
#include <string> #include <string>
#include <boost/algorithm/string/case_conv.hpp>
#include <openssl/err.h> #include <openssl/err.h>
#include <openssl/evp.h> #include <openssl/evp.h>
@@ -64,8 +63,8 @@ NAX::NAX(VirtualFile file_)
return; return;
} }
const std::string two_dir = boost::algorithm::to_upper_copy(std::string{match[1]}); const std::string two_dir = Common::ToUpper(std::string{match[1]});
const std::string nca_id = boost::algorithm::to_lower_copy(std::string{match[2]}); const std::string nca_id = Common::ToLower(std::string{match[2]});
status = Parse(fmt::format("/registered/{}/{}.nca", two_dir, nca_id)); status = Parse(fmt::format("/registered/{}/{}.nca", two_dir, nca_id));
} }
+2 -11
View File
@@ -1,6 +1,3 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
@@ -22,14 +19,8 @@ constexpr u32 NUM_CPU_CORES = 4; // Number of CPU Cores - sync wit
// Virtual to Physical core map. // Virtual to Physical core map.
constexpr std::array<s32, Common::BitSize<u64>()> VirtualToPhysicalCoreMap{ constexpr std::array<s32, Common::BitSize<u64>()> VirtualToPhysicalCoreMap{
0, 1, 2, 3, 0, 0, 0, 0, 0, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 3,
}; };
static constexpr inline size_t NumVirtualCores = Common::BitSize<u64>(); static constexpr inline size_t NumVirtualCores = Common::BitSize<u64>();
@@ -72,11 +72,9 @@ void GlobalSchedulerContext::UnregisterDummyThreadForWakeup(KThread* thread) noe
void GlobalSchedulerContext::WakeupWaitingDummyThreads(KernelCore& kernel) noexcept { void GlobalSchedulerContext::WakeupWaitingDummyThreads(KernelCore& kernel) noexcept {
ASSERT(this->IsLocked()); ASSERT(this->IsLocked());
if (m_woken_dummy_threads.size() > 0) { for (auto* thread : m_woken_dummy_threads)
for (auto* thread : m_woken_dummy_threads) thread->DummyThreadEndWait(kernel);
thread->DummyThreadEndWait(kernel); m_woken_dummy_threads.clear();
m_woken_dummy_threads.clear();
}
} }
} // namespace Kernel } // namespace Kernel
-1
View File
@@ -59,7 +59,6 @@ Result TerminateChildren(KernelCore& kernel, KProcess* process, const KThread* t
KThread* cur_child = nullptr; KThread* cur_child = nullptr;
{ {
KScopedLightLock proc_lk(process->GetListLock()); KScopedLightLock proc_lk(process->GetListLock());
auto& thread_list = process->GetThreadList(); auto& thread_list = process->GetThreadList();
for (auto it = thread_list.begin(); it != thread_list.end(); ++it) { for (auto it = thread_list.begin(); it != thread_list.end(); ++it) {
if (KThread* thread = std::addressof(*it); thread != thread_to_not_terminate) { if (KThread* thread = std::addressof(*it); thread != thread_to_not_terminate) {
+4 -10
View File
@@ -1236,18 +1236,13 @@ namespace Kernel {
KScopedSchedulerLock sl{kernel}; KScopedSchedulerLock sl{kernel};
// Determine if this is the first termination request. // Determine if this is the first termination request.
const bool first_request = [&]() -> bool { // Perform an atomic compare-and-swap from false to true.
// Perform an atomic compare-and-swap from false to true. bool expected = false;
bool expected = false;
return m_termination_requested.compare_exchange_strong(expected, true);
}();
// If this is the first request, start termination procedure. // If this is the first request, start termination procedure.
if (first_request) { if (m_termination_requested.compare_exchange_strong(expected, true)) {
// If the thread is in initialized state, just change state to terminated. // If the thread is in initialized state, just change state to terminated.
if (this->GetState() == ThreadState::Initialized) { if (this->GetState() == ThreadState::Initialized) {
m_thread_state = ThreadState::Terminated; return m_thread_state = ThreadState::Terminated;
return ThreadState::Terminated;
} }
// Register the terminating dpc. // Register the terminating dpc.
@@ -1281,7 +1276,6 @@ namespace Kernel {
m_wait_queue->CancelWait(kernel, this, ResultTerminationRequested, true); m_wait_queue->CancelWait(kernel, this, ResultTerminationRequested, true);
} }
} }
return this->GetState(); return this->GetState();
} }
+34 -5
View File
@@ -4,7 +4,9 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
#include <thread>
#include "common/assert.h" #include "common/assert.h"
#include "common/thread.h"
#include "core/hle/kernel/k_process.h" #include "core/hle/kernel/k_process.h"
#include "core/hle/kernel/k_thread.h" #include "core/hle/kernel/k_thread.h"
#include "core/hle/kernel/k_worker_task.h" #include "core/hle/kernel/k_worker_task.h"
@@ -26,7 +28,33 @@ void KWorkerTask::DoWorkerTask(KernelCore& kernel) {
} }
} }
KWorkerTaskManager::KWorkerTaskManager() : m_waiting_thread(1, "KWorkerTaskManager") {} KWorkerTaskManager::KWorkerTaskManager(KernelCore& kernel) {
m_waiting_thread = std::jthread([&kernel, this](std::stop_token stop_token) {
Common::SetCurrentThreadName("KWorkerManager");
while (!stop_token.stop_requested()) {
KWorkerTask* t;
{
std::unique_lock lk{m_task_mutex};
m_task_cv.wait(lk, m_waiting_thread.get_stop_token(), [&]() {
return !m_task_queue.empty();
});
if (stop_token.stop_requested())
break;
t = m_task_queue.back();
m_task_queue.pop_back();
}
t->DoWorkerTask(kernel);
}
});
}
KWorkerTaskManager::~KWorkerTaskManager() {
if (m_waiting_thread.joinable()) {
m_waiting_thread.request_stop();
m_task_cv.notify_one();
m_waiting_thread.join();
}
}
void KWorkerTaskManager::AddTask(KernelCore& kernel, WorkerType type, KWorkerTask* task) { void KWorkerTaskManager::AddTask(KernelCore& kernel, WorkerType type, KWorkerTask* task) {
ASSERT(type <= WorkerType::Count); ASSERT(type <= WorkerType::Count);
@@ -35,10 +63,11 @@ void KWorkerTaskManager::AddTask(KernelCore& kernel, WorkerType type, KWorkerTas
void KWorkerTaskManager::AddTask(KernelCore& kernel, KWorkerTask* task) { void KWorkerTaskManager::AddTask(KernelCore& kernel, KWorkerTask* task) {
KScopedSchedulerLock sl(kernel); KScopedSchedulerLock sl(kernel);
m_waiting_thread.QueueWork([&kernel, task]() { {
// Do the task. std::scoped_lock lk{m_task_mutex};
task->DoWorkerTask(kernel); m_task_queue.emplace_back(task);
}); }
m_task_cv.notify_one();
} }
} // namespace Kernel } // namespace Kernel
+11 -4
View File
@@ -1,8 +1,13 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
#pragma once #pragma once
#include <condition_variable>
#include <thread>
#include "common/common_types.h" #include "common/common_types.h"
#include "common/thread_worker.h" #include "common/thread_worker.h"
@@ -18,15 +23,17 @@ public:
Count, Count,
}; };
KWorkerTaskManager(); KWorkerTaskManager(KernelCore& kernel);
~KWorkerTaskManager();
static void AddTask(KernelCore& kernel, WorkerType type, KWorkerTask* task); static void AddTask(KernelCore& kernel, WorkerType type, KWorkerTask* task);
private: private:
void AddTask(KernelCore& kernel, KWorkerTask* task); void AddTask(KernelCore& kernel, KWorkerTask* task);
std::jthread m_waiting_thread;
private: std::mutex m_task_mutex;
Common::ThreadWorker m_waiting_thread; std::condition_variable_any m_task_cv;
std::vector<KWorkerTask*> m_task_queue;
}; };
} // namespace Kernel } // namespace Kernel
+17 -12
View File
@@ -80,7 +80,10 @@ struct KernelCore::Impl {
// so it will be statically given a TLS slot anyways. // so it will be statically given a TLS slot anyways.
static inline thread_local ThreadLocalData tls_data = {}; static inline thread_local ThreadLocalData tls_data = {};
explicit Impl(Core::System& system_, KernelCore& kernel_) : system{system_} { explicit Impl(Core::System& system_, KernelCore& kernel_)
: worker_task_manager{kernel_}
, system{system_}
{
tls_data.lock = true; tls_data.lock = true;
} }
@@ -967,12 +970,16 @@ Kernel::KHardwareTimer& KernelCore::HardwareTimer() {
return *impl->hardware_timer; return *impl->hardware_timer;
} }
KAutoObjectWithListContainer& KernelCore::ObjectListContainer() { KAutoObjectWithListContainer* KernelCore::ObjectListContainer() {
return *impl->global_object_list_container; if (!impl->global_object_list_container)
return nullptr;
return std::addressof(*impl->global_object_list_container);
} }
const KAutoObjectWithListContainer& KernelCore::ObjectListContainer() const { const KAutoObjectWithListContainer* KernelCore::ObjectListContainer() const {
return *impl->global_object_list_container; if (!impl->global_object_list_container)
return nullptr;
return std::addressof(*impl->global_object_list_container);
} }
void KernelCore::PrepareReschedule(std::size_t id) { void KernelCore::PrepareReschedule(std::size_t id) {
@@ -1001,16 +1008,10 @@ void KernelCore::UnregisterInUseObject(KAutoObject* object) {
void KernelCore::RunServer(std::unique_ptr<Service::ServerManager>&& server_manager) { void KernelCore::RunServer(std::unique_ptr<Service::ServerManager>&& server_manager) {
auto* manager = server_manager.get(); auto* manager = server_manager.get();
if (!impl->is_shutting_down) {
{
std::scoped_lock lk{impl->server_lock}; std::scoped_lock lk{impl->server_lock};
if (impl->is_shutting_down) {
return;
}
impl->server_managers.emplace_back(std::move(server_manager)); impl->server_managers.emplace_back(std::move(server_manager));
} }
manager->LoopProcess(); manager->LoopProcess();
} }
@@ -1255,6 +1256,10 @@ void KernelCore::SuspendEmulation(bool suspended) {
} }
void KernelCore::ShutdownCores() { void KernelCore::ShutdownCores() {
// Notify shutdown (pre-emptively)
for (auto& sm : impl->server_managers) {
sm->NotifyShutdown();
}
impl->TerminateAllProcesses(); impl->TerminateAllProcesses();
KScopedSchedulerLock lk{*this}; KScopedSchedulerLock lk{*this};
for (auto* thread : impl->shutdown_threads) for (auto* thread : impl->shutdown_threads)
+2 -3
View File
@@ -175,9 +175,8 @@ public:
/// Stops execution of 'id' core, in order to reschedule a new thread. /// Stops execution of 'id' core, in order to reschedule a new thread.
void PrepareReschedule(std::size_t id); void PrepareReschedule(std::size_t id);
KAutoObjectWithListContainer& ObjectListContainer(); KAutoObjectWithListContainer* ObjectListContainer();
const KAutoObjectWithListContainer* ObjectListContainer() const;
const KAutoObjectWithListContainer& ObjectListContainer() const;
/// Registers all kernel objects with the global emulation state, this is purely for tracking /// Registers all kernel objects with the global emulation state, this is purely for tracking
/// leaks after emulation has been shutdown. /// leaks after emulation has been shutdown.
+4 -3
View File
@@ -151,7 +151,8 @@ public:
const bool is_initialized = this->IsInitialized(); const bool is_initialized = this->IsInitialized();
uintptr_t arg = 0; uintptr_t arg = 0;
if (is_initialized) { if (is_initialized) {
kernel.ObjectListContainer().Unregister(this); if (auto const olc = kernel.ObjectListContainer(); olc)
olc->Unregister(this);
arg = this->GetPostDestroyArgument(); arg = this->GetPostDestroyArgument();
this->Finalize(kernel); this->Finalize(kernel);
} }
@@ -175,7 +176,7 @@ public:
public: public:
static void InitializeSlabHeap(KernelCore& kernel, void* memory, size_t memory_size) { static void InitializeSlabHeap(KernelCore& kernel, void* memory, size_t memory_size) {
kernel.SlabHeap<Derived>().Initialize(memory, memory_size); kernel.SlabHeap<Derived>().Initialize(memory, memory_size);
kernel.ObjectListContainer().Initialize(); kernel.ObjectListContainer()->Initialize();
} }
static Derived* Create(KernelCore& kernel) { static Derived* Create(KernelCore& kernel) {
@@ -186,7 +187,7 @@ public:
} }
static void Register(KernelCore& kernel, Derived* obj) { static void Register(KernelCore& kernel, Derived* obj) {
return kernel.ObjectListContainer().Register(obj); return kernel.ObjectListContainer()->Register(obj);
} }
static size_t GetObjectSize(KernelCore& kernel) { static size_t GetObjectSize(KernelCore& kernel) {
+2 -6
View File
@@ -15,13 +15,9 @@ void LoopProcess(Core::System& system) {
WindowSystem window_system(system); WindowSystem window_system(system);
ButtonPoller button_poller(system, window_system); ButtonPoller button_poller(system, window_system);
EventObserver event_observer(system, window_system); EventObserver event_observer(system, window_system);
auto server_manager = std::make_unique<ServerManager>(system); auto server_manager = std::make_unique<ServerManager>(system);
server_manager->RegisterNamedService("appletAE", std::make_shared<IAllSystemAppletProxiesService>(system, window_system));
server_manager->RegisterNamedService( server_manager->RegisterNamedService("appletOE", std::make_shared<IApplicationProxyService>(system, window_system));
"appletAE", std::make_shared<IAllSystemAppletProxiesService>(system, window_system));
server_manager->RegisterNamedService(
"appletOE", std::make_shared<IApplicationProxyService>(system, window_system));
ServerManager::RunServer(std::move(server_manager)); ServerManager::RunServer(std::move(server_manager));
} }
+9 -8
View File
@@ -26,24 +26,25 @@ EventObserver::EventObserver(Core::System& system, WindowSystem& window_system)
m_window_system.SetEventObserver(this); m_window_system.SetEventObserver(this);
m_wakeup_holder.SetUserData(static_cast<uintptr_t>(UserDataTag::WakeupEvent)); m_wakeup_holder.SetUserData(static_cast<uintptr_t>(UserDataTag::WakeupEvent));
m_wakeup_holder.LinkToMultiWait(std::addressof(m_multi_wait)); m_wakeup_holder.LinkToMultiWait(std::addressof(m_multi_wait));
m_thread = std::jthread([this](std::stop_token stop_token) { system.Kernel().RunOnGuestCoreProcess("am:EventObserver", [this]() {
Common::SetCurrentThreadName("am:EventObserver"); auto const stop_token = m_stop_source.get_token();
while (!stop_token.stop_requested()) { while (!stop_token.stop_requested()) {
auto* signaled_holder = this->WaitSignaled(stop_token); auto* signaled_holder = this->WaitSignaled(stop_token);
if (!signaled_holder) if (stop_token.stop_requested() || !signaled_holder)
break; break;
this->Process(signaled_holder); this->Process(signaled_holder);
} }
m_process_cv.notify_one();
}); });
} }
EventObserver::~EventObserver() { EventObserver::~EventObserver() {
// Signal thread and wait for processing to finish. // Signal thread and wait for processing to finish.
if (m_thread.joinable()) { m_stop_source.request_stop();
// Signal thread and wait for processing to finish. m_wakeup_event.Signal(m_system.Kernel());
m_thread.request_stop(); {
m_wakeup_event.Signal(m_system.Kernel()); std::unique_lock lk{m_process_mutex};
m_thread.join(); m_process_cv.wait(lk);
} }
// Free remaining owned sessions. // Free remaining owned sessions.
+5 -1
View File
@@ -6,6 +6,8 @@
#pragma once #pragma once
#include <condition_variable>
#include <stop_token>
#include "common/polyfill_thread.h" #include "common/polyfill_thread.h"
#include "common/thread.h" #include "common/thread.h"
#include "core/hle/service/kernel_helpers.h" #include "core/hle/service/kernel_helpers.h"
@@ -62,7 +64,9 @@ private:
MultiWait m_deferred_wait_list; MultiWait m_deferred_wait_list;
// Processing thread. // Processing thread.
std::jthread m_thread{}; std::stop_source m_stop_source{};
std::mutex m_process_mutex;
std::condition_variable m_process_cv{};
}; };
} // namespace Service::AM } // namespace Service::AM
+1 -1
View File
@@ -517,7 +517,7 @@ void WindowSystem::UpdateAppletStateLocked(Applet* applet, bool is_foreground, b
// Layer ordering. Composition sorts back-to-front. Now with enums for calrity. // Layer ordering. Composition sorts back-to-front. Now with enums for calrity.
s32 z_index = Background; s32 z_index = Background;
if (is_overlay) { if (is_overlay) {
z_index = this->IsOverlayOpenLocked(*applet) ? Overlay : Background; z_index = Overlay;
} else if (inherited_foreground) { } else if (inherited_foreground) {
z_index = is_obscured ? Foreground : ForegroundVisible; z_index = is_obscured ? Foreground : ForegroundVisible;
} }
+4 -5
View File
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project // SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
@@ -90,10 +90,9 @@ static Service::PSC::Time::LocationName GetTimeZoneString(
} }
TimeManager::TimeManager(Core::System& system) TimeManager::TimeManager(Core::System& system)
: m_steady_clock_resource{system}, m_time_zone_binary{system}, m_worker{ : m_steady_clock_resource{system}, m_time_zone_binary{system}
system, , m_worker{system, m_steady_clock_resource, m_file_timestamp_worker}
m_steady_clock_resource, {
m_file_timestamp_worker} {
m_time_m = m_time_m =
system.ServiceManager().GetService<Service::PSC::Time::ServiceManager>("time:m", true); system.ServiceManager().GetService<Service::PSC::Time::ServiceManager>("time:m", true);
+151 -151
View File
@@ -20,15 +20,17 @@
namespace Service::Glue::Time { namespace Service::Glue::Time {
TimeWorker::TimeWorker(Core::System& system, StandardSteadyClockResource& steady_clock_resource, TimeWorker::TimeWorker(Core::System& system, StandardSteadyClockResource& steady_clock_resource, FileTimestampWorker& file_timestamp_worker)
FileTimestampWorker& file_timestamp_worker) : m_system{system}
: m_system{system}, m_ctx{m_system, "Glue:TimeWorker"}, m_event{m_ctx.CreateEvent( , m_ctx{m_system, "Glue:TimeWorker"}
"Glue:TimeWorker:Event")}, , m_event{m_ctx.CreateEvent("Glue:TimeWorker:Event")}
m_steady_clock_resource{steady_clock_resource}, , m_steady_clock_resource{steady_clock_resource}
m_file_timestamp_worker{file_timestamp_worker}, m_timer_steady_clock{m_ctx.CreateEvent( , m_file_timestamp_worker{file_timestamp_worker}
"Glue:TimeWorker:SteadyClockTimerEvent")}, , m_timer_steady_clock{m_ctx.CreateEvent("Glue:TimeWorker:SteadyClockTimerEvent")}
m_timer_file_system{m_ctx.CreateEvent("Glue:TimeWorker:FileTimeTimerEvent")}, , m_timer_file_system{m_ctx.CreateEvent("Glue:TimeWorker:FileTimeTimerEvent")}
m_alarm_worker{m_system, m_steady_clock_resource}, m_pm_state_change_handler{m_alarm_worker} { , m_alarm_worker{m_system, m_steady_clock_resource}
, m_pm_state_change_handler{m_alarm_worker}
{
m_timer_steady_clock_timing_event = Core::Timing::CreateEvent( m_timer_steady_clock_timing_event = Core::Timing::CreateEvent(
"Time::SteadyClockEvent", "Time::SteadyClockEvent",
[this](s64 time, [this](s64 time,
@@ -47,14 +49,16 @@ TimeWorker::TimeWorker(Core::System& system, StandardSteadyClockResource& steady
} }
TimeWorker::~TimeWorker() { TimeWorker::~TimeWorker() {
// Wait for processing to stop
m_stop_source.request_stop();
m_local_clock_event->Signal(m_system.Kernel()); m_local_clock_event->Signal(m_system.Kernel());
m_network_clock_event->Signal(m_system.Kernel()); m_network_clock_event->Signal(m_system.Kernel());
m_ephemeral_clock_event->Signal(m_system.Kernel()); m_ephemeral_clock_event->Signal(m_system.Kernel());
std::this_thread::sleep_for(std::chrono::milliseconds(16));
m_thread.request_stop();
m_event->Signal(m_system.Kernel()); m_event->Signal(m_system.Kernel());
m_thread.join(); {
std::unique_lock lk{m_process_mutex};
m_process_cv.wait(lk);
}
m_ctx.CloseEvent(m_event); m_ctx.CloseEvent(m_event);
m_system.CoreTiming().UnscheduleEvent(m_timer_steady_clock_timing_event); m_system.CoreTiming().UnscheduleEvent(m_timer_steady_clock_timing_event);
@@ -122,166 +126,162 @@ void TimeWorker::Initialize(std::shared_ptr<Service::PSC::Time::StaticService> t
} }
void TimeWorker::StartThread() { void TimeWorker::StartThread() {
m_thread = std::jthread(std::bind_front(&TimeWorker::ThreadFunc, this)); m_system.Kernel().RunOnGuestCoreProcess("TimeWorker", [this]() {
} auto const stop_token = m_stop_source.get_token();
while (!stop_token.stop_requested()) {
void TimeWorker::ThreadFunc(std::stop_token stop_token) { enum class EventType : s32 {
Common::SetCurrentThreadName("TimeWorker"); Exit = 0,
Common::SetCurrentThreadPriority(Common::ThreadPriority::Low); PowerStateChange = 1,
SignalAlarms = 2,
while (!stop_token.stop_requested()) { UpdateLocalSystemClock = 3,
enum class EventType : s32 { UpdateNetworkSystemClock = 4,
Exit = 0, UpdateEphemeralSystemClock = 5,
PowerStateChange = 1, UpdateSteadyClock = 6,
SignalAlarms = 2, UpdateFileTimestamp = 7,
UpdateLocalSystemClock = 3, AutoCorrect = 8,
UpdateNetworkSystemClock = 4, };
UpdateEphemeralSystemClock = 5, s32 index{};
UpdateSteadyClock = 6, if (m_pm_state_change_handler.m_priority != 0) {
UpdateFileTimestamp = 7, // TODO: gIPmModuleService::GetEvent() 1
AutoCorrect = 8, index = WaitAny(
}; m_system.Kernel(),
&m_event->GetReadableEvent(), // 0
s32 index{}; &m_alarm_worker.GetEvent() // 1
);
if (m_pm_state_change_handler.m_priority != 0) { } else {
// TODO: gIPmModuleService::GetEvent() 1 // TODO: gIPmModuleService::GetEvent() 1
index = WaitAny(m_system.Kernel(), index = WaitAny(
&m_event->GetReadableEvent(), // 0 m_system.Kernel(),
&m_alarm_worker.GetEvent() // 1 &m_event->GetReadableEvent(), // 0
); &m_alarm_worker.GetEvent(), // 1
} else { &m_alarm_worker.GetTimerEvent().GetReadableEvent(), // 2
// TODO: gIPmModuleService::GetEvent() 1 m_local_clock_event, // 3
index = WaitAny(m_system.Kernel(), m_network_clock_event, // 4
&m_event->GetReadableEvent(), // 0 m_ephemeral_clock_event, // 5
&m_alarm_worker.GetEvent(), // 1 &m_timer_steady_clock->GetReadableEvent(), // 6
&m_alarm_worker.GetTimerEvent().GetReadableEvent(), // 2 &m_timer_file_system->GetReadableEvent(), // 7
m_local_clock_event, // 3 m_standard_user_auto_correct_clock_event // 8
m_network_clock_event, // 4 );
m_ephemeral_clock_event, // 5
&m_timer_steady_clock->GetReadableEvent(), // 6
&m_timer_file_system->GetReadableEvent(), // 7
m_standard_user_auto_correct_clock_event // 8
);
}
switch (static_cast<EventType>(index)) {
case EventType::Exit:
return;
case EventType::PowerStateChange:
m_alarm_worker.GetEvent().Clear(m_system.Kernel());
if (m_pm_state_change_handler.m_priority <= 1) {
m_alarm_worker.OnPowerStateChanged();
} }
break;
case EventType::SignalAlarms: if (stop_token.stop_requested())
m_alarm_worker.GetTimerEvent().Clear(m_system.Kernel()); break;
m_time_m->CheckAndSignalAlarms();
break;
case EventType::UpdateLocalSystemClock: { switch (EventType(index)) {
m_local_clock_event->Clear(m_system.Kernel()); case EventType::Exit:
return;
Service::PSC::Time::SystemClockContext context{}; case EventType::PowerStateChange:
R_ASSERT(m_local_clock->GetSystemClockContext(&context)); m_alarm_worker.GetEvent().Clear(m_system.Kernel());
if (m_pm_state_change_handler.m_priority <= 1) {
m_alarm_worker.OnPowerStateChanged();
}
break;
m_set_sys->SetUserSystemClockContext(context); case EventType::SignalAlarms:
m_file_timestamp_worker.SetFilesystemPosixTime(); m_alarm_worker.GetTimerEvent().Clear(m_system.Kernel());
break; m_time_m->CheckAndSignalAlarms();
} break;
case EventType::UpdateNetworkSystemClock: { case EventType::UpdateLocalSystemClock: {
m_network_clock_event->Clear(m_system.Kernel()); m_local_clock_event->Clear(m_system.Kernel());
Service::PSC::Time::SystemClockContext context{}; Service::PSC::Time::SystemClockContext context{};
R_ASSERT(m_network_clock->GetSystemClockContext(&context)); R_ASSERT(m_local_clock->GetSystemClockContext(&context));
m_set_sys->SetNetworkSystemClockContext(context); m_set_sys->SetUserSystemClockContext(context);
m_file_timestamp_worker.SetFilesystemPosixTime();
s64 time{};
if (m_network_clock->GetCurrentTime(&time) != ResultSuccess) {
break; break;
} }
[[maybe_unused]] auto offset_before{ case EventType::UpdateNetworkSystemClock: {
m_ig_report_network_clock_context_set ? m_report_network_clock_context.offset : 0}; m_network_clock_event->Clear(m_system.Kernel());
// TODO system report "standard_netclock_operation"
// "clock_time" = time
// "context_offset_before" = offset_before
// "context_offset_after" = context.offset
m_report_network_clock_context = context;
if (!m_ig_report_network_clock_context_set) {
m_ig_report_network_clock_context_set = true;
}
m_file_timestamp_worker.SetFilesystemPosixTime(); Service::PSC::Time::SystemClockContext context{};
break; R_ASSERT(m_network_clock->GetSystemClockContext(&context));
}
case EventType::UpdateEphemeralSystemClock: { m_set_sys->SetNetworkSystemClockContext(context);
m_ephemeral_clock_event->Clear(m_system.Kernel());
Service::PSC::Time::SystemClockContext context{}; s64 time{};
auto res = m_ephemeral_clock->GetSystemClockContext(&context); if (m_network_clock->GetCurrentTime(&time) != ResultSuccess) {
if (res != ResultSuccess) { break;
}
[[maybe_unused]] auto offset_before{
m_ig_report_network_clock_context_set ? m_report_network_clock_context.offset : 0};
// TODO system report "standard_netclock_operation"
// "clock_time" = time
// "context_offset_before" = offset_before
// "context_offset_after" = context.offset
m_report_network_clock_context = context;
if (!m_ig_report_network_clock_context_set) {
m_ig_report_network_clock_context_set = true;
}
m_file_timestamp_worker.SetFilesystemPosixTime();
break; break;
} }
s64 time{}; case EventType::UpdateEphemeralSystemClock: {
res = m_ephemeral_clock->GetCurrentTime(&time); m_ephemeral_clock_event->Clear(m_system.Kernel());
if (res != ResultSuccess) {
Service::PSC::Time::SystemClockContext context{};
auto res = m_ephemeral_clock->GetSystemClockContext(&context);
if (res != ResultSuccess) {
break;
}
s64 time{};
res = m_ephemeral_clock->GetCurrentTime(&time);
if (res != ResultSuccess) {
break;
}
[[maybe_unused]] auto offset_before{m_ig_report_ephemeral_clock_context_set
? m_report_ephemeral_clock_context.offset
: 0};
// TODO system report "ephemeral_netclock_operation"
// "clock_time" = time
// "context_offset_before" = offset_before
// "context_offset_after" = context.offset
m_report_ephemeral_clock_context = context;
if (!m_ig_report_ephemeral_clock_context_set) {
m_ig_report_ephemeral_clock_context_set = true;
}
break; break;
} }
[[maybe_unused]] auto offset_before{m_ig_report_ephemeral_clock_context_set case EventType::UpdateSteadyClock:
? m_report_ephemeral_clock_context.offset m_timer_steady_clock->Clear(m_system.Kernel());
: 0};
// TODO system report "ephemeral_netclock_operation" m_steady_clock_resource.UpdateTime();
// "clock_time" = time m_time_m->SetStandardSteadyClockBaseTime(m_steady_clock_resource.GetTime());
// "context_offset_before" = offset_before break;
// "context_offset_after" = context.offset
m_report_ephemeral_clock_context = context; case EventType::UpdateFileTimestamp:
if (!m_ig_report_ephemeral_clock_context_set) { m_timer_file_system->Clear(m_system.Kernel());
m_ig_report_ephemeral_clock_context_set = true; m_file_timestamp_worker.SetFilesystemPosixTime();
break;
case EventType::AutoCorrect: {
m_standard_user_auto_correct_clock_event->Clear(m_system.Kernel());
bool automatic_correction{};
R_ASSERT(m_time_sm->IsStandardUserSystemClockAutomaticCorrectionEnabled(&automatic_correction));
Service::PSC::Time::SteadyClockTimePoint time_point{};
R_ASSERT(m_time_sm->GetStandardUserSystemClockAutomaticCorrectionUpdatedTime(&time_point));
m_set_sys->SetUserSystemClockAutomaticCorrectionEnabled(automatic_correction);
m_set_sys->SetUserSystemClockAutomaticCorrectionUpdatedTime(time_point);
break;
}
default:
UNREACHABLE();
} }
break;
} }
m_process_cv.notify_one();
case EventType::UpdateSteadyClock: });
m_timer_steady_clock->Clear(m_system.Kernel());
m_steady_clock_resource.UpdateTime();
m_time_m->SetStandardSteadyClockBaseTime(m_steady_clock_resource.GetTime());
break;
case EventType::UpdateFileTimestamp:
m_timer_file_system->Clear(m_system.Kernel());
m_file_timestamp_worker.SetFilesystemPosixTime();
break;
case EventType::AutoCorrect: {
m_standard_user_auto_correct_clock_event->Clear(m_system.Kernel());
bool automatic_correction{};
R_ASSERT(m_time_sm->IsStandardUserSystemClockAutomaticCorrectionEnabled(
&automatic_correction));
Service::PSC::Time::SteadyClockTimePoint time_point{};
R_ASSERT(
m_time_sm->GetStandardUserSystemClockAutomaticCorrectionUpdatedTime(&time_point));
m_set_sys->SetUserSystemClockAutomaticCorrectionEnabled(automatic_correction);
m_set_sys->SetUserSystemClockAutomaticCorrectionUpdatedTime(time_point);
break;
}
default:
UNREACHABLE();
}
}
} }
} // namespace Service::Glue::Time } // namespace Service::Glue::Time
+8 -4
View File
@@ -1,8 +1,12 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
#pragma once #pragma once
#include <condition_variable>
#include "common/common_types.h" #include "common/common_types.h"
#include "core/hle/kernel/k_event.h" #include "core/hle/kernel/k_event.h"
#include "core/hle/service/glue/time/alarm_worker.h" #include "core/hle/service/glue/time/alarm_worker.h"
@@ -33,17 +37,17 @@ public:
void StartThread(); void StartThread();
private:
template <typename T> template <typename T>
T GetSettingsItemValue(const std::string& category, const std::string& name); T GetSettingsItemValue(const std::string& category, const std::string& name);
void ThreadFunc(std::stop_token stop_token);
Core::System& m_system; Core::System& m_system;
KernelHelpers::ServiceContext m_ctx; KernelHelpers::ServiceContext m_ctx;
std::shared_ptr<Service::Set::ISystemSettingsServer> m_set_sys; std::shared_ptr<Service::Set::ISystemSettingsServer> m_set_sys;
std::jthread m_thread; std::mutex m_process_mutex;
std::condition_variable m_process_cv;
std::stop_source m_stop_source;
Kernel::KEvent* m_event{}; Kernel::KEvent* m_event{};
std::shared_ptr<Service::PSC::Time::ServiceManager> m_time_m; std::shared_ptr<Service::PSC::Time::ServiceManager> m_time_m;
std::shared_ptr<Service::PSC::Time::StaticService> m_time_sm; std::shared_ptr<Service::PSC::Time::StaticService> m_time_sm;
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
@@ -24,7 +24,7 @@ IReceiverService::~IReceiverService() = default;
Result IReceiverService::OpenReceiver(Out<SharedPointer<IReceiver>> out_receiver) { Result IReceiverService::OpenReceiver(Out<SharedPointer<IReceiver>> out_receiver) {
LOG_DEBUG(Service_PSC, "called"); LOG_DEBUG(Service_PSC, "called");
*out_receiver = std::make_shared<IReceiver>(system); *out_receiver = std::shared_ptr<IReceiver>(new IReceiver(system));
R_SUCCEED(); R_SUCCEED();
} }
+12 -3
View File
@@ -98,8 +98,10 @@ ServerManager::~ServerManager() {
// Signal stop. // Signal stop.
m_stop_source.request_stop(); m_stop_source.request_stop();
m_wakeup_event->Signal(m_system.Kernel()); m_wakeup_event->Signal(m_system.Kernel());
if (m_deferral_event) m_deferral_event->Signal(m_system.Kernel()); //for sm: and ports
// Wait for processing to stop.
// Wait for processing to stop. Schedule
m_stopped.Wait(); m_stopped.Wait();
m_threads.clear(); m_threads.clear();
@@ -253,6 +255,14 @@ void ServerManager::StartAdditionalHostThreads(const char* name, size_t num_thre
} }
} }
/// @brief Notifies that the system is shutting down (pre-emptively terminate threads)
void ServerManager::NotifyShutdown() {
m_stop_source.request_stop();
// Wake them up regardless
m_wakeup_event->Signal(m_system.Kernel());
if (m_deferral_event) m_deferral_event->Signal(m_system.Kernel()); //for sm: and ports
}
Result ServerManager::LoopProcess() { Result ServerManager::LoopProcess() {
SCOPE_EXIT { SCOPE_EXIT {
m_stopped.Set(); m_stopped.Set();
@@ -285,9 +295,8 @@ MultiWaitHolder* ServerManager::WaitSignaled() {
this->LinkDeferred(); this->LinkDeferred();
// If we're done, return before we start waiting. // If we're done, return before we start waiting.
if (m_stop_source.stop_requested()) { if (m_stop_source.stop_requested())
return nullptr; return nullptr;
}
auto* selected = m_multi_wait.WaitAny(m_system.Kernel()); auto* selected = m_multi_wait.WaitAny(m_system.Kernel());
if (selected == std::addressof(*m_wakeup_holder)) { if (selected == std::addressof(*m_wakeup_holder)) {
+4
View File
@@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
@@ -49,6 +52,7 @@ public:
Result LoopProcess(); Result LoopProcess();
void StartAdditionalHostThreads(const char* name, size_t num_threads); void StartAdditionalHostThreads(const char* name, size_t num_threads);
void NotifyShutdown();
static void RunServer(std::unique_ptr<ServerManager>&& server); static void RunServer(std::unique_ptr<ServerManager>&& server);
+1 -1
View File
@@ -117,7 +117,6 @@ Services::Services(std::shared_ptr<SM::ServiceManager>& sm, Core::System& system
{"friends", &Friend::LoopProcess}, {"friends", &Friend::LoopProcess},
{"settings", &Set::LoopProcess}, {"settings", &Set::LoopProcess},
{"psc", &PSC::LoopProcess}, {"psc", &PSC::LoopProcess},
{"glue", &Glue::LoopProcess},
{"grc", &GRC::LoopProcess}, {"grc", &GRC::LoopProcess},
{"hid", &HID::LoopProcess}, {"hid", &HID::LoopProcess},
{"jit", &JIT::LoopProcess}, {"jit", &JIT::LoopProcess},
@@ -153,6 +152,7 @@ Services::Services(std::shared_ptr<SM::ServiceManager>& sm, Core::System& system
{"usb", &USB::LoopProcess}, {"usb", &USB::LoopProcess},
{"i2c", &I2C::LoopProcess}, {"i2c", &I2C::LoopProcess},
{"gpio", &GPIO::LoopProcess}, {"gpio", &GPIO::LoopProcess},
{"glue", &Glue::LoopProcess},
}) })
kernel.RunOnGuestCoreProcess(std::string(e.first), [&system, f = e.second] { f(system); }); kernel.RunOnGuestCoreProcess(std::string(e.first), [&system, f = e.second] { f(system); });
} }
+4 -6
View File
@@ -305,16 +305,14 @@ SM::SM(ServiceManager& service_manager_, Core::System& system_)
SM::~SM() = default; SM::~SM() = default;
void LoopProcess(Core::System& system) { void LoopProcess(Core::System& system) {
auto& service_manager = system.ServiceManager();
auto server_manager = std::make_unique<ServerManager>(system); auto server_manager = std::make_unique<ServerManager>(system);
Kernel::KEvent* deferral_event{}; Kernel::KEvent* deferral_event{};
server_manager->ManageDeferral(&deferral_event); server_manager->ManageDeferral(&deferral_event);
service_manager.SetDeferralEvent(deferral_event); system.ServiceManager().SetDeferralEvent(deferral_event);
auto sm_service = std::make_shared<SM>(system.ServiceManager(), system); auto sm_service = std::make_shared<SM>(system.ServiceManager(), system);
server_manager->ManageNamedPort("sm:", [sm_service] { return sm_service; }); server_manager->ManageNamedPort("sm:", [sm_service] {
return sm_service;
});
ServerManager::RunServer(std::move(server_manager)); ServerManager::RunServer(std::move(server_manager));
} }
+23 -33
View File
@@ -11,6 +11,7 @@
#include "common/thread.h" #include "common/thread.h"
#include "core/core.h" #include "core/core.h"
#include "core/core_timing.h" #include "core/core_timing.h"
#include "core/hle/kernel/kernel.h"
#include "core/hle/service/vi/conductor.h" #include "core/hle/service/vi/conductor.h"
#include "core/hle/service/vi/container.h" #include "core/hle/service/vi/container.h"
#include "core/hle/service/vi/display_list.h" #include "core/hle/service/vi/display_list.h"
@@ -29,24 +30,29 @@ Conductor::Conductor(Core::System& system, Container& container, DisplayList& di
}); });
if (system.IsMulticore()) { if (system.IsMulticore()) {
m_event = Core::Timing::CreateEvent( m_event = Core::Timing::CreateEvent("ScreenComposition", [this](s64 time, std::chrono::nanoseconds ns_late) -> std::optional<std::chrono::nanoseconds> {
"ScreenComposition", m_signal.Set();
[this](s64 time, return std::chrono::nanoseconds(this->GetNextTicks());
std::chrono::nanoseconds ns_late) -> std::optional<std::chrono::nanoseconds> { });
m_signal.Set();
return std::chrono::nanoseconds(this->GetNextTicks());
});
system.CoreTiming().ScheduleLoopingEvent(FrameNs, FrameNs, m_event); system.CoreTiming().ScheduleLoopingEvent(FrameNs, FrameNs, m_event);
m_thread = std::jthread([this](std::stop_token token) { this->VsyncThread(token); }); m_thread = system.Kernel().RunOnHostCoreThread("VSyncThread", [this]() {
} else { auto const stop_token = m_thread.get_stop_token();
m_event = Core::Timing::CreateEvent( Common::SetCurrentThreadPriority(Common::ThreadPriority::VeryHigh);
"ScreenComposition", Common::SetCurrentThreadToPerformanceCores();
[this](s64 time, while (!stop_token.stop_requested()) {
std::chrono::nanoseconds ns_late) -> std::optional<std::chrono::nanoseconds> { m_signal.Wait();
if (stop_token.stop_requested() || m_system.IsShuttingDown()) {
break;
}
this->ProcessVsync(); this->ProcessVsync();
return std::chrono::nanoseconds(this->GetNextTicks()); }
}); });
} else {
m_event = Core::Timing::CreateEvent("ScreenComposition", [this](s64 time, std::chrono::nanoseconds ns_late) -> std::optional<std::chrono::nanoseconds> {
this->ProcessVsync();
return std::chrono::nanoseconds(this->GetNextTicks());
});
system.CoreTiming().ScheduleLoopingEvent(FrameNs, FrameNs, m_event); system.CoreTiming().ScheduleLoopingEvent(FrameNs, FrameNs, m_event);
} }
@@ -54,10 +60,10 @@ Conductor::Conductor(Core::System& system, Container& container, DisplayList& di
Conductor::~Conductor() { Conductor::~Conductor() {
m_system.CoreTiming().UnscheduleEvent(m_event); m_system.CoreTiming().UnscheduleEvent(m_event);
if (m_thread.joinable()) {
if (m_system.IsMulticore()) {
m_thread.request_stop(); m_thread.request_stop();
m_signal.Set(); m_signal.Set();
m_thread.join();
} }
} }
@@ -83,22 +89,6 @@ void Conductor::ProcessVsync() {
} }
} }
void Conductor::VsyncThread(std::stop_token token) {
Common::SetCurrentThreadName("VSyncThread");
Common::SetCurrentThreadPriority(Common::ThreadPriority::VeryHigh);
Common::SetCurrentThreadToPerformanceCores();
while (!token.stop_requested()) {
m_signal.Wait();
if (m_system.IsShuttingDown()) {
return;
}
this->ProcessVsync();
}
}
s64 Conductor::GetNextTicks() const { s64 Conductor::GetNextTicks() const {
const auto& settings = Settings::values; const auto& settings = Settings::values;
auto speed_scale = 1.f; auto speed_scale = 1.f;
-205
View File
@@ -1,205 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#include <regex>
#include <cctype>
#include "common/assert.h"
#include "common/logging.h"
#include "common/settings.h"
#include "common/string_util.h"
#include "common/scm_rev.h"
#include "core/core.h"
#include "core/hle/service/acc/profile_manager.h"
#include "core/launch_params.h"
#undef _UNICODE
#include <getopt.h>
#ifndef _MSC_VER
#include <unistd.h>
#endif
namespace Core {
LaunchParams ParseLaunchParams(Core::System& system, int argc, char *argv[], wchar_t *argv_w[]) noexcept {
LaunchParams p{};
int option_index = 0;
static struct option long_options[] = {
// clang-format off
{"debug", no_argument, 0, 'd'},
{"config", required_argument, 0, 'c'},
{"fullscreen", no_argument, 0, 'f'},
{"help", no_argument, 0, 'h'},
{"game", required_argument, 0, 'g'},
{"multiplayer", required_argument, 0, 'm'},
{"program", optional_argument, 0, 'p'},
{"user", required_argument, 0, 'u'},
{"version", no_argument, 0, 'v'},
{"input-profile", no_argument, 0, 'i'},
{"null-render", no_argument, 0, 'n'},
{"singlecore", no_argument, 0, 's'},
{"filter", no_argument, 0, 'x'},
{0, 0, 0, 0},
// clang-format on
};
while (optind < argc) {
if (int arg = getopt_long(argc, argv, "dc:fhg:m:p:u:vinsx", long_options, &option_index); arg != -1) {
switch (char(arg)) {
case 'd':
p.override_gdb_port = uint16_t(atoi(optarg));
break;
case 'c':
p.config_path = optarg;
break;
case 'f':
p.fullscreen = true;
LOG_INFO(Frontend, "Starting in fullscreen mode...");
break;
case 'h':
p.print_help = true;
break;
case 'g':
p.filepath = std::string(optarg);
break;
case 'i': {
p.input_profile = std::string(optarg);
break;
}
case 'm': {
p.use_multiplayer = true;
const std::string str_arg(optarg);
// regex to check if the format is nickname:password@ip:port
// with optional :password
const std::regex re("^([^:]+)(?::(.+))?@([^:]+)(?::([0-9]+))?$");
if (std::regex_match(str_arg, re)) {
std::smatch match;
std::regex_search(str_arg, match, re);
ASSERT(match.size() == 5);
p.nickname = match[1];
p.password = match[2];
p.address = match[3];
if (!match[4].str().empty()) {
p.port = u16(std::strtoul(match[4].str().c_str(), nullptr, 0));
}
std::regex nickname_re("^[a-zA-Z0-9._\\- ]+$");
ASSERT(std::regex_match(p.nickname, nickname_re) && "Nickname is not valid. Must be 4 to 20 alphanumeric characters");
ASSERT(!p.address.empty() && "Address is empty");
}
break;
}
case 'p':
p.program_args = argv[optind];
++optind;
break;
case 'u': {
// Launch game with a specific user
bool argument_ok = isdigit(optarg[0]);
p.selected_user = atoi(optarg);
if (!argument_ok) {
// try to look it up by username, only finds the first username that matches.
auto const user_idx = system.GetProfileManager().GetUserIndex(optarg);
if (user_idx != std::nullopt) {
p.selected_user = user_idx.value();
} else {
LOG_ERROR(Frontend, "Invalid user argument '{}'", optarg);
break;
}
}
if (system.GetProfileManager().UserExistsIndex(*p.selected_user)) {
Settings::values.current_user = s32(*p.selected_user);
} else {
LOG_ERROR(Frontend, "Selected user {} doesn't exist", *p.selected_user);
}
break;
}
case 'v':
p.print_version = true;
break;
case 'n':
p.force_null_render = true;
break;
case 's':
p.force_single_core = true;
break;
case 'x':
p.log_filter = argv[optind];
++optind;
break;
}
} else {
// only kept due to shortcuts made by Qt frontend which use "-qlaunch"
if (strcmp(argv[optind], "-hlaunch") == 0) {
p.launch_hlaunch = true;
} else if (strcmp(argv[optind], "-qlaunch") == 0) {
p.launch_qlaunch = true;
} else if (strcmp(argv[optind], "-setup") == 0) {
p.launch_setup = true;
} else {
// qt feeds utf8 data, sdl frontend feeds raw utf16 data
#ifdef _WIN32
p.filepath = argv_w != nullptr
? Common::UTF16ToUTF8(argv_w[optind])
: argv[optind];
#else
p.filepath = argv[optind];
#endif
}
optind++;
}
}
p.argv0 = argv[0];
return p;
}
void ApplyLaunchParams(LaunchParams const& lp) noexcept {
// apply the log_filter setting
// the logger was initialized before and doesn't pick up the filter on its own
Common::Log::Filter filter{};
filter.ParseFilterString(lp.log_filter.value_or(Settings::values.log_filter.GetValue()));
Common::Log::SetGlobalFilter(filter);
if (!lp.program_args.empty()) {
Settings::values.program_args = lp.program_args;
}
if (!lp.input_profile.empty()) {
auto& players = Settings::values.players.GetValue();
players[0].profile_name = lp.input_profile;
}
if (lp.selected_user.has_value()) {
Settings::values.current_user = std::clamp(*lp.selected_user, 0, 7);
}
if (lp.override_gdb_port.has_value()) {
Settings::values.use_gdbstub = true;
Settings::values.gdbstub_port = *lp.override_gdb_port;
}
if (lp.force_single_core) {
Settings::values.use_multi_core = false;
}
if (lp.force_null_render) {
Settings::values.renderer_backend = Settings::RendererBackend::Null;
}
if (lp.print_version) {
LOG_INFO(Frontend, "Eden {} {}", Common::g_scm_branch, Common::g_scm_desc);
}
if (lp.print_help) {
LOG_INFO(Frontend,
"Usage: {}"
" [options] <filename>\n"
"-c, --config Load the specified configuration file\n"
"-f, --fullscreen Start in fullscreen mode\n"
"-g, --game File path of the game to load\n"
"-h, --help Display this help and exit\n"
"-m, --multiplayer=nick:password@address:port"
" Nickname, password, address and port for multiplayer\n"
"-p, --program Pass following string as arguments to executable\n"
"-u, --user Select a specific user profile from 0 to 7\n"
"-d, --debug Run the GDB stub on a port from 1 to 65535\n"
"-v, --version Output version information and exit\n",
lp.argv0
);
}
}
}
-43
View File
@@ -1,43 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
#include <string>
#include <optional>
#include "common/common_types.h"
#include "network/room.h"
namespace Core {
class System;
struct LaunchParams {
std::string argv0{};
std::string filepath{};
std::string nickname{};
std::string password{};
std::string address{};
std::string input_profile{};
std::string program_args{};
std::optional<std::string> config_path{};
std::optional<int> selected_user{};
std::optional<u16> override_gdb_port{};
std::optional<std::string> log_filter{};
u16 port = Network::DefaultRoomPort;
bool use_multiplayer = false;
bool fullscreen = false;
bool force_null_render = false;
bool force_single_core = false;
// print
bool print_version = false;
bool print_help = false;
// qt
bool launch_hlaunch = false;
bool launch_qlaunch = false;
bool launch_setup = false;
};
LaunchParams ParseLaunchParams(Core::System& system, int argc, char *argv[], wchar_t *argv_w[]) noexcept;
void ApplyLaunchParams(LaunchParams const& lp) noexcept;
}
+2 -2
View File
@@ -10,7 +10,6 @@
#include <string> #include <string>
#include <concepts> #include <concepts>
#include <algorithm> #include <algorithm>
#include <boost/algorithm/string/case_conv.hpp>
#include "common/concepts.h" #include "common/concepts.h"
#include "common/fs/path_util.h" #include "common/fs/path_util.h"
#include "common/logging.h" #include "common/logging.h"
@@ -171,7 +170,8 @@ FileType GuessFromFilename(const std::string& name) {
else if (name == "00") else if (name == "00")
return FileType::NCA; return FileType::NCA;
auto const extension = boost::algorithm::to_lower_copy(std::string(Common::FS::GetExtensionFromFilename(name))); auto const extension =
Common::ToLower(std::string(Common::FS::GetExtensionFromFilename(name)));
if (extension == "nro") if (extension == "nro")
return FileType::NRO; return FileType::NRO;
else if (extension == "nso") else if (extension == "nso")
+4 -31
View File
@@ -7,14 +7,12 @@
#include <algorithm> #include <algorithm>
#include <cinttypes> #include <cinttypes>
#include <cstring> #include <cstring>
#include <span>
#include <vector> #include <vector>
#include "common/common_funcs.h" #include "common/common_funcs.h"
#include "common/hex_util.h" #include "common/hex_util.h"
#include "common/logging.h" #include "common/logging.h"
#include "common/lz4_compression.h" #include "common/lz4_compression.h"
#include "common/zbic_compression.h"
#include "common/settings.h" #include "common/settings.h"
#include "common/swap.h" #include "common/swap.h"
#include "core/core.h" #include "core/core.h"
@@ -106,36 +104,11 @@ std::optional<VAddr> AppLoader_NSO::LoadModule(Kernel::KProcess& process, Core::
for (std::size_t i = 0; i < nso_header.segments.size(); ++i) { for (std::size_t i = 0; i < nso_header.segments.size(); ++i) {
nso_file.Read(compressed_data.data(), nso_header.segments_compressed_size[i], nso_header.segments[i].offset); nso_file.Read(compressed_data.data(), nso_header.segments_compressed_size[i], nso_header.segments[i].offset);
if (nso_header.IsSegmentCompressed(i)) { if (nso_header.IsSegmentCompressed(i)) {
if (nso_header.IsZBICCompressed()) { int r = Common::Compression::DecompressDataLZ4(decompressed_size.data(), nso_header.segments[i].size, compressed_data.data(), nso_header.segments_compressed_size[i]);
// ZBIC compression ASSERT(r == int(nso_header.segments[i].size));
const int r = Common::Compression::DecompressDataZBIC( std::memcpy(codeset.memory.data() + module_start + nso_header.segments[i].location, decompressed_size.data(), nso_header.segments[i].size);
std::span<u8>{decompressed_size}.first(nso_header.segments[i].size),
std::span<const u8>{compressed_data}.first(nso_header.segments_compressed_size[i])
);
ASSERT(r > 0);
} else {
// LZ4 compression
int r = Common::Compression::DecompressDataLZ4(
decompressed_size.data(),
nso_header.segments[i].size,
compressed_data.data(),
nso_header.segments_compressed_size[i]
);
ASSERT(r == int(nso_header.segments[i].size));
}
std::memcpy(
codeset.memory.data() + module_start + nso_header.segments[i].location,
decompressed_size.data(),
nso_header.segments[i].size
);
} else { } else {
// Not compressed std::memcpy(codeset.memory.data() + module_start + nso_header.segments[i].location, compressed_data.data(), nso_header.segments[i].size);
std::memcpy(
codeset.memory.data() + module_start + nso_header.segments[i].location,
compressed_data.data(),
nso_header.segments[i].size
);
} }
codeset.segments[i].addr = module_start + nso_header.segments[i].location; codeset.segments[i].addr = module_start + nso_header.segments[i].location;
codeset.segments[i].offset = module_start + nso_header.segments[i].location; codeset.segments[i].offset = module_start + nso_header.segments[i].location;
-6
View File
@@ -1,6 +1,3 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
@@ -61,9 +58,6 @@ struct NSOHeader {
std::array<SHA256Hash, 3> segment_hashes; std::array<SHA256Hash, 3> segment_hashes;
bool IsSegmentCompressed(size_t segment_num) const; bool IsSegmentCompressed(size_t segment_num) const;
bool IsZBICCompressed() const {
return ((flags >> 7) & 1) != 0;
}
}; };
static_assert(sizeof(NSOHeader) == 0x100, "NSOHeader has incorrect size."); static_assert(sizeof(NSOHeader) == 0x100, "NSOHeader has incorrect size.");
static_assert(std::is_trivially_copyable_v<NSOHeader>, "NSOHeader must be trivially copyable."); static_assert(std::is_trivially_copyable_v<NSOHeader>, "NSOHeader must be trivially copyable.");
+14
View File
@@ -1,6 +1,15 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
// gcc14 bug
#ifdef __GNUC__
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wmaybe-uninitialized"
#endif
#include "common/assert.h" #include "common/assert.h"
#include "common/scope_exit.h" #include "common/scope_exit.h"
#include "core/memory/dmnt_cheat_types.h" #include "core/memory/dmnt_cheat_types.h"
@@ -1266,3 +1275,8 @@ void DmntCheatVm::Execute(const CheatProcessMetadata& metadata) {
} }
} // namespace Core::Memory } // namespace Core::Memory
#ifdef __GNUC__
#pragma GCC diagnostic pop
#endif
+11 -9
View File
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project // SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
@@ -254,14 +254,16 @@ struct UnrecognizedInstruction {
struct CheatVmOpcode { struct CheatVmOpcode {
bool begin_conditional_block{}; bool begin_conditional_block{};
std::variant<StoreStaticOpcode, BeginConditionalOpcode, EndConditionalOpcode, ControlLoopOpcode, std::variant<
LoadRegisterStaticOpcode, LoadRegisterMemoryOpcode, StoreStaticToAddressOpcode, std::monostate,
PerformArithmeticStaticOpcode, BeginKeypressConditionalOpcode, StoreStaticOpcode, BeginConditionalOpcode, EndConditionalOpcode, ControlLoopOpcode,
PerformArithmeticRegisterOpcode, StoreRegisterToAddressOpcode, LoadRegisterStaticOpcode, LoadRegisterMemoryOpcode, StoreStaticToAddressOpcode,
BeginRegisterConditionalOpcode, SaveRestoreRegisterOpcode, PerformArithmeticStaticOpcode, BeginKeypressConditionalOpcode,
SaveRestoreRegisterMaskOpcode, ReadWriteStaticRegisterOpcode, PauseProcessOpcode, PerformArithmeticRegisterOpcode, StoreRegisterToAddressOpcode,
ResumeProcessOpcode, DebugLogOpcode, UnrecognizedInstruction> BeginRegisterConditionalOpcode, SaveRestoreRegisterOpcode,
opcode{}; SaveRestoreRegisterMaskOpcode, ReadWriteStaticRegisterOpcode, PauseProcessOpcode,
ResumeProcessOpcode, DebugLogOpcode, UnrecognizedInstruction
> opcode{};
}; };
class DmntCheatVm { class DmntCheatVm {
+1 -2
View File
@@ -14,7 +14,6 @@
#include <regex> #include <regex>
#include <string> #include <string>
#include <thread> #include <thread>
#include <boost/algorithm/string/trim.hpp>
#ifdef _WIN32 #ifdef _WIN32
// windows.h needs to be included before shellapi.h // windows.h needs to be included before shellapi.h
@@ -133,7 +132,7 @@ static Network::Room::BanList LoadBanList(const std::string& path) {
std::string line; std::string line;
std::getline(file, line); std::getline(file, line);
line.erase(std::remove(line.begin(), line.end(), '\0'), line.end()); line.erase(std::remove(line.begin(), line.end(), '\0'), line.end());
boost::trim(line); line = Common::StripSpaces(line);
if (line.empty()) { if (line.empty()) {
// An empty line marks start of the IP ban list // An empty line marks start of the IP ban list
ban_list_type = true; ban_list_type = true;
@@ -170,13 +170,19 @@ template<>
// check for marked bit, use as unmapped if marked // check for marked bit, use as unmapped if marked
if (ctx.conf.page_table_marked_bit) { if (ctx.conf.page_table_marked_bit) {
code.bt(page, *ctx.conf.page_table_marked_bit); auto const marked_bit = *ctx.conf.page_table_marked_bit;
code.jc(abort, code.T_NEAR); if (s64(s32(1 << marked_bit)) == s64(1 << marked_bit)) {
code.test(page, s32(1 << marked_bit));
code.jnz(abort, code.T_NEAR);
} else {
code.bt(page, marked_bit);
code.jc(abort, code.T_NEAR);
}
} }
// mask away attributes // mask away attributes
if (ctx.conf.page_table_pointer_mask == 0) { if (ctx.conf.page_table_pointer_mask == 0) {
code.test(page, page); code.test(page, page);
} else if (std::in_range<s32>(ctx.conf.page_table_pointer_mask)) { } else if (s64(s32(ctx.conf.page_table_pointer_mask)) == s64(ctx.conf.page_table_pointer_mask)) {
code.and_(page, ctx.conf.page_table_pointer_mask); code.and_(page, ctx.conf.page_table_pointer_mask);
} else { } else {
code.mov(tmp, ctx.conf.page_table_pointer_mask); code.mov(tmp, ctx.conf.page_table_pointer_mask);
+18 -4
View File
@@ -761,6 +761,9 @@ void EmulatedController::StartMotionCalibration() {
} }
void EmulatedController::SetButton(const Common::Input::CallbackStatus& callback, std::size_t index, Common::UUID uuid) { void EmulatedController::SetButton(const Common::Input::CallbackStatus& callback, std::size_t index, Common::UUID uuid) {
const auto player_index = Service::HID::NpadIdTypeToIndex(npad_id_type);
const auto& player = Settings::values.players.GetValue()[player_index];
if (index >= controller.button_values.size()) { if (index >= controller.button_values.size()) {
return; return;
} }
@@ -913,10 +916,21 @@ void EmulatedController::SetButton(const Common::Input::CallbackStatus& callback
break; break;
} }
const auto player_index = Service::HID::NpadIdTypeToIndex(npad_id_type); if (!is_connected) {
const auto& player = Settings::values.players.GetValue()[player_index]; if (npad_type == NpadStyleIndex::Handheld) {
if (player.connected) { if (npad_id_type == NpadIdType::Handheld) {
Connect(); Connect();
controller_connected[player_index] = true;
}
} else if (npad_type != NpadStyleIndex::Handheld) {
if (npad_id_type == NpadIdType::Player1) {
Connect();
controller_connected[player_index] = true;
} else if (player.connected && !controller_connected[player_index]) {
Connect();
controller_connected[player_index] = true;
}
}
} }
TriggerOnChange(ControllerTriggerType::Button, true); TriggerOnChange(ControllerTriggerType::Button, true);
@@ -22,6 +22,7 @@
#include "common/settings.h" #include "common/settings.h"
#include "common/vector_math.h" #include "common/vector_math.h"
#include "hid_core/frontend/motion_input.h" #include "hid_core/frontend/motion_input.h"
#include "hid_core/hid_core.h"
#include "hid_core/hid_types.h" #include "hid_core/hid_types.h"
#include "hid_core/irsensor/irs_types.h" #include "hid_core/irsensor/irs_types.h"
@@ -584,6 +585,7 @@ private:
std::array<VibrationValue, 2> last_vibration_value{DEFAULT_VIBRATION_VALUE, std::array<VibrationValue, 2> last_vibration_value{DEFAULT_VIBRATION_VALUE,
DEFAULT_VIBRATION_VALUE}; DEFAULT_VIBRATION_VALUE};
std::array<std::chrono::steady_clock::time_point, 2> last_vibration_timepoint{}; std::array<std::chrono::steady_clock::time_point, 2> last_vibration_timepoint{};
std::array<bool, HIDCore::available_controllers> controller_connected{};
// Atomically synched values // Atomically synched values
std::atomic<HID::NpadStyleIndex> npad_type{HID::NpadStyleIndex::None}; std::atomic<HID::NpadStyleIndex> npad_type{HID::NpadStyleIndex::None};
@@ -79,8 +79,10 @@ void NpadAbstractButtonHandler::UpdateAllButtonLifo() {
Core::HID::NpadIdType npad_id = properties_handler->GetNpadId(); Core::HID::NpadIdType npad_id = properties_handler->GetNpadId();
for (std::size_t i = 0; i < AruidIndexMax; i++) { for (std::size_t i = 0; i < AruidIndexMax; i++) {
auto* data = applet_resource_holder->applet_resource->GetAruidDataByIndex(i); auto* data = applet_resource_holder->applet_resource->GetAruidDataByIndex(i);
auto& npad_entry = data->shared_memory_format->npad.npad_entry[NpadIdTypeToIndex(npad_id)]; if (auto const shfmt = data->shared_memory_format; shfmt) {
UpdateButtonLifo(npad_entry, data->aruid); auto& npad_entry = shfmt->npad.npad_entry[NpadIdTypeToIndex(npad_id)];
UpdateButtonLifo(npad_entry, data->aruid);
}
} }
} }
@@ -88,19 +90,19 @@ void NpadAbstractButtonHandler::UpdateCoreBatteryState() {
Core::HID::NpadIdType npad_id = properties_handler->GetNpadId(); Core::HID::NpadIdType npad_id = properties_handler->GetNpadId();
for (std::size_t i = 0; i < AruidIndexMax; i++) { for (std::size_t i = 0; i < AruidIndexMax; i++) {
auto* data = applet_resource_holder->applet_resource->GetAruidDataByIndex(i); auto* data = applet_resource_holder->applet_resource->GetAruidDataByIndex(i);
auto& npad_entry = data->shared_memory_format->npad.npad_entry[NpadIdTypeToIndex(npad_id)]; if (auto const shfmt = data->shared_memory_format; shfmt) {
UpdateButtonLifo(npad_entry, data->aruid); auto& npad_entry = shfmt->npad.npad_entry[NpadIdTypeToIndex(npad_id)];
UpdateButtonLifo(npad_entry, data->aruid);
}
} }
} }
void NpadAbstractButtonHandler::UpdateButtonState(u64 aruid) { void NpadAbstractButtonHandler::UpdateButtonState(u64 aruid) {
Core::HID::NpadIdType npad_id = properties_handler->GetNpadId(); Core::HID::NpadIdType npad_id = properties_handler->GetNpadId();
auto* data = applet_resource_holder->applet_resource->GetAruidData(aruid); if (auto data = applet_resource_holder->applet_resource->GetAruidData(aruid); data) {
if (data == nullptr) { auto& npad_entry = data->shared_memory_format->npad.npad_entry[NpadIdTypeToIndex(npad_id)];
return; UpdateButtonLifo(npad_entry, aruid);
} }
auto& npad_entry = data->shared_memory_format->npad.npad_entry[NpadIdTypeToIndex(npad_id)];
UpdateButtonLifo(npad_entry, aruid);
} }
Result NpadAbstractButtonHandler::SetHomeProtection(bool is_enabled, u64 aruid) { Result NpadAbstractButtonHandler::SetHomeProtection(bool is_enabled, u64 aruid) {
+10 -13
View File
@@ -45,19 +45,6 @@ NPad::NPad(Core::HID::HIDCore& hid_core_, KernelHelpers::ServiceContext& service
AbstractPad{hid_core_.kernel}, AbstractPad{hid_core_.kernel},
}} }}
{ {
for (std::size_t aruid_index = 0; aruid_index < AruidIndexMax; ++aruid_index) {
for (std::size_t i = 0; i < controller_data[aruid_index].size(); ++i) {
auto& controller = controller_data[aruid_index][i];
controller.device = hid_core.GetEmulatedControllerByIndex(i);
Core::HID::ControllerUpdateCallback engine_callback{
.on_change = [this, i, kernel = &hid_core.kernel](Core::HID::ControllerTriggerType type) {
ControllerUpdate(*kernel, type, i);
},
.is_npad_service = true,
};
controller.callback_key = controller.device->SetCallback(engine_callback);
}
}
for (std::size_t i = 0; i < abstracted_pads.size(); ++i) { for (std::size_t i = 0; i < abstracted_pads.size(); ++i) {
abstracted_pads[i].SetNpadId(IndexToNpadIdType(i)); abstracted_pads[i].SetNpadId(IndexToNpadIdType(i));
} }
@@ -106,6 +93,16 @@ Result NPad::Activate(u64 aruid) {
for (std::size_t i = 0; i < controller_data[aruid_index].size(); ++i) { for (std::size_t i = 0; i < controller_data[aruid_index].size(); ++i) {
auto& controller = controller_data[aruid_index][i]; auto& controller = controller_data[aruid_index][i];
controller.shared_memory = &data->shared_memory_format->npad.npad_entry[i].internal_state; controller.shared_memory = &data->shared_memory_format->npad.npad_entry[i].internal_state;
controller.device = hid_core.GetEmulatedControllerByIndex(i);
if (!controller.callback_key) {
Core::HID::ControllerUpdateCallback engine_callback{
.on_change = [this, i](Core::HID::ControllerTriggerType type) {
ControllerUpdate(hid_core.kernel, type, i);
},
.is_npad_service = true,
};
controller.callback_key = controller.device->SetCallback(engine_callback);
}
} }
// Prefill controller buffers // Prefill controller buffers
+4 -1
View File
@@ -226,7 +226,9 @@ std::unique_ptr<TranslationMap> InitializeTranslations(QObject* parent) {
INSERT(Settings, dma_accuracy, tr("DMA Accuracy:"), INSERT(Settings, dma_accuracy, tr("DMA Accuracy:"),
tr("Controls the DMA read mode.\nUnsafe is faster, while Safe is more stable and can fix issues in some games.\nDefault follows the GPU Accuracy setting.")); tr("Controls the DMA read mode.\nUnsafe is faster, while Safe is more stable and can fix issues in some games.\nDefault follows the GPU Accuracy setting."));
INSERT(Settings, gpu_fence_behavior, tr("GPU Fence Behavior:"), INSERT(Settings, gpu_fence_behavior, tr("GPU Fence Behavior:"),
tr("Controls the GPU fence synchronization behavior.\nImmediate is the fastest option, but can introduce some issues.\nBalanced offers better compatibility and may fix issues in some games.\nAccurate further improves compatibility at the cost of some performance.\nDefault follows the GPU Mode setting.")); tr("Controls the GPU fence synchronization behavior.\nImmediate is the fastest option, but can introduce some issues.\nBalanced offers better compatibility and may fix issues in some games.\nAccurate further improves compatibility at the cost of some performance.\nStrict is the slowest option, but can fix issues that require stricter synchronization.\nDefault follows the GPU Accuracy setting."));
INSERT(Settings, enable_gpu_buffer_readback, tr("Enable GPU buffer readback"),
tr("Preserves GPU-modified data by reading it back before uploading.\nSome games require this to render certain effects properly."));
INSERT(Settings, use_asynchronous_shaders, tr("Enable asynchronous shader compilation"), INSERT(Settings, use_asynchronous_shaders, tr("Enable asynchronous shader compilation"),
tr("May reduce shader stutter.")); tr("May reduce shader stutter."));
INSERT(Settings, gpu_clock, tr("GPU Clocks"), INSERT(Settings, gpu_clock, tr("GPU Clocks"),
@@ -440,6 +442,7 @@ std::unique_ptr<ComboboxTranslationMap> ComboboxEnumeration(QObject* parent) {
PAIR(GpuFenceBehavior, Immediate, tr("Immediate")), PAIR(GpuFenceBehavior, Immediate, tr("Immediate")),
PAIR(GpuFenceBehavior, Balanced, tr("Balanced")), PAIR(GpuFenceBehavior, Balanced, tr("Balanced")),
PAIR(GpuFenceBehavior, Accurate, tr("Accurate")), PAIR(GpuFenceBehavior, Accurate, tr("Accurate")),
PAIR(GpuFenceBehavior, Strict, tr("Strict")),
}}); }});
translations->insert( translations->insert(
{Settings::EnumMetadata<Settings::CpuAccuracy>::Index(), {Settings::EnumMetadata<Settings::CpuAccuracy>::Index(),
+1 -2
View File
@@ -8,7 +8,6 @@
#include <string> #include <string>
#include <QEventLoop> #include <QEventLoop>
#include <boost/algorithm/string/case_conv.hpp>
#include <boost/algorithm/string/replace.hpp> #include <boost/algorithm/string/replace.hpp>
#include "common/httplib.h" #include "common/httplib.h"
@@ -46,7 +45,7 @@ void DiscordImpl::Pause() {
std::string DiscordImpl::GetGameString(const std::string& title) { std::string DiscordImpl::GetGameString(const std::string& title) {
// Convert to lowercase // Convert to lowercase
std::string icon_name = boost::algorithm::to_lower_copy(title); std::string icon_name = Common::ToLower(title);
// Replace spaces with dashes // Replace spaces with dashes
std::replace(icon_name.begin(), icon_name.end(), ' ', '-'); std::replace(icon_name.begin(), icon_name.end(), ' ', '-');

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