mirror of
https://git.eden-emu.dev/eden-emu/eden.git
synced 2026-08-18 06:10:35 +00:00
Compare commits
26 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a0bc8bfde4 | |||
| e5b247db35 | |||
| 44c0815dcd | |||
| b468602ffa | |||
| bc7219fd01 | |||
| a1caf852ff | |||
| 801c472017 | |||
| 53a98f9de1 | |||
| 6bad422d08 | |||
| 3469f3789f | |||
| 221ffea4c1 | |||
| c87f6202d3 | |||
| 8ab9521cea | |||
| 612409c7ba | |||
| 54046ac60e | |||
| 39763e7321 | |||
| e69415c07b | |||
| dc1486485b | |||
| f561a10bd8 | |||
| 6272f8ab24 | |||
| eebb4bd91e | |||
| 58dee53305 | |||
| 7b13113fbe | |||
| b9a88297cb | |||
| 0133caf702 | |||
| 9d60030af5 |
Vendored
+3
-1
@@ -804,7 +804,9 @@ Particles tend to only render correctly with Accurate mode.</source>
|
||||
<source>Controls the DMA read mode.
|
||||
Unsafe is faster, while Safe is more stable and can fix issues in some games.
|
||||
Default follows the GPU Accuracy setting.</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>Controla el modo de lectura DMA.
|
||||
Inseguro es más rápido, mientras que seguro es más estable y puede solucionar fallos en algunos juegos.
|
||||
Predeterminado sigue el ajuste de la precisión de GPU.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../src/qt_common/config/shared_translation.cpp" line="227"/>
|
||||
|
||||
Vendored
+3
-1
@@ -793,7 +793,9 @@ Safe to set at 16x on most GPUs.</source>
|
||||
<source>Controls the GPU emulation mode.
|
||||
Most games render fine with Fast, but Accurate is still required for some.
|
||||
Particles tend to only render correctly with Accurate mode.</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>Керує режимом емуляції ГП.
|
||||
Більшість ігор добре візуалізуються з режимом «Швидко», але деякі ігри можуть потребувати режиму «Точно».
|
||||
Частинки зазвичай правильно візуалізуються лише з режимом «Точно».</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../src/qt_common/config/shared_translation.cpp" line="225"/>
|
||||
|
||||
@@ -27,3 +27,6 @@ There are two main applications, an SDL-based app (`eden-cli`) and a Qt based ap
|
||||
- `--user/-u`: Specify the user index.
|
||||
- `--version/-v`: Display version and quit.
|
||||
- `--input-profile/-i`: 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.
|
||||
|
||||
@@ -50,6 +50,7 @@ enum class IntSetting(override val key: String) : AbstractIntSetting {
|
||||
GPU_UNSWIZZLE_TEXTURE_SIZE("gpu_unswizzle_texture_size"),
|
||||
GPU_UNSWIZZLE_STREAM_SIZE("gpu_unswizzle_stream_size"),
|
||||
GPU_UNSWIZZLE_CHUNK_SIZE("gpu_unswizzle_chunk_size"),
|
||||
HOMEBREW_NXLINK_SERVER_MODE("homebrew_nxlink_server_mode"),
|
||||
BAT_TEMPERATURE_UNIT("bat_temperature_unit"),
|
||||
CABINET_APPLET("cabinet_applet_mode"),
|
||||
CONTROLLER_APPLET("controller_applet_mode"),
|
||||
|
||||
+9
@@ -132,6 +132,15 @@ abstract class SettingsItem(
|
||||
descriptionId = R.string.program_args_description
|
||||
)
|
||||
)
|
||||
put(
|
||||
SingleChoiceSetting(
|
||||
IntSetting.HOMEBREW_NXLINK_SERVER_MODE,
|
||||
titleId = R.string.nxlink_server_mode,
|
||||
descriptionId = R.string.nxlink_server_mode_description,
|
||||
choicesId = R.array.nxlinkServerModeEntries,
|
||||
valuesId = R.array.nxlinkServerModeValues
|
||||
)
|
||||
)
|
||||
put(
|
||||
SwitchSetting(
|
||||
BooleanSetting.RENDERER_USE_SPEED_LIMIT,
|
||||
|
||||
+2
-2
@@ -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-FileCopyrightText: 2024 yuzu Emulator Project
|
||||
@@ -169,7 +169,7 @@ class InputDialogFragment : DialogFragment() {
|
||||
NativeInput.onGamePadButtonEvent(
|
||||
controllerData.getGUID(),
|
||||
controllerData.getPort(),
|
||||
event.keyCode,
|
||||
InputHandler.getButtonIdFromEvent(event),
|
||||
action
|
||||
)
|
||||
onInputReceived(event.device)
|
||||
|
||||
+1
@@ -1288,6 +1288,7 @@ class SettingsFragmentPresenter(
|
||||
|
||||
add(ShortSetting.DEBUG_KNOBS.key)
|
||||
add(StringSetting.PROGRAM_ARGS.key)
|
||||
add(IntSetting.HOMEBREW_NXLINK_SERVER_MODE.key)
|
||||
|
||||
if (!NativeConfig.isPerGameConfigLoaded()) {
|
||||
add(HeaderSetting(R.string.gpu_logging_header))
|
||||
|
||||
@@ -49,6 +49,12 @@ object InputHandler {
|
||||
MotionEvent.AXIS_RTRIGGER
|
||||
)
|
||||
|
||||
// Currently, Android doesn't support Joy-Con D-pad buttons. We fall back to the scan code
|
||||
private const val LINUX_BUTTON_DPAD_UP = 0x220
|
||||
private const val LINUX_BUTTON_DPAD_DOWN = 0x221
|
||||
private const val LINUX_BUTTON_DPAD_LEFT = 0x222
|
||||
private const val LINUX_BUTTON_DPAD_RIGHT = 0x223
|
||||
|
||||
fun isPhysicalGameController(device: InputDevice?): Boolean {
|
||||
device ?: return false
|
||||
|
||||
@@ -87,12 +93,25 @@ object InputHandler {
|
||||
NativeInput.onGamePadButtonEvent(
|
||||
controllerData.getGUID(),
|
||||
controllerData.getPort(),
|
||||
event.keyCode,
|
||||
getButtonIdFromEvent(event),
|
||||
action
|
||||
)
|
||||
return true
|
||||
}
|
||||
|
||||
fun getButtonIdFromEvent(event: KeyEvent): Int {
|
||||
if (event.keyCode == 0) {
|
||||
return when (event.scanCode) {
|
||||
LINUX_BUTTON_DPAD_UP -> KeyEvent.KEYCODE_DPAD_UP
|
||||
LINUX_BUTTON_DPAD_DOWN -> KeyEvent.KEYCODE_DPAD_DOWN
|
||||
LINUX_BUTTON_DPAD_LEFT -> KeyEvent.KEYCODE_DPAD_LEFT
|
||||
LINUX_BUTTON_DPAD_RIGHT -> KeyEvent.KEYCODE_DPAD_RIGHT
|
||||
else -> return 0
|
||||
}
|
||||
}
|
||||
return event.keyCode
|
||||
}
|
||||
|
||||
fun dispatchGenericMotionEvent(event: MotionEvent): Boolean {
|
||||
val controllerData =
|
||||
androidControllers[event.device.controllerNumber] ?: return false
|
||||
|
||||
@@ -473,7 +473,7 @@
|
||||
<string name="advanced">متقدم</string>
|
||||
|
||||
<string name="renderer_accuracy">وضع وحدة معالجة الرسومات</string>
|
||||
<string name="renderer_accuracy_description">يتحكم في وضع محاكاة وحدة معالجة الرسومات. تعمل معظم الألعاب بشكل جيد مع وضعي سريع أو متوازن، لكن الوضع الدقيق لا يزال مطلوبًا لبعض الألعاب. تميل الجسيمات إلى العرض بشكل صحيح فقط عند استخدام الوضع الدقيق.</string>
|
||||
<string name="renderer_accuracy_description">يتحكم هذا الخيار في وضع محاكاة وحدة معالجة الرسومات. تعمل معظم الألعاب بشكل جيد مع الوضع السريع، لكن الوضع الدقيق لا يزال ضروريًا لبعضها. تميل الجسيمات إلى الظهور بشكل صحيح فقط مع الوضع الدقيق.</string>
|
||||
<string name="dma_accuracy">دقة DMA</string>
|
||||
<string name="dma_accuracy_description">يتحكم في دقة DMA. يمكن أن تؤدي الدقة الآمنة إلى حل المشكلات في بعض الألعاب، ولكنها قد تؤثر أيضًا على الأداء في بعض الحالات. إذا لم تكن متأكدًا، فاترك هذا الخيار على الإعداد الافتراضي.</string>
|
||||
<string name="gpu_fence_behavior">سلوك حاجز وحدة معالجة الرسومات</string>
|
||||
|
||||
@@ -445,7 +445,6 @@
|
||||
<string name="advanced">Pokročilé</string>
|
||||
|
||||
<string name="renderer_accuracy">Režim GPU</string>
|
||||
<string name="renderer_accuracy_description">Určuje režim emulovaného GPU. Většina her běží bez problémů v rychlém, nebo vyváženém režimu, ale některé stále vyžadují přesný režim. Částicové efekty se většinou zobrazují korektně pouze v přesném režimu. </string>
|
||||
<string name="dma_accuracy">Přesnost DMA</string>
|
||||
<string name="dma_accuracy_description">Ovládá přesnost DMA. Bezpečná přesnost může vyřešit problémy v některých hrách, ale v některých případech může také ovlivnit výkon. Pokud si nejste jisti, použijte výchozí nastavení.</string>
|
||||
<string name="anisotropic_filtering">Anizotropní filtrování</string>
|
||||
|
||||
@@ -457,7 +457,6 @@ Wird der Handheld-Modus verwendet, verringert es die Auflösung und erhöht die
|
||||
<string name="advanced">Erweitert</string>
|
||||
|
||||
<string name="renderer_accuracy">GPU-Modus</string>
|
||||
<string name="renderer_accuracy_description">Steuert den GPU-Emulationsmodus. Die meisten Spiele werden im Modus \"Fast\" oder \"Balanced\" gut gerendert, für einige ist jedoch weiterhin der Modus \"Accurate\" erforderlich. Partikel werden in der Regel nur im Modus \"Accurate\" korrekt gerendert.</string>
|
||||
<string name="dma_accuracy">DMA-Genauigkeit</string>
|
||||
<string name="dma_accuracy_description">Steuert die DMA-Präzisionsgenauigkeit. Sichere Präzision kann Probleme in einigen Spielen beheben, kann aber in einigen Fällen auch die Leistung beeinträchtigen. Im Zweifel lassen Sie dies auf Standard stehen.</string>
|
||||
<string name="anisotropic_filtering">Anisotrope Filterung</string>
|
||||
|
||||
@@ -467,7 +467,6 @@
|
||||
<string name="advanced">Avanzado</string>
|
||||
|
||||
<string name="renderer_accuracy">Modo de la GPU</string>
|
||||
<string name="renderer_accuracy_description">Controla el modo de la emulación de la GPU. La mayoría de los juegos se renderizan correctamente en los modos Rápido o Equilibrado, pero algunos requieren Preciso. Las partículas tienden a renderizarse correctamente solo con el modo Preciso.</string>
|
||||
<string name="dma_accuracy">Precisión de DMA</string>
|
||||
<string name="dma_accuracy_description">Controla la precisión de DMA. La precisión segura puede solucionar problemas en algunos juegos, pero también puede afectar al rendimiento en algunos casos. Si no está seguro, déjelo en Predeterminado.</string>
|
||||
<string name="gpu_fence_behavior">Comportamiento de vallado de la GPU</string>
|
||||
|
||||
@@ -445,7 +445,6 @@
|
||||
<string name="advanced">Zaawansowane</string>
|
||||
|
||||
<string name="renderer_accuracy">Tryb GPU</string>
|
||||
<string name="renderer_accuracy_description">Steruje trybem emulacji GPU. Większość gier renderuje się poprawnie w trybach Szybki lub Zrównoważony, ale dla niektórych nadal wymagany jest tryb Dokładny. Efekty cząsteczkowe zwykle renderują się poprawnie tylko w trybie Dokładnym.</string>
|
||||
<string name="dma_accuracy">Dokładność DMA</string>
|
||||
<string name="dma_accuracy_description">Kontroluje dokładność precyzji DMA. Bezpieczna precyzja może naprawić problemy w niektórych grach, ale w niektórych przypadkach może również wpłynąć na wydajność. Jeśli nie jesteś pewien, pozostaw wartość Domyślną.</string>
|
||||
<string name="anisotropic_filtering">Filtrowanie anizotropowe</string>
|
||||
|
||||
@@ -424,6 +424,9 @@
|
||||
<string name="cpu_accuracy">Точность ЦП</string>
|
||||
<string name="value_with_units">%1$s%2$s</string>
|
||||
|
||||
<string name="program_args">Аргументы Homebrew</string>
|
||||
<string name="program_args_description">Аргументы командной строки, переданные Homebrew при запуске (например, -noglsl)</string>
|
||||
|
||||
<!-- System settings strings -->
|
||||
<string name="device_name">Название устройства</string>
|
||||
<string name="use_docked_mode">Режим док-станции</string>
|
||||
@@ -466,9 +469,11 @@
|
||||
<string name="advanced">Расширенные</string>
|
||||
|
||||
<string name="renderer_accuracy">Режим ГПУ</string>
|
||||
<string name="renderer_accuracy_description">Управляет режимом эмуляции графического процессора. Большинство игр нормально отображаются в режимах «Быстрый» или «Сбалансированный», но для некоторых требуется режим «Точный». Частицы обычно корректно отображаются только в режиме «Точный».</string>
|
||||
<string name="renderer_accuracy_description">Управляет режимом эмуляции ГПУ. Большинство игр нормально отображаются в режиме Быстрый, но для некоторых требуется режим Точный. Частицы обычно корректно отображаются только в режиме Точный.</string>
|
||||
<string name="dma_accuracy">Точность DMA</string>
|
||||
<string name="dma_accuracy_description">Управляет точностью DMA. Безопасная точность может исправить проблемы в некоторых играх, но в некоторых случаях также может повлиять на производительность. Если не уверены, оставьте значение По умолчанию.</string>
|
||||
<string name="gpu_fence_behavior">Поведение барьеров ГПУ</string>
|
||||
<string name="gpu_fence_behavior_description">Управляет поведением синхронизации через барьеры ГПУ. Мгновенный — самый быстрый вариант, но может вызывать некоторые проблемы. Сбалансированный обеспечивает лучшую совместимость и может исправлять проблемы в некоторых играх. Точный ещё больше повышает совместимость ценой некоторого снижения производительности. Строгий — самый медленный вариант, но может исправлять проблемы, требующие более строгой синхронизации. По умолчанию соответствует настройке точности ГПУ.</string>
|
||||
<string name="anisotropic_filtering">Анизотропная фильтрация</string>
|
||||
<string name="anisotropic_filtering_description">Улучшает качество текстур под углом</string>
|
||||
<string name="vram_usage_mode">Режим VRAM</string>
|
||||
@@ -490,6 +495,8 @@
|
||||
<string name="renderer_reactive_flushing_description">Повышение точности рендеринга в некоторых играх за счет снижения производительности.</string>
|
||||
<string name="enable_buffer_history">Включить историю буфера</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_description">Включает оптимизированную привязку вершинного буфера для повышения производительности. Требует Mesa Turnip 26.0+ / QCOM. Приводит к вылету на старых версиях драйверов Turnip (25.3 и ниже).</string>
|
||||
|
||||
@@ -564,6 +571,12 @@
|
||||
<string name="gpu_log_level_description">Уровень детализации логов ГПУ (больше значение = больше деталей, выше нагрузка)</string>
|
||||
<string name="gpu_log_vulkan_calls">Записывать вызовы Vulkan API</string>
|
||||
<string name="gpu_log_vulkan_calls_description">Отслеживать все вызовы Vulkan API в кольцевом буфере</string>
|
||||
<string name="gpu_log_shader_dumps">Выгрузить SPIR-V шейдеры</string>
|
||||
<string name="gpu_log_shader_dumps_description">Сохранять перекомпилированные SPIR-V бинарные файлы (.spv) в папку дампа. Проверять с помощью spirv-dis/spirv-cross/spirv-val.</string>
|
||||
<string name="dump_guest_shaders">Выгрузить гостевые (Maxwell) шейдеры</string>
|
||||
<string name="dump_guest_shaders_description">Сохранять файлы байткода гостевых шейдеров Maxwell (*.ash) в папку дампа. Проверять с помощью nvdisasm.</string>
|
||||
<string name="dump_macros">Выгрузить макросы Maxwell</string>
|
||||
<string name="dump_macros_description">Сохранять файлы макропрограмм Maxwell (*.macro) в папку дампа. Проверять с помощью envydis.</string>
|
||||
<string name="gpu_log_memory_tracking">Отслеживать память ГПУ</string>
|
||||
<string name="gpu_log_memory_tracking_description">Мониторить выделение и освобождение памяти ГПУ</string>
|
||||
<string name="gpu_log_driver_debug">Отладочная информация драйвера</string>
|
||||
@@ -990,6 +1003,13 @@
|
||||
<string name="dma_accuracy_unsafe">Небезопасно</string>
|
||||
<string name="dma_accuracy_safe">Безопасный</string>
|
||||
|
||||
<!-- GPU Fence Behavior -->
|
||||
<string name="gpu_fence_behavior_default">По умолчанию</string>
|
||||
<string name="gpu_fence_behavior_immediate">Мгновенный</string>
|
||||
<string name="gpu_fence_behavior_balanced">Сбалансированный</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_aggressive">Агрессивный</string>
|
||||
|
||||
|
||||
@@ -469,7 +469,6 @@
|
||||
<string name="advanced">Додаткові</string>
|
||||
|
||||
<string name="renderer_accuracy">Режим ГП</string>
|
||||
<string name="renderer_accuracy_description">Керує режимом емуляції ГП. Більшість ігор добре візуалізуються з режимами «Швидко» або «Збалансовано», але деякі ігри можуть потребувати режиму «Точно». Частинки зазвичай правильно візуалізуються лише з режимом «Точно».</string>
|
||||
<string name="dma_accuracy">Точність DMA</string>
|
||||
<string name="dma_accuracy_description">Керує точністю DMA. Безпечна точність може виправити проблеми в деяких іграх, але в деяких випадках також може вплинути на продуктивність. Якщо не впевнені, залиште це значення за замовчуванням.</string>
|
||||
<string name="anisotropic_filtering">Анізотропне фільтрування</string>
|
||||
|
||||
@@ -463,7 +463,7 @@
|
||||
<string name="advanced">高级</string>
|
||||
|
||||
<string name="renderer_accuracy">GPU 模式</string>
|
||||
<string name="renderer_accuracy_description">控制 GPU 模拟模式。大多数游戏在“快速”或“均衡”模式下都能获得良好的渲染,但有些游戏仍需使用“精确”模式。粒子效果通常只有在“精确”模式下才能正确渲染。</string>
|
||||
<string name="renderer_accuracy_description">控制 GPU 模拟模式。大多数游戏在“快速”模式下渲染效果良好,但有些游戏仍需使用“精确”模式。而粒子效果通常只有在“精确”模式下才能正确渲染。</string>
|
||||
<string name="dma_accuracy">DMA 精度</string>
|
||||
<string name="dma_accuracy_description">控制 DMA 的精准度。安全精度可以修复存在于某些游戏中的问题,但在某些情况下也会对性能造成影响。如不确定,请保持“默认”。</string>
|
||||
<string name="gpu_fence_behavior">GPU 围栏行为</string>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
<string name="app_disclaimer">本軟體可執行Nintendo Switch主機的遊戲,軟體不提供遊戲和金鑰檔案。<br/> <br/>在開始之前,請先安裝您的 <<![CDATA[ <b>prod.keys</b> ]]> 檔案,<br /><br /><![CDATA[<a href=\"https://yuzu-mirror.github.io/help/quickstart\">了解更多</a>]]></string>
|
||||
<string name="notice_notification_channel_name">通知和錯誤</string>
|
||||
<string name="notice_notification_channel_description">發生錯誤時顯示通知。</string>
|
||||
<string name="notice_notification_channel_description">發生錯誤時顯示通知</string>
|
||||
<string name="notification_permission_not_granted">未授予通知權限!</string>
|
||||
<string name="app_notification_channel_description">Eden模擬器通知</string>
|
||||
<string name="app_notification_running">Eden正在執行</string>
|
||||
@@ -16,7 +16,7 @@
|
||||
<string name="value_too_high">範圍最大必須為%1$d</string>
|
||||
<string name="invalid_value">無效的範圍</string>
|
||||
|
||||
<string name="using_per_game_config">使用自定義組態中</string>
|
||||
<string name="using_per_game_config">使用個別設定中</string>
|
||||
|
||||
<!-- Input Overlay -->
|
||||
<string name="show_input_overlay">顯示虛擬按鍵</string>
|
||||
@@ -25,7 +25,7 @@
|
||||
<string name="overlay_snap_to_grid_description">編輯時將虛擬按鍵與網格對齊</string>
|
||||
<string name="overlay_grid_size">網格大小</string>
|
||||
<string name="overlay_grid_size_description">調整網格格線間距</string>
|
||||
<string name="input_overlay_behavior">行為模式</string>
|
||||
<string name="input_overlay_behavior">隱藏</string>
|
||||
<string name="overlay_auto_hide">自動隱藏虛擬按鍵</string>
|
||||
<string name="overlay_auto_hide_description">在未使用虛擬按鍵幾秒後自動隱藏</string>
|
||||
<string name="enable_input_overlay_auto_hide">啟用自動隱藏虛擬按鍵</string>
|
||||
@@ -46,7 +46,7 @@
|
||||
<string name="shaders_suffix">著色器</string>
|
||||
<string name="charging">(充電中)</string>
|
||||
|
||||
<string name="system_info_label">系統:</string>
|
||||
<string name="system_info_label">系統:</string>
|
||||
<string name="show_stats_overlay">顯示效能統計疊加層</string>
|
||||
<string name="stats_overlay_customization">自訂</string>
|
||||
<string name="stats_overlay_items">可見項目</string>
|
||||
@@ -54,18 +54,20 @@
|
||||
<string name="enable_stats_overlay_">啟用效能統計疊加層</string>
|
||||
<string name="stats_overlay_options_description">設定疊加層中顯示的資訊</string>
|
||||
<string name="show_fps">顯示FPS</string>
|
||||
<string name="show_fps_description">顯示當前幀率</string>
|
||||
<string name="show_frametime">顯示幀時間</string>
|
||||
<string name="show_fps_description">顯示當前影格率</string>
|
||||
<string name="show_frametime">顯示影格時間</string>
|
||||
<string name="show_app_ram_usage">顯示應用程式的記憶體用量</string>
|
||||
<string name="show_app_ram_usage_description">顯示模擬器正在使用的記憶體量</string>
|
||||
<string name="show_app_ram_usage_description">顯示模擬器的記憶體用量</string>
|
||||
<string name="show_system_ram_usage">顯示系統記憶體用量</string>
|
||||
<string name="show_system_ram_usage_description">顯示系統使用的記憶體量</string>
|
||||
<string name="show_system_ram_usage_description">顯示系統的記憶體用量</string>
|
||||
<string name="show_bat_temperature">顯示電池溫度</string>
|
||||
<string name="bat_temperature_unit">電池溫度單位</string>
|
||||
<string name="show_power_info">顯示電池資訊</string>
|
||||
<string name="show_power_info_description">顯示當前功耗和電池可續航時間</string>
|
||||
<string name="show_shaders_building">當著色器在編譯時顯示</string>
|
||||
<string name="show_shaders_building_description">顯示正在編譯的著色器數量</string>
|
||||
<string name="pipeline_worker_cores">管線工作執行緒</string>
|
||||
<string name="pipeline_worker_cores_description">設定用於建置 Vulkan 管線的 CPU 核心數量,設定的數值越大效能越好,但溫度也會上升更快</string>
|
||||
<string name="overlay_position">疊加層位置</string>
|
||||
<string name="overlay_position_description">選擇疊加層在畫面上的顯示位置</string>
|
||||
<string name="overlay_position_top_left">左上</string>
|
||||
@@ -78,10 +80,10 @@
|
||||
<string name="perf_overlay_background_description">為疊加層添加背景以提高可讀性</string>
|
||||
|
||||
<!-- Device Overlay settings -->
|
||||
<string name="show_soc_overlay">顯示裝置資訊浮層</string>
|
||||
<string name="enable_soc_overlay">啟用裝置浮層</string>
|
||||
<string name="soc_overlay_options">裝置浮層</string>
|
||||
<string name="soc_overlay_options_description">設定裝置浮層中顯示的資訊</string>
|
||||
<string name="show_soc_overlay">顯示裝置資訊疊加層</string>
|
||||
<string name="enable_soc_overlay">啟用裝置疊加層</string>
|
||||
<string name="soc_overlay_options">裝置疊加層</string>
|
||||
<string name="soc_overlay_options_description">設定裝置疊加層中顯示的資訊</string>
|
||||
|
||||
<string name="show_build_id">顯示Eden的組建版本</string>
|
||||
<string name="show_driver_version">顯示圖形驅動程式的版本</string>
|
||||
@@ -92,10 +94,10 @@
|
||||
|
||||
<!-- Eden\'s Veil -->
|
||||
<string name="buffer_reorder_disable">停用緩衝區重新排序</string>
|
||||
<string name="buffer_reorder_disable_description">勾選時,停用映射記憶體上傳的重新排序功能,允許將上傳與特定繪製關聯。某些情況下可能會降低效能。</string>
|
||||
<string name="buffer_reorder_disable_description">勾選時,停用映射記憶體上傳的重新排序功能,允許將上傳與特定繪製關聯。某些情況下可能會降低效能</string>
|
||||
|
||||
<string name="use_sync_core">同步核心速度</string>
|
||||
<string name="use_sync_core_description">將核心速度與最大速度百分比同步,在不改變遊戲實際速度的情況下提高效能。</string>
|
||||
<string name="use_sync_core_description">將核心速度與最大速度百分比同步,在不改變遊戲實際速度的情況下提高效能</string>
|
||||
<string name="cpuopt_unsafe_host_mmu">啟用主機 MMU 模擬</string>
|
||||
<string name="cpuopt_unsafe_host_mmu_description">此最佳化可加速來賓程式的記憶體存取。啟用後,來賓記憶體讀取/寫入將直接在記憶體中執行並利用主機的 MMU。停用此功能將強制所有記憶體存取使用軟體 MMU 模擬。</string>
|
||||
<string name="debug_knobs">偵錯開關</string>
|
||||
@@ -171,7 +173,7 @@
|
||||
<string name="multiplayer_ban">封鎖使用者</string>
|
||||
<string name="multiplayer_room_browser">公開房間</string>
|
||||
<string name="multiplayer_no_rooms_found">未找到公開房間</string>
|
||||
<string name="multiplayer_password_required">需輸入密碼</string>
|
||||
<string name="multiplayer_password_required">需要輸入密碼</string>
|
||||
<string name="multiplayer_player_count">:%1$d/%2$d</string>
|
||||
<string name="multiplayer_game">遊戲</string>
|
||||
<string name="multiplayer_no_game_info">任意遊戲</string>
|
||||
@@ -190,7 +192,7 @@
|
||||
<string name="multiplayer_nickname_invalid">使用者名稱無效,請在系統→網路中檢查設定</string>
|
||||
<string name="multiplayer_token_error">必須為48個字元,且僅包含小寫字母a-z</string>
|
||||
<string name="multiplayer_port_error">埠號需為1-65535</string>
|
||||
<string name="cancel">取消</string>
|
||||
<string name="cancel">略過</string>
|
||||
<string name="ok">確定</string>
|
||||
<string name="refresh">重新整理</string>
|
||||
<string name="room_list">房間列表</string>
|
||||
@@ -244,21 +246,21 @@
|
||||
<string name="home_search_games">搜尋遊戲</string>
|
||||
<string name="search_settings">搜尋設定</string>
|
||||
<string name="install_prod_keys">安裝 prod.keys</string>
|
||||
<string name="install_prod_keys_description">需要解密零售遊戲</string>
|
||||
<string name="install_prod_keys_description">需要用來解密零售遊戲</string>
|
||||
<string name="install_prod_keys_warning">跳過安裝金鑰?</string>
|
||||
<string name="install_prod_keys_warning_description">模擬零售遊戲需要有效的金鑰,若要繼續,將僅有自製遊戲可以運作。</string>
|
||||
<string name="install_prod_keys_warning_description">模擬零售遊戲需要有效的金鑰,如果不安裝將僅有自製遊戲可以運作</string>
|
||||
<string name="install_prod_keys_warning_help">https://yuzu-mirror.github.io/help/quickstart/#guide-introduction</string>
|
||||
<string name="install_firmware_warning">跳過安裝韌體?</string>
|
||||
<string name="emulator_data">設定模擬器資料</string>
|
||||
<string name="emulator_data_description">模擬器需要金鑰才能正常執行,同時建議安裝韌體以啟動QLaunch小程式</string>
|
||||
<string name="permissions">授予權限</string>
|
||||
<string name="permissions_description">授予權限以使用模擬器的特定功能</string>
|
||||
<string name="install_firmware_warning_description">許多遊戲需要韌體才能正常運作。</string>
|
||||
<string name="install_firmware_warning_description">許多遊戲需要韌體才能正常運作</string>
|
||||
<string name="install_firmware_warning_help">https://yuzu-mirror.github.io/help/quickstart/#guide-introduction</string>
|
||||
<string name="notifications">通知</string>
|
||||
<string name="notifications_description">使用下方的按鈕授予通知權限。</string>
|
||||
<string name="notifications_description">使用下方的按鈕授予通知權限</string>
|
||||
<string name="permission_denied">權限遭拒</string>
|
||||
<string name="permission_denied_description">您曾多次拒絕了權限要求,現在您需要在系統設定中手動授予權限。</string>
|
||||
<string name="permission_denied_description">您多次拒絕了要求的權限,現在您需要在系統設定中手動授予</string>
|
||||
<string name="about">關於</string>
|
||||
<string name="about_description">組建版本、製作群、以及更多</string>
|
||||
<string name="system_information">裝置資訊</string>
|
||||
@@ -285,7 +287,7 @@
|
||||
<string name="warning_skip">跳過</string>
|
||||
<string name="warning_cancel">取消</string>
|
||||
<string name="install_amiibo_keys">安裝 Amiibo 金鑰</string>
|
||||
<string name="install_amiibo_keys_description">需要在遊戲中使用 Amiibo</string>
|
||||
<string name="install_amiibo_keys_description">若要在遊戲中使用 Amiibo 得先安裝</string>
|
||||
<string name="gpu_driver_fetcher">GPU驅動程式下載器</string>
|
||||
<string name="gpu_driver_manager">GPU 驅動程式管理員</string>
|
||||
<string name="install_gpu_driver_description">安裝替代驅動程式以取得潛在的更佳效能或準確度</string>
|
||||
@@ -300,14 +302,14 @@
|
||||
<string name="notification_no_directory_link">無法開啟 Eden 目錄</string>
|
||||
<string name="notification_no_directory_link_description">請使用檔案管理員的側邊面板手動定位到使用者資料夾。</string>
|
||||
<string name="manage_save_data">管理儲存資料</string>
|
||||
<string name="manage_save_data_description">已找到儲存資料,請選取下方的選項。</string>
|
||||
<string name="manage_save_data_description">導入/導出遊戲的儲存資料</string>
|
||||
<string name="import_save_warning">導入儲存資料</string>
|
||||
<string name="import_save_warning_description">這將會以提供的檔案覆寫所有現有的儲存資料,您確定要繼續嗎?</string>
|
||||
<string name="import_save_warning_description">這將會以提供的檔案覆寫現有的該遊戲儲存資料,您確定要繼續嗎?</string>
|
||||
<string name="save_files_importing">正在導入儲存資料…</string>
|
||||
<string name="save_files_exporting">正在導出儲存資料…</string>
|
||||
<string name="save_file_imported_success">已成功導入</string>
|
||||
<string name="save_file_invalid_zip_structure">無效的儲存目錄結構</string>
|
||||
<string name="save_file_invalid_zip_structure_description">首個子資料夾名稱必須為遊戲標題 ID。</string>
|
||||
<string name="save_file_invalid_zip_structure_description">首個子資料夾名稱必須為遊戲 ID</string>
|
||||
<string name="install_firmware">安裝韌體</string>
|
||||
<string name="install_firmware_description">韌體必須為 ZIP 壓縮檔,將會用於部分遊戲的啟動</string>
|
||||
<string name="firmware_installing">正在安裝韌體</string>
|
||||
@@ -317,12 +319,15 @@
|
||||
<string name="share_log">分享偵錯記錄</string>
|
||||
<string name="share_log_description">分享 Eden 的記錄檔以便對相關問題進行偵錯</string>
|
||||
<string name="share_log_missing">找不到日誌檔案</string>
|
||||
<string name="share_gpu_log">分享 GPU 日誌</string>
|
||||
<string name="share_gpu_log_description">分享 Eden 的 GPU 日誌以對圖形問題進行偵錯</string>
|
||||
<string name="share_gpu_log_missing">找不到 GPU 日誌檔案</string>
|
||||
<string name="install_game_content">安裝遊戲內容</string>
|
||||
<string name="install_game_content_description">安裝遊戲更新或 DLC</string>
|
||||
<string name="installing_game_content">正在安裝內容…</string>
|
||||
<string name="install_game_content_failure">安裝檔案至 NAND 時發生錯誤</string>
|
||||
<string name="install_game_content_failure_description">請確保內容有效並且 prod.keys 檔案已安裝。</string>
|
||||
<string name="install_game_content_failure_base">為避免可能的衝突,無法直接安裝遊戲本體。</string>
|
||||
<string name="install_game_content_failure_description">請確保內容有效並且 prod.keys 檔案已安裝</string>
|
||||
<string name="install_game_content_failure_base">為避免產生衝突,無法直接安裝遊戲本體</string>
|
||||
<string name="install_game_content_failed_count">%1$d 安裝錯誤</string>
|
||||
<string name="install_game_content_success">遊戲內容已成功安裝</string>
|
||||
<string name="install_game_content_success_install">%1$d 安裝成功</string>
|
||||
@@ -331,12 +336,12 @@
|
||||
<string name="custom_driver_not_supported">您的裝置不支援自訂驅動程式</string>
|
||||
<string name="custom_driver_not_supported_description">此裝置不支援注入自訂驅動程式。\n請以後再來查看是否已新增支援!</string>
|
||||
<string name="manage_yuzu_data">管理 Eden 資料</string>
|
||||
<string name="manage_yuzu_data_description">安裝韌體、金鑰,導入/導出使用者資料及安裝其他項目!</string>
|
||||
<string name="manage_yuzu_data_description">安裝韌體、金鑰,導入/導出使用者資料及安裝其它項目!</string>
|
||||
<string name="game_folders">遊戲資料夾</string>
|
||||
<string name="deep_scan">深度掃描</string>
|
||||
<string name="add_game_folder">新增遊戲資料夾</string>
|
||||
<string name="folder_already_added">這個資料夾已經新增過了!</string>
|
||||
<string name="game_folder_properties">遊戲資料夾屬性</string>
|
||||
<string name="game_folder_properties">遊戲資料夾設定</string>
|
||||
<plurals name="saves_import_failed">
|
||||
<item quantity="other">%d 個存檔導入失敗</item>
|
||||
</plurals>
|
||||
@@ -347,28 +352,30 @@
|
||||
<string name="verify_installed_content">驗證已安裝內容的完整性</string>
|
||||
<string name="verify_installed_content_description">檢查所有已安裝的內容是否有損壞</string>
|
||||
|
||||
<string name="keys_missing">缺少解密金鑰</string>
|
||||
<string name="keys_missing">缺少 prod.keys</string>
|
||||
<string name="keys_missing_description">無法解密韌體和零售遊戲</string>
|
||||
<string name="keys_missing_help">https://yuzu-mirror.github.io/help/quickstart/#dumping-decryption-keys</string>
|
||||
|
||||
<string name="uninstall_firmware">解除安裝韌體</string>
|
||||
<string name="uninstall_firmware_description">解除安裝韌體將從裝置中刪除它並可能影響遊戲相容性。</string>
|
||||
<string name="uninstall_firmware_description">解除安裝韌體將從裝置中刪除它並可能影響遊戲相容性</string>
|
||||
<string name="firmware_uninstalling">正在解除安裝韌體...</string>
|
||||
<string name="firmware_uninstalled_success">韌體解除安裝成功</string>
|
||||
|
||||
|
||||
<string name="keys_failed">金鑰安裝失敗</string>
|
||||
<string name="keys_install_success">金鑰安裝成功</string>
|
||||
<string name="error_keys_copy_failed">一個或多個金鑰複製失敗。</string>
|
||||
<string name="error_keys_copy_failed">一個或多個金鑰安裝失敗</string>
|
||||
<string name="error_keys_invalid_filename">請確保金鑰檔案具有.keys副檔名後重試。</string>
|
||||
<string name="error_keys_failed_init">金鑰初始化失敗。請檢查您的轉儲工具是否為最新版本並重新轉儲金鑰。</string>
|
||||
|
||||
<!-- Applet launcher strings -->
|
||||
<string name="qlaunch_applet">Qlaunch</string>
|
||||
<string name="qlaunch_description">從系統主畫面啟動應用程式</string>
|
||||
<string name="qlaunch_description">從Switch系統主畫面啟動應用程式(目前僅支援英文)</string>
|
||||
<string name="applets">小程式啟動器</string>
|
||||
<string name="applets_description">使用已安裝的韌體啟動系統小程式</string>
|
||||
<string name="applets_error_firmware">未安裝韌體</string>
|
||||
<string name="applets_error_applet">無法使用小程式</string>
|
||||
<string name="applets_error_description"><![CDATA[請確定您的<a href=\"https://yuzu-mirror.github.io/help/quickstart/#dumping-prodkeys-and-titlekeys\">prod.keys</a>檔案和<a href=\"https://yuzu-mirror.github.io/help/quickstart/#dumping-system-firmware\">韌體</a>已安裝並重試]]></string>
|
||||
<string name="album_applet">相簿</string>
|
||||
<string name="album_applet_description">使用系統相片檢視器查看儲存在使用者螢幕截圖資料夾中的影像</string>
|
||||
<string name="mii_edit_applet">Mii 編輯</string>
|
||||
@@ -377,47 +384,58 @@
|
||||
<string name="cabinet_applet_description">編輯、刪除儲存在 amiibo 上的資料</string>
|
||||
<string name="cabinet_launcher">Cabinet 啟動器</string>
|
||||
<string name="cabinet_nickname_and_owner">暱稱和擁有者設定</string>
|
||||
<string name="cabinet_game_data_eraser">遊戲資料橡皮擦</string>
|
||||
<string name="cabinet_game_data_eraser">刪除遊戲資料</string>
|
||||
<string name="cabinet_restorer">還原程式</string>
|
||||
<string name="cabinet_formatter">格式化程式</string>
|
||||
|
||||
<!-- About screen strings -->
|
||||
<string name="gaia_is_not_real">Gaia不是真的</string>
|
||||
<string name="gaia_is_not_real">Gaia 不存在</string>
|
||||
<string name="copied_to_clipboard">已複製到剪貼簿</string>
|
||||
<string name="about_app_description">一個開放原始碼的 Switch 模擬器</string>
|
||||
<string name="contributors">參與者</string>
|
||||
<string name="contributors_description">這些人讓 Eden Android 版成為可能</string>
|
||||
<string name="licenses_description">這些專案使 Eden Android 版成為可能</string>
|
||||
<string name="build">組建版本</string>
|
||||
<string name="user_data">使用者資料</string>
|
||||
<string name="user_data_description">導入/導出所有應用程式資料。\n\n導入使用者資料時,現有的使用者資料將被取代!\n\n直接從 Citron 導入資料可能會出現問題,建議手動導入所有所需資料。</string>
|
||||
<string name="user_data_description">導入/導出所有應用程式資料。\n\n導入使用者資料時,現有的使用者資料將被取代!\n\n直接從 Citron 導入使用者資料可能會出現問題,建議手動導入所有所需資料。</string>
|
||||
<string name="exporting_user_data">正在導出使用者資料…</string>
|
||||
<string name="importing_user_data">正在導入使用者資料…</string>
|
||||
<string name="invalid_yuzu_backup">無效的 Eden 備份</string>
|
||||
<string name="user_data_export_success">使用者資料導出成功</string>
|
||||
<string name="user_data_import_success">使用者資料導入成功</string>
|
||||
<string name="user_data_export_cancelled">導出已取消</string>
|
||||
<string name="user_data_import_failed_description">請確保使用者資料夾位於 zip 壓縮檔的根目錄,並在 config/config.ini 路徑中包含組態檔案,並再試一次。</string>
|
||||
<string name="user_data_import_failed_description">請確保使用者資料夾位於 zip 壓縮檔的根目錄,並在 config/config.ini 路徑中包含組態檔案,並再試一次</string>
|
||||
<!-- General settings strings -->
|
||||
<string name="frame_limit_enable">限制速度</string>
|
||||
<string name="frame_limit_enable_description">將模擬速度限制在標準速度的指定百分比。</string>
|
||||
<string name="frame_limit_enable">限制模擬速度</string>
|
||||
<string name="frame_limit_enable_description">將模擬速度限制在標準速度的指定百分比</string>
|
||||
<string name="frame_limit_slider">限制速度百分比</string>
|
||||
<string name="frame_limit_slider_description">指定限制模擬速度的百分比。100% 為標準速度,更高或更低的值將會增加或減少速度限制。</string>
|
||||
<string name="frame_limit_slider_description">指定模擬速度的百分比。100% 為標準速度,更高或更低的值將會減少或增加速度限制</string>
|
||||
<string name="turbo_speed_limit">加速模式</string>
|
||||
<string name="turbo_speed_limit_description">當開啟加速模式時,模擬器將以此速度執行</string>
|
||||
<string name="slow_speed_limit">慢速模式</string>
|
||||
<string name="slow_speed_limit_description">當慢速模式開啟時,模擬器將會以此速度執行</string>
|
||||
<string name="cpu_backend">CPU 後端</string>
|
||||
<string name="cpu_accuracy">CPU 準確度</string>
|
||||
<string name="value_with_units">%1$s%2$s</string>
|
||||
|
||||
<string name="program_args">Homebrew 參數</string>
|
||||
<string name="program_args_description">在啟動時傳遞給 Homebrew 的命令列參數(例如:-noglsl)</string>
|
||||
|
||||
<!-- System settings strings -->
|
||||
<string name="device_name">裝置名稱</string>
|
||||
<string name="use_docked_mode">底座模式</string>
|
||||
<string name="use_docked_mode_description">提高解析度,降低效能。停用後將會使用手提模式,會降低解析度並提高效能。</string>
|
||||
<string name="use_docked_mode_description">提高解析度,降低效能。停用後將會使用手提模式,會降低解析度並提高效能</string>
|
||||
<string name="emulated_region">模擬區域</string>
|
||||
<string name="emulated_language">模擬語言</string>
|
||||
<string name="select_rtc_date">選擇 RTC 日期</string>
|
||||
<string name="select_rtc_time">選擇 RTC 時間</string>
|
||||
<string name="use_custom_rtc">自訂 RTC</string>
|
||||
<string name="use_custom_rtc_description">允許您設定與您的目前系統時間相互獨立的自訂時間。</string>
|
||||
<string name="use_custom_rtc_description">允許您設定與您的目前系統時間相互獨立的自訂時間</string>
|
||||
<string name="set_custom_rtc">設定自訂 RTC</string>
|
||||
|
||||
<!-- CPU -->
|
||||
<string name="fast_cpu_time">CPU 超頻</string>
|
||||
<string name="fast_cpu_time_description">強制模擬 CPU 以更高的時脈運作,減少某些 FPS 限制。使用 加速 (1700MHz) 以 Switch 的最高原生時脈執行,或 高速 (2000MHz) 以雙倍時脈執行</string>
|
||||
<string name="custom_cpu_ticks">自訂CPU時脈</string>
|
||||
<string name="custom_cpu_ticks_description">自訂CPU時脈。更高的值可能提高效能,但也可能導致遊戲卡死。建議範圍為77-21000。</string>
|
||||
<string name="cpu_ticks">時脈</string>
|
||||
@@ -428,33 +446,41 @@
|
||||
|
||||
<!-- Network settings strings -->
|
||||
<string name="web_token">網路令牌</string>
|
||||
<string name="web_token_description">用於建立公開大廳的網路令牌。它是由48個小寫字母a-z組成的字串。</string>
|
||||
<string name="web_token_description">用於建立公開大廳的網路令牌。由48個小寫字母a-z組成</string>
|
||||
<string name="web_username">網路使用者名稱</string>
|
||||
<string name="web_username_description">多人遊戲房間中顯示的使用者名稱。必須為4-20個字元,僅能使用英文字母、數字、句點、破折號、底線和空格(標點符號須為英文格式)。</string>
|
||||
<string name="web_username_description">多人遊戲房間中顯示的使用者名稱。必須為4-20個字元,僅能使用英文字母、數字、句點、破折號、底線和空格(標點符號須為英文格式)</string>
|
||||
<string name="network">網路</string>
|
||||
|
||||
<!-- Graphics settings strings -->
|
||||
<string name="renderer_resolution">解析度 (手提/底座)</string>
|
||||
<string name="renderer_vsync">垂直同步</string>
|
||||
<string name="renderer_scaling_filter">視窗適應過濾器</string>
|
||||
<string name="renderer_scaling_filter">視窗自適應濾波器</string>
|
||||
<string name="fsr_sharpness">FSR/SGSR 銳化度</string>
|
||||
<string name="fsr_sharpness_description">使用 FSR/SGSR 時圖片的銳化程度</string>
|
||||
<string name="renderer_anti_aliasing">抗鋸齒</string>
|
||||
|
||||
|
||||
<string name="advanced">進階</string>
|
||||
|
||||
<string name="renderer_accuracy">GPU 模式</string>
|
||||
<string name="renderer_accuracy_description">設定 GPU 模擬的準確度。大多數遊戲在設定為快速時即可正常渲染,但有些需要渲染粒子的遊戲仍需設為準確來避免圖形錯誤</string>
|
||||
<string name="dma_accuracy">DMA 準確度</string>
|
||||
<string name="dma_accuracy_description">控制 DMA 準確度。安全準確度可以修復某些遊戲中的問題,但在某些情況下也可能影響效能。如果不確定,請保留為「預設」。</string>
|
||||
<string name="dma_accuracy_description">控制 DMA 準確度。將準確度設為穩定可以修復某些遊戲中的問題,但在某些情況下也可能影響效能。如果不確定,請保留為預設</string>
|
||||
<string name="gpu_fence_behavior">GPU 同步柵欄模式</string>
|
||||
<string name="gpu_fence_behavior_description">控制 GPU 同步柵欄模式。即時是速度最快的選項,但可能會導致一些問題。使用平衡可以提供更好的相容性並修復某些遊戲中的錯誤。準確則是在犧牲部分效能的情況下進一步提升相容性。嚴格是最慢的選項,但可以修復那些對同步要求更嚴格的遊戲中的問題。預設則會依照 GPU 準確度設定</string>
|
||||
<string name="anisotropic_filtering">各向異性過濾</string>
|
||||
<string name="anisotropic_filtering_description">改善斜角檢視時的紋理品質</string>
|
||||
<string name="vram_usage_mode">VRAM使用模式</string>
|
||||
<string name="vram_usage_mode_description">控制GPU記憶體的分配與釋放策略</string>
|
||||
<string name="accelerate_astc">ASTC解碼方式</string>
|
||||
<string name="accelerate_astc_description">選擇ASTC壓縮紋理的解碼方式:CPU(慢速、安全)、GPU(快速、推薦)或CPU非同步(無卡頓,可能導致問題)</string>
|
||||
<string name="accelerate_astc_description">選擇ASTC壓縮紋理的解碼方式:CPU(慢速、穩定)、GPU(快速、推薦)或CPU非同步(無卡頓,可能導致問題)</string>
|
||||
|
||||
<string name="sync_memory_operations">同步記憶體操作</string>
|
||||
<string name="sync_memory_operations_description">確保計算和記憶體操作之間的資料一致性。 此選項應能修復某些遊戲中的問題,但在某些情況下可能會降低效能。 使用Unreal Engine 4的遊戲似乎受影響最大。</string>
|
||||
<string name="sync_memory_operations_description">確保計算和記憶體操作之間的資料一致性。 此選項應能修復某些遊戲中的問題,但在某些情況下可能會降低效能。 使用Unreal Engine 4的遊戲似乎受影響最大</string>
|
||||
<string name="use_disk_shader_cache">磁碟著色器快取</string>
|
||||
<string name="use_disk_shader_cache_description">將產生的著色器快取儲存至硬碟以減少中斷。</string>
|
||||
<string name="use_disk_shader_cache_description">將產生的著色器快取儲存至硬碟以減少中斷</string>
|
||||
<string name="renderer_force_max_clock">強制使用最大時脈 (僅限Adreno)</string>
|
||||
<string name="renderer_force_max_clock_description">強制 GPU 以可能的最大時脈執行 (熱溫限制仍會被套用)。</string>
|
||||
<string name="renderer_force_max_clock_description">強制 GPU 以可能的最大時脈執行 (熱溫限制仍會被套用)</string>
|
||||
<string name="renderer_reactive_flushing">使用重新啟用排清</string>
|
||||
<string name="renderer_reactive_flushing_description">犧牲效能,以改善部分遊戲的轉譯準確度。</string>
|
||||
<string name="skip_cpu_inner_invalidation">跳過CPU內部失效處理</string>
|
||||
@@ -603,7 +629,7 @@
|
||||
<string name="import_complete">導入完成</string>
|
||||
<string name="use_global_setting">使用全域設定</string>
|
||||
<string name="operation_completed_successfully">操作已成功完成</string>
|
||||
<string name="confirm">確定</string>
|
||||
<string name="confirm">下載</string>
|
||||
<string name="load">載入</string>
|
||||
<string name="save">儲存</string>
|
||||
|
||||
@@ -821,8 +847,8 @@
|
||||
|
||||
<!-- Memory Layouts -->
|
||||
<string name="memory_4gb">4GB (推薦)</string>
|
||||
<string name="memory_6gb">6GB (不安全)</string>
|
||||
<string name="memory_8gb">8GB (不安全)</string>
|
||||
<string name="memory_6gb">6GB (不穩定)</string>
|
||||
<string name="memory_8gb">8GB (不穩定)</string>
|
||||
|
||||
<!--CPU clock speeds-->
|
||||
<string name="clock_boost">加速 (1700MHz)</string>
|
||||
@@ -847,8 +873,8 @@
|
||||
|
||||
<!-- DMA Accuracy -->
|
||||
<string name="dma_accuracy_default">預設</string>
|
||||
<string name="dma_accuracy_unsafe">不安全</string>
|
||||
<string name="dma_accuracy_safe">安全</string>
|
||||
<string name="dma_accuracy_unsafe">不穩定</string>
|
||||
<string name="dma_accuracy_safe">穩定</string>
|
||||
|
||||
<string name="vram_usage_conservative">保守</string>
|
||||
<string name="vram_usage_aggressive">積極</string>
|
||||
@@ -874,7 +900,7 @@
|
||||
|
||||
<!-- CPU Accuracy -->
|
||||
<string name="cpu_accuracy_accurate">高準確度</string>
|
||||
<string name="cpu_accuracy_unsafe">低準確度(不安全)</string>
|
||||
<string name="cpu_accuracy_unsafe">低準確度(不穩定)</string>
|
||||
<string name="cpu_accuracy_paranoid">不合理</string>
|
||||
<string name="cpu_accuracy_debugging">偵錯</string>
|
||||
|
||||
|
||||
@@ -630,6 +630,16 @@
|
||||
<item>3</item>
|
||||
</integer-array>
|
||||
|
||||
<string-array name="nxlinkServerModeEntries">
|
||||
<item>Disabled</item>
|
||||
<item>Eden Log</item>
|
||||
</string-array>
|
||||
|
||||
<integer-array name="nxlinkServerModeValues">
|
||||
<item>0</item>
|
||||
<item>1</item>
|
||||
</integer-array>
|
||||
|
||||
<string-array name="installKeysResults">
|
||||
<item>""</item>
|
||||
<item>""</item>
|
||||
|
||||
@@ -436,6 +436,8 @@
|
||||
|
||||
<string name="program_args">Homebrew Args</string>
|
||||
<string name="program_args_description">Command-line arguments passed to homebrew at launch (e.g. -noglsl).</string>
|
||||
<string name="nxlink_server_mode">nxlink Server</string>
|
||||
<string name="nxlink_server_mode_description">Starts a local nxlink server for homebrew stdout/stderr streams.</string>
|
||||
|
||||
<!-- System settings strings -->
|
||||
<string name="device_name">Device name</string>
|
||||
|
||||
@@ -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-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||
@@ -147,7 +147,7 @@ Result OpusDecoder::DecodeInterleavedForMultiStream(u32* out_data_size, u64* out
|
||||
auto* header_p{reinterpret_cast<const OpusPacketHeader*>(input_data.data())};
|
||||
OpusPacketHeader header{ReverseHeader(*header_p)};
|
||||
|
||||
LOG_TRACE(Service_Audio, "header size {:#X} input data size 0x{:X} in_data size 0x{:X}",
|
||||
LOG_TRACE(Service_Audio, "header size {:#x} input data size {:#x} in_data size {:#x}",
|
||||
header.size, input_data.size_bytes(), in_data.size_bytes());
|
||||
|
||||
R_UNLESS(in_data.size_bytes() >= header.size &&
|
||||
|
||||
@@ -369,7 +369,7 @@ Result InfoUpdater::UpdateMixes(MixContext& mix_context, const u32 mix_buffer_co
|
||||
if (mix_count < 0 || mix_count > 0x100) {
|
||||
LOG_ERROR(
|
||||
Service_Audio,
|
||||
"Invalid mix count from dirty parameter: count={}, magic=0x{:X}, expected_size={}",
|
||||
"Invalid mix count from dirty parameter: count={}, magic={:#x}, expected_size={}",
|
||||
mix_count, in_dirty_params->magic, in_header->mix_size);
|
||||
return Service::Audio::ResultInvalidUpdateInfo;
|
||||
}
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: 2021 Skyline Team and Contributors
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
@@ -43,13 +46,13 @@ MAP_MEMBER(void)::MapLocked(VaType virt, PaType phys, VaType size, ExtraBlockInf
|
||||
|
||||
if (virt_end > va_limit) {
|
||||
ASSERT_MSG(false,
|
||||
"Trying to map a block past the VA limit: virt_end: 0x{:X}, va_limit: 0x{:X}",
|
||||
"Trying to map a block past the VA limit: virt_end: {:#x}, va_limit: {:#x}",
|
||||
virt_end, va_limit);
|
||||
}
|
||||
|
||||
auto block_end_successor{std::lower_bound(blocks.begin(), blocks.end(), virt_end)};
|
||||
if (block_end_successor == blocks.begin()) {
|
||||
ASSERT_MSG(false, "Trying to map a block before the VA start: virt_end: 0x{:X}", virt_end);
|
||||
ASSERT_MSG(false, "Trying to map a block before the VA start: virt_end: {:#x}", virt_end);
|
||||
}
|
||||
|
||||
auto block_end_predecessor{std::prev(block_end_successor)};
|
||||
@@ -124,7 +127,7 @@ MAP_MEMBER(void)::MapLocked(VaType virt, PaType phys, VaType size, ExtraBlockInf
|
||||
|
||||
// Check that the start successor is either the end block or something in between
|
||||
if (block_start_successor->virt > virt_end) {
|
||||
ASSERT_MSG(false, "Unsorted block in AS map: virt: 0x{:X}", block_start_successor->virt);
|
||||
ASSERT_MSG(false, "Unsorted block in AS map: virt: {:#x}", block_start_successor->virt);
|
||||
} else if (block_start_successor->virt == virt_end) {
|
||||
// We need to create a new block as there are none spare that we would overwrite
|
||||
blocks.insert(block_start_successor, Block(virt, phys, extra_info));
|
||||
@@ -150,13 +153,13 @@ MAP_MEMBER(void)::UnmapLocked(VaType virt, VaType size) {
|
||||
|
||||
if (virt_end > va_limit) {
|
||||
ASSERT_MSG(false,
|
||||
"Trying to map a block past the VA limit: virt_end: 0x{:X}, va_limit: 0x{:X}",
|
||||
"Trying to map a block past the VA limit: virt_end: {:#x}, va_limit: {:#x}",
|
||||
virt_end, va_limit);
|
||||
}
|
||||
|
||||
auto block_end_successor{std::lower_bound(blocks.begin(), blocks.end(), virt_end)};
|
||||
if (block_end_successor == blocks.begin()) {
|
||||
ASSERT_MSG(false, "Trying to unmap a block before the VA start: virt_end: 0x{:X}",
|
||||
ASSERT_MSG(false, "Trying to unmap a block before the VA start: virt_end: {:#x}",
|
||||
virt_end);
|
||||
}
|
||||
|
||||
@@ -257,7 +260,7 @@ MAP_MEMBER(void)::UnmapLocked(VaType virt, VaType size) {
|
||||
auto block_start_successor{std::next(block_start_predecessor)};
|
||||
|
||||
if (block_start_successor->virt > virt_end) {
|
||||
ASSERT_MSG(false, "Unsorted block in AS map: virt: 0x{:X}", block_start_successor->virt);
|
||||
ASSERT_MSG(false, "Unsorted block in AS map: virt: {:#x}", block_start_successor->virt);
|
||||
} else if (block_start_successor->virt == virt_end) {
|
||||
// There are no blocks between the start and the end that would let us skip inserting a new
|
||||
// one for head
|
||||
@@ -333,22 +336,22 @@ ALLOC_MEMBER(VaType)::Allocate(VaType size) {
|
||||
current_linear_alloc_end = alloc_start + size;
|
||||
} else { // If linear allocation overflows the AS then find a gap
|
||||
if (this->blocks.size() <= 2) {
|
||||
ASSERT_MSG(false, "Unexpected allocator state!");
|
||||
}
|
||||
|
||||
auto search_predecessor{std::next(this->blocks.begin())};
|
||||
auto search_successor{std::next(search_predecessor)};
|
||||
|
||||
while (search_successor != this->blocks.end() &&
|
||||
(search_successor->virt - search_predecessor->virt < size ||
|
||||
search_predecessor->Mapped())) {
|
||||
search_predecessor = search_successor++;
|
||||
}
|
||||
|
||||
if (search_successor != this->blocks.end()) {
|
||||
alloc_start = search_predecessor->virt;
|
||||
alloc_start = virt_start;
|
||||
} else {
|
||||
return {}; // AS is full
|
||||
auto search_predecessor{std::next(this->blocks.begin())};
|
||||
auto search_successor{std::next(search_predecessor)};
|
||||
|
||||
while (search_successor != this->blocks.end() &&
|
||||
(search_successor->virt - search_predecessor->virt < size ||
|
||||
search_predecessor->Mapped())) {
|
||||
search_predecessor = search_successor++;
|
||||
}
|
||||
|
||||
if (search_successor != this->blocks.end()) {
|
||||
alloc_start = search_predecessor->virt;
|
||||
} else {
|
||||
return {}; // AS is full
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -361,6 +364,10 @@ ALLOC_MEMBER(void)::AllocateFixed(VaType virt, VaType size) {
|
||||
}
|
||||
|
||||
ALLOC_MEMBER(void)::Free(VaType virt, VaType size) {
|
||||
const VaType virt_end = virt + size;
|
||||
this->Unmap(virt, size);
|
||||
if (virt_end >= virt && virt_end == current_linear_alloc_end) {
|
||||
current_linear_alloc_end = virt < virt_start ? virt_start : virt;
|
||||
}
|
||||
}
|
||||
} // namespace Common
|
||||
|
||||
+62
-1
@@ -4,6 +4,8 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <cerrno>
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
#include "common/assert.h"
|
||||
@@ -15,8 +17,10 @@
|
||||
#include "common/logging.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <fcntl.h>
|
||||
#include <io.h>
|
||||
#include <share.h>
|
||||
#include <windows.h>
|
||||
#else
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
@@ -95,10 +99,65 @@ namespace {
|
||||
case FileShareFlag::ShareWriteOnly:
|
||||
return _SH_DENYRD;
|
||||
case FileShareFlag::ShareReadWrite:
|
||||
case FileShareFlag::ShareReadWriteDelete:
|
||||
return _SH_DENYNO;
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] std::FILE* OpenWithWindowsShareDelete(const fs::path& path, FileAccessMode mode,
|
||||
FileType type) {
|
||||
DWORD desired_access{};
|
||||
DWORD creation_disposition{OPEN_EXISTING};
|
||||
int open_flags = type == FileType::BinaryFile ? _O_BINARY : _O_TEXT;
|
||||
|
||||
switch (mode) {
|
||||
case FileAccessMode::Read:
|
||||
desired_access = GENERIC_READ;
|
||||
open_flags |= _O_RDONLY;
|
||||
break;
|
||||
case FileAccessMode::Write:
|
||||
desired_access = GENERIC_WRITE;
|
||||
creation_disposition = CREATE_ALWAYS;
|
||||
open_flags |= _O_WRONLY;
|
||||
break;
|
||||
case FileAccessMode::Append:
|
||||
desired_access = GENERIC_WRITE;
|
||||
creation_disposition = OPEN_ALWAYS;
|
||||
open_flags |= _O_WRONLY | _O_APPEND;
|
||||
break;
|
||||
case FileAccessMode::ReadWrite:
|
||||
desired_access = GENERIC_READ | GENERIC_WRITE;
|
||||
open_flags |= _O_RDWR;
|
||||
break;
|
||||
case FileAccessMode::ReadAppend:
|
||||
desired_access = GENERIC_READ | GENERIC_WRITE;
|
||||
creation_disposition = OPEN_ALWAYS;
|
||||
open_flags |= _O_RDWR | _O_APPEND;
|
||||
break;
|
||||
}
|
||||
|
||||
const auto handle =
|
||||
CreateFileW(path.c_str(), desired_access,
|
||||
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr,
|
||||
creation_disposition, FILE_ATTRIBUTE_NORMAL, nullptr);
|
||||
if (handle == INVALID_HANDLE_VALUE) {
|
||||
errno = EACCES;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const auto fd = _open_osfhandle(reinterpret_cast<intptr_t>(handle), open_flags);
|
||||
if (fd == -1) {
|
||||
CloseHandle(handle);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto* const file = _wfdopen(fd, AccessModeToWStr(mode, type));
|
||||
if (file == nullptr) {
|
||||
_close(fd);
|
||||
}
|
||||
return file;
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
/**
|
||||
@@ -254,7 +313,9 @@ void IOFile::Open(const fs::path& path, FileAccessMode mode, FileType type, File
|
||||
errno = 0;
|
||||
|
||||
#ifdef _WIN32
|
||||
if (flag != FileShareFlag::ShareNone) {
|
||||
if (flag == FileShareFlag::ShareReadWriteDelete) {
|
||||
file = OpenWithWindowsShareDelete(path, mode, type);
|
||||
} else if (flag != FileShareFlag::ShareNone) {
|
||||
file = _wfsopen(path.c_str(), AccessModeToWStr(mode, type), ToWindowsFileShareFlag(flag));
|
||||
} else {
|
||||
_wfopen_s(&file, path.c_str(), AccessModeToWStr(mode, type));
|
||||
|
||||
@@ -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-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
||||
@@ -49,10 +49,11 @@ enum class FileType {
|
||||
};
|
||||
|
||||
enum class FileShareFlag {
|
||||
ShareNone, // Provides exclusive access to the file.
|
||||
ShareReadOnly, // Provides read only shared access to the file.
|
||||
ShareWriteOnly, // Provides write only shared access to the file.
|
||||
ShareReadWrite, // Provides read and write shared access to the file.
|
||||
ShareNone, // Provides exclusive access to the file.
|
||||
ShareReadOnly, // Provides read only shared access to the file.
|
||||
ShareWriteOnly, // Provides write only shared access to the file.
|
||||
ShareReadWrite, // Provides read and write shared access to the file.
|
||||
ShareReadWriteDelete, // Provides read, write, and delete shared access to the file.
|
||||
};
|
||||
|
||||
enum class DirEntryFilter {
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: 2013 Dolphin Emulator Project
|
||||
// SPDX-FileCopyrightText: 2014 Citra Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
@@ -12,7 +15,7 @@ std::vector<u8> HexStringToVector(std::string_view str, bool little_endian) {
|
||||
for (std::size_t i = str.size() - 2; i <= str.size(); i -= 2)
|
||||
out[i / 2] = (ToHexNibble(str[i]) << 4) | ToHexNibble(str[i + 1]);
|
||||
} else {
|
||||
for (std::size_t i = 0; i < str.size(); i += 2)
|
||||
for (std::size_t i = 0; i + 1 < str.size(); i += 2)
|
||||
out[i / 2] = (ToHexNibble(str[i]) << 4) | ToHexNibble(str[i + 1]);
|
||||
}
|
||||
return out;
|
||||
|
||||
+12
-1
@@ -702,7 +702,15 @@ struct Values {
|
||||
|
||||
// Controls
|
||||
InputSetting<std::array<PlayerInput, 10>> players;
|
||||
|
||||
Setting<bool> disable_wgi_xinput{
|
||||
linkage, false, "disable_wgi_xinput", Category::Controls, Specialization::Default,
|
||||
// Only read/write disable_wgi_xinput on Windows platforms
|
||||
#ifdef _WIN32
|
||||
true
|
||||
#else
|
||||
false
|
||||
#endif
|
||||
};
|
||||
Setting<bool> enable_raw_input{
|
||||
linkage, false, "enable_raw_input", Category::Controls, Specialization::Default,
|
||||
// Only read/write enable_raw_input on Windows platforms
|
||||
@@ -832,6 +840,9 @@ struct Values {
|
||||
Setting<bool> gpu_log_driver_debug{linkage, true, "gpu_log_driver_debug", Category::Debugging};
|
||||
Setting<s32> gpu_log_ring_buffer_size{linkage, 512, "gpu_log_ring_buffer_size",
|
||||
Category::Debugging};
|
||||
Setting<HomebrewNxlinkServerMode> homebrew_nxlink_server_mode{
|
||||
linkage, HomebrewNxlinkServerMode::Disabled, "homebrew_nxlink_server_mode",
|
||||
Category::Debugging};
|
||||
|
||||
SwitchableSetting<u16, true> debug_knobs{linkage,
|
||||
0,
|
||||
|
||||
@@ -159,6 +159,7 @@ ENUM(GpuUnswizzleChunk, VeryLow, Low, Normal, Medium, High)
|
||||
ENUM(TemperatureUnits, Celsius, Fahrenheit)
|
||||
ENUM(ExtendedDynamicState, Disabled, EDS1, EDS2, EDS3);
|
||||
ENUM(GpuLogLevel, Off, Errors, Standard, Verbose, All)
|
||||
ENUM(HomebrewNxlinkServerMode, Disabled, EdenLog, HostStdout, File)
|
||||
ENUM(GameListMode, TreeView, GridView, CarouselView);
|
||||
ENUM(SpeedMode, Standard, Turbo, Slow);
|
||||
|
||||
|
||||
@@ -1139,6 +1139,8 @@ add_library(core STATIC
|
||||
launch_timestamp_cache.h
|
||||
loader/deconstructed_rom_directory.cpp
|
||||
loader/deconstructed_rom_directory.h
|
||||
loader/homebrew_nxlink.cpp
|
||||
loader/homebrew_nxlink.h
|
||||
loader/kip.cpp
|
||||
loader/kip.h
|
||||
loader/loader.cpp
|
||||
|
||||
+5
-5
@@ -52,6 +52,7 @@
|
||||
#include "core/hle/service/set/system_settings_server.h"
|
||||
#include "core/hle/service/sm/sm.h"
|
||||
#include "core/internal_network/network.h"
|
||||
#include "core/loader/homebrew_nxlink.h"
|
||||
#include "core/loader/loader.h"
|
||||
#include "core/memory.h"
|
||||
#include "core/memory/cheat_engine.h"
|
||||
@@ -264,9 +265,7 @@ struct System::Impl {
|
||||
|
||||
// Setting changes may require a full system reinitialization (e.g., disabling multicore).
|
||||
ReinitializeIfNecessary(system);
|
||||
|
||||
kernel.Initialize();
|
||||
cpu_manager.Initialize();
|
||||
}
|
||||
|
||||
SystemResultStatus SetupForApplicationProcess(System& system, Frontend::EmuWindow& emu_window) {
|
||||
@@ -293,9 +292,7 @@ struct System::Impl {
|
||||
return SystemResultStatus::Success;
|
||||
}
|
||||
|
||||
SystemResultStatus Load(System& system, Frontend::EmuWindow& emu_window,
|
||||
const std::string& filepath,
|
||||
Service::AM::FrontendAppletParameters& params) {
|
||||
SystemResultStatus Load(System& system, Frontend::EmuWindow& emu_window, const std::string& filepath, Service::AM::FrontendAppletParameters& params) {
|
||||
InitializeKernel(system);
|
||||
|
||||
const auto file = GetGameFileFromPath(virtual_filesystem, filepath);
|
||||
@@ -342,6 +339,8 @@ struct System::Impl {
|
||||
ShutdownMainProcess();
|
||||
return init_result;
|
||||
}
|
||||
// Waiting for GPU before initializing CPU
|
||||
cpu_manager.Initialize();
|
||||
|
||||
// Initialize cheat engine
|
||||
if (cheat_engine) {
|
||||
@@ -399,6 +398,7 @@ struct System::Impl {
|
||||
|
||||
stop_event.request_stop();
|
||||
core_timing.SyncPause(false);
|
||||
Loader::HomebrewNxlink::StopServer();
|
||||
Network::CancelPendingSocketOperations();
|
||||
kernel.SuspendEmulation(true);
|
||||
kernel.CloseServices();
|
||||
|
||||
@@ -431,7 +431,7 @@ void DeviceMemoryManager<Traits>::ReadBlock(DAddr address, void* dest_pointer, s
|
||||
[&](size_t copy_amount, DAddr current_vaddr) {
|
||||
LOG_ERROR(
|
||||
HW_Memory,
|
||||
"Unmapped Device ReadBlock @ 0x{:016X} (start address = 0x{:016X}, size = {})",
|
||||
"Unmapped Device ReadBlock @ {:#016x} (start address = {:#016x}, size = {})",
|
||||
current_vaddr, address, size);
|
||||
std::memset(dest_pointer, 0, copy_amount);
|
||||
},
|
||||
@@ -450,7 +450,7 @@ void DeviceMemoryManager<Traits>::WriteBlock(DAddr address, const void* src_poin
|
||||
[&](size_t copy_amount, DAddr current_vaddr) {
|
||||
LOG_ERROR(
|
||||
HW_Memory,
|
||||
"Unmapped Device WriteBlock @ 0x{:016X} (start address = 0x{:016X}, size = {})",
|
||||
"Unmapped Device WriteBlock @ {:#016x} (start address = {:#016x}, size = {})",
|
||||
current_vaddr, address, size);
|
||||
},
|
||||
[&](size_t copy_amount, u8* const dst_ptr) {
|
||||
@@ -489,7 +489,7 @@ void DeviceMemoryManager<Traits>::ReadBlockUnsafe(DAddr address, void* dest_poin
|
||||
[&](size_t copy_amount, DAddr current_vaddr) {
|
||||
LOG_ERROR(
|
||||
HW_Memory,
|
||||
"Unmapped Device ReadBlock @ 0x{:016X} (start address = 0x{:016X}, size = {})",
|
||||
"Unmapped Device ReadBlock @ {:#016x} (start address = {:#016x}, size = {})",
|
||||
current_vaddr, address, size);
|
||||
std::memset(dest_pointer, 0, copy_amount);
|
||||
},
|
||||
@@ -509,7 +509,7 @@ void DeviceMemoryManager<Traits>::WriteBlockUnsafe(DAddr address, const void* sr
|
||||
[&](size_t copy_amount, DAddr current_vaddr) {
|
||||
LOG_ERROR(
|
||||
HW_Memory,
|
||||
"Unmapped Device WriteBlock @ 0x{:016X} (start address = 0x{:016X}, size = {})",
|
||||
"Unmapped Device WriteBlock @ {:#016x} (start address = {:#016x}, size = {})",
|
||||
current_vaddr, address, size);
|
||||
},
|
||||
[&](size_t copy_amount, u8* const dst_ptr) {
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
namespace FileSys {
|
||||
|
||||
enum class OpenMode : u32 {
|
||||
Default = 0,
|
||||
Read = (1 << 0),
|
||||
Write = (1 << 1),
|
||||
AllowAppend = (1 << 2),
|
||||
|
||||
@@ -256,7 +256,7 @@ void IPSwitchCompiler::Parse() {
|
||||
const auto& patch_line = lines[++i];
|
||||
|
||||
// Patch line may contain comments
|
||||
if (StartsWith(patch_line, "//")) {
|
||||
if (StartsWith(patch_line, "//") || StartsWith(patch_line, "#")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -304,7 +304,7 @@ void IPSwitchCompiler::Parse() {
|
||||
|
||||
if (print_values) {
|
||||
LOG_INFO(Loader,
|
||||
"[IPSwitchCompiler ('{}')] - Patching value at offset 0x{:08X} "
|
||||
"[IPSwitchCompiler ('{}')] - Patching value at offset {:#08x} "
|
||||
"with byte string '{}'",
|
||||
patch_text->GetName(), offset, Common::HexToString(replace));
|
||||
}
|
||||
|
||||
@@ -181,11 +181,11 @@ const ProgramMetadata::KernelCapabilityDescriptors& ProgramMetadata::GetKernelCa
|
||||
|
||||
void ProgramMetadata::Print() const {
|
||||
LOG_DEBUG(Service_FS, "Magic: {:.4}", npdm_header.magic.data());
|
||||
LOG_DEBUG(Service_FS, "Main thread priority: 0x{:02X}", npdm_header.main_thread_priority);
|
||||
LOG_DEBUG(Service_FS, "Main thread priority: {:#02x}", npdm_header.main_thread_priority);
|
||||
LOG_DEBUG(Service_FS, "Main thread core: {}", npdm_header.main_thread_cpu);
|
||||
LOG_DEBUG(Service_FS, "Main thread stack size: {:#X} bytes", npdm_header.main_stack_size);
|
||||
LOG_DEBUG(Service_FS, "Main thread stack size: {:#x} bytes", npdm_header.main_stack_size);
|
||||
LOG_DEBUG(Service_FS, "Process category: {}", npdm_header.process_category);
|
||||
LOG_DEBUG(Service_FS, "Flags: 0x{:02X}", npdm_header.flags);
|
||||
LOG_DEBUG(Service_FS, "Flags: {:#02x}", npdm_header.flags);
|
||||
LOG_DEBUG(Service_FS, " > 64-bit instructions: {}",
|
||||
npdm_header.has_64_bit_instructions ? "YES" : "NO");
|
||||
|
||||
@@ -209,15 +209,15 @@ void ProgramMetadata::Print() const {
|
||||
|
||||
// Begin ACID printing (potential perms, signed)
|
||||
LOG_DEBUG(Service_FS, "Magic: {:.4}", acid_header.magic.data());
|
||||
LOG_DEBUG(Service_FS, "Flags: 0x{:02X}", acid_header.flags);
|
||||
LOG_DEBUG(Service_FS, "Flags: {:#02x}", acid_header.flags);
|
||||
LOG_DEBUG(Service_FS, " > Is Retail: {}", acid_header.production_flag ? "YES" : "NO");
|
||||
LOG_DEBUG(Service_FS, "Title ID Min: 0x{:016X}", acid_header.title_id_min);
|
||||
LOG_DEBUG(Service_FS, "Title ID Max: 0x{:016X}", acid_header.title_id_max);
|
||||
LOG_DEBUG(Service_FS, "Filesystem Access: 0x{:016X}\n", acid_file_access.permissions);
|
||||
LOG_DEBUG(Service_FS, "Title ID Min: {:#016x}", acid_header.title_id_min);
|
||||
LOG_DEBUG(Service_FS, "Title ID Max: {:#016x}", acid_header.title_id_max);
|
||||
LOG_DEBUG(Service_FS, "Filesystem Access: {:#016x}\n", acid_file_access.permissions);
|
||||
|
||||
// Begin ACI0 printing (actual perms, unsigned)
|
||||
LOG_DEBUG(Service_FS, "Magic: {:.4}", aci_header.magic.data());
|
||||
LOG_DEBUG(Service_FS, "Title ID: 0x{:016X}", aci_header.title_id);
|
||||
LOG_DEBUG(Service_FS, "Filesystem Access: 0x{:016X}\n", aci_file_access.permissions);
|
||||
LOG_DEBUG(Service_FS, "Title ID: {:#016x}", aci_header.title_id);
|
||||
LOG_DEBUG(Service_FS, "Filesystem Access: {:#016x}\n", aci_file_access.permissions);
|
||||
}
|
||||
} // namespace FileSys
|
||||
|
||||
@@ -76,7 +76,7 @@ constexpr inline SystemArchiveDescriptor GetSystemArchive(u64 title_id) {
|
||||
|
||||
VirtualFile SynthesizeSystemArchive(const u64 title_id) {
|
||||
auto const desc = GetSystemArchive(title_id);
|
||||
LOG_INFO(Service_FS, "Synthesizing system archive '{}' (0x{:016X}).", desc.name, title_id);
|
||||
LOG_INFO(Service_FS, "Synthesizing system archive '{}' ({:#016x}).", desc.name, title_id);
|
||||
if (desc.supplier != nullptr) {
|
||||
if (auto const dir = desc.supplier(); dir != nullptr) {
|
||||
if (auto const romfs = CreateRomFS(dir); romfs != nullptr) {
|
||||
|
||||
@@ -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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||
@@ -40,7 +40,7 @@ VfsEntryType VfsFilesystem::GetEntryType(std::string_view path_) const {
|
||||
|
||||
VirtualFile VfsFilesystem::OpenFile(std::string_view path_, OpenMode perms) {
|
||||
const auto path = Common::FS::SanitizePath(path_);
|
||||
return root->GetFileRelative(path);
|
||||
return root->GetFileRelative(path, perms);
|
||||
}
|
||||
|
||||
VirtualFile VfsFilesystem::CreateFile(std::string_view path_, OpenMode perms) {
|
||||
@@ -201,7 +201,7 @@ std::string VfsFile::GetFullPath() const {
|
||||
return GetContainingDirectory()->GetFullPath() + '/' + GetName();
|
||||
}
|
||||
|
||||
VirtualFile VfsDirectory::GetFileRelative(std::string_view path) const {
|
||||
VirtualFile VfsDirectory::GetFileRelative(std::string_view path, OpenMode perms) const {
|
||||
auto vec = Common::FS::SplitPathComponents(path);
|
||||
if (vec.empty()) {
|
||||
return nullptr;
|
||||
@@ -224,7 +224,10 @@ VirtualFile VfsDirectory::GetFileRelative(std::string_view path) const {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return dir->GetFile(vec.back());
|
||||
if (perms == OpenMode::Default) {
|
||||
return dir->GetFile(vec.back());
|
||||
}
|
||||
return dir->GetFileRelative(vec.back(), perms);
|
||||
}
|
||||
|
||||
VirtualFile VfsDirectory::GetFileAbsolute(std::string_view path) const {
|
||||
|
||||
@@ -201,7 +201,8 @@ public:
|
||||
|
||||
// Retrieves the file located at path as if the current directory was root. Returns nullptr if
|
||||
// not found.
|
||||
virtual VirtualFile GetFileRelative(std::string_view path) const;
|
||||
virtual VirtualFile GetFileRelative(std::string_view path,
|
||||
OpenMode perms = OpenMode::Default) const;
|
||||
// Calls GetFileRelative(path) on the root of the current directory.
|
||||
virtual VirtualFile GetFileAbsolute(std::string_view path) const;
|
||||
|
||||
|
||||
@@ -27,9 +27,9 @@ VirtualDir LayeredVfsDirectory::MakeLayeredDirectory(std::vector<VirtualDir> dir
|
||||
return VirtualDir(new LayeredVfsDirectory(std::move(dirs), std::move(name)));
|
||||
}
|
||||
|
||||
VirtualFile LayeredVfsDirectory::GetFileRelative(std::string_view path) const {
|
||||
VirtualFile LayeredVfsDirectory::GetFileRelative(std::string_view path, OpenMode perms) const {
|
||||
for (const auto& layer : dirs) {
|
||||
const auto file = layer->GetFileRelative(path);
|
||||
const auto file = layer->GetFileRelative(path, perms);
|
||||
if (file != nullptr)
|
||||
return file;
|
||||
}
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
@@ -20,7 +23,8 @@ public:
|
||||
/// Wrapper function to allow for more efficient handling of dirs.size() == 0, 1 cases.
|
||||
static VirtualDir MakeLayeredDirectory(std::vector<VirtualDir> dirs, std::string name = "");
|
||||
|
||||
VirtualFile GetFileRelative(std::string_view path) const override;
|
||||
VirtualFile GetFileRelative(std::string_view path,
|
||||
OpenMode perms = OpenMode::Default) const override;
|
||||
VirtualDir GetDirectoryRelative(std::string_view path) const override;
|
||||
VirtualFile GetFile(std::string_view file_name) const override;
|
||||
VirtualDir GetSubdirectory(std::string_view subdir_name) const override;
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
#include "common/fs/file.h"
|
||||
#include "common/fs/fs.h"
|
||||
#include "common/fs/path_util.h"
|
||||
#include "common/logging.h"
|
||||
#include "core/file_sys/vfs/vfs.h"
|
||||
#include "core/file_sys/vfs/vfs_real.h"
|
||||
|
||||
@@ -46,17 +45,11 @@ bool IsWithinRoot(std::string_view root, std::string_view full_path) {
|
||||
}
|
||||
|
||||
constexpr FS::FileAccessMode ModeFlagsToFileAccessMode(OpenMode mode) {
|
||||
switch (mode) {
|
||||
case OpenMode::Read:
|
||||
return FS::FileAccessMode::Read;
|
||||
case OpenMode::Write:
|
||||
case OpenMode::ReadWrite:
|
||||
case OpenMode::AllowAppend:
|
||||
case OpenMode::All:
|
||||
if (True(mode & OpenMode::Write) || True(mode & OpenMode::AllowAppend)) {
|
||||
return FS::FileAccessMode::ReadWrite;
|
||||
default:
|
||||
return {};
|
||||
}
|
||||
|
||||
return FS::FileAccessMode::Read;
|
||||
}
|
||||
|
||||
} // Anonymous namespace
|
||||
@@ -94,9 +87,11 @@ VirtualFile RealVfsFilesystem::OpenFileFromEntry(std::string_view path_, std::op
|
||||
std::optional<std::string> parent_path,
|
||||
OpenMode perms) {
|
||||
const auto path = FS::SanitizePath(path_, FS::DirectorySeparator::PlatformDefault);
|
||||
const auto open_perms = perms == OpenMode::Default ? OpenMode::Read : perms;
|
||||
std::scoped_lock lk{list_lock};
|
||||
|
||||
if (auto it = cache.find(path); it != cache.end()) {
|
||||
const CacheKey cache_key{path, open_perms};
|
||||
if (auto it = cache.find(cache_key); it != cache.end()) {
|
||||
if (auto file = it->second.lock(); file) {
|
||||
return file;
|
||||
}
|
||||
@@ -110,8 +105,9 @@ VirtualFile RealVfsFilesystem::OpenFileFromEntry(std::string_view path_, std::op
|
||||
this->InsertReferenceIntoListLocked(*reference);
|
||||
|
||||
auto file = std::shared_ptr<RealVfsFile>(
|
||||
new RealVfsFile(*this, std::move(reference), path, perms, size, std::move(parent_path)));
|
||||
cache[path] = file;
|
||||
new RealVfsFile(*this, std::move(reference), path, open_perms, size,
|
||||
std::move(parent_path)));
|
||||
cache[cache_key] = file;
|
||||
|
||||
return file;
|
||||
}
|
||||
@@ -124,7 +120,7 @@ VirtualFile RealVfsFilesystem::CreateFile(std::string_view path_, OpenMode perms
|
||||
const auto path = FS::SanitizePath(path_, FS::DirectorySeparator::PlatformDefault);
|
||||
{
|
||||
std::scoped_lock lk{list_lock};
|
||||
cache.erase(path);
|
||||
CloseCachedFileReferenceLocked(path);
|
||||
}
|
||||
|
||||
// Current usages of CreateFile expect to delete the contents of an existing file.
|
||||
@@ -157,8 +153,8 @@ VirtualFile RealVfsFilesystem::MoveFile(std::string_view old_path_, std::string_
|
||||
const auto new_path = FS::SanitizePath(new_path_, FS::DirectorySeparator::PlatformDefault);
|
||||
{
|
||||
std::scoped_lock lk{list_lock};
|
||||
cache.erase(old_path);
|
||||
cache.erase(new_path);
|
||||
CloseCachedFileReferenceLocked(old_path);
|
||||
CloseCachedFileReferenceLocked(new_path);
|
||||
}
|
||||
if (!FS::RenameFile(old_path, new_path)) {
|
||||
return nullptr;
|
||||
@@ -170,14 +166,15 @@ bool RealVfsFilesystem::DeleteFile(std::string_view path_) {
|
||||
const auto path = FS::SanitizePath(path_, FS::DirectorySeparator::PlatformDefault);
|
||||
{
|
||||
std::scoped_lock lk{list_lock};
|
||||
cache.erase(path);
|
||||
CloseCachedFileReferenceLocked(path);
|
||||
}
|
||||
return FS::RemoveFile(path);
|
||||
}
|
||||
|
||||
VirtualDir RealVfsFilesystem::OpenDirectory(std::string_view path_, OpenMode perms) {
|
||||
const auto path = FS::SanitizePath(path_, FS::DirectorySeparator::PlatformDefault);
|
||||
return std::shared_ptr<RealVfsDirectory>(new RealVfsDirectory(*this, path, perms));
|
||||
return std::shared_ptr<RealVfsDirectory>(
|
||||
new RealVfsDirectory(*this, path, perms == OpenMode::Default ? OpenMode::Read : perms));
|
||||
}
|
||||
|
||||
VirtualDir RealVfsFilesystem::CreateDirectory(std::string_view path_, OpenMode perms) {
|
||||
@@ -222,8 +219,8 @@ std::unique_lock<std::mutex> RealVfsFilesystem::RefreshReference(const std::stri
|
||||
if (!reference.file) {
|
||||
this->EvictSingleReferenceLocked();
|
||||
|
||||
reference.file =
|
||||
FS::FileOpen(path, ModeFlagsToFileAccessMode(perms), FS::FileType::BinaryFile);
|
||||
reference.file = FS::FileOpen(path, ModeFlagsToFileAccessMode(perms),
|
||||
FS::FileType::BinaryFile, FS::FileShareFlag::ShareReadWriteDelete);
|
||||
if (reference.file) {
|
||||
num_open_files++;
|
||||
}
|
||||
@@ -297,15 +294,45 @@ RealVfsFile::~RealVfsFile() {
|
||||
base.DropReference(std::move(reference));
|
||||
}
|
||||
|
||||
void RealVfsFilesystem::CloseCachedFileReferenceLocked(const std::string& path) {
|
||||
for (auto it = cache.lower_bound(CacheKey{path, OpenMode::Default});
|
||||
it != cache.end() && it->first.first == path;) {
|
||||
const auto cached_file = it->second.lock();
|
||||
if (cached_file) {
|
||||
auto* const real_file = static_cast<RealVfsFile*>(cached_file.get());
|
||||
auto& reference = real_file->reference;
|
||||
if (reference && reference->file) {
|
||||
RemoveReferenceFromListLocked(*reference);
|
||||
reference->file.reset();
|
||||
num_open_files--;
|
||||
InsertReferenceIntoListLocked(*reference);
|
||||
}
|
||||
}
|
||||
it = cache.erase(it);
|
||||
}
|
||||
}
|
||||
|
||||
std::string RealVfsFile::GetName() const {
|
||||
#ifdef __ANDROID__
|
||||
if (path[0] != '/') {
|
||||
if (!path.empty() && path[0] != '/') {
|
||||
return FS::Android::GetFilename(path);
|
||||
}
|
||||
#endif
|
||||
return path_components.empty() ? "" : std::string(path_components.back());
|
||||
}
|
||||
|
||||
std::string RealVfsFile::GetFullPath() const {
|
||||
#ifdef __ANDROID__
|
||||
if (!path.empty() && path[0] != '/') {
|
||||
auto out = path;
|
||||
std::replace(out.begin(), out.end(), '\\', '/');
|
||||
return out;
|
||||
}
|
||||
#endif
|
||||
|
||||
return VfsFile::GetFullPath();
|
||||
}
|
||||
|
||||
std::size_t RealVfsFile::GetSize() const {
|
||||
if (size) {
|
||||
return *size;
|
||||
@@ -411,13 +438,14 @@ RealVfsDirectory::RealVfsDirectory(RealVfsFilesystem& base_, const std::string&
|
||||
|
||||
RealVfsDirectory::~RealVfsDirectory() = default;
|
||||
|
||||
VirtualFile RealVfsDirectory::GetFileRelative(std::string_view relative_path) const {
|
||||
VirtualFile RealVfsDirectory::GetFileRelative(std::string_view relative_path,
|
||||
OpenMode open_perms) const {
|
||||
const auto full_path = FS::SanitizePath(path + '/' + std::string(relative_path));
|
||||
if (!FS::Exists(full_path) || FS::IsDir(full_path)
|
||||
|| !IsWithinRoot(FS::SanitizePath(path), full_path)) {
|
||||
return nullptr;
|
||||
}
|
||||
return base.OpenFile(full_path, perms);
|
||||
return base.OpenFile(full_path, open_perms == OpenMode::Default ? perms : open_perms);
|
||||
}
|
||||
|
||||
VirtualDir RealVfsDirectory::GetDirectoryRelative(std::string_view relative_path) const {
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
#include "common/intrusive_list.h"
|
||||
#include "core/file_sys/fs_filesystem.h"
|
||||
#include "core/file_sys/vfs/vfs.h"
|
||||
@@ -49,8 +50,9 @@ public:
|
||||
bool DeleteDirectory(std::string_view path) override;
|
||||
|
||||
private:
|
||||
using CacheKey = std::pair<std::string, OpenMode>;
|
||||
using ReferenceListType = Common::IntrusiveListBaseTraits<FileReference>::ListType;
|
||||
std::map<std::string, std::weak_ptr<VfsFile>, std::less<>> cache;
|
||||
std::map<CacheKey, std::weak_ptr<VfsFile>, std::less<>> cache;
|
||||
ReferenceListType open_references;
|
||||
ReferenceListType closed_references;
|
||||
std::mutex list_lock;
|
||||
@@ -63,6 +65,7 @@ private:
|
||||
std::unique_lock<std::mutex> RefreshReference(const std::string& path, OpenMode perms,
|
||||
FileReference& reference);
|
||||
void DropReference(std::unique_ptr<FileReference>&& reference);
|
||||
void CloseCachedFileReferenceLocked(const std::string& path);
|
||||
|
||||
private:
|
||||
friend class RealVfsDirectory;
|
||||
@@ -85,6 +88,7 @@ public:
|
||||
~RealVfsFile() override;
|
||||
|
||||
std::string GetName() const override;
|
||||
std::string GetFullPath() const override;
|
||||
std::size_t GetSize() const override;
|
||||
bool Resize(std::size_t new_size) override;
|
||||
VirtualDir GetContainingDirectory() const override;
|
||||
@@ -115,7 +119,8 @@ class RealVfsDirectory : public VfsDirectory {
|
||||
public:
|
||||
~RealVfsDirectory() override;
|
||||
|
||||
VirtualFile GetFileRelative(std::string_view relative_path) const override;
|
||||
VirtualFile GetFileRelative(std::string_view relative_path,
|
||||
OpenMode perms = OpenMode::Default) const override;
|
||||
VirtualDir GetDirectoryRelative(std::string_view relative_path) const override;
|
||||
VirtualFile GetFile(std::string_view name) const override;
|
||||
VirtualDir GetSubdirectory(std::string_view name) const override;
|
||||
|
||||
@@ -670,6 +670,11 @@ public:
|
||||
size_t GetHeapRegionSize() const {
|
||||
return m_heap_region_end - m_heap_region_start;
|
||||
}
|
||||
size_t GetCurrentHeapSize() const {
|
||||
KScopedLightLock lk(m_general_lock);
|
||||
|
||||
return m_current_heap_end - m_heap_region_start;
|
||||
}
|
||||
size_t GetAliasRegionSize() const {
|
||||
return m_alias_region_end - m_alias_region_start;
|
||||
}
|
||||
|
||||
@@ -210,6 +210,11 @@ Result KProcess::Initialize(KernelCore& kernel, const Svc::CreateProcessParamete
|
||||
m_arg_pointer = 0;
|
||||
m_arg_return_address = 0;
|
||||
m_main_thread_handle_addr = 0;
|
||||
m_process_handle_addr = 0;
|
||||
m_homebrew_next_load_path_addr = 0;
|
||||
m_homebrew_next_load_argv_addr = 0;
|
||||
m_is_homebrew_in_place_next_load = false;
|
||||
m_has_homebrew_nxlink_argv_marker = false;
|
||||
m_code_size = params.code_num_pages * PageSize;
|
||||
m_is_application = True(params.flags & Svc::CreateProcessFlag::IsApplication);
|
||||
|
||||
@@ -947,6 +952,7 @@ Result KProcess::Run(KernelCore& kernel, s32 priority, size_t stack_size) {
|
||||
|
||||
stack_top = stack_bottom + stack_size;
|
||||
m_main_thread_stack_size = stack_size;
|
||||
m_main_thread_stack_top = stack_top;
|
||||
}
|
||||
|
||||
// Ensure our stack is safe to clean up on exit.
|
||||
@@ -1005,6 +1011,11 @@ Result KProcess::Run(KernelCore& kernel, s32 priority, size_t stack_size) {
|
||||
if (GetInteger(m_main_thread_handle_addr) != 0) {
|
||||
this->GetMemory().Write32(m_main_thread_handle_addr, thread_handle);
|
||||
}
|
||||
if (GetInteger(m_process_handle_addr) != 0) {
|
||||
Handle process_handle;
|
||||
R_TRY(m_handle_table.Add(kernel, std::addressof(process_handle), this));
|
||||
this->GetMemory().Write32(m_process_handle_addr, process_handle);
|
||||
}
|
||||
} else {
|
||||
main_thread->GetContext().r[0] = 0;
|
||||
main_thread->GetContext().r[1] = thread_handle;
|
||||
|
||||
@@ -6,7 +6,9 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <map>
|
||||
#include <string_view>
|
||||
|
||||
#include "core/arm/arm_interface.h"
|
||||
#include "core/file_sys/program_metadata.h"
|
||||
@@ -87,6 +89,10 @@ private:
|
||||
KProcessAddress m_arg_pointer{};
|
||||
KProcessAddress m_arg_return_address{};
|
||||
KProcessAddress m_main_thread_handle_addr{};
|
||||
KProcessAddress m_process_handle_addr{};
|
||||
KProcessAddress m_homebrew_next_load_path_addr{};
|
||||
KProcessAddress m_homebrew_next_load_argv_addr{};
|
||||
std::array<char, 16> m_homebrew_nxlink_argv_marker{};
|
||||
KHandleTable m_handle_table;
|
||||
KProcessAddress m_plr_address{};
|
||||
ThreadList m_thread_list{};
|
||||
@@ -112,6 +118,7 @@ private:
|
||||
|
||||
size_t m_code_size{};
|
||||
size_t m_main_thread_stack_size{};
|
||||
KProcessAddress m_main_thread_stack_top{};
|
||||
size_t m_max_process_memory{};
|
||||
size_t m_memory_release_hint{};
|
||||
s64 m_schedule_count{};
|
||||
@@ -139,6 +146,8 @@ private:
|
||||
bool m_is_suspended : 1 = false;
|
||||
bool m_is_immortal : 1 = false;
|
||||
bool m_is_handle_table_initialized : 1 = false;
|
||||
bool m_is_homebrew_in_place_next_load : 1 = false;
|
||||
bool m_has_homebrew_nxlink_argv_marker : 1 = false;
|
||||
|
||||
private:
|
||||
Result StartTermination(KernelCore& kernel);
|
||||
@@ -231,6 +240,49 @@ public:
|
||||
void SetMainThreadHandleAddr(KProcessAddress addr) {
|
||||
m_main_thread_handle_addr = addr;
|
||||
}
|
||||
void SetProcessHandleAddr(KProcessAddress addr) {
|
||||
m_process_handle_addr = addr;
|
||||
}
|
||||
void SetHomebrewNextLoadBufferAddrs(KProcessAddress path_addr, KProcessAddress argv_addr) {
|
||||
m_homebrew_next_load_path_addr = path_addr;
|
||||
m_homebrew_next_load_argv_addr = argv_addr;
|
||||
}
|
||||
void SetHomebrewInPlaceNextLoad(bool enabled) {
|
||||
m_is_homebrew_in_place_next_load = enabled;
|
||||
}
|
||||
void SetHomebrewNxlinkArgvMarker(std::string_view marker) {
|
||||
if (marker.size() != m_homebrew_nxlink_argv_marker.size()) {
|
||||
m_has_homebrew_nxlink_argv_marker = false;
|
||||
return;
|
||||
}
|
||||
|
||||
marker.copy(m_homebrew_nxlink_argv_marker.data(), m_homebrew_nxlink_argv_marker.size());
|
||||
m_has_homebrew_nxlink_argv_marker = true;
|
||||
}
|
||||
void ClearHomebrewNxlinkArgvMarker() {
|
||||
m_has_homebrew_nxlink_argv_marker = false;
|
||||
}
|
||||
std::string_view GetHomebrewNxlinkArgvMarker() const {
|
||||
if (!m_has_homebrew_nxlink_argv_marker) {
|
||||
return {};
|
||||
}
|
||||
return {m_homebrew_nxlink_argv_marker.data(), m_homebrew_nxlink_argv_marker.size()};
|
||||
}
|
||||
bool IsHomebrewInPlaceNextLoad() const {
|
||||
return m_is_homebrew_in_place_next_load;
|
||||
}
|
||||
KProcessAddress GetHomebrewNextLoadPathAddr() const {
|
||||
return m_homebrew_next_load_path_addr;
|
||||
}
|
||||
KProcessAddress GetHomebrewNextLoadArgvAddr() const {
|
||||
return m_homebrew_next_load_argv_addr;
|
||||
}
|
||||
size_t GetCodeSize() const {
|
||||
return m_code_size;
|
||||
}
|
||||
KProcessAddress GetMainThreadStackTop() const {
|
||||
return m_main_thread_stack_top;
|
||||
}
|
||||
|
||||
size_t GetMainStackSize() const {
|
||||
return m_main_thread_stack_size;
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-late
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
// This file is automatically generated using svc_generator.py.
|
||||
// DO NOT MODIFY IT MANUALLY
|
||||
@@ -128,7 +129,7 @@ static void SvcWrap_QueryMemory64From32(Core::System& system, std::span<uint64_t
|
||||
}
|
||||
|
||||
static void SvcWrap_ExitProcess64From32(Core::System& system, std::span<uint64_t, 8> args) {
|
||||
ExitProcess64From32(system);
|
||||
ExitProcess64From32(system, args);
|
||||
}
|
||||
|
||||
static void SvcWrap_CreateThread64From32(Core::System& system, std::span<uint64_t, 8> args) {
|
||||
@@ -1298,7 +1299,7 @@ static void SvcWrap_QueryMemory64(Core::System& system, std::span<uint64_t, 8> a
|
||||
}
|
||||
|
||||
static void SvcWrap_ExitProcess64(Core::System& system, std::span<uint64_t, 8> args) {
|
||||
ExitProcess64(system);
|
||||
ExitProcess64(system, args);
|
||||
}
|
||||
|
||||
static void SvcWrap_CreateThread64(Core::System& system, std::span<uint64_t, 8> args) {
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-late
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
// This file is automatically generated using svc_generator.py.
|
||||
// DO NOT MODIFY IT MANUALLY
|
||||
@@ -25,7 +26,7 @@ Result SetMemoryAttribute(Core::System& system, uint64_t address, uint64_t size,
|
||||
Result MapMemory(Core::System& system, uint64_t dst_address, uint64_t src_address, uint64_t size);
|
||||
Result UnmapMemory(Core::System& system, uint64_t dst_address, uint64_t src_address, uint64_t size);
|
||||
Result QueryMemory(Core::System& system, uint64_t out_memory_info, PageInfo* out_page_info, uint64_t address);
|
||||
void ExitProcess(Core::System& system);
|
||||
void ExitProcess(Core::System& system, std::span<uint64_t, 8> args);
|
||||
Result CreateThread(Core::System& system, Handle* out_handle, uint64_t func, uint64_t arg, uint64_t stack_bottom, int32_t priority, int32_t core_id);
|
||||
Result StartThread(Core::System& system, Handle thread_handle);
|
||||
void ExitThread(Core::System& system);
|
||||
@@ -146,7 +147,7 @@ Result SetMemoryAttribute64From32(Core::System& system, uint32_t address, uint32
|
||||
Result MapMemory64From32(Core::System& system, uint32_t dst_address, uint32_t src_address, uint32_t size);
|
||||
Result UnmapMemory64From32(Core::System& system, uint32_t dst_address, uint32_t src_address, uint32_t size);
|
||||
Result QueryMemory64From32(Core::System& system, uint32_t out_memory_info, PageInfo* out_page_info, uint32_t address);
|
||||
void ExitProcess64From32(Core::System& system);
|
||||
void ExitProcess64From32(Core::System& system, std::span<uint64_t, 8> args);
|
||||
Result CreateThread64From32(Core::System& system, Handle* out_handle, uint32_t func, uint32_t arg, uint32_t stack_bottom, int32_t priority, int32_t core_id);
|
||||
Result StartThread64From32(Core::System& system, Handle thread_handle);
|
||||
void ExitThread64From32(Core::System& system);
|
||||
@@ -267,7 +268,7 @@ Result SetMemoryAttribute64(Core::System& system, uint64_t address, uint64_t siz
|
||||
Result MapMemory64(Core::System& system, uint64_t dst_address, uint64_t src_address, uint64_t size);
|
||||
Result UnmapMemory64(Core::System& system, uint64_t dst_address, uint64_t src_address, uint64_t size);
|
||||
Result QueryMemory64(Core::System& system, uint64_t out_memory_info, PageInfo* out_page_info, uint64_t address);
|
||||
void ExitProcess64(Core::System& system);
|
||||
void ExitProcess64(Core::System& system, std::span<uint64_t, 8> args);
|
||||
Result CreateThread64(Core::System& system, Handle* out_handle, uint64_t func, uint64_t arg, uint64_t stack_bottom, int32_t priority, int32_t core_id);
|
||||
Result StartThread64(Core::System& system, Handle thread_handle);
|
||||
void ExitThread64(Core::System& system);
|
||||
|
||||
@@ -15,7 +15,7 @@ namespace Kernel::Svc {
|
||||
/// Sets the thread activity
|
||||
Result SetThreadActivity(Core::System& system, Handle thread_handle,
|
||||
ThreadActivity thread_activity) {
|
||||
LOG_DEBUG(Kernel_SVC, "called, handle=0x{:08X}, activity=0x{:08X}", thread_handle,
|
||||
LOG_DEBUG(Kernel_SVC, "called, handle={:#08x}, activity={:#08x}", thread_handle,
|
||||
thread_activity);
|
||||
|
||||
// Validate the activity.
|
||||
|
||||
@@ -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-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||
@@ -43,7 +43,7 @@ constexpr bool IsValidArbitrationType(Svc::ArbitrationType type) {
|
||||
// Wait for an address (via Address Arbiter)
|
||||
Result WaitForAddress(Core::System& system, u64 address, ArbitrationType arb_type, s32 value,
|
||||
s64 timeout_ns) {
|
||||
LOG_TRACE(Kernel_SVC, "called, address={:#X}, arb_type=0x{:X}, value=0x{:X}, timeout_ns={}",
|
||||
LOG_TRACE(Kernel_SVC, "called, address={:#x}, arb_type={:#x}, value={:#x}, timeout_ns={}",
|
||||
address, arb_type, value, timeout_ns);
|
||||
|
||||
// Validate input.
|
||||
@@ -74,7 +74,7 @@ Result WaitForAddress(Core::System& system, u64 address, ArbitrationType arb_typ
|
||||
// Signals to an address (via Address Arbiter)
|
||||
Result SignalToAddress(Core::System& system, u64 address, SignalType signal_type, s32 value,
|
||||
s32 count) {
|
||||
LOG_TRACE(Kernel_SVC, "called, address={:#X}, signal_type=0x{:X}, value=0x{:X}, count=0x{:X}",
|
||||
LOG_TRACE(Kernel_SVC, "called, address={:#x}, signal_type={:#x}, value={:#x}, count={:#x}",
|
||||
address, signal_type, value, count);
|
||||
|
||||
// Validate input.
|
||||
|
||||
@@ -33,7 +33,7 @@ constexpr bool IsValidUnmapFromOwnerCodeMemoryPermission(MemoryPermission perm)
|
||||
} // namespace
|
||||
|
||||
Result CreateCodeMemory(Core::System& system, Handle* out, u64 address, uint64_t size) {
|
||||
LOG_TRACE(Kernel_SVC, "called, address={:#X}, size=0x{:X}", address, size);
|
||||
LOG_TRACE(Kernel_SVC, "called, address={:#x}, size={:#x}", address, size);
|
||||
|
||||
// Validate address / size.
|
||||
R_UNLESS(Common::IsAligned(address, PageSize), ResultInvalidAddress);
|
||||
@@ -69,8 +69,8 @@ Result ControlCodeMemory(Core::System& system, Handle code_memory_handle,
|
||||
MemoryPermission perm) {
|
||||
|
||||
LOG_TRACE(Kernel_SVC,
|
||||
"called, code_memory_handle={:#X}, operation=0x{:X}, address=0x{:X}, size=0x{:X}, "
|
||||
"permission={:#X}",
|
||||
"called, code_memory_handle={:#x}, operation={:#x}, address={:#x}, size={:#x}, "
|
||||
"permission={:#x}",
|
||||
code_memory_handle, operation, address, size, perm);
|
||||
|
||||
// Validate the address / size.
|
||||
|
||||
@@ -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-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||
@@ -17,7 +17,7 @@ namespace Kernel::Svc {
|
||||
/// Wait process wide key atomic
|
||||
Result WaitProcessWideKeyAtomic(Core::System& system, u64 address, u64 cv_key, u32 tag,
|
||||
s64 timeout_ns) {
|
||||
LOG_TRACE(Kernel_SVC, "called address={:X}, cv_key={:X}, tag=0x{:08X}, timeout_ns={}", address,
|
||||
LOG_TRACE(Kernel_SVC, "called address={:X}, cv_key={:X}, tag={:#08x}, timeout_ns={}", address,
|
||||
cv_key, tag, timeout_ns);
|
||||
|
||||
// Validate input.
|
||||
@@ -48,7 +48,7 @@ Result WaitProcessWideKeyAtomic(Core::System& system, u64 address, u64 cv_key, u
|
||||
|
||||
/// Signal process wide key
|
||||
void SignalProcessWideKey(Core::System& system, u64 cv_key, s32 count) {
|
||||
LOG_TRACE(Kernel_SVC, "called, cv_key={:#X}, count=0x{:08X}", cv_key, count);
|
||||
LOG_TRACE(Kernel_SVC, "called, cv_key={:#x}, count={:#08x}", cv_key, count);
|
||||
|
||||
// Signal the condition variable.
|
||||
return GetCurrentProcess(system.Kernel())
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
namespace Kernel::Svc {
|
||||
|
||||
Result SignalEvent(Core::System& system, Handle event_handle) {
|
||||
LOG_DEBUG(Kernel_SVC, "called, event_handle=0x{:08X}", event_handle);
|
||||
LOG_DEBUG(Kernel_SVC, "called, event_handle={:#08x}", event_handle);
|
||||
|
||||
// Get the current handle table.
|
||||
const KHandleTable& handle_table = GetCurrentProcess(system.Kernel()).GetHandleTable();
|
||||
@@ -26,7 +26,7 @@ Result SignalEvent(Core::System& system, Handle event_handle) {
|
||||
if (event.IsNotNull()) {
|
||||
event->Signal(system.Kernel());
|
||||
} else {
|
||||
LOG_WARNING(Kernel_SVC, "SignalEvent best-effort unknown handle=0x{:08X} (ignored)",
|
||||
LOG_WARNING(Kernel_SVC, "SignalEvent best-effort unknown handle={:#08x} (ignored)",
|
||||
event_handle);
|
||||
}
|
||||
R_SUCCEED();
|
||||
@@ -41,7 +41,7 @@ Result SignalEvent(Core::System& system, Handle event_handle) {
|
||||
}
|
||||
|
||||
Result ClearEvent(Core::System& system, Handle event_handle) {
|
||||
LOG_TRACE(Kernel_SVC, "called, event_handle=0x{:08X}", event_handle);
|
||||
LOG_TRACE(Kernel_SVC, "called, event_handle={:#08x}", event_handle);
|
||||
|
||||
// Get the current handle table.
|
||||
const auto& handle_table = GetCurrentProcess(system.Kernel()).GetHandleTable();
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <cctype>
|
||||
#include "core/core.h"
|
||||
#include "core/debugger/debugger.h"
|
||||
#include "core/hle/kernel/k_process.h"
|
||||
@@ -41,7 +42,7 @@ void Break(Core::System& system, BreakReason reason, u64 info1, u64 info2) {
|
||||
std::string hexdump;
|
||||
for (std::size_t i = 0; i < debug_buffer.size(); i++) {
|
||||
hexdump += fmt::format("{:02X} ", debug_buffer[i]);
|
||||
if (i != 0 && i % 16 == 0) {
|
||||
if ((i + 1) % 32 == 0) {
|
||||
hexdump += '\n';
|
||||
}
|
||||
}
|
||||
@@ -51,35 +52,35 @@ void Break(Core::System& system, BreakReason reason, u64 info1, u64 info2) {
|
||||
};
|
||||
switch (break_reason) {
|
||||
case BreakReason::Panic:
|
||||
LOG_CRITICAL(Debug_Emulated, "Userspace PANIC! info1=0x{:016X}, info2=0x{:016X}", info1,
|
||||
LOG_CRITICAL(Debug_Emulated, "Userspace PANIC! info1={:#016x}, info2={:#016x}", info1,
|
||||
info2);
|
||||
handle_debug_buffer(info1, info2);
|
||||
break;
|
||||
case BreakReason::Assert:
|
||||
LOG_CRITICAL(Debug_Emulated, "Userspace Assertion failed! info1=0x{:016X}, info2=0x{:016X}",
|
||||
LOG_CRITICAL(Debug_Emulated, "Userspace Assertion failed! info1={:#016x}, info2={:#016x}",
|
||||
info1, info2);
|
||||
handle_debug_buffer(info1, info2);
|
||||
break;
|
||||
case BreakReason::User:
|
||||
LOG_WARNING(Debug_Emulated, "Userspace Break! 0x{:016X} with size 0x{:016X}", info1, info2);
|
||||
LOG_WARNING(Debug_Emulated, "Userspace Break! {:#016x} with size {:#016x}", info1, info2);
|
||||
handle_debug_buffer(info1, info2);
|
||||
break;
|
||||
case BreakReason::PreLoadDll:
|
||||
LOG_INFO(Debug_Emulated,
|
||||
"Userspace Attempting to load an NRO at 0x{:016X} with size 0x{:016X}", info1,
|
||||
"Userspace Attempting to load an NRO at {:#016x} with size {:#016x}", info1,
|
||||
info2);
|
||||
break;
|
||||
case BreakReason::PostLoadDll:
|
||||
LOG_INFO(Debug_Emulated, "Userspace Loaded an NRO at 0x{:016X} with size 0x{:016X}", info1,
|
||||
LOG_INFO(Debug_Emulated, "Userspace Loaded an NRO at {:#016x} with size {:#016x}", info1,
|
||||
info2);
|
||||
break;
|
||||
case BreakReason::PreUnloadDll:
|
||||
LOG_INFO(Debug_Emulated,
|
||||
"Userspace Attempting to unload an NRO at 0x{:016X} with size 0x{:016X}", info1,
|
||||
"Userspace Attempting to unload an NRO at {:#016x} with size {:#016x}", info1,
|
||||
info2);
|
||||
break;
|
||||
case BreakReason::PostUnloadDll:
|
||||
LOG_INFO(Debug_Emulated, "Userspace Unloaded an NRO at 0x{:016X} with size 0x{:016X}",
|
||||
LOG_INFO(Debug_Emulated, "Userspace Unloaded an NRO at {:#016x} with size {:#016x}",
|
||||
info1, info2);
|
||||
break;
|
||||
case BreakReason::CppException:
|
||||
@@ -88,7 +89,7 @@ void Break(Core::System& system, BreakReason reason, u64 info1, u64 info2) {
|
||||
default:
|
||||
LOG_WARNING(
|
||||
Debug_Emulated,
|
||||
"Signalling debugger, Unknown break reason {:#X}, info1=0x{:016X}, info2=0x{:016X}",
|
||||
"Signalling debugger, Unknown break reason {:#x}, info1={:#016x}, info2={:#016x}",
|
||||
reason, info1, info2);
|
||||
handle_debug_buffer(info1, info2);
|
||||
break;
|
||||
@@ -101,7 +102,7 @@ void Break(Core::System& system, BreakReason reason, u64 info1, u64 info2) {
|
||||
if (!notification_only) {
|
||||
LOG_CRITICAL(
|
||||
Debug_Emulated,
|
||||
"Emulated program broke execution! reason=0x{:016X}, info1=0x{:016X}, info2=0x{:016X}",
|
||||
"Emulated program broke execution! reason={:#016x}, info1={:#016x}, info2={:#016x}",
|
||||
reason, info1, info2);
|
||||
|
||||
handle_debug_buffer(info1, info2);
|
||||
|
||||
@@ -16,7 +16,7 @@ namespace Kernel::Svc {
|
||||
/// Gets system/memory information for the current process
|
||||
Result GetInfo(Core::System& system, u64* result, InfoType info_id_type, Handle handle,
|
||||
u64 info_sub_id) {
|
||||
LOG_TRACE(Kernel_SVC, "called info_id={:#X}, info_sub_id=0x{:X}, handle=0x{:08X}",
|
||||
LOG_TRACE(Kernel_SVC, "called info_id={:#x}, info_sub_id={:#x}, handle={:#08x}",
|
||||
info_id_type, info_sub_id, handle);
|
||||
|
||||
u32 info_id = static_cast<u32>(info_id_type);
|
||||
@@ -153,7 +153,7 @@ Result GetInfo(Core::System& system, u64* result, InfoType info_id_type, Handle
|
||||
break;
|
||||
}
|
||||
|
||||
LOG_ERROR(Kernel_SVC, "Unimplemented svcGetInfo id=0x{:016X}", info_id);
|
||||
LOG_ERROR(Kernel_SVC, "Unimplemented svcGetInfo id={:#016x}", info_id);
|
||||
R_THROW(ResultInvalidEnumValue);
|
||||
}
|
||||
|
||||
@@ -206,7 +206,7 @@ Result GetInfo(Core::System& system, u64* result, InfoType info_id_type, Handle
|
||||
.GetHandleTable()
|
||||
.GetObject<KThread>(system.Kernel(), Handle(handle));
|
||||
if (thread.IsNull()) {
|
||||
LOG_ERROR(Kernel_SVC, "Thread handle does not exist, handle=0x{:08X}",
|
||||
LOG_ERROR(Kernel_SVC, "Thread handle does not exist, handle={:#08x}",
|
||||
static_cast<Handle>(handle));
|
||||
R_THROW(ResultInvalidHandle);
|
||||
}
|
||||
@@ -265,7 +265,7 @@ Result GetInfo(Core::System& system, u64* result, InfoType info_id_type, Handle
|
||||
R_SUCCEED();
|
||||
}
|
||||
default:
|
||||
LOG_ERROR(Kernel_SVC, "Unimplemented svcGetInfo id=0x{:016X}", info_id);
|
||||
LOG_ERROR(Kernel_SVC, "Unimplemented svcGetInfo id={:#016x}", info_id);
|
||||
R_THROW(ResultInvalidEnumValue);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||
@@ -13,7 +13,7 @@ namespace Kernel::Svc {
|
||||
|
||||
/// Attempts to locks a mutex
|
||||
Result ArbitrateLock(Core::System& system, Handle thread_handle, u64 address, u32 tag) {
|
||||
LOG_TRACE(Kernel_SVC, "called thread_handle=0x{:08X}, address={:#X}, tag=0x{:08X}",
|
||||
LOG_TRACE(Kernel_SVC, "called thread_handle={:#08x}, address={:#x}, tag={:#08x}",
|
||||
thread_handle, address, tag);
|
||||
|
||||
// Validate the input address.
|
||||
@@ -25,7 +25,7 @@ Result ArbitrateLock(Core::System& system, Handle thread_handle, u64 address, u3
|
||||
|
||||
/// Unlock a mutex
|
||||
Result ArbitrateUnlock(Core::System& system, u64 address) {
|
||||
LOG_TRACE(Kernel_SVC, "called address={:#X}", address);
|
||||
LOG_TRACE(Kernel_SVC, "called address={:#x}", address);
|
||||
|
||||
// Validate the input address.
|
||||
R_UNLESS(!IsKernelAddress(address), ResultInvalidCurrentMemory);
|
||||
|
||||
@@ -4,6 +4,9 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <algorithm>
|
||||
#include <vector>
|
||||
|
||||
#include "core/core.h"
|
||||
#include "core/hle/kernel/k_process.h"
|
||||
#include "core/hle/kernel/svc.h"
|
||||
@@ -22,6 +25,179 @@ constexpr bool IsValidSetMemoryPermission(MemoryPermission perm) {
|
||||
}
|
||||
}
|
||||
|
||||
bool IsHomebrewInPlaceNextLoadCodeRange(const KProcess& process, u64 address, u64 size) {
|
||||
if (!process.IsHomebrewInPlaceNextLoad()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const u64 code_start = GetInteger(process.GetEntryPoint());
|
||||
const size_t code_size = process.GetCodeSize();
|
||||
if (code_start == 0 || code_size == 0 || size > code_size || address < code_start) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return address - code_start <= code_size - size;
|
||||
}
|
||||
|
||||
struct HomebrewInPlaceMemoryBlock {
|
||||
u64 address;
|
||||
u64 size;
|
||||
KMemoryState state;
|
||||
KMemoryPermission permission;
|
||||
KMemoryAttribute attribute;
|
||||
bool use_process_permission;
|
||||
};
|
||||
|
||||
Result SetHomebrewInPlaceMemoryPermissionByBlocks(KProcess& process, u64 address, u64 size,
|
||||
MemoryPermission perm, Result original_result) {
|
||||
auto& page_table = process.GetPageTable();
|
||||
const u64 end = address + size;
|
||||
const auto requested_permission = ConvertToKMemoryPermission(perm);
|
||||
std::vector<HomebrewInPlaceMemoryBlock> blocks;
|
||||
|
||||
for (u64 cursor = address; cursor < end;) {
|
||||
KMemoryInfo info;
|
||||
PageInfo page_info;
|
||||
const auto query_result = page_table.QueryInfo(std::addressof(info),
|
||||
std::addressof(page_info), cursor);
|
||||
if (query_result.IsError()) {
|
||||
LOG_WARNING(Kernel_SVC,
|
||||
"NextLoad in-place: split permission query failed "
|
||||
"address=0x{:016X}, result={:#X}, original={:#X}",
|
||||
cursor, query_result.raw, original_result.raw);
|
||||
R_RETURN(original_result);
|
||||
}
|
||||
|
||||
const u64 block_end = (std::min<u64>)(info.GetEndAddress(), end);
|
||||
if (block_end <= cursor) {
|
||||
LOG_WARNING(Kernel_SVC,
|
||||
"NextLoad in-place: split permission walk stalled "
|
||||
"cursor=0x{:016X}, block=0x{:016X}/0x{:X}, original={:#X}",
|
||||
cursor, info.GetAddress(), info.GetSize(), original_result.raw);
|
||||
R_RETURN(original_result);
|
||||
}
|
||||
|
||||
const bool can_reprotect = True(info.GetState() & KMemoryState::FlagCanReprotect);
|
||||
const bool can_process_reprotect = True(info.GetState() & KMemoryState::FlagCode);
|
||||
if (!can_reprotect && !can_process_reprotect) {
|
||||
LOG_WARNING(Kernel_SVC,
|
||||
"NextLoad in-place: split permission unsupported block "
|
||||
"address=0x{:016X}, size=0x{:X}, state=0x{:08X}, svc_state={}, "
|
||||
"perm=0x{:08X}, attr=0x{:08X}, original={:#X}",
|
||||
cursor, block_end - cursor, static_cast<u32>(info.GetState()),
|
||||
static_cast<u32>(info.GetSvcState()),
|
||||
static_cast<u32>(info.GetPermission()),
|
||||
static_cast<u32>(info.GetAttribute()), original_result.raw);
|
||||
R_RETURN(original_result);
|
||||
}
|
||||
|
||||
blocks.push_back({
|
||||
.address = cursor,
|
||||
.size = block_end - cursor,
|
||||
.state = info.GetState(),
|
||||
.permission = info.GetPermission(),
|
||||
.attribute = info.GetAttribute(),
|
||||
.use_process_permission = !can_reprotect && can_process_reprotect,
|
||||
});
|
||||
cursor = block_end;
|
||||
}
|
||||
|
||||
for (const auto& block : blocks) {
|
||||
if (block.permission == requested_permission) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto block_result =
|
||||
block.use_process_permission
|
||||
? page_table.SetProcessMemoryPermission(block.address, block.size, perm)
|
||||
: page_table.SetMemoryPermission(block.address, block.size, perm);
|
||||
if (block_result.IsError()) {
|
||||
LOG_WARNING(Kernel_SVC,
|
||||
"NextLoad in-place: split permission block failed "
|
||||
"address=0x{:016X}, size=0x{:X}, state=0x{:08X}, perm=0x{:08X}, "
|
||||
"attr=0x{:08X}, result={:#X}, original={:#X}",
|
||||
block.address, block.size, static_cast<u32>(block.state),
|
||||
static_cast<u32>(block.permission), static_cast<u32>(block.attribute),
|
||||
block_result.raw, original_result.raw);
|
||||
R_RETURN(block_result);
|
||||
}
|
||||
}
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
struct HomebrewInPlaceDeviceSharedBlock {
|
||||
u64 address;
|
||||
u64 size;
|
||||
u16 device_use_count;
|
||||
};
|
||||
|
||||
Result UnlockHomebrewInPlaceDeviceSharedSource(KProcess& process, u64 address, u64 size,
|
||||
Result original_result) {
|
||||
auto& page_table = process.GetPageTable();
|
||||
const u64 end = address + size;
|
||||
std::vector<HomebrewInPlaceDeviceSharedBlock> blocks;
|
||||
|
||||
for (u64 cursor = address; cursor < end;) {
|
||||
KMemoryInfo info;
|
||||
PageInfo page_info;
|
||||
const auto query_result = page_table.QueryInfo(std::addressof(info),
|
||||
std::addressof(page_info), cursor);
|
||||
if (query_result.IsError()) {
|
||||
R_RETURN(original_result);
|
||||
}
|
||||
|
||||
const u64 block_end = (std::min<u64>)(info.GetEndAddress(), end);
|
||||
if (block_end <= cursor) {
|
||||
R_RETURN(original_result);
|
||||
}
|
||||
|
||||
const bool can_device_map = True(info.GetState() & KMemoryState::FlagCanDeviceMap);
|
||||
const bool is_clean_memory =
|
||||
can_device_map && info.GetPermission() == KMemoryPermission::UserReadWrite &&
|
||||
info.GetAttribute() == KMemoryAttribute::None && info.m_device_use_count == 0;
|
||||
const bool is_stale_device_shared =
|
||||
can_device_map && info.GetPermission() == KMemoryPermission::UserReadWrite &&
|
||||
info.GetAttribute() == KMemoryAttribute::DeviceShared && info.m_device_use_count > 0;
|
||||
if (is_clean_memory) {
|
||||
cursor = block_end;
|
||||
continue;
|
||||
}
|
||||
if (!is_stale_device_shared) {
|
||||
R_RETURN(original_result);
|
||||
}
|
||||
|
||||
blocks.push_back({
|
||||
.address = cursor,
|
||||
.size = block_end - cursor,
|
||||
.device_use_count = info.m_device_use_count,
|
||||
});
|
||||
cursor = block_end;
|
||||
}
|
||||
|
||||
if (blocks.empty()) {
|
||||
R_RETURN(original_result);
|
||||
}
|
||||
|
||||
for (const auto& block : blocks) {
|
||||
for (u16 unlock = 0; unlock < block.device_use_count; unlock++) {
|
||||
const auto unlock_result =
|
||||
page_table.UnlockForDeviceAddressSpace(block.address, block.size);
|
||||
if (unlock_result.IsError()) {
|
||||
LOG_WARNING(Kernel_SVC,
|
||||
"NextLoad in-place: device-shared source unlock failed "
|
||||
"address=0x{:016X}, size=0x{:X}, remaining={}, result={:#X}, "
|
||||
"original={:#X}",
|
||||
block.address, block.size, block.device_use_count - unlock - 1,
|
||||
unlock_result.raw, original_result.raw);
|
||||
R_RETURN(original_result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
// Checks if address + size is greater than the given address
|
||||
// This can return false if the size causes an overflow of a 64-bit type
|
||||
// or if the given size is zero.
|
||||
@@ -34,12 +210,12 @@ constexpr bool IsValidAddressRange(u64 address, u64 size) {
|
||||
// in the same order.
|
||||
Result MapUnmapMemorySanityChecks(const KProcessPageTable& manager, u64 dst_addr, u64 src_addr, u64 size) {
|
||||
if (!Common::IsAligned(dst_addr, Core::Memory::YUZU_PAGESIZE)) {
|
||||
LOG_ERROR(Kernel_SVC, "Destination address is not aligned to 4KB, 0x{:016X}", dst_addr);
|
||||
LOG_ERROR(Kernel_SVC, "Destination address is not aligned to 4KB, {:#016X}", dst_addr);
|
||||
R_THROW(ResultInvalidAddress);
|
||||
}
|
||||
|
||||
if (!Common::IsAligned(src_addr, Core::Memory::YUZU_PAGESIZE)) {
|
||||
LOG_ERROR(Kernel_SVC, "Source address is not aligned to 4KB, 0x{:016X}", src_addr);
|
||||
LOG_ERROR(Kernel_SVC, "Source address is not aligned to 4KB, {:#016X}", src_addr);
|
||||
R_THROW(ResultInvalidSize);
|
||||
}
|
||||
|
||||
@@ -49,26 +225,26 @@ Result MapUnmapMemorySanityChecks(const KProcessPageTable& manager, u64 dst_addr
|
||||
}
|
||||
|
||||
if (!Common::IsAligned(size, Core::Memory::YUZU_PAGESIZE)) {
|
||||
LOG_ERROR(Kernel_SVC, "Size is not aligned to 4KB, 0x{:016X}", size);
|
||||
LOG_ERROR(Kernel_SVC, "Size is not aligned to 4KB, {:#016X}", size);
|
||||
R_THROW(ResultInvalidSize);
|
||||
}
|
||||
|
||||
if (!IsValidAddressRange(dst_addr, size)) {
|
||||
LOG_ERROR(Kernel_SVC,
|
||||
"Destination is not a valid address range, addr=0x{:016X}, size=0x{:016X}",
|
||||
"Destination is not a valid address range, addr={:#016x}, size={:#016x}",
|
||||
dst_addr, size);
|
||||
R_THROW(ResultInvalidCurrentMemory);
|
||||
}
|
||||
|
||||
if (!IsValidAddressRange(src_addr, size)) {
|
||||
LOG_ERROR(Kernel_SVC, "Source is not a valid address range, addr=0x{:016X}, size=0x{:016X}",
|
||||
LOG_ERROR(Kernel_SVC, "Source is not a valid address range, addr={:#016x}, size={:#016x}",
|
||||
src_addr, size);
|
||||
R_THROW(ResultInvalidCurrentMemory);
|
||||
}
|
||||
|
||||
if (!manager.Contains(src_addr, size)) {
|
||||
LOG_ERROR(Kernel_SVC,
|
||||
"Source is not within the address space, addr=0x{:016X}, size=0x{:016X}",
|
||||
"Source is not within the address space, addr={:#016x}, size={:#016x}",
|
||||
src_addr, size);
|
||||
R_THROW(ResultInvalidCurrentMemory);
|
||||
}
|
||||
@@ -79,7 +255,7 @@ Result MapUnmapMemorySanityChecks(const KProcessPageTable& manager, u64 dst_addr
|
||||
} // namespace
|
||||
|
||||
Result SetMemoryPermission(Core::System& system, u64 address, u64 size, MemoryPermission perm) {
|
||||
LOG_DEBUG(Kernel_SVC, "called, address=0x{:016X}, size={:#X}, perm=0x{:08X}", address, size,
|
||||
LOG_DEBUG(Kernel_SVC, "called, address={:#016x}, size={:#x}, perm={:#08x}", address, size,
|
||||
perm);
|
||||
|
||||
// Validate address / size.
|
||||
@@ -92,16 +268,24 @@ Result SetMemoryPermission(Core::System& system, u64 address, u64 size, MemoryPe
|
||||
R_UNLESS(IsValidSetMemoryPermission(perm), ResultInvalidNewMemoryPermission);
|
||||
|
||||
// Validate that the region is in range for the current process.
|
||||
auto& page_table = GetCurrentProcess(system.Kernel()).GetPageTable();
|
||||
auto& process = GetCurrentProcess(system.Kernel());
|
||||
auto& page_table = process.GetPageTable();
|
||||
R_UNLESS(page_table.Contains(address, size), ResultInvalidCurrentMemory);
|
||||
|
||||
// Set the memory attribute.
|
||||
R_RETURN(page_table.SetMemoryPermission(address, size, perm));
|
||||
const auto result = page_table.SetMemoryPermission(address, size, perm);
|
||||
if (result.raw == ResultInvalidCurrentMemory.raw &&
|
||||
IsHomebrewInPlaceNextLoadCodeRange(process, address, size)) {
|
||||
R_RETURN(
|
||||
SetHomebrewInPlaceMemoryPermissionByBlocks(process, address, size, perm, result));
|
||||
}
|
||||
|
||||
R_RETURN(result);
|
||||
}
|
||||
|
||||
Result SetMemoryAttribute(Core::System& system, u64 address, u64 size, u32 mask, u32 attr) {
|
||||
LOG_DEBUG(Kernel_SVC,
|
||||
"called, address=0x{:016X}, size={:#X}, mask=0x{:08X}, attribute=0x{:08X}", address,
|
||||
"called, address={:#016x}, size={:#x}, mask={:#08x}, attribute={:#08x}", address,
|
||||
size, mask, attr);
|
||||
|
||||
// Validate address / size.
|
||||
@@ -132,32 +316,57 @@ Result SetMemoryAttribute(Core::System& system, u64 address, u64 size, u32 mask,
|
||||
|
||||
/// Maps a memory range into a different range.
|
||||
Result MapMemory(Core::System& system, u64 dst_addr, u64 src_addr, u64 size) {
|
||||
LOG_TRACE(Kernel_SVC, "called, dst_addr={:#X}, src_addr=0x{:X}, size=0x{:X}", dst_addr,
|
||||
LOG_TRACE(Kernel_SVC, "called, dst_addr={:#x}, src_addr={:#x}, size={:#x}", dst_addr,
|
||||
src_addr, size);
|
||||
|
||||
auto& page_table{GetCurrentProcess(system.Kernel()).GetPageTable()};
|
||||
auto& process = GetCurrentProcess(system.Kernel());
|
||||
auto& page_table = process.GetPageTable();
|
||||
|
||||
if (const Result result{MapUnmapMemorySanityChecks(page_table, dst_addr, src_addr, size)};
|
||||
result.IsError()) {
|
||||
return result;
|
||||
}
|
||||
|
||||
R_RETURN(page_table.MapMemory(dst_addr, src_addr, size));
|
||||
const auto result = page_table.MapMemory(dst_addr, src_addr, size);
|
||||
if (result.raw == ResultInvalidCurrentMemory.raw && process.IsHomebrewInPlaceNextLoad()) {
|
||||
if (UnlockHomebrewInPlaceDeviceSharedSource(process, src_addr, size, result).IsSuccess()) {
|
||||
const auto retry_result = page_table.MapMemory(dst_addr, src_addr, size);
|
||||
if (retry_result.IsError()) {
|
||||
LOG_WARNING(Kernel_SVC,
|
||||
"NextLoad in-place: svcMapMemory retry failed after "
|
||||
"DeviceShared cleanup dst=0x{:016X}, src=0x{:016X}, size=0x{:X}, "
|
||||
"result={:#X}",
|
||||
dst_addr, src_addr, size, retry_result.raw);
|
||||
}
|
||||
R_RETURN(retry_result);
|
||||
}
|
||||
}
|
||||
|
||||
R_RETURN(result);
|
||||
}
|
||||
|
||||
/// Unmaps a region that was previously mapped with svcMapMemory
|
||||
Result UnmapMemory(Core::System& system, u64 dst_addr, u64 src_addr, u64 size) {
|
||||
LOG_TRACE(Kernel_SVC, "called, dst_addr={:#X}, src_addr=0x{:X}, size=0x{:X}", dst_addr,
|
||||
LOG_TRACE(Kernel_SVC, "called, dst_addr={:#x}, src_addr={:#x}, size={:#x}", dst_addr,
|
||||
src_addr, size);
|
||||
|
||||
auto& page_table{GetCurrentProcess(system.Kernel()).GetPageTable()};
|
||||
auto& process = GetCurrentProcess(system.Kernel());
|
||||
auto& page_table = process.GetPageTable();
|
||||
|
||||
if (const Result result{MapUnmapMemorySanityChecks(page_table, dst_addr, src_addr, size)};
|
||||
result.IsError()) {
|
||||
return result;
|
||||
}
|
||||
|
||||
R_RETURN(page_table.UnmapMemory(dst_addr, src_addr, size));
|
||||
const auto result = page_table.UnmapMemory(dst_addr, src_addr, size);
|
||||
if (result.raw == ResultInvalidCurrentMemory.raw && process.IsHomebrewInPlaceNextLoad()) {
|
||||
LOG_WARNING(Kernel_SVC,
|
||||
"NextLoad in-place: svcUnmapMemory failed dst=0x{:016X}, "
|
||||
"src=0x{:016X}, size=0x{:X}, result={:#X}",
|
||||
dst_addr, src_addr, size, result.raw);
|
||||
}
|
||||
|
||||
R_RETURN(result);
|
||||
}
|
||||
|
||||
Result SetMemoryPermission64(Core::System& system, uint64_t address, uint64_t size,
|
||||
|
||||
@@ -12,7 +12,7 @@ namespace Kernel::Svc {
|
||||
|
||||
/// Set the process heap to a given Size. It can both extend and shrink the heap.
|
||||
Result SetHeapSize(Core::System& system, u64* out_address, u64 size) {
|
||||
LOG_TRACE(Kernel_SVC, "called, heap_size={:#X}", size);
|
||||
LOG_TRACE(Kernel_SVC, "called, heap_size={:#x}", size);
|
||||
|
||||
// Validate size.
|
||||
R_UNLESS(Common::IsAligned(size, HeapSizeAlignment), ResultInvalidSize);
|
||||
@@ -31,10 +31,10 @@ Result SetHeapSize(Core::System& system, u64* out_address, u64 size) {
|
||||
|
||||
/// Maps memory at a desired address
|
||||
Result MapPhysicalMemory(Core::System& system, u64 addr, u64 size) {
|
||||
LOG_DEBUG(Kernel_SVC, "called, addr=0x{:016X}, size={:#X}", addr, size);
|
||||
LOG_DEBUG(Kernel_SVC, "called, addr={:#016x}, size={:#x}", addr, size);
|
||||
|
||||
if (!Common::IsAligned(addr, Core::Memory::YUZU_PAGESIZE)) {
|
||||
LOG_ERROR(Kernel_SVC, "Address is not aligned to 4KB, 0x{:016X}", addr);
|
||||
LOG_ERROR(Kernel_SVC, "Address is not aligned to 4KB, {:#016X}", addr);
|
||||
R_THROW(ResultInvalidAddress);
|
||||
}
|
||||
|
||||
@@ -63,14 +63,14 @@ Result MapPhysicalMemory(Core::System& system, u64 addr, u64 size) {
|
||||
|
||||
if (!page_table.Contains(addr, size)) {
|
||||
LOG_ERROR(Kernel_SVC,
|
||||
"Address is not within the address space, addr=0x{:016X}, size=0x{:016X}", addr,
|
||||
"Address is not within the address space, addr={:#016x}, size={:#016x}", addr,
|
||||
size);
|
||||
R_THROW(ResultInvalidMemoryRegion);
|
||||
}
|
||||
|
||||
if (!page_table.IsInAliasRegion(addr, size)) {
|
||||
LOG_ERROR(Kernel_SVC,
|
||||
"Address is not within the alias region, addr=0x{:016X}, size=0x{:016X}", addr,
|
||||
"Address is not within the alias region, addr={:#016x}, size={:#016x}", addr,
|
||||
size);
|
||||
R_THROW(ResultInvalidMemoryRegion);
|
||||
}
|
||||
@@ -80,10 +80,10 @@ Result MapPhysicalMemory(Core::System& system, u64 addr, u64 size) {
|
||||
|
||||
/// Unmaps memory previously mapped via MapPhysicalMemory
|
||||
Result UnmapPhysicalMemory(Core::System& system, u64 addr, u64 size) {
|
||||
LOG_DEBUG(Kernel_SVC, "called, addr=0x{:016X}, size={:#X}", addr, size);
|
||||
LOG_DEBUG(Kernel_SVC, "called, addr={:#016x}, size={:#x}", addr, size);
|
||||
|
||||
if (!Common::IsAligned(addr, Core::Memory::YUZU_PAGESIZE)) {
|
||||
LOG_ERROR(Kernel_SVC, "Address is not aligned to 4KB, 0x{:016X}", addr);
|
||||
LOG_ERROR(Kernel_SVC, "Address is not aligned to 4KB, {:#016X}", addr);
|
||||
R_THROW(ResultInvalidAddress);
|
||||
}
|
||||
|
||||
@@ -112,14 +112,14 @@ Result UnmapPhysicalMemory(Core::System& system, u64 addr, u64 size) {
|
||||
|
||||
if (!page_table.Contains(addr, size)) {
|
||||
LOG_ERROR(Kernel_SVC,
|
||||
"Address is not within the address space, addr=0x{:016X}, size=0x{:016X}", addr,
|
||||
"Address is not within the address space, addr={:#016x}, size={:#016x}", addr,
|
||||
size);
|
||||
R_THROW(ResultInvalidMemoryRegion);
|
||||
}
|
||||
|
||||
if (!page_table.IsInAliasRegion(addr, size)) {
|
||||
LOG_ERROR(Kernel_SVC,
|
||||
"Address is not within the alias region, addr=0x{:016X}, size=0x{:016X}", addr,
|
||||
"Address is not within the alias region, addr={:#016x}, size={:#016x}", addr,
|
||||
size);
|
||||
R_THROW(ResultInvalidMemoryRegion);
|
||||
}
|
||||
|
||||
@@ -4,17 +4,216 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <algorithm>
|
||||
#include <filesystem>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
#include "common/fs/fs.h"
|
||||
#include "common/fs/path_util.h"
|
||||
#include "common/input.h"
|
||||
#include "core/core.h"
|
||||
#include "core/file_sys/vfs/vfs_types.h"
|
||||
#include "core/hle/kernel/k_process.h"
|
||||
#include "core/hle/kernel/k_thread.h"
|
||||
#include "core/hle/kernel/kernel.h"
|
||||
#include "core/hle/kernel/physical_core.h"
|
||||
#include "core/hle/kernel/svc.h"
|
||||
#include "core/hle/service/hid/hid_server.h"
|
||||
#include "core/hle/service/nvdrv/nvdrv_interface.h"
|
||||
#include "core/hle/service/sm/sm.h"
|
||||
#include "core/loader/nro.h"
|
||||
#include "hid_core/frontend/emulated_controller.h"
|
||||
#include "hid_core/hid_core.h"
|
||||
#include "hid_core/resource_manager.h"
|
||||
|
||||
namespace Kernel::Svc {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr size_t HomebrewNextLoadPathSize = 0x200;
|
||||
constexpr size_t HomebrewNextLoadArgvSize = 0x800;
|
||||
|
||||
std::string ReadHomebrewString(Core::Memory::Memory& memory, KProcessAddress address,
|
||||
size_t max_size) {
|
||||
if (GetInteger(address) == 0) {
|
||||
return {};
|
||||
}
|
||||
return memory.ReadCString(Common::ProcessAddress{GetInteger(address)}, max_size);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
/// Exits the current process
|
||||
void ExitProcess(Core::System& system) {
|
||||
void ExitProcess(Core::System& system, std::span<uint64_t, 8> args) {
|
||||
auto* current_process = GetCurrentProcessPointer(system.Kernel());
|
||||
auto* current_thread = GetCurrentThreadPointer(system.Kernel());
|
||||
|
||||
LOG_INFO(Kernel_SVC, "Process {} exiting", current_process->GetProcessId());
|
||||
const auto next_load_path_addr = current_process->GetHomebrewNextLoadPathAddr();
|
||||
const auto next_load_argv_addr = current_process->GetHomebrewNextLoadArgvAddr();
|
||||
if (GetInteger(next_load_path_addr) != 0) {
|
||||
auto& memory = current_process->GetMemory();
|
||||
const auto next_load_path =
|
||||
ReadHomebrewString(memory, next_load_path_addr, HomebrewNextLoadPathSize);
|
||||
const auto next_load_argv =
|
||||
ReadHomebrewString(memory, next_load_argv_addr, HomebrewNextLoadArgvSize);
|
||||
if (!next_load_path.empty()) {
|
||||
auto guest_path = Common::FS::SanitizePath(next_load_path);
|
||||
constexpr std::string_view SdmcPrefix{"sdmc:"};
|
||||
FileSys::VirtualFile file{};
|
||||
|
||||
const bool is_sdmc_path = guest_path.rfind(SdmcPrefix, 0) == 0;
|
||||
const bool is_absolute_guest_path = !guest_path.empty() && guest_path.front() == '/';
|
||||
if (is_sdmc_path || is_absolute_guest_path) {
|
||||
auto relative_path =
|
||||
is_sdmc_path ? guest_path.substr(SdmcPrefix.size()) : guest_path;
|
||||
while (!relative_path.empty() && relative_path.front() == '/') {
|
||||
relative_path.erase(relative_path.begin());
|
||||
}
|
||||
|
||||
const auto host_path = Common::FS::GetEdenPath(Common::FS::EdenPath::SDMCDir) /
|
||||
std::filesystem::path{Common::FS::ToU8String(relative_path)};
|
||||
const auto host_path_string = Common::FS::PathToUTF8String(host_path);
|
||||
file = Core::GetGameFileFromPath(system.GetFilesystem(), host_path_string);
|
||||
if (!file) {
|
||||
LOG_WARNING(Kernel_SVC,
|
||||
"NextLoad: failed to open guest_path='{}', host_path='{}'",
|
||||
next_load_path, host_path_string);
|
||||
}
|
||||
} else {
|
||||
file = Core::GetGameFileFromPath(system.GetFilesystem(), guest_path);
|
||||
if (!file) {
|
||||
LOG_WARNING(Kernel_SVC, "NextLoad: failed to open guest_path='{}'",
|
||||
next_load_path);
|
||||
}
|
||||
}
|
||||
|
||||
if (file) {
|
||||
const auto nvdrv =
|
||||
system.ServiceManager().GetService<Service::Nvidia::NVDRV>("nvdrv:s");
|
||||
if (!nvdrv) {
|
||||
LOG_WARNING(Kernel_SVC, "NextLoad: NVDRV service unavailable for reset");
|
||||
} else {
|
||||
nvdrv->GetModule()->ResetForProcess(current_process);
|
||||
}
|
||||
|
||||
auto& page_table = current_process->GetPageTable();
|
||||
const u64 heap_start = GetInteger(page_table.GetHeapRegionStart());
|
||||
const u64 heap_size = page_table.GetHeapRegionSize();
|
||||
const u64 heap_end = heap_start + heap_size;
|
||||
if (heap_start != 0 && heap_size != 0 && heap_end > heap_start) {
|
||||
struct DeviceSharedBlock {
|
||||
u64 address;
|
||||
u64 size;
|
||||
u16 device_use_count;
|
||||
};
|
||||
|
||||
std::vector<DeviceSharedBlock> blocks;
|
||||
for (u64 cursor = heap_start; cursor < heap_end;) {
|
||||
KMemoryInfo info;
|
||||
PageInfo page_info;
|
||||
const auto query_result =
|
||||
page_table.QueryInfo(std::addressof(info), std::addressof(page_info),
|
||||
cursor);
|
||||
if (query_result.IsError()) {
|
||||
LOG_WARNING(Kernel_SVC,
|
||||
"NextLoad: DeviceShared heap cleanup query failed "
|
||||
"address=0x{:016X}, result={:#X}",
|
||||
cursor, query_result.raw);
|
||||
break;
|
||||
}
|
||||
|
||||
const u64 block_end = (std::min<u64>)(info.GetEndAddress(), heap_end);
|
||||
if (block_end <= cursor) {
|
||||
LOG_WARNING(Kernel_SVC,
|
||||
"NextLoad: DeviceShared heap cleanup walk stalled "
|
||||
"cursor=0x{:016X}, block=0x{:016X}/0x{:X}",
|
||||
cursor, info.GetAddress(), info.GetSize());
|
||||
break;
|
||||
}
|
||||
|
||||
const bool is_device_shared =
|
||||
True(info.GetState() & KMemoryState::FlagCanDeviceMap) &&
|
||||
info.GetAttribute() == KMemoryAttribute::DeviceShared &&
|
||||
info.m_device_use_count > 0;
|
||||
if (is_device_shared) {
|
||||
blocks.push_back(DeviceSharedBlock{
|
||||
.address = cursor,
|
||||
.size = block_end - cursor,
|
||||
.device_use_count = info.m_device_use_count,
|
||||
});
|
||||
}
|
||||
cursor = block_end;
|
||||
}
|
||||
|
||||
for (const auto& block : blocks) {
|
||||
for (u16 unlock = 0; unlock < block.device_use_count; unlock++) {
|
||||
const auto unlock_result =
|
||||
page_table.UnlockForDeviceAddressSpace(block.address, block.size);
|
||||
if (unlock_result.IsError()) {
|
||||
LOG_WARNING(Kernel_SVC,
|
||||
"NextLoad: DeviceShared heap cleanup unlock failed "
|
||||
"address=0x{:016X}, size=0x{:X}, remaining={}, "
|
||||
"result={:#X}",
|
||||
block.address, block.size,
|
||||
block.device_use_count - unlock - 1, unlock_result.raw);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (Loader::LoadNroInPlace(system, *current_process, *current_thread, file,
|
||||
next_load_path, next_load_argv)) {
|
||||
const auto aruid = current_process->GetProcessId();
|
||||
if (const auto hid =
|
||||
system.ServiceManager().GetService<Service::HID::IHidServer>("hid")) {
|
||||
const auto resource_manager = hid->GetResourceManager();
|
||||
resource_manager->UnregisterAppletResourceUserId(aruid);
|
||||
const auto register_result =
|
||||
resource_manager->RegisterAppletResourceUserId(aruid, true);
|
||||
if (register_result.IsError()) {
|
||||
LOG_WARNING(Kernel_SVC,
|
||||
"NextLoad: failed to register HID applet resource "
|
||||
"aruid={}, result={:#X}",
|
||||
aruid, register_result.raw);
|
||||
}
|
||||
} else {
|
||||
LOG_WARNING(Kernel_SVC, "NextLoad: HID service unavailable for reset");
|
||||
}
|
||||
|
||||
auto& hid_core = system.HIDCore();
|
||||
hid_core.DisableAllControllerConfiguration();
|
||||
hid_core.SetSupportedStyleTag({Core::HID::NpadStyleSet::All});
|
||||
hid_core.ReloadInputDevices();
|
||||
|
||||
const auto activate_controller = [&](Core::HID::NpadIdType npad_id) {
|
||||
auto* controller = hid_core.GetEmulatedController(npad_id);
|
||||
if (controller == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
(void)controller->SetPollingMode(Core::HID::EmulatedDeviceIndex::AllDevices,
|
||||
Common::Input::PollingMode::Active);
|
||||
};
|
||||
|
||||
activate_controller(Core::HID::NpadIdType::Player1);
|
||||
activate_controller(Core::HID::NpadIdType::Handheld);
|
||||
|
||||
system.Kernel().CurrentPhysicalCore().LoadContext(current_thread);
|
||||
|
||||
const auto& context = current_thread->GetContext();
|
||||
for (size_t i = 0; i < args.size(); i++) {
|
||||
args[i] = context.r[i];
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
ASSERT_MSG(current_process->GetState() == KProcess::State::Running,
|
||||
"Process has already exited");
|
||||
|
||||
@@ -23,7 +222,7 @@ void ExitProcess(Core::System& system) {
|
||||
|
||||
/// Gets the ID of the specified process or a specified thread's owning process.
|
||||
Result GetProcessId(Core::System& system, u64* out_process_id, Handle handle) {
|
||||
LOG_DEBUG(Kernel_SVC, "called handle=0x{:08X}", handle);
|
||||
LOG_DEBUG(Kernel_SVC, "called handle={:#08x}", handle);
|
||||
|
||||
// Get the object from the handle table.
|
||||
KScopedAutoObject obj = GetCurrentProcess(system.Kernel())
|
||||
@@ -55,7 +254,7 @@ Result GetProcessId(Core::System& system, u64* out_process_id, Handle handle) {
|
||||
|
||||
Result GetProcessList(Core::System& system, s32* out_num_processes, u64 out_process_ids,
|
||||
int32_t out_process_ids_size) {
|
||||
LOG_DEBUG(Kernel_SVC, "called. out_process_ids=0x{:016X}, out_process_ids_size={}",
|
||||
LOG_DEBUG(Kernel_SVC, "called. out_process_ids={:#016x}, out_process_ids_size={}",
|
||||
out_process_ids, out_process_ids_size);
|
||||
|
||||
// If the supplied size is negative or greater than INT32_MAX / sizeof(u64), bail.
|
||||
@@ -71,7 +270,7 @@ Result GetProcessList(Core::System& system, s32* out_num_processes, u64 out_proc
|
||||
|
||||
if (out_process_ids_size > 0 &&
|
||||
!GetCurrentProcess(kernel).GetPageTable().Contains(out_process_ids, total_copy_size)) {
|
||||
LOG_ERROR(Kernel_SVC, "Address range outside address space. begin=0x{:016X}, end=0x{:016X}",
|
||||
LOG_ERROR(Kernel_SVC, "Address range outside address space. begin={:#016x}, end={:#016x}",
|
||||
out_process_ids, out_process_ids + total_copy_size);
|
||||
R_THROW(ResultInvalidCurrentMemory);
|
||||
}
|
||||
@@ -95,12 +294,12 @@ Result GetProcessList(Core::System& system, s32* out_num_processes, u64 out_proc
|
||||
|
||||
Result GetProcessInfo(Core::System& system, s64* out, Handle process_handle,
|
||||
ProcessInfoType info_type) {
|
||||
LOG_DEBUG(Kernel_SVC, "called, handle=0x{:08X}, type={:#X}", process_handle, info_type);
|
||||
LOG_DEBUG(Kernel_SVC, "called, handle={:#08x}, type={:#x}", process_handle, info_type);
|
||||
|
||||
const auto& handle_table = GetCurrentProcess(system.Kernel()).GetHandleTable();
|
||||
KScopedAutoObject process = handle_table.GetObject<KProcess>(system.Kernel(), process_handle);
|
||||
if (process.IsNull()) {
|
||||
LOG_ERROR(Kernel_SVC, "Process handle does not exist, process_handle=0x{:08X}",
|
||||
LOG_ERROR(Kernel_SVC, "Process handle does not exist, process_handle={:#08x}",
|
||||
process_handle);
|
||||
R_THROW(ResultInvalidHandle);
|
||||
}
|
||||
@@ -132,8 +331,8 @@ Result TerminateProcess(Core::System& system, Handle process_handle) {
|
||||
R_THROW(ResultNotImplemented);
|
||||
}
|
||||
|
||||
void ExitProcess64(Core::System& system) {
|
||||
ExitProcess(system);
|
||||
void ExitProcess64(Core::System& system, std::span<uint64_t, 8> args) {
|
||||
ExitProcess(system, args);
|
||||
}
|
||||
|
||||
Result GetProcessId64(Core::System& system, uint64_t* out_process_id, Handle process_handle) {
|
||||
@@ -164,8 +363,8 @@ Result GetProcessInfo64(Core::System& system, int64_t* out_info, Handle process_
|
||||
R_RETURN(GetProcessInfo(system, out_info, process_handle, info_type));
|
||||
}
|
||||
|
||||
void ExitProcess64From32(Core::System& system) {
|
||||
ExitProcess(system);
|
||||
void ExitProcess64From32(Core::System& system, std::span<uint64_t, 8> args) {
|
||||
ExitProcess(system, args);
|
||||
}
|
||||
|
||||
Result GetProcessId64From32(Core::System& system, uint64_t* out_process_id, Handle process_handle) {
|
||||
|
||||
@@ -32,7 +32,7 @@ constexpr bool IsValidProcessMemoryPermission(Svc::MemoryPermission perm) {
|
||||
Result SetProcessMemoryPermission(Core::System& system, Handle process_handle, u64 address,
|
||||
u64 size, Svc::MemoryPermission perm) {
|
||||
LOG_TRACE(Kernel_SVC,
|
||||
"called, process_handle={:#X}, addr=0x{:X}, size=0x{:X}, permissions=0x{:08X}",
|
||||
"called, process_handle={:#x}, addr={:#x}, size={:#x}, permissions={:#08x}",
|
||||
process_handle, address, size, perm);
|
||||
|
||||
// Validate the address/size.
|
||||
@@ -62,7 +62,7 @@ Result SetProcessMemoryPermission(Core::System& system, Handle process_handle, u
|
||||
Result MapProcessMemory(Core::System& system, u64 dst_address, Handle process_handle,
|
||||
u64 src_address, u64 size) {
|
||||
LOG_TRACE(Kernel_SVC,
|
||||
"called, dst_address={:#X}, process_handle=0x{:X}, src_address=0x{:X}, size=0x{:X}",
|
||||
"called, dst_address={:#x}, process_handle={:#x}, src_address={:#x}, size={:#x}",
|
||||
dst_address, process_handle, src_address, size);
|
||||
|
||||
// Validate the address/size.
|
||||
@@ -103,7 +103,7 @@ Result MapProcessMemory(Core::System& system, u64 dst_address, Handle process_ha
|
||||
Result UnmapProcessMemory(Core::System& system, u64 dst_address, Handle process_handle,
|
||||
u64 src_address, u64 size) {
|
||||
LOG_TRACE(Kernel_SVC,
|
||||
"called, dst_address={:#X}, process_handle=0x{:X}, src_address=0x{:X}, size=0x{:X}",
|
||||
"called, dst_address={:#x}, process_handle={:#x}, src_address={:#x}, size={:#x}",
|
||||
dst_address, process_handle, src_address, size);
|
||||
|
||||
// Validate the address/size.
|
||||
@@ -136,39 +136,39 @@ Result UnmapProcessMemory(Core::System& system, u64 dst_address, Handle process_
|
||||
Result MapProcessCodeMemory(Core::System& system, Handle process_handle, u64 dst_address,
|
||||
u64 src_address, u64 size) {
|
||||
LOG_DEBUG(Kernel_SVC,
|
||||
"called. process_handle=0x{:08X}, dst_address=0x{:016X}, "
|
||||
"src_address=0x{:016X}, size=0x{:016X}",
|
||||
"called. process_handle={:#08x}, dst_address={:#016x}, "
|
||||
"src_address={:#016x}, size={:#016x}",
|
||||
process_handle, dst_address, src_address, size);
|
||||
|
||||
if (!Common::IsAligned(src_address, Core::Memory::YUZU_PAGESIZE)) {
|
||||
LOG_ERROR(Kernel_SVC, "src_address is not page-aligned (src_address=0x{:016X}).",
|
||||
LOG_ERROR(Kernel_SVC, "src_address is not page-aligned (src_address={:#016X}).",
|
||||
src_address);
|
||||
R_THROW(ResultInvalidAddress);
|
||||
}
|
||||
|
||||
if (!Common::IsAligned(dst_address, Core::Memory::YUZU_PAGESIZE)) {
|
||||
LOG_ERROR(Kernel_SVC, "dst_address is not page-aligned (dst_address=0x{:016X}).",
|
||||
LOG_ERROR(Kernel_SVC, "dst_address is not page-aligned (dst_address={:#016X}).",
|
||||
dst_address);
|
||||
R_THROW(ResultInvalidAddress);
|
||||
}
|
||||
|
||||
if (size == 0 || !Common::IsAligned(size, Core::Memory::YUZU_PAGESIZE)) {
|
||||
LOG_ERROR(Kernel_SVC, "Size is zero or not page-aligned (size=0x{:016X})", size);
|
||||
LOG_ERROR(Kernel_SVC, "Size is zero or not page-aligned (size={:#016X})", size);
|
||||
R_THROW(ResultInvalidSize);
|
||||
}
|
||||
|
||||
if (!IsValidAddressRange(dst_address, size)) {
|
||||
LOG_ERROR(Kernel_SVC,
|
||||
"Destination address range overflows the address space (dst_address=0x{:016X}, "
|
||||
"size=0x{:016X}).",
|
||||
"Destination address range overflows the address space (dst_address={:#016x}, "
|
||||
"size={:#016x}).",
|
||||
dst_address, size);
|
||||
R_THROW(ResultInvalidCurrentMemory);
|
||||
}
|
||||
|
||||
if (!IsValidAddressRange(src_address, size)) {
|
||||
LOG_ERROR(Kernel_SVC,
|
||||
"Source address range overflows the address space (src_address=0x{:016X}, "
|
||||
"size=0x{:016X}).",
|
||||
"Source address range overflows the address space (src_address={:#016x}, "
|
||||
"size={:#016x}).",
|
||||
src_address, size);
|
||||
R_THROW(ResultInvalidCurrentMemory);
|
||||
}
|
||||
@@ -176,7 +176,7 @@ Result MapProcessCodeMemory(Core::System& system, Handle process_handle, u64 dst
|
||||
const auto& handle_table = GetCurrentProcess(system.Kernel()).GetHandleTable();
|
||||
KScopedAutoObject process = handle_table.GetObject<KProcess>(system.Kernel(), process_handle);
|
||||
if (process.IsNull()) {
|
||||
LOG_ERROR(Kernel_SVC, "Invalid process handle specified (handle=0x{:08X}).",
|
||||
LOG_ERROR(Kernel_SVC, "Invalid process handle specified (handle={:#08x}).",
|
||||
process_handle);
|
||||
R_THROW(ResultInvalidHandle);
|
||||
}
|
||||
@@ -184,8 +184,8 @@ Result MapProcessCodeMemory(Core::System& system, Handle process_handle, u64 dst
|
||||
auto& page_table = process->GetPageTable();
|
||||
if (!page_table.Contains(src_address, size)) {
|
||||
LOG_ERROR(Kernel_SVC,
|
||||
"Source address range is not within the address space (src_address=0x{:016X}, "
|
||||
"size=0x{:016X}).",
|
||||
"Source address range is not within the address space (src_address={:#016x}, "
|
||||
"size={:#016x}).",
|
||||
src_address, size);
|
||||
R_THROW(ResultInvalidCurrentMemory);
|
||||
}
|
||||
@@ -197,39 +197,39 @@ Result MapProcessCodeMemory(Core::System& system, Handle process_handle, u64 dst
|
||||
Result UnmapProcessCodeMemory(Core::System& system, Handle process_handle, u64 dst_address,
|
||||
u64 src_address, u64 size) {
|
||||
LOG_DEBUG(Kernel_SVC,
|
||||
"called. process_handle=0x{:08X}, dst_address=0x{:016X}, src_address=0x{:016X}, "
|
||||
"size=0x{:016X}",
|
||||
"called. process_handle={:#08x}, dst_address={:#016x}, src_address={:#016x}, "
|
||||
"size={:#016x}",
|
||||
process_handle, dst_address, src_address, size);
|
||||
|
||||
if (!Common::IsAligned(dst_address, Core::Memory::YUZU_PAGESIZE)) {
|
||||
LOG_ERROR(Kernel_SVC, "dst_address is not page-aligned (dst_address=0x{:016X}).",
|
||||
LOG_ERROR(Kernel_SVC, "dst_address is not page-aligned (dst_address={:#016X}).",
|
||||
dst_address);
|
||||
R_THROW(ResultInvalidAddress);
|
||||
}
|
||||
|
||||
if (!Common::IsAligned(src_address, Core::Memory::YUZU_PAGESIZE)) {
|
||||
LOG_ERROR(Kernel_SVC, "src_address is not page-aligned (src_address=0x{:016X}).",
|
||||
LOG_ERROR(Kernel_SVC, "src_address is not page-aligned (src_address={:#016X}).",
|
||||
src_address);
|
||||
R_THROW(ResultInvalidAddress);
|
||||
}
|
||||
|
||||
if (size == 0 || !Common::IsAligned(size, Core::Memory::YUZU_PAGESIZE)) {
|
||||
LOG_ERROR(Kernel_SVC, "Size is zero or not page-aligned (size=0x{:016X}).", size);
|
||||
LOG_ERROR(Kernel_SVC, "Size is zero or not page-aligned (size={:#016X}).", size);
|
||||
R_THROW(ResultInvalidSize);
|
||||
}
|
||||
|
||||
if (!IsValidAddressRange(dst_address, size)) {
|
||||
LOG_ERROR(Kernel_SVC,
|
||||
"Destination address range overflows the address space (dst_address=0x{:016X}, "
|
||||
"size=0x{:016X}).",
|
||||
"Destination address range overflows the address space (dst_address={:#016x}, "
|
||||
"size={:#016x}).",
|
||||
dst_address, size);
|
||||
R_THROW(ResultInvalidCurrentMemory);
|
||||
}
|
||||
|
||||
if (!IsValidAddressRange(src_address, size)) {
|
||||
LOG_ERROR(Kernel_SVC,
|
||||
"Source address range overflows the address space (src_address=0x{:016X}, "
|
||||
"size=0x{:016X}).",
|
||||
"Source address range overflows the address space (src_address={:#016x}, "
|
||||
"size={:#016x}).",
|
||||
src_address, size);
|
||||
R_THROW(ResultInvalidCurrentMemory);
|
||||
}
|
||||
@@ -237,7 +237,7 @@ Result UnmapProcessCodeMemory(Core::System& system, Handle process_handle, u64 d
|
||||
const auto& handle_table = GetCurrentProcess(system.Kernel()).GetHandleTable();
|
||||
KScopedAutoObject process = handle_table.GetObject<KProcess>(system.Kernel(), process_handle);
|
||||
if (process.IsNull()) {
|
||||
LOG_ERROR(Kernel_SVC, "Invalid process handle specified (handle=0x{:08X}).",
|
||||
LOG_ERROR(Kernel_SVC, "Invalid process handle specified (handle={:#08x}).",
|
||||
process_handle);
|
||||
R_THROW(ResultInvalidHandle);
|
||||
}
|
||||
@@ -245,8 +245,8 @@ Result UnmapProcessCodeMemory(Core::System& system, Handle process_handle, u64 d
|
||||
auto& page_table = process->GetPageTable();
|
||||
if (!page_table.Contains(src_address, size)) {
|
||||
LOG_ERROR(Kernel_SVC,
|
||||
"Source address range is not within the address space (src_address=0x{:016X}, "
|
||||
"size=0x{:016X}).",
|
||||
"Source address range is not within the address space (src_address={:#016x}, "
|
||||
"size={:#016x}).",
|
||||
src_address, size);
|
||||
R_THROW(ResultInvalidCurrentMemory);
|
||||
}
|
||||
|
||||
@@ -13,8 +13,8 @@ namespace Kernel::Svc {
|
||||
Result QueryMemory(Core::System& system, uint64_t out_memory_info, PageInfo* out_page_info,
|
||||
u64 query_address) {
|
||||
LOG_TRACE(Kernel_SVC,
|
||||
"called, out_memory_info=0x{:016X}, "
|
||||
"query_address=0x{:016X}",
|
||||
"called, out_memory_info={:#016x}, "
|
||||
"query_address={:#016x}",
|
||||
out_memory_info, query_address);
|
||||
|
||||
// Query memory is just QueryProcessMemory on the current process.
|
||||
@@ -24,11 +24,11 @@ Result QueryMemory(Core::System& system, uint64_t out_memory_info, PageInfo* out
|
||||
|
||||
Result QueryProcessMemory(Core::System& system, uint64_t out_memory_info, PageInfo* out_page_info,
|
||||
Handle process_handle, uint64_t address) {
|
||||
LOG_TRACE(Kernel_SVC, "called process=0x{:08X} address={:X}", process_handle, address);
|
||||
LOG_TRACE(Kernel_SVC, "called process={:#08x} address={:X}", process_handle, address);
|
||||
const auto& handle_table = GetCurrentProcess(system.Kernel()).GetHandleTable();
|
||||
KScopedAutoObject process = handle_table.GetObject<KProcess>(system.Kernel(), process_handle);
|
||||
if (process.IsNull()) {
|
||||
LOG_ERROR(Kernel_SVC, "Process handle does not exist, process_handle=0x{:08X}",
|
||||
LOG_ERROR(Kernel_SVC, "Process handle does not exist, process_handle={:#08x}",
|
||||
process_handle);
|
||||
R_THROW(ResultInvalidHandle);
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ constexpr bool IsValidSharedMemoryPermission(MemoryPermission perm) {
|
||||
Result MapSharedMemory(Core::System& system, Handle shmem_handle, u64 address, u64 size,
|
||||
Svc::MemoryPermission map_perm) {
|
||||
LOG_TRACE(Kernel_SVC,
|
||||
"called, shared_memory_handle={:#X}, addr=0x{:X}, size=0x{:X}, permissions=0x{:08X}",
|
||||
"called, shared_memory_handle={:#x}, addr={:#x}, size={:#x}, permissions={:#08x}",
|
||||
shmem_handle, address, size, map_perm);
|
||||
|
||||
// Validate the address/size.
|
||||
|
||||
@@ -17,7 +17,7 @@ namespace Kernel::Svc {
|
||||
|
||||
/// Close a handle
|
||||
Result CloseHandle(Core::System& system, Handle handle) {
|
||||
LOG_TRACE(Kernel_SVC, "Closing handle 0x{:08X}", handle);
|
||||
LOG_TRACE(Kernel_SVC, "Closing handle {:#08x}", handle);
|
||||
|
||||
// Remove the handle.
|
||||
R_UNLESS(GetCurrentProcess(system.Kernel()).GetHandleTable().Remove(system.Kernel(), handle),
|
||||
@@ -28,7 +28,7 @@ Result CloseHandle(Core::System& system, Handle handle) {
|
||||
|
||||
/// Clears the signaled state of an event or process.
|
||||
Result ResetSignal(Core::System& system, Handle handle) {
|
||||
LOG_DEBUG(Kernel_SVC, "called handle 0x{:08X}", handle);
|
||||
LOG_DEBUG(Kernel_SVC, "called handle {:#08x}", handle);
|
||||
|
||||
// Get the current handle table.
|
||||
const auto& handle_table = GetCurrentProcess(system.Kernel()).GetHandleTable();
|
||||
@@ -103,7 +103,7 @@ Result WaitSynchronization(Core::System& system, int32_t* out_index, u64 user_ha
|
||||
|
||||
/// Resumes a thread waiting on WaitSynchronization
|
||||
Result CancelSynchronization(Core::System& system, Handle handle) {
|
||||
LOG_TRACE(Kernel_SVC, "called handle={:#X}", handle);
|
||||
LOG_TRACE(Kernel_SVC, "called handle={:#x}", handle);
|
||||
|
||||
// Get the thread from its handle.
|
||||
KScopedAutoObject thread = GetCurrentProcess(system.Kernel()).GetHandleTable().GetObject<KThread>(system.Kernel(), handle);
|
||||
|
||||
@@ -26,8 +26,8 @@ constexpr bool IsValidVirtualCoreId(int32_t core_id) {
|
||||
Result CreateThread(Core::System& system, Handle* out_handle, u64 entry_point, u64 arg,
|
||||
u64 stack_bottom, s32 priority, s32 core_id) {
|
||||
LOG_DEBUG(Kernel_SVC,
|
||||
"called entry_point=0x{:08X}, arg=0x{:08X}, stack_bottom=0x{:08X}, "
|
||||
"priority=0x{:08X}, core_id=0x{:08X}",
|
||||
"called entry_point={:#08x}, arg={:#08x}, stack_bottom={:#08x}, "
|
||||
"priority={:#08x}, core_id={:#08x}",
|
||||
entry_point, arg, stack_bottom, priority, core_id);
|
||||
|
||||
// Adjust core id, if it's the default magic.
|
||||
@@ -85,7 +85,7 @@ Result CreateThread(Core::System& system, Handle* out_handle, u64 entry_point, u
|
||||
|
||||
/// Starts the thread for the provided handle
|
||||
Result StartThread(Core::System& system, Handle thread_handle) {
|
||||
LOG_DEBUG(Kernel_SVC, "called thread=0x{:08X}", thread_handle);
|
||||
LOG_DEBUG(Kernel_SVC, "called thread={:#08x}", thread_handle);
|
||||
|
||||
// Get the thread from its handle.
|
||||
KScopedAutoObject thread = GetCurrentProcess(system.Kernel()).GetHandleTable().GetObject<KThread>(system.Kernel(), thread_handle);
|
||||
@@ -143,7 +143,7 @@ void SleepThread(Core::System& system, s64 ns) {
|
||||
|
||||
/// Gets the thread context
|
||||
Result GetThreadContext3(Core::System& system, u64 out_context, Handle thread_handle) {
|
||||
LOG_DEBUG(Kernel_SVC, "called, out_context=0x{:08X}, thread_handle={:#X}", out_context, thread_handle);
|
||||
LOG_DEBUG(Kernel_SVC, "called, out_context={:#08x}, thread_handle={:#x}", out_context, thread_handle);
|
||||
|
||||
// Get the thread from its handle.
|
||||
KScopedAutoObject thread = GetCurrentProcess(system.Kernel()).GetHandleTable().GetObject<KThread>(system.Kernel(), thread_handle);
|
||||
@@ -202,7 +202,7 @@ Result GetThreadList(Core::System& system, s32* out_num_threads, u64 out_thread_
|
||||
// TODO: Handle this case when debug events are supported.
|
||||
UNIMPLEMENTED_IF(debug_handle != InvalidHandle);
|
||||
|
||||
LOG_DEBUG(Kernel_SVC, "called. out_thread_ids=0x{:016X}, out_thread_ids_size={}",
|
||||
LOG_DEBUG(Kernel_SVC, "called. out_thread_ids={:#016x}, out_thread_ids_size={}",
|
||||
out_thread_ids, out_thread_ids_size);
|
||||
|
||||
// If the size is negative or larger than INT32_MAX / sizeof(u64)
|
||||
@@ -217,7 +217,7 @@ Result GetThreadList(Core::System& system, s32* out_num_threads, u64 out_thread_
|
||||
|
||||
if (out_thread_ids_size > 0 &&
|
||||
!current_process->GetPageTable().Contains(out_thread_ids, total_copy_size)) {
|
||||
LOG_ERROR(Kernel_SVC, "Address range outside address space. begin=0x{:016X}, end=0x{:016X}",
|
||||
LOG_ERROR(Kernel_SVC, "Address range outside address space. begin={:#016x}, end={:#016x}",
|
||||
out_thread_ids, out_thread_ids + total_copy_size);
|
||||
R_THROW(ResultInvalidCurrentMemory);
|
||||
}
|
||||
@@ -239,7 +239,7 @@ Result GetThreadList(Core::System& system, s32* out_num_threads, u64 out_thread_
|
||||
|
||||
Result GetThreadCoreMask(Core::System& system, s32* out_core_id, u64* out_affinity_mask,
|
||||
Handle thread_handle) {
|
||||
LOG_TRACE(Kernel_SVC, "called, handle=0x{:08X}", thread_handle);
|
||||
LOG_TRACE(Kernel_SVC, "called, handle={:#08x}", thread_handle);
|
||||
|
||||
// Get the thread from its handle.
|
||||
KScopedAutoObject thread = GetCurrentProcess(system.Kernel()).GetHandleTable().GetObject<KThread>(system.Kernel(), thread_handle);
|
||||
|
||||
@@ -41,7 +41,6 @@ ButtonPoller::ButtonPoller(Core::System& system, WindowSystem& window_system) {
|
||||
Core::HID::ControllerUpdateCallback engine_callback{
|
||||
.on_change = [this, &window_system](Core::HID::ControllerTriggerType type) {
|
||||
if (type == Core::HID::ControllerTriggerType::Button) {
|
||||
std::unique_lock lk{m_mutex};
|
||||
OnButtonStateChanged(window_system);
|
||||
}
|
||||
},
|
||||
@@ -52,29 +51,11 @@ ButtonPoller::ButtonPoller(Core::System& system, WindowSystem& window_system) {
|
||||
m_handheld_key = m_handheld->SetCallback(engine_callback);
|
||||
m_player1 = system.HIDCore().GetEmulatedController(Core::HID::NpadIdType::Player1);
|
||||
m_player1_key = m_player1->SetCallback(engine_callback);
|
||||
|
||||
m_thread = std::jthread([this, &window_system](std::stop_token stop_token) {
|
||||
Common::SetCurrentThreadName("ButtonPoller");
|
||||
while (!stop_token.stop_requested()) {
|
||||
using namespace std::chrono_literals;
|
||||
std::unique_lock lk{m_mutex};
|
||||
m_cv.wait_for(lk, 50ms);
|
||||
if (stop_token.stop_requested())
|
||||
break;
|
||||
OnButtonStateChanged(window_system);
|
||||
std::this_thread::sleep_for(5ms);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
ButtonPoller::~ButtonPoller() {
|
||||
m_handheld->DeleteCallback(m_handheld_key);
|
||||
m_player1->DeleteCallback(m_player1_key);
|
||||
m_cv.notify_all();
|
||||
if (m_thread.joinable()) {
|
||||
m_thread.request_stop();
|
||||
m_thread.join();
|
||||
}
|
||||
}
|
||||
|
||||
void ButtonPoller::OnButtonStateChanged(WindowSystem& window_system) {
|
||||
|
||||
@@ -33,9 +33,6 @@ public:
|
||||
void OnButtonStateChanged(WindowSystem& window_system);
|
||||
|
||||
private:
|
||||
std::mutex m_mutex;
|
||||
std::condition_variable m_cv;
|
||||
std::jthread m_thread;
|
||||
std::optional<std::chrono::steady_clock::time_point> m_home_button_press_start{};
|
||||
std::optional<std::chrono::steady_clock::time_point> m_capture_button_press_start{};
|
||||
std::optional<std::chrono::steady_clock::time_point> m_power_button_press_start{};
|
||||
|
||||
@@ -26,13 +26,12 @@ EventObserver::EventObserver(Core::System& system, WindowSystem& window_system)
|
||||
m_window_system.SetEventObserver(this);
|
||||
m_wakeup_holder.SetUserData(static_cast<uintptr_t>(UserDataTag::WakeupEvent));
|
||||
m_wakeup_holder.LinkToMultiWait(std::addressof(m_multi_wait));
|
||||
m_thread = std::thread([this] {
|
||||
m_thread = std::jthread([this](std::stop_token stop_token) {
|
||||
Common::SetCurrentThreadName("am:EventObserver");
|
||||
while (true) {
|
||||
auto* signaled_holder = this->WaitSignaled();
|
||||
if (!signaled_holder) {
|
||||
while (!stop_token.stop_requested()) {
|
||||
auto* signaled_holder = this->WaitSignaled(stop_token);
|
||||
if (!signaled_holder)
|
||||
break;
|
||||
}
|
||||
this->Process(signaled_holder);
|
||||
}
|
||||
});
|
||||
@@ -40,9 +39,12 @@ EventObserver::EventObserver(Core::System& system, WindowSystem& window_system)
|
||||
|
||||
EventObserver::~EventObserver() {
|
||||
// Signal thread and wait for processing to finish.
|
||||
m_stop_source.request_stop();
|
||||
m_wakeup_event.Signal(m_system.Kernel());
|
||||
m_thread.join();
|
||||
if (m_thread.joinable()) {
|
||||
// Signal thread and wait for processing to finish.
|
||||
m_thread.request_stop();
|
||||
m_wakeup_event.Signal(m_system.Kernel());
|
||||
m_thread.join();
|
||||
}
|
||||
|
||||
// Free remaining owned sessions.
|
||||
auto it = m_process_holder_list.begin();
|
||||
@@ -88,12 +90,12 @@ void EventObserver::LinkDeferred() {
|
||||
m_multi_wait.MoveAll(std::addressof(m_deferred_wait_list));
|
||||
}
|
||||
|
||||
MultiWaitHolder* EventObserver::WaitSignaled() {
|
||||
MultiWaitHolder* EventObserver::WaitSignaled(std::stop_token stop_token) {
|
||||
while (true) {
|
||||
this->LinkDeferred();
|
||||
|
||||
// If we're done, return before we start waiting.
|
||||
if (m_stop_source.stop_requested()) {
|
||||
if (stop_token.stop_requested()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
@@ -131,7 +133,6 @@ void EventObserver::OnProcessEvent(ProcessHolder* holder) {
|
||||
// Check process state.
|
||||
auto& applet = holder->GetApplet();
|
||||
auto& process = holder->GetProcess();
|
||||
|
||||
{
|
||||
std::scoped_lock lk{m_lock, applet.lock};
|
||||
if (process.IsTerminated()) {
|
||||
@@ -156,7 +157,6 @@ void EventObserver::OnProcessEvent(ProcessHolder* holder) {
|
||||
void EventObserver::DestroyAppletProcessHolderLocked(ProcessHolder* holder) {
|
||||
// Remove from owned list.
|
||||
m_process_holder_list.erase(m_process_holder_list.iterator_to(*holder));
|
||||
|
||||
// Destroy and free.
|
||||
delete holder;
|
||||
}
|
||||
|
||||
@@ -32,19 +32,14 @@ public:
|
||||
|
||||
private:
|
||||
void LinkDeferred();
|
||||
MultiWaitHolder* WaitSignaled();
|
||||
MultiWaitHolder* WaitSignaled(std::stop_token stop_token);
|
||||
void Process(MultiWaitHolder* holder);
|
||||
bool WaitAndProcessImpl();
|
||||
void LoopProcess();
|
||||
|
||||
private:
|
||||
void OnWakeupEvent(MultiWaitHolder* holder);
|
||||
void OnProcessEvent(ProcessHolder* holder);
|
||||
|
||||
private:
|
||||
void DestroyAppletProcessHolderLocked(ProcessHolder* holder);
|
||||
|
||||
private:
|
||||
// System reference and context.
|
||||
Core::System& m_system;
|
||||
KernelHelpers::ServiceContext m_context;
|
||||
@@ -67,8 +62,7 @@ private:
|
||||
MultiWait m_deferred_wait_list;
|
||||
|
||||
// Processing thread.
|
||||
std::thread m_thread{};
|
||||
std::stop_source m_stop_source{};
|
||||
std::jthread m_thread{};
|
||||
};
|
||||
|
||||
} // namespace Service::AM
|
||||
|
||||
@@ -88,7 +88,7 @@ Result IAudioRenderer::RequestUpdateAuto(
|
||||
|
||||
const auto result = impl->RequestUpdate(input, out_performance_buffer, out_buffer);
|
||||
if (result.IsFailure()) {
|
||||
LOG_ERROR(Service_Audio, "RequestUpdate failed error 0x{:02X}!", result.GetDescription());
|
||||
LOG_ERROR(Service_Audio, "RequestUpdate failed error {:#02x}!", result.GetDescription());
|
||||
}
|
||||
|
||||
R_RETURN(result);
|
||||
|
||||
@@ -76,7 +76,7 @@ Result IAlbumAccessorService::GetAlbumFileList(
|
||||
}
|
||||
|
||||
Result IAlbumAccessorService::DeleteAlbumFile(AlbumFileId file_id) {
|
||||
LOG_INFO(Service_Capture, "called, application_id=0x{:0x}, storage={}, type={}",
|
||||
LOG_INFO(Service_Capture, "called, application_id={:#0x}, storage={}, type={}",
|
||||
file_id.application_id, file_id.storage, file_id.type);
|
||||
|
||||
const Result result = manager->DeleteAlbumFile(file_id);
|
||||
@@ -120,7 +120,7 @@ Result IAlbumAccessorService::LoadAlbumScreenShotImageEx1(
|
||||
OutLargeData<LoadAlbumScreenShotImageOutput, BufferAttr_HipcMapAlias> out_image_output,
|
||||
OutArray<u8, BufferAttr_HipcMapAlias | BufferAttr_HipcMapTransferAllowsNonSecure> out_image,
|
||||
OutArray<u8, BufferAttr_HipcMapAlias> out_buffer) {
|
||||
LOG_INFO(Service_Capture, "called, application_id=0x{:0x}, storage={}, type={}, flags={}",
|
||||
LOG_INFO(Service_Capture, "called, application_id={:#0x}, storage={}, type={}, flags={}",
|
||||
file_id.application_id, file_id.storage, file_id.type, decoder_options.flags);
|
||||
|
||||
const Result result =
|
||||
@@ -133,7 +133,7 @@ Result IAlbumAccessorService::LoadAlbumScreenShotThumbnailImageEx1(
|
||||
OutLargeData<LoadAlbumScreenShotImageOutput, BufferAttr_HipcMapAlias> out_image_output,
|
||||
OutArray<u8, BufferAttr_HipcMapAlias | BufferAttr_HipcMapTransferAllowsNonSecure> out_image,
|
||||
OutArray<u8, BufferAttr_HipcMapAlias> out_buffer) {
|
||||
LOG_INFO(Service_Capture, "called, application_id=0x{:0x}, storage={}, type={}, flags={}",
|
||||
LOG_INFO(Service_Capture, "called, application_id={:#0x}, storage={}, type={}, flags={}",
|
||||
file_id.application_id, file_id.storage, file_id.type, decoder_options.flags);
|
||||
|
||||
const Result result = manager->LoadAlbumScreenShotThumbnail(*out_image_output, out_image,
|
||||
|
||||
@@ -444,7 +444,7 @@ void CmifReplyWrapImpl(HLERequestContext& ctx, T& t, Result (T::*f)(A...)) {
|
||||
const bool is_domain = mgr ? mgr->IsDomain() : false;
|
||||
ASSERT_MSG(!is_domain,
|
||||
"Non-domain reply used on domain session\n"
|
||||
"Service={} (TIPC={} CmdType={} Cmd=0x{:08X}\n"
|
||||
"Service={} (TIPC={} CmdType={} Cmd={:#08x}\n"
|
||||
"HasDomainHeader={} DomainHandlers={}\nDesc={}",
|
||||
t.GetServiceName(), ctx.IsTipc(),
|
||||
u32(ctx.GetCommandType()), u32(ctx.GetCommand()),
|
||||
|
||||
@@ -71,9 +71,9 @@ static void GenerateErrorReport(Core::System& system, Result error_code, const F
|
||||
std::string crash_report = fmt::format(
|
||||
"Eden {}-{} crash report\n"
|
||||
"Title ID: {:016x}\n"
|
||||
"Result: {:#X} ({:04}-{:04d})\n"
|
||||
"Set flags: 0x{:16X}\n"
|
||||
"Program entry point: 0x{:16X}\n"
|
||||
"Result: {:#x} ({:04}-{:04d})\n"
|
||||
"Set flags: {:#16X}\n"
|
||||
"Program entry point: {:#16X}\n"
|
||||
"\n",
|
||||
Common::g_scm_branch, Common::g_scm_desc, title_id, error_code.raw,
|
||||
2000 + static_cast<u32>(error_code.GetModule()),
|
||||
@@ -98,7 +98,7 @@ static void GenerateErrorReport(Core::System& system, Result error_code, const F
|
||||
}
|
||||
|
||||
crash_report += fmt::format("Architecture: {}\n", info.ArchAsString());
|
||||
crash_report += fmt::format("Unknown 10: 0x{:016x}\n", info.unk10);
|
||||
crash_report += fmt::format("Unknown 10: {:#016x}\n", info.unk10);
|
||||
}
|
||||
|
||||
LOG_ERROR(Service_Fatal, "{}", crash_report);
|
||||
@@ -111,7 +111,7 @@ static void GenerateErrorReport(Core::System& system, Result error_code, const F
|
||||
|
||||
static void ThrowFatalError(Core::System& system, Result error_code, FatalType fatal_type,
|
||||
const FatalInfo& info) {
|
||||
LOG_ERROR(Service_Fatal, "Threw fatal error type {} with error code {:#X}", fatal_type,
|
||||
LOG_ERROR(Service_Fatal, "Threw fatal error type {} with error code {:#x}", fatal_type,
|
||||
error_code.raw);
|
||||
|
||||
switch (fatal_type) {
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
|
||||
#include "common/assert.h"
|
||||
@@ -257,7 +258,7 @@ Result VfsDirectoryServiceWrapper::OpenFile(FileSys::VirtualFile* out_file,
|
||||
npath.remove_prefix(1);
|
||||
}
|
||||
|
||||
auto file = backing->GetFileRelative(npath);
|
||||
auto file = backing->GetFileRelative(npath, mode);
|
||||
if (file == nullptr) {
|
||||
return FileSys::ResultPathNotFound;
|
||||
}
|
||||
@@ -333,14 +334,16 @@ FileSystemController::~FileSystemController() = default;
|
||||
|
||||
Result FileSystemController::RegisterProcess(
|
||||
ProcessId process_id, ProgramId program_id,
|
||||
std::shared_ptr<FileSys::RomFSFactory>&& romfs_factory) {
|
||||
std::shared_ptr<FileSys::RomFSFactory>&& romfs_factory, std::string homebrew_initial_cwd) {
|
||||
std::scoped_lock lk{registration_lock};
|
||||
|
||||
registrations.emplace(process_id, Registration{
|
||||
.program_id = program_id,
|
||||
.romfs_factory = std::move(romfs_factory),
|
||||
.save_data_factory = CreateSaveDataFactory(program_id),
|
||||
});
|
||||
registrations.insert_or_assign(process_id,
|
||||
Registration{
|
||||
.program_id = program_id,
|
||||
.romfs_factory = std::move(romfs_factory),
|
||||
.save_data_factory = CreateSaveDataFactory(program_id),
|
||||
.homebrew_initial_cwd = std::move(homebrew_initial_cwd),
|
||||
});
|
||||
|
||||
LOG_DEBUG(Service_FS, "Registered for process {}", process_id);
|
||||
return ResultSuccess;
|
||||
@@ -348,7 +351,8 @@ Result FileSystemController::RegisterProcess(
|
||||
|
||||
Result FileSystemController::OpenProcess(
|
||||
ProgramId* out_program_id, std::shared_ptr<SaveDataController>* out_save_data_controller,
|
||||
std::shared_ptr<RomFsController>* out_romfs_controller, ProcessId process_id) {
|
||||
std::shared_ptr<RomFsController>* out_romfs_controller, ProcessId process_id,
|
||||
std::string* out_homebrew_initial_cwd) {
|
||||
std::scoped_lock lk{registration_lock};
|
||||
|
||||
const auto it = registrations.find(process_id);
|
||||
@@ -361,6 +365,9 @@ Result FileSystemController::OpenProcess(
|
||||
std::make_shared<SaveDataController>(system, it->second.save_data_factory);
|
||||
*out_romfs_controller =
|
||||
std::make_shared<RomFsController>(it->second.romfs_factory, it->second.program_id);
|
||||
if (out_homebrew_initial_cwd != nullptr) {
|
||||
*out_homebrew_initial_cwd = it->second.homebrew_initial_cwd;
|
||||
}
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include "common/common_types.h"
|
||||
#include "core/file_sys/fs_directory.h"
|
||||
#include "core/file_sys/fs_filesystem.h"
|
||||
@@ -71,11 +72,12 @@ public:
|
||||
~FileSystemController();
|
||||
|
||||
Result RegisterProcess(ProcessId process_id, ProgramId program_id,
|
||||
std::shared_ptr<FileSys::RomFSFactory>&& factory);
|
||||
std::shared_ptr<FileSys::RomFSFactory>&& factory,
|
||||
std::string homebrew_initial_cwd = {});
|
||||
Result OpenProcess(ProgramId* out_program_id,
|
||||
std::shared_ptr<SaveDataController>* out_save_data_controller,
|
||||
std::shared_ptr<RomFsController>* out_romfs_controller,
|
||||
ProcessId process_id);
|
||||
std::shared_ptr<SaveDataController>* out_save_data_controller,
|
||||
std::shared_ptr<RomFsController>* out_romfs_controller,
|
||||
ProcessId process_id, std::string* out_homebrew_initial_cwd = nullptr);
|
||||
void SetPackedUpdate(ProcessId process_id, FileSys::VirtualFile update_raw);
|
||||
|
||||
std::shared_ptr<SaveDataController> OpenSaveDataController();
|
||||
@@ -136,6 +138,7 @@ private:
|
||||
ProgramId program_id;
|
||||
std::shared_ptr<FileSys::RomFSFactory> romfs_factory;
|
||||
std::shared_ptr<FileSys::SaveDataFactory> save_data_factory;
|
||||
std::string homebrew_initial_cwd;
|
||||
};
|
||||
|
||||
std::mutex registration_lock;
|
||||
|
||||
@@ -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-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||
@@ -30,7 +30,7 @@ Result IFile::Read(
|
||||
FileSys::ReadOption option, Out<s64> out_size, s64 offset,
|
||||
const OutBuffer<BufferAttr_HipcMapAlias | BufferAttr_HipcMapTransferAllowsNonSecure> out_buffer,
|
||||
s64 size) {
|
||||
LOG_DEBUG(Service_FS, "called, option={}, offset={:#X}, length={}", option.value, offset,
|
||||
LOG_DEBUG(Service_FS, "called, option={}, offset={:#x}, length={}", option.value, offset,
|
||||
size);
|
||||
|
||||
// Read the data from the Storage backend
|
||||
@@ -41,7 +41,7 @@ Result IFile::Read(
|
||||
Result IFile::Write(
|
||||
const InBuffer<BufferAttr_HipcMapAlias | BufferAttr_HipcMapTransferAllowsNonSecure> buffer,
|
||||
FileSys::WriteOption option, s64 offset, s64 size) {
|
||||
LOG_DEBUG(Service_FS, "called, option={}, offset={:#X}, length={}", option.value, offset,
|
||||
LOG_DEBUG(Service_FS, "called, option={}, offset={:#x}, length={}", option.value, offset,
|
||||
size);
|
||||
|
||||
R_RETURN(backend->Write(offset, buffer.data(), size, option));
|
||||
|
||||
@@ -4,6 +4,9 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <string_view>
|
||||
|
||||
#include "common/fs/path_util.h"
|
||||
#include "common/string_util.h"
|
||||
#include "core/file_sys/fssrv/fssrv_sf_path.h"
|
||||
#include "core/hle/service/cmif_serialization.h"
|
||||
@@ -13,10 +16,29 @@
|
||||
|
||||
namespace Service::FileSystem {
|
||||
|
||||
IFileSystem::IFileSystem(Core::System& system_, FileSys::VirtualDir dir_, SizeGetter size_getter_)
|
||||
: ServiceFramework{system_, "IFileSystem"}, backend{std::make_unique<FileSys::Fsa::IFileSystem>(
|
||||
dir_)},
|
||||
size_getter{std::move(size_getter_)} {
|
||||
static std::string ResolveHomebrewCwdRootAlias(std::string path,
|
||||
std::string_view homebrew_initial_cwd) {
|
||||
if (homebrew_initial_cwd.empty()) {
|
||||
return path;
|
||||
}
|
||||
|
||||
const std::string normalized_path = Common::FS::SanitizePath(path);
|
||||
if (normalized_path.empty() || normalized_path == "/" ||
|
||||
normalized_path != homebrew_initial_cwd ||
|
||||
path.size() != normalized_path.size() + 2 ||
|
||||
path.substr(0, normalized_path.size()) != normalized_path ||
|
||||
path.substr(normalized_path.size()) != "//") {
|
||||
return path;
|
||||
}
|
||||
|
||||
return "/";
|
||||
}
|
||||
|
||||
IFileSystem::IFileSystem(Core::System& system_, FileSys::VirtualDir dir_, SizeGetter size_getter_,
|
||||
std::string homebrew_initial_cwd_)
|
||||
: ServiceFramework{system_, "IFileSystem"},
|
||||
backend{std::make_unique<FileSys::Fsa::IFileSystem>(dir_)},
|
||||
size_getter{std::move(size_getter_)}, homebrew_initial_cwd{std::move(homebrew_initial_cwd_)} {
|
||||
static const FunctionInfo functions[] = {
|
||||
{0, D<&IFileSystem::CreateFile>, "CreateFile"},
|
||||
{1, D<&IFileSystem::DeleteFile>, "DeleteFile"},
|
||||
@@ -41,9 +63,10 @@ IFileSystem::IFileSystem(Core::System& system_, FileSys::VirtualDir dir_, SizeGe
|
||||
|
||||
Result IFileSystem::CreateFile(const InLargeData<FileSys::Sf::Path, BufferAttr_HipcPointer> path,
|
||||
s32 option, s64 size) {
|
||||
LOG_DEBUG(Service_FS, "called. file={}, option={:#X}, size=0x{:08X}", path->str, option, size);
|
||||
LOG_DEBUG(Service_FS, "called. file={}, option={:#x}, size={:#08x}", path->str, option, size);
|
||||
|
||||
R_RETURN(backend->CreateFile(FileSys::Path(path->str), size));
|
||||
const auto fs_path = ResolveHomebrewCwdRootAlias(path->str, homebrew_initial_cwd);
|
||||
R_RETURN(backend->CreateFile(FileSys::Path(fs_path.c_str()), size));
|
||||
}
|
||||
|
||||
Result IFileSystem::DeleteFile(const InLargeData<FileSys::Sf::Path, BufferAttr_HipcPointer> path) {
|
||||
@@ -94,7 +117,8 @@ Result IFileSystem::OpenFile(OutInterface<IFile> out_interface,
|
||||
LOG_DEBUG(Service_FS, "called. file={}, mode={}", path->str, mode);
|
||||
|
||||
FileSys::VirtualFile vfs_file{};
|
||||
R_TRY(backend->OpenFile(&vfs_file, FileSys::Path(path->str),
|
||||
const auto fs_path = ResolveHomebrewCwdRootAlias(path->str, homebrew_initial_cwd);
|
||||
R_TRY(backend->OpenFile(&vfs_file, FileSys::Path(fs_path.c_str()),
|
||||
static_cast<FileSys::OpenMode>(mode)));
|
||||
|
||||
*out_interface = std::make_shared<IFile>(system, vfs_file);
|
||||
@@ -107,7 +131,8 @@ Result IFileSystem::OpenDirectory(OutInterface<IDirectory> out_interface,
|
||||
LOG_DEBUG(Service_FS, "called. directory={}, mode={}", path->str, mode);
|
||||
|
||||
FileSys::VirtualDir vfs_dir{};
|
||||
R_TRY(backend->OpenDirectory(&vfs_dir, FileSys::Path(path->str),
|
||||
const auto fs_path = ResolveHomebrewCwdRootAlias(path->str, homebrew_initial_cwd);
|
||||
R_TRY(backend->OpenDirectory(&vfs_dir, FileSys::Path(fs_path.c_str()),
|
||||
static_cast<FileSys::OpenDirectoryMode>(mode)));
|
||||
|
||||
*out_interface = std::make_shared<IDirectory>(system, vfs_dir,
|
||||
@@ -120,7 +145,8 @@ Result IFileSystem::GetEntryType(
|
||||
LOG_DEBUG(Service_FS, "called. file={}", path->str);
|
||||
|
||||
FileSys::DirectoryEntryType vfs_entry_type{};
|
||||
R_TRY(backend->GetEntryType(&vfs_entry_type, FileSys::Path(path->str)));
|
||||
const auto fs_path = ResolveHomebrewCwdRootAlias(path->str, homebrew_initial_cwd);
|
||||
R_TRY(backend->GetEntryType(&vfs_entry_type, FileSys::Path(fs_path.c_str())));
|
||||
|
||||
*out_type = static_cast<u32>(vfs_entry_type);
|
||||
R_SUCCEED();
|
||||
|
||||
@@ -1,8 +1,13 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "common/common_funcs.h"
|
||||
#include "core/file_sys/fs_filesystem.h"
|
||||
#include "core/file_sys/fsa/fs_i_filesystem.h"
|
||||
@@ -23,7 +28,8 @@ class IDirectory;
|
||||
|
||||
class IFileSystem final : public ServiceFramework<IFileSystem> {
|
||||
public:
|
||||
explicit IFileSystem(Core::System& system_, FileSys::VirtualDir dir_, SizeGetter size_getter_);
|
||||
explicit IFileSystem(Core::System& system_, FileSys::VirtualDir dir_, SizeGetter size_getter_,
|
||||
std::string homebrew_initial_cwd_ = {});
|
||||
|
||||
Result CreateFile(const InLargeData<FileSys::Sf::Path, BufferAttr_HipcPointer> path, s32 option,
|
||||
s64 size);
|
||||
@@ -55,6 +61,7 @@ public:
|
||||
private:
|
||||
std::unique_ptr<FileSys::Fsa::IFileSystem> backend;
|
||||
SizeGetter size_getter;
|
||||
std::string homebrew_initial_cwd;
|
||||
};
|
||||
|
||||
} // namespace Service::FileSystem
|
||||
|
||||
@@ -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-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||
@@ -26,7 +26,7 @@ IStorage::IStorage(Core::System& system_, FileSys::VirtualFile backend_)
|
||||
Result IStorage::Read(
|
||||
OutBuffer<BufferAttr_HipcMapAlias | BufferAttr_HipcMapTransferAllowsNonSecure> out_bytes,
|
||||
s64 offset, s64 length) {
|
||||
LOG_DEBUG(Service_FS, "called, offset={:#X}, length={}", offset, length);
|
||||
LOG_DEBUG(Service_FS, "called, offset={:#x}, length={}", offset, length);
|
||||
|
||||
R_UNLESS(length >= 0, FileSys::ResultInvalidSize);
|
||||
R_UNLESS(offset >= 0, FileSys::ResultInvalidOffset);
|
||||
|
||||
@@ -190,10 +190,11 @@ FSP_SRV::~FSP_SRV() = default;
|
||||
Result FSP_SRV::SetCurrentProcess(ClientProcessId pid) {
|
||||
current_process_id = *pid;
|
||||
|
||||
LOG_DEBUG(Service_FS, "called. current_process_id=0x{:016X}", current_process_id);
|
||||
LOG_DEBUG(Service_FS, "called. current_process_id={:#016x}", current_process_id);
|
||||
|
||||
R_RETURN(
|
||||
fsc.OpenProcess(&program_id, &save_data_controller, &romfs_controller, current_process_id));
|
||||
homebrew_initial_cwd.clear();
|
||||
R_RETURN(fsc.OpenProcess(&program_id, &save_data_controller, &romfs_controller,
|
||||
current_process_id, &homebrew_initial_cwd));
|
||||
}
|
||||
|
||||
Result FSP_SRV::OpenFileSystemWithPatch(OutInterface<IFileSystem> out_interface,
|
||||
@@ -224,7 +225,8 @@ Result FSP_SRV::OpenSdCardFileSystem(OutInterface<IFileSystem> out_interface) {
|
||||
fsc.OpenSDMC(&sdmc_dir);
|
||||
|
||||
*out_interface = std::make_shared<IFileSystem>(
|
||||
system, sdmc_dir, SizeGetter::FromStorageId(fsc, FileSys::StorageId::SdCard));
|
||||
system, sdmc_dir, SizeGetter::FromStorageId(fsc, FileSys::StorageId::SdCard),
|
||||
homebrew_initial_cwd);
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include "core/file_sys/fs_save_data_types.h"
|
||||
#include "core/hle/service/cmif_types.h"
|
||||
#include "core/hle/service/filesystem/fsp/fsp_types.h"
|
||||
@@ -123,6 +124,7 @@ private:
|
||||
u32 access_log_program_index = 0;
|
||||
AccessLogMode access_log_mode = AccessLogMode::None;
|
||||
u64 program_id = 0;
|
||||
std::string homebrew_initial_cwd;
|
||||
std::shared_ptr<SaveDataController> save_data_controller;
|
||||
std::shared_ptr<RomFsController> romfs_controller;
|
||||
};
|
||||
|
||||
@@ -30,7 +30,7 @@ Result IAppletResource::GetSharedMemoryHandle(
|
||||
OutCopyHandle<Kernel::KSharedMemory> out_shared_memory_handle) {
|
||||
const auto result = resource_manager->GetSharedMemoryHandle(out_shared_memory_handle, aruid);
|
||||
|
||||
LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}, result={:#X}", aruid, result.raw);
|
||||
LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}, result={:#x}", aruid, result.raw);
|
||||
R_RETURN(result);
|
||||
}
|
||||
|
||||
|
||||
@@ -251,7 +251,7 @@ Result IHidServer::CreateAppletResource(OutInterface<IAppletResource> out_applet
|
||||
ClientAppletResourceUserId aruid) {
|
||||
const auto result = GetResourceManager()->CreateAppletResource(aruid.pid);
|
||||
|
||||
LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}, result={:#X}", aruid.pid,
|
||||
LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}, result={:#x}", aruid.pid,
|
||||
result.raw);
|
||||
|
||||
*out_applet_resource = std::make_shared<IAppletResource>(system, resource_manager, aruid.pid);
|
||||
@@ -1150,7 +1150,7 @@ Result IHidServer::InitializeSevenSixAxisSensor(ClientAppletResourceUserId aruid
|
||||
InCopyHandle<Kernel::KTransferMemory> t_mem_1,
|
||||
InCopyHandle<Kernel::KTransferMemory> t_mem_2) {
|
||||
LOG_WARNING(Service_HID,
|
||||
"called, t_mem_1_size=0x{:08X}, t_mem_2_size=0x{:08X}, "
|
||||
"called, t_mem_1_size={:#08x}, t_mem_2_size={:#08x}, "
|
||||
"applet_resource_user_id={}",
|
||||
t_mem_1_size, t_mem_2_size, aruid.pid);
|
||||
|
||||
|
||||
@@ -113,7 +113,7 @@ Result SessionRequestManager::HandleDomainSyncRequest(Kernel::KServerSession* se
|
||||
}
|
||||
|
||||
case IPC::DomainMessageHeader::CommandType::CloseVirtualHandle: {
|
||||
LOG_DEBUG(IPC, "CloseVirtualHandle, object_id=0x{:08X}", object_id);
|
||||
LOG_DEBUG(IPC, "CloseVirtualHandle, object_id={:#08x}", object_id);
|
||||
|
||||
this->CloseDomainHandler(object_id - 1);
|
||||
|
||||
|
||||
@@ -142,7 +142,7 @@ public:
|
||||
if (boost::icl::contains(mapped_ranges, vaddr)) {
|
||||
memory.ReadBlock(vaddr, &ret, sizeof(T));
|
||||
} else if (vaddr + sizeof(T) > local_memory.size()) {
|
||||
LOG_CRITICAL(Service_JIT, "plugin: unmapped read @ 0x{:016x}", vaddr);
|
||||
LOG_CRITICAL(Service_JIT, "plugin: unmapped read @ {:#016x}", vaddr);
|
||||
} else {
|
||||
std::memcpy(&ret, local_memory.data() + vaddr, sizeof(T));
|
||||
}
|
||||
@@ -154,7 +154,7 @@ public:
|
||||
if (boost::icl::contains(mapped_ranges, vaddr)) {
|
||||
memory.WriteBlock(vaddr, &value, sizeof(T));
|
||||
} else if (vaddr + sizeof(T) > local_memory.size()) {
|
||||
LOG_CRITICAL(Service_JIT, "plugin: unmapped write @ 0x{:016x}", vaddr);
|
||||
LOG_CRITICAL(Service_JIT, "plugin: unmapped write @ {:#016x}", vaddr);
|
||||
} else {
|
||||
std::memcpy(local_memory.data() + vaddr, &value, sizeof(T));
|
||||
}
|
||||
@@ -416,7 +416,7 @@ void DynarmicCallbacks64::CallSVC(u32 swi) {
|
||||
LOG_CRITICAL(Service_JIT, "plugin panicked!");
|
||||
parent.jit->HaltExecution();
|
||||
} else {
|
||||
LOG_CRITICAL(Service_JIT, "plugin issued syscall at unknown address 0x{:x}", pc);
|
||||
LOG_CRITICAL(Service_JIT, "plugin issued syscall at unknown address {:#x}", pc);
|
||||
parent.jit->HaltExecution();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -77,7 +77,7 @@ Result DatabaseManager::Initialize(DatabaseSessionMetadata& metadata, bool& is_d
|
||||
const auto result = database.CheckIntegrity();
|
||||
|
||||
if (result.IsError()) {
|
||||
LOG_ERROR(Service_Mii, "Mii database is corrupted 0x{:0x}", result.raw);
|
||||
LOG_ERROR(Service_Mii, "Mii database is corrupted {:#0x}", result.raw);
|
||||
database.CleanDatabase();
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
@@ -1,10 +1,15 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: 2022 yuzu Emulator Project
|
||||
// SPDX-FileCopyrightText: 2022 Skyline Team and Contributors
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <deque>
|
||||
#include <mutex>
|
||||
#include <vector>
|
||||
|
||||
#include "core/hle/kernel/k_process.h"
|
||||
#include "core/hle/service/nvdrv/core/container.h"
|
||||
@@ -40,6 +45,16 @@ Container::Container(Tegra::Host1x::Host1x& host1x_) {
|
||||
|
||||
Container::~Container() = default;
|
||||
|
||||
static bool IsSameProcess(Kernel::KProcess* lhs, Kernel::KProcess* rhs) {
|
||||
if (lhs == rhs) {
|
||||
return true;
|
||||
}
|
||||
if (lhs == nullptr || rhs == nullptr) {
|
||||
return false;
|
||||
}
|
||||
return lhs->GetProcessId() == rhs->GetProcessId();
|
||||
}
|
||||
|
||||
SessionId Container::OpenSession(Kernel::KProcess* process) {
|
||||
using namespace Common::Literals;
|
||||
|
||||
@@ -48,7 +63,7 @@ SessionId Container::OpenSession(Kernel::KProcess* process) {
|
||||
if (!session.is_active) {
|
||||
continue;
|
||||
}
|
||||
if (session.process == process) {
|
||||
if (IsSameProcess(session.process, process)) {
|
||||
session.ref_count++;
|
||||
return session.id;
|
||||
}
|
||||
@@ -116,7 +131,15 @@ SessionId Container::OpenSession(Kernel::KProcess* process) {
|
||||
|
||||
void Container::CloseSession(SessionId session_id) {
|
||||
std::scoped_lock lk(impl->session_guard);
|
||||
if (session_id.id >= impl->sessions.size()) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto& session = impl->sessions[session_id.id];
|
||||
if (!session.is_active || session.ref_count <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (--session.ref_count > 0) {
|
||||
return;
|
||||
}
|
||||
@@ -134,6 +157,73 @@ void Container::CloseSession(SessionId session_id) {
|
||||
impl->id_pool.emplace_front(session_id.id);
|
||||
}
|
||||
|
||||
size_t Container::CloseSessions(std::span<const SessionId> session_ids) {
|
||||
std::vector<SessionId> valid_session_ids;
|
||||
valid_session_ids.reserve(session_ids.size());
|
||||
|
||||
{
|
||||
std::scoped_lock lk(impl->session_guard);
|
||||
for (const auto session_id : session_ids) {
|
||||
if (session_id.id >= impl->sessions.size()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto& session = impl->sessions[session_id.id];
|
||||
if (!session.is_active) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto duplicate = std::ranges::any_of(
|
||||
valid_session_ids, [session_id](const auto candidate) {
|
||||
return candidate.id == session_id.id;
|
||||
});
|
||||
if (duplicate) {
|
||||
continue;
|
||||
}
|
||||
|
||||
session.ref_count = 1;
|
||||
valid_session_ids.push_back(session_id);
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto session_id : valid_session_ids) {
|
||||
CloseSession(session_id);
|
||||
}
|
||||
|
||||
return valid_session_ids.size();
|
||||
}
|
||||
|
||||
std::vector<SessionId> Container::GetSessionIdsForProcess(Kernel::KProcess* process) {
|
||||
std::vector<SessionId> session_ids;
|
||||
std::scoped_lock lk(impl->session_guard);
|
||||
for (const auto& session : impl->sessions) {
|
||||
if (!session.is_active || !IsSameProcess(session.process, process)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
session_ids.push_back(session.id);
|
||||
}
|
||||
|
||||
return session_ids;
|
||||
}
|
||||
|
||||
std::vector<SessionId> Container::GetActiveSessionIds() const {
|
||||
std::vector<SessionId> session_ids;
|
||||
std::scoped_lock lk(impl->session_guard);
|
||||
for (const auto& session : impl->sessions) {
|
||||
if (session.is_active) {
|
||||
session_ids.push_back(session.id);
|
||||
}
|
||||
}
|
||||
|
||||
return session_ids;
|
||||
}
|
||||
|
||||
bool Container::IsSessionActive(SessionId session_id) const {
|
||||
std::scoped_lock lk(impl->session_guard);
|
||||
return session_id.id < impl->sessions.size() && impl->sessions[session_id.id].is_active;
|
||||
}
|
||||
|
||||
Session* Container::GetSession(SessionId session_id) {
|
||||
std::atomic_thread_fence(std::memory_order_acquire);
|
||||
return &impl->sessions[session_id.id];
|
||||
|
||||
@@ -9,7 +9,10 @@
|
||||
|
||||
#include <deque>
|
||||
#include <memory>
|
||||
#include <span>
|
||||
#include <cstddef>
|
||||
#include <ankerl/unordered_dense.h>
|
||||
#include <vector>
|
||||
|
||||
#include "core/device_memory_manager.h"
|
||||
#include "core/hle/service/nvdrv/nvdata.h"
|
||||
@@ -59,6 +62,10 @@ public:
|
||||
|
||||
SessionId OpenSession(Kernel::KProcess* process);
|
||||
void CloseSession(SessionId id);
|
||||
size_t CloseSessions(std::span<const SessionId> session_ids);
|
||||
std::vector<SessionId> GetSessionIdsForProcess(Kernel::KProcess* process);
|
||||
std::vector<SessionId> GetActiveSessionIds() const;
|
||||
bool IsSessionActive(SessionId id) const;
|
||||
|
||||
Session* GetSession(SessionId id);
|
||||
|
||||
|
||||
@@ -6,10 +6,12 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include <functional>
|
||||
#include <vector>
|
||||
|
||||
#include "common/alignment.h"
|
||||
#include "common/assert.h"
|
||||
#include "common/logging.h"
|
||||
#include "core/hle/kernel/k_process.h"
|
||||
#include "core/hle/service/nvdrv/core/container.h"
|
||||
#include "core/hle/service/nvdrv/core/heap_mapper.h"
|
||||
#include "core/hle/service/nvdrv/core/nvmap.h"
|
||||
@@ -326,19 +328,65 @@ std::optional<NvMap::FreeInfo> NvMap::FreeHandle(Handle::Id handle, bool interna
|
||||
}
|
||||
|
||||
void NvMap::UnmapAllHandles(NvCore::SessionId session_id) {
|
||||
auto handles_copy = [&] {
|
||||
auto* session = core.GetSession(session_id);
|
||||
auto* process = session != nullptr ? session->process : nullptr;
|
||||
auto handle_ids = [&] {
|
||||
std::scoped_lock lk{handles_lock};
|
||||
return handles;
|
||||
std::vector<Handle::Id> ids;
|
||||
ids.reserve(handles.size());
|
||||
|
||||
for (const auto& entry : handles) {
|
||||
ids.push_back(entry.first);
|
||||
}
|
||||
|
||||
return ids;
|
||||
}();
|
||||
|
||||
for (auto& [id, handle] : handles_copy) {
|
||||
{
|
||||
std::scoped_lock lk{handle->mutex};
|
||||
if (handle->session_id.id != session_id.id || handle->dupes <= 0) {
|
||||
continue;
|
||||
for (const auto id : handle_ids) {
|
||||
bool unlocked_pages = false;
|
||||
while (true) {
|
||||
bool last_user_reference = false;
|
||||
VAddr address = 0;
|
||||
size_t size = 0;
|
||||
{
|
||||
const auto handle = GetHandle(id);
|
||||
if (!handle) {
|
||||
break;
|
||||
}
|
||||
|
||||
std::scoped_lock lk{handle->mutex};
|
||||
if (handle->session_id.id != session_id.id || handle->dupes <= 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
last_user_reference = handle->dupes == 1;
|
||||
address = handle->address;
|
||||
size = handle->size;
|
||||
}
|
||||
|
||||
const auto free_info = FreeHandle(id, false);
|
||||
if (!free_info) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (!unlocked_pages && process != nullptr && address != 0 && size != 0 &&
|
||||
(free_info->can_unlock || last_user_reference)) {
|
||||
const auto unlock_result =
|
||||
process->GetPageTable().UnlockForDeviceAddressSpace(address, size);
|
||||
if (unlock_result.IsError()) {
|
||||
LOG_WARNING(Service_NVDRV,
|
||||
"NextLoad: nvmap session cleanup unlock failed, "
|
||||
"handle={}, session={}, address=0x{:016X}, size=0x{:X}, "
|
||||
"result={:#X}",
|
||||
id, session_id.id, address, size, unlock_result.raw);
|
||||
}
|
||||
unlocked_pages = true;
|
||||
}
|
||||
|
||||
if (last_user_reference) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
FreeHandle(id, false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -94,7 +94,7 @@ void nvhost_as_gpu::OnOpen(NvCore::SessionId session_id, DeviceFD fd) {}
|
||||
void nvhost_as_gpu::OnClose(DeviceFD fd) {}
|
||||
|
||||
NvResult nvhost_as_gpu::AllocAsEx(IoctlAllocAsEx& params) {
|
||||
LOG_DEBUG(Service_NVDRV, "called, big_page_size={:#X}", params.big_page_size);
|
||||
LOG_DEBUG(Service_NVDRV, "called, big_page_size={:#x}", params.big_page_size);
|
||||
|
||||
std::scoped_lock lock(mutex);
|
||||
|
||||
@@ -105,12 +105,12 @@ NvResult nvhost_as_gpu::AllocAsEx(IoctlAllocAsEx& params) {
|
||||
|
||||
if (params.big_page_size) {
|
||||
if (!std::has_single_bit(params.big_page_size)) {
|
||||
LOG_ERROR(Service_NVDRV, "Non power-of-2 big page size: {:#X}!", params.big_page_size);
|
||||
LOG_ERROR(Service_NVDRV, "Non power-of-2 big page size: {:#x}!", params.big_page_size);
|
||||
return NvResult::BadValue;
|
||||
}
|
||||
|
||||
if ((params.big_page_size & VM::SUPPORTED_BIG_PAGE_SIZES) == 0) {
|
||||
LOG_ERROR(Service_NVDRV, "Unsupported big page size: {:#X}!", params.big_page_size);
|
||||
LOG_ERROR(Service_NVDRV, "Unsupported big page size: {:#x}!", params.big_page_size);
|
||||
return NvResult::BadValue;
|
||||
}
|
||||
|
||||
@@ -252,7 +252,7 @@ NvResult nvhost_as_gpu::FreeSpace(IoctlFreeSpace& params) {
|
||||
}
|
||||
|
||||
NvResult nvhost_as_gpu::Remap(std::span<IoctlRemapEntry> entries) {
|
||||
LOG_DEBUG(Service_NVDRV, "called, num_entries={:#X}", entries.size());
|
||||
LOG_DEBUG(Service_NVDRV, "called, num_entries={:#x}", entries.size());
|
||||
|
||||
if (!vm.initialised) {
|
||||
return NvResult::BadValue;
|
||||
@@ -300,7 +300,7 @@ NvResult nvhost_as_gpu::Remap(std::span<IoctlRemapEntry> entries) {
|
||||
NvResult nvhost_as_gpu::MapBufferEx(IoctlMapBufferEx& params) {
|
||||
LOG_DEBUG(Service_NVDRV,
|
||||
"called, flags={:X}, nvmap_handle={:X}, buffer_offset={}, mapping_size={}"
|
||||
", offset={:#X}",
|
||||
", offset={:#x}",
|
||||
params.flags, params.handle, params.buffer_offset, params.mapping_size,
|
||||
params.offset);
|
||||
|
||||
@@ -315,7 +315,7 @@ NvResult nvhost_as_gpu::MapBufferEx(IoctlMapBufferEx& params) {
|
||||
if (auto const it = mapping_map.find(params.offset); it != mapping_map.end()) {
|
||||
auto const mapping = it->second;
|
||||
if (mapping.size < params.mapping_size) {
|
||||
LOG_WARNING(Service_NVDRV, "Cannot remap a partially mapped GPU address space region: {:#X}", params.offset);
|
||||
LOG_WARNING(Service_NVDRV, "Cannot remap a partially mapped GPU address space region: {:#x}", params.offset);
|
||||
return NvResult::BadValue;
|
||||
}
|
||||
u64 gpu_address = u64(params.offset + params.buffer_offset);
|
||||
@@ -323,7 +323,7 @@ NvResult nvhost_as_gpu::MapBufferEx(IoctlMapBufferEx& params) {
|
||||
gmmu->Map(gpu_address, device_address, params.mapping_size, Tegra::PTEKind(params.kind), mapping.big_page);
|
||||
return NvResult::Success;
|
||||
} else {
|
||||
LOG_WARNING(Service_NVDRV, "Cannot remap an unmapped GPU address space region: {:#X}", params.offset);
|
||||
LOG_WARNING(Service_NVDRV, "Cannot remap an unmapped GPU address space region: {:#x}", params.offset);
|
||||
return NvResult::BadValue;
|
||||
}
|
||||
}
|
||||
@@ -383,7 +383,7 @@ NvResult nvhost_as_gpu::MapBufferEx(IoctlMapBufferEx& params) {
|
||||
NvResult nvhost_as_gpu::UnmapBuffer(IoctlUnmapBuffer& params) {
|
||||
std::scoped_lock lock(mutex);
|
||||
if (auto const offset_it = map_buffer_offsets.find(params.offset); offset_it != map_buffer_offsets.end()) {
|
||||
LOG_DEBUG(Service_NVDRV, "called, offset={:#X}", params.offset);
|
||||
LOG_DEBUG(Service_NVDRV, "called, offset={:#x}", params.offset);
|
||||
if (!vm.initialised) {
|
||||
return NvResult::BadValue;
|
||||
}
|
||||
|
||||
@@ -210,7 +210,7 @@ NvResult nvhost_ctrl_gpu::GetCharacteristics3(
|
||||
}
|
||||
|
||||
NvResult nvhost_ctrl_gpu::GetTPCMasks1(IoctlGpuGetTpcMasksArgs& params) {
|
||||
LOG_DEBUG(Service_NVDRV, "called, mask_buffer_size={:#X}", params.mask_buffer_size);
|
||||
LOG_DEBUG(Service_NVDRV, "called, mask_buffer_size={:#x}", params.mask_buffer_size);
|
||||
if (params.mask_buffer_size != 0) {
|
||||
params.tcp_mask = 3;
|
||||
}
|
||||
@@ -218,7 +218,7 @@ NvResult nvhost_ctrl_gpu::GetTPCMasks1(IoctlGpuGetTpcMasksArgs& params) {
|
||||
}
|
||||
|
||||
NvResult nvhost_ctrl_gpu::GetTPCMasks3(IoctlGpuGetTpcMasksArgs& params, std::span<u32> tpc_mask) {
|
||||
LOG_DEBUG(Service_NVDRV, "called, mask_buffer_size={:#X}", params.mask_buffer_size);
|
||||
LOG_DEBUG(Service_NVDRV, "called, mask_buffer_size={:#x}", params.mask_buffer_size);
|
||||
if (params.mask_buffer_size != 0) {
|
||||
params.tcp_mask = 3;
|
||||
}
|
||||
@@ -265,7 +265,7 @@ NvResult nvhost_ctrl_gpu::ZCullGetInfo(IoctlNvgpuGpuZcullGetInfoArgs& params) {
|
||||
|
||||
NvResult nvhost_ctrl_gpu::ZBCSetTable(IoctlZbcSetTable& params) {
|
||||
if (params.type > supported_types) {
|
||||
LOG_ERROR(Service_NVDRV, "ZBCSetTable: invalid type {:#X}", params.type);
|
||||
LOG_ERROR(Service_NVDRV, "ZBCSetTable: invalid type {:#x}", params.type);
|
||||
return NvResult::BadParameter;
|
||||
}
|
||||
|
||||
@@ -288,11 +288,11 @@ NvResult nvhost_ctrl_gpu::ZBCSetTable(IoctlZbcSetTable& params) {
|
||||
|
||||
if (color_it != zbc_colors.end()) {
|
||||
++color_it->ref_cnt;
|
||||
LOG_DEBUG(Service_NVDRV, "ZBCSetTable: reused color entry fmt={:#X}, ref_cnt={:#X}",
|
||||
LOG_DEBUG(Service_NVDRV, "ZBCSetTable: reused color entry fmt={:#x}, ref_cnt={:#x}",
|
||||
params.format, color_it->ref_cnt);
|
||||
} else {
|
||||
zbc_colors.push_back(color_entry);
|
||||
LOG_DEBUG(Service_NVDRV, "ZBCSetTable: added color entry fmt={:#X}, index={:#X}",
|
||||
LOG_DEBUG(Service_NVDRV, "ZBCSetTable: added color entry fmt={:#x}, index={:#x}",
|
||||
params.format, zbc_colors.size() - 1);
|
||||
}
|
||||
break;
|
||||
@@ -308,11 +308,11 @@ NvResult nvhost_ctrl_gpu::ZBCSetTable(IoctlZbcSetTable& params) {
|
||||
|
||||
if (depth_it != zbc_depths.end()) {
|
||||
++depth_it->ref_cnt;
|
||||
LOG_DEBUG(Service_NVDRV, "ZBCSetTable: reused depth entry fmt={:#X}, ref_cnt={:#X}",
|
||||
LOG_DEBUG(Service_NVDRV, "ZBCSetTable: reused depth entry fmt={:#x}, ref_cnt={:#x}",
|
||||
depth_entry.format, depth_it->ref_cnt);
|
||||
} else {
|
||||
zbc_depths.push_back(depth_entry);
|
||||
LOG_DEBUG(Service_NVDRV, "ZBCSetTable: added depth entry fmt={:#X}, index={:#X}",
|
||||
LOG_DEBUG(Service_NVDRV, "ZBCSetTable: added depth entry fmt={:#x}, index={:#x}",
|
||||
depth_entry.format, zbc_depths.size() - 1);
|
||||
}
|
||||
}
|
||||
@@ -323,7 +323,7 @@ NvResult nvhost_ctrl_gpu::ZBCSetTable(IoctlZbcSetTable& params) {
|
||||
|
||||
NvResult nvhost_ctrl_gpu::ZBCQueryTable(IoctlZbcQueryTable& params) {
|
||||
if (params.type > supported_types) {
|
||||
LOG_ERROR(Service_NVDRV, "ZBCQueryTable: invalid type {:#X}", params.type);
|
||||
LOG_ERROR(Service_NVDRV, "ZBCQueryTable: invalid type {:#x}", params.type);
|
||||
return NvResult::BadParameter;
|
||||
}
|
||||
|
||||
@@ -332,7 +332,7 @@ NvResult nvhost_ctrl_gpu::ZBCQueryTable(IoctlZbcQueryTable& params) {
|
||||
switch (static_cast<ZBCTypes>(params.type)) {
|
||||
case ZBCTypes::color: {
|
||||
if (params.index_size >= zbc_colors.size()) {
|
||||
LOG_ERROR(Service_NVDRV, "ZBCQueryTable: invalid color index {:#X}", params.index_size);
|
||||
LOG_ERROR(Service_NVDRV, "ZBCQueryTable: invalid color index {:#x}", params.index_size);
|
||||
return NvResult::BadParameter;
|
||||
}
|
||||
|
||||
@@ -347,7 +347,7 @@ NvResult nvhost_ctrl_gpu::ZBCQueryTable(IoctlZbcQueryTable& params) {
|
||||
}
|
||||
case ZBCTypes::depth: {
|
||||
if (params.index_size >= zbc_depths.size()) {
|
||||
LOG_ERROR(Service_NVDRV, "ZBCQueryTable: invalid depth index {:#X}", params.index_size);
|
||||
LOG_ERROR(Service_NVDRV, "ZBCQueryTable: invalid depth index {:#x}", params.index_size);
|
||||
return NvResult::BadParameter;
|
||||
}
|
||||
|
||||
@@ -365,7 +365,7 @@ NvResult nvhost_ctrl_gpu::ZBCQueryTable(IoctlZbcQueryTable& params) {
|
||||
}
|
||||
|
||||
NvResult nvhost_ctrl_gpu::FlushL2(IoctlFlushL2& params) {
|
||||
LOG_DEBUG(Service_NVDRV, "called {:#X}", params.flush);
|
||||
LOG_DEBUG(Service_NVDRV, "called {:#x}", params.flush);
|
||||
// if ((params.flush & 0x01) != 0) //l2 flush
|
||||
// /* we dont emulate l2 */;
|
||||
// if ((params.flush & 0x04) != 0) //fb flush
|
||||
|
||||
@@ -259,7 +259,7 @@ s32_le nvhost_gpu::GetObjectContextClassNumberIndex(CtxClasses class_number) {
|
||||
}
|
||||
|
||||
NvResult nvhost_gpu::AllocateObjectContext(IoctlAllocObjCtx& params) {
|
||||
LOG_DEBUG(Service_NVDRV, "called, class_num={:#X}, flags={:#X}, obj_id={:#X}", params.class_num,
|
||||
LOG_DEBUG(Service_NVDRV, "called, class_num={:#x}, flags={:#x}, obj_id={:#x}", params.class_num,
|
||||
params.flags, params.obj_id);
|
||||
|
||||
// Do not require channel initialization here: some clients allocate contexts before binding.
|
||||
@@ -271,7 +271,7 @@ NvResult nvhost_gpu::AllocateObjectContext(IoctlAllocObjCtx& params) {
|
||||
std::scoped_lock lk(channel_mutex);
|
||||
|
||||
if (params.flags) {
|
||||
LOG_WARNING(Service_NVDRV, "non-zero flags={:#X} for class={:#X}", params.flags,
|
||||
LOG_WARNING(Service_NVDRV, "non-zero flags={:#x} for class={:#x}", params.flags,
|
||||
params.class_num);
|
||||
|
||||
constexpr u32 allowed_mask{};
|
||||
@@ -281,13 +281,13 @@ NvResult nvhost_gpu::AllocateObjectContext(IoctlAllocObjCtx& params) {
|
||||
s32_le ctx_class_number_index =
|
||||
GetObjectContextClassNumberIndex(static_cast<CtxClasses>(params.class_num));
|
||||
if (ctx_class_number_index < 0) {
|
||||
LOG_ERROR(Service_NVDRV, "Invalid class number for object context: {:#X}",
|
||||
LOG_ERROR(Service_NVDRV, "Invalid class number for object context: {:#x}",
|
||||
params.class_num);
|
||||
return NvResult::BadParameter;
|
||||
}
|
||||
|
||||
if (ctxObjs[ctx_class_number_index].has_value()) {
|
||||
LOG_WARNING(Service_NVDRV, "Object context for class {:#X} already allocated on this channel",
|
||||
LOG_WARNING(Service_NVDRV, "Object context for class {:#x} already allocated on this channel",
|
||||
params.class_num);
|
||||
return NvResult::AlreadyAllocated;
|
||||
}
|
||||
@@ -420,20 +420,20 @@ NvResult nvhost_gpu::SubmitGPFIFOBase2(IoctlSubmitGpfifo& params,
|
||||
}
|
||||
|
||||
NvResult nvhost_gpu::GetWaitbase(IoctlGetWaitbase& params) {
|
||||
LOG_INFO(Service_NVDRV, "called, unknown={:#X}", params.unknown);
|
||||
LOG_INFO(Service_NVDRV, "called, unknown={:#x}", params.unknown);
|
||||
|
||||
params.value = 0; // Seems to be hard coded at 0
|
||||
return NvResult::Success;
|
||||
}
|
||||
|
||||
NvResult nvhost_gpu::ChannelSetTimeout(IoctlChannelSetTimeout& params) {
|
||||
LOG_INFO(Service_NVDRV, "called, timeout={:#X}", params.timeout);
|
||||
LOG_INFO(Service_NVDRV, "called, timeout={:#x}", params.timeout);
|
||||
|
||||
return NvResult::Success;
|
||||
}
|
||||
|
||||
NvResult nvhost_gpu::ChannelSetTimeslice(IoctlSetTimeslice& params) {
|
||||
LOG_INFO(Service_NVDRV, "called, timeslice={:#X}", params.timeslice);
|
||||
LOG_INFO(Service_NVDRV, "called, timeslice={:#x}", params.timeslice);
|
||||
|
||||
if (params.timeslice < 1000 || params.timeslice > 5000) {
|
||||
return NvResult::BadParameter;
|
||||
|
||||
@@ -81,7 +81,7 @@ void nvmap::OnClose(DeviceFD fd) {
|
||||
}
|
||||
|
||||
NvResult nvmap::IocCreate(IocCreateParams& params) {
|
||||
LOG_DEBUG(Service_NVDRV, "called, size=0x{:08X}", params.size);
|
||||
LOG_DEBUG(Service_NVDRV, "called, size={:#08x}", params.size);
|
||||
|
||||
std::shared_ptr<NvCore::NvMap::Handle> handle_description{};
|
||||
auto result =
|
||||
@@ -92,7 +92,7 @@ NvResult nvmap::IocCreate(IocCreateParams& params) {
|
||||
}
|
||||
handle_description->orig_size = params.size; // Orig size is the unaligned size
|
||||
params.handle = handle_description->id;
|
||||
LOG_DEBUG(Service_NVDRV, "handle: {}, size: {:#X}", handle_description->id, params.size);
|
||||
LOG_DEBUG(Service_NVDRV, "handle: {}, size: {:#x}", handle_description->id, params.size);
|
||||
|
||||
return NvResult::Success;
|
||||
}
|
||||
|
||||
@@ -1,12 +1,17 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: 2021 yuzu Emulator Project
|
||||
// SPDX-FileCopyrightText: 2021 Skyline Team and Contributors
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include <algorithm>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include <fmt/ranges.h>
|
||||
#include "core/core.h"
|
||||
#include "core/hle/kernel/k_event.h"
|
||||
#include "core/hle/kernel/k_process.h"
|
||||
#include "core/hle/service/ipc_helpers.h"
|
||||
#include "core/hle/service/nvdrv/core/container.h"
|
||||
#include "core/hle/service/nvdrv/devices/nvdevice.h"
|
||||
@@ -133,6 +138,9 @@ DeviceFD Module::Open(const std::string& device_name, NvCore::SessionId session_
|
||||
auto device = builder(fd)->second;
|
||||
|
||||
device->OnOpen(session_id, fd);
|
||||
if (container.IsSessionActive(session_id)) {
|
||||
open_file_sessions.emplace(fd, session_id);
|
||||
}
|
||||
|
||||
return fd;
|
||||
}
|
||||
@@ -204,6 +212,7 @@ NvResult Module::Close(DeviceFD fd) {
|
||||
itr->second->OnClose(fd);
|
||||
|
||||
open_files.erase(itr);
|
||||
open_file_sessions.erase(fd);
|
||||
|
||||
return NvResult::Success;
|
||||
}
|
||||
@@ -228,4 +237,98 @@ NvResult Module::QueryEvent(DeviceFD fd, u32 event_id, Kernel::KEvent*& event) {
|
||||
return NvResult::Success;
|
||||
}
|
||||
|
||||
static bool ContainsSession(std::span<const NvCore::SessionId> session_ids,
|
||||
NvCore::SessionId session_id) {
|
||||
return std::ranges::any_of(session_ids, [session_id](const auto candidate) {
|
||||
return candidate.id == session_id.id;
|
||||
});
|
||||
}
|
||||
|
||||
static void AppendUniqueSession(std::vector<NvCore::SessionId>& session_ids,
|
||||
NvCore::SessionId session_id) {
|
||||
if (!ContainsSession(session_ids, session_id)) {
|
||||
session_ids.push_back(session_id);
|
||||
}
|
||||
}
|
||||
|
||||
size_t Module::CloseFilesForSessions(std::span<const NvCore::SessionId> session_ids) {
|
||||
std::vector<DeviceFD> fds;
|
||||
fds.reserve(open_file_sessions.size());
|
||||
|
||||
for (const auto& [fd, session_id] : open_file_sessions) {
|
||||
if (ContainsSession(session_ids, session_id)) {
|
||||
fds.push_back(fd);
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto fd : fds) {
|
||||
Close(fd);
|
||||
}
|
||||
|
||||
return fds.size();
|
||||
}
|
||||
|
||||
void Module::CloseSession(NvCore::SessionId session_id) {
|
||||
container.CloseSession(session_id);
|
||||
}
|
||||
|
||||
void Module::TrackSessionAruid(NvCore::SessionId session_id, u64 aruid) {
|
||||
const bool active = container.IsSessionActive(session_id);
|
||||
if (active) {
|
||||
session_aruids[session_id.id] = aruid;
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<NvCore::SessionId> Module::GetSessionIdsForAruid(u64 aruid) const {
|
||||
std::vector<NvCore::SessionId> session_ids;
|
||||
for (const auto& [session_id, session_aruid] : session_aruids) {
|
||||
if (session_aruid == aruid) {
|
||||
session_ids.push_back(NvCore::SessionId{session_id});
|
||||
}
|
||||
}
|
||||
|
||||
return session_ids;
|
||||
}
|
||||
|
||||
size_t Module::ResetForProcess(Kernel::KProcess* process) {
|
||||
const auto process_id = process != nullptr ? process->GetProcessId() : 0;
|
||||
auto session_ids = container.GetSessionIdsForProcess(process);
|
||||
|
||||
if (process_id != 0) {
|
||||
for (const auto session_id : GetSessionIdsForAruid(process_id)) {
|
||||
AppendUniqueSession(session_ids, session_id);
|
||||
}
|
||||
}
|
||||
|
||||
const auto active_session_ids = container.GetActiveSessionIds();
|
||||
const auto active_before = active_session_ids.size();
|
||||
const bool has_active_candidate =
|
||||
std::ranges::any_of(session_ids, [this](const auto session_id) {
|
||||
return container.IsSessionActive(session_id);
|
||||
});
|
||||
bool used_active_sessions = false;
|
||||
if (!has_active_candidate && !active_session_ids.empty()) {
|
||||
for (const auto session_id : active_session_ids) {
|
||||
AppendUniqueSession(session_ids, session_id);
|
||||
}
|
||||
used_active_sessions = true;
|
||||
}
|
||||
|
||||
const auto closed_files = CloseFilesForSessions(session_ids);
|
||||
const auto closed_sessions = container.CloseSessions(session_ids);
|
||||
for (const auto session_id : session_ids) {
|
||||
if (!container.IsSessionActive(session_id)) {
|
||||
session_aruids.erase(session_id.id);
|
||||
}
|
||||
}
|
||||
|
||||
if (used_active_sessions) {
|
||||
LOG_WARNING(Service_NVDRV,
|
||||
"NextLoad: NVDRV reset used active sessions because process-owned "
|
||||
"sessions were not found, process_id={}, sessions={}, files={}, active_before={}",
|
||||
process_id, closed_sessions, closed_files, active_before);
|
||||
}
|
||||
return closed_sessions;
|
||||
}
|
||||
|
||||
} // namespace Service::Nvidia
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#include <memory>
|
||||
#include <span>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <ankerl/unordered_dense.h>
|
||||
|
||||
#include "common/common_types.h"
|
||||
@@ -26,6 +27,7 @@ class System;
|
||||
|
||||
namespace Kernel {
|
||||
class KEvent;
|
||||
class KProcess;
|
||||
}
|
||||
|
||||
namespace Service::Nvidia {
|
||||
@@ -89,6 +91,9 @@ public:
|
||||
NvResult Close(DeviceFD fd);
|
||||
|
||||
NvResult QueryEvent(DeviceFD fd, u32 event_id, Kernel::KEvent*& event);
|
||||
void CloseSession(NvCore::SessionId session_id);
|
||||
void TrackSessionAruid(NvCore::SessionId session_id, u64 aruid);
|
||||
size_t ResetForProcess(Kernel::KProcess* process);
|
||||
|
||||
NvCore::Container& GetContainer() {
|
||||
return container;
|
||||
@@ -106,12 +111,17 @@ private:
|
||||
using FilesContainerType = ankerl::unordered_dense::map<DeviceFD, std::shared_ptr<Devices::nvdevice>>;
|
||||
/// Mapping of file descriptors to the devices they reference.
|
||||
FilesContainerType open_files;
|
||||
ankerl::unordered_dense::map<DeviceFD, NvCore::SessionId> open_file_sessions;
|
||||
ankerl::unordered_dense::map<size_t, u64> session_aruids;
|
||||
|
||||
KernelHelpers::ServiceContext service_context;
|
||||
|
||||
EventInterface events_interface;
|
||||
|
||||
ankerl::unordered_dense::map<std::string, std::function<FilesContainerType::iterator(DeviceFD)>> builders;
|
||||
|
||||
size_t CloseFilesForSessions(std::span<const NvCore::SessionId> session_ids);
|
||||
std::vector<NvCore::SessionId> GetSessionIdsForAruid(u64 aruid) const;
|
||||
};
|
||||
|
||||
void LoopProcess(Core::System& system);
|
||||
|
||||
@@ -59,7 +59,7 @@ void NVDRV::Ioctl1(HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
const auto fd = rp.Pop<DeviceFD>();
|
||||
const auto command = rp.PopRaw<Ioctl>();
|
||||
LOG_DEBUG(Service_NVDRV, "called fd={}, ioctl=0x{:08X}", fd, command.raw);
|
||||
LOG_DEBUG(Service_NVDRV, "called fd={}, ioctl={:#08x}", fd, command.raw);
|
||||
|
||||
if (!is_initialized) {
|
||||
ServiceError(ctx, NvResult::NotInitialized);
|
||||
@@ -85,7 +85,7 @@ void NVDRV::Ioctl2(HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
const auto fd = rp.Pop<DeviceFD>();
|
||||
const auto command = rp.PopRaw<Ioctl>();
|
||||
LOG_DEBUG(Service_NVDRV, "called fd={}, ioctl=0x{:08X}", fd, command.raw);
|
||||
LOG_DEBUG(Service_NVDRV, "called fd={}, ioctl={:#08x}", fd, command.raw);
|
||||
|
||||
if (!is_initialized) {
|
||||
ServiceError(ctx, NvResult::NotInitialized);
|
||||
@@ -112,7 +112,7 @@ void NVDRV::Ioctl3(HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
const auto fd = rp.Pop<DeviceFD>();
|
||||
const auto command = rp.PopRaw<Ioctl>();
|
||||
LOG_DEBUG(Service_NVDRV, "called fd={}, ioctl=0x{:08X}", fd, command.raw);
|
||||
LOG_DEBUG(Service_NVDRV, "called fd={}, ioctl={:#08x}", fd, command.raw);
|
||||
|
||||
if (!is_initialized) {
|
||||
ServiceError(ctx, NvResult::NotInitialized);
|
||||
@@ -213,6 +213,9 @@ void NVDRV::SetAruid(HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
pid = rp.Pop<u64>();
|
||||
LOG_WARNING(Service_NVDRV, "(STUBBED) called, pid={:#X}", pid);
|
||||
if (is_initialized) {
|
||||
nvdrv->TrackSessionAruid(session_id, pid);
|
||||
}
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 3};
|
||||
rb.Push(ResultSuccess);
|
||||
|
||||
@@ -91,7 +91,7 @@ Result SteadyClock::IsRtcResetDetected(Out<bool> out_is_detected) {
|
||||
|
||||
Result SteadyClock::GetSetupResultValue(Out<Result> out_result) {
|
||||
SCOPE_EXIT {
|
||||
LOG_DEBUG(Service_Time, "called. out_result={:#X}", out_result->raw);
|
||||
LOG_DEBUG(Service_Time, "called. out_result={:#x}", out_result->raw);
|
||||
};
|
||||
|
||||
R_UNLESS(m_can_write_uninitialized_clock || m_clock_core.IsInitialized(),
|
||||
|
||||
@@ -25,7 +25,7 @@ namespace Service {
|
||||
int num_params = (cmd_buf[0] & 0x3F) + ((cmd_buf[0] >> 6) & 0x3F);
|
||||
std::string function_string = fmt::format("fn '{}': port={}", name, port_name);
|
||||
for (int i = 1; i <= num_params; ++i)
|
||||
function_string += fmt::format(", cmd_buf[{}]={:#X}", i, cmd_buf[i]);
|
||||
function_string += fmt::format(", cmd_buf[{}]={:#x}", i, cmd_buf[i]);
|
||||
return function_string;
|
||||
}
|
||||
|
||||
@@ -62,9 +62,9 @@ void ServiceFrameworkBase::ReportUnimplementedFunction(HLERequestContext& ctx,
|
||||
std::string function_name = info == nullptr ? "<unknown>" : info->name;
|
||||
|
||||
fmt::memory_buffer buf;
|
||||
fmt::format_to(std::back_inserter(buf), "function '{}({})': port='{}' cmd_buf={{[0]={:#X}", ctx.GetCommand(), function_name, service_name, cmd_buf[0]);
|
||||
fmt::format_to(std::back_inserter(buf), "function '{}({})': port='{}' cmd_buf={{[0]={:#x}", ctx.GetCommand(), function_name, service_name, cmd_buf[0]);
|
||||
for (int i = 1; i <= 8; ++i)
|
||||
fmt::format_to(std::back_inserter(buf), ", [{}]={:#X}", i, cmd_buf[i]);
|
||||
fmt::format_to(std::back_inserter(buf), ", [{}]={:#x}", i, cmd_buf[i]);
|
||||
buf.push_back('}');
|
||||
|
||||
system.GetReporter().SaveUnimplementedFunctionReport(ctx, ctx.GetCommand(), function_name, service_name);
|
||||
|
||||
@@ -89,9 +89,7 @@ Services::Services(std::shared_ptr<SM::ServiceManager>& sm, Core::System& system
|
||||
for (auto const& e : std::vector<std::pair<std::string_view, void (*)(Core::System&)>>{
|
||||
{"audio", &Audio::LoopProcess},
|
||||
{"FS", &FileSystem::LoopProcess},
|
||||
{"jit", &JIT::LoopProcess},
|
||||
{"ldn", &LDN::LoopProcess},
|
||||
{"Loader", &LDR::LoopProcess},
|
||||
{"nvservices", &Nvidia::LoopProcess},
|
||||
{"bsdsocket", &Sockets::LoopProcess},
|
||||
})
|
||||
@@ -120,19 +118,21 @@ Services::Services(std::shared_ptr<SM::ServiceManager>& sm, Core::System& system
|
||||
{"glue", &Glue::LoopProcess},
|
||||
{"grc", &GRC::LoopProcess},
|
||||
{"hid", &HID::LoopProcess},
|
||||
{"jit", &JIT::LoopProcess},
|
||||
{"lbl", &LBL::LoopProcess},
|
||||
{"Loader", &LDR::LoopProcess},
|
||||
{"LogManager.Prod", &LM::LoopProcess},
|
||||
{"mig", &Migration::LoopProcess},
|
||||
{"mii", &Mii::LoopProcess},
|
||||
{"mm", &MM::LoopProcess},
|
||||
{"mnpp", &MNPP::LoopProcess},
|
||||
{"nvnflinger", &Nvnflinger::LoopProcess},
|
||||
{"NCM", &NCM::LoopProcess},
|
||||
{"nfc", &NFC::LoopProcess},
|
||||
{"nfp", &NFP::LoopProcess},
|
||||
{"ngc", &NGC::LoopProcess},
|
||||
{"nifm", &NIFM::LoopProcess},
|
||||
{"nim", &NIM::LoopProcess},
|
||||
{"nvnflinger", &Nvnflinger::LoopProcess},
|
||||
{"npns", &NPNS::LoopProcess},
|
||||
{"ns", &NS::LoopProcess},
|
||||
{"olsc", &OLSC::LoopProcess},
|
||||
|
||||
@@ -196,7 +196,7 @@ Result SM::GetServiceImpl(Kernel::KClientSession** out_client_session, HLEReques
|
||||
// Create a new session.
|
||||
Kernel::KClientSession* session{};
|
||||
if (const auto result = client_port->CreateSession(kernel, &session); result.IsError()) {
|
||||
LOG_ERROR(Service_SM, "called service={} -> error 0x{:08X}", name, result.raw);
|
||||
LOG_ERROR(Service_SM, "called service={} -> error {:#08x}", name, result.raw);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
@@ -283,7 +283,7 @@ void BSD::GetSockOpt(HLERequestContext& ctx) {
|
||||
|
||||
std::vector<u8> optval(ctx.GetWriteBufferSize());
|
||||
|
||||
LOG_DEBUG(Service, "called. fd={} level={} optname=0x{:x} len=0x{:x}", fd, level, optname,
|
||||
LOG_DEBUG(Service, "called. fd={} level={} optname={:#x} len={:#x}", fd, level, optname,
|
||||
optval.size());
|
||||
|
||||
const Errno err = GetSockOptImpl(fd, level, optname, optval);
|
||||
@@ -331,7 +331,7 @@ void BSD::SetSockOpt(HLERequestContext& ctx) {
|
||||
const OptName optname = static_cast<OptName>(rp.Pop<u32>());
|
||||
const auto optval = ctx.ReadBuffer();
|
||||
|
||||
LOG_DEBUG(Service, "called. fd={} level={} optname=0x{:x} optlen={}", fd, level,
|
||||
LOG_DEBUG(Service, "called. fd={} level={} optname={:#x} optlen={}", fd, level,
|
||||
static_cast<u32>(optname), optval.size());
|
||||
|
||||
BuildErrnoResponse(ctx, SetSockOptImpl(fd, level, optname, optval));
|
||||
@@ -354,7 +354,7 @@ void BSD::Recv(HLERequestContext& ctx) {
|
||||
const s32 fd = rp.Pop<s32>();
|
||||
const u32 flags = rp.Pop<u32>();
|
||||
|
||||
LOG_DEBUG(Service, "called. fd={} flags=0x{:x} len={}", fd, flags, ctx.GetWriteBufferSize());
|
||||
LOG_DEBUG(Service, "called. fd={} flags={:#x} len={}", fd, flags, ctx.GetWriteBufferSize());
|
||||
|
||||
ExecuteWork(ctx, RecvWork{
|
||||
.fd = fd,
|
||||
@@ -369,7 +369,7 @@ void BSD::RecvFrom(HLERequestContext& ctx) {
|
||||
const s32 fd = rp.Pop<s32>();
|
||||
const u32 flags = rp.Pop<u32>();
|
||||
|
||||
LOG_DEBUG(Service, "called. fd={} flags=0x{:x} len={} addrlen={}", fd, flags,
|
||||
LOG_DEBUG(Service, "called. fd={} flags={:#x} len={} addrlen={}", fd, flags,
|
||||
ctx.GetWriteBufferSize(0), ctx.GetWriteBufferSize(1));
|
||||
|
||||
ExecuteWork(ctx, RecvFromWork{
|
||||
@@ -386,7 +386,7 @@ void BSD::Send(HLERequestContext& ctx) {
|
||||
const s32 fd = rp.Pop<s32>();
|
||||
const u32 flags = rp.Pop<u32>();
|
||||
|
||||
LOG_DEBUG(Service, "called. fd={} flags=0x{:x} len={}", fd, flags, ctx.GetReadBufferSize());
|
||||
LOG_DEBUG(Service, "called. fd={} flags={:#x} len={}", fd, flags, ctx.GetReadBufferSize());
|
||||
|
||||
ExecuteWork(ctx, SendWork{
|
||||
.fd = fd,
|
||||
|
||||
@@ -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-FileCopyrightText: Copyright 2024 yuzu Emulator Project
|
||||
@@ -141,7 +141,7 @@ Result IApplicationDisplayService::GetDisplayResolution(Out<s64> out_width, Out<
|
||||
}
|
||||
|
||||
Result IApplicationDisplayService::SetLayerScalingMode(NintendoScaleMode scale_mode, u64 layer_id) {
|
||||
LOG_DEBUG(Service_VI, "called. scale_mode={}, unknown=0x{:016X}", scale_mode, layer_id);
|
||||
LOG_DEBUG(Service_VI, "called. scale_mode={}, unknown={:#016x}", scale_mode, layer_id);
|
||||
|
||||
if (scale_mode > NintendoScaleMode::PreserveAspectRatio) {
|
||||
LOG_ERROR(Service_VI, "Invalid scaling mode provided.");
|
||||
|
||||
@@ -40,6 +40,7 @@ namespace Network {
|
||||
namespace {
|
||||
|
||||
enum class CallType {
|
||||
Connect,
|
||||
Send,
|
||||
Other,
|
||||
};
|
||||
@@ -131,7 +132,7 @@ Errno TranslateNativeError(int e, CallType call_type = CallType::Other) {
|
||||
case WSAENOTCONN:
|
||||
return Errno::NOTCONN;
|
||||
case WSAEWOULDBLOCK:
|
||||
return Errno::AGAIN;
|
||||
return call_type == CallType::Connect ? Errno::INPROGRESS : Errno::AGAIN;
|
||||
case WSAECONNREFUSED:
|
||||
return Errno::CONNREFUSED;
|
||||
case WSAECONNABORTED:
|
||||
@@ -563,6 +564,7 @@ int TranslateTypeToNative(Type type) {
|
||||
NETWORK_PROTOCOL_TRANSLATE_ELEM(UDPLITE)
|
||||
#elif defined(_WIN32)
|
||||
#define NETWORK_PROTOCOL_TRANSLATE_LIST \
|
||||
NETWORK_PROTOCOL_TRANSLATE_ELEM(IP) \
|
||||
/*NETWORK_PROTOCOL_TRANSLATE_ELEM(HOPOPTS)*/ \
|
||||
NETWORK_PROTOCOL_TRANSLATE_ELEM(ICMP) \
|
||||
NETWORK_PROTOCOL_TRANSLATE_ELEM(IGMP) \
|
||||
@@ -652,13 +654,13 @@ short TranslatePollEvents(PollEvents events) {
|
||||
// Unlike poll on other OSes, WSAPoll will complain if any other flags are set on input.
|
||||
if (result & ~allowed_events) {
|
||||
LOG_DEBUG(Network,
|
||||
"Removing WSAPoll input events 0x{:x} because Windows doesn't support them",
|
||||
"Removing WSAPoll input events {:#x} because Windows doesn't support them",
|
||||
result & ~allowed_events);
|
||||
}
|
||||
result &= allowed_events;
|
||||
#endif
|
||||
|
||||
UNIMPLEMENTED_IF_MSG((u16)events != 0, "Unhandled guest events=0x{:x}", (u16)events);
|
||||
UNIMPLEMENTED_IF_MSG((u16)events != 0, "Unhandled guest events={:#x}", (u16)events);
|
||||
|
||||
return result;
|
||||
}
|
||||
@@ -682,7 +684,7 @@ PollEvents TranslatePollRevents(short revents) {
|
||||
translate(POLLRDBAND, PollEvents::RdBand);
|
||||
translate(POLLWRBAND, PollEvents::WrBand);
|
||||
|
||||
UNIMPLEMENTED_IF_MSG(revents != 0, "Unhandled host revents=0x{:x}", revents);
|
||||
UNIMPLEMENTED_IF_MSG(revents != 0, "Unhandled host revents={:#x}", revents);
|
||||
|
||||
return result;
|
||||
}
|
||||
@@ -888,7 +890,7 @@ Errno Socket::Connect(SockAddrIn addr_in) {
|
||||
return Errno::SUCCESS;
|
||||
}
|
||||
|
||||
return GetAndLogLastError();
|
||||
return GetAndLogLastError(CallType::Connect);
|
||||
}
|
||||
|
||||
std::pair<SockAddrIn, Errno> Socket::GetPeerName() {
|
||||
|
||||
@@ -263,7 +263,7 @@ AppLoader_DeconstructedRomDirectory::LoadResult AppLoader_DeconstructedRomDirect
|
||||
|
||||
next_load_addr = *tentative_next_load_addr;
|
||||
modules.insert_or_assign(load_addr, module);
|
||||
LOG_DEBUG(Loader, "loaded module {} @ {:#X}", module, load_addr);
|
||||
LOG_DEBUG(Loader, "loaded module {} @ {:#x}", module, load_addr);
|
||||
}
|
||||
|
||||
is_loaded = true;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user