summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--CMakeLists.txt6
-rw-r--r--dist/languages/uk.ts7321
-rw-r--r--src/core/CMakeLists.txt6
-rw-r--r--src/core/hle/ipc_helpers.h17
-rw-r--r--src/core/hle/kernel/board/nintendo/nx/k_system_control.h4
-rw-r--r--src/core/hle/kernel/hle_ipc.cpp55
-rw-r--r--src/core/hle/kernel/hle_ipc.h29
-rw-r--r--src/core/hle/kernel/init/init_slab_setup.cpp74
-rw-r--r--src/core/hle/kernel/init/init_slab_setup.h2
-rw-r--r--src/core/hle/kernel/k_class_token.cpp3
-rw-r--r--src/core/hle/kernel/k_class_token.h6
-rw-r--r--src/core/hle/kernel/k_client_port.cpp5
-rw-r--r--src/core/hle/kernel/k_client_port.h3
-rw-r--r--src/core/hle/kernel/k_debug.h20
-rw-r--r--src/core/hle/kernel/k_dynamic_page_manager.h67
-rw-r--r--src/core/hle/kernel/k_dynamic_resource_manager.h3
-rw-r--r--src/core/hle/kernel/k_event_info.h64
-rw-r--r--src/core/hle/kernel/k_handle_table.cpp33
-rw-r--r--src/core/hle/kernel/k_handle_table.h106
-rw-r--r--src/core/hle/kernel/k_memory_block.h110
-rw-r--r--src/core/hle/kernel/k_memory_layout.cpp10
-rw-r--r--src/core/hle/kernel/k_memory_layout.h19
-rw-r--r--src/core/hle/kernel/k_memory_manager.cpp270
-rw-r--r--src/core/hle/kernel/k_memory_manager.h259
-rw-r--r--src/core/hle/kernel/k_memory_region_type.h123
-rw-r--r--src/core/hle/kernel/k_page_bitmap.h243
-rw-r--r--src/core/hle/kernel/k_page_buffer.h14
-rw-r--r--src/core/hle/kernel/k_page_group.h86
-rw-r--r--src/core/hle/kernel/k_page_heap.cpp86
-rw-r--r--src/core/hle/kernel/k_page_heap.h39
-rw-r--r--src/core/hle/kernel/k_page_table.cpp1129
-rw-r--r--src/core/hle/kernel/k_page_table.h142
-rw-r--r--src/core/hle/kernel/k_page_table_manager.h55
-rw-r--r--src/core/hle/kernel/k_page_table_slab_heap.h93
-rw-r--r--src/core/hle/kernel/k_port.cpp6
-rw-r--r--src/core/hle/kernel/k_process.cpp4
-rw-r--r--src/core/hle/kernel/k_server_port.cpp6
-rw-r--r--src/core/hle/kernel/k_server_port.h19
-rw-r--r--src/core/hle/kernel/k_server_session.cpp187
-rw-r--r--src/core/hle/kernel/k_server_session.h41
-rw-r--r--src/core/hle/kernel/k_session.cpp5
-rw-r--r--src/core/hle/kernel/k_session.h3
-rw-r--r--src/core/hle/kernel/k_system_resource.cpp26
-rw-r--r--src/core/hle/kernel/k_system_resource.h137
-rw-r--r--src/core/hle/kernel/kernel.cpp179
-rw-r--r--src/core/hle/kernel/kernel.h38
-rw-r--r--src/core/hle/kernel/service_thread.cpp230
-rw-r--r--src/core/hle/kernel/service_thread.h6
-rw-r--r--src/core/hle/kernel/slab_helpers.h78
-rw-r--r--src/core/hle/kernel/svc.cpp9
-rw-r--r--src/core/hle/kernel/svc_results.h1
-rw-r--r--src/core/hle/kernel/svc_types.h40
-rw-r--r--src/core/hle/result.h13
-rw-r--r--src/core/hle/service/acc/acc.cpp34
-rw-r--r--src/core/hle/service/acc/acc.h1
-rw-r--r--src/core/hle/service/acc/acc_u0.cpp2
-rw-r--r--src/core/hle/service/acc/profile_manager.cpp25
-rw-r--r--src/core/hle/service/acc/profile_manager.h3
-rw-r--r--src/core/hle/service/nvdrv/devices/nvmap.cpp6
-rw-r--r--src/core/hle/service/service.cpp21
-rw-r--r--src/core/hle/service/service.h4
-rw-r--r--src/core/hle/service/sm/sm.cpp43
-rw-r--r--src/core/hle/service/sm/sm.h2
-rw-r--r--src/core/hle/service/sm/sm_controller.cpp41
-rw-r--r--src/core/internal_network/socket_proxy.cpp4
-rw-r--r--src/shader_recompiler/CMakeLists.txt1
-rw-r--r--src/shader_recompiler/backend/glasm/emit_glasm.cpp3
-rw-r--r--src/shader_recompiler/backend/glasm/emit_glasm_bitwise_conversion.cpp4
-rw-r--r--src/shader_recompiler/backend/glasm/emit_glasm_context_get_set.cpp4
-rw-r--r--src/shader_recompiler/backend/glasm/emit_glasm_instructions.h2
-rw-r--r--src/shader_recompiler/backend/glsl/emit_glsl_bitwise_conversion.cpp4
-rw-r--r--src/shader_recompiler/backend/glsl/emit_glsl_context_get_set.cpp4
-rw-r--r--src/shader_recompiler/backend/glsl/emit_glsl_instructions.h2
-rw-r--r--src/shader_recompiler/backend/glsl/glsl_emit_context.cpp3
-rw-r--r--src/shader_recompiler/backend/spirv/emit_spirv.h4
-rw-r--r--src/shader_recompiler/backend/spirv/emit_spirv_bitwise_conversion.cpp4
-rw-r--r--src/shader_recompiler/backend/spirv/emit_spirv_context_get_set.cpp12
-rw-r--r--src/shader_recompiler/backend/spirv/emit_spirv_instructions.h4
-rw-r--r--src/shader_recompiler/backend/spirv/spirv_emit_context.cpp31
-rw-r--r--src/shader_recompiler/backend/spirv/spirv_emit_context.h4
-rw-r--r--src/shader_recompiler/environment.h4
-rw-r--r--src/shader_recompiler/frontend/ir/ir_emitter.cpp13
-rw-r--r--src/shader_recompiler/frontend/ir/ir_emitter.h3
-rw-r--r--src/shader_recompiler/frontend/ir/opcodes.h1
-rw-r--r--src/shader_recompiler/frontend/ir/opcodes.inc2
-rw-r--r--src/shader_recompiler/frontend/ir/type.h31
-rw-r--r--src/shader_recompiler/frontend/ir/value.cpp3
-rw-r--r--src/shader_recompiler/frontend/ir/value.h12
-rw-r--r--src/shader_recompiler/frontend/maxwell/translate_program.cpp2
-rw-r--r--src/shader_recompiler/ir_opt/passes.h1
-rw-r--r--src/shader_recompiler/ir_opt/position_pass.cpp77
-rw-r--r--src/shader_recompiler/ir_opt/texture_pass.cpp49
-rw-r--r--src/shader_recompiler/shader_info.h11
-rw-r--r--src/video_core/renderer_opengl/gl_buffer_cache.cpp15
-rw-r--r--src/video_core/renderer_opengl/gl_graphics_pipeline.cpp11
-rw-r--r--src/video_core/renderer_opengl/gl_rasterizer.cpp10
-rw-r--r--src/video_core/renderer_opengl/gl_shader_cache.cpp2
-rw-r--r--src/video_core/renderer_vulkan/pipeline_helper.h10
-rw-r--r--src/video_core/renderer_vulkan/vk_blit_screen.cpp6
-rw-r--r--src/video_core/renderer_vulkan/vk_blit_screen.h5
-rw-r--r--src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp22
-rw-r--r--src/video_core/renderer_vulkan/vk_graphics_pipeline.h4
-rw-r--r--src/video_core/renderer_vulkan/vk_pipeline_cache.cpp2
-rw-r--r--src/video_core/renderer_vulkan/vk_rasterizer.cpp22
-rw-r--r--src/video_core/renderer_vulkan/vk_rasterizer.h1
-rw-r--r--src/video_core/renderer_vulkan/vk_scheduler.h4
-rw-r--r--src/video_core/renderer_vulkan/vk_texture_cache.cpp15
-rw-r--r--src/video_core/renderer_vulkan/vk_texture_cache.h11
-rw-r--r--src/video_core/shader_environment.cpp104
-rw-r--r--src/video_core/shader_environment.h21
-rw-r--r--src/video_core/texture_cache/util.cpp1
-rw-r--r--src/yuzu/CMakeLists.txt2
-rw-r--r--src/yuzu/configuration/config.cpp4
-rw-r--r--src/yuzu/configuration/configure_ui.cpp6
-rw-r--r--src/yuzu/configuration/configure_ui.ui16
-rw-r--r--src/yuzu/game_list.cpp2
-rw-r--r--src/yuzu/main.cpp26
-rw-r--r--src/yuzu/main.h6
-rw-r--r--src/yuzu/main.ui1
-rw-r--r--src/yuzu/uisettings.h4
120 files changed, 11159 insertions, 1185 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index b625743ea..c6fc5dd9e 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -218,11 +218,11 @@ if(ENABLE_QT)
set(QT_VERSION 5.15)
# Check for system Qt on Linux, fallback to bundled Qt
- if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
+ if (UNIX AND NOT APPLE)
if (NOT YUZU_USE_BUNDLED_QT)
find_package(Qt5 ${QT_VERSION} COMPONENTS Widgets DBus Multimedia)
endif()
- if (NOT Qt5_FOUND OR YUZU_USE_BUNDLED_QT)
+ if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux" AND (NOT Qt5_FOUND OR YUZU_USE_BUNDLED_QT))
# Check for dependencies, then enable bundled Qt download
# Check that the system GLIBCXX version is compatible
@@ -323,7 +323,7 @@ if(ENABLE_QT)
set(YUZU_QT_NO_CMAKE_SYSTEM_PATH "NO_CMAKE_SYSTEM_PATH")
endif()
- if ((${CMAKE_SYSTEM_NAME} STREQUAL "Linux") AND YUZU_USE_BUNDLED_QT)
+ if (UNIX AND NOT APPLE AND YUZU_USE_BUNDLED_QT)
find_package(Qt5 ${QT_VERSION} REQUIRED COMPONENTS Widgets Concurrent Multimedia DBus ${QT_PREFIX_HINT} ${YUZU_QT_NO_CMAKE_SYSTEM_PATH})
else()
find_package(Qt5 ${QT_VERSION} REQUIRED COMPONENTS Widgets Concurrent Multimedia ${QT_PREFIX_HINT} ${YUZU_QT_NO_CMAKE_SYSTEM_PATH})
diff --git a/dist/languages/uk.ts b/dist/languages/uk.ts
new file mode 100644
index 000000000..66a3ac96e
--- /dev/null
+++ b/dist/languages/uk.ts
@@ -0,0 +1,7321 @@
+<?xml version="1.0" ?><!DOCTYPE TS><TS version="2.1" language="uk" sourcelanguage="en_US">
+<context>
+ <name>AboutDialog</name>
+ <message>
+ <location filename="../../src/yuzu/aboutdialog.ui" line="14"/>
+ <source>About yuzu</source>
+ <translation>Про yuzu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/aboutdialog.ui" line="72"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:28pt;&quot;&gt;yuzu&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:28pt;&quot;&gt;yuzu&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/aboutdialog.ui" line="85"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;%1 (%2)&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;%1 (%2)&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/aboutdialog.ui" line="98"/>
+ <source>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
+&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
+p, li { white-space: pre-wrap; }
+&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'Ubuntu'; font-size:11pt; font-weight:400; font-style:normal;&quot;&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:'MS Shell Dlg 2'; font-size:12pt;&quot;&gt;yuzu is an experimental open-source emulator for the Nintendo Switch licensed under GPLv3.0+.&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'MS Shell Dlg 2'; font-size:8pt;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:&apos;MS Shell Dlg 2&apos;; font-size:12pt;&quot;&gt;This software should not be used to play games you have not legally obtained.&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
+&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
+p, li { white-space: pre-wrap; }
+&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:&apos;Ubuntu&apos;; font-size:11pt; font-weight:400; font-style:normal;&quot;&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:&apos;MS Shell Dlg 2&apos;; font-size:12pt;&quot;&gt;yuzu є експериментальним емулятором Nintendo Switch з відкритим кодом ліцензований під GPLv3.0+.&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:&apos;MS Shell Dlg 2&apos;; font-size:8pt;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:&apos;MS Shell Dlg 2&apos;; font-size:12pt;&quot;&gt;Це програмне забезпечення не слід використовувати для ігор, які ви отримали незаконним шляхом.&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/aboutdialog.ui" line="130"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;a href=&quot;https://yuzu-emu.org/&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Website&lt;/span&gt;&lt;/a&gt; | &lt;a href=&quot;https://github.com/yuzu-emu&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Source Code&lt;/span&gt;&lt;/a&gt; | &lt;a href=&quot;https://github.com/yuzu-emu/yuzu/graphs/contributors&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Contributors&lt;/span&gt;&lt;/a&gt; | &lt;a href=&quot;https://github.com/yuzu-emu/yuzu/blob/master/LICENSE.txt&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;License&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;a href=&quot;https://yuzu-emu.org/&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Веб-сайт&lt;/span&gt;&lt;/a&gt; | &lt;a href=&quot;https://github.com/yuzu-emu&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Вихідний код&lt;/span&gt;&lt;/a&gt; | &lt;a href=&quot;https://github.com/yuzu-emu/yuzu/graphs/contributors&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Вкладники&lt;/span&gt;&lt;/a&gt; | &lt;a href=&quot;https://github.com/yuzu-emu/yuzu/blob/master/LICENSE.txt&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Ліцензія&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/aboutdialog.ui" line="146"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:7pt;&quot;&gt;&amp;quot;Nintendo Switch&amp;quot; is a trademark of Nintendo. yuzu is not affiliated with Nintendo in any way.&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:7pt;&quot;&gt;&amp;quot;Nintendo Switch&amp;quot; є торговою маркою Nintendo. yuzu не пов&apos;язаний з Nintendo у будь-якому вигляді.&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+</context>
+<context>
+ <name>CalibrationConfigurationDialog</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="23"/>
+ <source>Communicating with the server...</source>
+ <translation>Зв&apos;язок із сервером...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="24"/>
+ <source>Cancel</source>
+ <translation>Скасувати</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="43"/>
+ <source>Touch the top left corner &lt;br&gt;of your touchpad.</source>
+ <translation>Торкніться верхнього лівого кута &lt;br&gt; вашого тачпаду.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="46"/>
+ <source>Now touch the bottom right corner &lt;br&gt;of your touchpad.</source>
+ <translation>Тепер торкніться правого нижнього кута &lt;br&gt; вашого тачпаду.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="49"/>
+ <source>Configuration completed!</source>
+ <translation>Налаштування завершено!</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="57"/>
+ <source>OK</source>
+ <translation>ОК</translation>
+ </message>
+</context>
+<context>
+ <name>ChatRoom</name>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/chat_room.ui" line="14"/>
+ <source>Room Window</source>
+ <translation>Вікно кімнати</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/chat_room.ui" line="40"/>
+ <source>Send Chat Message</source>
+ <translation>Надіслати повідомлення в чат</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/chat_room.ui" line="47"/>
+ <source>Send Message</source>
+ <translation>Надіслати повідомлення</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/chat_room.cpp" line="181"/>
+ <source>Members</source>
+ <translation>Члени</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/chat_room.cpp" line="318"/>
+ <source>%1 has joined</source>
+ <translation>%1 приєднався</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/chat_room.cpp" line="321"/>
+ <source>%1 has left</source>
+ <translation>%1 вийшов</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/chat_room.cpp" line="324"/>
+ <source>%1 has been kicked</source>
+ <translation>%1 вигнано</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/chat_room.cpp" line="327"/>
+ <source>%1 has been banned</source>
+ <translation>%1 заблоковано</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/chat_room.cpp" line="330"/>
+ <source>%1 has been unbanned</source>
+ <translation>%1 розблоковано</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/chat_room.cpp" line="446"/>
+ <source>View Profile</source>
+ <translation>Переглянути профіль</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/chat_room.cpp" line="459"/>
+ <location filename="../../src/yuzu/multiplayer/chat_room.cpp" line="469"/>
+ <source>Block Player</source>
+ <translation>Заблокувати гравця</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/chat_room.cpp" line="470"/>
+ <source>When you block a player, you will no longer receive chat messages from them.&lt;br&gt;&lt;br&gt;Are you sure you would like to block %1?</source>
+ <translation>Коли ви блокуєте гравця, ви більше не отримуватиме від нього повідомлення у чаті. &lt;br&gt;&lt;br&gt;Ви впевнені що бажаєте заблокувати %1?</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/chat_room.cpp" line="483"/>
+ <source>Kick</source>
+ <translation>Вигнати</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/chat_room.cpp" line="484"/>
+ <source>Ban</source>
+ <translation>Заблокувати</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/chat_room.cpp" line="488"/>
+ <source>Kick Player</source>
+ <translation>Вигнати гравця</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/chat_room.cpp" line="489"/>
+ <source>Are you sure you would like to &lt;b&gt;kick&lt;/b&gt; %1?</source>
+ <translation>Ви впевнені що бажаєте &lt;b&gt;вигнати&lt;/b&gt; %1?</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/chat_room.cpp" line="497"/>
+ <source>Ban Player</source>
+ <translation>Заблокувати гравця</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/chat_room.cpp" line="498"/>
+ <source>Are you sure you would like to &lt;b&gt;kick and ban&lt;/b&gt; %1?
+
+This would ban both their forum username and their IP address.</source>
+ <translation>Ви впевнені що бажаєте &lt;b&gt;вигнати і заблокувати&lt;/b&gt; %1?
+
+Ця дія заблокує ім&apos;я користувача на форумі та IP-адресу.</translation>
+ </message>
+</context>
+<context>
+ <name>ClientRoom</name>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/client_room.ui" line="14"/>
+ <source>Room Window</source>
+ <translation>Вікно кімнати</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/client_room.ui" line="27"/>
+ <source>Room Description</source>
+ <translation>Опис кімнати</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/client_room.ui" line="47"/>
+ <source>Moderation...</source>
+ <translation>Модерація...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/client_room.ui" line="57"/>
+ <source>Leave Room</source>
+ <translation>Залишити кімнату</translation>
+ </message>
+</context>
+<context>
+ <name>ClientRoomWindow</name>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/client_room.cpp" line="78"/>
+ <source>Connected</source>
+ <translation>З&apos;єднано</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/client_room.cpp" line="87"/>
+ <source>Disconnected</source>
+ <translation>Роз&apos;єднано</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/client_room.cpp" line="100"/>
+ <source>%1 - %2 (%3/%4 members) - connected</source>
+ <translation>%1 - %2 (%3/%4 члени) - з&apos;єднано</translation>
+ </message>
+</context>
+<context>
+ <name>CompatDB</name>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="20"/>
+ <source>Report Compatibility</source>
+ <translation>Повідомити про сумісність</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="27"/>
+ <location filename="../../src/yuzu/compatdb.ui" line="63"/>
+ <source>Report Game Compatibility</source>
+ <translation>Повідомити про сумісність гри</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="36"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;Should you choose to submit a test case to the &lt;/span&gt;&lt;a href=&quot;https://yuzu-emu.org/game/&quot;&gt;&lt;span style=&quot; font-size:10pt; text-decoration: underline; color:#0000ff;&quot;&gt;yuzu Compatibility List&lt;/span&gt;&lt;/a&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;, The following information will be collected and displayed on the site:&lt;/span&gt;&lt;/p&gt;&lt;ul style=&quot;margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;&quot;&gt;&lt;li style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;Hardware Information (CPU / GPU / Operating System)&lt;/li&gt;&lt;li style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;Which version of yuzu you are running&lt;/li&gt;&lt;li style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;The connected yuzu account&lt;/li&gt;&lt;/ul&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;Якщо ви бажаєте надіслати звіт до &lt;/span&gt;&lt;a href=&quot;https://yuzu-emu.org/game/&quot;&gt;&lt;span style=&quot; font-size:10pt; text-decoration: underline; color:#0000ff;&quot;&gt;списку сумісності yuzu&lt;/span&gt;&lt;/a&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;, наступна інформація буде зібрана та відображена на сайті:&lt;/span&gt;&lt;/p&gt;&lt;ul style=&quot;margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;&quot;&gt;&lt;li style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;Інформація про залізо (ЦП / ГП / Операційна система)&lt;/li&gt;&lt;li style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;Версія yuzu&lt;/li&gt;&lt;li style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;Підключений акаунт yuzu&lt;/li&gt;&lt;/ul&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="72"/>
+ <source>Perfect</source>
+ <translation>Ідеально</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="79"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Game functions flawlessly with no audio or graphical glitches.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Гра працює ідеально, без звукових чи графічних артефактів.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="89"/>
+ <source>Great</source>
+ <translation>Чудово</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="96"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Game functions with minor graphical or audio glitches and is playable from start to finish. May require some workarounds.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Гра працює з невеликими графічними або звуковими артефактами та може бути пройдена від початку до кінця. Можуть знадобитися обхідні шляхи.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="106"/>
+ <source>Okay</source>
+ <translation>Добре</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="113"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Game functions with major graphical or audio glitches, but game is playable from start to finish with workarounds.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Гра працює зі суттєвими графічними або звуковими артефактами, але з обхідними шляхами може бути пройдена.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="123"/>
+ <source>Bad</source>
+ <translation>Погано</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="130"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Game functions, but with major graphical or audio glitches. Unable to progress in specific areas due to glitches even with workarounds.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Гра працює, але з суттєвими графічними чи звуковими артефактами. У деяких місцях неможливо пройти навіть із обхідними шляхами.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="140"/>
+ <source>Intro/Menu</source>
+ <translation>Вступ/Меню</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="147"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Game is completely unplayable due to major graphical or audio glitches. Unable to progress past the Start Screen.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;У гру неможливо грати через серйозні графічні або звукові артефакти. Неможливо просунутися далі за стартове меню.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="157"/>
+ <source>Won&apos;t Boot</source>
+ <translation>Не запускається</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="170"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;The game crashes when attempting to startup.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Гра вилітає при спробі запуску.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="182"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Independent of speed or performance, how well does this game play from start to finish on this version of yuzu?&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Незалежно від швидкості або продуктивності, наскільки добре ця гра працює від початку до кінця у поточній версії yuzu?&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="206"/>
+ <source>Thank you for your submission!</source>
+ <translation>Дякуємо за ваш звіт!</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.cpp" line="58"/>
+ <source>Submitting</source>
+ <translation>Надсилання</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.cpp" line="71"/>
+ <source>Communication error</source>
+ <translation>Помилка з&apos;єднання</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.cpp" line="72"/>
+ <source>An error occurred while sending the Testcase</source>
+ <translation>Сталася помилка під час надсилання звіту</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.cpp" line="74"/>
+ <source>Next</source>
+ <translation>Далі</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureAudio</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="14"/>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="20"/>
+ <source>Audio</source>
+ <translation>Аудіо</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="28"/>
+ <source>Output Engine:</source>
+ <translation>Рушій виводу:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="42"/>
+ <source>Output Device</source>
+ <translation>Пристрій виводу</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="56"/>
+ <source>Input Device</source>
+ <translation>Пристрій вводу</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="84"/>
+ <source>Use global volume</source>
+ <translation>Використовувати загальну гучність</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="89"/>
+ <source>Set volume:</source>
+ <translation>Встановити гучність:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="97"/>
+ <source>Volume:</source>
+ <translation>Гучність</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="142"/>
+ <source>0 %</source>
+ <translation>0 %</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.cpp" line="108"/>
+ <source>%1%</source>
+ <comment>Volume percentage (e.g. 50%)</comment>
+ <translation>%1%</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureCamera</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_camera.ui" line="14"/>
+ <source>Configure Infrared Camera</source>
+ <translation>Налаштування інфрачервоної камери</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_camera.ui" line="26"/>
+ <source>Select where the image of the emulated camera comes from. It may be a virtual camera or a real camera.</source>
+ <translation>Виберіть, звідки береться зображення емульованої камери. Це може бути віртуальна або реальна камера.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_camera.ui" line="52"/>
+ <source>Camera Image Source:</source>
+ <translation>Джерело зображення камери:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_camera.ui" line="71"/>
+ <source>Input device:</source>
+ <translation>Пристрій вводу:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_camera.ui" line="96"/>
+ <source>Preview</source>
+ <translation>Попередній перегляд</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_camera.ui" line="108"/>
+ <source>Resolution: 320*240</source>
+ <translation>Роздільна здатність: 320*240</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_camera.ui" line="115"/>
+ <source>Click to preview</source>
+ <translation>Натисніть для попереднього перегляду</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_camera.ui" line="140"/>
+ <source>Restore Defaults</source>
+ <translation>Значення за замовчуванням</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_camera.cpp" line="135"/>
+ <source>Auto</source>
+ <translation>Авто</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureCpu</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="14"/>
+ <source>Form</source>
+ <translation>Форма</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="17"/>
+ <source>CPU</source>
+ <translation>ЦП</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="25"/>
+ <source>General</source>
+ <translation>Загальні</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="34"/>
+ <source>Accuracy:</source>
+ <translation>Точність:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="42"/>
+ <source>Auto</source>
+ <translation>Авто</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="47"/>
+ <source>Accurate</source>
+ <translation>Точно</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="52"/>
+ <source>Unsafe</source>
+ <translation>Небезпечно</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="57"/>
+ <source>Paranoid (disables most optimizations)</source>
+ <translation>Параноїк (відключає більшість оптимізацій)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="68"/>
+ <source>We recommend setting accuracy to &quot;Auto&quot;.</source>
+ <translation>Ми рекомендуємо встановити точність на &quot;Авто&quot;.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="85"/>
+ <source>Unsafe CPU Optimization Settings</source>
+ <translation>Небезпечні налаштування оптимізації ЦП</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="91"/>
+ <source>These settings reduce accuracy for speed.</source>
+ <translation>Ці налаштування зменшують точність заради швидкості. </translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="101"/>
+ <source>
+ &lt;div&gt;This option improves speed by reducing accuracy of fused-multiply-add instructions on CPUs without native FMA support.&lt;/div&gt;
+ </source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="106"/>
+ <source>Unfuse FMA (improve performance on CPUs without FMA)</source>
+ <translation>Не використовувати FMA (покращує продуктивність на ЦП без FMA)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="113"/>
+ <source>
+ &lt;div&gt;This option improves the speed of some approximate floating-point functions by using less accurate native approximations.&lt;/div&gt;
+ </source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="118"/>
+ <source>Faster FRSQRTE and FRECPE</source>
+ <translation>Прискорені FRSQRTE та FRECPE</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="125"/>
+ <source>
+ &lt;div&gt;This option improves the speed of 32 bits ASIMD floating-point functions by running with incorrect rounding modes.&lt;/div&gt;
+ </source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="130"/>
+ <source>Faster ASIMD instructions (32 bits only)</source>
+ <translation>Швидші інструкції ASIMD (лише 32 біт)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="137"/>
+ <source>
+ &lt;div&gt;This option improves speed by removing NaN checking. Please note this also reduces accuracy of certain floating-point instructions.&lt;/div&gt;
+ </source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="142"/>
+ <source>Inaccurate NaN handling</source>
+ <translation>Неправильна обробка NaN</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="149"/>
+ <source>
+ &lt;div&gt;This option improves speed by eliminating a safety check before every memory read/write in guest. Disabling it may allow a game to read/write the emulator's memory.&lt;/div&gt;
+ </source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="154"/>
+ <source>Disable address space checks</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="161"/>
+ <source>
+ &lt;div&gt;This option improves speed by relying only on the semantics of cmpxchg to ensure safety of exclusive access instructions. Please note this may result in deadlocks and other race conditions.&lt;/div&gt;
+ </source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="166"/>
+ <source>Ignore global monitor</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="191"/>
+ <source>CPU settings are available only when game is not running.</source>
+ <translation>Налаштування ЦП недоступні, поки запущена гра.</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureCpuDebug</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="14"/>
+ <source>Form</source>
+ <translation>Форма</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="17"/>
+ <source>CPU</source>
+ <translation>ЦП</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="25"/>
+ <source>Toggle CPU Optimizations</source>
+ <translation>Увімкнути оптимізації ЦП</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="31"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;For debugging only.&lt;/span&gt;&lt;br/&gt;If you&apos;re not sure what these do, keep all of these enabled. &lt;br/&gt;These settings, when disabled, only take effect when CPU Debugging is enabled. &lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;Тільки для налагодження.&lt;/span&gt;&lt;br/&gt;Якщо ви не впевнені в тому, що вони роблять, залиште всі ці параметри увімкненими. &lt;br/&gt;Коли їх вимкнено, ці параметри набувають чинності лише за увімкненого налагодження ЦП. &lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="41"/>
+ <source>
+ &lt;div style=&quot;white-space: nowrap&quot;&gt;This optimization speeds up memory accesses by the guest program.&lt;/div&gt;
+ &lt;div style=&quot;white-space: nowrap&quot;&gt;Enabling it inlines accesses to PageTable::pointers into emitted code.&lt;/div&gt;
+ &lt;div style=&quot;white-space: nowrap&quot;&gt;Disabling this forces all memory accesses to go through the Memory::Read/Memory::Write functions.&lt;/div&gt;
+ </source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="48"/>
+ <source>Enable inline page tables</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="55"/>
+ <source>
+ &lt;div&gt;This optimization avoids dispatcher lookups by allowing emitted basic blocks to jump directly to other basic blocks if the destination PC is static.&lt;/div&gt;
+ </source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="60"/>
+ <source>Enable block linking</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="67"/>
+ <source>
+ &lt;div&gt;This optimization avoids dispatcher lookups by keeping track potential return addresses of BL instructions. This approximates what happens with a return stack buffer on a real CPU.&lt;/div&gt;
+ </source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="72"/>
+ <source>Enable return stack buffer</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="79"/>
+ <source>
+ &lt;div&gt;Enable a two-tiered dispatch system. A faster dispatcher written in assembly has a small MRU cache of jump destinations is used first. If that fails, dispatch falls back to the slower C++ dispatcher.&lt;/div&gt;
+ </source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="84"/>
+ <source>Enable fast dispatcher</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="91"/>
+ <source>
+ &lt;div&gt;Enables an IR optimization that reduces unnecessary accesses to the CPU context structure.&lt;/div&gt;
+ </source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="96"/>
+ <source>Enable context elimination</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="103"/>
+ <source>
+ &lt;div&gt;Enables IR optimizations that involve constant propagation.&lt;/div&gt;
+ </source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="108"/>
+ <source>Enable constant propagation</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="115"/>
+ <source>
+ &lt;div&gt;Enables miscellaneous IR optimizations.&lt;/div&gt;
+ </source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="120"/>
+ <source>Enable miscellaneous optimizations</source>
+ <translation>Увімкнути різні оптимізації</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="127"/>
+ <source>
+ &lt;div style=&quot;white-space: nowrap&quot;&gt;When enabled, a misalignment is only triggered when an access crosses a page boundary.&lt;/div&gt;
+ &lt;div style=&quot;white-space: nowrap&quot;&gt;When disabled, a misalignment is triggered on all misaligned accesses.&lt;/div&gt;
+ </source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="133"/>
+ <source>Enable misalignment check reduction</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="140"/>
+ <source>
+ &lt;div style=&quot;white-space: nowrap&quot;&gt;This optimization speeds up memory accesses by the guest program.&lt;/div&gt;
+ &lt;div style=&quot;white-space: nowrap&quot;&gt;Enabling it causes guest memory reads/writes to be done directly into memory and make use of Host's MMU.&lt;/div&gt;
+ &lt;div style=&quot;white-space: nowrap&quot;&gt;Disabling this forces all memory accesses to use Software MMU Emulation.&lt;/div&gt;
+ </source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="147"/>
+ <source>Enable Host MMU Emulation (general memory instructions)</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="154"/>
+ <source>
+ &lt;div style=&quot;white-space: nowrap&quot;&gt;This optimization speeds up exclusive memory accesses by the guest program.&lt;/div&gt;
+ &lt;div style=&quot;white-space: nowrap&quot;&gt;Enabling it causes guest exclusive memory reads/writes to be done directly into memory and make use of Host's MMU.&lt;/div&gt;
+ &lt;div style=&quot;white-space: nowrap&quot;&gt;Disabling this forces all exclusive memory accesses to use Software MMU Emulation.&lt;/div&gt;
+ </source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="161"/>
+ <source>Enable Host MMU Emulation (exclusive memory instructions)</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="168"/>
+ <source>
+ &lt;div style=&quot;white-space: nowrap&quot;&gt;This optimization speeds up exclusive memory accesses by the guest program.&lt;/div&gt;
+ &lt;div style=&quot;white-space: nowrap&quot;&gt;Enabling it reduces the overhead of fastmem failure of exclusive memory accesses.&lt;/div&gt;
+ </source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="174"/>
+ <source>Enable recompilation of exclusive memory instructions</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="199"/>
+ <source>CPU settings are available only when game is not running.</source>
+ <translation>Налаштування ЦП доступні тільки тоді, коли гру не запущено.</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureDebug</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="15"/>
+ <source>Debugger</source>
+ <translation>Налагоджувач</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="23"/>
+ <source>Enable GDB Stub</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="43"/>
+ <source>Port:</source>
+ <translation>Порт:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="67"/>
+ <source>Logging</source>
+ <translation>Журналювання</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="75"/>
+ <source>Global Log Filter</source>
+ <translation>Глобальний фільтр журналів</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="87"/>
+ <source>Show Log in Console</source>
+ <translation>Показувати журнал у консолі</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="94"/>
+ <source>Open Log Location</source>
+ <translation>Відкрити папку для журналів</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="104"/>
+ <source>When checked, the max size of the log increases from 100 MB to 1 GB</source>
+ <translation>Якщо увімкнено, максимальний розмір журналу збільшується зі 100 МБ до 1 ГБ</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="107"/>
+ <source>Enable Extended Logging**</source>
+ <translation>Увімкнути розширене ведення журналу**</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="117"/>
+ <source>Homebrew</source>
+ <translation>Homebrew</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="125"/>
+ <source>Arguments String</source>
+ <translation>Рядок аргументів</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="140"/>
+ <source>Graphics</source>
+ <translation>Графіка</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="149"/>
+ <source>When checked, the graphics API enters a slower debugging mode</source>
+ <translation>Якщо увімкнено, графічний API переходить у повільніший режим налагодження</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="152"/>
+ <source>Enable Graphics Debugging</source>
+ <translation>Увімкнути налагодження графіки</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="159"/>
+ <source>When checked, it enables Nsight Aftermath crash dumps</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="162"/>
+ <source>Enable Nsight Aftermath</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="172"/>
+ <source>When checked, it will dump all the original assembler shaders from the disk shader cache or game as found</source>
+ <translation>Якщо ввімкнено, буде дампити всі оригінальні шейдери асемблера з кешу шейдерів на диску або гри як знайдені</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="175"/>
+ <source>Dump Game Shaders</source>
+ <translation>Дамп ігрових шейдерів</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="185"/>
+ <source>When checked, it will dump all the macro programs of the GPU</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="188"/>
+ <source>Dump Maxwell Macros</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="198"/>
+ <source>When checked, it disables the macro Just In Time compiler. Enabling this makes games run slower</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="201"/>
+ <source>Disable Macro JIT</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="208"/>
+ <source>When checked, yuzu will log statistics about the compiled pipeline cache</source>
+ <translation>Якщо увімкнено, yuzu записуватиме статистику про скомпільований кеш конвеєра</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="211"/>
+ <source>Enable Shader Feedback</source>
+ <translation>Увімкнути зворотний зв&apos;язок про шейдери</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="218"/>
+ <source>When checked, it executes shaders without loop logic changes</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="221"/>
+ <source>Disable Loop safety checks</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="231"/>
+ <source>Debugging</source>
+ <translation>Налагодження</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="237"/>
+ <source>Enable Verbose Reporting Services**</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="244"/>
+ <source>Enable FS Access Log</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="251"/>
+ <source>Enable this to output the latest generated audio command list to the console. Only affects games using the audio renderer.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="254"/>
+ <source>Dump Audio Commands To Console**</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="261"/>
+ <source>Create Minidump After Crash</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="271"/>
+ <source>Advanced</source>
+ <translation>Розширені</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="277"/>
+ <source>Kiosk (Quest) Mode</source>
+ <translation>Режим кіоску (Квест)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="284"/>
+ <source>Enable CPU Debugging</source>
+ <translation>Увімкнути налагодження ЦП</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="291"/>
+ <source>Enable Debug Asserts</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="298"/>
+ <source>Enable Auto-Stub**</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="305"/>
+ <source>Enable All Controller Types</source>
+ <translation>Увімкнути всі типи контролерів</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="312"/>
+ <source>Disable Web Applet</source>
+ <translation>Вимкнути веб-аплет</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="319"/>
+ <source>Enables yuzu to check for a working Vulkan environment when the program starts up. Disable this if this is causing issues with external programs seeing yuzu.</source>
+ <translation>Дозволяє yuzu перевіряти наявність робочого середовища Vulkan під час запуску програми. Вимкніть цю опцію, якщо це викликає проблеми з тим, що зовнішні програми бачать yuzu.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="322"/>
+ <source>Perform Startup Vulkan Check</source>
+ <translation>Виконувати перевірку Vulkan під час запуску</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="337"/>
+ <source>**This will be reset automatically when yuzu closes.</source>
+ <translation>**Це буде автоматично скинуто після закриття yuzu.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.cpp" line="35"/>
+ <source>Restart Required</source>
+ <translation>Потрібен перезапуск</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.cpp" line="36"/>
+ <source>yuzu is required to restart in order to apply this setting.</source>
+ <translation>yuzu потрібно перезапустити, щоб застосувати це налаштування.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.cpp" line="86"/>
+ <source>Web applet not compiled</source>
+ <translation>Веб-аплет не скомпільовано</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.cpp" line="93"/>
+ <source>MiniDump creation not compiled</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>ConfigureDebugController</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug_controller.ui" line="14"/>
+ <source>Configure Debug Controller</source>
+ <translation>Налаштування налагоджувального контролера</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug_controller.ui" line="40"/>
+ <source>Clear</source>
+ <translation>Очистити</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug_controller.ui" line="47"/>
+ <source>Defaults</source>
+ <translation>За замовчуванням</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureDebugTab</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug_tab.ui" line="14"/>
+ <source>Form</source>
+ <translation>Форма</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug_tab.ui" line="17"/>
+ <location filename="../../src/yuzu/configuration/configure_debug_tab.cpp" line="16"/>
+ <source>Debug</source>
+ <translation>Налагодження</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug_tab.cpp" line="17"/>
+ <source>CPU</source>
+ <translation>ЦП</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureDialog</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="20"/>
+ <source>yuzu Configuration</source>
+ <translation>Налаштування yuzu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="52"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="156"/>
+ <source>Audio</source>
+ <translation>Аудіо</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="53"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="154"/>
+ <source>CPU</source>
+ <translation>ЦП</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="54"/>
+ <source>Debug</source>
+ <translation>Налагодження</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="55"/>
+ <source>Filesystem</source>
+ <translation>Файлова система</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="56"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="150"/>
+ <source>General</source>
+ <translation>Загальні</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="57"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="155"/>
+ <source>Graphics</source>
+ <translation>Графіка</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="58"/>
+ <source>GraphicsAdvanced</source>
+ <translation>ГрафікаРозширені</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="59"/>
+ <source>Hotkeys</source>
+ <translation>Гарячі клавіші</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="60"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="157"/>
+ <source>Controls</source>
+ <translation>Керування</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="61"/>
+ <source>Profiles</source>
+ <translation>Профілі</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="62"/>
+ <source>Network</source>
+ <translation>Мережа</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="63"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="152"/>
+ <source>System</source>
+ <translation>Система</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="64"/>
+ <source>Game List</source>
+ <translation>Список ігор</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="65"/>
+ <source>Web</source>
+ <translation>Мережа</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureFilesystem</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="14"/>
+ <source>Form</source>
+ <translation>Форма</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="17"/>
+ <source>Filesystem</source>
+ <translation>Файлова система</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="25"/>
+ <source>Storage Directories</source>
+ <translation>Каталоги зберігання</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="31"/>
+ <source>NAND</source>
+ <translation>NAND</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="38"/>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="58"/>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="114"/>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="136"/>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="143"/>
+ <source>...</source>
+ <translation>...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="51"/>
+ <source>SD Card</source>
+ <translation>SD карта</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="84"/>
+ <source>Gamecard</source>
+ <translation>Картридж</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="90"/>
+ <source>Path</source>
+ <translation>Шлях</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="100"/>
+ <source>Inserted</source>
+ <translation>Вставлений</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="107"/>
+ <source>Current Game</source>
+ <translation>Поточна гра</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="124"/>
+ <source>Patch Manager</source>
+ <translation>Керування патчами</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="152"/>
+ <source>Dump Decompressed NSOs</source>
+ <translation>Дамп розпакованих NSO</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="159"/>
+ <source>Dump ExeFS</source>
+ <translation>Дамп ExeFS</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="168"/>
+ <source>Mod Load Root</source>
+ <translation>Папка з модами</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="175"/>
+ <source>Dump Root</source>
+ <translation>Корінь дампу</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="201"/>
+ <source>Caching</source>
+ <translation>Кешування</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="209"/>
+ <source>Cache Game List Metadata</source>
+ <translation>Кешувати метадані списку ігор</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="216"/>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="131"/>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="135"/>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="140"/>
+ <source>Reset Metadata Cache</source>
+ <translation>Скинути кеш метаданих</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="93"/>
+ <source>Select Emulated NAND Directory...</source>
+ <translation>Виберіть папку для емульованого NAND...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="96"/>
+ <source>Select Emulated SD Directory...</source>
+ <translation>Виберіть папку для емульованого SD...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="99"/>
+ <source>Select Gamecard Path...</source>
+ <translation>Оберіть папку для картриджів...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="102"/>
+ <source>Select Dump Directory...</source>
+ <translation>Оберіть папку для дампів...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="105"/>
+ <source>Select Mod Load Directory...</source>
+ <translation>Оберіть папку для модів...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="132"/>
+ <source>The metadata cache is already empty.</source>
+ <translation>Кеш метаданих вже порожній.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="136"/>
+ <source>The operation completed successfully.</source>
+ <translation>Операція завершилася успішно.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="141"/>
+ <source>The metadata cache couldn&apos;t be deleted. It might be in use or non-existent.</source>
+ <translation>Кеш метаданих не можна видалити. Можливо, він використовується або відсутній.</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureGeneral</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="14"/>
+ <source>Form</source>
+ <translation>Форма</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="17"/>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="25"/>
+ <source>General</source>
+ <translation>Загальні</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="35"/>
+ <source>Limit Speed Percent</source>
+ <translation>Обмеження відсотка швидкості</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="42"/>
+ <source>%</source>
+ <translation>%</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="60"/>
+ <source>Multicore CPU Emulation</source>
+ <translation>Багатоядерна емуляція ЦП</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="67"/>
+ <source>Extended memory layout (6GB DRAM)</source>
+ <translation>Розширене компонування пам&apos;яті (6 ГБ DRAM)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="74"/>
+ <source>Confirm exit while emulation is running</source>
+ <translation>Підтверджувати вихід під час емуляції</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="81"/>
+ <source>Prompt for user on game boot</source>
+ <translation>Запитувати користувача під час запуску гри</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="88"/>
+ <source>Pause emulation when in background</source>
+ <translation>Призупиняти емуляцію у фоновому режимі</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="95"/>
+ <source>Mute audio when in background</source>
+ <translation>Приглушити звук у фоновому режимі</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="102"/>
+ <source>Hide mouse on inactivity</source>
+ <translation>Приховування миші при бездіяльності</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="144"/>
+ <source>Reset All Settings</source>
+ <translation>Скинути всі налаштування</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.cpp" line="68"/>
+ <source>yuzu</source>
+ <translation>yuzu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.cpp" line="69"/>
+ <source>This reset all settings and remove all per-game configurations. This will not delete game directories, profiles, or input profiles. Proceed?</source>
+ <translation>Це скине всі налаштування і видалить усі конфігурації під окремі ігри. При цьому не будуть видалені шляхи до ігор, профілів або профілів вводу. Продовжити?</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureGraphics</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="14"/>
+ <source>Form</source>
+ <translation>Форма</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="17"/>
+ <source>Graphics</source>
+ <translation>Графіка</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="25"/>
+ <source>API Settings</source>
+ <translation>Налаштування API</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="64"/>
+ <source>Shader Backend:</source>
+ <translation>Бекенд шейдерів:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="92"/>
+ <source>Device:</source>
+ <translation>Пристрій:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="120"/>
+ <source>API:</source>
+ <translation>API:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="156"/>
+ <source>Graphics Settings</source>
+ <translation>Налаштування графіки</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="162"/>
+ <source>Use disk pipeline cache</source>
+ <translation>Використовувати кеш конвеєра на диску</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="169"/>
+ <source>Use asynchronous GPU emulation</source>
+ <translation>Використовувати асинхронну емуляцію ГП</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="176"/>
+ <source>Accelerate ASTC texture decoding</source>
+ <translation>Прискорення декодування текстур ASTC</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="198"/>
+ <source>NVDEC emulation:</source>
+ <translation>Емуляція NVDEC:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="206"/>
+ <source>No Video Output</source>
+ <translation>Відсутність відеовиходу</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="211"/>
+ <source>CPU Video Decoding</source>
+ <translation>Декодування відео на ЦП</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="216"/>
+ <source>GPU Video Decoding (Default)</source>
+ <translation>Декодування відео на ГП (за замовчуванням)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="242"/>
+ <source>Fullscreen Mode:</source>
+ <translation>Повноекранний режим:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="250"/>
+ <source>Borderless Windowed</source>
+ <translation>Вікно без рамок</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="255"/>
+ <source>Exclusive Fullscreen</source>
+ <translation>Ексклюзивний повноекранний</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="281"/>
+ <source>Aspect Ratio:</source>
+ <translation>Співвідношення сторін:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="289"/>
+ <source>Default (16:9)</source>
+ <translation>За замовчуванням (16:9)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="294"/>
+ <source>Force 4:3</source>
+ <translation>Змусити 4:3</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="299"/>
+ <source>Force 21:9</source>
+ <translation>Змусити 21:9</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="304"/>
+ <source>Force 16:10</source>
+ <translation>Змусити 16:10</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="309"/>
+ <source>Stretch to Window</source>
+ <translation>Розтягнути до вікна</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="335"/>
+ <source>Resolution:</source>
+ <translation>Роздільна здатність:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="343"/>
+ <source>0.5X (360p/540p) [EXPERIMENTAL]</source>
+ <translation>0.5X (360p/540p) [ЕКСПЕРИМЕНТАЛЬНЕ]</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="348"/>
+ <source>0.75X (540p/810p) [EXPERIMENTAL]</source>
+ <translation>0.75X (540p/810p) [ЕКСПЕРИМЕНТАЛЬНЕ]</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="353"/>
+ <source>1X (720p/1080p)</source>
+ <translation>1X (720p/1080p)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="358"/>
+ <source>2X (1440p/2160p)</source>
+ <translation>2X (1440p/2160p)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="363"/>
+ <source>3X (2160p/3240p)</source>
+ <translation>3X (2160p/3240p)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="368"/>
+ <source>4X (2880p/4320p)</source>
+ <translation>4X (2880p/4320p)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="373"/>
+ <source>5X (3600p/5400p)</source>
+ <translation>5X (3600p/5400p)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="378"/>
+ <source>6X (4320p/6480p)</source>
+ <translation>6X (4320p/6480p)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="404"/>
+ <source>Window Adapting Filter:</source>
+ <translation>Фільтр адаптації вікна:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="412"/>
+ <source>Nearest Neighbor</source>
+ <translation>Найближчий сусід</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="417"/>
+ <source>Bilinear</source>
+ <translation>Білінійне</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="422"/>
+ <source>Bicubic</source>
+ <translation>Бікубічне</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="427"/>
+ <source>Gaussian</source>
+ <translation>Гауса</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="432"/>
+ <source>ScaleForce</source>
+ <translation>ScaleForce</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="437"/>
+ <source>AMD FidelityFX™️ Super Resolution (Vulkan Only)</source>
+ <translation>AMD FidelityFX™️ Super Resolution (Лише для Vulkan)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="463"/>
+ <source>Anti-Aliasing Method:</source>
+ <translation>Метод згладжування:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="471"/>
+ <source>None</source>
+ <translation>Вимкнено</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="476"/>
+ <source>FXAA</source>
+ <translation>FXAA</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="511"/>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="521"/>
+ <source>Use global background color</source>
+ <translation>Використовувати глобальний фоновий колір</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="526"/>
+ <source>Set background color:</source>
+ <translation>Встановити фоновий колір:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="534"/>
+ <source>Background Color:</source>
+ <translation>Фоновий колір:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.cpp" line="33"/>
+ <source>GLASM (Assembly Shaders, NVIDIA Only)</source>
+ <translation>GLASM (асемблерні шейдери, лише для NVIDIA)</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureGraphicsAdvanced</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="14"/>
+ <source>Form</source>
+ <translation>Форма</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="17"/>
+ <source>Advanced</source>
+ <translation>Розширені</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="25"/>
+ <source>Advanced Graphics Settings</source>
+ <translation>Розширені налаштування графіки</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="46"/>
+ <source>Accuracy Level:</source>
+ <translation>Рівень точності:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="75"/>
+ <source>VSync prevents the screen from tearing, but some graphics cards have lower performance with VSync enabled. Keep it enabled if you don&apos;t notice a performance difference.</source>
+ <translation>Вертикальна синхронізація запобігає розривам екрана, але деякі відеокарти мають нижчу продуктивність при вертикальній синхронізації. Залишайте увімкненим, якщо ви не помічаєте різниці в продуктивності.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="78"/>
+ <source>Use VSync</source>
+ <translation>Використувати вертикальну сінхронізацію</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="85"/>
+ <source>Enables asynchronous shader compilation, which may reduce shader stutter. This feature is experimental.</source>
+ <translation>Вмикає асинхронну компіляцію шейдерів, що зменшить зависання через шейдери. Функція є експериментальною.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="88"/>
+ <source>Use asynchronous shader building (Hack)</source>
+ <translation>Використовувати асинхронну побудову шейдерів (хак)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="95"/>
+ <source>Enables Fast GPU Time. This option will force most games to run at their highest native resolution.</source>
+ <translation>Вмикає функцію Fast GPU Time. Цей параметр змусить більшість ігор працювати в максимальній рідній роздільній здатності.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="98"/>
+ <source>Use Fast GPU Time (Hack)</source>
+ <translation>Увімкнути Fast GPU Time (Хак)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="105"/>
+ <source>Enables pessimistic buffer flushes. This option will force unmodified buffers to be flushed, which can cost performance.</source>
+ <translation>Вмикає песимістичне очищення буферів. Ця опція змушує промивати немодифіковані буфери, що може знизити продуктивність.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="108"/>
+ <source>Use pessimistic buffer flushes (Hack)</source>
+ <translation>Використовувати песимістичне очищення буферів (Хак)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="130"/>
+ <source>Anisotropic Filtering:</source>
+ <translation>Анізотропна фільтрація:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="138"/>
+ <source>Automatic</source>
+ <translation>Автоматично</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="143"/>
+ <source>Default</source>
+ <translation>За замовчуванням</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="148"/>
+ <source>2x</source>
+ <translation>2x</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="153"/>
+ <source>4x</source>
+ <translation>4x</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="158"/>
+ <source>8x</source>
+ <translation>8x</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="163"/>
+ <source>16x</source>
+ <translation>16x</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureHotkeys</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.ui" line="14"/>
+ <source>Hotkey Settings</source>
+ <translation>Налаштування гарячих клавіш</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.ui" line="17"/>
+ <source>Hotkeys</source>
+ <translation>Гарячі клавіші</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.ui" line="25"/>
+ <source>Double-click on a binding to change it.</source>
+ <translation>Натисніть двічі на прив&apos;язці, щоб змінити її.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.ui" line="45"/>
+ <source>Clear All</source>
+ <translation>Очистити все</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.ui" line="52"/>
+ <source>Restore Defaults</source>
+ <translation>Відновити значення за замовчуванням.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="98"/>
+ <source>Action</source>
+ <translation>Дія</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="98"/>
+ <source>Hotkey</source>
+ <translation>Гаряча клавіша</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="98"/>
+ <source>Controller Hotkey</source>
+ <translation>Гаряча клавіша контролера</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="138"/>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="164"/>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="379"/>
+ <source>Conflicting Key Sequence</source>
+ <translation>Конфліктуюча комбінація клавіш</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="139"/>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="165"/>
+ <source>The entered key sequence is already assigned to: %1</source>
+ <translation>Введена комбінація вже призначена до: %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="158"/>
+ <source>Home+%1</source>
+ <translation>Home+%1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="172"/>
+ <source>[waiting]</source>
+ <translation>[очікування]</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="242"/>
+ <source>Invalid</source>
+ <translation>Неприпустимо</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="343"/>
+ <source>Restore Default</source>
+ <translation>Відновити значення за замовчуванням</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="344"/>
+ <source>Clear</source>
+ <translation>Очистити</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="365"/>
+ <source>Conflicting Button Sequence</source>
+ <translation>Конфліктуюче поєднання кнопок</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="366"/>
+ <source>The default button sequence is already assigned to: %1</source>
+ <translation>Типова комбінація кнопок вже призначена до: %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="380"/>
+ <source>The default key sequence is already assigned to: %1</source>
+ <translation>Типова комбінація клавіш вже призначена до: %1</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureInput</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="14"/>
+ <source>ConfigureInput</source>
+ <translation>НалаштуванняВводу</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="39"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="42"/>
+ <source>Player 1</source>
+ <translation>Гравець 1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="47"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="50"/>
+ <source>Player 2</source>
+ <translation>Гравець 2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="55"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="58"/>
+ <source>Player 3</source>
+ <translation>Гравець 3</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="63"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="66"/>
+ <source>Player 4</source>
+ <translation>Гравець 4</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="71"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="74"/>
+ <source>Player 5</source>
+ <translation>Гравець 5</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="79"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="82"/>
+ <source>Player 6</source>
+ <translation>Гравець 6</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="87"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="90"/>
+ <source>Player 7</source>
+ <translation>Гравець 7</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="95"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="98"/>
+ <source>Player 8</source>
+ <translation>Гравець 8</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="103"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="106"/>
+ <source>Advanced</source>
+ <translation>Розширені</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="138"/>
+ <source>Console Mode</source>
+ <translation>Режим консолі</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="159"/>
+ <source>Docked</source>
+ <translation>У док-станції</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="169"/>
+ <source>Handheld</source>
+ <translation>Портативний</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="179"/>
+ <source>Vibration</source>
+ <translation>Вібрація</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="215"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="261"/>
+ <source>Configure</source>
+ <translation>Налаштувати</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="225"/>
+ <source>Motion</source>
+ <translation>Рух</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="296"/>
+ <source>Controllers</source>
+ <translation>Контролери</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="324"/>
+ <source>1</source>
+ <translation>1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="365"/>
+ <source>2</source>
+ <translation>2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="375"/>
+ <source>3</source>
+ <translation>3</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="385"/>
+ <source>4</source>
+ <translation>4</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="395"/>
+ <source>5</source>
+ <translation>5</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="405"/>
+ <source>6</source>
+ <translation>6</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="415"/>
+ <source>7</source>
+ <translation>7</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="425"/>
+ <source>8</source>
+ <translation>8</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="435"/>
+ <source>Connected</source>
+ <translation>З&apos;єднано</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="494"/>
+ <source>Defaults</source>
+ <translation>За замовчуванням</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="537"/>
+ <source>Clear</source>
+ <translation>Очистити</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureInputAdvanced</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="14"/>
+ <source>Configure Input</source>
+ <translation>Налаштування вводу</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="74"/>
+ <source>Joycon Colors</source>
+ <translation>Кольори Joy-Con&apos;ів</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="125"/>
+ <source>Player 1</source>
+ <translation>Гравець 1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="164"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="450"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="754"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1040"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1365"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1651"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1955"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2241"/>
+ <source>L Body</source>
+ <translation>Лівий контролер</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="219"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="505"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="809"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1095"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1420"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1706"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2010"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2296"/>
+ <source>L Button</source>
+ <translation>Кнопка L</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="295"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="581"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="885"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1171"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1496"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1782"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2086"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2372"/>
+ <source>R Body</source>
+ <translation>Правий контролер</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="350"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="636"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="940"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1226"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1551"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1837"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2141"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2427"/>
+ <source>R Button</source>
+ <translation>Кнопка R</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="411"/>
+ <source>Player 2</source>
+ <translation>Гравець 2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="715"/>
+ <source>Player 3</source>
+ <translation>Гравець 3</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1001"/>
+ <source>Player 4</source>
+ <translation>Гравець 4</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1326"/>
+ <source>Player 5</source>
+ <translation>Гравець 5</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1612"/>
+ <source>Player 6</source>
+ <translation>Гравець 6</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1916"/>
+ <source>Player 7</source>
+ <translation>Гравець 7</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2202"/>
+ <source>Player 8</source>
+ <translation>Гравець 8</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2533"/>
+ <source>Emulated Devices</source>
+ <translation>Емульовані пристрої</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2545"/>
+ <source>Keyboard</source>
+ <translation>Клавіатура</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2558"/>
+ <source>Mouse</source>
+ <translation>Миша</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2565"/>
+ <source>Touchscreen</source>
+ <translation>Сенсорний екран</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2588"/>
+ <source>Advanced</source>
+ <translation>Розширені</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2595"/>
+ <source>Debug Controller</source>
+ <translation>Налагоджувальний контролер</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2602"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2616"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2630"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2743"/>
+ <source>Configure</source>
+ <translation>Налаштувати</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2609"/>
+ <source>Ring Controller</source>
+ <translation>Контролер Ring</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2623"/>
+ <source>Infrared Camera</source>
+ <translation>Інфрачервона камера</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2640"/>
+ <source>Other</source>
+ <translation>Інше</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2652"/>
+ <source>Emulate Analog with Keyboard Input</source>
+ <translation>Емуляція аналогового вводу з клавіатури</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2659"/>
+ <source>Requires restarting yuzu</source>
+ <translation>Потребує перезапуску yuzu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2668"/>
+ <source>Enable XInput 8 player support (disables web applet)</source>
+ <translation>Увімкнути підтримку 8-ми гравців на XInput (відключає веб-аплет)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2681"/>
+ <source>Enable UDP controllers (not needed for motion)</source>
+ <translation>Увімкнути UDP контролери (не обов&apos;язково для руху)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2694"/>
+ <source>Controller navigation</source>
+ <translation>Навігація контролера</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2707"/>
+ <source>Enable mouse panning</source>
+ <translation>Увімкнути панорамування миші</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2714"/>
+ <source>Mouse sensitivity</source>
+ <translation>Чутливість миші</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2720"/>
+ <source>%</source>
+ <translation>%</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2736"/>
+ <source>Motion / Touch</source>
+ <translation>Рух і сенсор</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureInputPlayer</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="14"/>
+ <source>Configure Input</source>
+ <translation>Налаштування вводу</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="63"/>
+ <source>Connect Controller</source>
+ <translation>Підключити контролер</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="100"/>
+ <source>Input Device</source>
+ <translation>Пристрій вводу</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="156"/>
+ <source>Profile</source>
+ <translation>Профіль</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="196"/>
+ <source>Save</source>
+ <translation>Зберегти</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="212"/>
+ <source>New</source>
+ <translation>Новий</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="228"/>
+ <source>Delete</source>
+ <translation>Видалити</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="291"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="1291"/>
+ <source>Left Stick</source>
+ <translation>Лівий міні-джойстик</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="349"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="391"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="925"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="964"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2583"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2622"/>
+ <source>Up</source>
+ <translation>Вгору</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="422"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="461"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="995"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1034"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2069"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2653"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2692"/>
+ <source>Left</source>
+ <translation>Вліво</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="471"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="510"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1044"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1083"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2118"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2702"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2741"/>
+ <source>Right</source>
+ <translation>Вправо</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="553"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="592"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1126"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1165"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2784"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2823"/>
+ <source>Down</source>
+ <translation>Вниз</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="623"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="662"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2854"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2893"/>
+ <source>Pressed</source>
+ <translation>Натиснення</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="672"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="711"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2903"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2942"/>
+ <source>Modifier</source>
+ <translation>Модифікатор</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="721"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2952"/>
+ <source>Range</source>
+ <translation>Діапазон</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="754"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2985"/>
+ <source>%</source>
+ <translation>%</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="797"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="3025"/>
+ <source>Deadzone: 0%</source>
+ <translation>Мертва зона: 0%</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="821"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="3049"/>
+ <source>Modifier Range: 0%</source>
+ <translation>Діапазон модифікатора: 0%</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="867"/>
+ <source>D-Pad</source>
+ <translation>Кнопки напрямків</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1251"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1290"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="1296"/>
+ <source>L</source>
+ <translation>L</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1306"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1345"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="1288"/>
+ <source>ZL</source>
+ <translation>ZL</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1426"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1465"/>
+ <source>Minus</source>
+ <translation>Мінус</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1475"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1514"/>
+ <source>Capture</source>
+ <translation>Захоплення</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1545"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1584"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="1287"/>
+ <source>Plus</source>
+ <translation>Плюс</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1594"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1633"/>
+ <source>Home</source>
+ <translation>Home</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1698"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1737"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="1290"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="1297"/>
+ <source>R</source>
+ <translation>R</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1753"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1792"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="1289"/>
+ <source>ZR</source>
+ <translation>ZR</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1873"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1912"/>
+ <source>SL</source>
+ <translation>SL</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1922"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1961"/>
+ <source>SR</source>
+ <translation>SR</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2030"/>
+ <source>Motion 1</source>
+ <translation>Рух 1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2079"/>
+ <source>Motion 2</source>
+ <translation>Рух 2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2170"/>
+ <source>Face Buttons</source>
+ <translation>Кнопки A/B/X/Y</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2228"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2267"/>
+ <source>X</source>
+ <translation>X</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2298"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2337"/>
+ <source>Y</source>
+ <translation>Y</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2347"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2386"/>
+ <source>A</source>
+ <translation>A</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2429"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2468"/>
+ <source>B</source>
+ <translation>B</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2516"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="1292"/>
+ <source>Right Stick</source>
+ <translation>Правий міні-джойстик</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="361"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="434"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="529"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="624"/>
+ <source>Clear</source>
+ <translation>Очистити</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="363"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="436"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="533"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="552"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="626"/>
+ <source>[not set]</source>
+ <translation>[не задано]</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="366"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="638"/>
+ <source>Invert button</source>
+ <translation>Інвертувати кнопку</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="372"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="629"/>
+ <source>Toggle button</source>
+ <translation>Переключити кнопку</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="380"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="580"/>
+ <source>Invert axis</source>
+ <translation>Інвертувати осі</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="386"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="390"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="443"/>
+ <source>Set threshold</source>
+ <translation>Встановити поріг</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="390"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="443"/>
+ <source>Choose a value between 0% and 100%</source>
+ <translation>Оберіть значення між 0% і 100%</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="402"/>
+ <source>Toggle axis</source>
+ <translation>Переключити осі</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="439"/>
+ <source>Set gyro threshold</source>
+ <translation>Встановити поріг гіроскопа</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="485"/>
+ <source>Map Analog Stick</source>
+ <translation>Задати аналоговий міні-джойстик</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="486"/>
+ <source>After pressing OK, first move your joystick horizontally, and then vertically.
+To invert the axes, first move your joystick vertically, and then horizontally.</source>
+ <translation>Після натискання на ОК, рухайте ваш міні-джойстик горизонтально, а потім вертикально.
+Щоб інвертувати осі, спочатку рухайте ваш міні-джойстик вертикально, а потім горизонтально.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="554"/>
+ <source>Center axis</source>
+ <translation>Центрувати осі</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="662"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="1016"/>
+ <source>Deadzone: %1%</source>
+ <translation>Мертва зона: %1%</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="671"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="1021"/>
+ <source>Modifier Range: %1%</source>
+ <translation>Діапазон модифікатора: %1%</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="697"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="1046"/>
+ <source>Pro Controller</source>
+ <translation>Контролер Pro</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="1050"/>
+ <source>Dual Joycons</source>
+ <translation>Подвійні Joy-Con&apos;и</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="1054"/>
+ <source>Left Joycon</source>
+ <translation>Лівий Joy-Con</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="1058"/>
+ <source>Right Joycon</source>
+ <translation>Правий Joy-Con</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="1062"/>
+ <source>Handheld</source>
+ <translation>Портативний</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="1066"/>
+ <source>GameCube Controller</source>
+ <translation>Контролер GameCube</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="1075"/>
+ <source>Poke Ball Plus</source>
+ <translation>Poke Ball Plus</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="1079"/>
+ <source>NES Controller</source>
+ <translation>Контролер NES</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="1083"/>
+ <source>SNES Controller</source>
+ <translation>Контролер SNES</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="1087"/>
+ <source>N64 Controller</source>
+ <translation>Контролер N64</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="1091"/>
+ <source>Sega Genesis</source>
+ <translation>Sega Genesis</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="1295"/>
+ <source>Start / Pause</source>
+ <translation>Старт / Пауза</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="1298"/>
+ <source>Z</source>
+ <translation>Z</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="1299"/>
+ <source>Control Stick</source>
+ <translation>Міні-джойстик керування</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="1300"/>
+ <source>C-Stick</source>
+ <translation>C-Джойстик</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="1401"/>
+ <source>Shake!</source>
+ <translation>Потрусіть!</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="1403"/>
+ <source>[waiting]</source>
+ <translation>[очікування]</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="1485"/>
+ <source>New Profile</source>
+ <translation>Новий профіль</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="1485"/>
+ <source>Enter a profile name:</source>
+ <translation>Введіть ім&apos;я профілю:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="1493"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="1501"/>
+ <source>Create Input Profile</source>
+ <translation>Створити профіль контролю</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="1494"/>
+ <source>The given profile name is not valid!</source>
+ <translation>Задане ім&apos;я профілю недійсне!</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="1502"/>
+ <source>Failed to create the input profile &quot;%1&quot;</source>
+ <translation>Не вдалося створити профіль контролю &quot;%1&quot;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="1522"/>
+ <source>Delete Input Profile</source>
+ <translation>Видалити профіль контролю</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="1523"/>
+ <source>Failed to delete the input profile &quot;%1&quot;</source>
+ <translation>Не вдалося видалити профіль контролю &quot;%1&quot;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="1545"/>
+ <source>Load Input Profile</source>
+ <translation>Завантажити профіль контролю</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="1546"/>
+ <source>Failed to load the input profile &quot;%1&quot;</source>
+ <translation>Не вдалося завантажити профіль контролю &quot;%1&quot;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="1565"/>
+ <source>Save Input Profile</source>
+ <translation>Зберегти профіль контролю</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="1566"/>
+ <source>Failed to save the input profile &quot;%1&quot;</source>
+ <translation>Не вдалося зберегти профіль контролю &quot;%1&quot;</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureInputProfileDialog</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_profile_dialog.ui" line="14"/>
+ <source>Create Input Profile</source>
+ <translation>Створити профіль контролю</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_profile_dialog.ui" line="40"/>
+ <source>Clear</source>
+ <translation>Очистити</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_profile_dialog.ui" line="47"/>
+ <source>Defaults</source>
+ <translation>За замовчуванням</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureMotionTouch</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="6"/>
+ <source>Configure Motion / Touch</source>
+ <translation>Налаштування руху та сенсора</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="15"/>
+ <source>Touch</source>
+ <translation>Сенсор</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="23"/>
+ <source>UDP Calibration:</source>
+ <translation>Калібрація UDP:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="30"/>
+ <source>(100, 50) - (1800, 850)</source>
+ <translation>(100, 50) - (1800, 850)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="46"/>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="73"/>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="243"/>
+ <source>Configure</source>
+ <translation>Налаштувати</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="57"/>
+ <source>Touch from button profile:</source>
+ <translation>Торкніться з профілю кнопки:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="85"/>
+ <source>CemuhookUDP Config</source>
+ <translation>Налаштування CemuhookUDP</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="91"/>
+ <source>You may use any Cemuhook compatible UDP input source to provide motion and touch input.</source>
+ <translation>Ви можете використовувати будь-яке сумісне з Cemuhook джерело UDP сигналу для руху і сенсора.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="134"/>
+ <source>Server:</source>
+ <translation>Сервер:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="161"/>
+ <source>Port:</source>
+ <translation>Порт:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="188"/>
+ <source>Learn More</source>
+ <translation>Дізнатися більше</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="201"/>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="266"/>
+ <source>Test</source>
+ <translation>Тест</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="214"/>
+ <source>Add Server</source>
+ <translation>Додати сервер</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="247"/>
+ <source>Remove Server</source>
+ <translation>Видалити сервер</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="87"/>
+ <source>&lt;a href=&apos;https://yuzu-emu.org/wiki/using-a-controller-or-android-phone-for-motion-or-touch-input&apos;&gt;&lt;span style=&quot;text-decoration: underline; color:#039be5;&quot;&gt;Learn More&lt;/span&gt;&lt;/a&gt;</source>
+ <translation>&lt;a href=&apos;https://yuzu-emu.org/wiki/using-a-controller-or-android-phone-for-motion-or-touch-input&apos;&gt;&lt;span style=&quot;text-decoration: underline; color:#039be5;&quot;&gt;Дізнатися більше&lt;/span&gt;&lt;/a&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="169"/>
+ <source>%1:%2</source>
+ <translation>%1:%2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="174"/>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="178"/>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="182"/>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="188"/>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="194"/>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="288"/>
+ <source>yuzu</source>
+ <translation>yuzu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="174"/>
+ <source>Port number has invalid characters</source>
+ <translation>Номер порту містить неприпустимі символи</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="178"/>
+ <source>Port has to be in range 0 and 65353</source>
+ <translation>Порт повинен бути в районі від 0 до 65353</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="182"/>
+ <source>IP address is not valid</source>
+ <translation>IP-адреса недійсна</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="188"/>
+ <source>This UDP server already exists</source>
+ <translation>Цей UDP сервер уже існує</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="194"/>
+ <source>Unable to add more than 8 servers</source>
+ <translation>Неможливо додати більше 8 серверів</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="210"/>
+ <source>Testing</source>
+ <translation>Тестування</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="226"/>
+ <source>Configuring</source>
+ <translation>Налаштування</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="257"/>
+ <source>Test Successful</source>
+ <translation>Тест успішний</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="258"/>
+ <source>Successfully received data from the server.</source>
+ <translation>Успішно отримано інформацію із сервера</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="260"/>
+ <source>Test Failed</source>
+ <translation>Тест провалено</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="261"/>
+ <source>Could not receive valid data from the server.&lt;br&gt;Please verify that the server is set up correctly and the address and port are correct.</source>
+ <translation>Не вдалося отримати дійсні дані з сервера.&lt;br&gt;Переконайтеся, що сервер правильно налаштований, а також перевірте адресу та порт.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="289"/>
+ <source>UDP Test or calibration configuration is in progress.&lt;br&gt;Please wait for them to finish.</source>
+ <translation>Тест UDP або калібрація в процесі.&lt;br&gt;Будь ласка, зачекайте завершення.</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureNetwork</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_network.ui" line="14"/>
+ <source>Form</source>
+ <translation>Форма</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_network.ui" line="17"/>
+ <source>Network</source>
+ <translation>Мережа</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_network.ui" line="25"/>
+ <source>General</source>
+ <translation>Загальні</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_network.ui" line="34"/>
+ <source>Network Interface</source>
+ <translation>Інтерфейс мережі</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_network.cpp" line="15"/>
+ <source>None</source>
+ <translation>Нічого</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigurePerGame</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="12"/>
+ <source>Dialog</source>
+ <translation>Діалог</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="26"/>
+ <source>Info</source>
+ <translation>Інформація</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="85"/>
+ <source>Name</source>
+ <translation>Назва</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="92"/>
+ <source>Title ID</source>
+ <translation>Ідентифікатор гри</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="129"/>
+ <source>Filename</source>
+ <translation>Ім&apos;я файлу</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="156"/>
+ <source>Format</source>
+ <translation>Формат</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="163"/>
+ <source>Version</source>
+ <translation>Версія</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="170"/>
+ <source>Size</source>
+ <translation>Розмір</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="177"/>
+ <source>Developer</source>
+ <translation>Розробник</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.cpp" line="57"/>
+ <source>Add-Ons</source>
+ <translation>Доповнення</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.cpp" line="58"/>
+ <source>General</source>
+ <translation>Загальні</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.cpp" line="59"/>
+ <source>System</source>
+ <translation>Система</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.cpp" line="60"/>
+ <source>CPU</source>
+ <translation>ЦП</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.cpp" line="61"/>
+ <source>Graphics</source>
+ <translation>Графіка</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.cpp" line="62"/>
+ <source>Adv. Graphics</source>
+ <translation>Розш. Графіка</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.cpp" line="63"/>
+ <source>Audio</source>
+ <translation>Аудіо</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.cpp" line="66"/>
+ <source>Properties</source>
+ <translation>Властивості</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configuration_shared.cpp" line="91"/>
+ <source>Use global configuration (%1)</source>
+ <translation>Використовувати глобальне налаштування (%1)</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigurePerGameAddons</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game_addons.ui" line="14"/>
+ <source>Form</source>
+ <translation>Форма</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game_addons.ui" line="17"/>
+ <source>Add-Ons</source>
+ <translation>Доповнення</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game_addons.cpp" line="46"/>
+ <source>Patch Name</source>
+ <translation>Назва патчу</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game_addons.cpp" line="47"/>
+ <source>Version</source>
+ <translation>Версія</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureProfileManager</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="14"/>
+ <source>Form</source>
+ <translation>Форма</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="17"/>
+ <source>Profiles</source>
+ <translation>Профілі</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="25"/>
+ <source>Profile Manager</source>
+ <translation>Керування профілями</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="42"/>
+ <source>Current User</source>
+ <translation>Поточний користувач</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="80"/>
+ <source>Username</source>
+ <translation>Ім&apos;я користувача</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="110"/>
+ <source>Set Image</source>
+ <translation>Обрати зображення</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="130"/>
+ <source>Add</source>
+ <translation>Додати</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="140"/>
+ <source>Rename</source>
+ <translation>Перейменувати</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="150"/>
+ <source>Remove</source>
+ <translation>Видалити</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="162"/>
+ <source>Profile management is available only when game is not running.</source>
+ <translation>Керування профілями недоступне, поки запущена гра.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="52"/>
+ <source>%1
+%2</source>
+ <comment>%1 is the profile username, %2 is the formatted UUID (e.g. 00112233-4455-6677-8899-AABBCCDDEEFF))</comment>
+ <translation>%1
+%2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="70"/>
+ <source>Enter Username</source>
+ <translation>Введіть ім&apos;я користувача</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="133"/>
+ <source>Users</source>
+ <translation>Користувачі</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="195"/>
+ <source>Enter a username for the new user:</source>
+ <translation>Введіть ім&apos;я користувача для нового профілю:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="215"/>
+ <source>Enter a new username:</source>
+ <translation>Введіть нове ім&apos;я користувача:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="240"/>
+ <source>Confirm Delete</source>
+ <translation>Підтвердити видалення</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="241"/>
+ <source>You are about to delete user with name &quot;%1&quot;. Are you sure?</source>
+ <translation>Ви збираєтеся видалити користувача &quot;%1&quot;. Ви впевнені?</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="268"/>
+ <source>Select User Image</source>
+ <translation>Оберіть зображення користувача</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="269"/>
+ <source>JPEG Images (*.jpg *.jpeg)</source>
+ <translation>Зображення JPEG (*.jpg *.jpeg)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="278"/>
+ <source>Error deleting image</source>
+ <translation>Помилка під час видалення зображення</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="279"/>
+ <source>Error occurred attempting to overwrite previous image at: %1.</source>
+ <translation>Помилка під час спроби перезапису попереднього зображення в: %1.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="287"/>
+ <source>Error deleting file</source>
+ <translation>Помилка під час видалення файлу</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="288"/>
+ <source>Unable to delete existing file: %1.</source>
+ <translation>Не вдалося видалити наявний файл: %1.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="295"/>
+ <source>Error creating user image directory</source>
+ <translation>Помилка під час створення папки користувацьких зображень</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="296"/>
+ <source>Unable to create directory %1 for storing user images.</source>
+ <translation>Не вийшло створити папку %1 для зберігання зображень користувача.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="301"/>
+ <source>Error copying user image</source>
+ <translation>Помилка під час копіювання зображення користувача</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="302"/>
+ <source>Unable to copy image from %1 to %2</source>
+ <translation>Не вийшло скопіювати зображення з %1 у %2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="311"/>
+ <source>Error resizing user image</source>
+ <translation>Помилка під час зміни розміру зображення користувача</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="312"/>
+ <source>Unable to resize image</source>
+ <translation>Неможливо змінити розмір зображення</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureRingController</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ringcon.ui" line="14"/>
+ <source>Configure Ring Controller</source>
+ <translation>Налаштування контролера Ring</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ringcon.ui" line="26"/>
+ <source>If you want to use this controller configure player 1 as right controller and player 2 as dual joycon before starting the game to allow this controller to be detected properly.</source>
+ <translation>Якщо ви хочете використовувати цей контролер, налаштуйте гравця 1 як правий контролер, а гравця 2 як подвійний Joy-Соп перед початком гри, щоб цей контролер був виявлений правильно.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ringcon.ui" line="52"/>
+ <source>Ring Sensor Parameters</source>
+ <translation>Параметри сенсора Ring</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ringcon.ui" line="84"/>
+ <location filename="../../src/yuzu/configuration/configure_ringcon.ui" line="123"/>
+ <source>Pull</source>
+ <translation>Потягнути</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ringcon.ui" line="133"/>
+ <location filename="../../src/yuzu/configuration/configure_ringcon.ui" line="172"/>
+ <source>Push</source>
+ <translation>Натиснути</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ringcon.ui" line="206"/>
+ <source>Deadzone: 0%</source>
+ <translation>Мертва зона: 0%</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ringcon.ui" line="248"/>
+ <source>Restore Defaults</source>
+ <translation>За замовчуванням</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ringcon.cpp" line="159"/>
+ <source>Clear</source>
+ <translation>Очистити</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ringcon.cpp" line="161"/>
+ <source>[not set]</source>
+ <translation>[не задано]</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ringcon.cpp" line="163"/>
+ <source>Invert axis</source>
+ <translation>Інвертувати осі</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ringcon.cpp" line="182"/>
+ <location filename="../../src/yuzu/configuration/configure_ringcon.cpp" line="238"/>
+ <source>Deadzone: %1%</source>
+ <translation>Мертва зона: %1%</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ringcon.cpp" line="262"/>
+ <source>[waiting]</source>
+ <translation>[очікування]</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureSystem</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="14"/>
+ <source>Form</source>
+ <translation>Форма</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="17"/>
+ <source>System</source>
+ <translation>Система</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="25"/>
+ <source>System Settings</source>
+ <translation>Налаштування системи</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="33"/>
+ <source>Region:</source>
+ <translation>Регіон:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="41"/>
+ <source>Auto</source>
+ <translation>Авто</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="46"/>
+ <source>Default</source>
+ <translation>За замовчуванням</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="51"/>
+ <source>CET</source>
+ <translation>CET</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="56"/>
+ <source>CST6CDT</source>
+ <translation>CST6CDT</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="61"/>
+ <source>Cuba</source>
+ <translation>Куба</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="66"/>
+ <source>EET</source>
+ <translation>EET</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="71"/>
+ <source>Egypt</source>
+ <translation>Єгипет</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="76"/>
+ <source>Eire</source>
+ <translation>Ейре</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="81"/>
+ <source>EST</source>
+ <translation>EST</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="86"/>
+ <source>EST5EDT</source>
+ <translation>EST5EDT</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="91"/>
+ <source>GB</source>
+ <translation>GB</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="96"/>
+ <source>GB-Eire</source>
+ <translation>GB-Ейре</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="101"/>
+ <source>GMT</source>
+ <translation>GMT</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="106"/>
+ <source>GMT+0</source>
+ <translation>GMT+0</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="111"/>
+ <source>GMT-0</source>
+ <translation>GMT-0</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="116"/>
+ <source>GMT0</source>
+ <translation>GMT0</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="121"/>
+ <source>Greenwich</source>
+ <translation>Гринвіч</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="126"/>
+ <source>Hongkong</source>
+ <translation>Гонконг</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="131"/>
+ <source>HST</source>
+ <translation>HST</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="136"/>
+ <source>Iceland</source>
+ <translation>Ісландія</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="141"/>
+ <source>Iran</source>
+ <translation>Іран</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="146"/>
+ <source>Israel</source>
+ <translation>Ізраїль</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="151"/>
+ <source>Jamaica</source>
+ <translation>Ямайка</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="156"/>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="275"/>
+ <source>Japan</source>
+ <translation>Японія</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="161"/>
+ <source>Kwajalein</source>
+ <translation>Кваджалейн</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="166"/>
+ <source>Libya</source>
+ <translation>Лівія</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="171"/>
+ <source>MET</source>
+ <translation>MET</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="176"/>
+ <source>MST</source>
+ <translation>MST</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="181"/>
+ <source>MST7MDT</source>
+ <translation>MST7MDT</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="186"/>
+ <source>Navajo</source>
+ <translation>Навахо</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="191"/>
+ <source>NZ</source>
+ <translation>NZ</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="196"/>
+ <source>NZ-CHAT</source>
+ <translation>NZ-CHAT</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="201"/>
+ <source>Poland</source>
+ <translation>Польща</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="206"/>
+ <source>Portugal</source>
+ <translation>Португалія</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="211"/>
+ <source>PRC</source>
+ <translation>PRC</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="216"/>
+ <source>PST8PDT</source>
+ <translation>PST8PDT</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="221"/>
+ <source>ROC</source>
+ <translation>ROC</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="226"/>
+ <source>ROK</source>
+ <translation>ROK</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="231"/>
+ <source>Singapore</source>
+ <translation>Сінгапур</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="236"/>
+ <source>Turkey</source>
+ <translation>Туреччина</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="241"/>
+ <source>UCT</source>
+ <translation>UCT</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="246"/>
+ <source>Universal</source>
+ <translation>Універсальний</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="251"/>
+ <source>UTC</source>
+ <translation>UTC</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="256"/>
+ <source>W-SU</source>
+ <translation>W-SU</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="261"/>
+ <source>WET</source>
+ <translation>WET</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="266"/>
+ <source>Zulu</source>
+ <translation>Зулуси</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="280"/>
+ <source>USA</source>
+ <translation>США</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="285"/>
+ <source>Europe</source>
+ <translation>Європа</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="290"/>
+ <source>Australia</source>
+ <translation>Австралія</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="295"/>
+ <source>China</source>
+ <translation>Китай</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="300"/>
+ <source>Korea</source>
+ <translation>Корея</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="305"/>
+ <source>Taiwan</source>
+ <translation>Тайвань</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="313"/>
+ <source>Time Zone:</source>
+ <translation>Часовий пояс:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="320"/>
+ <source>Note: this can be overridden when region setting is auto-select</source>
+ <translation>Примітка: може бути перезаписано якщо регіон вибирається автоматично</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="324"/>
+ <source>Japanese (日本語)</source>
+ <translation>Японська (日本語)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="329"/>
+ <source>English</source>
+ <translation>Англійська (English)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="334"/>
+ <source>French (français)</source>
+ <translation>Французька (français)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="339"/>
+ <source>German (Deutsch)</source>
+ <translation>Німецька (Deutsch)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="344"/>
+ <source>Italian (italiano)</source>
+ <translation>Італійська (italiano)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="349"/>
+ <source>Spanish (español)</source>
+ <translation>Іспанська (español)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="354"/>
+ <source>Chinese</source>
+ <translation>Китайська</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="359"/>
+ <source>Korean (한국어)</source>
+ <translation>Корейська (한국어)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="364"/>
+ <source>Dutch (Nederlands)</source>
+ <translation>Голландська (Nederlands)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="369"/>
+ <source>Portuguese (português)</source>
+ <translation>Португальська (português)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="374"/>
+ <source>Russian (Русский)</source>
+ <translation>Російська (Русский)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="379"/>
+ <source>Taiwanese</source>
+ <translation>Тайванська</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="384"/>
+ <source>British English</source>
+ <translation>Британська Англійська</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="389"/>
+ <source>Canadian French</source>
+ <translation>Канадська Французька</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="394"/>
+ <source>Latin American Spanish</source>
+ <translation>Латиноамериканська Іспанська</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="399"/>
+ <source>Simplified Chinese</source>
+ <translation>Спрощена Китайська</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="404"/>
+ <source>Traditional Chinese (正體中文)</source>
+ <translation>Традиційна Китайська (正體中文)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="409"/>
+ <source>Brazilian Portuguese (português do Brasil)</source>
+ <translation>Бразильська Португальська (português do Brasil)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="417"/>
+ <source>Custom RTC</source>
+ <translation>Користувацький RTC</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="424"/>
+ <source>Language</source>
+ <translation>Мова</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="431"/>
+ <source>RNG Seed</source>
+ <translation>Сід RNG</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="439"/>
+ <source>Mono</source>
+ <translation>Моно</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="444"/>
+ <source>Stereo</source>
+ <translation>Стерео</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="449"/>
+ <source>Surround</source>
+ <translation>Об&apos;ємний звук</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="457"/>
+ <source>Console ID:</source>
+ <translation>Ідентифікатор консолі:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="464"/>
+ <source>Sound output mode</source>
+ <translation>Режим виводу звуку</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="512"/>
+ <source>Regenerate</source>
+ <translation>Перегенерувати</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="537"/>
+ <source>System settings are available only when game is not running.</source>
+ <translation>Налаштування системи доступні тільки тоді, коли гру не запущено.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.cpp" line="161"/>
+ <source>This will replace your current virtual Switch with a new one. Your current virtual Switch will not be recoverable. This might have unexpected effects in games. This might fail, if you use an outdated config savegame. Continue?</source>
+ <translation>Це замінить ваш поточний віртуальний Switch новим. Ваш поточний віртуальний Switch буде безповоротно втрачено. Це може мати несподівані наслідки в іграх. Може не спрацювати, якщо ви використовуєте застарілу конфігурацію збережених ігор. Продовжити?</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.cpp" line="165"/>
+ <source>Warning</source>
+ <translation>Увага</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.cpp" line="173"/>
+ <source>Console ID: 0x%1</source>
+ <translation>Ідентифікатор консолі: 0x%1</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureTas</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_tas.ui" line="11"/>
+ <source>TAS</source>
+ <translation>TAS</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_tas.ui" line="17"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Reads controller input from scripts in the same format as TAS-nx scripts.&lt;br/&gt;For a more detailed explanation, please consult the &lt;a href=&quot;https://yuzu-emu.org/help/feature/tas/&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;help page&lt;/span&gt;&lt;/a&gt; on the yuzu website.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Зчитує вхідні дані контролера зі скриптів у тому ж форматі, що і скрипти TAS-nx.&lt;br/&gt;Для більш детального пояснення зверніться до &lt;a href=&quot;https://yuzu-emu.org/help/feature/tas/&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;сторінки допомоги&lt;/span&gt;&lt;/a&gt; на сайті yuzu.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_tas.ui" line="27"/>
+ <source>To check which hotkeys control the playback/recording, please refer to the Hotkey settings (Configure -&gt; General -&gt; Hotkeys).</source>
+ <translation>Щоб перевірити, які гарячі клавіші керують відтворенням/записом, зверніться до налаштувань гарячих клавіш (Налаштування - Загальні -&gt; Гарячі клавіші).</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_tas.ui" line="37"/>
+ <source>WARNING: This is an experimental feature.&lt;br/&gt;It will not play back scripts frame perfectly with the current, imperfect syncing method.</source>
+ <translation>ПОПЕРЕДЖЕННЯ: Це експериментальна функція.&lt;br/&gt;Вона не буде ідеально відтворювати кадри сценаріїв за поточного недосконалого методу синхронізації.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_tas.ui" line="54"/>
+ <source>Settings</source>
+ <translation>Налаштування</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_tas.ui" line="60"/>
+ <source>Enable TAS features</source>
+ <translation>Увімкнути функції TAS</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_tas.ui" line="67"/>
+ <source>Loop script</source>
+ <translation>Зациклити скрипт</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_tas.ui" line="77"/>
+ <source>Pause execution during loads</source>
+ <translation>Призупинити виконання під час завантаження</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_tas.ui" line="91"/>
+ <source>Script Directory</source>
+ <translation>Папка для скриптів</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_tas.ui" line="97"/>
+ <source>Path</source>
+ <translation>Шлях</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_tas.ui" line="104"/>
+ <source>...</source>
+ <translation>...</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureTasDialog</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_tas.cpp" line="19"/>
+ <source>TAS Configuration</source>
+ <translation>Налаштування TAS</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_tas.cpp" line="50"/>
+ <source>Select TAS Load Directory...</source>
+ <translation>Обрати папку завантаження TAS...</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureTouchFromButton</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.ui" line="14"/>
+ <source>Configure Touchscreen Mappings</source>
+ <translation>Налаштування відображення сенсорного екрана</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.ui" line="22"/>
+ <source>Mapping:</source>
+ <translation>Прив&apos;язки:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.ui" line="48"/>
+ <source>New</source>
+ <translation>Новий</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.ui" line="61"/>
+ <source>Delete</source>
+ <translation>Видалити</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.ui" line="74"/>
+ <source>Rename</source>
+ <translation>Перейменувати</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.ui" line="92"/>
+ <source>Click the bottom area to add a point, then press a button to bind.
+Drag points to change position, or double-click table cells to edit values.</source>
+ <translation>Натисніть на нижній області, щоб додати точку, після чого натисніть кнопку для прив&apos;язки.
+Перетягніть точки, щоб змінити позицію, або натисніть двічі на комірки таблиці для зміни значень.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.ui" line="116"/>
+ <source>Delete Point</source>
+ <translation>Видалити точку</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="78"/>
+ <source>Button</source>
+ <translation>Кнопка</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="78"/>
+ <source>X</source>
+ <comment>X axis</comment>
+ <translation>X</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="78"/>
+ <source>Y</source>
+ <comment>Y axis</comment>
+ <translation>Y</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="195"/>
+ <source>New Profile</source>
+ <translation>Новий профіль</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="195"/>
+ <source>Enter the name for the new profile.</source>
+ <translation>Введіть ім&apos;я вашого нового профілю.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="206"/>
+ <source>Delete Profile</source>
+ <translation>Видалити профіль</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="206"/>
+ <source>Delete profile %1?</source>
+ <translation>Видалити профіль %1?</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="219"/>
+ <source>Rename Profile</source>
+ <translation>Перейменувати профіль</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="219"/>
+ <source>New name:</source>
+ <translation>Нова назва:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="231"/>
+ <source>[press key]</source>
+ <translation>[натисніть клавішу]</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureTouchscreenAdvanced</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="14"/>
+ <source>Configure Touchscreen</source>
+ <translation>Налаштування сенсорного екрана</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="26"/>
+ <source>Warning: The settings in this page affect the inner workings of yuzu&apos;s emulated touchscreen. Changing them may result in undesirable behavior, such as the touchscreen partially or not working. You should only use this page if you know what you are doing.</source>
+ <translation>Увага: Налаштування на цій сторінці впливають на внутрішню роботу емульованого сенсорного екрана yuzu. Їх зміна може призвести до небажаної поведінки, як часткова або повна непрацездатність сенсорного екрана. Використовуйте цю сторінку лише якщо ви знаєте, що робите.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="52"/>
+ <source>Touch Parameters</source>
+ <translation>Параметри сенсора</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="71"/>
+ <source>Touch Diameter Y</source>
+ <translation>Діаметр сенсора Y</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="91"/>
+ <source>Touch Diameter X</source>
+ <translation>Діаметр сенсора X</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="98"/>
+ <source>Rotational Angle</source>
+ <translation>Кут повороту</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="132"/>
+ <source>Restore Defaults</source>
+ <translation>За замовчуванням</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureUI</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.cpp" line="20"/>
+ <location filename="../../src/yuzu/configuration/configure_ui.cpp" line="28"/>
+ <location filename="../../src/yuzu/configuration/configure_ui.cpp" line="40"/>
+ <source>None</source>
+ <translation>Нічого</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.cpp" line="21"/>
+ <source>Small (32x32)</source>
+ <translation>Маленький (32х32)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.cpp" line="22"/>
+ <source>Standard (64x64)</source>
+ <translation>Стандартний (64х64)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.cpp" line="23"/>
+ <source>Large (128x128)</source>
+ <translation>Великий (128х128)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.cpp" line="24"/>
+ <source>Full Size (256x256)</source>
+ <translation>Повнорозмірний (256х256)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.cpp" line="29"/>
+ <source>Small (24x24)</source>
+ <translation>Маленький (24х24)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.cpp" line="30"/>
+ <source>Standard (48x48)</source>
+ <translation>Стандартний (48х48)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.cpp" line="31"/>
+ <source>Large (72x72)</source>
+ <translation>Великий (72х72)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.cpp" line="36"/>
+ <source>Filename</source>
+ <translation>Ім&apos;я файлу</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.cpp" line="37"/>
+ <source>Filetype</source>
+ <translation>Тип файлу</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.cpp" line="38"/>
+ <source>Title ID</source>
+ <translation>Ідентифікатор гри</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.cpp" line="39"/>
+ <source>Title Name</source>
+ <translation>Назва гри</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureUi</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="14"/>
+ <source>Form</source>
+ <translation>Форма</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="17"/>
+ <source>UI</source>
+ <translation>Інтерфейс</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="23"/>
+ <source>General</source>
+ <translation>Загальні</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="31"/>
+ <source>Note: Changing language will apply your configuration.</source>
+ <translation>Примітка: Зміна мови призведе до застосування налаштувань.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="43"/>
+ <source>Interface language:</source>
+ <translation>Мова інтерфейсу:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="57"/>
+ <source>Theme:</source>
+ <translation>Тема:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="74"/>
+ <source>Game List</source>
+ <translation>Список ігор</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="82"/>
+ <source>Show Compatibility List</source>
+ <translation>Показати список сумісності</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="89"/>
+ <source>Show Add-Ons Column</source>
+ <translation>Показувати стовпець доповнень</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="98"/>
+ <source>Game Icon Size:</source>
+ <translation>Розмір іконки гри:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="112"/>
+ <source>Folder Icon Size:</source>
+ <translation>Розмір іконки папки:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="126"/>
+ <source>Row 1 Text:</source>
+ <translation>Текст 1-го рядку:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="140"/>
+ <source>Row 2 Text:</source>
+ <translation>Текст 2-го рядку:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="157"/>
+ <source>Screenshots</source>
+ <translation>Знімки екрану</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="165"/>
+ <source>Ask Where To Save Screenshots (Windows Only)</source>
+ <translation>Запитувати куди зберігати знімки екрану (Тільки для Windows)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="174"/>
+ <source>Screenshots Path: </source>
+ <translation>Папка для знімків екрану:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="184"/>
+ <source>...</source>
+ <translation>...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.cpp" line="94"/>
+ <source>Select Screenshots Path...</source>
+ <translation>Виберіть папку для знімків екрану...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.cpp" line="219"/>
+ <source>&lt;System&gt;</source>
+ <translation>&lt;System&gt;</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureVibration</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_vibration.ui" line="14"/>
+ <source>Configure Vibration</source>
+ <translation>Налаштування вібрації</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_vibration.ui" line="23"/>
+ <source>Press any controller button to vibrate the controller.</source>
+ <translation>Натисніть будь-яку кнопку контролера, щоб викликати вібрацію.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_vibration.ui" line="30"/>
+ <source>Vibration</source>
+ <translation>Вібрація</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_vibration.ui" line="63"/>
+ <source>Player 1</source>
+ <translation>Гравець 1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_vibration.ui" line="96"/>
+ <location filename="../../src/yuzu/configuration/configure_vibration.ui" line="148"/>
+ <location filename="../../src/yuzu/configuration/configure_vibration.ui" line="200"/>
+ <location filename="../../src/yuzu/configuration/configure_vibration.ui" line="252"/>
+ <location filename="../../src/yuzu/configuration/configure_vibration.ui" line="322"/>
+ <location filename="../../src/yuzu/configuration/configure_vibration.ui" line="374"/>
+ <location filename="../../src/yuzu/configuration/configure_vibration.ui" line="426"/>
+ <location filename="../../src/yuzu/configuration/configure_vibration.ui" line="478"/>
+ <source>%</source>
+ <translation>%</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_vibration.ui" line="115"/>
+ <source>Player 2</source>
+ <translation>Гравець 2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_vibration.ui" line="167"/>
+ <source>Player 3</source>
+ <translation>Гравець 3</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_vibration.ui" line="219"/>
+ <source>Player 4</source>
+ <translation>Гравець 4</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_vibration.ui" line="289"/>
+ <source>Player 5</source>
+ <translation>Гравець 5</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_vibration.ui" line="341"/>
+ <source>Player 6</source>
+ <translation>Гравець 6</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_vibration.ui" line="393"/>
+ <source>Player 7</source>
+ <translation>Гравець 7</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_vibration.ui" line="445"/>
+ <source>Player 8</source>
+ <translation>Гравець 8</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_vibration.ui" line="503"/>
+ <source>Settings</source>
+ <translation>Налаштування</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_vibration.ui" line="509"/>
+ <source>Enable Accurate Vibration</source>
+ <translation>Увімкнути точну вібрацію</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureWeb</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="14"/>
+ <source>Form</source>
+ <translation>Форма</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="17"/>
+ <source>Web</source>
+ <translation>Мережа</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="25"/>
+ <source>yuzu Web Service</source>
+ <translation>Веб-сервіс yuzu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="31"/>
+ <source>By providing your username and token, you agree to allow yuzu to collect additional usage data, which may include user identifying information.</source>
+ <translation>Надаючи своє ім&apos;я користувача і токен, ви погоджуєтеся дозволити yuzu збирати додаткові дані про використання, які можуть включати інформацію, що ідентифікує користувача.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="49"/>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="159"/>
+ <source>Verify</source>
+ <translation>Підтвердити</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="56"/>
+ <source>Sign up</source>
+ <translation>Реєстрація</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="66"/>
+ <source>Token: </source>
+ <translation>Токен:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="76"/>
+ <source>Username: </source>
+ <translation>Ім&apos;я користувача:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="93"/>
+ <source>What is my token?</source>
+ <translation>Що таке токен і де його знайти?</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="118"/>
+ <source>Web Service configuration can only be changed when a public room isn&apos;t being hosted.</source>
+ <translation>Налаштування веб-служби можуть бути змінені тільки в тому випадку, коли не хоститься публічна кімната.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="128"/>
+ <source>Telemetry</source>
+ <translation>Телеметрія</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="134"/>
+ <source>Share anonymous usage data with the yuzu team</source>
+ <translation>Ділитися анонімною інформацією про використання з командою yuzu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="141"/>
+ <source>Learn more</source>
+ <translation>Дізнатися більше</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="150"/>
+ <source>Telemetry ID:</source>
+ <translation>Ідентифікатор телеметрії:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="166"/>
+ <source>Regenerate</source>
+ <translation>Перегенерувати</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="180"/>
+ <source>Discord Presence</source>
+ <translation>Discord Presence</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="186"/>
+ <source>Show Current Game in your Discord Status</source>
+ <translation>Показувати поточну гру у вашому статусі Discord</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="68"/>
+ <source>&lt;a href=&apos;https://yuzu-emu.org/help/feature/telemetry/&apos;&gt;&lt;span style=&quot;text-decoration: underline; color:#039be5;&quot;&gt;Learn more&lt;/span&gt;&lt;/a&gt;</source>
+ <translation>&lt;a href=&apos;https://yuzu-emu.org/help/feature/telemetry/&apos;&gt;&lt;span style=&quot;text-decoration: underline; color:#039be5;&quot;&gt;Дізнатися більше&lt;/span&gt;&lt;/a&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="72"/>
+ <source>&lt;a href=&apos;https://profile.yuzu-emu.org/&apos;&gt;&lt;span style=&quot;text-decoration: underline; color:#039be5;&quot;&gt;Sign up&lt;/span&gt;&lt;/a&gt;</source>
+ <translation>&lt;a href=&apos;https://profile.yuzu-emu.org/&apos;&gt;&lt;span style=&quot;text-decoration: underline; color:#039be5;&quot;&gt;Реєстрація&lt;/span&gt;&lt;/a&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="76"/>
+ <source>&lt;a href=&apos;https://yuzu-emu.org/wiki/yuzu-web-service/&apos;&gt;&lt;span style=&quot;text-decoration: underline; color:#039be5;&quot;&gt;What is my token?&lt;/span&gt;&lt;/a&gt;</source>
+ <translation>&lt;a href=&apos;https://yuzu-emu.org/wiki/yuzu-web-service/&apos;&gt;&lt;span style=&quot;text-decoration: underline; color:#039be5;&quot;&gt;Що таке токен і де його знайти?&lt;/span&gt;&lt;/a&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="80"/>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="125"/>
+ <source>Telemetry ID: 0x%1</source>
+ <translation>Ідентифікатор телеметрії: 0x%1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="91"/>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="170"/>
+ <source>Unspecified</source>
+ <translation>Відсутній</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="117"/>
+ <source>Token not verified</source>
+ <translation>Токен не підтверджено</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="118"/>
+ <source>Token was not verified. The change to your token has not been saved.</source>
+ <translation>Токен не було підтверджено. Зміну вашого токена не було збережено.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="141"/>
+ <source>Unverified, please click Verify before saving configuration</source>
+ <comment>Tooltip</comment>
+ <translation>Не підтверджено, будь ласка, натисніть кнопку Підтвердити, перш ніж зберігати конфігурацію.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="147"/>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="149"/>
+ <source>Verifying...</source>
+ <translation>Підтверждення...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="164"/>
+ <source>Verified</source>
+ <comment>Tooltip</comment>
+ <translation>Підтверджено</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="169"/>
+ <source>Verification failed</source>
+ <comment>Tooltip</comment>
+ <translation>Підтверждення не було успішним</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="171"/>
+ <source>Verification failed</source>
+ <translation>Підтверждення не було успішним</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="172"/>
+ <source>Verification failed. Check that you have entered your token correctly, and that your internet connection is working.</source>
+ <translation>Підтверждення не було успішним. Переконайтеся, що ви правильно ввели свій токен і що ваше інтернет-з&apos;єднання працює.</translation>
+ </message>
+</context>
+<context>
+ <name>ControllerDialog</name>
+ <message>
+ <location filename="../../src/yuzu/debugger/controller.cpp" line="20"/>
+ <source>Controller P1</source>
+ <translation>Контролер P1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/controller.cpp" line="59"/>
+ <source>&amp;Controller P1</source>
+ <translation>[&amp;C] Контролер P1</translation>
+ </message>
+</context>
+<context>
+ <name>DirectConnect</name>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/direct_connect.ui" line="14"/>
+ <source>Direct Connect</source>
+ <translation>Пряме підключення</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/direct_connect.ui" line="33"/>
+ <source>IP Address</source>
+ <translation>IP-адреса</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/direct_connect.ui" line="56"/>
+ <source>IP</source>
+ <translation>IP</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/direct_connect.ui" line="63"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;IPv4 address of the host&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;IPv4 адреса хоста&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/direct_connect.ui" line="73"/>
+ <source>Port</source>
+ <translation>Порт</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/direct_connect.ui" line="80"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Port number the host is listening on&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Номер порту, який прослуховується хостом&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/direct_connect.ui" line="100"/>
+ <source>Nickname</source>
+ <translation>Псевдонім</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/direct_connect.ui" line="114"/>
+ <source>Password</source>
+ <translation>Пароль</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/direct_connect.ui" line="156"/>
+ <source>Connect</source>
+ <translation>Підключитися</translation>
+ </message>
+</context>
+<context>
+ <name>DirectConnectWindow</name>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/direct_connect.cpp" line="127"/>
+ <source>Connecting</source>
+ <translation>Підключення</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/direct_connect.cpp" line="132"/>
+ <source>Connect</source>
+ <translation>Підключитися</translation>
+ </message>
+</context>
+<context>
+ <name>GMainWindow</name>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="188"/>
+ <source>&lt;a href=&apos;https://yuzu-emu.org/help/feature/telemetry/&apos;&gt;Anonymous data is collected&lt;/a&gt; to help improve yuzu. &lt;br/&gt;&lt;br/&gt;Would you like to share your usage data with us?</source>
+ <translation>&lt;a href=&apos;https://yuzu-emu.org/help/feature/telemetry/&apos;&gt;Анонімні дані збираються для того,&lt;/a&gt; щоб допомогти поліпшити роботу yuzu. &lt;br/&gt;&lt;br/&gt;Хотіли б ви ділитися даними про використання з нами?</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="191"/>
+ <source>Telemetry</source>
+ <translation>Телеметрія</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="405"/>
+ <source>Broken Vulkan Installation Detected</source>
+ <translation>Виявлено пошкоджену інсталяцію Vulkan</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="406"/>
+ <source>Vulkan initialization failed during boot.&lt;br&gt;&lt;br&gt;Click &lt;a href=&apos;https://yuzu-emu.org/wiki/faq/#yuzu-starts-with-the-error-broken-vulkan-installation-detected&apos;&gt;here for instructions to fix the issue&lt;/a&gt;.</source>
+ <translation>Не вдалося виконати ініціалізацію Vulkan під час завантаження.&lt;br&gt;&lt;br&gt;Натисніть &lt;a href=&apos;https://yuzu-emu.org/wiki/faq/#yuzu-starts-with-the-error-broken-vulkan-installation-detected&apos;&gt;тут для отримання інструкцій щодо усунення проблеми&lt;/a&gt;.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="734"/>
+ <source>Loading Web Applet...</source>
+ <translation>Завантаження веб-аплета...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="781"/>
+ <location filename="../../src/yuzu/main.cpp" line="784"/>
+ <source>Disable Web Applet</source>
+ <translation>Вимкнути веб-аплет</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="785"/>
+ <source>Disabling the web applet can lead to undefined behavior and should only be used with Super Mario 3D All-Stars. Are you sure you want to disable the web applet?
+(This can be re-enabled in the Debug settings.)</source>
+ <translation>Вимкнення веб-апплета може призвести до несподіваної поведінки, і його слід вимикати лише заради Super Mario 3D All-Stars. Ви впевнені, що хочете вимкнути веб-апплет?
+(Його можна знову ввімкнути в налаштуваннях налагодження.)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="892"/>
+ <source>The amount of shaders currently being built</source>
+ <translation>Кількість створюваних шейдерів на цей момент</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="894"/>
+ <source>The current selected resolution scaling multiplier.</source>
+ <translation>Поточний обраний множник масштабування роздільної здатності.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="897"/>
+ <source>Current emulation speed. Values higher or lower than 100% indicate emulation is running faster or slower than a Switch.</source>
+ <translation>Поточна швидкість емуляції. Значення вище або нижче 100% вказують на те, що емуляція йде швидше або повільніше, ніж на Switch.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="900"/>
+ <source>How many frames per second the game is currently displaying. This will vary from game to game and scene to scene.</source>
+ <translation>Кількість кадрів на секунду в цей момент. Значення буде змінюватися між іграми та сценами.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="904"/>
+ <source>Time taken to emulate a Switch frame, not counting framelimiting or v-sync. For full-speed emulation this should be at most 16.67 ms.</source>
+ <translation>Час, який потрібен для емуляції 1 кадру Switch, не беручи до уваги обмеження FPS або вертикальну синхронізацію. Для емуляції в повній швидкості значення має бути не більше 16,67 мс.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="983"/>
+ <source>VULKAN</source>
+ <translation>VULKAN</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="983"/>
+ <source>OPENGL</source>
+ <translation>OPENGL</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1045"/>
+ <source>&amp;Clear Recent Files</source>
+ <translation>[&amp;C] Очистити нещодавні файли</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1353"/>
+ <source>&amp;Continue</source>
+ <translation>[&amp;C] Продовжити</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1355"/>
+ <source>&amp;Pause</source>
+ <translation>[&amp;P] Пауза</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1435"/>
+ <source>yuzu is running a game</source>
+ <extracomment>TRANSLATORS: This string is shown to the user to explain why yuzu needs to prevent the computer from sleeping</extracomment>
+ <translation>В yuzu запущено гру</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1566"/>
+ <source>Warning Outdated Game Format</source>
+ <translation>Попередження застарілий формат гри</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1567"/>
+ <source>You are using the deconstructed ROM directory format for this game, which is an outdated format that has been superseded by others such as NCA, NAX, XCI, or NSP. Deconstructed ROM directories lack icons, metadata, and update support.&lt;br&gt;&lt;br&gt;For an explanation of the various Switch formats yuzu supports, &lt;a href=&apos;https://yuzu-emu.org/wiki/overview-of-switch-game-formats&apos;&gt;check out our wiki&lt;/a&gt;. This message will not be shown again.</source>
+ <translation>Для цієї гри ви використовуєте розархівований формат ROM&apos;а, який є застарілим і був замінений іншими, такими як NCA, NAX, XCI або NSP. У розархівованих каталогах ROM&apos;а відсутні іконки, метадані та підтримка оновлень. &lt;br&gt;&lt;br&gt;Для отримання інформації про різні формати Switch, підтримувані yuzu, &lt;a href=&apos;https://yuzu-emu.org/wiki/overview-of-switch-game-formats&apos;&gt;перегляньте нашу вікі&lt;/a&gt;. Це повідомлення більше не буде відображатися.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1579"/>
+ <location filename="../../src/yuzu/main.cpp" line="1613"/>
+ <source>Error while loading ROM!</source>
+ <translation>Помилка під час завантаження ROM!</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1580"/>
+ <source>The ROM format is not supported.</source>
+ <translation>Формат ROM&apos;а не підтримується.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1584"/>
+ <source>An error occurred initializing the video core.</source>
+ <translation>Сталася помилка під час ініціалізації відеоядра.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1585"/>
+ <source>yuzu has encountered an error while running the video core. This is usually caused by outdated GPU drivers, including integrated ones. Please see the log for more details. For more information on accessing the log, please see the following page: &lt;a href=&apos;https://yuzu-emu.org/help/reference/log-files/&apos;&gt;How to Upload the Log File&lt;/a&gt;. </source>
+ <translation>yuzu зіткнувся з помилкою під час запуску відеоядра. Зазвичай це спричинено застарілими драйверами ГП, включно з інтегрованими. Перевірте журнал для отримання більш детальної інформації. Додаткову інформацію про доступ до журналу дивіться на наступній сторінці: &lt;a href=&apos;https://yuzu-emu.org/help/reference/log-files/&apos;&gt;Як завантажити файл журналу&lt;/a&gt;. </translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1600"/>
+ <source>Error while loading ROM! %1</source>
+ <comment>%1 signifies a numeric error code.</comment>
+ <translation>Помилка під час завантаження ROM&apos;а! %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1603"/>
+ <source>%1&lt;br&gt;Please follow &lt;a href=&apos;https://yuzu-emu.org/help/quickstart/&apos;&gt;the yuzu quickstart guide&lt;/a&gt; to redump your files.&lt;br&gt;You can refer to the yuzu wiki&lt;/a&gt; or the yuzu Discord&lt;/a&gt; for help.</source>
+ <comment>%1 signifies an error string.</comment>
+ <translation>%1&lt;br&gt;Будь ласка, дотримуйтесь &lt;a href=&apos;https://yuzu-emu.org/help/quickstart/&apos;&gt;короткого керівництва користувача yuzu&lt;/a&gt; щоб пере-дампити ваші файли&lt;br&gt;Ви можете звернутися до вікі yuzu&lt;/a&gt; або Discord yuzu&lt;/a&gt; для допомоги</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1614"/>
+ <source>An unknown error occurred. Please see the log for more details.</source>
+ <translation>Сталася невідома помилка. Будь ласка, перевірте журнал для подробиць.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1746"/>
+ <source>(64-bit)</source>
+ <translation>(64-бітний)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1746"/>
+ <source>(32-bit)</source>
+ <translation>(32-бітний)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1747"/>
+ <source>%1 %2</source>
+ <comment>%1 is the title name. %2 indicates if the title is 64-bit or 32-bit</comment>
+ <translation>%1 %2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1897"/>
+ <source>Save Data</source>
+ <translation>Збереження</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1947"/>
+ <source>Mod Data</source>
+ <translation>Дані модів</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1959"/>
+ <source>Error Opening %1 Folder</source>
+ <translation>Помилка під час відкриття папки %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1960"/>
+ <location filename="../../src/yuzu/main.cpp" line="2366"/>
+ <source>Folder does not exist!</source>
+ <translation>Папка не існує!</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1972"/>
+ <source>Error Opening Transferable Shader Cache</source>
+ <translation>Помилка під час відкриття переносного кешу шейдерів</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1973"/>
+ <source>Failed to create the shader cache directory for this title.</source>
+ <translation>Не вдалося створити папку кешу шейдерів для цієї гри.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2025"/>
+ <source>Contents</source>
+ <translation>Зміст</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2027"/>
+ <source>Update</source>
+ <translation>Оновлення</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2029"/>
+ <source>DLC</source>
+ <translation>DLC</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2036"/>
+ <source>Remove Entry</source>
+ <translation>Видалити запис</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2036"/>
+ <source>Remove Installed Game %1?</source>
+ <translation>Видалити встановлену гру %1?</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2066"/>
+ <location filename="../../src/yuzu/main.cpp" line="2082"/>
+ <location filename="../../src/yuzu/main.cpp" line="2113"/>
+ <location filename="../../src/yuzu/main.cpp" line="2174"/>
+ <location filename="../../src/yuzu/main.cpp" line="2192"/>
+ <location filename="../../src/yuzu/main.cpp" line="2215"/>
+ <source>Successfully Removed</source>
+ <translation>Успішно видалено</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2067"/>
+ <source>Successfully removed the installed base game.</source>
+ <translation>Встановлену гру успішно видалено.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2070"/>
+ <location filename="../../src/yuzu/main.cpp" line="2085"/>
+ <location filename="../../src/yuzu/main.cpp" line="2108"/>
+ <source>Error Removing %1</source>
+ <translation>Помилка під час видалення %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2071"/>
+ <source>The base game is not installed in the NAND and cannot be removed.</source>
+ <translation>Гру не встановлено в NAND і не може буде видалено.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2083"/>
+ <source>Successfully removed the installed update.</source>
+ <translation>Встановлене оновлення успішно видалено.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2086"/>
+ <source>There is no update installed for this title.</source>
+ <translation>Для цієї гри не було встановлено оновлення.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2109"/>
+ <source>There are no DLC installed for this title.</source>
+ <translation>Для цієї гри не було встановлено DLC.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2114"/>
+ <source>Successfully removed %1 installed DLC.</source>
+ <translation>Встановлений DLC %1 було успішно видалено</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2122"/>
+ <source>Delete OpenGL Transferable Shader Cache?</source>
+ <translation>Видалити переносний кеш шейдерів OpenGL?</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2124"/>
+ <source>Delete Vulkan Transferable Shader Cache?</source>
+ <translation>Видалити переносний кеш шейдерів Vulakn?</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2126"/>
+ <source>Delete All Transferable Shader Caches?</source>
+ <translation>Видалити весь переносний кеш шейдерів?</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2128"/>
+ <source>Remove Custom Game Configuration?</source>
+ <translation>Видалити користувацьке налаштування гри?</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2134"/>
+ <source>Remove File</source>
+ <translation>Видалити файл</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2169"/>
+ <location filename="../../src/yuzu/main.cpp" line="2177"/>
+ <source>Error Removing Transferable Shader Cache</source>
+ <translation>Помилка під час видалення переносного кешу шейдерів</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2170"/>
+ <location filename="../../src/yuzu/main.cpp" line="2188"/>
+ <source>A shader cache for this title does not exist.</source>
+ <translation>Кеш шейдерів для цієї гри не існує.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2175"/>
+ <source>Successfully removed the transferable shader cache.</source>
+ <translation>Переносний кеш шейдерів успішно видалено.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2178"/>
+ <source>Failed to remove the transferable shader cache.</source>
+ <translation>Не вдалося видалити переносний кеш шейдерів.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2187"/>
+ <location filename="../../src/yuzu/main.cpp" line="2195"/>
+ <source>Error Removing Transferable Shader Caches</source>
+ <translation>Помилка під час видалення переносного кешу шейдерів</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2193"/>
+ <source>Successfully removed the transferable shader caches.</source>
+ <translation>Переносний кеш шейдерів успішно видалено.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2196"/>
+ <source>Failed to remove the transferable shader cache directory.</source>
+ <translation>Помилка під час видалення папки переносного кешу шейдерів.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2209"/>
+ <location filename="../../src/yuzu/main.cpp" line="2218"/>
+ <source>Error Removing Custom Configuration</source>
+ <translation>Помилка під час видалення користувацького налаштування</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2210"/>
+ <source>A custom configuration for this title does not exist.</source>
+ <translation>Користувацьких налаштувань для цієї гри не існує.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2216"/>
+ <source>Successfully removed the custom game configuration.</source>
+ <translation>Користувацьке налаштування гри успішно видалено.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2219"/>
+ <source>Failed to remove the custom game configuration.</source>
+ <translation>Не вдалося видалити користувацьке налаштування гри.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2226"/>
+ <location filename="../../src/yuzu/main.cpp" line="2305"/>
+ <source>RomFS Extraction Failed!</source>
+ <translation>Не вдалося вилучити RomFS!</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2227"/>
+ <source>There was an error copying the RomFS files or the user cancelled the operation.</source>
+ <translation>Сталася помилка під час копіювання файлів RomFS або користувач скасував операцію.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2285"/>
+ <source>Full</source>
+ <translation>Повний</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2285"/>
+ <source>Skeleton</source>
+ <translation>Скелет</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2287"/>
+ <source>Select RomFS Dump Mode</source>
+ <translation>Виберіть режим дампа RomFS</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2288"/>
+ <source>Please select the how you would like the RomFS dumped.&lt;br&gt;Full will copy all of the files into the new directory while &lt;br&gt;skeleton will only create the directory structure.</source>
+ <translation>Будь ласка, виберіть, як ви хочете виконати дамп RomFS &lt;br&gt;Повний скопіює всі файли в нову папку, тоді як &lt;br&gt;скелет створить лише структуру папок.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2306"/>
+ <source>There is not enough free space at %1 to extract the RomFS. Please free up space or select a different dump directory at Emulation &gt; Configure &gt; System &gt; Filesystem &gt; Dump Root</source>
+ <translation>В %1 недостатньо вільного місця для вилучення RomFS. Будь ласка, звільніть місце або виберіть іншу папку для дампа в Емуляція &gt; Налаштування &gt; Система &gt; Файлова система &gt; Корінь дампа</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2313"/>
+ <source>Extracting RomFS...</source>
+ <translation>Вилучення RomFS...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2313"/>
+ <location filename="../../src/yuzu/main.cpp" line="2499"/>
+ <source>Cancel</source>
+ <translation>Скасувати</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2320"/>
+ <source>RomFS Extraction Succeeded!</source>
+ <translation>Вилучення RomFS пройшло успішно!</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2321"/>
+ <source>The operation completed successfully.</source>
+ <translation>Операція завершилася успішно.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2365"/>
+ <source>Error Opening %1</source>
+ <translation>Помилка відкриття %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2374"/>
+ <source>Select Directory</source>
+ <translation>Обрати папку</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2401"/>
+ <source>Properties</source>
+ <translation>Властивості</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2402"/>
+ <source>The game properties could not be loaded.</source>
+ <translation>Не вдалося завантажити властивості гри.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2419"/>
+ <source>Switch Executable (%1);;All Files (*.*)</source>
+ <comment>%1 is an identifier for the Switch executable file extensions.</comment>
+ <translation>Виконуваний файл Switch (%1);;Усі файли (*.*)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2423"/>
+ <source>Load File</source>
+ <translation>Завантажити файл</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2436"/>
+ <source>Open Extracted ROM Directory</source>
+ <translation>Відкрити папку вилученого ROM&apos;а</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2447"/>
+ <source>Invalid Directory Selected</source>
+ <translation>Вибрано неприпустиму папку</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2448"/>
+ <source>The directory you have selected does not contain a &apos;main&apos; file.</source>
+ <translation>Папка, яку ви вибрали, не містить файлу &apos;main&apos;.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2458"/>
+ <source>Installable Switch File (*.nca *.nsp *.xci);;Nintendo Content Archive (*.nca);;Nintendo Submission Package (*.nsp);;NX Cartridge Image (*.xci)</source>
+ <translation>Встановлюваний файл Switch (*.nca, *.nsp, *.xci);;Архів контенту Nintendo (*.nca);;Пакет подачі Nintendo (*.nsp);;Образ картриджа NX (*.xci)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2463"/>
+ <source>Install Files</source>
+ <translation>Встановити файли</translation>
+ </message>
+ <message numerus="yes">
+ <location filename="../../src/yuzu/main.cpp" line="2507"/>
+ <source>%n file(s) remaining</source>
+ <translation><numerusform>Залишився %n файл</numerusform><numerusform>Залишилося %n файл(ів)</numerusform><numerusform>Залишилося %n файл(ів)</numerusform><numerusform>Залишилося %n файл(ів)</numerusform></translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2509"/>
+ <source>Installing file &quot;%1&quot;...</source>
+ <translation>Встановлення файлу &quot;%1&quot;...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2555"/>
+ <location filename="../../src/yuzu/main.cpp" line="2569"/>
+ <source>Install Results</source>
+ <translation>Результати встановлення</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2556"/>
+ <source>To avoid possible conflicts, we discourage users from installing base games to the NAND.
+Please, only use this feature to install updates and DLC.</source>
+ <translation>Щоб уникнути можливих конфліктів, ми не рекомендуємо користувачам встановлювати ігри в NAND.
+Будь ласка, використовуйте цю функцію тільки для встановлення оновлень і завантажуваного контенту.</translation>
+ </message>
+ <message numerus="yes">
+ <location filename="../../src/yuzu/main.cpp" line="2562"/>
+ <source>%n file(s) were newly installed
+</source>
+ <translation><numerusform>%n файл було нещодавно встановлено
+</numerusform><numerusform>%n файл(ів) було нещодавно встановлено
+</numerusform><numerusform>%n файл(ів) було нещодавно встановлено
+</numerusform><numerusform>%n файл(ів) було нещодавно встановлено
+</numerusform></translation>
+ </message>
+ <message numerus="yes">
+ <location filename="../../src/yuzu/main.cpp" line="2565"/>
+ <source>%n file(s) were overwritten
+</source>
+ <translation><numerusform>%n файл було перезаписано
+</numerusform><numerusform>%n файл(ів) було перезаписано
+</numerusform><numerusform>%n файл(ів) було перезаписано
+</numerusform><numerusform>%n файл(ів) було перезаписано
+</numerusform></translation>
+ </message>
+ <message numerus="yes">
+ <location filename="../../src/yuzu/main.cpp" line="2567"/>
+ <source>%n file(s) failed to install
+</source>
+ <translation><numerusform>%n файл не вдалося встановити
+</numerusform><numerusform>%n файл(ів) не вдалося встановити
+</numerusform><numerusform>%n файл(ів) не вдалося встановити
+</numerusform><numerusform>%n файл(ів) не вдалося встановити
+</numerusform></translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2668"/>
+ <source>System Application</source>
+ <translation>Системний додаток</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2669"/>
+ <source>System Archive</source>
+ <translation>Системний архів</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2670"/>
+ <source>System Application Update</source>
+ <translation>Оновлення системного додатку</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2671"/>
+ <source>Firmware Package (Type A)</source>
+ <translation>Пакет прошивки (Тип А)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2672"/>
+ <source>Firmware Package (Type B)</source>
+ <translation>Пакет прошивки (Тип Б)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2673"/>
+ <source>Game</source>
+ <translation>Гра</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2674"/>
+ <source>Game Update</source>
+ <translation>Оновлення гри</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2675"/>
+ <source>Game DLC</source>
+ <translation>DLC до гри</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2676"/>
+ <source>Delta Title</source>
+ <translation>Дельта-титул</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2679"/>
+ <source>Select NCA Install Type...</source>
+ <translation>Виберіть тип установки NCA...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2680"/>
+ <source>Please select the type of title you would like to install this NCA as:
+(In most instances, the default &apos;Game&apos; is fine.)</source>
+ <translation>Будь ласка, виберіть тип додатку, який ви хочете встановити для цього NCA:
+(У більшості випадків, підходить стандартний вибір &quot;Гра&quot;.)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2686"/>
+ <source>Failed to Install</source>
+ <translation>Помилка встановлення</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2687"/>
+ <source>The title type you selected for the NCA is invalid.</source>
+ <translation>Тип додатку, який ви вибрали для NCA, недійсний.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2722"/>
+ <source>File not found</source>
+ <translation>Файл не знайдено</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2723"/>
+ <source>File &quot;%1&quot; not found</source>
+ <translation>Файл &quot;%1&quot; не знайдено</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2798"/>
+ <source>OK</source>
+ <translation>ОК</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2812"/>
+ <source>Missing yuzu Account</source>
+ <translation>Відсутній обліковий запис yuzu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2813"/>
+ <source>In order to submit a game compatibility test case, you must link your yuzu account.&lt;br&gt;&lt;br/&gt;To link your yuzu account, go to Emulation &amp;gt; Configuration &amp;gt; Web.</source>
+ <translation>Щоб надіслати звіт про сумісність гри, необхідно прив&apos;язати свій обліковий запис yuzu. &lt;br&gt;&lt;br/&gt;Щоб прив&apos;язати свій обліковий запис yuzu, перейдіть у розділ Емуляція &amp;gt; Параметри &amp;gt; Мережа.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2823"/>
+ <source>Error opening URL</source>
+ <translation>Помилка під час відкриття URL</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2824"/>
+ <source>Unable to open the URL &quot;%1&quot;.</source>
+ <translation>Не вдалося відкрити URL: &quot;%1&quot;.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="3120"/>
+ <source>TAS Recording</source>
+ <translation>Запис TAS</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="3121"/>
+ <source>Overwrite file of player 1?</source>
+ <translation>Перезаписати файл гравця 1?</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="3147"/>
+ <source>Invalid config detected</source>
+ <translation>Виявлено неприпустиму конфігурацію</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="3148"/>
+ <source>Handheld controller can&apos;t be used on docked mode. Pro controller will be selected.</source>
+ <translation>Портативний контролер не може бути використаний у режимі док-станції. Буде обрано контролер Pro.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="3243"/>
+ <location filename="../../src/yuzu/main.cpp" line="3271"/>
+ <source>Amiibo</source>
+ <translation>Amiibo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="3243"/>
+ <location filename="../../src/yuzu/main.cpp" line="3271"/>
+ <source>The current amiibo has been removed</source>
+ <translation>Поточний amiibo було прибрано</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="3248"/>
+ <source>Error</source>
+ <translation>Помилка</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="3248"/>
+ <location filename="../../src/yuzu/main.cpp" line="3283"/>
+ <source>The current game is not looking for amiibos</source>
+ <translation>Поточна гра не шукає amiibo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="3254"/>
+ <source>Amiibo File (%1);; All Files (*.*)</source>
+ <translation>Файл Amiibo (%1);; Всі Файли (*.*)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="3255"/>
+ <source>Load Amiibo</source>
+ <translation>Завантажити Amiibo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="3267"/>
+ <source>Error loading Amiibo data</source>
+ <translation>Помилка під час завантаження даних Amiibo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="3277"/>
+ <source>The selected file is not a valid amiibo</source>
+ <translation>Обраний файл не є допустимим amiibo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="3280"/>
+ <source>The selected file is already on use</source>
+ <translation>Обраний файл уже використовується</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="3286"/>
+ <source>An unknown error occurred</source>
+ <translation>Виникла невідома помилка</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="3338"/>
+ <source>Capture Screenshot</source>
+ <translation>Зробити знімок екрану</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="3339"/>
+ <source>PNG Image (*.png)</source>
+ <translation>Зображення PNG (*.png)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="3405"/>
+ <source>TAS state: Running %1/%2</source>
+ <translation>Стан TAS: Виконується %1/%2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="3407"/>
+ <source>TAS state: Recording %1</source>
+ <translation>Стан TAS: Записується %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="3409"/>
+ <source>TAS state: Idle %1/%2</source>
+ <translation>Стан TAS: Простий %1/%2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="3411"/>
+ <source>TAS State: Invalid</source>
+ <translation>Стан TAS: Неприпустимий</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="3425"/>
+ <source>&amp;Stop Running</source>
+ <translation>[&amp;S] Зупинка</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="3425"/>
+ <source>&amp;Start</source>
+ <translation>[&amp;S] Почати</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="3426"/>
+ <source>Stop R&amp;ecording</source>
+ <translation>[&amp;E] Закінчити запис</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="3426"/>
+ <source>R&amp;ecord</source>
+ <translation>[&amp;E] Запис</translation>
+ </message>
+ <message numerus="yes">
+ <location filename="../../src/yuzu/main.cpp" line="3450"/>
+ <source>Building: %n shader(s)</source>
+ <translation><numerusform>Побудова: %n шейдер</numerusform><numerusform>Побудова: %n шейдер(ів)</numerusform><numerusform>Побудова: %n шейдер(ів)</numerusform><numerusform>Побудова: %n шейдер(ів)</numerusform></translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="3459"/>
+ <source>Scale: %1x</source>
+ <comment>%1 is the resolution scaling factor</comment>
+ <translation>Масштаб: %1x</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="3462"/>
+ <source>Speed: %1% / %2%</source>
+ <translation>Швидкість: %1% / %2%</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="3466"/>
+ <source>Speed: %1%</source>
+ <translation>Швидкість: %1%</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="3470"/>
+ <source>Game: %1 FPS (Unlocked)</source>
+ <translation>Гра: %1 FPS (Необмежено)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="3473"/>
+ <source>Game: %1 FPS</source>
+ <translation>Гра: %1 FPS</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="3475"/>
+ <source>Frame: %1 ms</source>
+ <translation>Кадр: %1 мс</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="3486"/>
+ <source>GPU NORMAL</source>
+ <translation>ГП НОРМАЛЬНО</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="3491"/>
+ <source>GPU HIGH</source>
+ <translation>ГП ВИСОКО</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="3496"/>
+ <source>GPU EXTREME</source>
+ <translation>ГП ЕКСТРИМ</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="3501"/>
+ <source>GPU ERROR</source>
+ <translation>ГП ПОМИЛКА</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="3510"/>
+ <source>DOCKED</source>
+ <translation>В ДОК-СТАНЦІЇ</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="3510"/>
+ <source>HANDHELD</source>
+ <translation>ПОРТАТИВНИЙ</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="3517"/>
+ <source>NEAREST</source>
+ <translation>НАЙБЛИЖЧІЙ</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="3520"/>
+ <location filename="../../src/yuzu/main.cpp" line="3535"/>
+ <source>BILINEAR</source>
+ <translation>БІЛІНІЙНИЙ</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="3523"/>
+ <source>BICUBIC</source>
+ <translation>БІКУБІЧНИЙ</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="3526"/>
+ <source>GAUSSIAN</source>
+ <translation>ГАУС</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="3529"/>
+ <source>SCALEFORCE</source>
+ <translation>SCALEFORCE</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="3532"/>
+ <source>FSR</source>
+ <translation>FSR</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="3544"/>
+ <location filename="../../src/yuzu/main.cpp" line="3550"/>
+ <source>NO AA</source>
+ <translation>БЕЗ ЗГЛАДЖУВАННЯ</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="3547"/>
+ <source>FXAA</source>
+ <translation>FXAA</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="3624"/>
+ <source>The game you are trying to load requires additional files from your Switch to be dumped before playing.&lt;br/&gt;&lt;br/&gt;For more information on dumping these files, please see the following wiki page: &lt;a href=&apos;https://yuzu-emu.org/wiki/dumping-system-archives-and-the-shared-fonts-from-a-switch-console/&apos;&gt;Dumping System Archives and the Shared Fonts from a Switch Console&lt;/a&gt;.&lt;br/&gt;&lt;br/&gt;Would you like to quit back to the game list? Continuing emulation may result in crashes, corrupted save data, or other bugs.</source>
+ <translation>Гра, яку ви намагаєтеся завантажити, вимагає, щоб додаткові файли були здамплені з вашого Switch перед початком гри. &lt;br/&gt;&lt;br/&gt;Для отримання додаткової інформації про дамп цих файлів див. наступну вікі: &lt;a href=&apos;https://yuzu-emu.org/wiki/dumping-system-archives-and-the-shared-fonts-from-a-switch-console/&apos;&gt;Дамп системних архівів і загальних шрифтів з консолі&lt;/a&gt;. &lt;br/&gt;&lt;br/&gt;Хочете повернутися до списку ігор? Продовження емуляції може призвести до збоїв, пошкодження збережених даних або інших помилок.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="3639"/>
+ <source>yuzu was unable to locate a Switch system archive. %1</source>
+ <translation>yuzu не вдалося знайти системний архів Switch. %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="3641"/>
+ <source>yuzu was unable to locate a Switch system archive: %1. %2</source>
+ <translation>yuzu не вдалося знайти системний архів Switch: %1. %2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="3645"/>
+ <source>System Archive Not Found</source>
+ <translation>Системний архів не знайдено</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="3647"/>
+ <source>System Archive Missing</source>
+ <translation>Відсутній системний архів</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="3653"/>
+ <source>yuzu was unable to locate the Switch shared fonts. %1</source>
+ <translation>yuzu не вдалося знайти загальні шрифти Switch. %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="3654"/>
+ <source>Shared Fonts Not Found</source>
+ <translation>Загальні шрифти не знайдено</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="3656"/>
+ <source>Shared Font Missing</source>
+ <translation>Загальні шрифти відсутні</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="3662"/>
+ <source>Fatal Error</source>
+ <translation>Фатальна помилка</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="3663"/>
+ <source>yuzu has encountered a fatal error, please see the log for more details. For more information on accessing the log, please see the following page: &lt;a href=&apos;https://community.citra-emu.org/t/how-to-upload-the-log-file/296&apos;&gt;How to Upload the Log File&lt;/a&gt;.&lt;br/&gt;&lt;br/&gt;Would you like to quit back to the game list? Continuing emulation may result in crashes, corrupted save data, or other bugs.</source>
+ <translation>yuzu зіткнувся з фатальною помилкою, перевірте журнал для отримання більш детальної інформації. Для отримання додаткової інформації про доступ до журналу відкрийте наступну сторінку: &lt;a href=&apos;https://community.citra-emu.org/t/how-to-upload-the-log-file/296&apos;&gt;Як завантажити файл журналу&lt;/a&gt;.&lt;br/&gt;&lt;br/&gt;Ви хочете повернутися до списку ігор? Продовження емуляції може призвести до збоїв, пошкодження збережень або інших помилок.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="3672"/>
+ <source>Fatal Error encountered</source>
+ <translation>Сталася фатальна помилка</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="3695"/>
+ <source>Confirm Key Rederivation</source>
+ <translation>Підтвердіть перерахунок ключа</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="3696"/>
+ <source>You are about to force rederive all of your keys.
+If you do not know what this means or what you are doing,
+this is a potentially destructive action.
+Please make sure this is what you want
+and optionally make backups.
+
+This will delete your autogenerated key files and re-run the key derivation module.</source>
+ <translation>Ви збираєтеся примусово перерахувати всі ваші ключі.
+Якщо ви не знаєте, що це означає або що ви робите,
+це потенційно руйнівна дія.
+Будь ласка, переконайтеся, що це те, що ви хочете
+і, по бажанню, зробіть резервні копії.
+
+Це видалить ваші автоматично згенеровані файли ключів і повторно запустить модуль розрахунку ключів.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="3728"/>
+ <source>Missing fuses</source>
+ <translation>Відсутні запобіжники</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="3731"/>
+ <source> - Missing BOOT0</source>
+ <translation>- Відсутній BOOT0</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="3734"/>
+ <source> - Missing BCPKG2-1-Normal-Main</source>
+ <translation>- Відсутній BCPKG2-1-Normal-Main</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="3737"/>
+ <source> - Missing PRODINFO</source>
+ <translation> - Відсутній PRODINFO</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="3741"/>
+ <source>Derivation Components Missing</source>
+ <translation>Компоненти розрахунку відсутні</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="3742"/>
+ <source>Encryption keys are missing. &lt;br&gt;Please follow &lt;a href=&apos;https://yuzu-emu.org/help/quickstart/&apos;&gt;the yuzu quickstart guide&lt;/a&gt; to get all your keys, firmware and games.&lt;br&gt;&lt;br&gt;&lt;small&gt;(%1)&lt;/small&gt;</source>
+ <translation>Ключі шифрування відсутні.&lt;br&gt;Будь ласка, дотримуйтесь &lt;a href=&apos;https://yuzu-emu.org/help/quickstart/&apos;&gt;короткого керівництва користувача yuzu&lt;/a&gt;, щоб отримати всі ваші ключі, прошивку та ігри&lt;br&gt;&lt;br&gt;&lt;small&gt;(%1)&lt;/small&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="3751"/>
+ <source>Deriving keys...
+This may take up to a minute depending
+on your system&apos;s performance.</source>
+ <translation>Отримання ключів...
+Це може зайняти до хвилини залежно від
+від продуктивності вашої системи.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="3753"/>
+ <source>Deriving Keys</source>
+ <translation>Отримання ключів</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="3798"/>
+ <source>Select RomFS Dump Target</source>
+ <translation>Оберіть ціль для дампа RomFS</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="3799"/>
+ <source>Please select which RomFS you would like to dump.</source>
+ <translation>Будь ласка, виберіть, який RomFS ви хочете здампити.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="3814"/>
+ <source>Are you sure you want to close yuzu?</source>
+ <translation>Ви впевнені, що хочете закрити yuzu?</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="3815"/>
+ <location filename="../../src/yuzu/main.cpp" line="3913"/>
+ <location filename="../../src/yuzu/main.cpp" line="3926"/>
+ <source>yuzu</source>
+ <translation>yuzu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="3914"/>
+ <source>Are you sure you want to stop the emulation? Any unsaved progress will be lost.</source>
+ <translation>Ви впевнені, що хочете зупинити емуляцію? Будь-який незбережений прогрес буде втрачено.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="3923"/>
+ <source>The currently running application has requested yuzu to not exit.
+
+Would you like to bypass this and exit anyway?</source>
+ <translation>Запущений на даний момент додаток просить yuzu не завершувати роботу.
+
+Чи хочете ви обійти це і вийти в будь-якому випадку?</translation>
+ </message>
+</context>
+<context>
+ <name>GRenderWindow</name>
+ <message>
+ <location filename="../../src/yuzu/bootmanager.cpp" line="1047"/>
+ <source>OpenGL not available!</source>
+ <translation>OpenGL недоступний!</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/bootmanager.cpp" line="1048"/>
+ <source>yuzu has not been compiled with OpenGL support.</source>
+ <translation>yuzu не було зібрано з підтримкою OpenGL.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/bootmanager.cpp" line="1067"/>
+ <location filename="../../src/yuzu/bootmanager.cpp" line="1087"/>
+ <source>Error while initializing OpenGL!</source>
+ <translation>Помилка під час ініціалізації OpenGL!</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/bootmanager.cpp" line="1068"/>
+ <source>Your GPU may not support OpenGL, or you do not have the latest graphics driver.</source>
+ <translation>Ваш ГП може не підтримувати OpenGL, або у вас встановлено застарілий графічний драйвер.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/bootmanager.cpp" line="1077"/>
+ <source>Error while initializing OpenGL 4.6!</source>
+ <translation>Помилка під час ініціалізації OpenGL 4.6!</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/bootmanager.cpp" line="1078"/>
+ <source>Your GPU may not support OpenGL 4.6, or you do not have the latest graphics driver.&lt;br&gt;&lt;br&gt;GL Renderer:&lt;br&gt;%1</source>
+ <translation>Ваш ГП може не підтримувати OpenGL 4.6, або у вас встановлено застарілий графічний драйвер.&lt;br&gt;&lt;br&gt;Рендерер GL:&lt;br&gt;%1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/bootmanager.cpp" line="1088"/>
+ <source>Your GPU may not support one or more required OpenGL extensions. Please ensure you have the latest graphics driver.&lt;br&gt;&lt;br&gt;GL Renderer:&lt;br&gt;%1&lt;br&gt;&lt;br&gt;Unsupported extensions:&lt;br&gt;%2</source>
+ <translation>Ваш ГП може не підтримувати одне або кілька необхідних розширень OpenGL. Будь ласка, переконайтеся в тому, що у вас встановлено останній графічний драйвер.&lt;br&gt;&lt;br&gt;Рендерер GL:&lt;br&gt;%1&lt;br&gt;&lt;br&gt;Розширення, що не підтримуються:&lt;br&gt;%2</translation>
+ </message>
+</context>
+<context>
+ <name>GameList</name>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="532"/>
+ <source>Favorite</source>
+ <translation>Улюблені</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="534"/>
+ <source>Start Game</source>
+ <translation>Запустити гру</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="536"/>
+ <source>Start Game without Custom Configuration</source>
+ <translation>Запустити гру без користувацького налаштування</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="538"/>
+ <source>Open Save Data Location</source>
+ <translation>Відкрити папку для збережень</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="539"/>
+ <source>Open Mod Data Location</source>
+ <translation>Відкрити папку для модів</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="541"/>
+ <source>Open Transferable Pipeline Cache</source>
+ <translation>Відкрити переносний кеш конвеєра</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="543"/>
+ <source>Remove</source>
+ <translation>Видалити</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="544"/>
+ <source>Remove Installed Update</source>
+ <translation>Видалити встановлене оновлення</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="545"/>
+ <source>Remove All Installed DLC</source>
+ <translation>Видалити усі DLC</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="546"/>
+ <source>Remove Custom Configuration</source>
+ <translation>Видалити користувацьке налаштування</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="547"/>
+ <source>Remove OpenGL Pipeline Cache</source>
+ <translation>Видалити кеш конвеєра OpenGL</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="548"/>
+ <source>Remove Vulkan Pipeline Cache</source>
+ <translation>Видалити кеш конвеєра Vulkan</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="550"/>
+ <source>Remove All Pipeline Caches</source>
+ <translation>Видалити весь кеш конвеєра </translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="551"/>
+ <source>Remove All Installed Contents</source>
+ <translation>Видалити весь встановлений вміст</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="552"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="553"/>
+ <source>Dump RomFS</source>
+ <translation>Дамп RomFS</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="554"/>
+ <source>Dump RomFS to SDMC</source>
+ <translation>Здампити RomFS у SDMC</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="555"/>
+ <source>Copy Title ID to Clipboard</source>
+ <translation>Скопіювати ідентифікатор додатку в буфер обміну</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="556"/>
+ <source>Navigate to GameDB entry</source>
+ <translation>Перейти до сторінки GameDB</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="558"/>
+ <source>Properties</source>
+ <translation>Властивості</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="630"/>
+ <source>Scan Subfolders</source>
+ <translation>Сканувати підпапки</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="631"/>
+ <source>Remove Game Directory</source>
+ <translation>Видалити директорію гри</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="650"/>
+ <source>▲ Move Up</source>
+ <translation>▲ Перемістити вверх</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="651"/>
+ <source>▼ Move Down</source>
+ <translation>▼ Перемістити вниз</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="652"/>
+ <source>Open Directory Location</source>
+ <translation>Відкрити розташування папки</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="697"/>
+ <source>Clear</source>
+ <translation>Очистити</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="761"/>
+ <source>Name</source>
+ <translation>Назва</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="762"/>
+ <source>Compatibility</source>
+ <translation>Сумісність</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="763"/>
+ <source>Add-ons</source>
+ <translation>Доповнення</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="764"/>
+ <source>File type</source>
+ <translation>Тип файлу</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="765"/>
+ <source>Size</source>
+ <translation>Розмір</translation>
+ </message>
+</context>
+<context>
+ <name>GameListItemCompat</name>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="149"/>
+ <source>Perfect</source>
+ <translation>Ідеально</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="149"/>
+ <source>Game functions flawless with no audio or graphical glitches, all tested functionality works as intended without
+any workarounds needed.</source>
+ <translation>Гра працює бездоганно, без звукових або графічних артефактів, усі протестовані функції працюють без обхідних шляхів.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="150"/>
+ <source>Great</source>
+ <translation>Чудово</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="150"/>
+ <source>Game functions with minor graphical or audio glitches and is playable from start to finish. May require some
+workarounds.</source>
+ <translation>Гра працює з невеликими графічними або звуковими артефактами і може бути пройдена від
+ початку до кінця. Можуть знадобитися обхідні шляхи.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="151"/>
+ <source>Okay</source>
+ <translation>Добре</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="151"/>
+ <source>Game functions with major graphical or audio glitches, but game is playable from start to finish with
+workarounds.</source>
+ <translation>Гра працює з істотними графічними або звуковими артефактами, але може бути пройдена
+з використанням обхідних шляхів.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="152"/>
+ <source>Bad</source>
+ <translation>Погано</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="152"/>
+ <source>Game functions, but with major graphical or audio glitches. Unable to progress in specific areas due to glitches
+even with workarounds.</source>
+ <translation>Гра працює, але з істотними графічними або звуковими артефактами.
+У деяких частинах неможливо просунутися навіть з обхідними шляхами.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="153"/>
+ <source>Intro/Menu</source>
+ <translation>Вступ/Меню</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="153"/>
+ <source>Game is completely unplayable due to major graphical or audio glitches. Unable to progress past the Start
+Screen.</source>
+ <translation>У гру неможливо грати через графічні або звукові артефакти.
+Неможливо просунутися далі стартового екрана.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="154"/>
+ <source>Won&apos;t Boot</source>
+ <translation>Не запускається</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="154"/>
+ <source>The game crashes when attempting to startup.</source>
+ <translation>Гра вилітає під час запуску.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="155"/>
+ <source>Not Tested</source>
+ <translation>Не перевірено</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="155"/>
+ <source>The game has not yet been tested.</source>
+ <translation>Гру ще не перевіряли на сумісність.</translation>
+ </message>
+</context>
+<context>
+ <name>GameListPlaceholder</name>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="935"/>
+ <source>Double-click to add a new folder to the game list</source>
+ <translation>Натисніть двічі, щоб додати нову папку до списку ігор</translation>
+ </message>
+</context>
+<context>
+ <name>GameListSearchField</name>
+ <message numerus="yes">
+ <location filename="../../src/yuzu/game_list.cpp" line="86"/>
+ <source>%1 of %n result(s)</source>
+ <translation><numerusform>%1 із %n результат(ів)</numerusform><numerusform>%1 із %n результат(ів)</numerusform><numerusform>%1 із %n результат(ів)</numerusform><numerusform>%1 із %n результат(ів)</numerusform></translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="777"/>
+ <source>Filter:</source>
+ <translation>Пошук:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="778"/>
+ <source>Enter pattern to filter</source>
+ <translation>Введіть текст для пошуку</translation>
+ </message>
+</context>
+<context>
+ <name>HostRoom</name>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/host_room.ui" line="14"/>
+ <source>Create Room</source>
+ <translation>Створити кімнату</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/host_room.ui" line="37"/>
+ <source>Room Name</source>
+ <translation>Назва кімнати</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/host_room.ui" line="51"/>
+ <source>Preferred Game</source>
+ <translation>Переважна гра</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/host_room.ui" line="61"/>
+ <source>Max Players</source>
+ <translation>Максимальна кількість гравців</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/host_room.ui" line="91"/>
+ <source>Username</source>
+ <translation>Ім&apos;я користувача</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/host_room.ui" line="101"/>
+ <source>(Leave blank for open game)</source>
+ <translation>(Залиште порожнім для відкритої гри)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/host_room.ui" line="118"/>
+ <source>Password</source>
+ <translation>Пароль</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/host_room.ui" line="125"/>
+ <source>Port</source>
+ <translation>Порт</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/host_room.ui" line="139"/>
+ <source>Room Description</source>
+ <translation>Опис кімнати</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/host_room.ui" line="153"/>
+ <source>Load Previous Ban List</source>
+ <translation>Завантажити попередній список заблокованих</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/host_room.ui" line="184"/>
+ <source>Public</source>
+ <translation>Публічна</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/host_room.ui" line="189"/>
+ <source>Unlisted</source>
+ <translation>Прихована</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/host_room.ui" line="197"/>
+ <source>Host Room</source>
+ <translation>Створити кімнату</translation>
+ </message>
+</context>
+<context>
+ <name>HostRoomWindow</name>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/host_room.cpp" line="182"/>
+ <source>Error</source>
+ <translation>Помилка</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/host_room.cpp" line="183"/>
+ <source>Failed to announce the room to the public lobby. In order to host a room publicly, you must have a valid yuzu account configured in Emulation -&gt; Configure -&gt; Web. If you do not want to publish a room in the public lobby, then select Unlisted instead.
+Debug Message: </source>
+ <translation>Не вдалося оголосити кімнату в публічному фойє. Щоб хостити публічну кімнату, у вас має бути діючий обліковий запис yuzu, налаштований в Емуляція -&gt; Налаштування -&gt; Мережа. Якщо ви не хочете оголошувати кімнату в публічному лобі, виберіть замість цього прихований тип.
+Повідомлення налагодження:</translation>
+ </message>
+</context>
+<context>
+ <name>Hotkeys</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/config.cpp" line="73"/>
+ <source>Audio Mute/Unmute</source>
+ <translation>Увімкнення/вимкнення звуку</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/config.cpp" line="73"/>
+ <location filename="../../src/yuzu/configuration/config.cpp" line="74"/>
+ <location filename="../../src/yuzu/configuration/config.cpp" line="75"/>
+ <location filename="../../src/yuzu/configuration/config.cpp" line="76"/>
+ <location filename="../../src/yuzu/configuration/config.cpp" line="77"/>
+ <location filename="../../src/yuzu/configuration/config.cpp" line="78"/>
+ <location filename="../../src/yuzu/configuration/config.cpp" line="79"/>
+ <location filename="../../src/yuzu/configuration/config.cpp" line="80"/>
+ <location filename="../../src/yuzu/configuration/config.cpp" line="81"/>
+ <location filename="../../src/yuzu/configuration/config.cpp" line="82"/>
+ <location filename="../../src/yuzu/configuration/config.cpp" line="83"/>
+ <location filename="../../src/yuzu/configuration/config.cpp" line="84"/>
+ <location filename="../../src/yuzu/configuration/config.cpp" line="85"/>
+ <location filename="../../src/yuzu/configuration/config.cpp" line="86"/>
+ <location filename="../../src/yuzu/configuration/config.cpp" line="87"/>
+ <location filename="../../src/yuzu/configuration/config.cpp" line="88"/>
+ <location filename="../../src/yuzu/configuration/config.cpp" line="89"/>
+ <location filename="../../src/yuzu/configuration/config.cpp" line="90"/>
+ <location filename="../../src/yuzu/configuration/config.cpp" line="91"/>
+ <location filename="../../src/yuzu/configuration/config.cpp" line="92"/>
+ <location filename="../../src/yuzu/configuration/config.cpp" line="93"/>
+ <location filename="../../src/yuzu/configuration/config.cpp" line="94"/>
+ <source>Main Window</source>
+ <translation>Основне вікно</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/config.cpp" line="74"/>
+ <source>Audio Volume Down</source>
+ <translation>Зменшити гучність звуку</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/config.cpp" line="75"/>
+ <source>Audio Volume Up</source>
+ <translation>Підвищити гучність звуку</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/config.cpp" line="76"/>
+ <source>Capture Screenshot</source>
+ <translation>Зробити знімок екрану</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/config.cpp" line="77"/>
+ <source>Change Adapting Filter</source>
+ <translation>Змінити адаптуючий фільтр</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/config.cpp" line="78"/>
+ <source>Change Docked Mode</source>
+ <translation>Змінити режим консолі</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/config.cpp" line="79"/>
+ <source>Change GPU Accuracy</source>
+ <translation>Змінити точність ГП</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/config.cpp" line="80"/>
+ <source>Continue/Pause Emulation</source>
+ <translation>Продовження/Пауза емуляції</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/config.cpp" line="81"/>
+ <source>Exit Fullscreen</source>
+ <translation>Вийти з повноекранного режиму</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/config.cpp" line="82"/>
+ <source>Exit yuzu</source>
+ <translation>Вийти з yuzu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/config.cpp" line="83"/>
+ <source>Fullscreen</source>
+ <translation>Повний екран</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/config.cpp" line="84"/>
+ <source>Load File</source>
+ <translation>Завантажити файл</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/config.cpp" line="85"/>
+ <source>Load/Remove Amiibo</source>
+ <translation>Завантажити/видалити Amiibo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/config.cpp" line="86"/>
+ <source>Restart Emulation</source>
+ <translation>Перезапустити емуляцію</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/config.cpp" line="87"/>
+ <source>Stop Emulation</source>
+ <translation>Зупинити емуляцію</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/config.cpp" line="88"/>
+ <source>TAS Record</source>
+ <translation>Запис TAS</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/config.cpp" line="89"/>
+ <source>TAS Reset</source>
+ <translation>Скидання TAS</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/config.cpp" line="90"/>
+ <source>TAS Start/Stop</source>
+ <translation>Старт/Стоп TAS</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/config.cpp" line="91"/>
+ <source>Toggle Filter Bar</source>
+ <translation>Переключити панель пошуку</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/config.cpp" line="92"/>
+ <source>Toggle Framerate Limit</source>
+ <translation>Переключити обмеження частоти кадрів</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/config.cpp" line="93"/>
+ <source>Toggle Mouse Panning</source>
+ <translation>Переключити панорамування миші</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/config.cpp" line="94"/>
+ <source>Toggle Status Bar</source>
+ <translation>Переключити панель стану</translation>
+ </message>
+</context>
+<context>
+ <name>InstallDialog</name>
+ <message>
+ <location filename="../../src/yuzu/install_dialog.cpp" line="29"/>
+ <source>Please confirm these are the files you wish to install.</source>
+ <translation>Будь ласка, переконайтеся, що це ті файли, які ви хочете встановити.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/install_dialog.cpp" line="32"/>
+ <source>Installing an Update or DLC will overwrite the previously installed one.</source>
+ <translation>Встановлення оновлення або завантажуваного контенту перезапише раніше встановлене.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/install_dialog.cpp" line="36"/>
+ <source>Install</source>
+ <translation>Встановити</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/install_dialog.cpp" line="50"/>
+ <source>Install Files to NAND</source>
+ <translation>Встановити файли в NAND</translation>
+ </message>
+</context>
+<context>
+ <name>LimitableInputDialog</name>
+ <message>
+ <location filename="../../src/yuzu/util/limitable_input_dialog.cpp" line="61"/>
+ <source>The text can't contain any of the following characters:
+%1</source>
+ <translation>У тексті неприпустимі такі символи:
+%1</translation>
+ </message>
+</context>
+<context>
+ <name>LoadingScreen</name>
+ <message>
+ <location filename="../../src/yuzu/loading_screen.ui" line="84"/>
+ <source>Loading Shaders 387 / 1628</source>
+ <translation>Завантаження шейдерів 387 / 1628</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/loading_screen.ui" line="121"/>
+ <source>Loading Shaders %v out of %m</source>
+ <translation>Завантаження шейдерів %v із %m</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/loading_screen.ui" line="135"/>
+ <source>Estimated Time 5m 4s</source>
+ <translation>Залишилося приблизно 5м 4с</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/loading_screen.cpp" line="83"/>
+ <source>Loading...</source>
+ <translation>Завантаження...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/loading_screen.cpp" line="84"/>
+ <source>Loading Shaders %1 / %2</source>
+ <translation>Завантаження шейдерів %1 / %2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/loading_screen.cpp" line="85"/>
+ <source>Launching...</source>
+ <translation>Запуск...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/loading_screen.cpp" line="170"/>
+ <source>Estimated Time %1</source>
+ <translation>Залишилося приблизно %1</translation>
+ </message>
+</context>
+<context>
+ <name>Lobby</name>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/lobby.ui" line="14"/>
+ <source>Public Room Browser</source>
+ <translation>Браузер публічних кімнат</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/lobby.ui" line="32"/>
+ <location filename="../../src/yuzu/multiplayer/lobby.ui" line="39"/>
+ <source>Nickname</source>
+ <translation>Псевдонім</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/lobby.ui" line="59"/>
+ <source>Filters</source>
+ <translation>Фільтри</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/lobby.ui" line="66"/>
+ <source>Search</source>
+ <translation>Пошук</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/lobby.ui" line="76"/>
+ <source>Games I Own</source>
+ <translation>Ігри, якими я володію</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/lobby.ui" line="83"/>
+ <source>Hide Full Rooms</source>
+ <translation>Приховати повні кімнати</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/lobby.ui" line="103"/>
+ <source>Refresh Lobby</source>
+ <translation>Оновити фойє</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/lobby.cpp" line="112"/>
+ <source>Password Required to Join</source>
+ <translation>Для входу необхідний пароль</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/lobby.cpp" line="112"/>
+ <source>Password:</source>
+ <translation>Пароль:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/lobby.cpp" line="215"/>
+ <source>Players</source>
+ <translation>Гравці</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/lobby.cpp" line="216"/>
+ <source>Room Name</source>
+ <translation>Назва кімнати</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/lobby.cpp" line="217"/>
+ <source>Preferred Game</source>
+ <translation>Переважна гра</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/lobby.cpp" line="218"/>
+ <source>Host</source>
+ <translation>Хост</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/lobby.cpp" line="225"/>
+ <source>Refreshing</source>
+ <translation>Оновлення</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/lobby.cpp" line="282"/>
+ <source>Refresh List</source>
+ <translation>Оновити список</translation>
+ </message>
+</context>
+<context>
+ <name>MainWindow</name>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="14"/>
+ <source>yuzu</source>
+ <translation>yuzu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="44"/>
+ <source>&amp;File</source>
+ <translation>[&amp;F] Файл</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="48"/>
+ <source>&amp;Recent Files</source>
+ <translation>[&amp;R] Нещодавні файли</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="67"/>
+ <source>&amp;Emulation</source>
+ <translation>[&amp;E] Емуляція</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="78"/>
+ <source>&amp;View</source>
+ <translation>[&amp;V] Вигляд</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="82"/>
+ <source>&amp;Reset Window Size</source>
+ <translation>[&amp;R] Скинути розмір вікна</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="87"/>
+ <source>&amp;Debugging</source>
+ <translation>[&amp;D] Налагодження</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="92"/>
+ <source>Reset Window Size to &amp;720p</source>
+ <translation>Скинути розмір вікна до &amp;720p</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="95"/>
+ <source>Reset Window Size to 720p</source>
+ <translation>Скинути розмір вікна до 720p</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="100"/>
+ <source>Reset Window Size to &amp;900p</source>
+ <translation>Скинути розмір вікна до &amp;900p</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="103"/>
+ <source>Reset Window Size to 900p</source>
+ <translation>Скинути розмір вікна до 900p</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="108"/>
+ <source>Reset Window Size to &amp;1080p</source>
+ <translation>Скинути розмір вікна до &amp;1080p</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="111"/>
+ <source>Reset Window Size to 1080p</source>
+ <translation>Скинути розмір вікна до 1080p</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="128"/>
+ <source>&amp;Multiplayer</source>
+ <translation>[&amp;M] Мультиплеєр</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="139"/>
+ <source>&amp;Tools</source>
+ <translation>[&amp;T] Інструменти</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="143"/>
+ <source>&amp;TAS</source>
+ <translation>[&amp;T] TAS</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="158"/>
+ <source>&amp;Help</source>
+ <translation>[&amp;H] Допомога</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="179"/>
+ <source>&amp;Install Files to NAND...</source>
+ <translation>[&amp;I] Встановити файли в NAND...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="184"/>
+ <source>L&amp;oad File...</source>
+ <translation>[&amp;O] Завантажити файл...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="189"/>
+ <source>Load &amp;Folder...</source>
+ <translation>[&amp;F] Завантажити папку...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="194"/>
+ <source>E&amp;xit</source>
+ <translation>[&amp;X] Вихід</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="202"/>
+ <source>&amp;Pause</source>
+ <translation>[&amp;P] Пауза</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="210"/>
+ <source>&amp;Stop</source>
+ <translation>[&amp;S] Стоп</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="215"/>
+ <source>&amp;Reinitialize keys...</source>
+ <translation>[&amp;R] Переініціалізувати ключі...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="220"/>
+ <source>&amp;About yuzu</source>
+ <translation>[&amp;A] Про yuzu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="228"/>
+ <source>Single &amp;Window Mode</source>
+ <translation>[&amp;W] Режим одного вікна</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="233"/>
+ <source>Con&amp;figure...</source>
+ <translation>[&amp;F] Налаштування...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="241"/>
+ <source>Display D&amp;ock Widget Headers</source>
+ <translation>[&amp;O] Відображати заголовки віджетів дока</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="249"/>
+ <source>Show &amp;Filter Bar</source>
+ <translation>[&amp;F] Показати панель пошуку</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="257"/>
+ <source>Show &amp;Status Bar</source>
+ <translation>[&amp;S] Показати панель статусу</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="260"/>
+ <source>Show Status Bar</source>
+ <translation>Показати панель статусу</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="268"/>
+ <source>&amp;Browse Public Game Lobby</source>
+ <translation>[&amp;B] Переглянути публічні ігрові фойє</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="276"/>
+ <source>&amp;Create Room</source>
+ <translation>[&amp;C] Створити кімнату</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="284"/>
+ <source>&amp;Leave Room</source>
+ <translation>[&amp;L] Залишити кімнату</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="289"/>
+ <source>&amp;Direct Connect to Room</source>
+ <translation>[&amp;D] Пряме під&apos;єднання до кімнати</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="297"/>
+ <source>&amp;Show Current Room</source>
+ <translation>[&amp;S] Показати поточну кімнату</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="305"/>
+ <source>F&amp;ullscreen</source>
+ <translation>[&amp;U] Повноекранний</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="313"/>
+ <source>&amp;Restart</source>
+ <translation>[&amp;R] Перезапустити</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="321"/>
+ <source>Load/Remove &amp;Amiibo...</source>
+ <translation>[&amp;A] Завантажити/Видалити Amiibo...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="329"/>
+ <source>&amp;Report Compatibility</source>
+ <translation>[&amp;R] Повідомити про сумісність</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="337"/>
+ <source>Open &amp;Mods Page</source>
+ <translation>[&amp;M] Відкрити сторінку модів</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="342"/>
+ <source>Open &amp;Quickstart Guide</source>
+ <translation>[&amp;Q] Відкрити посібник користувача</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="347"/>
+ <source>&amp;FAQ</source>
+ <translation>[&amp;F] ЧАП</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="352"/>
+ <source>Open &amp;yuzu Folder</source>
+ <translation>[&amp;Y] Відкрити папку yuzu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="360"/>
+ <source>&amp;Capture Screenshot</source>
+ <translation>[&amp;C] Зробити знімок екрану</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="365"/>
+ <source>&amp;Configure TAS...</source>
+ <translation>[&amp;C] Налаштування TAS...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="373"/>
+ <source>Configure C&amp;urrent Game...</source>
+ <translation>[&amp;U] Налаштувати поточну гру...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="381"/>
+ <source>&amp;Start</source>
+ <translation>[&amp;S] Почати</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="389"/>
+ <source>&amp;Reset</source>
+ <translation>[&amp;S] Скинути</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="397"/>
+ <source>R&amp;ecord</source>
+ <translation>[&amp;E] Запис</translation>
+ </message>
+</context>
+<context>
+ <name>MicroProfileDialog</name>
+ <message>
+ <location filename="../../src/yuzu/debugger/profiler.cpp" line="50"/>
+ <source>&amp;MicroProfile</source>
+ <translation>[&amp;M] MicroProfile</translation>
+ </message>
+</context>
+<context>
+ <name>ModerationDialog</name>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/moderation_dialog.ui" line="6"/>
+ <source>Moderation</source>
+ <translation>Модерація</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/moderation_dialog.ui" line="20"/>
+ <source>Ban List</source>
+ <translation>Список заблокованих</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/moderation_dialog.ui" line="41"/>
+ <location filename="../../src/yuzu/multiplayer/moderation_dialog.cpp" line="73"/>
+ <source>Refreshing</source>
+ <translation>Оновлення</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/moderation_dialog.ui" line="51"/>
+ <source>Unban</source>
+ <translation>Розблокувати</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/moderation_dialog.cpp" line="40"/>
+ <source>Subject</source>
+ <translation>Суб&apos;єкт</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/moderation_dialog.cpp" line="41"/>
+ <source>Type</source>
+ <translation>Тип</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/moderation_dialog.cpp" line="83"/>
+ <source>Forum Username</source>
+ <translation>Ім&apos;я користувача на форумі</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/moderation_dialog.cpp" line="88"/>
+ <source>IP Address</source>
+ <translation>IP-адреса</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/moderation_dialog.cpp" line="95"/>
+ <source>Refresh</source>
+ <translation>Оновити</translation>
+ </message>
+</context>
+<context>
+ <name>MultiplayerState</name>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/state.cpp" line="90"/>
+ <source>Current connection status</source>
+ <translation>Поточний стан з&apos;єднання</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/state.cpp" line="117"/>
+ <source>Not Connected. Click here to find a room!</source>
+ <translation>Не з&apos;єднано. Натисніть тут, щоб знайти кімнату!</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/state.cpp" line="123"/>
+ <source>Not Connected</source>
+ <translation>Не з&apos;єднано</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/state.cpp" line="129"/>
+ <source>Connected</source>
+ <translation>З&apos;єднано</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/state.cpp" line="136"/>
+ <source>New Messages Received</source>
+ <translation>Отримано нові повідомлення</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/state.cpp" line="207"/>
+ <source>Error</source>
+ <translation>Помилка</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/state.cpp" line="208"/>
+ <source>Failed to update the room information. Please check your Internet connection and try hosting the room again.
+Debug Message: </source>
+ <translation>Не вдалося оновити інформацію про кімнату. Будь ласка, перевірте підключення до Інтернету та спробуйте знову зайти в кімнату.
+Повідомлення налагодження:</translation>
+ </message>
+</context>
+<context>
+ <name>NetworkMessage</name>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/message.cpp" line="11"/>
+ <source>Username is not valid. Must be 4 to 20 alphanumeric characters.</source>
+ <translation>Ім&apos;я користувача неприпустиме. Має бути від 4 до 20 буквено-цифрових символів.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/message.cpp" line="13"/>
+ <source>Room name is not valid. Must be 4 to 20 alphanumeric characters.</source>
+ <translation>Назва кімнати неприпустима. Має бути від 4 до 20 буквено-цифрових символів.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/message.cpp" line="15"/>
+ <source>Username is already in use or not valid. Please choose another.</source>
+ <translation>Ім&apos;я користувача вже використовується або недійсне. Будь ласка, виберіть інше.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/message.cpp" line="17"/>
+ <source>IP is not a valid IPv4 address.</source>
+ <translation>IP-адреса не є дійсною адресою IPv4.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/message.cpp" line="19"/>
+ <source>Port must be a number between 0 to 65535.</source>
+ <translation>Порт повинен бути числом від 0 до 65535.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/message.cpp" line="20"/>
+ <source>You must choose a Preferred Game to host a room. If you do not have any games in your game list yet, add a game folder by clicking on the plus icon in the game list.</source>
+ <translation>Ви повинні вибрати бажану гру, щоб хостити кімнату. Якщо у вашому списку ігор ще немає жодної гри, додайте папку з грою, натиснувши на значок плюса у списку ігор.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/message.cpp" line="24"/>
+ <source>Unable to find an internet connection. Check your internet settings.</source>
+ <translation>Неможливо знайти підключення до Інтернету. Перевірте налаштування інтернету.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/message.cpp" line="26"/>
+ <source>Unable to connect to the host. Verify that the connection settings are correct. If you still cannot connect, contact the room host and verify that the host is properly configured with the external port forwarded.</source>
+ <translation>Неможливо підключитися до хоста. Перевірте правильність налаштувань підключення. Якщо під&apos;єднання, як і раніше, неможливе, зв&apos;яжіться з хостом кімнати та переконайтеся, що хост правильно налаштований із прокинутим зовнішнім портом.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/message.cpp" line="30"/>
+ <source>Unable to connect to the room because it is already full.</source>
+ <translation>Неможливо підключитися до кімнати, оскільки вона вже заповнена.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/message.cpp" line="32"/>
+ <source>Creating a room failed. Please retry. Restarting yuzu might be necessary.</source>
+ <translation>Створення кімнати не вдалося. Будь ласка, повторіть спробу. Можливо, потрібно перезапустити yuzu.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/message.cpp" line="34"/>
+ <source>The host of the room has banned you. Speak with the host to unban you or try a different room.</source>
+ <translation>Хост кімнати заблокував вас. Поговоріть із хостом, щоб він розблокував вас, або спробуйте іншу кімнату.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/message.cpp" line="37"/>
+ <source>Version mismatch! Please update to the latest version of yuzu. If the problem persists, contact the room host and ask them to update the server.</source>
+ <translation>Невідповідність версій! Будь ласка, оновіть yuzu до останньої версії. Якщо проблема не зникне, зверніться до хосту кімнати і попросіть його оновити сервер.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/message.cpp" line="39"/>
+ <source>Incorrect password.</source>
+ <translation>Невірний пароль.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/message.cpp" line="40"/>
+ <source>An unknown error occurred. If this error continues to occur, please open an issue</source>
+ <translation>Сталася невідома помилка. Якщо ця помилка продовжує виникати, будь ласка, відкрийте проблему</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/message.cpp" line="43"/>
+ <source>Connection to room lost. Try to reconnect.</source>
+ <translation>З&apos;єднання з кімнатою втрачено. Спробуйте підключитися знову.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/message.cpp" line="45"/>
+ <source>You have been kicked by the room host.</source>
+ <translation>Вас вигнав хост кімнати.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/message.cpp" line="47"/>
+ <source>IP address is already in use. Please choose another.</source>
+ <translation>IP-адреса вже використовується. Будь ласка, виберіть іншу.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/message.cpp" line="49"/>
+ <source>You do not have enough permission to perform this action.</source>
+ <translation>У вас немає достатніх дозволів для виконання цієї дії.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/message.cpp" line="50"/>
+ <source>The user you are trying to kick/ban could not be found.
+They may have left the room.</source>
+ <translation>Користувача, якого ви намагаєтеся вигнати/заблокувати, не знайдено.
+Можливо, вони покинули кімнату.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/message.cpp" line="52"/>
+ <source>No valid network interface is selected.
+Please go to Configure -&gt; System -&gt; Network and make a selection.</source>
+ <translation>Не вибрано припустимий інтерфейс мережі.
+Будь ласка, перейдіть у Налаштування -&gt; Система -&gt; Мережа та зробіть вибір.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/message.cpp" line="68"/>
+ <source>Game already running</source>
+ <translation>Гру вже запущено</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/message.cpp" line="69"/>
+ <source>Joining a room when the game is already running is discouraged and can cause the room feature not to work correctly.
+Proceed anyway?</source>
+ <translation>Приєднуватися до кімнати, коли гру вже запущено, не рекомендується, це може призвести до неправильної роботи функції кімнати.
+Все одно продовжити?</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/message.cpp" line="75"/>
+ <source>Leave Room</source>
+ <translation>Залишити кімнату</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/message.cpp" line="76"/>
+ <source>You are about to close the room. Any network connections will be closed.</source>
+ <translation>Ви збираєтеся закрити кімнату. Усі мережеві підключення буде закрито.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/message.cpp" line="81"/>
+ <source>Disconnect</source>
+ <translation>Від&apos;єднатися</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/message.cpp" line="82"/>
+ <source>You are about to leave the room. Any network connections will be closed.</source>
+ <translation>Ви збираєтеся покинути кімнату. Усі мережеві підключення буде закрито.</translation>
+ </message>
+</context>
+<context>
+ <name>NetworkMessage::ErrorManager</name>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/message.cpp" line="63"/>
+ <source>Error</source>
+ <translation>Помилка</translation>
+ </message>
+</context>
+<context>
+ <name>OverlayDialog</name>
+ <message>
+ <location filename="../../src/yuzu/util/overlay_dialog.ui" line="14"/>
+ <source>Dialog</source>
+ <translation>Діалог</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/util/overlay_dialog.ui" line="134"/>
+ <location filename="../../src/yuzu/util/overlay_dialog.ui" line="353"/>
+ <source>Cancel</source>
+ <translation>Скасувати</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/util/overlay_dialog.ui" line="152"/>
+ <location filename="../../src/yuzu/util/overlay_dialog.ui" line="371"/>
+ <source>OK</source>
+ <translation>ОК</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/util/overlay_dialog.ui" line="313"/>
+ <source>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
+&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
+p, li { white-space: pre-wrap; }
+&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'MS Shell Dlg 2'; font-size:18pt; font-weight:400; font-style:normal;&quot;&gt;
+&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
+&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
+p, li { white-space: pre-wrap; }
+&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:&apos;MS Shell Dlg 2&apos;; font-size:18pt; font-weight:400; font-style:normal;&quot;&gt;
+&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+</context>
+<context>
+ <name>PlayerControlPreview</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player_widget.cpp" line="1575"/>
+ <source>START/PAUSE</source>
+ <translation>СТАРТ/ПАУЗА</translation>
+ </message>
+</context>
+<context>
+ <name>QObject</name>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/lobby_p.h" line="236"/>
+ <source>%1 is not playing a game</source>
+ <translation>%1 не грає у гру</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/lobby_p.h" line="238"/>
+ <source>%1 is playing %2</source>
+ <translation>%1 грає в %2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/chat_room.cpp" line="142"/>
+ <source>Not playing a game</source>
+ <translation>Не грає в гру</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="242"/>
+ <source>Installed SD Titles</source>
+ <translation>Встановлені SD ігри</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="250"/>
+ <source>Installed NAND Titles</source>
+ <translation>Встановлені NAND ігри</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="258"/>
+ <source>System Titles</source>
+ <translation>Системні ігри</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="301"/>
+ <source>Add New Game Directory</source>
+ <translation>Додати нову папку з іграми</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="324"/>
+ <source>Favorites</source>
+ <translation>Улюблені</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="21"/>
+ <location filename="../../src/yuzu/configuration/configure_ringcon.cpp" line="30"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="41"/>
+ <source>Shift</source>
+ <translation>Shift</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="23"/>
+ <location filename="../../src/yuzu/configuration/configure_ringcon.cpp" line="32"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="43"/>
+ <source>Ctrl</source>
+ <translation>Ctrl</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="25"/>
+ <location filename="../../src/yuzu/configuration/configure_ringcon.cpp" line="34"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="45"/>
+ <source>Alt</source>
+ <translation>Alt</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="35"/>
+ <location filename="../../src/yuzu/configuration/configure_ringcon.cpp" line="318"/>
+ <location filename="../../src/yuzu/configuration/configure_ringcon.cpp" line="384"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="159"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="226"/>
+ <source>[not set]</source>
+ <translation>[не задано]</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="47"/>
+ <source>Hat %1 %2</source>
+ <translation>Напр. %1 %2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="54"/>
+ <location filename="../../src/yuzu/configuration/configure_ringcon.cpp" line="407"/>
+ <location filename="../../src/yuzu/configuration/configure_ringcon.cpp" line="411"/>
+ <location filename="../../src/yuzu/configuration/configure_ringcon.cpp" line="415"/>
+ <location filename="../../src/yuzu/configuration/configure_ringcon.cpp" line="419"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="249"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="253"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="257"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="261"/>
+ <source>Axis %1%2</source>
+ <translation>Ось %1%2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="60"/>
+ <source>Button %1</source>
+ <translation>Кнопка %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="66"/>
+ <location filename="../../src/yuzu/configuration/configure_ringcon.cpp" line="378"/>
+ <location filename="../../src/yuzu/configuration/configure_ringcon.cpp" line="392"/>
+ <location filename="../../src/yuzu/configuration/configure_ringcon.cpp" line="422"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="220"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="234"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="264"/>
+ <source>[unknown]</source>
+ <translation>[невідомо]</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ringcon.cpp" line="45"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="56"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="124"/>
+ <source>Left</source>
+ <translation>Вліво</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ringcon.cpp" line="47"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="58"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="127"/>
+ <source>Right</source>
+ <translation>Вправо</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ringcon.cpp" line="49"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="60"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="133"/>
+ <source>Down</source>
+ <translation>Вниз</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ringcon.cpp" line="51"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="62"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="130"/>
+ <source>Up</source>
+ <translation>Вгору</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ringcon.cpp" line="53"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="64"/>
+ <source>Z</source>
+ <translation>Z</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ringcon.cpp" line="55"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="66"/>
+ <source>R</source>
+ <translation>R</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ringcon.cpp" line="57"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="68"/>
+ <source>L</source>
+ <translation>L</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ringcon.cpp" line="59"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="70"/>
+ <source>A</source>
+ <translation>A</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ringcon.cpp" line="61"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="72"/>
+ <source>B</source>
+ <translation>B</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ringcon.cpp" line="63"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="74"/>
+ <source>X</source>
+ <translation>X</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ringcon.cpp" line="65"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="76"/>
+ <source>Y</source>
+ <translation>Y</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ringcon.cpp" line="67"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="78"/>
+ <source>Start</source>
+ <translation>Start</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ringcon.cpp" line="69"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="80"/>
+ <source>L1</source>
+ <translation>L1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ringcon.cpp" line="71"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="82"/>
+ <source>L2</source>
+ <translation>L2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ringcon.cpp" line="73"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="84"/>
+ <source>L3</source>
+ <translation>L3</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ringcon.cpp" line="75"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="86"/>
+ <source>R1</source>
+ <translation>R1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ringcon.cpp" line="77"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="88"/>
+ <source>R2</source>
+ <translation>R2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ringcon.cpp" line="79"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="90"/>
+ <source>R3</source>
+ <translation>R3</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ringcon.cpp" line="81"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="92"/>
+ <source>Circle</source>
+ <translation>Кружечок</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ringcon.cpp" line="83"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="94"/>
+ <source>Cross</source>
+ <translation>Хрестик</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ringcon.cpp" line="85"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="96"/>
+ <source>Square</source>
+ <translation>Квадратик</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ringcon.cpp" line="87"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="98"/>
+ <source>Triangle</source>
+ <translation>Трикутничок</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ringcon.cpp" line="89"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="100"/>
+ <source>Share</source>
+ <translation>Share</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ringcon.cpp" line="91"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="102"/>
+ <source>Options</source>
+ <translation>Options</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ringcon.cpp" line="93"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="118"/>
+ <source>[undefined]</source>
+ <translation>[невизначено]</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ringcon.cpp" line="328"/>
+ <source>%1%2</source>
+ <translation>%1%2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ringcon.cpp" line="332"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="174"/>
+ <source>[invalid]</source>
+ <translation>[неприпустимо]</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ringcon.cpp" line="342"/>
+ <location filename="../../src/yuzu/configuration/configure_ringcon.cpp" line="366"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="184"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="208"/>
+ <source>%1%2Hat %3</source>
+ <translation>%1%2Напр. %3</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ringcon.cpp" line="346"/>
+ <location filename="../../src/yuzu/configuration/configure_ringcon.cpp" line="369"/>
+ <location filename="../../src/yuzu/configuration/configure_ringcon.cpp" line="372"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="188"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="211"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="214"/>
+ <source>%1%2Axis %3</source>
+ <translation>%1%2Ось %3</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ringcon.cpp" line="352"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="194"/>
+ <source>%1%2Axis %3,%4,%5</source>
+ <translation>%1%2Ось %3,%4,%5</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ringcon.cpp" line="356"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="198"/>
+ <source>%1%2Motion %3</source>
+ <translation>%1%2Рух %3</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ringcon.cpp" line="360"/>
+ <location filename="../../src/yuzu/configuration/configure_ringcon.cpp" line="375"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="202"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="217"/>
+ <source>%1%2Button %3</source>
+ <translation>%1%2Кнопка %3</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ringcon.cpp" line="402"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="244"/>
+ <source>[unused]</source>
+ <translation>[не використаний]</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="104"/>
+ <source>Home</source>
+ <translation>Home</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="106"/>
+ <source>Touch</source>
+ <translation>Сенсор</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="108"/>
+ <source>Wheel</source>
+ <comment>Indicates the mouse wheel</comment>
+ <translation>Коліщатко</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="110"/>
+ <source>Backward</source>
+ <translation>Назад</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="112"/>
+ <source>Forward</source>
+ <translation>Вперед</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="114"/>
+ <source>Task</source>
+ <translation>Задача</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="116"/>
+ <source>Extra</source>
+ <translation>Додаткова</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="170"/>
+ <source>%1%2%3</source>
+ <translation>%1%2%3</translation>
+ </message>
+</context>
+<context>
+ <name>QtControllerSelectorDialog</name>
+ <message>
+ <location filename="../../src/yuzu/applets/qt_controller.ui" line="14"/>
+ <source>Controller Applet</source>
+ <translation>Аплет контролера</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/qt_controller.ui" line="129"/>
+ <source>Supported Controller Types:</source>
+ <translation>Підтримувані типи контролерів:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/qt_controller.ui" line="282"/>
+ <source>Players:</source>
+ <translation>Гравці:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/qt_controller.ui" line="300"/>
+ <source>1 - 8</source>
+ <translation>1 - 8</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/qt_controller.ui" line="418"/>
+ <source>P4</source>
+ <translation>P4</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/qt_controller.ui" line="514"/>
+ <location filename="../../src/yuzu/applets/qt_controller.ui" line="711"/>
+ <location filename="../../src/yuzu/applets/qt_controller.ui" line="912"/>
+ <location filename="../../src/yuzu/applets/qt_controller.ui" line="1222"/>
+ <location filename="../../src/yuzu/applets/qt_controller.ui" line="1459"/>
+ <location filename="../../src/yuzu/applets/qt_controller.ui" line="1656"/>
+ <location filename="../../src/yuzu/applets/qt_controller.ui" line="1857"/>
+ <location filename="../../src/yuzu/applets/qt_controller.ui" line="2054"/>
+ <location filename="../../src/yuzu/applets/qt_controller.cpp" line="414"/>
+ <source>Pro Controller</source>
+ <translation>Контролер Pro</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/qt_controller.ui" line="519"/>
+ <location filename="../../src/yuzu/applets/qt_controller.ui" line="716"/>
+ <location filename="../../src/yuzu/applets/qt_controller.ui" line="917"/>
+ <location filename="../../src/yuzu/applets/qt_controller.ui" line="1227"/>
+ <location filename="../../src/yuzu/applets/qt_controller.ui" line="1464"/>
+ <location filename="../../src/yuzu/applets/qt_controller.ui" line="1661"/>
+ <location filename="../../src/yuzu/applets/qt_controller.ui" line="1862"/>
+ <location filename="../../src/yuzu/applets/qt_controller.ui" line="2059"/>
+ <location filename="../../src/yuzu/applets/qt_controller.cpp" line="418"/>
+ <source>Dual Joycons</source>
+ <translation>Подвійні Joy-Con&apos;и</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/qt_controller.ui" line="524"/>
+ <location filename="../../src/yuzu/applets/qt_controller.ui" line="721"/>
+ <location filename="../../src/yuzu/applets/qt_controller.ui" line="922"/>
+ <location filename="../../src/yuzu/applets/qt_controller.ui" line="1232"/>
+ <location filename="../../src/yuzu/applets/qt_controller.ui" line="1469"/>
+ <location filename="../../src/yuzu/applets/qt_controller.ui" line="1666"/>
+ <location filename="../../src/yuzu/applets/qt_controller.ui" line="1867"/>
+ <location filename="../../src/yuzu/applets/qt_controller.ui" line="2064"/>
+ <location filename="../../src/yuzu/applets/qt_controller.cpp" line="422"/>
+ <source>Left Joycon</source>
+ <translation>Лівий Joy-Con</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/qt_controller.ui" line="529"/>
+ <location filename="../../src/yuzu/applets/qt_controller.ui" line="726"/>
+ <location filename="../../src/yuzu/applets/qt_controller.ui" line="927"/>
+ <location filename="../../src/yuzu/applets/qt_controller.ui" line="1237"/>
+ <location filename="../../src/yuzu/applets/qt_controller.ui" line="1474"/>
+ <location filename="../../src/yuzu/applets/qt_controller.ui" line="1671"/>
+ <location filename="../../src/yuzu/applets/qt_controller.ui" line="1872"/>
+ <location filename="../../src/yuzu/applets/qt_controller.ui" line="2069"/>
+ <location filename="../../src/yuzu/applets/qt_controller.cpp" line="426"/>
+ <source>Right Joycon</source>
+ <translation>Правий Joy-Con</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/qt_controller.ui" line="538"/>
+ <location filename="../../src/yuzu/applets/qt_controller.ui" line="735"/>
+ <location filename="../../src/yuzu/applets/qt_controller.ui" line="941"/>
+ <location filename="../../src/yuzu/applets/qt_controller.ui" line="1246"/>
+ <location filename="../../src/yuzu/applets/qt_controller.ui" line="1483"/>
+ <location filename="../../src/yuzu/applets/qt_controller.ui" line="1680"/>
+ <location filename="../../src/yuzu/applets/qt_controller.ui" line="1881"/>
+ <location filename="../../src/yuzu/applets/qt_controller.ui" line="2078"/>
+ <source>Use Current Config</source>
+ <translation>Використовувати поточну конфігурацію</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/qt_controller.ui" line="615"/>
+ <source>P2</source>
+ <translation>P2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/qt_controller.ui" line="812"/>
+ <source>P1</source>
+ <translation>P1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/qt_controller.ui" line="932"/>
+ <location filename="../../src/yuzu/applets/qt_controller.ui" line="2303"/>
+ <location filename="../../src/yuzu/applets/qt_controller.cpp" line="430"/>
+ <source>Handheld</source>
+ <translation>Портативний</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/qt_controller.ui" line="1126"/>
+ <source>P3</source>
+ <translation>P3</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/qt_controller.ui" line="1363"/>
+ <source>P7</source>
+ <translation>P7</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/qt_controller.ui" line="1560"/>
+ <source>P8</source>
+ <translation>P8</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/qt_controller.ui" line="1757"/>
+ <source>P5</source>
+ <translation>P5</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/qt_controller.ui" line="1958"/>
+ <source>P6</source>
+ <translation>P6</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/qt_controller.ui" line="2272"/>
+ <source>Console Mode</source>
+ <translation>Режим консолі</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/qt_controller.ui" line="2293"/>
+ <source>Docked</source>
+ <translation>У док-станції</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/qt_controller.ui" line="2313"/>
+ <source>Vibration</source>
+ <translation>Вібрація</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/qt_controller.ui" line="2349"/>
+ <location filename="../../src/yuzu/applets/qt_controller.ui" line="2395"/>
+ <source>Configure</source>
+ <translation>Налаштувати</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/qt_controller.ui" line="2359"/>
+ <source>Motion</source>
+ <translation>Рух</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/qt_controller.ui" line="2405"/>
+ <source>Profiles</source>
+ <translation>Профілі</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/qt_controller.ui" line="2432"/>
+ <source>Create</source>
+ <translation>Створити</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/qt_controller.ui" line="2467"/>
+ <source>Controllers</source>
+ <translation>Контролери</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/qt_controller.ui" line="2481"/>
+ <source>1</source>
+ <translation>1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/qt_controller.ui" line="2508"/>
+ <source>2</source>
+ <translation>2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/qt_controller.ui" line="2518"/>
+ <source>4</source>
+ <translation>4</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/qt_controller.ui" line="2528"/>
+ <source>3</source>
+ <translation>3</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/qt_controller.ui" line="2538"/>
+ <source>Connected</source>
+ <translation>З&apos;єднано</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/qt_controller.ui" line="2552"/>
+ <source>5</source>
+ <translation>5</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/qt_controller.ui" line="2569"/>
+ <source>7</source>
+ <translation>7</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/qt_controller.ui" line="2586"/>
+ <source>6</source>
+ <translation>6</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/qt_controller.ui" line="2596"/>
+ <source>8</source>
+ <translation>8</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/qt_controller.cpp" line="434"/>
+ <source>GameCube Controller</source>
+ <translation>Контролер GameCube</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/qt_controller.cpp" line="443"/>
+ <source>Poke Ball Plus</source>
+ <translation>Poke Ball Plus</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/qt_controller.cpp" line="447"/>
+ <source>NES Controller</source>
+ <translation>Контролер NES</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/qt_controller.cpp" line="451"/>
+ <source>SNES Controller</source>
+ <translation>Контролер SNES</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/qt_controller.cpp" line="455"/>
+ <source>N64 Controller</source>
+ <translation>Контролер N64</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/qt_controller.cpp" line="459"/>
+ <source>Sega Genesis</source>
+ <translation>Sega Genesis</translation>
+ </message>
+</context>
+<context>
+ <name>QtErrorDisplay</name>
+ <message>
+ <location filename="../../src/yuzu/applets/qt_error.cpp" line="20"/>
+ <location filename="../../src/yuzu/applets/qt_error.cpp" line="33"/>
+ <location filename="../../src/yuzu/applets/qt_error.cpp" line="48"/>
+ <source>Error Code: %1-%2 (0x%3)</source>
+ <translation>Код помилки: %1-%2 (0x%3)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/qt_error.cpp" line="24"/>
+ <source>An error has occurred.
+Please try again or contact the developer of the software.</source>
+ <translation>Сталася помилка.
+Будь ласка, спробуйте ще раз або зв&apos;яжіться з розробником ПЗ.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/qt_error.cpp" line="37"/>
+ <source>An error occurred on %1 at %2.
+Please try again or contact the developer of the software.</source>
+ <translation>Сталася помилка на %1 у %2.
+Будь ласка, спробуйте ще раз або зв&apos;яжіться з розробником ПЗ.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/qt_error.cpp" line="52"/>
+ <source>An error has occurred.
+
+%1
+
+%2</source>
+ <translation>Сталася помилка.
+
+%1
+
+%2</translation>
+ </message>
+</context>
+<context>
+ <name>QtProfileSelectionDialog</name>
+ <message>
+ <location filename="../../src/yuzu/applets/qt_profile_select.cpp" line="23"/>
+ <source>%1
+%2</source>
+ <comment>%1 is the profile username, %2 is the formatted UUID (e.g. 00112233-4455-6677-8899-AABBCCDDEEFF))</comment>
+ <translation>%1
+%2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/qt_profile_select.cpp" line="53"/>
+ <source>Select a user:</source>
+ <translation>Оберить користувача</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/qt_profile_select.cpp" line="83"/>
+ <source>Users</source>
+ <translation>Користувачі</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/qt_profile_select.cpp" line="123"/>
+ <source>Profile Selector</source>
+ <translation>Вибір профілю</translation>
+ </message>
+</context>
+<context>
+ <name>QtSoftwareKeyboardDialog</name>
+ <message>
+ <location filename="../../src/yuzu/applets/qt_software_keyboard.ui" line="14"/>
+ <source>Software Keyboard</source>
+ <translation>Віртуальна клавіатура</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/qt_software_keyboard.ui" line="199"/>
+ <source>Enter Text</source>
+ <translation>Введіть текст</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/qt_software_keyboard.ui" line="479"/>
+ <source>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
+&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
+p, li { white-space: pre-wrap; }
+&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'MS Shell Dlg 2'; font-size:26pt; font-weight:400; font-style:normal;&quot;&gt;
+&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
+&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
+p, li { white-space: pre-wrap; }
+&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:&apos;MS Shell Dlg 2&apos;; font-size:26pt; font-weight:400; font-style:normal;&quot;&gt;
+&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/qt_software_keyboard.cpp" line="403"/>
+ <location filename="../../src/yuzu/applets/qt_software_keyboard.cpp" line="413"/>
+ <source>OK</source>
+ <translation>ОК</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/qt_software_keyboard.cpp" line="413"/>
+ <source>Cancel</source>
+ <translation>Скасувати</translation>
+ </message>
+</context>
+<context>
+ <name>SequenceDialog</name>
+ <message>
+ <location filename="../../src/yuzu/util/sequence_dialog/sequence_dialog.cpp" line="10"/>
+ <source>Enter a hotkey</source>
+ <translation>Введіть комбінацію</translation>
+ </message>
+</context>
+<context>
+ <name>WaitTreeCallstack</name>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="147"/>
+ <source>Call stack</source>
+ <translation>Стек викликів</translation>
+ </message>
+</context>
+<context>
+ <name>WaitTreeMutexInfo</name>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="126"/>
+ <source>waiting for mutex 0x%1</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="133"/>
+ <source>has waiters: %1</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="135"/>
+ <source>owner handle: 0x%1</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>WaitTreeObjectList</name>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="228"/>
+ <source>waiting for all objects</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="229"/>
+ <source>waiting for one of the following objects</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>WaitTreeSynchronizationObject</name>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="185"/>
+ <source>[%1] %2 %3</source>
+ <translation>[%1] %2 %3</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="212"/>
+ <source>waited by no thread</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>WaitTreeThread</name>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="250"/>
+ <source>runnable</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="252"/>
+ <source>paused</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="258"/>
+ <source>sleeping</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="261"/>
+ <source>waiting for IPC reply</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="264"/>
+ <source>waiting for objects</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="267"/>
+ <source>waiting for condition variable</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="270"/>
+ <source>waiting for address arbiter</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="273"/>
+ <source>waiting for suspend resume</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="276"/>
+ <source>waiting</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="281"/>
+ <source>initialized</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="284"/>
+ <source>terminated</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="287"/>
+ <source>unknown</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="292"/>
+ <source> PC = 0x%1 LR = 0x%2</source>
+ <translation> PC = 0x%1 LR = 0x%2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="342"/>
+ <source>ideal</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="345"/>
+ <source>core %1</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="349"/>
+ <source>processor = %1</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="351"/>
+ <source>ideal core = %1</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="353"/>
+ <source>affinity mask = %1</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="354"/>
+ <source>thread id = %1</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="355"/>
+ <source>priority = %1(current) / %2(normal)</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="359"/>
+ <source>last running ticks = %1</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="367"/>
+ <source>not waiting for mutex</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>WaitTreeThreadList</name>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="391"/>
+ <source>waited by thread</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>WaitTreeWidget</name>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="465"/>
+ <source>&amp;Wait Tree</source>
+ <translation>[&amp;W] Дерево очікування</translation>
+ </message>
+</context>
+</TS> \ No newline at end of file
diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt
index 113e663b5..f6e082c36 100644
--- a/src/core/CMakeLists.txt
+++ b/src/core/CMakeLists.txt
@@ -190,11 +190,13 @@ add_library(core STATIC
hle/kernel/k_code_memory.h
hle/kernel/k_condition_variable.cpp
hle/kernel/k_condition_variable.h
+ hle/kernel/k_debug.h
hle/kernel/k_dynamic_page_manager.h
hle/kernel/k_dynamic_resource_manager.h
hle/kernel/k_dynamic_slab_heap.h
hle/kernel/k_event.cpp
hle/kernel/k_event.h
+ hle/kernel/k_event_info.h
hle/kernel/k_handle_table.cpp
hle/kernel/k_handle_table.h
hle/kernel/k_interrupt_manager.cpp
@@ -222,6 +224,8 @@ add_library(core STATIC
hle/kernel/k_page_group.h
hle/kernel/k_page_table.cpp
hle/kernel/k_page_table.h
+ hle/kernel/k_page_table_manager.h
+ hle/kernel/k_page_table_slab_heap.h
hle/kernel/k_port.cpp
hle/kernel/k_port.h
hle/kernel/k_priority_queue.h
@@ -254,6 +258,8 @@ add_library(core STATIC
hle/kernel/k_synchronization_object.cpp
hle/kernel/k_synchronization_object.h
hle/kernel/k_system_control.h
+ hle/kernel/k_system_resource.cpp
+ hle/kernel/k_system_resource.h
hle/kernel/k_thread.cpp
hle/kernel/k_thread.h
hle/kernel/k_thread_local_page.cpp
diff --git a/src/core/hle/ipc_helpers.h b/src/core/hle/ipc_helpers.h
index 18fde8bd6..3bb111748 100644
--- a/src/core/hle/ipc_helpers.h
+++ b/src/core/hle/ipc_helpers.h
@@ -86,13 +86,13 @@ public:
u32 num_domain_objects{};
const bool always_move_handles{
(static_cast<u32>(flags) & static_cast<u32>(Flags::AlwaysMoveHandles)) != 0};
- if (!ctx.Session()->GetSessionRequestManager()->IsDomain() || always_move_handles) {
+ if (!ctx.GetManager()->IsDomain() || always_move_handles) {
num_handles_to_move = num_objects_to_move;
} else {
num_domain_objects = num_objects_to_move;
}
- if (ctx.Session()->GetSessionRequestManager()->IsDomain()) {
+ if (ctx.GetManager()->IsDomain()) {
raw_data_size +=
static_cast<u32>(sizeof(DomainMessageHeader) / sizeof(u32) + num_domain_objects);
ctx.write_size += num_domain_objects;
@@ -125,8 +125,7 @@ public:
if (!ctx.IsTipc()) {
AlignWithPadding();
- if (ctx.Session()->GetSessionRequestManager()->IsDomain() &&
- ctx.HasDomainMessageHeader()) {
+ if (ctx.GetManager()->IsDomain() && ctx.HasDomainMessageHeader()) {
IPC::DomainMessageHeader domain_header{};
domain_header.num_objects = num_domain_objects;
PushRaw(domain_header);
@@ -146,18 +145,18 @@ public:
template <class T>
void PushIpcInterface(std::shared_ptr<T> iface) {
- if (context->Session()->GetSessionRequestManager()->IsDomain()) {
+ if (context->GetManager()->IsDomain()) {
context->AddDomainObject(std::move(iface));
} else {
kernel.CurrentProcess()->GetResourceLimit()->Reserve(
Kernel::LimitableResource::Sessions, 1);
auto* session = Kernel::KSession::Create(kernel);
- session->Initialize(nullptr, iface->GetServiceName(),
- std::make_shared<Kernel::SessionRequestManager>(kernel));
+ session->Initialize(nullptr, iface->GetServiceName());
+ iface->RegisterSession(&session->GetServerSession(),
+ std::make_shared<Kernel::SessionRequestManager>(kernel));
context->AddMoveObject(&session->GetClientSession());
- iface->ClientConnected(&session->GetServerSession());
}
}
@@ -387,7 +386,7 @@ public:
template <class T>
std::weak_ptr<T> PopIpcInterface() {
- ASSERT(context->Session()->GetSessionRequestManager()->IsDomain());
+ ASSERT(context->GetManager()->IsDomain());
ASSERT(context->GetDomainMessageHeader().input_object_count > 0);
return context->GetDomainHandler<T>(Pop<u32>() - 1);
}
diff --git a/src/core/hle/kernel/board/nintendo/nx/k_system_control.h b/src/core/hle/kernel/board/nintendo/nx/k_system_control.h
index fe375769e..4b717d091 100644
--- a/src/core/hle/kernel/board/nintendo/nx/k_system_control.h
+++ b/src/core/hle/kernel/board/nintendo/nx/k_system_control.h
@@ -9,6 +9,10 @@ namespace Kernel::Board::Nintendo::Nx {
class KSystemControl {
public:
+ // This can be overridden as needed.
+ static constexpr size_t SecureAppletMemorySize = 4 * 1024 * 1024; // 4_MB
+
+public:
class Init {
public:
// Initialization.
diff --git a/src/core/hle/kernel/hle_ipc.cpp b/src/core/hle/kernel/hle_ipc.cpp
index e4f43a053..fd354d484 100644
--- a/src/core/hle/kernel/hle_ipc.cpp
+++ b/src/core/hle/kernel/hle_ipc.cpp
@@ -16,6 +16,7 @@
#include "core/hle/kernel/k_auto_object.h"
#include "core/hle/kernel/k_handle_table.h"
#include "core/hle/kernel/k_process.h"
+#include "core/hle/kernel/k_server_port.h"
#include "core/hle/kernel/k_server_session.h"
#include "core/hle/kernel/k_thread.h"
#include "core/hle/kernel/kernel.h"
@@ -35,7 +36,21 @@ SessionRequestHandler::SessionRequestHandler(KernelCore& kernel_, const char* se
}
SessionRequestHandler::~SessionRequestHandler() {
- kernel.ReleaseServiceThread(service_thread);
+ kernel.ReleaseServiceThread(service_thread.lock());
+}
+
+void SessionRequestHandler::AcceptSession(KServerPort* server_port) {
+ auto* server_session = server_port->AcceptSession();
+ ASSERT(server_session != nullptr);
+
+ RegisterSession(server_session, std::make_shared<SessionRequestManager>(kernel));
+}
+
+void SessionRequestHandler::RegisterSession(KServerSession* server_session,
+ std::shared_ptr<SessionRequestManager> manager) {
+ manager->SetSessionHandler(shared_from_this());
+ service_thread.lock()->RegisterServerSession(server_session, manager);
+ server_session->Close();
}
SessionRequestManager::SessionRequestManager(KernelCore& kernel_) : kernel{kernel_} {}
@@ -92,7 +107,7 @@ Result SessionRequestManager::HandleDomainSyncRequest(KServerSession* server_ses
}
// Set domain handlers in HLE context, used for domain objects (IPC interfaces) as inputs
- context.SetSessionRequestManager(server_session->GetSessionRequestManager());
+ ASSERT(context.GetManager().get() == this);
// If there is a DomainMessageHeader, then this is CommandType "Request"
const auto& domain_message_header = context.GetDomainMessageHeader();
@@ -130,31 +145,6 @@ Result SessionRequestManager::HandleDomainSyncRequest(KServerSession* server_ses
return ResultSuccess;
}
-Result SessionRequestManager::QueueSyncRequest(KSession* parent,
- std::shared_ptr<HLERequestContext>&& context) {
- // Ensure we have a session request handler
- if (this->HasSessionRequestHandler(*context)) {
- if (auto strong_ptr = this->GetServiceThread().lock()) {
- strong_ptr->QueueSyncRequest(*parent, std::move(context));
- } else {
- ASSERT_MSG(false, "strong_ptr is nullptr!");
- }
- } else {
- ASSERT_MSG(false, "handler is invalid!");
- }
-
- return ResultSuccess;
-}
-
-void SessionRequestHandler::ClientConnected(KServerSession* session) {
- session->GetSessionRequestManager()->SetSessionHandler(shared_from_this());
-
- // Ensure our server session is tracked globally.
- kernel.RegisterServerObject(session);
-}
-
-void SessionRequestHandler::ClientDisconnected(KServerSession* session) {}
-
HLERequestContext::HLERequestContext(KernelCore& kernel_, Core::Memory::Memory& memory_,
KServerSession* server_session_, KThread* thread_)
: server_session(server_session_), thread(thread_), kernel{kernel_}, memory{memory_} {
@@ -214,7 +204,7 @@ void HLERequestContext::ParseCommandBuffer(const KHandleTable& handle_table, u32
// Padding to align to 16 bytes
rp.AlignWithPadding();
- if (Session()->GetSessionRequestManager()->IsDomain() &&
+ if (GetManager()->IsDomain() &&
((command_header->type == IPC::CommandType::Request ||
command_header->type == IPC::CommandType::RequestWithContext) ||
!incoming)) {
@@ -223,7 +213,7 @@ void HLERequestContext::ParseCommandBuffer(const KHandleTable& handle_table, u32
if (incoming || domain_message_header) {
domain_message_header = rp.PopRaw<IPC::DomainMessageHeader>();
} else {
- if (Session()->GetSessionRequestManager()->IsDomain()) {
+ if (GetManager()->IsDomain()) {
LOG_WARNING(IPC, "Domain request has no DomainMessageHeader!");
}
}
@@ -316,12 +306,11 @@ Result HLERequestContext::WriteToOutgoingCommandBuffer(KThread& requesting_threa
// Write the domain objects to the command buffer, these go after the raw untranslated data.
// TODO(Subv): This completely ignores C buffers.
- if (server_session->GetSessionRequestManager()->IsDomain()) {
+ if (GetManager()->IsDomain()) {
current_offset = domain_offset - static_cast<u32>(outgoing_domain_objects.size());
for (auto& object : outgoing_domain_objects) {
- server_session->GetSessionRequestManager()->AppendDomainHandler(std::move(object));
- cmd_buf[current_offset++] = static_cast<u32_le>(
- server_session->GetSessionRequestManager()->DomainHandlerCount());
+ GetManager()->AppendDomainHandler(std::move(object));
+ cmd_buf[current_offset++] = static_cast<u32_le>(GetManager()->DomainHandlerCount());
}
}
diff --git a/src/core/hle/kernel/hle_ipc.h b/src/core/hle/kernel/hle_ipc.h
index 1083638a9..67da8e7e1 100644
--- a/src/core/hle/kernel/hle_ipc.h
+++ b/src/core/hle/kernel/hle_ipc.h
@@ -45,11 +45,13 @@ class KAutoObject;
class KernelCore;
class KEvent;
class KHandleTable;
+class KServerPort;
class KProcess;
class KServerSession;
class KThread;
class KReadableEvent;
class KSession;
+class SessionRequestManager;
class ServiceThread;
enum class ThreadWakeupReason;
@@ -76,19 +78,9 @@ public:
virtual Result HandleSyncRequest(Kernel::KServerSession& session,
Kernel::HLERequestContext& context) = 0;
- /**
- * Signals that a client has just connected to this HLE handler and keeps the
- * associated ServerSession alive for the duration of the connection.
- * @param server_session Owning pointer to the ServerSession associated with the connection.
- */
- void ClientConnected(KServerSession* session);
-
- /**
- * Signals that a client has just disconnected from this HLE handler and releases the
- * associated ServerSession.
- * @param server_session ServerSession associated with the connection.
- */
- void ClientDisconnected(KServerSession* session);
+ void AcceptSession(KServerPort* server_port);
+ void RegisterSession(KServerSession* server_session,
+ std::shared_ptr<SessionRequestManager> manager);
std::weak_ptr<ServiceThread> GetServiceThread() const {
return service_thread;
@@ -170,7 +162,6 @@ public:
Result HandleDomainSyncRequest(KServerSession* server_session, HLERequestContext& context);
Result CompleteSyncRequest(KServerSession* server_session, HLERequestContext& context);
- Result QueueSyncRequest(KSession* parent, std::shared_ptr<HLERequestContext>&& context);
private:
bool convert_to_domain{};
@@ -350,11 +341,11 @@ public:
template <typename T>
std::shared_ptr<T> GetDomainHandler(std::size_t index) const {
- return std::static_pointer_cast<T>(manager.lock()->DomainHandler(index).lock());
+ return std::static_pointer_cast<T>(GetManager()->DomainHandler(index).lock());
}
void SetSessionRequestManager(std::weak_ptr<SessionRequestManager> manager_) {
- manager = std::move(manager_);
+ manager = manager_;
}
std::string Description() const;
@@ -363,6 +354,10 @@ public:
return *thread;
}
+ std::shared_ptr<SessionRequestManager> GetManager() const {
+ return manager.lock();
+ }
+
private:
friend class IPC::ResponseBuilder;
@@ -396,7 +391,7 @@ private:
u32 handles_offset{};
u32 domain_offset{};
- std::weak_ptr<SessionRequestManager> manager;
+ std::weak_ptr<SessionRequestManager> manager{};
KernelCore& kernel;
Core::Memory::Memory& memory;
diff --git a/src/core/hle/kernel/init/init_slab_setup.cpp b/src/core/hle/kernel/init/init_slab_setup.cpp
index 477e4e407..aa2dddcc6 100644
--- a/src/core/hle/kernel/init/init_slab_setup.cpp
+++ b/src/core/hle/kernel/init/init_slab_setup.cpp
@@ -10,7 +10,9 @@
#include "core/hardware_properties.h"
#include "core/hle/kernel/init/init_slab_setup.h"
#include "core/hle/kernel/k_code_memory.h"
+#include "core/hle/kernel/k_debug.h"
#include "core/hle/kernel/k_event.h"
+#include "core/hle/kernel/k_event_info.h"
#include "core/hle/kernel/k_memory_layout.h"
#include "core/hle/kernel/k_memory_manager.h"
#include "core/hle/kernel/k_page_buffer.h"
@@ -22,6 +24,7 @@
#include "core/hle/kernel/k_shared_memory.h"
#include "core/hle/kernel/k_shared_memory_info.h"
#include "core/hle/kernel/k_system_control.h"
+#include "core/hle/kernel/k_system_resource.h"
#include "core/hle/kernel/k_thread.h"
#include "core/hle/kernel/k_thread_local_page.h"
#include "core/hle/kernel/k_transfer_memory.h"
@@ -44,7 +47,10 @@ namespace Kernel::Init {
HANDLER(KThreadLocalPage, \
(SLAB_COUNT(KProcess) + (SLAB_COUNT(KProcess) + SLAB_COUNT(KThread)) / 8), \
##__VA_ARGS__) \
- HANDLER(KResourceLimit, (SLAB_COUNT(KResourceLimit)), ##__VA_ARGS__)
+ HANDLER(KResourceLimit, (SLAB_COUNT(KResourceLimit)), ##__VA_ARGS__) \
+ HANDLER(KEventInfo, (SLAB_COUNT(KThread) + SLAB_COUNT(KDebug)), ##__VA_ARGS__) \
+ HANDLER(KDebug, (SLAB_COUNT(KDebug)), ##__VA_ARGS__) \
+ HANDLER(KSecureSystemResource, (SLAB_COUNT(KProcess)), ##__VA_ARGS__)
namespace {
@@ -73,8 +79,20 @@ constexpr size_t SlabCountKResourceLimit = 5;
constexpr size_t SlabCountKDebug = Core::Hardware::NUM_CPU_CORES;
constexpr size_t SlabCountKIoPool = 1;
constexpr size_t SlabCountKIoRegion = 6;
+constexpr size_t SlabcountKSessionRequestMappings = 40;
-constexpr size_t SlabCountExtraKThread = 160;
+constexpr size_t SlabCountExtraKThread = (1024 + 256 + 256) - SlabCountKThread;
+
+namespace test {
+
+static_assert(KernelPageBufferHeapSize ==
+ 2 * PageSize + (SlabCountKProcess + SlabCountKThread +
+ (SlabCountKProcess + SlabCountKThread) / 8) *
+ PageSize);
+static_assert(KernelPageBufferAdditionalSize ==
+ (SlabCountExtraKThread + (SlabCountExtraKThread / 8)) * PageSize);
+
+} // namespace test
/// Helper function to translate from the slab virtual address to the reserved location in physical
/// memory.
@@ -109,7 +127,7 @@ VAddr InitializeSlabHeap(Core::System& system, KMemoryLayout& memory_layout, VAd
}
size_t CalculateSlabHeapGapSize() {
- constexpr size_t KernelSlabHeapGapSize = 2_MiB - 296_KiB;
+ constexpr size_t KernelSlabHeapGapSize = 2_MiB - 320_KiB;
static_assert(KernelSlabHeapGapSize <= KernelSlabHeapGapsSizeMax);
return KernelSlabHeapGapSize;
}
@@ -134,6 +152,7 @@ KSlabResourceCounts KSlabResourceCounts::CreateDefault() {
.num_KDebug = SlabCountKDebug,
.num_KIoPool = SlabCountKIoPool,
.num_KIoRegion = SlabCountKIoRegion,
+ .num_KSessionRequestMappings = SlabcountKSessionRequestMappings,
};
}
@@ -164,29 +183,6 @@ size_t CalculateTotalSlabHeapSize(const KernelCore& kernel) {
return size;
}
-void InitializeKPageBufferSlabHeap(Core::System& system) {
- auto& kernel = system.Kernel();
-
- const auto& counts = kernel.SlabResourceCounts();
- const size_t num_pages =
- counts.num_KProcess + counts.num_KThread + (counts.num_KProcess + counts.num_KThread) / 8;
- const size_t slab_size = num_pages * PageSize;
-
- // Reserve memory from the system resource limit.
- ASSERT(kernel.GetSystemResourceLimit()->Reserve(LimitableResource::PhysicalMemory, slab_size));
-
- // Allocate memory for the slab.
- constexpr auto AllocateOption = KMemoryManager::EncodeOption(
- KMemoryManager::Pool::System, KMemoryManager::Direction::FromFront);
- const PAddr slab_address =
- kernel.MemoryManager().AllocateAndOpenContinuous(num_pages, 1, AllocateOption);
- ASSERT(slab_address != 0);
-
- // Initialize the slabheap.
- KPageBuffer::InitializeSlabHeap(kernel, system.DeviceMemory().GetPointer<void>(slab_address),
- slab_size);
-}
-
void InitializeSlabHeaps(Core::System& system, KMemoryLayout& memory_layout) {
auto& kernel = system.Kernel();
@@ -258,3 +254,29 @@ void InitializeSlabHeaps(Core::System& system, KMemoryLayout& memory_layout) {
}
} // namespace Kernel::Init
+
+namespace Kernel {
+
+void KPageBufferSlabHeap::Initialize(Core::System& system) {
+ auto& kernel = system.Kernel();
+ const auto& counts = kernel.SlabResourceCounts();
+ const size_t num_pages =
+ counts.num_KProcess + counts.num_KThread + (counts.num_KProcess + counts.num_KThread) / 8;
+ const size_t slab_size = num_pages * PageSize;
+
+ // Reserve memory from the system resource limit.
+ ASSERT(kernel.GetSystemResourceLimit()->Reserve(LimitableResource::PhysicalMemory, slab_size));
+
+ // Allocate memory for the slab.
+ constexpr auto AllocateOption = KMemoryManager::EncodeOption(
+ KMemoryManager::Pool::System, KMemoryManager::Direction::FromFront);
+ const PAddr slab_address =
+ kernel.MemoryManager().AllocateAndOpenContinuous(num_pages, 1, AllocateOption);
+ ASSERT(slab_address != 0);
+
+ // Initialize the slabheap.
+ KPageBuffer::InitializeSlabHeap(kernel, system.DeviceMemory().GetPointer<void>(slab_address),
+ slab_size);
+}
+
+} // namespace Kernel
diff --git a/src/core/hle/kernel/init/init_slab_setup.h b/src/core/hle/kernel/init/init_slab_setup.h
index 13be63c87..5e22821bc 100644
--- a/src/core/hle/kernel/init/init_slab_setup.h
+++ b/src/core/hle/kernel/init/init_slab_setup.h
@@ -33,11 +33,11 @@ struct KSlabResourceCounts {
size_t num_KDebug;
size_t num_KIoPool;
size_t num_KIoRegion;
+ size_t num_KSessionRequestMappings;
};
void InitializeSlabResourceCounts(KernelCore& kernel);
size_t CalculateTotalSlabHeapSize(const KernelCore& kernel);
-void InitializeKPageBufferSlabHeap(Core::System& system);
void InitializeSlabHeaps(Core::System& system, KMemoryLayout& memory_layout);
} // namespace Kernel::Init
diff --git a/src/core/hle/kernel/k_class_token.cpp b/src/core/hle/kernel/k_class_token.cpp
index 10265c23c..a850db3c4 100644
--- a/src/core/hle/kernel/k_class_token.cpp
+++ b/src/core/hle/kernel/k_class_token.cpp
@@ -16,6 +16,7 @@
#include "core/hle/kernel/k_session.h"
#include "core/hle/kernel/k_shared_memory.h"
#include "core/hle/kernel/k_synchronization_object.h"
+#include "core/hle/kernel/k_system_resource.h"
#include "core/hle/kernel/k_thread.h"
#include "core/hle/kernel/k_transfer_memory.h"
@@ -119,4 +120,6 @@ static_assert(std::is_final_v<KTransferMemory> && std::is_base_of_v<KAutoObject,
// static_assert(std::is_final_v<KCodeMemory> &&
// std::is_base_of_v<KAutoObject, KCodeMemory>);
+static_assert(std::is_base_of_v<KAutoObject, KSystemResource>);
+
} // namespace Kernel
diff --git a/src/core/hle/kernel/k_class_token.h b/src/core/hle/kernel/k_class_token.h
index ab20e00ff..e75b1c035 100644
--- a/src/core/hle/kernel/k_class_token.h
+++ b/src/core/hle/kernel/k_class_token.h
@@ -10,6 +10,8 @@ namespace Kernel {
class KAutoObject;
+class KSystemResource;
+
class KClassTokenGenerator {
public:
using TokenBaseType = u16;
@@ -58,7 +60,7 @@ private:
if constexpr (std::is_same<T, KAutoObject>::value) {
static_assert(T::ObjectType == ObjectType::KAutoObject);
return 0;
- } else if constexpr (!std::is_final<T>::value) {
+ } else if constexpr (!std::is_final<T>::value && !std::same_as<T, KSystemResource>) {
static_assert(ObjectType::BaseClassesStart <= T::ObjectType &&
T::ObjectType < ObjectType::BaseClassesEnd);
constexpr auto ClassIndex = static_cast<TokenBaseType>(T::ObjectType) -
@@ -108,6 +110,8 @@ public:
KSessionRequest,
KCodeMemory,
+ KSystemResource,
+
// NOTE: True order for these has not been determined yet.
KAlpha,
KBeta,
diff --git a/src/core/hle/kernel/k_client_port.cpp b/src/core/hle/kernel/k_client_port.cpp
index 3cb22ff4d..eaa2e094c 100644
--- a/src/core/hle/kernel/k_client_port.cpp
+++ b/src/core/hle/kernel/k_client_port.cpp
@@ -58,8 +58,7 @@ bool KClientPort::IsSignaled() const {
return num_sessions < max_sessions;
}
-Result KClientPort::CreateSession(KClientSession** out,
- std::shared_ptr<SessionRequestManager> session_manager) {
+Result KClientPort::CreateSession(KClientSession** out) {
// Reserve a new session from the resource limit.
KScopedResourceReservation session_reservation(kernel.CurrentProcess()->GetResourceLimit(),
LimitableResource::Sessions);
@@ -104,7 +103,7 @@ Result KClientPort::CreateSession(KClientSession** out,
}
// Initialize the session.
- session->Initialize(this, parent->GetName(), session_manager);
+ session->Initialize(this, parent->GetName());
// Commit the session reservation.
session_reservation.Commit();
diff --git a/src/core/hle/kernel/k_client_port.h b/src/core/hle/kernel/k_client_port.h
index e17eff28f..81046fb86 100644
--- a/src/core/hle/kernel/k_client_port.h
+++ b/src/core/hle/kernel/k_client_port.h
@@ -52,8 +52,7 @@ public:
void Destroy() override;
bool IsSignaled() const override;
- Result CreateSession(KClientSession** out,
- std::shared_ptr<SessionRequestManager> session_manager = nullptr);
+ Result CreateSession(KClientSession** out);
private:
std::atomic<s32> num_sessions{};
diff --git a/src/core/hle/kernel/k_debug.h b/src/core/hle/kernel/k_debug.h
new file mode 100644
index 000000000..e3a0689c8
--- /dev/null
+++ b/src/core/hle/kernel/k_debug.h
@@ -0,0 +1,20 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "core/hle/kernel/k_auto_object.h"
+#include "core/hle/kernel/slab_helpers.h"
+
+namespace Kernel {
+
+class KDebug final : public KAutoObjectWithSlabHeapAndContainer<KDebug, KAutoObjectWithList> {
+ KERNEL_AUTOOBJECT_TRAITS(KDebug, KAutoObject);
+
+public:
+ explicit KDebug(KernelCore& kernel_) : KAutoObjectWithSlabHeapAndContainer{kernel_} {}
+
+ static void PostDestroy([[maybe_unused]] uintptr_t arg) {}
+};
+
+} // namespace Kernel
diff --git a/src/core/hle/kernel/k_dynamic_page_manager.h b/src/core/hle/kernel/k_dynamic_page_manager.h
index 9076c8fa3..ac80d60a1 100644
--- a/src/core/hle/kernel/k_dynamic_page_manager.h
+++ b/src/core/hle/kernel/k_dynamic_page_manager.h
@@ -3,6 +3,8 @@
#pragma once
+#include <vector>
+
#include "common/alignment.h"
#include "common/common_types.h"
#include "core/hle/kernel/k_page_bitmap.h"
@@ -33,28 +35,36 @@ public:
return reinterpret_cast<T*>(m_backing_memory.data() + (addr - m_address));
}
- Result Initialize(VAddr addr, size_t sz) {
+ Result Initialize(VAddr memory, size_t size, size_t align) {
// We need to have positive size.
- R_UNLESS(sz > 0, ResultOutOfMemory);
- m_backing_memory.resize(sz);
+ R_UNLESS(size > 0, ResultOutOfMemory);
+ m_backing_memory.resize(size);
+
+ // Set addresses.
+ m_address = memory;
+ m_aligned_address = Common::AlignDown(memory, align);
- // Calculate management overhead.
- const size_t management_size =
- KPageBitmap::CalculateManagementOverheadSize(sz / sizeof(PageBuffer));
- const size_t allocatable_size = sz - management_size;
+ // Calculate extents.
+ const size_t managed_size = m_address + size - m_aligned_address;
+ const size_t overhead_size = Common::AlignUp(
+ KPageBitmap::CalculateManagementOverheadSize(managed_size / sizeof(PageBuffer)),
+ sizeof(PageBuffer));
+ R_UNLESS(overhead_size < size, ResultOutOfMemory);
// Set tracking fields.
- m_address = addr;
- m_size = Common::AlignDown(allocatable_size, sizeof(PageBuffer));
- m_count = allocatable_size / sizeof(PageBuffer);
- R_UNLESS(m_count > 0, ResultOutOfMemory);
+ m_size = Common::AlignDown(size - overhead_size, sizeof(PageBuffer));
+ m_count = m_size / sizeof(PageBuffer);
// Clear the management region.
- u64* management_ptr = GetPointer<u64>(m_address + allocatable_size);
- std::memset(management_ptr, 0, management_size);
+ u64* management_ptr = GetPointer<u64>(m_address + size - overhead_size);
+ std::memset(management_ptr, 0, overhead_size);
// Initialize the bitmap.
- m_page_bitmap.Initialize(management_ptr, m_count);
+ const size_t allocatable_region_size =
+ (m_address + size - overhead_size) - m_aligned_address;
+ ASSERT(allocatable_region_size >= sizeof(PageBuffer));
+
+ m_page_bitmap.Initialize(management_ptr, allocatable_region_size / sizeof(PageBuffer));
// Free the pages to the bitmap.
for (size_t i = 0; i < m_count; i++) {
@@ -62,7 +72,8 @@ public:
std::memset(GetPointer<PageBuffer>(m_address) + i, 0, PageSize);
// Set the bit for the free page.
- m_page_bitmap.SetBit(i);
+ m_page_bitmap.SetBit((m_address + (i * sizeof(PageBuffer)) - m_aligned_address) /
+ sizeof(PageBuffer));
}
R_SUCCEED();
@@ -101,7 +112,28 @@ public:
m_page_bitmap.ClearBit(offset);
m_peak = std::max(m_peak, (++m_used));
- return GetPointer<PageBuffer>(m_address) + offset;
+ return GetPointer<PageBuffer>(m_aligned_address) + offset;
+ }
+
+ PageBuffer* Allocate(size_t count) {
+ // Take the lock.
+ // TODO(bunnei): We should disable interrupts here via KScopedInterruptDisable.
+ KScopedSpinLock lk(m_lock);
+
+ // Find a random free block.
+ s64 soffset = m_page_bitmap.FindFreeRange(count);
+ if (soffset < 0) [[likely]] {
+ return nullptr;
+ }
+
+ const size_t offset = static_cast<size_t>(soffset);
+
+ // Update our tracking.
+ m_page_bitmap.ClearRange(offset, count);
+ m_used += count;
+ m_peak = std::max(m_peak, m_used);
+
+ return GetPointer<PageBuffer>(m_aligned_address) + offset;
}
void Free(PageBuffer* pb) {
@@ -113,7 +145,7 @@ public:
KScopedSpinLock lk(m_lock);
// Set the bit for the free page.
- size_t offset = (reinterpret_cast<uintptr_t>(pb) - m_address) / sizeof(PageBuffer);
+ size_t offset = (reinterpret_cast<uintptr_t>(pb) - m_aligned_address) / sizeof(PageBuffer);
m_page_bitmap.SetBit(offset);
// Decrement our used count.
@@ -127,6 +159,7 @@ private:
size_t m_peak{};
size_t m_count{};
VAddr m_address{};
+ VAddr m_aligned_address{};
size_t m_size{};
// TODO(bunnei): Back by host memory until we emulate kernel virtual address space.
diff --git a/src/core/hle/kernel/k_dynamic_resource_manager.h b/src/core/hle/kernel/k_dynamic_resource_manager.h
index 1ce517e8e..b6a27d648 100644
--- a/src/core/hle/kernel/k_dynamic_resource_manager.h
+++ b/src/core/hle/kernel/k_dynamic_resource_manager.h
@@ -6,6 +6,7 @@
#include "common/common_funcs.h"
#include "core/hle/kernel/k_dynamic_slab_heap.h"
#include "core/hle/kernel/k_memory_block.h"
+#include "core/hle/kernel/k_page_group.h"
namespace Kernel {
@@ -51,8 +52,10 @@ private:
DynamicSlabType* m_slab_heap{};
};
+class KBlockInfoManager : public KDynamicResourceManager<KBlockInfo> {};
class KMemoryBlockSlabManager : public KDynamicResourceManager<KMemoryBlock> {};
+using KBlockInfoSlabHeap = typename KBlockInfoManager::DynamicSlabType;
using KMemoryBlockSlabHeap = typename KMemoryBlockSlabManager::DynamicSlabType;
} // namespace Kernel
diff --git a/src/core/hle/kernel/k_event_info.h b/src/core/hle/kernel/k_event_info.h
new file mode 100644
index 000000000..25b3ff594
--- /dev/null
+++ b/src/core/hle/kernel/k_event_info.h
@@ -0,0 +1,64 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <array>
+
+#include <boost/intrusive/list.hpp>
+
+#include "core/hle/kernel/slab_helpers.h"
+#include "core/hle/kernel/svc_types.h"
+
+namespace Kernel {
+
+class KEventInfo : public KSlabAllocated<KEventInfo>, public boost::intrusive::list_base_hook<> {
+public:
+ struct InfoCreateThread {
+ u32 thread_id{};
+ uintptr_t tls_address{};
+ };
+
+ struct InfoExitProcess {
+ Svc::ProcessExitReason reason{};
+ };
+
+ struct InfoExitThread {
+ Svc::ThreadExitReason reason{};
+ };
+
+ struct InfoException {
+ Svc::DebugException exception_type{};
+ s32 exception_data_count{};
+ uintptr_t exception_address{};
+ std::array<uintptr_t, 4> exception_data{};
+ };
+
+ struct InfoSystemCall {
+ s64 tick{};
+ s32 id{};
+ };
+
+public:
+ KEventInfo() = default;
+ ~KEventInfo() = default;
+
+public:
+ Svc::DebugEvent event{};
+ u32 thread_id{};
+ u32 flags{};
+ bool is_attached{};
+ bool continue_flag{};
+ bool ignore_continue{};
+ bool close_once{};
+ union {
+ InfoCreateThread create_thread;
+ InfoExitProcess exit_process;
+ InfoExitThread exit_thread;
+ InfoException exception;
+ InfoSystemCall system_call;
+ } info{};
+ KThread* debug_thread{};
+};
+
+} // namespace Kernel
diff --git a/src/core/hle/kernel/k_handle_table.cpp b/src/core/hle/kernel/k_handle_table.cpp
index e830ca46e..1c7a766c8 100644
--- a/src/core/hle/kernel/k_handle_table.cpp
+++ b/src/core/hle/kernel/k_handle_table.cpp
@@ -5,14 +5,11 @@
namespace Kernel {
-KHandleTable::KHandleTable(KernelCore& kernel_) : kernel{kernel_} {}
-KHandleTable::~KHandleTable() = default;
-
Result KHandleTable::Finalize() {
// Get the table and clear our record of it.
u16 saved_table_size = 0;
{
- KScopedDisableDispatch dd(kernel);
+ KScopedDisableDispatch dd{m_kernel};
KScopedSpinLock lk(m_lock);
std::swap(m_table_size, saved_table_size);
@@ -25,28 +22,28 @@ Result KHandleTable::Finalize() {
}
}
- return ResultSuccess;
+ R_SUCCEED();
}
bool KHandleTable::Remove(Handle handle) {
// Don't allow removal of a pseudo-handle.
- if (Svc::IsPseudoHandle(handle)) {
+ if (Svc::IsPseudoHandle(handle)) [[unlikely]] {
return false;
}
// Handles must not have reserved bits set.
const auto handle_pack = HandlePack(handle);
- if (handle_pack.reserved != 0) {
+ if (handle_pack.reserved != 0) [[unlikely]] {
return false;
}
// Find the object and free the entry.
KAutoObject* obj = nullptr;
{
- KScopedDisableDispatch dd(kernel);
+ KScopedDisableDispatch dd{m_kernel};
KScopedSpinLock lk(m_lock);
- if (this->IsValidHandle(handle)) {
+ if (this->IsValidHandle(handle)) [[likely]] {
const auto index = handle_pack.index;
obj = m_objects[index];
@@ -57,13 +54,13 @@ bool KHandleTable::Remove(Handle handle) {
}
// Close the object.
- kernel.UnregisterInUseObject(obj);
+ m_kernel.UnregisterInUseObject(obj);
obj->Close();
return true;
}
Result KHandleTable::Add(Handle* out_handle, KAutoObject* obj) {
- KScopedDisableDispatch dd(kernel);
+ KScopedDisableDispatch dd{m_kernel};
KScopedSpinLock lk(m_lock);
// Never exceed our capacity.
@@ -82,22 +79,22 @@ Result KHandleTable::Add(Handle* out_handle, KAutoObject* obj) {
*out_handle = EncodeHandle(static_cast<u16>(index), linear_id);
}
- return ResultSuccess;
+ R_SUCCEED();
}
Result KHandleTable::Reserve(Handle* out_handle) {
- KScopedDisableDispatch dd(kernel);
+ KScopedDisableDispatch dd{m_kernel};
KScopedSpinLock lk(m_lock);
// Never exceed our capacity.
R_UNLESS(m_count < m_table_size, ResultOutOfHandles);
*out_handle = EncodeHandle(static_cast<u16>(this->AllocateEntry()), this->AllocateLinearId());
- return ResultSuccess;
+ R_SUCCEED();
}
void KHandleTable::Unreserve(Handle handle) {
- KScopedDisableDispatch dd(kernel);
+ KScopedDisableDispatch dd{m_kernel};
KScopedSpinLock lk(m_lock);
// Unpack the handle.
@@ -108,7 +105,7 @@ void KHandleTable::Unreserve(Handle handle) {
ASSERT(reserved == 0);
ASSERT(linear_id != 0);
- if (index < m_table_size) {
+ if (index < m_table_size) [[likely]] {
// NOTE: This code does not check the linear id.
ASSERT(m_objects[index] == nullptr);
this->FreeEntry(index);
@@ -116,7 +113,7 @@ void KHandleTable::Unreserve(Handle handle) {
}
void KHandleTable::Register(Handle handle, KAutoObject* obj) {
- KScopedDisableDispatch dd(kernel);
+ KScopedDisableDispatch dd{m_kernel};
KScopedSpinLock lk(m_lock);
// Unpack the handle.
@@ -127,7 +124,7 @@ void KHandleTable::Register(Handle handle, KAutoObject* obj) {
ASSERT(reserved == 0);
ASSERT(linear_id != 0);
- if (index < m_table_size) {
+ if (index < m_table_size) [[likely]] {
// Set the entry.
ASSERT(m_objects[index] == nullptr);
diff --git a/src/core/hle/kernel/k_handle_table.h b/src/core/hle/kernel/k_handle_table.h
index 0864a737c..65cae3b27 100644
--- a/src/core/hle/kernel/k_handle_table.h
+++ b/src/core/hle/kernel/k_handle_table.h
@@ -21,33 +21,38 @@ namespace Kernel {
class KernelCore;
class KHandleTable {
-public:
YUZU_NON_COPYABLE(KHandleTable);
YUZU_NON_MOVEABLE(KHandleTable);
+public:
static constexpr size_t MaxTableSize = 1024;
- explicit KHandleTable(KernelCore& kernel_);
- ~KHandleTable();
+public:
+ explicit KHandleTable(KernelCore& kernel) : m_kernel(kernel) {}
Result Initialize(s32 size) {
+ // Check that the table size is valid.
R_UNLESS(size <= static_cast<s32>(MaxTableSize), ResultOutOfMemory);
+ // Lock.
+ KScopedDisableDispatch dd{m_kernel};
+ KScopedSpinLock lk(m_lock);
+
// Initialize all fields.
m_max_count = 0;
- m_table_size = static_cast<u16>((size <= 0) ? MaxTableSize : size);
+ m_table_size = static_cast<s16>((size <= 0) ? MaxTableSize : size);
m_next_linear_id = MinLinearId;
m_count = 0;
m_free_head_index = -1;
// Free all entries.
- for (s16 i = 0; i < static_cast<s16>(m_table_size); ++i) {
+ for (s32 i = 0; i < static_cast<s32>(m_table_size); ++i) {
m_objects[i] = nullptr;
- m_entry_infos[i].next_free_index = i - 1;
+ m_entry_infos[i].next_free_index = static_cast<s16>(i - 1);
m_free_head_index = i;
}
- return ResultSuccess;
+ R_SUCCEED();
}
size_t GetTableSize() const {
@@ -66,13 +71,13 @@ public:
template <typename T = KAutoObject>
KScopedAutoObject<T> GetObjectWithoutPseudoHandle(Handle handle) const {
// Lock and look up in table.
- KScopedDisableDispatch dd(kernel);
+ KScopedDisableDispatch dd{m_kernel};
KScopedSpinLock lk(m_lock);
if constexpr (std::is_same_v<T, KAutoObject>) {
return this->GetObjectImpl(handle);
} else {
- if (auto* obj = this->GetObjectImpl(handle); obj != nullptr) {
+ if (auto* obj = this->GetObjectImpl(handle); obj != nullptr) [[likely]] {
return obj->DynamicCast<T*>();
} else {
return nullptr;
@@ -85,13 +90,13 @@ public:
// Handle pseudo-handles.
if constexpr (std::derived_from<KProcess, T>) {
if (handle == Svc::PseudoHandle::CurrentProcess) {
- auto* const cur_process = kernel.CurrentProcess();
+ auto* const cur_process = m_kernel.CurrentProcess();
ASSERT(cur_process != nullptr);
return cur_process;
}
} else if constexpr (std::derived_from<KThread, T>) {
if (handle == Svc::PseudoHandle::CurrentThread) {
- auto* const cur_thread = GetCurrentThreadPointer(kernel);
+ auto* const cur_thread = GetCurrentThreadPointer(m_kernel);
ASSERT(cur_thread != nullptr);
return cur_thread;
}
@@ -100,6 +105,37 @@ public:
return this->template GetObjectWithoutPseudoHandle<T>(handle);
}
+ KScopedAutoObject<KAutoObject> GetObjectForIpcWithoutPseudoHandle(Handle handle) const {
+ // Lock and look up in table.
+ KScopedDisableDispatch dd{m_kernel};
+ KScopedSpinLock lk(m_lock);
+
+ return this->GetObjectImpl(handle);
+ }
+
+ KScopedAutoObject<KAutoObject> GetObjectForIpc(Handle handle, KThread* cur_thread) const {
+ // Handle pseudo-handles.
+ ASSERT(cur_thread != nullptr);
+ if (handle == Svc::PseudoHandle::CurrentProcess) {
+ auto* const cur_process =
+ static_cast<KAutoObject*>(static_cast<void*>(cur_thread->GetOwnerProcess()));
+ ASSERT(cur_process != nullptr);
+ return cur_process;
+ }
+ if (handle == Svc::PseudoHandle::CurrentThread) {
+ return static_cast<KAutoObject*>(cur_thread);
+ }
+
+ return GetObjectForIpcWithoutPseudoHandle(handle);
+ }
+
+ KScopedAutoObject<KAutoObject> GetObjectByIndex(Handle* out_handle, size_t index) const {
+ KScopedDisableDispatch dd{m_kernel};
+ KScopedSpinLock lk(m_lock);
+
+ return this->GetObjectByIndexImpl(out_handle, index);
+ }
+
Result Reserve(Handle* out_handle);
void Unreserve(Handle handle);
@@ -112,7 +148,7 @@ public:
size_t num_opened;
{
// Lock the table.
- KScopedDisableDispatch dd(kernel);
+ KScopedDisableDispatch dd{m_kernel};
KScopedSpinLock lk(m_lock);
for (num_opened = 0; num_opened < num_handles; num_opened++) {
// Get the current handle.
@@ -120,13 +156,13 @@ public:
// Get the object for the current handle.
KAutoObject* cur_object = this->GetObjectImpl(cur_handle);
- if (cur_object == nullptr) {
+ if (cur_object == nullptr) [[unlikely]] {
break;
}
// Cast the current object to the desired type.
T* cur_t = cur_object->DynamicCast<T*>();
- if (cur_t == nullptr) {
+ if (cur_t == nullptr) [[unlikely]] {
break;
}
@@ -137,7 +173,7 @@ public:
}
// If we converted every object, succeed.
- if (num_opened == num_handles) {
+ if (num_opened == num_handles) [[likely]] {
return true;
}
@@ -191,21 +227,21 @@ private:
ASSERT(reserved == 0);
// Validate our indexing information.
- if (raw_value == 0) {
+ if (raw_value == 0) [[unlikely]] {
return false;
}
- if (linear_id == 0) {
+ if (linear_id == 0) [[unlikely]] {
return false;
}
- if (index >= m_table_size) {
+ if (index >= m_table_size) [[unlikely]] {
return false;
}
// Check that there's an object, and our serial id is correct.
- if (m_objects[index] == nullptr) {
+ if (m_objects[index] == nullptr) [[unlikely]] {
return false;
}
- if (m_entry_infos[index].GetLinearId() != linear_id) {
+ if (m_entry_infos[index].GetLinearId() != linear_id) [[unlikely]] {
return false;
}
@@ -215,11 +251,11 @@ private:
KAutoObject* GetObjectImpl(Handle handle) const {
// Handles must not have reserved bits set.
const auto handle_pack = HandlePack(handle);
- if (handle_pack.reserved != 0) {
+ if (handle_pack.reserved != 0) [[unlikely]] {
return nullptr;
}
- if (this->IsValidHandle(handle)) {
+ if (this->IsValidHandle(handle)) [[likely]] {
return m_objects[handle_pack.index];
} else {
return nullptr;
@@ -227,9 +263,8 @@ private:
}
KAutoObject* GetObjectByIndexImpl(Handle* out_handle, size_t index) const {
-
// Index must be in bounds.
- if (index >= m_table_size) {
+ if (index >= m_table_size) [[unlikely]] {
return nullptr;
}
@@ -244,18 +279,15 @@ private:
private:
union HandlePack {
- HandlePack() = default;
- HandlePack(Handle handle) : raw{static_cast<u32>(handle)} {}
+ constexpr HandlePack() = default;
+ constexpr HandlePack(Handle handle) : raw{static_cast<u32>(handle)} {}
- u32 raw;
+ u32 raw{};
BitField<0, 15, u32> index;
BitField<15, 15, u32> linear_id;
BitField<30, 2, u32> reserved;
};
- static constexpr u16 MinLinearId = 1;
- static constexpr u16 MaxLinearId = 0x7FFF;
-
static constexpr Handle EncodeHandle(u16 index, u16 linear_id) {
HandlePack handle{};
handle.index.Assign(index);
@@ -264,6 +296,10 @@ private:
return handle.raw;
}
+private:
+ static constexpr u16 MinLinearId = 1;
+ static constexpr u16 MaxLinearId = 0x7FFF;
+
union EntryInfo {
u16 linear_id;
s16 next_free_index;
@@ -271,21 +307,21 @@ private:
constexpr u16 GetLinearId() const {
return linear_id;
}
- constexpr s16 GetNextFreeIndex() const {
+ constexpr s32 GetNextFreeIndex() const {
return next_free_index;
}
};
private:
+ KernelCore& m_kernel;
std::array<EntryInfo, MaxTableSize> m_entry_infos{};
std::array<KAutoObject*, MaxTableSize> m_objects{};
- s32 m_free_head_index{-1};
+ mutable KSpinLock m_lock;
+ s32 m_free_head_index{};
u16 m_table_size{};
u16 m_max_count{};
- u16 m_next_linear_id{MinLinearId};
+ u16 m_next_linear_id{};
u16 m_count{};
- mutable KSpinLock m_lock;
- KernelCore& kernel;
};
} // namespace Kernel
diff --git a/src/core/hle/kernel/k_memory_block.h b/src/core/hle/kernel/k_memory_block.h
index 9444f6bd2..6f845d675 100644
--- a/src/core/hle/kernel/k_memory_block.h
+++ b/src/core/hle/kernel/k_memory_block.h
@@ -35,26 +35,32 @@ enum class KMemoryState : u32 {
FlagCanMapProcess = (1 << 23),
FlagCanChangeAttribute = (1 << 24),
FlagCanCodeMemory = (1 << 25),
+ FlagLinearMapped = (1 << 26),
FlagsData = FlagCanReprotect | FlagCanUseIpc | FlagCanUseNonDeviceIpc | FlagCanUseNonSecureIpc |
FlagMapped | FlagCanAlias | FlagCanTransfer | FlagCanQueryPhysical |
FlagCanDeviceMap | FlagCanAlignedDeviceMap | FlagCanIpcUserBuffer |
- FlagReferenceCounted | FlagCanChangeAttribute,
+ FlagReferenceCounted | FlagCanChangeAttribute | FlagLinearMapped,
FlagsCode = FlagCanDebug | FlagCanUseIpc | FlagCanUseNonDeviceIpc | FlagCanUseNonSecureIpc |
FlagMapped | FlagCode | FlagCanQueryPhysical | FlagCanDeviceMap |
- FlagCanAlignedDeviceMap | FlagReferenceCounted,
+ FlagCanAlignedDeviceMap | FlagReferenceCounted | FlagLinearMapped,
- FlagsMisc = FlagMapped | FlagReferenceCounted | FlagCanQueryPhysical | FlagCanDeviceMap,
+ FlagsMisc = FlagMapped | FlagReferenceCounted | FlagCanQueryPhysical | FlagCanDeviceMap |
+ FlagLinearMapped,
Free = static_cast<u32>(Svc::MemoryState::Free),
- Io = static_cast<u32>(Svc::MemoryState::Io) | FlagMapped,
+ Io = static_cast<u32>(Svc::MemoryState::Io) | FlagMapped | FlagCanDeviceMap |
+ FlagCanAlignedDeviceMap,
Static = static_cast<u32>(Svc::MemoryState::Static) | FlagMapped | FlagCanQueryPhysical,
Code = static_cast<u32>(Svc::MemoryState::Code) | FlagsCode | FlagCanMapProcess,
CodeData = static_cast<u32>(Svc::MemoryState::CodeData) | FlagsData | FlagCanMapProcess |
FlagCanCodeMemory,
- Shared = static_cast<u32>(Svc::MemoryState::Shared) | FlagMapped | FlagReferenceCounted,
Normal = static_cast<u32>(Svc::MemoryState::Normal) | FlagsData | FlagCanCodeMemory,
+ Shared = static_cast<u32>(Svc::MemoryState::Shared) | FlagMapped | FlagReferenceCounted |
+ FlagLinearMapped,
+
+ // Alias was removed after 1.0.0.
AliasCode = static_cast<u32>(Svc::MemoryState::AliasCode) | FlagsCode | FlagCanMapProcess |
FlagCanCodeAlias,
@@ -67,18 +73,18 @@ enum class KMemoryState : u32 {
Stack = static_cast<u32>(Svc::MemoryState::Stack) | FlagsMisc | FlagCanAlignedDeviceMap |
FlagCanUseIpc | FlagCanUseNonSecureIpc | FlagCanUseNonDeviceIpc,
- ThreadLocal =
- static_cast<u32>(Svc::MemoryState::ThreadLocal) | FlagMapped | FlagReferenceCounted,
+ ThreadLocal = static_cast<u32>(Svc::MemoryState::ThreadLocal) | FlagMapped | FlagLinearMapped,
- Transfered = static_cast<u32>(Svc::MemoryState::Transferred) | FlagsMisc |
+ Transfered = static_cast<u32>(Svc::MemoryState::Transfered) | FlagsMisc |
FlagCanAlignedDeviceMap | FlagCanChangeAttribute | FlagCanUseIpc |
FlagCanUseNonSecureIpc | FlagCanUseNonDeviceIpc,
- SharedTransfered = static_cast<u32>(Svc::MemoryState::SharedTransferred) | FlagsMisc |
+ SharedTransfered = static_cast<u32>(Svc::MemoryState::SharedTransfered) | FlagsMisc |
FlagCanAlignedDeviceMap | FlagCanUseNonSecureIpc | FlagCanUseNonDeviceIpc,
SharedCode = static_cast<u32>(Svc::MemoryState::SharedCode) | FlagMapped |
- FlagReferenceCounted | FlagCanUseNonSecureIpc | FlagCanUseNonDeviceIpc,
+ FlagReferenceCounted | FlagLinearMapped | FlagCanUseNonSecureIpc |
+ FlagCanUseNonDeviceIpc,
Inaccessible = static_cast<u32>(Svc::MemoryState::Inaccessible),
@@ -91,69 +97,69 @@ enum class KMemoryState : u32 {
Kernel = static_cast<u32>(Svc::MemoryState::Kernel) | FlagMapped,
GeneratedCode = static_cast<u32>(Svc::MemoryState::GeneratedCode) | FlagMapped |
- FlagReferenceCounted | FlagCanDebug,
- CodeOut = static_cast<u32>(Svc::MemoryState::CodeOut) | FlagMapped | FlagReferenceCounted,
+ FlagReferenceCounted | FlagCanDebug | FlagLinearMapped,
+ CodeOut = static_cast<u32>(Svc::MemoryState::CodeOut) | FlagMapped | FlagReferenceCounted |
+ FlagLinearMapped,
Coverage = static_cast<u32>(Svc::MemoryState::Coverage) | FlagMapped,
+
+ Insecure = static_cast<u32>(Svc::MemoryState::Insecure) | FlagMapped | FlagReferenceCounted |
+ FlagLinearMapped | FlagCanChangeAttribute | FlagCanDeviceMap |
+ FlagCanAlignedDeviceMap | FlagCanUseNonSecureIpc | FlagCanUseNonDeviceIpc,
};
DECLARE_ENUM_FLAG_OPERATORS(KMemoryState);
static_assert(static_cast<u32>(KMemoryState::Free) == 0x00000000);
-static_assert(static_cast<u32>(KMemoryState::Io) == 0x00002001);
+static_assert(static_cast<u32>(KMemoryState::Io) == 0x00182001);
static_assert(static_cast<u32>(KMemoryState::Static) == 0x00042002);
-static_assert(static_cast<u32>(KMemoryState::Code) == 0x00DC7E03);
-static_assert(static_cast<u32>(KMemoryState::CodeData) == 0x03FEBD04);
-static_assert(static_cast<u32>(KMemoryState::Normal) == 0x037EBD05);
-static_assert(static_cast<u32>(KMemoryState::Shared) == 0x00402006);
-static_assert(static_cast<u32>(KMemoryState::AliasCode) == 0x00DD7E08);
-static_assert(static_cast<u32>(KMemoryState::AliasCodeData) == 0x03FFBD09);
-static_assert(static_cast<u32>(KMemoryState::Ipc) == 0x005C3C0A);
-static_assert(static_cast<u32>(KMemoryState::Stack) == 0x005C3C0B);
-static_assert(static_cast<u32>(KMemoryState::ThreadLocal) == 0x0040200C);
-static_assert(static_cast<u32>(KMemoryState::Transfered) == 0x015C3C0D);
-static_assert(static_cast<u32>(KMemoryState::SharedTransfered) == 0x005C380E);
-static_assert(static_cast<u32>(KMemoryState::SharedCode) == 0x0040380F);
+static_assert(static_cast<u32>(KMemoryState::Code) == 0x04DC7E03);
+static_assert(static_cast<u32>(KMemoryState::CodeData) == 0x07FEBD04);
+static_assert(static_cast<u32>(KMemoryState::Normal) == 0x077EBD05);
+static_assert(static_cast<u32>(KMemoryState::Shared) == 0x04402006);
+
+static_assert(static_cast<u32>(KMemoryState::AliasCode) == 0x04DD7E08);
+static_assert(static_cast<u32>(KMemoryState::AliasCodeData) == 0x07FFBD09);
+static_assert(static_cast<u32>(KMemoryState::Ipc) == 0x045C3C0A);
+static_assert(static_cast<u32>(KMemoryState::Stack) == 0x045C3C0B);
+static_assert(static_cast<u32>(KMemoryState::ThreadLocal) == 0x0400200C);
+static_assert(static_cast<u32>(KMemoryState::Transfered) == 0x055C3C0D);
+static_assert(static_cast<u32>(KMemoryState::SharedTransfered) == 0x045C380E);
+static_assert(static_cast<u32>(KMemoryState::SharedCode) == 0x0440380F);
static_assert(static_cast<u32>(KMemoryState::Inaccessible) == 0x00000010);
-static_assert(static_cast<u32>(KMemoryState::NonSecureIpc) == 0x005C3811);
-static_assert(static_cast<u32>(KMemoryState::NonDeviceIpc) == 0x004C2812);
+static_assert(static_cast<u32>(KMemoryState::NonSecureIpc) == 0x045C3811);
+static_assert(static_cast<u32>(KMemoryState::NonDeviceIpc) == 0x044C2812);
static_assert(static_cast<u32>(KMemoryState::Kernel) == 0x00002013);
-static_assert(static_cast<u32>(KMemoryState::GeneratedCode) == 0x00402214);
-static_assert(static_cast<u32>(KMemoryState::CodeOut) == 0x00402015);
+static_assert(static_cast<u32>(KMemoryState::GeneratedCode) == 0x04402214);
+static_assert(static_cast<u32>(KMemoryState::CodeOut) == 0x04402015);
static_assert(static_cast<u32>(KMemoryState::Coverage) == 0x00002016);
+static_assert(static_cast<u32>(KMemoryState::Insecure) == 0x05583817);
enum class KMemoryPermission : u8 {
None = 0,
All = static_cast<u8>(~None),
- Read = 1 << 0,
- Write = 1 << 1,
- Execute = 1 << 2,
-
- ReadAndWrite = Read | Write,
- ReadAndExecute = Read | Execute,
-
- UserMask = static_cast<u8>(Svc::MemoryPermission::Read | Svc::MemoryPermission::Write |
- Svc::MemoryPermission::Execute),
-
KernelShift = 3,
- KernelRead = Read << KernelShift,
- KernelWrite = Write << KernelShift,
- KernelExecute = Execute << KernelShift,
+ KernelRead = static_cast<u8>(Svc::MemoryPermission::Read) << KernelShift,
+ KernelWrite = static_cast<u8>(Svc::MemoryPermission::Write) << KernelShift,
+ KernelExecute = static_cast<u8>(Svc::MemoryPermission::Execute) << KernelShift,
NotMapped = (1 << (2 * KernelShift)),
KernelReadWrite = KernelRead | KernelWrite,
KernelReadExecute = KernelRead | KernelExecute,
- UserRead = Read | KernelRead,
- UserWrite = Write | KernelWrite,
- UserExecute = Execute,
+ UserRead = static_cast<u8>(Svc::MemoryPermission::Read) | KernelRead,
+ UserWrite = static_cast<u8>(Svc::MemoryPermission::Write) | KernelWrite,
+ UserExecute = static_cast<u8>(Svc::MemoryPermission::Execute),
UserReadWrite = UserRead | UserWrite,
UserReadExecute = UserRead | UserExecute,
- IpcLockChangeMask = NotMapped | UserReadWrite
+ UserMask = static_cast<u8>(Svc::MemoryPermission::Read | Svc::MemoryPermission::Write |
+ Svc::MemoryPermission::Execute),
+
+ IpcLockChangeMask = NotMapped | UserReadWrite,
};
DECLARE_ENUM_FLAG_OPERATORS(KMemoryPermission);
@@ -468,6 +474,7 @@ public:
constexpr void UpdateDeviceDisableMergeStateForShareLeft(
[[maybe_unused]] KMemoryPermission new_perm, bool left, [[maybe_unused]] bool right) {
+ // New permission/right aren't used.
if (left) {
m_disable_merge_attribute = static_cast<KMemoryBlockDisableMergeAttribute>(
m_disable_merge_attribute | KMemoryBlockDisableMergeAttribute::DeviceLeft);
@@ -478,6 +485,7 @@ public:
constexpr void UpdateDeviceDisableMergeStateForShareRight(
[[maybe_unused]] KMemoryPermission new_perm, [[maybe_unused]] bool left, bool right) {
+ // New permission/left aren't used.
if (right) {
m_disable_merge_attribute = static_cast<KMemoryBlockDisableMergeAttribute>(
m_disable_merge_attribute | KMemoryBlockDisableMergeAttribute::DeviceRight);
@@ -494,6 +502,8 @@ public:
constexpr void ShareToDevice([[maybe_unused]] KMemoryPermission new_perm, bool left,
bool right) {
+ // New permission isn't used.
+
// We must either be shared or have a zero lock count.
ASSERT((m_attribute & KMemoryAttribute::DeviceShared) == KMemoryAttribute::DeviceShared ||
m_device_use_count == 0);
@@ -509,6 +519,7 @@ public:
constexpr void UpdateDeviceDisableMergeStateForUnshareLeft(
[[maybe_unused]] KMemoryPermission new_perm, bool left, [[maybe_unused]] bool right) {
+ // New permission/right aren't used.
if (left) {
if (!m_device_disable_merge_left_count) {
@@ -528,6 +539,8 @@ public:
constexpr void UpdateDeviceDisableMergeStateForUnshareRight(
[[maybe_unused]] KMemoryPermission new_perm, [[maybe_unused]] bool left, bool right) {
+ // New permission/left aren't used.
+
if (right) {
const u16 old_device_disable_merge_right_count = m_device_disable_merge_right_count--;
ASSERT(old_device_disable_merge_right_count > 0);
@@ -546,6 +559,8 @@ public:
constexpr void UnshareToDevice([[maybe_unused]] KMemoryPermission new_perm, bool left,
bool right) {
+ // New permission isn't used.
+
// We must be shared.
ASSERT((m_attribute & KMemoryAttribute::DeviceShared) == KMemoryAttribute::DeviceShared);
@@ -563,6 +578,7 @@ public:
constexpr void UnshareToDeviceRight([[maybe_unused]] KMemoryPermission new_perm, bool left,
bool right) {
+ // New permission isn't used.
// We must be shared.
ASSERT((m_attribute & KMemoryAttribute::DeviceShared) == KMemoryAttribute::DeviceShared);
@@ -613,6 +629,8 @@ public:
constexpr void UnlockForIpc([[maybe_unused]] KMemoryPermission new_perm, bool left,
[[maybe_unused]] bool right) {
+ // New permission isn't used.
+
// We must be locked.
ASSERT((m_attribute & KMemoryAttribute::IpcLocked) == KMemoryAttribute::IpcLocked);
diff --git a/src/core/hle/kernel/k_memory_layout.cpp b/src/core/hle/kernel/k_memory_layout.cpp
index 55dc296d0..72c3ee4b7 100644
--- a/src/core/hle/kernel/k_memory_layout.cpp
+++ b/src/core/hle/kernel/k_memory_layout.cpp
@@ -153,13 +153,9 @@ void KMemoryLayout::InitializeLinearMemoryRegionTrees(PAddr aligned_linear_phys_
}
}
-size_t KMemoryLayout::GetResourceRegionSizeForInit() {
- // Calculate resource region size based on whether we allow extra threads.
- const bool use_extra_resources = KSystemControl::Init::ShouldIncreaseThreadResourceLimit();
- size_t resource_region_size =
- KernelResourceSize + (use_extra_resources ? KernelSlabHeapAdditionalSize : 0);
-
- return resource_region_size;
+size_t KMemoryLayout::GetResourceRegionSizeForInit(bool use_extra_resource) {
+ return KernelResourceSize + KSystemControl::SecureAppletMemorySize +
+ (use_extra_resource ? KernelSlabHeapAdditionalSize + KernelPageBufferAdditionalSize : 0);
}
} // namespace Kernel
diff --git a/src/core/hle/kernel/k_memory_layout.h b/src/core/hle/kernel/k_memory_layout.h
index 884fc623a..fd6e1d3e6 100644
--- a/src/core/hle/kernel/k_memory_layout.h
+++ b/src/core/hle/kernel/k_memory_layout.h
@@ -60,10 +60,12 @@ constexpr std::size_t KernelSlabHeapGapsSizeMax = 2_MiB - 64_KiB;
constexpr std::size_t KernelSlabHeapSize = KernelSlabHeapDataSize + KernelSlabHeapGapsSizeMax;
// NOTE: This is calculated from KThread slab counts, assuming KThread size <= 0x860.
-constexpr std::size_t KernelSlabHeapAdditionalSize = 0x68000;
+constexpr size_t KernelPageBufferHeapSize = 0x3E0000;
+constexpr size_t KernelSlabHeapAdditionalSize = 0x148000;
+constexpr size_t KernelPageBufferAdditionalSize = 0x33C000;
-constexpr std::size_t KernelResourceSize =
- KernelPageTableHeapSize + KernelInitialPageHeapSize + KernelSlabHeapSize;
+constexpr std::size_t KernelResourceSize = KernelPageTableHeapSize + KernelInitialPageHeapSize +
+ KernelSlabHeapSize + KernelPageBufferHeapSize;
constexpr bool IsKernelAddressKey(VAddr key) {
return KernelVirtualAddressSpaceBase <= key && key <= KernelVirtualAddressSpaceLast;
@@ -168,6 +170,11 @@ public:
KMemoryRegionType_VirtualDramKernelTraceBuffer));
}
+ const KMemoryRegion& GetSecureAppletMemoryRegion() {
+ return Dereference(GetVirtualMemoryRegionTree().FindByType(
+ KMemoryRegionType_VirtualDramKernelSecureAppletMemory));
+ }
+
const KMemoryRegion& GetVirtualLinearRegion(VAddr address) const {
return Dereference(FindVirtualLinear(address));
}
@@ -229,7 +236,7 @@ public:
void InitializeLinearMemoryRegionTrees(PAddr aligned_linear_phys_start,
VAddr linear_virtual_start);
- static size_t GetResourceRegionSizeForInit();
+ static size_t GetResourceRegionSizeForInit(bool use_extra_resource);
auto GetKernelRegionExtents() const {
return GetVirtualMemoryRegionTree().GetDerivedRegionExtents(KMemoryRegionType_Kernel);
@@ -279,6 +286,10 @@ public:
return GetPhysicalMemoryRegionTree().GetDerivedRegionExtents(
KMemoryRegionType_DramKernelSlab);
}
+ auto GetKernelSecureAppletMemoryRegionPhysicalExtents() {
+ return GetPhysicalMemoryRegionTree().GetDerivedRegionExtents(
+ KMemoryRegionType_DramKernelSecureAppletMemory);
+ }
auto GetKernelPageTableHeapRegionPhysicalExtents() const {
return GetPhysicalMemoryRegionTree().GetDerivedRegionExtents(
KMemoryRegionType_DramKernelPtHeap);
diff --git a/src/core/hle/kernel/k_memory_manager.cpp b/src/core/hle/kernel/k_memory_manager.cpp
index 646711505..c4bf306e8 100644
--- a/src/core/hle/kernel/k_memory_manager.cpp
+++ b/src/core/hle/kernel/k_memory_manager.cpp
@@ -29,43 +29,44 @@ constexpr KMemoryManager::Pool GetPoolFromMemoryRegionType(u32 type) {
} else if ((type | KMemoryRegionType_DramSystemNonSecurePool) == type) {
return KMemoryManager::Pool::SystemNonSecure;
} else {
- ASSERT_MSG(false, "InvalidMemoryRegionType for conversion to Pool");
- return {};
+ UNREACHABLE_MSG("InvalidMemoryRegionType for conversion to Pool");
}
}
} // namespace
-KMemoryManager::KMemoryManager(Core::System& system_)
- : system{system_}, pool_locks{
- KLightLock{system_.Kernel()},
- KLightLock{system_.Kernel()},
- KLightLock{system_.Kernel()},
- KLightLock{system_.Kernel()},
- } {}
+KMemoryManager::KMemoryManager(Core::System& system)
+ : m_system{system}, m_memory_layout{system.Kernel().MemoryLayout()},
+ m_pool_locks{
+ KLightLock{system.Kernel()},
+ KLightLock{system.Kernel()},
+ KLightLock{system.Kernel()},
+ KLightLock{system.Kernel()},
+ } {}
void KMemoryManager::Initialize(VAddr management_region, size_t management_region_size) {
// Clear the management region to zero.
const VAddr management_region_end = management_region + management_region_size;
+ // std::memset(GetVoidPointer(management_region), 0, management_region_size);
// Reset our manager count.
- num_managers = 0;
+ m_num_managers = 0;
// Traverse the virtual memory layout tree, initializing each manager as appropriate.
- while (num_managers != MaxManagerCount) {
+ while (m_num_managers != MaxManagerCount) {
// Locate the region that should initialize the current manager.
PAddr region_address = 0;
size_t region_size = 0;
Pool region_pool = Pool::Count;
- for (const auto& it : system.Kernel().MemoryLayout().GetPhysicalMemoryRegionTree()) {
+ for (const auto& it : m_system.Kernel().MemoryLayout().GetPhysicalMemoryRegionTree()) {
// We only care about regions that we need to create managers for.
if (!it.IsDerivedFrom(KMemoryRegionType_DramUserPool)) {
continue;
}
// We want to initialize the managers in order.
- if (it.GetAttributes() != num_managers) {
+ if (it.GetAttributes() != m_num_managers) {
continue;
}
@@ -97,8 +98,8 @@ void KMemoryManager::Initialize(VAddr management_region, size_t management_regio
}
// Initialize a new manager for the region.
- Impl* manager = std::addressof(managers[num_managers++]);
- ASSERT(num_managers <= managers.size());
+ Impl* manager = std::addressof(m_managers[m_num_managers++]);
+ ASSERT(m_num_managers <= m_managers.size());
const size_t cur_size = manager->Initialize(region_address, region_size, management_region,
management_region_end, region_pool);
@@ -107,13 +108,13 @@ void KMemoryManager::Initialize(VAddr management_region, size_t management_regio
// Insert the manager into the pool list.
const auto region_pool_index = static_cast<u32>(region_pool);
- if (pool_managers_tail[region_pool_index] == nullptr) {
- pool_managers_head[region_pool_index] = manager;
+ if (m_pool_managers_tail[region_pool_index] == nullptr) {
+ m_pool_managers_head[region_pool_index] = manager;
} else {
- pool_managers_tail[region_pool_index]->SetNext(manager);
- manager->SetPrev(pool_managers_tail[region_pool_index]);
+ m_pool_managers_tail[region_pool_index]->SetNext(manager);
+ manager->SetPrev(m_pool_managers_tail[region_pool_index]);
}
- pool_managers_tail[region_pool_index] = manager;
+ m_pool_managers_tail[region_pool_index] = manager;
}
// Free each region to its corresponding heap.
@@ -121,11 +122,10 @@ void KMemoryManager::Initialize(VAddr management_region, size_t management_regio
const PAddr ini_start = GetInitialProcessBinaryPhysicalAddress();
const PAddr ini_end = ini_start + InitialProcessBinarySizeMax;
const PAddr ini_last = ini_end - 1;
- for (const auto& it : system.Kernel().MemoryLayout().GetPhysicalMemoryRegionTree()) {
+ for (const auto& it : m_system.Kernel().MemoryLayout().GetPhysicalMemoryRegionTree()) {
if (it.IsDerivedFrom(KMemoryRegionType_DramUserPool)) {
// Get the manager for the region.
- auto index = it.GetAttributes();
- auto& manager = managers[index];
+ auto& manager = m_managers[it.GetAttributes()];
const PAddr cur_start = it.GetAddress();
const PAddr cur_last = it.GetLastAddress();
@@ -162,11 +162,19 @@ void KMemoryManager::Initialize(VAddr management_region, size_t management_regio
}
// Update the used size for all managers.
- for (size_t i = 0; i < num_managers; ++i) {
- managers[i].SetInitialUsedHeapSize(reserved_sizes[i]);
+ for (size_t i = 0; i < m_num_managers; ++i) {
+ m_managers[i].SetInitialUsedHeapSize(reserved_sizes[i]);
}
}
+Result KMemoryManager::InitializeOptimizedMemory(u64 process_id, Pool pool) {
+ UNREACHABLE();
+}
+
+void KMemoryManager::FinalizeOptimizedMemory(u64 process_id, Pool pool) {
+ UNREACHABLE();
+}
+
PAddr KMemoryManager::AllocateAndOpenContinuous(size_t num_pages, size_t align_pages, u32 option) {
// Early return if we're allocating no pages.
if (num_pages == 0) {
@@ -175,7 +183,7 @@ PAddr KMemoryManager::AllocateAndOpenContinuous(size_t num_pages, size_t align_p
// Lock the pool that we're allocating from.
const auto [pool, dir] = DecodeOption(option);
- KScopedLightLock lk(pool_locks[static_cast<std::size_t>(pool)]);
+ KScopedLightLock lk(m_pool_locks[static_cast<std::size_t>(pool)]);
// Choose a heap based on our page size request.
const s32 heap_index = KPageHeap::GetAlignedBlockIndex(num_pages, align_pages);
@@ -185,7 +193,7 @@ PAddr KMemoryManager::AllocateAndOpenContinuous(size_t num_pages, size_t align_p
PAddr allocated_block = 0;
for (chosen_manager = this->GetFirstManager(pool, dir); chosen_manager != nullptr;
chosen_manager = this->GetNextManager(chosen_manager, dir)) {
- allocated_block = chosen_manager->AllocateBlock(heap_index, true);
+ allocated_block = chosen_manager->AllocateAligned(heap_index, num_pages, align_pages);
if (allocated_block != 0) {
break;
}
@@ -196,10 +204,9 @@ PAddr KMemoryManager::AllocateAndOpenContinuous(size_t num_pages, size_t align_p
return 0;
}
- // If we allocated more than we need, free some.
- const size_t allocated_pages = KPageHeap::GetBlockNumPages(heap_index);
- if (allocated_pages > num_pages) {
- chosen_manager->Free(allocated_block + num_pages * PageSize, allocated_pages - num_pages);
+ // Maintain the optimized memory bitmap, if we should.
+ if (m_has_optimized_process[static_cast<size_t>(pool)]) {
+ UNIMPLEMENTED();
}
// Open the first reference to the pages.
@@ -209,20 +216,21 @@ PAddr KMemoryManager::AllocateAndOpenContinuous(size_t num_pages, size_t align_p
}
Result KMemoryManager::AllocatePageGroupImpl(KPageGroup* out, size_t num_pages, Pool pool,
- Direction dir, bool random) {
+ Direction dir, bool unoptimized, bool random) {
// Choose a heap based on our page size request.
const s32 heap_index = KPageHeap::GetBlockIndex(num_pages);
R_UNLESS(0 <= heap_index, ResultOutOfMemory);
// Ensure that we don't leave anything un-freed.
- auto group_guard = SCOPE_GUARD({
+ ON_RESULT_FAILURE {
for (const auto& it : out->Nodes()) {
- auto& manager = this->GetManager(system.Kernel().MemoryLayout(), it.GetAddress());
- const size_t num_pages_to_free =
+ auto& manager = this->GetManager(it.GetAddress());
+ const size_t node_num_pages =
std::min(it.GetNumPages(), (manager.GetEndAddress() - it.GetAddress()) / PageSize);
- manager.Free(it.GetAddress(), num_pages_to_free);
+ manager.Free(it.GetAddress(), node_num_pages);
}
- });
+ out->Finalize();
+ };
// Keep allocating until we've allocated all our pages.
for (s32 index = heap_index; index >= 0 && num_pages > 0; index--) {
@@ -236,12 +244,17 @@ Result KMemoryManager::AllocatePageGroupImpl(KPageGroup* out, size_t num_pages,
break;
}
- // Safely add it to our group.
- {
- auto block_guard =
- SCOPE_GUARD({ cur_manager->Free(allocated_block, pages_per_alloc); });
- R_TRY(out->AddBlock(allocated_block, pages_per_alloc));
- block_guard.Cancel();
+ // Ensure we don't leak the block if we fail.
+ ON_RESULT_FAILURE_2 {
+ cur_manager->Free(allocated_block, pages_per_alloc);
+ };
+
+ // Add the block to our group.
+ R_TRY(out->AddBlock(allocated_block, pages_per_alloc));
+
+ // Maintain the optimized memory bitmap, if we should.
+ if (unoptimized) {
+ UNIMPLEMENTED();
}
num_pages -= pages_per_alloc;
@@ -253,8 +266,7 @@ Result KMemoryManager::AllocatePageGroupImpl(KPageGroup* out, size_t num_pages,
R_UNLESS(num_pages == 0, ResultOutOfMemory);
// We succeeded!
- group_guard.Cancel();
- return ResultSuccess;
+ R_SUCCEED();
}
Result KMemoryManager::AllocateAndOpen(KPageGroup* out, size_t num_pages, u32 option) {
@@ -266,10 +278,11 @@ Result KMemoryManager::AllocateAndOpen(KPageGroup* out, size_t num_pages, u32 op
// Lock the pool that we're allocating from.
const auto [pool, dir] = DecodeOption(option);
- KScopedLightLock lk(pool_locks[static_cast<size_t>(pool)]);
+ KScopedLightLock lk(m_pool_locks[static_cast<size_t>(pool)]);
// Allocate the page group.
- R_TRY(this->AllocatePageGroupImpl(out, num_pages, pool, dir, false));
+ R_TRY(this->AllocatePageGroupImpl(out, num_pages, pool, dir,
+ m_has_optimized_process[static_cast<size_t>(pool)], true));
// Open the first reference to the pages.
for (const auto& block : out->Nodes()) {
@@ -277,7 +290,7 @@ Result KMemoryManager::AllocateAndOpen(KPageGroup* out, size_t num_pages, u32 op
size_t remaining_pages = block.GetNumPages();
while (remaining_pages > 0) {
// Get the manager for the current address.
- auto& manager = this->GetManager(system.Kernel().MemoryLayout(), cur_address);
+ auto& manager = this->GetManager(cur_address);
// Process part or all of the block.
const size_t cur_pages =
@@ -290,11 +303,11 @@ Result KMemoryManager::AllocateAndOpen(KPageGroup* out, size_t num_pages, u32 op
}
}
- return ResultSuccess;
+ R_SUCCEED();
}
-Result KMemoryManager::AllocateAndOpenForProcess(KPageGroup* out, size_t num_pages, u32 option,
- u64 process_id, u8 fill_pattern) {
+Result KMemoryManager::AllocateForProcess(KPageGroup* out, size_t num_pages, u32 option,
+ u64 process_id, u8 fill_pattern) {
ASSERT(out != nullptr);
ASSERT(out->GetNumPages() == 0);
@@ -302,83 +315,89 @@ Result KMemoryManager::AllocateAndOpenForProcess(KPageGroup* out, size_t num_pag
const auto [pool, dir] = DecodeOption(option);
// Allocate the memory.
+ bool optimized;
{
// Lock the pool that we're allocating from.
- KScopedLightLock lk(pool_locks[static_cast<size_t>(pool)]);
+ KScopedLightLock lk(m_pool_locks[static_cast<size_t>(pool)]);
+
+ // Check if we have an optimized process.
+ const bool has_optimized = m_has_optimized_process[static_cast<size_t>(pool)];
+ const bool is_optimized = m_optimized_process_ids[static_cast<size_t>(pool)] == process_id;
// Allocate the page group.
- R_TRY(this->AllocatePageGroupImpl(out, num_pages, pool, dir, false));
+ R_TRY(this->AllocatePageGroupImpl(out, num_pages, pool, dir, has_optimized && !is_optimized,
+ false));
- // Open the first reference to the pages.
- for (const auto& block : out->Nodes()) {
- PAddr cur_address = block.GetAddress();
- size_t remaining_pages = block.GetNumPages();
- while (remaining_pages > 0) {
- // Get the manager for the current address.
- auto& manager = this->GetManager(system.Kernel().MemoryLayout(), cur_address);
-
- // Process part or all of the block.
- const size_t cur_pages =
- std::min(remaining_pages, manager.GetPageOffsetToEnd(cur_address));
- manager.OpenFirst(cur_address, cur_pages);
-
- // Advance.
- cur_address += cur_pages * PageSize;
- remaining_pages -= cur_pages;
- }
- }
+ // Set whether we should optimize.
+ optimized = has_optimized && is_optimized;
}
- // Set all the allocated memory.
- for (const auto& block : out->Nodes()) {
- std::memset(system.DeviceMemory().GetPointer<void>(block.GetAddress()), fill_pattern,
- block.GetSize());
- }
+ // Perform optimized memory tracking, if we should.
+ if (optimized) {
+ // Iterate over the allocated blocks.
+ for (const auto& block : out->Nodes()) {
+ // Get the block extents.
+ const PAddr block_address = block.GetAddress();
+ const size_t block_pages = block.GetNumPages();
- return ResultSuccess;
-}
+ // If it has no pages, we don't need to do anything.
+ if (block_pages == 0) {
+ continue;
+ }
-void KMemoryManager::Open(PAddr address, size_t num_pages) {
- // Repeatedly open references until we've done so for all pages.
- while (num_pages) {
- auto& manager = this->GetManager(system.Kernel().MemoryLayout(), address);
- const size_t cur_pages = std::min(num_pages, manager.GetPageOffsetToEnd(address));
+ // Fill all the pages that we need to fill.
+ bool any_new = false;
+ {
+ PAddr cur_address = block_address;
+ size_t remaining_pages = block_pages;
+ while (remaining_pages > 0) {
+ // Get the manager for the current address.
+ auto& manager = this->GetManager(cur_address);
+
+ // Process part or all of the block.
+ const size_t cur_pages =
+ std::min(remaining_pages, manager.GetPageOffsetToEnd(cur_address));
+ any_new =
+ manager.ProcessOptimizedAllocation(cur_address, cur_pages, fill_pattern);
+
+ // Advance.
+ cur_address += cur_pages * PageSize;
+ remaining_pages -= cur_pages;
+ }
+ }
- {
- KScopedLightLock lk(pool_locks[static_cast<size_t>(manager.GetPool())]);
- manager.Open(address, cur_pages);
+ // If there are new pages, update tracking for the allocation.
+ if (any_new) {
+ // Update tracking for the allocation.
+ PAddr cur_address = block_address;
+ size_t remaining_pages = block_pages;
+ while (remaining_pages > 0) {
+ // Get the manager for the current address.
+ auto& manager = this->GetManager(cur_address);
+
+ // Lock the pool for the manager.
+ KScopedLightLock lk(m_pool_locks[static_cast<size_t>(manager.GetPool())]);
+
+ // Track some or all of the current pages.
+ const size_t cur_pages =
+ std::min(remaining_pages, manager.GetPageOffsetToEnd(cur_address));
+ manager.TrackOptimizedAllocation(cur_address, cur_pages);
+
+ // Advance.
+ cur_address += cur_pages * PageSize;
+ remaining_pages -= cur_pages;
+ }
+ }
}
-
- num_pages -= cur_pages;
- address += cur_pages * PageSize;
- }
-}
-
-void KMemoryManager::Close(PAddr address, size_t num_pages) {
- // Repeatedly close references until we've done so for all pages.
- while (num_pages) {
- auto& manager = this->GetManager(system.Kernel().MemoryLayout(), address);
- const size_t cur_pages = std::min(num_pages, manager.GetPageOffsetToEnd(address));
-
- {
- KScopedLightLock lk(pool_locks[static_cast<size_t>(manager.GetPool())]);
- manager.Close(address, cur_pages);
+ } else {
+ // Set all the allocated memory.
+ for (const auto& block : out->Nodes()) {
+ std::memset(m_system.DeviceMemory().GetPointer<void>(block.GetAddress()), fill_pattern,
+ block.GetSize());
}
-
- num_pages -= cur_pages;
- address += cur_pages * PageSize;
}
-}
-void KMemoryManager::Close(const KPageGroup& pg) {
- for (const auto& node : pg.Nodes()) {
- Close(node.GetAddress(), node.GetNumPages());
- }
-}
-void KMemoryManager::Open(const KPageGroup& pg) {
- for (const auto& node : pg.Nodes()) {
- Open(node.GetAddress(), node.GetNumPages());
- }
+ R_SUCCEED();
}
size_t KMemoryManager::Impl::Initialize(PAddr address, size_t size, VAddr management,
@@ -394,18 +413,31 @@ size_t KMemoryManager::Impl::Initialize(PAddr address, size_t size, VAddr manage
ASSERT(Common::IsAligned(total_management_size, PageSize));
// Setup region.
- pool = p;
- management_region = management;
- page_reference_counts.resize(
+ m_pool = p;
+ m_management_region = management;
+ m_page_reference_counts.resize(
Kernel::Board::Nintendo::Nx::KSystemControl::Init::GetIntendedMemorySize() / PageSize);
- ASSERT(Common::IsAligned(management_region, PageSize));
+ ASSERT(Common::IsAligned(m_management_region, PageSize));
// Initialize the manager's KPageHeap.
- heap.Initialize(address, size, management + manager_size, page_heap_size);
+ m_heap.Initialize(address, size, management + manager_size, page_heap_size);
return total_management_size;
}
+void KMemoryManager::Impl::TrackUnoptimizedAllocation(PAddr block, size_t num_pages) {
+ UNREACHABLE();
+}
+
+void KMemoryManager::Impl::TrackOptimizedAllocation(PAddr block, size_t num_pages) {
+ UNREACHABLE();
+}
+
+bool KMemoryManager::Impl::ProcessOptimizedAllocation(PAddr block, size_t num_pages,
+ u8 fill_pattern) {
+ UNREACHABLE();
+}
+
size_t KMemoryManager::Impl::CalculateManagementOverheadSize(size_t region_size) {
const size_t ref_count_size = (region_size / PageSize) * sizeof(u16);
const size_t optimize_map_size =
diff --git a/src/core/hle/kernel/k_memory_manager.h b/src/core/hle/kernel/k_memory_manager.h
index dcb9b6348..401d4e644 100644
--- a/src/core/hle/kernel/k_memory_manager.h
+++ b/src/core/hle/kernel/k_memory_manager.h
@@ -21,11 +21,8 @@ namespace Kernel {
class KPageGroup;
-class KMemoryManager final {
+class KMemoryManager {
public:
- YUZU_NON_COPYABLE(KMemoryManager);
- YUZU_NON_MOVEABLE(KMemoryManager);
-
enum class Pool : u32 {
Application = 0,
Applet = 1,
@@ -45,16 +42,85 @@ public:
enum class Direction : u32 {
FromFront = 0,
FromBack = 1,
-
Shift = 0,
Mask = (0xF << Shift),
};
- explicit KMemoryManager(Core::System& system_);
+ static constexpr size_t MaxManagerCount = 10;
+
+ explicit KMemoryManager(Core::System& system);
void Initialize(VAddr management_region, size_t management_region_size);
- constexpr size_t GetSize(Pool pool) const {
+ Result InitializeOptimizedMemory(u64 process_id, Pool pool);
+ void FinalizeOptimizedMemory(u64 process_id, Pool pool);
+
+ PAddr AllocateAndOpenContinuous(size_t num_pages, size_t align_pages, u32 option);
+ Result AllocateAndOpen(KPageGroup* out, size_t num_pages, u32 option);
+ Result AllocateForProcess(KPageGroup* out, size_t num_pages, u32 option, u64 process_id,
+ u8 fill_pattern);
+
+ Pool GetPool(PAddr address) const {
+ return this->GetManager(address).GetPool();
+ }
+
+ void Open(PAddr address, size_t num_pages) {
+ // Repeatedly open references until we've done so for all pages.
+ while (num_pages) {
+ auto& manager = this->GetManager(address);
+ const size_t cur_pages = std::min(num_pages, manager.GetPageOffsetToEnd(address));
+
+ {
+ KScopedLightLock lk(m_pool_locks[static_cast<size_t>(manager.GetPool())]);
+ manager.Open(address, cur_pages);
+ }
+
+ num_pages -= cur_pages;
+ address += cur_pages * PageSize;
+ }
+ }
+
+ void OpenFirst(PAddr address, size_t num_pages) {
+ // Repeatedly open references until we've done so for all pages.
+ while (num_pages) {
+ auto& manager = this->GetManager(address);
+ const size_t cur_pages = std::min(num_pages, manager.GetPageOffsetToEnd(address));
+
+ {
+ KScopedLightLock lk(m_pool_locks[static_cast<size_t>(manager.GetPool())]);
+ manager.OpenFirst(address, cur_pages);
+ }
+
+ num_pages -= cur_pages;
+ address += cur_pages * PageSize;
+ }
+ }
+
+ void Close(PAddr address, size_t num_pages) {
+ // Repeatedly close references until we've done so for all pages.
+ while (num_pages) {
+ auto& manager = this->GetManager(address);
+ const size_t cur_pages = std::min(num_pages, manager.GetPageOffsetToEnd(address));
+
+ {
+ KScopedLightLock lk(m_pool_locks[static_cast<size_t>(manager.GetPool())]);
+ manager.Close(address, cur_pages);
+ }
+
+ num_pages -= cur_pages;
+ address += cur_pages * PageSize;
+ }
+ }
+
+ size_t GetSize() {
+ size_t total = 0;
+ for (size_t i = 0; i < m_num_managers; i++) {
+ total += m_managers[i].GetSize();
+ }
+ return total;
+ }
+
+ size_t GetSize(Pool pool) {
constexpr Direction GetSizeDirection = Direction::FromFront;
size_t total = 0;
for (auto* manager = this->GetFirstManager(pool, GetSizeDirection); manager != nullptr;
@@ -64,18 +130,36 @@ public:
return total;
}
- PAddr AllocateAndOpenContinuous(size_t num_pages, size_t align_pages, u32 option);
- Result AllocateAndOpen(KPageGroup* out, size_t num_pages, u32 option);
- Result AllocateAndOpenForProcess(KPageGroup* out, size_t num_pages, u32 option, u64 process_id,
- u8 fill_pattern);
+ size_t GetFreeSize() {
+ size_t total = 0;
+ for (size_t i = 0; i < m_num_managers; i++) {
+ KScopedLightLock lk(m_pool_locks[static_cast<size_t>(m_managers[i].GetPool())]);
+ total += m_managers[i].GetFreeSize();
+ }
+ return total;
+ }
- static constexpr size_t MaxManagerCount = 10;
+ size_t GetFreeSize(Pool pool) {
+ KScopedLightLock lk(m_pool_locks[static_cast<size_t>(pool)]);
+
+ constexpr Direction GetSizeDirection = Direction::FromFront;
+ size_t total = 0;
+ for (auto* manager = this->GetFirstManager(pool, GetSizeDirection); manager != nullptr;
+ manager = this->GetNextManager(manager, GetSizeDirection)) {
+ total += manager->GetFreeSize();
+ }
+ return total;
+ }
- void Close(PAddr address, size_t num_pages);
- void Close(const KPageGroup& pg);
+ void DumpFreeList(Pool pool) {
+ KScopedLightLock lk(m_pool_locks[static_cast<size_t>(pool)]);
- void Open(PAddr address, size_t num_pages);
- void Open(const KPageGroup& pg);
+ constexpr Direction DumpDirection = Direction::FromFront;
+ for (auto* manager = this->GetFirstManager(pool, DumpDirection); manager != nullptr;
+ manager = this->GetNextManager(manager, DumpDirection)) {
+ manager->DumpFreeList();
+ }
+ }
public:
static size_t CalculateManagementOverheadSize(size_t region_size) {
@@ -88,14 +172,13 @@ public:
}
static constexpr Pool GetPool(u32 option) {
- return static_cast<Pool>((static_cast<u32>(option) & static_cast<u32>(Pool::Mask)) >>
+ return static_cast<Pool>((option & static_cast<u32>(Pool::Mask)) >>
static_cast<u32>(Pool::Shift));
}
static constexpr Direction GetDirection(u32 option) {
- return static_cast<Direction>(
- (static_cast<u32>(option) & static_cast<u32>(Direction::Mask)) >>
- static_cast<u32>(Direction::Shift));
+ return static_cast<Direction>((option & static_cast<u32>(Direction::Mask)) >>
+ static_cast<u32>(Direction::Shift));
}
static constexpr std::tuple<Pool, Direction> DecodeOption(u32 option) {
@@ -103,74 +186,88 @@ public:
}
private:
- class Impl final {
+ class Impl {
public:
- YUZU_NON_COPYABLE(Impl);
- YUZU_NON_MOVEABLE(Impl);
+ static size_t CalculateManagementOverheadSize(size_t region_size);
+
+ static constexpr size_t CalculateOptimizedProcessOverheadSize(size_t region_size) {
+ return (Common::AlignUp((region_size / PageSize), Common::BitSize<u64>()) /
+ Common::BitSize<u64>()) *
+ sizeof(u64);
+ }
+ public:
Impl() = default;
- ~Impl() = default;
size_t Initialize(PAddr address, size_t size, VAddr management, VAddr management_end,
Pool p);
- VAddr AllocateBlock(s32 index, bool random) {
- return heap.AllocateBlock(index, random);
+ PAddr AllocateBlock(s32 index, bool random) {
+ return m_heap.AllocateBlock(index, random);
}
-
- void Free(VAddr addr, size_t num_pages) {
- heap.Free(addr, num_pages);
+ PAddr AllocateAligned(s32 index, size_t num_pages, size_t align_pages) {
+ return m_heap.AllocateAligned(index, num_pages, align_pages);
+ }
+ void Free(PAddr addr, size_t num_pages) {
+ m_heap.Free(addr, num_pages);
}
void SetInitialUsedHeapSize(size_t reserved_size) {
- heap.SetInitialUsedSize(reserved_size);
+ m_heap.SetInitialUsedSize(reserved_size);
}
- constexpr Pool GetPool() const {
- return pool;
+ void InitializeOptimizedMemory() {
+ UNIMPLEMENTED();
}
+ void TrackUnoptimizedAllocation(PAddr block, size_t num_pages);
+ void TrackOptimizedAllocation(PAddr block, size_t num_pages);
+
+ bool ProcessOptimizedAllocation(PAddr block, size_t num_pages, u8 fill_pattern);
+
+ constexpr Pool GetPool() const {
+ return m_pool;
+ }
constexpr size_t GetSize() const {
- return heap.GetSize();
+ return m_heap.GetSize();
+ }
+ constexpr PAddr GetEndAddress() const {
+ return m_heap.GetEndAddress();
}
- constexpr VAddr GetAddress() const {
- return heap.GetAddress();
+ size_t GetFreeSize() const {
+ return m_heap.GetFreeSize();
}
- constexpr VAddr GetEndAddress() const {
- return heap.GetEndAddress();
+ void DumpFreeList() const {
+ UNIMPLEMENTED();
}
constexpr size_t GetPageOffset(PAddr address) const {
- return heap.GetPageOffset(address);
+ return m_heap.GetPageOffset(address);
}
-
constexpr size_t GetPageOffsetToEnd(PAddr address) const {
- return heap.GetPageOffsetToEnd(address);
+ return m_heap.GetPageOffsetToEnd(address);
}
constexpr void SetNext(Impl* n) {
- next = n;
+ m_next = n;
}
-
constexpr void SetPrev(Impl* n) {
- prev = n;
+ m_prev = n;
}
-
constexpr Impl* GetNext() const {
- return next;
+ return m_next;
}
-
constexpr Impl* GetPrev() const {
- return prev;
+ return m_prev;
}
void OpenFirst(PAddr address, size_t num_pages) {
size_t index = this->GetPageOffset(address);
const size_t end = index + num_pages;
while (index < end) {
- const RefCount ref_count = (++page_reference_counts[index]);
+ const RefCount ref_count = (++m_page_reference_counts[index]);
ASSERT(ref_count == 1);
index++;
@@ -181,7 +278,7 @@ private:
size_t index = this->GetPageOffset(address);
const size_t end = index + num_pages;
while (index < end) {
- const RefCount ref_count = (++page_reference_counts[index]);
+ const RefCount ref_count = (++m_page_reference_counts[index]);
ASSERT(ref_count > 1);
index++;
@@ -195,8 +292,8 @@ private:
size_t free_start = 0;
size_t free_count = 0;
while (index < end) {
- ASSERT(page_reference_counts[index] > 0);
- const RefCount ref_count = (--page_reference_counts[index]);
+ ASSERT(m_page_reference_counts[index] > 0);
+ const RefCount ref_count = (--m_page_reference_counts[index]);
// Keep track of how many zero refcounts we see in a row, to minimize calls to free.
if (ref_count == 0) {
@@ -208,7 +305,7 @@ private:
}
} else {
if (free_count > 0) {
- this->Free(heap.GetAddress() + free_start * PageSize, free_count);
+ this->Free(m_heap.GetAddress() + free_start * PageSize, free_count);
free_count = 0;
}
}
@@ -217,44 +314,36 @@ private:
}
if (free_count > 0) {
- this->Free(heap.GetAddress() + free_start * PageSize, free_count);
+ this->Free(m_heap.GetAddress() + free_start * PageSize, free_count);
}
}
- static size_t CalculateManagementOverheadSize(size_t region_size);
-
- static constexpr size_t CalculateOptimizedProcessOverheadSize(size_t region_size) {
- return (Common::AlignUp((region_size / PageSize), Common::BitSize<u64>()) /
- Common::BitSize<u64>()) *
- sizeof(u64);
- }
-
private:
using RefCount = u16;
- KPageHeap heap;
- std::vector<RefCount> page_reference_counts;
- VAddr management_region{};
- Pool pool{};
- Impl* next{};
- Impl* prev{};
+ KPageHeap m_heap;
+ std::vector<RefCount> m_page_reference_counts;
+ VAddr m_management_region{};
+ Pool m_pool{};
+ Impl* m_next{};
+ Impl* m_prev{};
};
private:
- Impl& GetManager(const KMemoryLayout& memory_layout, PAddr address) {
- return managers[memory_layout.GetPhysicalLinearRegion(address).GetAttributes()];
+ Impl& GetManager(PAddr address) {
+ return m_managers[m_memory_layout.GetPhysicalLinearRegion(address).GetAttributes()];
}
- const Impl& GetManager(const KMemoryLayout& memory_layout, PAddr address) const {
- return managers[memory_layout.GetPhysicalLinearRegion(address).GetAttributes()];
+ const Impl& GetManager(PAddr address) const {
+ return m_managers[m_memory_layout.GetPhysicalLinearRegion(address).GetAttributes()];
}
- constexpr Impl* GetFirstManager(Pool pool, Direction dir) const {
- return dir == Direction::FromBack ? pool_managers_tail[static_cast<size_t>(pool)]
- : pool_managers_head[static_cast<size_t>(pool)];
+ constexpr Impl* GetFirstManager(Pool pool, Direction dir) {
+ return dir == Direction::FromBack ? m_pool_managers_tail[static_cast<size_t>(pool)]
+ : m_pool_managers_head[static_cast<size_t>(pool)];
}
- constexpr Impl* GetNextManager(Impl* cur, Direction dir) const {
+ constexpr Impl* GetNextManager(Impl* cur, Direction dir) {
if (dir == Direction::FromBack) {
return cur->GetPrev();
} else {
@@ -263,15 +352,21 @@ private:
}
Result AllocatePageGroupImpl(KPageGroup* out, size_t num_pages, Pool pool, Direction dir,
- bool random);
+ bool unoptimized, bool random);
private:
- Core::System& system;
- std::array<KLightLock, static_cast<size_t>(Pool::Count)> pool_locks;
- std::array<Impl*, MaxManagerCount> pool_managers_head{};
- std::array<Impl*, MaxManagerCount> pool_managers_tail{};
- std::array<Impl, MaxManagerCount> managers;
- size_t num_managers{};
+ template <typename T>
+ using PoolArray = std::array<T, static_cast<size_t>(Pool::Count)>;
+
+ Core::System& m_system;
+ const KMemoryLayout& m_memory_layout;
+ PoolArray<KLightLock> m_pool_locks;
+ std::array<Impl*, MaxManagerCount> m_pool_managers_head{};
+ std::array<Impl*, MaxManagerCount> m_pool_managers_tail{};
+ std::array<Impl, MaxManagerCount> m_managers;
+ size_t m_num_managers{};
+ PoolArray<u64> m_optimized_process_ids{};
+ PoolArray<bool> m_has_optimized_process{};
};
} // namespace Kernel
diff --git a/src/core/hle/kernel/k_memory_region_type.h b/src/core/hle/kernel/k_memory_region_type.h
index 7e2fcccdc..e5630c1ac 100644
--- a/src/core/hle/kernel/k_memory_region_type.h
+++ b/src/core/hle/kernel/k_memory_region_type.h
@@ -142,32 +142,38 @@ private:
} // namespace impl
-constexpr auto KMemoryRegionType_None = impl::KMemoryRegionTypeValue();
-constexpr auto KMemoryRegionType_Kernel = KMemoryRegionType_None.DeriveInitial(0, 2);
-constexpr auto KMemoryRegionType_Dram = KMemoryRegionType_None.DeriveInitial(1, 2);
+constexpr inline auto KMemoryRegionType_None = impl::KMemoryRegionTypeValue();
+
+constexpr inline auto KMemoryRegionType_Kernel = KMemoryRegionType_None.DeriveInitial(0, 2);
+constexpr inline auto KMemoryRegionType_Dram = KMemoryRegionType_None.DeriveInitial(1, 2);
static_assert(KMemoryRegionType_Kernel.GetValue() == 0x1);
static_assert(KMemoryRegionType_Dram.GetValue() == 0x2);
-constexpr auto KMemoryRegionType_DramKernelBase =
+// constexpr inline auto KMemoryRegionType_CoreLocalRegion =
+// KMemoryRegionType_None.DeriveInitial(2).Finalize();
+// static_assert(KMemoryRegionType_CoreLocalRegion.GetValue() == 0x4);
+
+constexpr inline auto KMemoryRegionType_DramKernelBase =
KMemoryRegionType_Dram.DeriveSparse(0, 3, 0)
.SetAttribute(KMemoryRegionAttr_NoUserMap)
.SetAttribute(KMemoryRegionAttr_CarveoutProtected);
-constexpr auto KMemoryRegionType_DramReservedBase = KMemoryRegionType_Dram.DeriveSparse(0, 3, 1);
-constexpr auto KMemoryRegionType_DramHeapBase =
+constexpr inline auto KMemoryRegionType_DramReservedBase =
+ KMemoryRegionType_Dram.DeriveSparse(0, 3, 1);
+constexpr inline auto KMemoryRegionType_DramHeapBase =
KMemoryRegionType_Dram.DeriveSparse(0, 3, 2).SetAttribute(KMemoryRegionAttr_LinearMapped);
static_assert(KMemoryRegionType_DramKernelBase.GetValue() ==
(0xE | KMemoryRegionAttr_CarveoutProtected | KMemoryRegionAttr_NoUserMap));
static_assert(KMemoryRegionType_DramReservedBase.GetValue() == (0x16));
static_assert(KMemoryRegionType_DramHeapBase.GetValue() == (0x26 | KMemoryRegionAttr_LinearMapped));
-constexpr auto KMemoryRegionType_DramKernelCode =
+constexpr inline auto KMemoryRegionType_DramKernelCode =
KMemoryRegionType_DramKernelBase.DeriveSparse(0, 4, 0);
-constexpr auto KMemoryRegionType_DramKernelSlab =
+constexpr inline auto KMemoryRegionType_DramKernelSlab =
KMemoryRegionType_DramKernelBase.DeriveSparse(0, 4, 1);
-constexpr auto KMemoryRegionType_DramKernelPtHeap =
+constexpr inline auto KMemoryRegionType_DramKernelPtHeap =
KMemoryRegionType_DramKernelBase.DeriveSparse(0, 4, 2).SetAttribute(
KMemoryRegionAttr_LinearMapped);
-constexpr auto KMemoryRegionType_DramKernelInitPt =
+constexpr inline auto KMemoryRegionType_DramKernelInitPt =
KMemoryRegionType_DramKernelBase.DeriveSparse(0, 4, 3).SetAttribute(
KMemoryRegionAttr_LinearMapped);
static_assert(KMemoryRegionType_DramKernelCode.GetValue() ==
@@ -181,32 +187,40 @@ static_assert(KMemoryRegionType_DramKernelInitPt.GetValue() ==
(0x44E | KMemoryRegionAttr_CarveoutProtected | KMemoryRegionAttr_NoUserMap |
KMemoryRegionAttr_LinearMapped));
-constexpr auto KMemoryRegionType_DramReservedEarly =
+constexpr inline auto KMemoryRegionType_DramKernelSecureAppletMemory =
+ KMemoryRegionType_DramKernelBase.DeriveSparse(1, 3, 0).SetAttribute(
+ KMemoryRegionAttr_LinearMapped);
+static_assert(KMemoryRegionType_DramKernelSecureAppletMemory.GetValue() ==
+ (0x18E | KMemoryRegionAttr_CarveoutProtected | KMemoryRegionAttr_NoUserMap |
+ KMemoryRegionAttr_LinearMapped));
+
+constexpr inline auto KMemoryRegionType_DramReservedEarly =
KMemoryRegionType_DramReservedBase.DeriveAttribute(KMemoryRegionAttr_NoUserMap);
static_assert(KMemoryRegionType_DramReservedEarly.GetValue() ==
(0x16 | KMemoryRegionAttr_NoUserMap));
-constexpr auto KMemoryRegionType_KernelTraceBuffer =
+constexpr inline auto KMemoryRegionType_KernelTraceBuffer =
KMemoryRegionType_DramReservedBase.DeriveSparse(0, 3, 0)
.SetAttribute(KMemoryRegionAttr_LinearMapped)
.SetAttribute(KMemoryRegionAttr_UserReadOnly);
-constexpr auto KMemoryRegionType_OnMemoryBootImage =
+constexpr inline auto KMemoryRegionType_OnMemoryBootImage =
KMemoryRegionType_DramReservedBase.DeriveSparse(0, 3, 1);
-constexpr auto KMemoryRegionType_DTB = KMemoryRegionType_DramReservedBase.DeriveSparse(0, 3, 2);
+constexpr inline auto KMemoryRegionType_DTB =
+ KMemoryRegionType_DramReservedBase.DeriveSparse(0, 3, 2);
static_assert(KMemoryRegionType_KernelTraceBuffer.GetValue() ==
(0xD6 | KMemoryRegionAttr_LinearMapped | KMemoryRegionAttr_UserReadOnly));
static_assert(KMemoryRegionType_OnMemoryBootImage.GetValue() == 0x156);
static_assert(KMemoryRegionType_DTB.GetValue() == 0x256);
-constexpr auto KMemoryRegionType_DramPoolPartition =
+constexpr inline auto KMemoryRegionType_DramPoolPartition =
KMemoryRegionType_DramHeapBase.DeriveAttribute(KMemoryRegionAttr_NoUserMap);
static_assert(KMemoryRegionType_DramPoolPartition.GetValue() ==
(0x26 | KMemoryRegionAttr_LinearMapped | KMemoryRegionAttr_NoUserMap));
-constexpr auto KMemoryRegionType_DramPoolManagement =
+constexpr inline auto KMemoryRegionType_DramPoolManagement =
KMemoryRegionType_DramPoolPartition.DeriveTransition(0, 2).DeriveTransition().SetAttribute(
KMemoryRegionAttr_CarveoutProtected);
-constexpr auto KMemoryRegionType_DramUserPool =
+constexpr inline auto KMemoryRegionType_DramUserPool =
KMemoryRegionType_DramPoolPartition.DeriveTransition(1, 2).DeriveTransition();
static_assert(KMemoryRegionType_DramPoolManagement.GetValue() ==
(0x166 | KMemoryRegionAttr_LinearMapped | KMemoryRegionAttr_NoUserMap |
@@ -214,11 +228,13 @@ static_assert(KMemoryRegionType_DramPoolManagement.GetValue() ==
static_assert(KMemoryRegionType_DramUserPool.GetValue() ==
(0x1A6 | KMemoryRegionAttr_LinearMapped | KMemoryRegionAttr_NoUserMap));
-constexpr auto KMemoryRegionType_DramApplicationPool = KMemoryRegionType_DramUserPool.Derive(4, 0);
-constexpr auto KMemoryRegionType_DramAppletPool = KMemoryRegionType_DramUserPool.Derive(4, 1);
-constexpr auto KMemoryRegionType_DramSystemNonSecurePool =
+constexpr inline auto KMemoryRegionType_DramApplicationPool =
+ KMemoryRegionType_DramUserPool.Derive(4, 0);
+constexpr inline auto KMemoryRegionType_DramAppletPool =
+ KMemoryRegionType_DramUserPool.Derive(4, 1);
+constexpr inline auto KMemoryRegionType_DramSystemNonSecurePool =
KMemoryRegionType_DramUserPool.Derive(4, 2);
-constexpr auto KMemoryRegionType_DramSystemPool =
+constexpr inline auto KMemoryRegionType_DramSystemPool =
KMemoryRegionType_DramUserPool.Derive(4, 3).SetAttribute(KMemoryRegionAttr_CarveoutProtected);
static_assert(KMemoryRegionType_DramApplicationPool.GetValue() ==
(0x7A6 | KMemoryRegionAttr_LinearMapped | KMemoryRegionAttr_NoUserMap));
@@ -230,50 +246,55 @@ static_assert(KMemoryRegionType_DramSystemPool.GetValue() ==
(0x13A6 | KMemoryRegionAttr_LinearMapped | KMemoryRegionAttr_NoUserMap |
KMemoryRegionAttr_CarveoutProtected));
-constexpr auto KMemoryRegionType_VirtualDramHeapBase = KMemoryRegionType_Dram.DeriveSparse(1, 3, 0);
-constexpr auto KMemoryRegionType_VirtualDramKernelPtHeap =
+constexpr inline auto KMemoryRegionType_VirtualDramHeapBase =
+ KMemoryRegionType_Dram.DeriveSparse(1, 3, 0);
+constexpr inline auto KMemoryRegionType_VirtualDramKernelPtHeap =
KMemoryRegionType_Dram.DeriveSparse(1, 3, 1);
-constexpr auto KMemoryRegionType_VirtualDramKernelTraceBuffer =
+constexpr inline auto KMemoryRegionType_VirtualDramKernelTraceBuffer =
KMemoryRegionType_Dram.DeriveSparse(1, 3, 2);
static_assert(KMemoryRegionType_VirtualDramHeapBase.GetValue() == 0x1A);
static_assert(KMemoryRegionType_VirtualDramKernelPtHeap.GetValue() == 0x2A);
static_assert(KMemoryRegionType_VirtualDramKernelTraceBuffer.GetValue() == 0x4A);
// UNUSED: .DeriveSparse(2, 2, 0);
-constexpr auto KMemoryRegionType_VirtualDramUnknownDebug =
+constexpr inline auto KMemoryRegionType_VirtualDramUnknownDebug =
KMemoryRegionType_Dram.DeriveSparse(2, 2, 1);
static_assert(KMemoryRegionType_VirtualDramUnknownDebug.GetValue() == (0x52));
-constexpr auto KMemoryRegionType_VirtualDramKernelInitPt =
+constexpr inline auto KMemoryRegionType_VirtualDramKernelSecureAppletMemory =
+ KMemoryRegionType_Dram.DeriveSparse(3, 1, 0);
+static_assert(KMemoryRegionType_VirtualDramKernelSecureAppletMemory.GetValue() == (0x62));
+
+constexpr inline auto KMemoryRegionType_VirtualDramKernelInitPt =
KMemoryRegionType_VirtualDramHeapBase.Derive(3, 0);
-constexpr auto KMemoryRegionType_VirtualDramPoolManagement =
+constexpr inline auto KMemoryRegionType_VirtualDramPoolManagement =
KMemoryRegionType_VirtualDramHeapBase.Derive(3, 1);
-constexpr auto KMemoryRegionType_VirtualDramUserPool =
+constexpr inline auto KMemoryRegionType_VirtualDramUserPool =
KMemoryRegionType_VirtualDramHeapBase.Derive(3, 2);
static_assert(KMemoryRegionType_VirtualDramKernelInitPt.GetValue() == 0x19A);
static_assert(KMemoryRegionType_VirtualDramPoolManagement.GetValue() == 0x29A);
static_assert(KMemoryRegionType_VirtualDramUserPool.GetValue() == 0x31A);
-// NOTE: For unknown reason, the pools are derived out-of-order here. It's worth eventually trying
-// to understand why Nintendo made this choice.
+// NOTE: For unknown reason, the pools are derived out-of-order here.
+// It's worth eventually trying to understand why Nintendo made this choice.
// UNUSED: .Derive(6, 0);
// UNUSED: .Derive(6, 1);
-constexpr auto KMemoryRegionType_VirtualDramAppletPool =
+constexpr inline auto KMemoryRegionType_VirtualDramAppletPool =
KMemoryRegionType_VirtualDramUserPool.Derive(6, 2);
-constexpr auto KMemoryRegionType_VirtualDramApplicationPool =
+constexpr inline auto KMemoryRegionType_VirtualDramApplicationPool =
KMemoryRegionType_VirtualDramUserPool.Derive(6, 3);
-constexpr auto KMemoryRegionType_VirtualDramSystemNonSecurePool =
+constexpr inline auto KMemoryRegionType_VirtualDramSystemNonSecurePool =
KMemoryRegionType_VirtualDramUserPool.Derive(6, 4);
-constexpr auto KMemoryRegionType_VirtualDramSystemPool =
+constexpr inline auto KMemoryRegionType_VirtualDramSystemPool =
KMemoryRegionType_VirtualDramUserPool.Derive(6, 5);
static_assert(KMemoryRegionType_VirtualDramAppletPool.GetValue() == 0x1B1A);
static_assert(KMemoryRegionType_VirtualDramApplicationPool.GetValue() == 0x271A);
static_assert(KMemoryRegionType_VirtualDramSystemNonSecurePool.GetValue() == 0x2B1A);
static_assert(KMemoryRegionType_VirtualDramSystemPool.GetValue() == 0x331A);
-constexpr auto KMemoryRegionType_ArchDeviceBase =
+constexpr inline auto KMemoryRegionType_ArchDeviceBase =
KMemoryRegionType_Kernel.DeriveTransition(0, 1).SetSparseOnly();
-constexpr auto KMemoryRegionType_BoardDeviceBase =
+constexpr inline auto KMemoryRegionType_BoardDeviceBase =
KMemoryRegionType_Kernel.DeriveTransition(0, 2).SetDenseOnly();
static_assert(KMemoryRegionType_ArchDeviceBase.GetValue() == 0x5);
static_assert(KMemoryRegionType_BoardDeviceBase.GetValue() == 0x5);
@@ -284,7 +305,7 @@ static_assert(KMemoryRegionType_BoardDeviceBase.GetValue() == 0x5);
#error "Unimplemented"
#else
// Default to no architecture devices.
-constexpr auto NumArchitectureDeviceRegions = 0;
+constexpr inline auto NumArchitectureDeviceRegions = 0;
#endif
static_assert(NumArchitectureDeviceRegions >= 0);
@@ -292,34 +313,35 @@ static_assert(NumArchitectureDeviceRegions >= 0);
#include "core/hle/kernel/board/nintendo/nx/k_memory_region_device_types.inc"
#else
// Default to no board devices.
-constexpr auto NumBoardDeviceRegions = 0;
+constexpr inline auto NumBoardDeviceRegions = 0;
#endif
static_assert(NumBoardDeviceRegions >= 0);
-constexpr auto KMemoryRegionType_KernelCode = KMemoryRegionType_Kernel.DeriveSparse(1, 4, 0);
-constexpr auto KMemoryRegionType_KernelStack = KMemoryRegionType_Kernel.DeriveSparse(1, 4, 1);
-constexpr auto KMemoryRegionType_KernelMisc = KMemoryRegionType_Kernel.DeriveSparse(1, 4, 2);
-constexpr auto KMemoryRegionType_KernelSlab = KMemoryRegionType_Kernel.DeriveSparse(1, 4, 3);
+constexpr inline auto KMemoryRegionType_KernelCode = KMemoryRegionType_Kernel.DeriveSparse(1, 4, 0);
+constexpr inline auto KMemoryRegionType_KernelStack =
+ KMemoryRegionType_Kernel.DeriveSparse(1, 4, 1);
+constexpr inline auto KMemoryRegionType_KernelMisc = KMemoryRegionType_Kernel.DeriveSparse(1, 4, 2);
+constexpr inline auto KMemoryRegionType_KernelSlab = KMemoryRegionType_Kernel.DeriveSparse(1, 4, 3);
static_assert(KMemoryRegionType_KernelCode.GetValue() == 0x19);
static_assert(KMemoryRegionType_KernelStack.GetValue() == 0x29);
static_assert(KMemoryRegionType_KernelMisc.GetValue() == 0x49);
static_assert(KMemoryRegionType_KernelSlab.GetValue() == 0x89);
-constexpr auto KMemoryRegionType_KernelMiscDerivedBase =
+constexpr inline auto KMemoryRegionType_KernelMiscDerivedBase =
KMemoryRegionType_KernelMisc.DeriveTransition();
static_assert(KMemoryRegionType_KernelMiscDerivedBase.GetValue() == 0x149);
// UNUSED: .Derive(7, 0);
-constexpr auto KMemoryRegionType_KernelMiscMainStack =
+constexpr inline auto KMemoryRegionType_KernelMiscMainStack =
KMemoryRegionType_KernelMiscDerivedBase.Derive(7, 1);
-constexpr auto KMemoryRegionType_KernelMiscMappedDevice =
+constexpr inline auto KMemoryRegionType_KernelMiscMappedDevice =
KMemoryRegionType_KernelMiscDerivedBase.Derive(7, 2);
-constexpr auto KMemoryRegionType_KernelMiscExceptionStack =
+constexpr inline auto KMemoryRegionType_KernelMiscExceptionStack =
KMemoryRegionType_KernelMiscDerivedBase.Derive(7, 3);
-constexpr auto KMemoryRegionType_KernelMiscUnknownDebug =
+constexpr inline auto KMemoryRegionType_KernelMiscUnknownDebug =
KMemoryRegionType_KernelMiscDerivedBase.Derive(7, 4);
// UNUSED: .Derive(7, 5);
-constexpr auto KMemoryRegionType_KernelMiscIdleStack =
+constexpr inline auto KMemoryRegionType_KernelMiscIdleStack =
KMemoryRegionType_KernelMiscDerivedBase.Derive(7, 6);
static_assert(KMemoryRegionType_KernelMiscMainStack.GetValue() == 0xB49);
static_assert(KMemoryRegionType_KernelMiscMappedDevice.GetValue() == 0xD49);
@@ -327,7 +349,8 @@ static_assert(KMemoryRegionType_KernelMiscExceptionStack.GetValue() == 0x1349);
static_assert(KMemoryRegionType_KernelMiscUnknownDebug.GetValue() == 0x1549);
static_assert(KMemoryRegionType_KernelMiscIdleStack.GetValue() == 0x2349);
-constexpr auto KMemoryRegionType_KernelTemp = KMemoryRegionType_Kernel.Advance(2).Derive(2, 0);
+constexpr inline auto KMemoryRegionType_KernelTemp =
+ KMemoryRegionType_Kernel.Advance(2).Derive(2, 0);
static_assert(KMemoryRegionType_KernelTemp.GetValue() == 0x31);
constexpr KMemoryRegionType GetTypeForVirtualLinearMapping(u32 type_id) {
@@ -335,6 +358,8 @@ constexpr KMemoryRegionType GetTypeForVirtualLinearMapping(u32 type_id) {
return KMemoryRegionType_VirtualDramKernelTraceBuffer;
} else if (KMemoryRegionType_DramKernelPtHeap.IsAncestorOf(type_id)) {
return KMemoryRegionType_VirtualDramKernelPtHeap;
+ } else if (KMemoryRegionType_DramKernelSecureAppletMemory.IsAncestorOf(type_id)) {
+ return KMemoryRegionType_VirtualDramKernelSecureAppletMemory;
} else if ((type_id | KMemoryRegionAttr_ShouldKernelMap) == type_id) {
return KMemoryRegionType_VirtualDramUnknownDebug;
} else {
diff --git a/src/core/hle/kernel/k_page_bitmap.h b/src/core/hle/kernel/k_page_bitmap.h
index c97b3dc0b..0ff987732 100644
--- a/src/core/hle/kernel/k_page_bitmap.h
+++ b/src/core/hle/kernel/k_page_bitmap.h
@@ -16,107 +16,126 @@
namespace Kernel {
class KPageBitmap {
-private:
+public:
class RandomBitGenerator {
- private:
- Common::TinyMT rng{};
- u32 entropy{};
- u32 bits_available{};
+ public:
+ RandomBitGenerator() {
+ m_rng.Initialize(static_cast<u32>(KSystemControl::GenerateRandomU64()));
+ }
+
+ u64 SelectRandomBit(u64 bitmap) {
+ u64 selected = 0;
+
+ for (size_t cur_num_bits = Common::BitSize<decltype(bitmap)>() / 2; cur_num_bits != 0;
+ cur_num_bits /= 2) {
+ const u64 high = (bitmap >> cur_num_bits);
+ const u64 low = (bitmap & (~(UINT64_C(0xFFFFFFFFFFFFFFFF) << cur_num_bits)));
+
+ // Choose high if we have high and (don't have low or select high randomly).
+ if (high && (low == 0 || this->GenerateRandomBit())) {
+ bitmap = high;
+ selected += cur_num_bits;
+ } else {
+ bitmap = low;
+ selected += 0;
+ }
+ }
+
+ return selected;
+ }
+
+ u64 GenerateRandom(u64 max) {
+ // Determine the number of bits we need.
+ const u64 bits_needed = 1 + (Common::BitSize<decltype(max)>() - std::countl_zero(max));
+
+ // Generate a random value of the desired bitwidth.
+ const u64 rnd = this->GenerateRandomBits(static_cast<u32>(bits_needed));
+
+ // Adjust the value to be in range.
+ return rnd - ((rnd / max) * max);
+ }
private:
void RefreshEntropy() {
- entropy = rng.GenerateRandomU32();
- bits_available = static_cast<u32>(Common::BitSize<decltype(entropy)>());
+ m_entropy = m_rng.GenerateRandomU32();
+ m_bits_available = static_cast<u32>(Common::BitSize<decltype(m_entropy)>());
}
bool GenerateRandomBit() {
- if (bits_available == 0) {
+ if (m_bits_available == 0) {
this->RefreshEntropy();
}
- const bool rnd_bit = (entropy & 1) != 0;
- entropy >>= 1;
- --bits_available;
+ const bool rnd_bit = (m_entropy & 1) != 0;
+ m_entropy >>= 1;
+ --m_bits_available;
return rnd_bit;
}
- public:
- RandomBitGenerator() {
- rng.Initialize(static_cast<u32>(KSystemControl::GenerateRandomU64()));
- }
+ u64 GenerateRandomBits(u32 num_bits) {
+ u64 result = 0;
- std::size_t SelectRandomBit(u64 bitmap) {
- u64 selected = 0;
+ // Iteratively add random bits to our result.
+ while (num_bits > 0) {
+ // Ensure we have random bits to take from.
+ if (m_bits_available == 0) {
+ this->RefreshEntropy();
+ }
- u64 cur_num_bits = Common::BitSize<decltype(bitmap)>() / 2;
- u64 cur_mask = (1ULL << cur_num_bits) - 1;
+ // Determine how many bits to take this round.
+ const auto cur_bits = std::min(num_bits, m_bits_available);
- while (cur_num_bits) {
- const u64 low = (bitmap >> 0) & cur_mask;
- const u64 high = (bitmap >> cur_num_bits) & cur_mask;
+ // Generate mask for our current bits.
+ const u64 mask = (static_cast<u64>(1) << cur_bits) - 1;
- bool choose_low;
- if (high == 0) {
- // If only low val is set, choose low.
- choose_low = true;
- } else if (low == 0) {
- // If only high val is set, choose high.
- choose_low = false;
- } else {
- // If both are set, choose random.
- choose_low = this->GenerateRandomBit();
- }
+ // Add bits to output from our entropy.
+ result <<= cur_bits;
+ result |= (m_entropy & mask);
- // If we chose low, proceed with low.
- if (choose_low) {
- bitmap = low;
- selected += 0;
- } else {
- bitmap = high;
- selected += cur_num_bits;
- }
+ // Remove bits from our entropy.
+ m_entropy >>= cur_bits;
+ m_bits_available -= cur_bits;
- // Proceed.
- cur_num_bits /= 2;
- cur_mask >>= cur_num_bits;
+ // Advance.
+ num_bits -= cur_bits;
}
- return selected;
+ return result;
}
+
+ private:
+ Common::TinyMT m_rng;
+ u32 m_entropy{};
+ u32 m_bits_available{};
};
public:
- static constexpr std::size_t MaxDepth = 4;
-
-private:
- std::array<u64*, MaxDepth> bit_storages{};
- RandomBitGenerator rng{};
- std::size_t num_bits{};
- std::size_t used_depths{};
+ static constexpr size_t MaxDepth = 4;
public:
KPageBitmap() = default;
- constexpr std::size_t GetNumBits() const {
- return num_bits;
+ constexpr size_t GetNumBits() const {
+ return m_num_bits;
}
constexpr s32 GetHighestDepthIndex() const {
- return static_cast<s32>(used_depths) - 1;
+ return static_cast<s32>(m_used_depths) - 1;
}
- u64* Initialize(u64* storage, std::size_t size) {
+ u64* Initialize(u64* storage, size_t size) {
// Initially, everything is un-set.
- num_bits = 0;
+ m_num_bits = 0;
// Calculate the needed bitmap depth.
- used_depths = static_cast<std::size_t>(GetRequiredDepth(size));
- ASSERT(used_depths <= MaxDepth);
+ m_used_depths = static_cast<size_t>(GetRequiredDepth(size));
+ ASSERT(m_used_depths <= MaxDepth);
// Set the bitmap pointers.
for (s32 depth = this->GetHighestDepthIndex(); depth >= 0; depth--) {
- bit_storages[depth] = storage;
+ m_bit_storages[depth] = storage;
size = Common::AlignUp(size, Common::BitSize<u64>()) / Common::BitSize<u64>();
storage += size;
+ m_end_storages[depth] = storage;
}
return storage;
@@ -128,19 +147,19 @@ public:
if (random) {
do {
- const u64 v = bit_storages[depth][offset];
+ const u64 v = m_bit_storages[depth][offset];
if (v == 0) {
// If depth is bigger than zero, then a previous level indicated a block was
// free.
ASSERT(depth == 0);
return -1;
}
- offset = offset * Common::BitSize<u64>() + rng.SelectRandomBit(v);
+ offset = offset * Common::BitSize<u64>() + m_rng.SelectRandomBit(v);
++depth;
- } while (depth < static_cast<s32>(used_depths));
+ } while (depth < static_cast<s32>(m_used_depths));
} else {
do {
- const u64 v = bit_storages[depth][offset];
+ const u64 v = m_bit_storages[depth][offset];
if (v == 0) {
// If depth is bigger than zero, then a previous level indicated a block was
// free.
@@ -149,28 +168,69 @@ public:
}
offset = offset * Common::BitSize<u64>() + std::countr_zero(v);
++depth;
- } while (depth < static_cast<s32>(used_depths));
+ } while (depth < static_cast<s32>(m_used_depths));
}
return static_cast<s64>(offset);
}
- void SetBit(std::size_t offset) {
+ s64 FindFreeRange(size_t count) {
+ // Check that it is possible to find a range.
+ const u64* const storage_start = m_bit_storages[m_used_depths - 1];
+ const u64* const storage_end = m_end_storages[m_used_depths - 1];
+
+ // If we don't have a storage to iterate (or want more blocks than fit in a single storage),
+ // we can't find a free range.
+ if (!(storage_start < storage_end && count <= Common::BitSize<u64>())) {
+ return -1;
+ }
+
+ // Walk the storages to select a random free range.
+ const size_t options_per_storage = std::max<size_t>(Common::BitSize<u64>() / count, 1);
+ const size_t num_entries = std::max<size_t>(storage_end - storage_start, 1);
+
+ const u64 free_mask = (static_cast<u64>(1) << count) - 1;
+
+ size_t num_valid_options = 0;
+ s64 chosen_offset = -1;
+ for (size_t storage_index = 0; storage_index < num_entries; ++storage_index) {
+ u64 storage = storage_start[storage_index];
+ for (size_t option = 0; option < options_per_storage; ++option) {
+ if ((storage & free_mask) == free_mask) {
+ // We've found a new valid option.
+ ++num_valid_options;
+
+ // Select the Kth valid option with probability 1/K. This leads to an overall
+ // uniform distribution.
+ if (num_valid_options == 1 || m_rng.GenerateRandom(num_valid_options) == 0) {
+ // This is our first option, so select it.
+ chosen_offset = storage_index * Common::BitSize<u64>() + option * count;
+ }
+ }
+ storage >>= count;
+ }
+ }
+
+ // Return the random offset we chose.*/
+ return chosen_offset;
+ }
+
+ void SetBit(size_t offset) {
this->SetBit(this->GetHighestDepthIndex(), offset);
- num_bits++;
+ m_num_bits++;
}
- void ClearBit(std::size_t offset) {
+ void ClearBit(size_t offset) {
this->ClearBit(this->GetHighestDepthIndex(), offset);
- num_bits--;
+ m_num_bits--;
}
- bool ClearRange(std::size_t offset, std::size_t count) {
+ bool ClearRange(size_t offset, size_t count) {
s32 depth = this->GetHighestDepthIndex();
- u64* bits = bit_storages[depth];
- std::size_t bit_ind = offset / Common::BitSize<u64>();
- if (count < Common::BitSize<u64>()) {
- const std::size_t shift = offset % Common::BitSize<u64>();
+ u64* bits = m_bit_storages[depth];
+ size_t bit_ind = offset / Common::BitSize<u64>();
+ if (count < Common::BitSize<u64>()) [[likely]] {
+ const size_t shift = offset % Common::BitSize<u64>();
ASSERT(shift + count <= Common::BitSize<u64>());
// Check that all the bits are set.
const u64 mask = ((u64(1) << count) - 1) << shift;
@@ -189,8 +249,8 @@ public:
ASSERT(offset % Common::BitSize<u64>() == 0);
ASSERT(count % Common::BitSize<u64>() == 0);
// Check that all the bits are set.
- std::size_t remaining = count;
- std::size_t i = 0;
+ size_t remaining = count;
+ size_t i = 0;
do {
if (bits[bit_ind + i++] != ~u64(0)) {
return false;
@@ -209,18 +269,18 @@ public:
} while (remaining > 0);
}
- num_bits -= count;
+ m_num_bits -= count;
return true;
}
private:
- void SetBit(s32 depth, std::size_t offset) {
+ void SetBit(s32 depth, size_t offset) {
while (depth >= 0) {
- std::size_t ind = offset / Common::BitSize<u64>();
- std::size_t which = offset % Common::BitSize<u64>();
+ size_t ind = offset / Common::BitSize<u64>();
+ size_t which = offset % Common::BitSize<u64>();
const u64 mask = u64(1) << which;
- u64* bit = std::addressof(bit_storages[depth][ind]);
+ u64* bit = std::addressof(m_bit_storages[depth][ind]);
u64 v = *bit;
ASSERT((v & mask) == 0);
*bit = v | mask;
@@ -232,13 +292,13 @@ private:
}
}
- void ClearBit(s32 depth, std::size_t offset) {
+ void ClearBit(s32 depth, size_t offset) {
while (depth >= 0) {
- std::size_t ind = offset / Common::BitSize<u64>();
- std::size_t which = offset % Common::BitSize<u64>();
+ size_t ind = offset / Common::BitSize<u64>();
+ size_t which = offset % Common::BitSize<u64>();
const u64 mask = u64(1) << which;
- u64* bit = std::addressof(bit_storages[depth][ind]);
+ u64* bit = std::addressof(m_bit_storages[depth][ind]);
u64 v = *bit;
ASSERT((v & mask) != 0);
v &= ~mask;
@@ -252,7 +312,7 @@ private:
}
private:
- static constexpr s32 GetRequiredDepth(std::size_t region_size) {
+ static constexpr s32 GetRequiredDepth(size_t region_size) {
s32 depth = 0;
while (true) {
region_size /= Common::BitSize<u64>();
@@ -264,8 +324,8 @@ private:
}
public:
- static constexpr std::size_t CalculateManagementOverheadSize(std::size_t region_size) {
- std::size_t overhead_bits = 0;
+ static constexpr size_t CalculateManagementOverheadSize(size_t region_size) {
+ size_t overhead_bits = 0;
for (s32 depth = GetRequiredDepth(region_size) - 1; depth >= 0; depth--) {
region_size =
Common::AlignUp(region_size, Common::BitSize<u64>()) / Common::BitSize<u64>();
@@ -273,6 +333,13 @@ public:
}
return overhead_bits * sizeof(u64);
}
+
+private:
+ std::array<u64*, MaxDepth> m_bit_storages{};
+ std::array<u64*, MaxDepth> m_end_storages{};
+ RandomBitGenerator m_rng;
+ size_t m_num_bits{};
+ size_t m_used_depths{};
};
} // namespace Kernel
diff --git a/src/core/hle/kernel/k_page_buffer.h b/src/core/hle/kernel/k_page_buffer.h
index aef06e213..cfedaae61 100644
--- a/src/core/hle/kernel/k_page_buffer.h
+++ b/src/core/hle/kernel/k_page_buffer.h
@@ -11,6 +11,16 @@
namespace Kernel {
+class KernelCore;
+
+class KPageBufferSlabHeap : protected impl::KSlabHeapImpl {
+public:
+ static constexpr size_t BufferSize = PageSize;
+
+public:
+ void Initialize(Core::System& system);
+};
+
class KPageBuffer final : public KSlabAllocated<KPageBuffer> {
public:
explicit KPageBuffer(KernelCore&) {}
@@ -21,8 +31,6 @@ public:
private:
[[maybe_unused]] alignas(PageSize) std::array<u8, PageSize> m_buffer{};
};
-
-static_assert(sizeof(KPageBuffer) == PageSize);
-static_assert(alignof(KPageBuffer) == PageSize);
+static_assert(sizeof(KPageBuffer) == KPageBufferSlabHeap::BufferSize);
} // namespace Kernel
diff --git a/src/core/hle/kernel/k_page_group.h b/src/core/hle/kernel/k_page_group.h
index 968753992..316f172f2 100644
--- a/src/core/hle/kernel/k_page_group.h
+++ b/src/core/hle/kernel/k_page_group.h
@@ -5,6 +5,7 @@
#include <list>
+#include "common/alignment.h"
#include "common/assert.h"
#include "common/common_types.h"
#include "core/hle/kernel/memory_types.h"
@@ -12,6 +13,89 @@
namespace Kernel {
+class KPageGroup;
+
+class KBlockInfo {
+private:
+ friend class KPageGroup;
+
+public:
+ constexpr KBlockInfo() = default;
+
+ constexpr void Initialize(PAddr addr, size_t np) {
+ ASSERT(Common::IsAligned(addr, PageSize));
+ ASSERT(static_cast<u32>(np) == np);
+
+ m_page_index = static_cast<u32>(addr) / PageSize;
+ m_num_pages = static_cast<u32>(np);
+ }
+
+ constexpr PAddr GetAddress() const {
+ return m_page_index * PageSize;
+ }
+ constexpr size_t GetNumPages() const {
+ return m_num_pages;
+ }
+ constexpr size_t GetSize() const {
+ return this->GetNumPages() * PageSize;
+ }
+ constexpr PAddr GetEndAddress() const {
+ return (m_page_index + m_num_pages) * PageSize;
+ }
+ constexpr PAddr GetLastAddress() const {
+ return this->GetEndAddress() - 1;
+ }
+
+ constexpr KBlockInfo* GetNext() const {
+ return m_next;
+ }
+
+ constexpr bool IsEquivalentTo(const KBlockInfo& rhs) const {
+ return m_page_index == rhs.m_page_index && m_num_pages == rhs.m_num_pages;
+ }
+
+ constexpr bool operator==(const KBlockInfo& rhs) const {
+ return this->IsEquivalentTo(rhs);
+ }
+
+ constexpr bool operator!=(const KBlockInfo& rhs) const {
+ return !(*this == rhs);
+ }
+
+ constexpr bool IsStrictlyBefore(PAddr addr) const {
+ const PAddr end = this->GetEndAddress();
+
+ if (m_page_index != 0 && end == 0) {
+ return false;
+ }
+
+ return end < addr;
+ }
+
+ constexpr bool operator<(PAddr addr) const {
+ return this->IsStrictlyBefore(addr);
+ }
+
+ constexpr bool TryConcatenate(PAddr addr, size_t np) {
+ if (addr != 0 && addr == this->GetEndAddress()) {
+ m_num_pages += static_cast<u32>(np);
+ return true;
+ }
+ return false;
+ }
+
+private:
+ constexpr void SetNext(KBlockInfo* next) {
+ m_next = next;
+ }
+
+private:
+ KBlockInfo* m_next{};
+ u32 m_page_index{};
+ u32 m_num_pages{};
+};
+static_assert(sizeof(KBlockInfo) <= 0x10);
+
class KPageGroup final {
public:
class Node final {
@@ -92,6 +176,8 @@ public:
return nodes.empty();
}
+ void Finalize() {}
+
private:
std::list<Node> nodes;
};
diff --git a/src/core/hle/kernel/k_page_heap.cpp b/src/core/hle/kernel/k_page_heap.cpp
index 5ede60168..7b02c7d8b 100644
--- a/src/core/hle/kernel/k_page_heap.cpp
+++ b/src/core/hle/kernel/k_page_heap.cpp
@@ -44,11 +44,11 @@ size_t KPageHeap::GetNumFreePages() const {
return num_free;
}
-PAddr KPageHeap::AllocateBlock(s32 index, bool random) {
+PAddr KPageHeap::AllocateByLinearSearch(s32 index) {
const size_t needed_size = m_blocks[index].GetSize();
for (s32 i = index; i < static_cast<s32>(m_num_blocks); i++) {
- if (const PAddr addr = m_blocks[i].PopBlock(random); addr != 0) {
+ if (const PAddr addr = m_blocks[i].PopBlock(false); addr != 0) {
if (const size_t allocated_size = m_blocks[i].GetSize(); allocated_size > needed_size) {
this->Free(addr + needed_size, (allocated_size - needed_size) / PageSize);
}
@@ -59,6 +59,88 @@ PAddr KPageHeap::AllocateBlock(s32 index, bool random) {
return 0;
}
+PAddr KPageHeap::AllocateByRandom(s32 index, size_t num_pages, size_t align_pages) {
+ // Get the size and required alignment.
+ const size_t needed_size = num_pages * PageSize;
+ const size_t align_size = align_pages * PageSize;
+
+ // Determine meta-alignment of our desired alignment size.
+ const size_t align_shift = std::countr_zero(align_size);
+
+ // Decide on a block to allocate from.
+ constexpr size_t MinimumPossibleAlignmentsForRandomAllocation = 4;
+ {
+ // By default, we'll want to look at all blocks larger than our current one.
+ s32 max_blocks = static_cast<s32>(m_num_blocks);
+
+ // Determine the maximum block we should try to allocate from.
+ size_t possible_alignments = 0;
+ for (s32 i = index; i < max_blocks; ++i) {
+ // Add the possible alignments from blocks at the current size.
+ possible_alignments += (1 + ((m_blocks[i].GetSize() - needed_size) >> align_shift)) *
+ m_blocks[i].GetNumFreeBlocks();
+
+ // If there are enough possible alignments, we don't need to look at larger blocks.
+ if (possible_alignments >= MinimumPossibleAlignmentsForRandomAllocation) {
+ max_blocks = i + 1;
+ break;
+ }
+ }
+
+ // If we have any possible alignments which require a larger block, we need to pick one.
+ if (possible_alignments > 0 && index + 1 < max_blocks) {
+ // Select a random alignment from the possibilities.
+ const size_t rnd = m_rng.GenerateRandom(possible_alignments);
+
+ // Determine which block corresponds to the random alignment we chose.
+ possible_alignments = 0;
+ for (s32 i = index; i < max_blocks; ++i) {
+ // Add the possible alignments from blocks at the current size.
+ possible_alignments +=
+ (1 + ((m_blocks[i].GetSize() - needed_size) >> align_shift)) *
+ m_blocks[i].GetNumFreeBlocks();
+
+ // If the current block gets us to our random choice, use the current block.
+ if (rnd < possible_alignments) {
+ index = i;
+ break;
+ }
+ }
+ }
+ }
+
+ // Pop a block from the index we selected.
+ if (PAddr addr = m_blocks[index].PopBlock(true); addr != 0) {
+ // Determine how much size we have left over.
+ if (const size_t leftover_size = m_blocks[index].GetSize() - needed_size;
+ leftover_size > 0) {
+ // Determine how many valid alignments we can have.
+ const size_t possible_alignments = 1 + (leftover_size >> align_shift);
+
+ // Select a random valid alignment.
+ const size_t random_offset = m_rng.GenerateRandom(possible_alignments) << align_shift;
+
+ // Free memory before the random offset.
+ if (random_offset != 0) {
+ this->Free(addr, random_offset / PageSize);
+ }
+
+ // Advance our block by the random offset.
+ addr += random_offset;
+
+ // Free memory after our allocated block.
+ if (random_offset != leftover_size) {
+ this->Free(addr + needed_size, (leftover_size - random_offset) / PageSize);
+ }
+ }
+
+ // Return the block we allocated.
+ return addr;
+ }
+
+ return 0;
+}
+
void KPageHeap::FreeBlock(PAddr block, s32 index) {
do {
block = m_blocks[index++].PushBlock(block);
diff --git a/src/core/hle/kernel/k_page_heap.h b/src/core/hle/kernel/k_page_heap.h
index 0917a8bed..9021edcf7 100644
--- a/src/core/hle/kernel/k_page_heap.h
+++ b/src/core/hle/kernel/k_page_heap.h
@@ -14,13 +14,9 @@
namespace Kernel {
-class KPageHeap final {
+class KPageHeap {
public:
- YUZU_NON_COPYABLE(KPageHeap);
- YUZU_NON_MOVEABLE(KPageHeap);
-
KPageHeap() = default;
- ~KPageHeap() = default;
constexpr PAddr GetAddress() const {
return m_heap_address;
@@ -57,7 +53,20 @@ public:
m_initial_used_size = m_heap_size - free_size - reserved_size;
}
- PAddr AllocateBlock(s32 index, bool random);
+ PAddr AllocateBlock(s32 index, bool random) {
+ if (random) {
+ const size_t block_pages = m_blocks[index].GetNumPages();
+ return this->AllocateByRandom(index, block_pages, block_pages);
+ } else {
+ return this->AllocateByLinearSearch(index);
+ }
+ }
+
+ PAddr AllocateAligned(s32 index, size_t num_pages, size_t align_pages) {
+ // TODO: linear search support?
+ return this->AllocateByRandom(index, num_pages, align_pages);
+ }
+
void Free(PAddr addr, size_t num_pages);
static size_t CalculateManagementOverheadSize(size_t region_size) {
@@ -68,7 +77,7 @@ public:
static constexpr s32 GetAlignedBlockIndex(size_t num_pages, size_t align_pages) {
const size_t target_pages = std::max(num_pages, align_pages);
for (size_t i = 0; i < NumMemoryBlockPageShifts; i++) {
- if (target_pages <= (size_t(1) << MemoryBlockPageShifts[i]) / PageSize) {
+ if (target_pages <= (static_cast<size_t>(1) << MemoryBlockPageShifts[i]) / PageSize) {
return static_cast<s32>(i);
}
}
@@ -77,7 +86,7 @@ public:
static constexpr s32 GetBlockIndex(size_t num_pages) {
for (s32 i = static_cast<s32>(NumMemoryBlockPageShifts) - 1; i >= 0; i--) {
- if (num_pages >= (size_t(1) << MemoryBlockPageShifts[i]) / PageSize) {
+ if (num_pages >= (static_cast<size_t>(1) << MemoryBlockPageShifts[i]) / PageSize) {
return i;
}
}
@@ -85,7 +94,7 @@ public:
}
static constexpr size_t GetBlockSize(size_t index) {
- return size_t(1) << MemoryBlockPageShifts[index];
+ return static_cast<size_t>(1) << MemoryBlockPageShifts[index];
}
static constexpr size_t GetBlockNumPages(size_t index) {
@@ -93,13 +102,9 @@ public:
}
private:
- class Block final {
+ class Block {
public:
- YUZU_NON_COPYABLE(Block);
- YUZU_NON_MOVEABLE(Block);
-
Block() = default;
- ~Block() = default;
constexpr size_t GetShift() const {
return m_block_shift;
@@ -201,6 +206,9 @@ private:
};
private:
+ PAddr AllocateByLinearSearch(s32 index);
+ PAddr AllocateByRandom(s32 index, size_t num_pages, size_t align_pages);
+
static size_t CalculateManagementOverheadSize(size_t region_size, const size_t* block_shifts,
size_t num_block_shifts);
@@ -209,7 +217,8 @@ private:
size_t m_heap_size{};
size_t m_initial_used_size{};
size_t m_num_blocks{};
- std::array<Block, NumMemoryBlockPageShifts> m_blocks{};
+ std::array<Block, NumMemoryBlockPageShifts> m_blocks;
+ KPageBitmap::RandomBitGenerator m_rng;
std::vector<u64> m_management_data;
};
diff --git a/src/core/hle/kernel/k_page_table.cpp b/src/core/hle/kernel/k_page_table.cpp
index 307e491cb..fab55a057 100644
--- a/src/core/hle/kernel/k_page_table.cpp
+++ b/src/core/hle/kernel/k_page_table.cpp
@@ -15,6 +15,7 @@
#include "core/hle/kernel/k_resource_limit.h"
#include "core/hle/kernel/k_scoped_resource_reservation.h"
#include "core/hle/kernel/k_system_control.h"
+#include "core/hle/kernel/k_system_resource.h"
#include "core/hle/kernel/kernel.h"
#include "core/hle/kernel/svc_results.h"
#include "core/memory.h"
@@ -23,6 +24,61 @@ namespace Kernel {
namespace {
+class KScopedLightLockPair {
+ YUZU_NON_COPYABLE(KScopedLightLockPair);
+ YUZU_NON_MOVEABLE(KScopedLightLockPair);
+
+private:
+ KLightLock* m_lower;
+ KLightLock* m_upper;
+
+public:
+ KScopedLightLockPair(KLightLock& lhs, KLightLock& rhs) {
+ // Ensure our locks are in a consistent order.
+ if (std::addressof(lhs) <= std::addressof(rhs)) {
+ m_lower = std::addressof(lhs);
+ m_upper = std::addressof(rhs);
+ } else {
+ m_lower = std::addressof(rhs);
+ m_upper = std::addressof(lhs);
+ }
+
+ // Acquire both locks.
+ m_lower->Lock();
+ if (m_lower != m_upper) {
+ m_upper->Lock();
+ }
+ }
+
+ ~KScopedLightLockPair() {
+ // Unlock the upper lock.
+ if (m_upper != nullptr && m_upper != m_lower) {
+ m_upper->Unlock();
+ }
+
+ // Unlock the lower lock.
+ if (m_lower != nullptr) {
+ m_lower->Unlock();
+ }
+ }
+
+public:
+ // Utility.
+ void TryUnlockHalf(KLightLock& lock) {
+ // Only allow unlocking if the lock is half the pair.
+ if (m_lower != m_upper) {
+ // We want to be sure the lock is one we own.
+ if (m_lower == std::addressof(lock)) {
+ lock.Unlock();
+ m_lower = nullptr;
+ } else if (m_upper == std::addressof(lock)) {
+ lock.Unlock();
+ m_upper = nullptr;
+ }
+ }
+ }
+};
+
using namespace Common::Literals;
constexpr size_t GetAddressSpaceWidthFromType(FileSys::ProgramAddressSpaceType as_type) {
@@ -49,9 +105,10 @@ KPageTable::KPageTable(Core::System& system_)
KPageTable::~KPageTable() = default;
Result KPageTable::InitializeForProcess(FileSys::ProgramAddressSpaceType as_type, bool enable_aslr,
- VAddr code_addr, size_t code_size,
- KMemoryBlockSlabManager* mem_block_slab_manager,
- KMemoryManager::Pool pool) {
+ bool enable_das_merge, bool from_back,
+ KMemoryManager::Pool pool, VAddr code_addr,
+ size_t code_size, KSystemResource* system_resource,
+ KResourceLimit* resource_limit) {
const auto GetSpaceStart = [this](KAddressSpaceInfo::Type type) {
return KAddressSpaceInfo::GetAddressSpaceStart(m_address_space_width, type);
@@ -112,11 +169,13 @@ Result KPageTable::InitializeForProcess(FileSys::ProgramAddressSpaceType as_type
// Set other basic fields
m_enable_aslr = enable_aslr;
- m_enable_device_address_space_merge = false;
+ m_enable_device_address_space_merge = enable_das_merge;
m_address_space_start = start;
m_address_space_end = end;
m_is_kernel = false;
- m_memory_block_slab_manager = mem_block_slab_manager;
+ m_memory_block_slab_manager = system_resource->GetMemoryBlockSlabManagerPointer();
+ m_block_info_manager = system_resource->GetBlockInfoManagerPointer();
+ m_resource_limit = resource_limit;
// Determine the region we can place our undetermineds in
VAddr alloc_start{};
@@ -215,10 +274,22 @@ Result KPageTable::InitializeForProcess(FileSys::ProgramAddressSpaceType as_type
}
}
- // Set heap members
+ // Set heap and fill members.
m_current_heap_end = m_heap_region_start;
m_max_heap_size = 0;
- m_max_physical_memory_size = 0;
+ m_mapped_physical_memory_size = 0;
+ m_mapped_unsafe_physical_memory = 0;
+ m_mapped_insecure_memory = 0;
+ m_mapped_ipc_server_memory = 0;
+
+ m_heap_fill_value = 0;
+ m_ipc_fill_value = 0;
+ m_stack_fill_value = 0;
+
+ // Set allocation option.
+ m_allocate_option =
+ KMemoryManager::EncodeOption(pool, from_back ? KMemoryManager::Direction::FromBack
+ : KMemoryManager::Direction::FromFront);
// Ensure that we regions inside our address space
auto IsInAddressSpace = [&](VAddr addr) {
@@ -267,6 +338,16 @@ void KPageTable::Finalize() {
m_system.Memory().UnmapRegion(*m_page_table_impl, addr, size);
});
+ // Release any insecure mapped memory.
+ if (m_mapped_insecure_memory) {
+ UNIMPLEMENTED();
+ }
+
+ // Release any ipc server memory.
+ if (m_mapped_ipc_server_memory) {
+ UNIMPLEMENTED();
+ }
+
// Close the backing page table, as the destructor is not called for guest objects.
m_page_table_impl.reset();
}
@@ -650,7 +731,8 @@ bool KPageTable::IsValidPageGroup(const KPageGroup& pg_ll, VAddr addr, size_t nu
Result KPageTable::UnmapProcessMemory(VAddr dst_addr, size_t size, KPageTable& src_page_table,
VAddr src_addr) {
- KScopedLightLock lk(m_general_lock);
+ // Acquire the table locks.
+ KScopedLightLockPair lk(src_page_table.m_general_lock, m_general_lock);
const size_t num_pages{size / PageSize};
@@ -686,9 +768,753 @@ Result KPageTable::UnmapProcessMemory(VAddr dst_addr, size_t size, KPageTable& s
R_SUCCEED();
}
+Result KPageTable::SetupForIpcClient(PageLinkedList* page_list, size_t* out_blocks_needed,
+ VAddr address, size_t size, KMemoryPermission test_perm,
+ KMemoryState dst_state) {
+ // Validate pre-conditions.
+ ASSERT(this->IsLockedByCurrentThread());
+ ASSERT(test_perm == KMemoryPermission::UserReadWrite ||
+ test_perm == KMemoryPermission::UserRead);
+
+ // Check that the address is in range.
+ R_UNLESS(this->Contains(address, size), ResultInvalidCurrentMemory);
+
+ // Get the source permission.
+ const auto src_perm = (test_perm == KMemoryPermission::UserReadWrite)
+ ? KMemoryPermission::KernelReadWrite | KMemoryPermission::NotMapped
+ : KMemoryPermission::UserRead;
+
+ // Get aligned extents.
+ const VAddr aligned_src_start = Common::AlignDown((address), PageSize);
+ const VAddr aligned_src_end = Common::AlignUp((address) + size, PageSize);
+ const VAddr mapping_src_start = Common::AlignUp((address), PageSize);
+ const VAddr mapping_src_end = Common::AlignDown((address) + size, PageSize);
+
+ const auto aligned_src_last = (aligned_src_end)-1;
+ const auto mapping_src_last = (mapping_src_end)-1;
+
+ // Get the test state and attribute mask.
+ KMemoryState test_state;
+ KMemoryAttribute test_attr_mask;
+ switch (dst_state) {
+ case KMemoryState::Ipc:
+ test_state = KMemoryState::FlagCanUseIpc;
+ test_attr_mask =
+ KMemoryAttribute::Uncached | KMemoryAttribute::DeviceShared | KMemoryAttribute::Locked;
+ break;
+ case KMemoryState::NonSecureIpc:
+ test_state = KMemoryState::FlagCanUseNonSecureIpc;
+ test_attr_mask = KMemoryAttribute::Uncached | KMemoryAttribute::Locked;
+ break;
+ case KMemoryState::NonDeviceIpc:
+ test_state = KMemoryState::FlagCanUseNonDeviceIpc;
+ test_attr_mask = KMemoryAttribute::Uncached | KMemoryAttribute::Locked;
+ break;
+ default:
+ R_THROW(ResultInvalidCombination);
+ }
+
+ // Ensure that on failure, we roll back appropriately.
+ size_t mapped_size = 0;
+ ON_RESULT_FAILURE {
+ if (mapped_size > 0) {
+ this->CleanupForIpcClientOnServerSetupFailure(page_list, mapping_src_start, mapped_size,
+ src_perm);
+ }
+ };
+
+ size_t blocks_needed = 0;
+
+ // Iterate, mapping as needed.
+ KMemoryBlockManager::const_iterator it = m_memory_block_manager.FindIterator(aligned_src_start);
+ while (true) {
+ const KMemoryInfo info = it->GetMemoryInfo();
+
+ // Validate the current block.
+ R_TRY(this->CheckMemoryState(info, test_state, test_state, test_perm, test_perm,
+ test_attr_mask, KMemoryAttribute::None));
+
+ if (mapping_src_start < mapping_src_end && (mapping_src_start) < info.GetEndAddress() &&
+ info.GetAddress() < (mapping_src_end)) {
+ const auto cur_start =
+ info.GetAddress() >= (mapping_src_start) ? info.GetAddress() : (mapping_src_start);
+ const auto cur_end = mapping_src_last >= info.GetLastAddress() ? info.GetEndAddress()
+ : (mapping_src_end);
+ const size_t cur_size = cur_end - cur_start;
+
+ if (info.GetAddress() < (mapping_src_start)) {
+ ++blocks_needed;
+ }
+ if (mapping_src_last < info.GetLastAddress()) {
+ ++blocks_needed;
+ }
+
+ // Set the permissions on the block, if we need to.
+ if ((info.GetPermission() & KMemoryPermission::IpcLockChangeMask) != src_perm) {
+ R_TRY(Operate(cur_start, cur_size / PageSize, src_perm,
+ OperationType::ChangePermissions));
+ }
+
+ // Note that we mapped this part.
+ mapped_size += cur_size;
+ }
+
+ // If the block is at the end, we're done.
+ if (aligned_src_last <= info.GetLastAddress()) {
+ break;
+ }
+
+ // Advance.
+ ++it;
+ ASSERT(it != m_memory_block_manager.end());
+ }
+
+ if (out_blocks_needed != nullptr) {
+ ASSERT(blocks_needed <= KMemoryBlockManagerUpdateAllocator::MaxBlocks);
+ *out_blocks_needed = blocks_needed;
+ }
+
+ R_SUCCEED();
+}
+
+Result KPageTable::SetupForIpcServer(VAddr* out_addr, size_t size, VAddr src_addr,
+ KMemoryPermission test_perm, KMemoryState dst_state,
+ KPageTable& src_page_table, bool send) {
+ ASSERT(this->IsLockedByCurrentThread());
+ ASSERT(src_page_table.IsLockedByCurrentThread());
+
+ // Check that we can theoretically map.
+ const VAddr region_start = m_alias_region_start;
+ const size_t region_size = m_alias_region_end - m_alias_region_start;
+ R_UNLESS(size < region_size, ResultOutOfAddressSpace);
+
+ // Get aligned source extents.
+ const VAddr src_start = src_addr;
+ const VAddr src_end = src_addr + size;
+ const VAddr aligned_src_start = Common::AlignDown((src_start), PageSize);
+ const VAddr aligned_src_end = Common::AlignUp((src_start) + size, PageSize);
+ const VAddr mapping_src_start = Common::AlignUp((src_start), PageSize);
+ const VAddr mapping_src_end = Common::AlignDown((src_start) + size, PageSize);
+ const size_t aligned_src_size = aligned_src_end - aligned_src_start;
+ const size_t mapping_src_size =
+ (mapping_src_start < mapping_src_end) ? (mapping_src_end - mapping_src_start) : 0;
+
+ // Select a random address to map at.
+ VAddr dst_addr =
+ this->FindFreeArea(region_start, region_size / PageSize, aligned_src_size / PageSize,
+ PageSize, 0, this->GetNumGuardPages());
+
+ R_UNLESS(dst_addr != 0, ResultOutOfAddressSpace);
+
+ // Check that we can perform the operation we're about to perform.
+ ASSERT(this->CanContain(dst_addr, aligned_src_size, dst_state));
+
+ // Create an update allocator.
+ Result allocator_result;
+ KMemoryBlockManagerUpdateAllocator allocator(std::addressof(allocator_result),
+ m_memory_block_slab_manager);
+ R_TRY(allocator_result);
+
+ // We're going to perform an update, so create a helper.
+ KScopedPageTableUpdater updater(this);
+
+ // Reserve space for any partial pages we allocate.
+ const size_t unmapped_size = aligned_src_size - mapping_src_size;
+ KScopedResourceReservation memory_reservation(m_resource_limit,
+ LimitableResource::PhysicalMemory, unmapped_size);
+ R_UNLESS(memory_reservation.Succeeded(), ResultLimitReached);
+
+ // Ensure that we manage page references correctly.
+ PAddr start_partial_page = 0;
+ PAddr end_partial_page = 0;
+ VAddr cur_mapped_addr = dst_addr;
+
+ // If the partial pages are mapped, an extra reference will have been opened. Otherwise, they'll
+ // free on scope exit.
+ SCOPE_EXIT({
+ if (start_partial_page != 0) {
+ m_system.Kernel().MemoryManager().Close(start_partial_page, 1);
+ }
+ if (end_partial_page != 0) {
+ m_system.Kernel().MemoryManager().Close(end_partial_page, 1);
+ }
+ });
+
+ ON_RESULT_FAILURE {
+ if (cur_mapped_addr != dst_addr) {
+ // HACK: Manually close the pages.
+ HACK_ClosePages(dst_addr, (cur_mapped_addr - dst_addr) / PageSize);
+
+ ASSERT(Operate(dst_addr, (cur_mapped_addr - dst_addr) / PageSize,
+ KMemoryPermission::None, OperationType::Unmap)
+ .IsSuccess());
+ }
+ };
+
+ // Allocate the start page as needed.
+ if (aligned_src_start < mapping_src_start) {
+ start_partial_page =
+ m_system.Kernel().MemoryManager().AllocateAndOpenContinuous(1, 1, m_allocate_option);
+ R_UNLESS(start_partial_page != 0, ResultOutOfMemory);
+ }
+
+ // Allocate the end page as needed.
+ if (mapping_src_end < aligned_src_end &&
+ (aligned_src_start < mapping_src_end || aligned_src_start == mapping_src_start)) {
+ end_partial_page =
+ m_system.Kernel().MemoryManager().AllocateAndOpenContinuous(1, 1, m_allocate_option);
+ R_UNLESS(end_partial_page != 0, ResultOutOfMemory);
+ }
+
+ // Get the implementation.
+ auto& src_impl = src_page_table.PageTableImpl();
+
+ // Get the fill value for partial pages.
+ const auto fill_val = m_ipc_fill_value;
+
+ // Begin traversal.
+ Common::PageTable::TraversalContext context;
+ Common::PageTable::TraversalEntry next_entry;
+ bool traverse_valid = src_impl.BeginTraversal(next_entry, context, aligned_src_start);
+ ASSERT(traverse_valid);
+
+ // Prepare tracking variables.
+ PAddr cur_block_addr = next_entry.phys_addr;
+ size_t cur_block_size =
+ next_entry.block_size - ((cur_block_addr) & (next_entry.block_size - 1));
+ size_t tot_block_size = cur_block_size;
+
+ // Map the start page, if we have one.
+ if (start_partial_page != 0) {
+ // Ensure the page holds correct data.
+ const VAddr start_partial_virt =
+ GetHeapVirtualAddress(m_system.Kernel().MemoryLayout(), start_partial_page);
+ if (send) {
+ const size_t partial_offset = src_start - aligned_src_start;
+ size_t copy_size, clear_size;
+ if (src_end < mapping_src_start) {
+ copy_size = size;
+ clear_size = mapping_src_start - src_end;
+ } else {
+ copy_size = mapping_src_start - src_start;
+ clear_size = 0;
+ }
+
+ std::memset(m_system.Memory().GetPointer<void>(start_partial_virt), fill_val,
+ partial_offset);
+ std::memcpy(
+ m_system.Memory().GetPointer<void>(start_partial_virt + partial_offset),
+ m_system.Memory().GetPointer<void>(
+ GetHeapVirtualAddress(m_system.Kernel().MemoryLayout(), cur_block_addr) +
+ partial_offset),
+ copy_size);
+ if (clear_size > 0) {
+ std::memset(m_system.Memory().GetPointer<void>(start_partial_virt + partial_offset +
+ copy_size),
+ fill_val, clear_size);
+ }
+ } else {
+ std::memset(m_system.Memory().GetPointer<void>(start_partial_virt), fill_val, PageSize);
+ }
+
+ // Map the page.
+ R_TRY(Operate(cur_mapped_addr, 1, test_perm, OperationType::Map, start_partial_page));
+
+ // HACK: Manually open the pages.
+ HACK_OpenPages(start_partial_page, 1);
+
+ // Update tracking extents.
+ cur_mapped_addr += PageSize;
+ cur_block_addr += PageSize;
+ cur_block_size -= PageSize;
+
+ // If the block's size was one page, we may need to continue traversal.
+ if (cur_block_size == 0 && aligned_src_size > PageSize) {
+ traverse_valid = src_impl.ContinueTraversal(next_entry, context);
+ ASSERT(traverse_valid);
+
+ cur_block_addr = next_entry.phys_addr;
+ cur_block_size = next_entry.block_size;
+ tot_block_size += next_entry.block_size;
+ }
+ }
+
+ // Map the remaining pages.
+ while (aligned_src_start + tot_block_size < mapping_src_end) {
+ // Continue the traversal.
+ traverse_valid = src_impl.ContinueTraversal(next_entry, context);
+ ASSERT(traverse_valid);
+
+ // Process the block.
+ if (next_entry.phys_addr != cur_block_addr + cur_block_size) {
+ // Map the block we've been processing so far.
+ R_TRY(Operate(cur_mapped_addr, cur_block_size / PageSize, test_perm, OperationType::Map,
+ cur_block_addr));
+
+ // HACK: Manually open the pages.
+ HACK_OpenPages(cur_block_addr, cur_block_size / PageSize);
+
+ // Update tracking extents.
+ cur_mapped_addr += cur_block_size;
+ cur_block_addr = next_entry.phys_addr;
+ cur_block_size = next_entry.block_size;
+ } else {
+ cur_block_size += next_entry.block_size;
+ }
+ tot_block_size += next_entry.block_size;
+ }
+
+ // Handle the last direct-mapped page.
+ if (const VAddr mapped_block_end = aligned_src_start + tot_block_size - cur_block_size;
+ mapped_block_end < mapping_src_end) {
+ const size_t last_block_size = mapping_src_end - mapped_block_end;
+
+ // Map the last block.
+ R_TRY(Operate(cur_mapped_addr, last_block_size / PageSize, test_perm, OperationType::Map,
+ cur_block_addr));
+
+ // HACK: Manually open the pages.
+ HACK_OpenPages(cur_block_addr, last_block_size / PageSize);
+
+ // Update tracking extents.
+ cur_mapped_addr += last_block_size;
+ cur_block_addr += last_block_size;
+ if (mapped_block_end + cur_block_size < aligned_src_end &&
+ cur_block_size == last_block_size) {
+ traverse_valid = src_impl.ContinueTraversal(next_entry, context);
+ ASSERT(traverse_valid);
+
+ cur_block_addr = next_entry.phys_addr;
+ }
+ }
+
+ // Map the end page, if we have one.
+ if (end_partial_page != 0) {
+ // Ensure the page holds correct data.
+ const VAddr end_partial_virt =
+ GetHeapVirtualAddress(m_system.Kernel().MemoryLayout(), end_partial_page);
+ if (send) {
+ const size_t copy_size = src_end - mapping_src_end;
+ std::memcpy(m_system.Memory().GetPointer<void>(end_partial_virt),
+ m_system.Memory().GetPointer<void>(GetHeapVirtualAddress(
+ m_system.Kernel().MemoryLayout(), cur_block_addr)),
+ copy_size);
+ std::memset(m_system.Memory().GetPointer<void>(end_partial_virt + copy_size), fill_val,
+ PageSize - copy_size);
+ } else {
+ std::memset(m_system.Memory().GetPointer<void>(end_partial_virt), fill_val, PageSize);
+ }
+
+ // Map the page.
+ R_TRY(Operate(cur_mapped_addr, 1, test_perm, OperationType::Map, end_partial_page));
+
+ // HACK: Manually open the pages.
+ HACK_OpenPages(end_partial_page, 1);
+ }
+
+ // Update memory blocks to reflect our changes
+ m_memory_block_manager.Update(std::addressof(allocator), dst_addr, aligned_src_size / PageSize,
+ dst_state, test_perm, KMemoryAttribute::None,
+ KMemoryBlockDisableMergeAttribute::Normal,
+ KMemoryBlockDisableMergeAttribute::None);
+
+ // Set the output address.
+ *out_addr = dst_addr + (src_start - aligned_src_start);
+
+ // We succeeded.
+ memory_reservation.Commit();
+ R_SUCCEED();
+}
+
+Result KPageTable::SetupForIpc(VAddr* out_dst_addr, size_t size, VAddr src_addr,
+ KPageTable& src_page_table, KMemoryPermission test_perm,
+ KMemoryState dst_state, bool send) {
+ // For convenience, alias this.
+ KPageTable& dst_page_table = *this;
+
+ // Acquire the table locks.
+ KScopedLightLockPair lk(src_page_table.m_general_lock, dst_page_table.m_general_lock);
+
+ // We're going to perform an update, so create a helper.
+ KScopedPageTableUpdater updater(std::addressof(src_page_table));
+
+ // Perform client setup.
+ size_t num_allocator_blocks;
+ R_TRY(src_page_table.SetupForIpcClient(updater.GetPageList(),
+ std::addressof(num_allocator_blocks), src_addr, size,
+ test_perm, dst_state));
+
+ // Create an update allocator.
+ Result allocator_result;
+ KMemoryBlockManagerUpdateAllocator allocator(std::addressof(allocator_result),
+ src_page_table.m_memory_block_slab_manager,
+ num_allocator_blocks);
+ R_TRY(allocator_result);
+
+ // Get the mapped extents.
+ const VAddr src_map_start = Common::AlignUp((src_addr), PageSize);
+ const VAddr src_map_end = Common::AlignDown((src_addr) + size, PageSize);
+ const size_t src_map_size = src_map_end - src_map_start;
+
+ // Ensure that we clean up appropriately if we fail after this.
+ const auto src_perm = (test_perm == KMemoryPermission::UserReadWrite)
+ ? KMemoryPermission::KernelReadWrite | KMemoryPermission::NotMapped
+ : KMemoryPermission::UserRead;
+ ON_RESULT_FAILURE {
+ if (src_map_end > src_map_start) {
+ src_page_table.CleanupForIpcClientOnServerSetupFailure(
+ updater.GetPageList(), src_map_start, src_map_size, src_perm);
+ }
+ };
+
+ // Perform server setup.
+ R_TRY(dst_page_table.SetupForIpcServer(out_dst_addr, size, src_addr, test_perm, dst_state,
+ src_page_table, send));
+
+ // If anything was mapped, ipc-lock the pages.
+ if (src_map_start < src_map_end) {
+ // Get the source permission.
+ src_page_table.m_memory_block_manager.UpdateLock(std::addressof(allocator), src_map_start,
+ (src_map_end - src_map_start) / PageSize,
+ &KMemoryBlock::LockForIpc, src_perm);
+ }
+
+ R_SUCCEED();
+}
+
+Result KPageTable::CleanupForIpcServer(VAddr address, size_t size, KMemoryState dst_state) {
+ // Validate the address.
+ R_UNLESS(this->Contains(address, size), ResultInvalidCurrentMemory);
+
+ // Lock the table.
+ KScopedLightLock lk(m_general_lock);
+
+ // Validate the memory state.
+ size_t num_allocator_blocks;
+ R_TRY(this->CheckMemoryState(std::addressof(num_allocator_blocks), address, size,
+ KMemoryState::All, dst_state, KMemoryPermission::UserRead,
+ KMemoryPermission::UserRead, KMemoryAttribute::All,
+ KMemoryAttribute::None));
+
+ // Create an update allocator.
+ Result allocator_result;
+ KMemoryBlockManagerUpdateAllocator allocator(std::addressof(allocator_result),
+ m_memory_block_slab_manager, num_allocator_blocks);
+ R_TRY(allocator_result);
+
+ // We're going to perform an update, so create a helper.
+ KScopedPageTableUpdater updater(this);
+
+ // Get aligned extents.
+ const VAddr aligned_start = Common::AlignDown((address), PageSize);
+ const VAddr aligned_end = Common::AlignUp((address) + size, PageSize);
+ const size_t aligned_size = aligned_end - aligned_start;
+ const size_t aligned_num_pages = aligned_size / PageSize;
+
+ // HACK: Manually close the pages.
+ HACK_ClosePages(aligned_start, aligned_num_pages);
+
+ // Unmap the pages.
+ R_TRY(Operate(aligned_start, aligned_num_pages, KMemoryPermission::None, OperationType::Unmap));
+
+ // Update memory blocks.
+ m_memory_block_manager.Update(std::addressof(allocator), aligned_start, aligned_num_pages,
+ KMemoryState::None, KMemoryPermission::None,
+ KMemoryAttribute::None, KMemoryBlockDisableMergeAttribute::None,
+ KMemoryBlockDisableMergeAttribute::Normal);
+
+ // Release from the resource limit as relevant.
+ const VAddr mapping_start = Common::AlignUp((address), PageSize);
+ const VAddr mapping_end = Common::AlignDown((address) + size, PageSize);
+ const size_t mapping_size = (mapping_start < mapping_end) ? mapping_end - mapping_start : 0;
+ m_resource_limit->Release(LimitableResource::PhysicalMemory, aligned_size - mapping_size);
+
+ R_SUCCEED();
+}
+
+Result KPageTable::CleanupForIpcClient(VAddr address, size_t size, KMemoryState dst_state) {
+ // Validate the address.
+ R_UNLESS(this->Contains(address, size), ResultInvalidCurrentMemory);
+
+ // Get aligned source extents.
+ const VAddr mapping_start = Common::AlignUp((address), PageSize);
+ const VAddr mapping_end = Common::AlignDown((address) + size, PageSize);
+ const VAddr mapping_last = mapping_end - 1;
+ const size_t mapping_size = (mapping_start < mapping_end) ? (mapping_end - mapping_start) : 0;
+
+ // If nothing was mapped, we're actually done immediately.
+ R_SUCCEED_IF(mapping_size == 0);
+
+ // Get the test state and attribute mask.
+ KMemoryState test_state;
+ KMemoryAttribute test_attr_mask;
+ switch (dst_state) {
+ case KMemoryState::Ipc:
+ test_state = KMemoryState::FlagCanUseIpc;
+ test_attr_mask =
+ KMemoryAttribute::Uncached | KMemoryAttribute::DeviceShared | KMemoryAttribute::Locked;
+ break;
+ case KMemoryState::NonSecureIpc:
+ test_state = KMemoryState::FlagCanUseNonSecureIpc;
+ test_attr_mask = KMemoryAttribute::Uncached | KMemoryAttribute::Locked;
+ break;
+ case KMemoryState::NonDeviceIpc:
+ test_state = KMemoryState::FlagCanUseNonDeviceIpc;
+ test_attr_mask = KMemoryAttribute::Uncached | KMemoryAttribute::Locked;
+ break;
+ default:
+ R_THROW(ResultInvalidCombination);
+ }
+
+ // Lock the table.
+ // NOTE: Nintendo does this *after* creating the updater below, but this does not follow
+ // convention elsewhere in KPageTable.
+ KScopedLightLock lk(m_general_lock);
+
+ // We're going to perform an update, so create a helper.
+ KScopedPageTableUpdater updater(this);
+
+ // Ensure that on failure, we roll back appropriately.
+ size_t mapped_size = 0;
+ ON_RESULT_FAILURE {
+ if (mapped_size > 0) {
+ // Determine where the mapping ends.
+ const auto mapped_end = (mapping_start) + mapped_size;
+ const auto mapped_last = mapped_end - 1;
+
+ // Get current and next iterators.
+ KMemoryBlockManager::const_iterator start_it =
+ m_memory_block_manager.FindIterator(mapping_start);
+ KMemoryBlockManager::const_iterator next_it = start_it;
+ ++next_it;
+
+ // Get the current block info.
+ KMemoryInfo cur_info = start_it->GetMemoryInfo();
+
+ // Create tracking variables.
+ VAddr cur_address = cur_info.GetAddress();
+ size_t cur_size = cur_info.GetSize();
+ bool cur_perm_eq = cur_info.GetPermission() == cur_info.GetOriginalPermission();
+ bool cur_needs_set_perm = !cur_perm_eq && cur_info.GetIpcLockCount() == 1;
+ bool first =
+ cur_info.GetIpcDisableMergeCount() == 1 &&
+ (cur_info.GetDisableMergeAttribute() & KMemoryBlockDisableMergeAttribute::Locked) ==
+ KMemoryBlockDisableMergeAttribute::None;
+
+ while (((cur_address) + cur_size - 1) < mapped_last) {
+ // Check that we have a next block.
+ ASSERT(next_it != m_memory_block_manager.end());
+
+ // Get the next info.
+ const KMemoryInfo next_info = next_it->GetMemoryInfo();
+
+ // Check if we can consolidate the next block's permission set with the current one.
+
+ const bool next_perm_eq =
+ next_info.GetPermission() == next_info.GetOriginalPermission();
+ const bool next_needs_set_perm = !next_perm_eq && next_info.GetIpcLockCount() == 1;
+ if (cur_perm_eq == next_perm_eq && cur_needs_set_perm == next_needs_set_perm &&
+ cur_info.GetOriginalPermission() == next_info.GetOriginalPermission()) {
+ // We can consolidate the reprotection for the current and next block into a
+ // single call.
+ cur_size += next_info.GetSize();
+ } else {
+ // We have to operate on the current block.
+ if ((cur_needs_set_perm || first) && !cur_perm_eq) {
+ ASSERT(Operate(cur_address, cur_size / PageSize, cur_info.GetPermission(),
+ OperationType::ChangePermissions)
+ .IsSuccess());
+ }
+
+ // Advance.
+ cur_address = next_info.GetAddress();
+ cur_size = next_info.GetSize();
+ first = false;
+ }
+
+ // Advance.
+ cur_info = next_info;
+ cur_perm_eq = next_perm_eq;
+ cur_needs_set_perm = next_needs_set_perm;
+ ++next_it;
+ }
+
+ // Process the last block.
+ if ((first || cur_needs_set_perm) && !cur_perm_eq) {
+ ASSERT(Operate(cur_address, cur_size / PageSize, cur_info.GetPermission(),
+ OperationType::ChangePermissions)
+ .IsSuccess());
+ }
+ }
+ };
+
+ // Iterate, reprotecting as needed.
+ {
+ // Get current and next iterators.
+ KMemoryBlockManager::const_iterator start_it =
+ m_memory_block_manager.FindIterator(mapping_start);
+ KMemoryBlockManager::const_iterator next_it = start_it;
+ ++next_it;
+
+ // Validate the current block.
+ KMemoryInfo cur_info = start_it->GetMemoryInfo();
+ ASSERT(this->CheckMemoryState(cur_info, test_state, test_state, KMemoryPermission::None,
+ KMemoryPermission::None,
+ test_attr_mask | KMemoryAttribute::IpcLocked,
+ KMemoryAttribute::IpcLocked)
+ .IsSuccess());
+
+ // Create tracking variables.
+ VAddr cur_address = cur_info.GetAddress();
+ size_t cur_size = cur_info.GetSize();
+ bool cur_perm_eq = cur_info.GetPermission() == cur_info.GetOriginalPermission();
+ bool cur_needs_set_perm = !cur_perm_eq && cur_info.GetIpcLockCount() == 1;
+ bool first =
+ cur_info.GetIpcDisableMergeCount() == 1 &&
+ (cur_info.GetDisableMergeAttribute() & KMemoryBlockDisableMergeAttribute::Locked) ==
+ KMemoryBlockDisableMergeAttribute::None;
+
+ while ((cur_address + cur_size - 1) < mapping_last) {
+ // Check that we have a next block.
+ ASSERT(next_it != m_memory_block_manager.end());
+
+ // Get the next info.
+ const KMemoryInfo next_info = next_it->GetMemoryInfo();
+
+ // Validate the next block.
+ ASSERT(this->CheckMemoryState(next_info, test_state, test_state,
+ KMemoryPermission::None, KMemoryPermission::None,
+ test_attr_mask | KMemoryAttribute::IpcLocked,
+ KMemoryAttribute::IpcLocked)
+ .IsSuccess());
+
+ // Check if we can consolidate the next block's permission set with the current one.
+ const bool next_perm_eq =
+ next_info.GetPermission() == next_info.GetOriginalPermission();
+ const bool next_needs_set_perm = !next_perm_eq && next_info.GetIpcLockCount() == 1;
+ if (cur_perm_eq == next_perm_eq && cur_needs_set_perm == next_needs_set_perm &&
+ cur_info.GetOriginalPermission() == next_info.GetOriginalPermission()) {
+ // We can consolidate the reprotection for the current and next block into a single
+ // call.
+ cur_size += next_info.GetSize();
+ } else {
+ // We have to operate on the current block.
+ if ((cur_needs_set_perm || first) && !cur_perm_eq) {
+ R_TRY(Operate(cur_address, cur_size / PageSize,
+ cur_needs_set_perm ? cur_info.GetOriginalPermission()
+ : cur_info.GetPermission(),
+ OperationType::ChangePermissions));
+ }
+
+ // Mark that we mapped the block.
+ mapped_size += cur_size;
+
+ // Advance.
+ cur_address = next_info.GetAddress();
+ cur_size = next_info.GetSize();
+ first = false;
+ }
+
+ // Advance.
+ cur_info = next_info;
+ cur_perm_eq = next_perm_eq;
+ cur_needs_set_perm = next_needs_set_perm;
+ ++next_it;
+ }
+
+ // Process the last block.
+ const auto lock_count =
+ cur_info.GetIpcLockCount() +
+ (next_it != m_memory_block_manager.end()
+ ? (next_it->GetIpcDisableMergeCount() - next_it->GetIpcLockCount())
+ : 0);
+ if ((first || cur_needs_set_perm || (lock_count == 1)) && !cur_perm_eq) {
+ R_TRY(Operate(cur_address, cur_size / PageSize,
+ cur_needs_set_perm ? cur_info.GetOriginalPermission()
+ : cur_info.GetPermission(),
+ OperationType::ChangePermissions));
+ }
+ }
+
+ // Create an update allocator.
+ // NOTE: Guaranteed zero blocks needed here.
+ Result allocator_result;
+ KMemoryBlockManagerUpdateAllocator allocator(std::addressof(allocator_result),
+ m_memory_block_slab_manager, 0);
+ R_TRY(allocator_result);
+
+ // Unlock the pages.
+ m_memory_block_manager.UpdateLock(std::addressof(allocator), mapping_start,
+ mapping_size / PageSize, &KMemoryBlock::UnlockForIpc,
+ KMemoryPermission::None);
+
+ R_SUCCEED();
+}
+
+void KPageTable::CleanupForIpcClientOnServerSetupFailure([[maybe_unused]] PageLinkedList* page_list,
+ VAddr address, size_t size,
+ KMemoryPermission prot_perm) {
+ ASSERT(this->IsLockedByCurrentThread());
+ ASSERT(Common::IsAligned(address, PageSize));
+ ASSERT(Common::IsAligned(size, PageSize));
+
+ // Get the mapped extents.
+ const VAddr src_map_start = address;
+ const VAddr src_map_end = address + size;
+ const VAddr src_map_last = src_map_end - 1;
+
+ // This function is only invoked when there's something to do.
+ ASSERT(src_map_end > src_map_start);
+
+ // Iterate over blocks, fixing permissions.
+ KMemoryBlockManager::const_iterator it = m_memory_block_manager.FindIterator(address);
+ while (true) {
+ const KMemoryInfo info = it->GetMemoryInfo();
+
+ const auto cur_start =
+ info.GetAddress() >= src_map_start ? info.GetAddress() : src_map_start;
+ const auto cur_end =
+ src_map_last <= info.GetLastAddress() ? src_map_end : info.GetEndAddress();
+
+ // If we can, fix the protections on the block.
+ if ((info.GetIpcLockCount() == 0 &&
+ (info.GetPermission() & KMemoryPermission::IpcLockChangeMask) != prot_perm) ||
+ (info.GetIpcLockCount() != 0 &&
+ (info.GetOriginalPermission() & KMemoryPermission::IpcLockChangeMask) != prot_perm)) {
+ // Check if we actually need to fix the protections on the block.
+ if (cur_end == src_map_end || info.GetAddress() <= src_map_start ||
+ (info.GetPermission() & KMemoryPermission::IpcLockChangeMask) != prot_perm) {
+ ASSERT(Operate(cur_start, (cur_end - cur_start) / PageSize, info.GetPermission(),
+ OperationType::ChangePermissions)
+ .IsSuccess());
+ }
+ }
+
+ // If we're past the end of the region, we're done.
+ if (src_map_last <= info.GetLastAddress()) {
+ break;
+ }
+
+ // Advance.
+ ++it;
+ ASSERT(it != m_memory_block_manager.end());
+ }
+}
+
+void KPageTable::HACK_OpenPages(PAddr phys_addr, size_t num_pages) {
+ m_system.Kernel().MemoryManager().OpenFirst(phys_addr, num_pages);
+}
+
+void KPageTable::HACK_ClosePages(VAddr virt_addr, size_t num_pages) {
+ for (size_t index = 0; index < num_pages; ++index) {
+ const auto paddr = GetPhysicalAddr(virt_addr + (index * PageSize));
+ m_system.Kernel().MemoryManager().Close(paddr, 1);
+ }
+}
+
Result KPageTable::MapPhysicalMemory(VAddr address, size_t size) {
// Lock the physical memory lock.
- KScopedLightLock map_phys_mem_lk(m_map_physical_memory_lock);
+ KScopedLightLock phys_lk(m_map_physical_memory_lock);
// Calculate the last address for convenience.
const VAddr last_address = address + size - 1;
@@ -742,15 +1568,19 @@ Result KPageTable::MapPhysicalMemory(VAddr address, size_t size) {
{
// Reserve the memory from the process resource limit.
KScopedResourceReservation memory_reservation(
- m_system.Kernel().CurrentProcess()->GetResourceLimit(),
- LimitableResource::PhysicalMemory, size - mapped_size);
+ m_resource_limit, LimitableResource::PhysicalMemory, size - mapped_size);
R_UNLESS(memory_reservation.Succeeded(), ResultLimitReached);
// Allocate pages for the new memory.
KPageGroup pg;
- R_TRY(m_system.Kernel().MemoryManager().AllocateAndOpenForProcess(
- &pg, (size - mapped_size) / PageSize,
- KMemoryManager::EncodeOption(m_memory_pool, m_allocation_option), 0, 0));
+ R_TRY(m_system.Kernel().MemoryManager().AllocateForProcess(
+ &pg, (size - mapped_size) / PageSize, m_allocate_option, 0, 0));
+
+ // If we fail in the next bit (or retry), we need to cleanup the pages.
+ // auto pg_guard = SCOPE_GUARD {
+ // pg.OpenFirst();
+ // pg.Close();
+ //};
// Map the memory.
{
@@ -810,15 +1640,24 @@ Result KPageTable::MapPhysicalMemory(VAddr address, size_t size) {
// Create an update allocator.
ASSERT(num_allocator_blocks <= KMemoryBlockManagerUpdateAllocator::MaxBlocks);
- Result allocator_result{ResultSuccess};
+ Result allocator_result;
KMemoryBlockManagerUpdateAllocator allocator(std::addressof(allocator_result),
m_memory_block_slab_manager,
num_allocator_blocks);
R_TRY(allocator_result);
+ // We're going to perform an update, so create a helper.
+ KScopedPageTableUpdater updater(this);
+
+ // Prepare to iterate over the memory.
+ auto pg_it = pg.Nodes().begin();
+ PAddr pg_phys_addr = pg_it->GetAddress();
+ size_t pg_pages = pg_it->GetNumPages();
+
// Reset the current tracking address, and make sure we clean up on failure.
+ // pg_guard.Cancel();
cur_address = address;
- auto unmap_guard = detail::ScopeExit([&] {
+ ON_RESULT_FAILURE {
if (cur_address > address) {
const VAddr last_unmap_address = cur_address - 1;
@@ -841,6 +1680,9 @@ Result KPageTable::MapPhysicalMemory(VAddr address, size_t size) {
last_unmap_address + 1 - cur_address) /
PageSize;
+ // HACK: Manually close the pages.
+ HACK_ClosePages(cur_address, cur_pages);
+
// Unmap.
ASSERT(Operate(cur_address, cur_pages, KMemoryPermission::None,
OperationType::Unmap)
@@ -857,12 +1699,17 @@ Result KPageTable::MapPhysicalMemory(VAddr address, size_t size) {
++it;
}
}
- });
- // Iterate over the memory.
- auto pg_it = pg.Nodes().begin();
- PAddr pg_phys_addr = pg_it->GetAddress();
- size_t pg_pages = pg_it->GetNumPages();
+ // Release any remaining unmapped memory.
+ m_system.Kernel().MemoryManager().OpenFirst(pg_phys_addr, pg_pages);
+ m_system.Kernel().MemoryManager().Close(pg_phys_addr, pg_pages);
+ for (++pg_it; pg_it != pg.Nodes().end(); ++pg_it) {
+ m_system.Kernel().MemoryManager().OpenFirst(pg_it->GetAddress(),
+ pg_it->GetNumPages());
+ m_system.Kernel().MemoryManager().Close(pg_it->GetAddress(),
+ pg_it->GetNumPages());
+ }
+ };
auto it = m_memory_block_manager.FindIterator(cur_address);
while (true) {
@@ -897,6 +1744,9 @@ Result KPageTable::MapPhysicalMemory(VAddr address, size_t size) {
R_TRY(Operate(cur_address, cur_pages, KMemoryPermission::UserReadWrite,
OperationType::Map, pg_phys_addr));
+ // HACK: Manually open the pages.
+ HACK_OpenPages(pg_phys_addr, cur_pages);
+
// Advance.
cur_address += cur_pages * PageSize;
map_pages -= cur_pages;
@@ -928,9 +1778,6 @@ Result KPageTable::MapPhysicalMemory(VAddr address, size_t size) {
KMemoryPermission::None, KMemoryAttribute::None, KMemoryState::Normal,
KMemoryPermission::UserReadWrite, KMemoryAttribute::None);
- // Cancel our guard.
- unmap_guard.Cancel();
-
R_SUCCEED();
}
}
@@ -939,7 +1786,7 @@ Result KPageTable::MapPhysicalMemory(VAddr address, size_t size) {
Result KPageTable::UnmapPhysicalMemory(VAddr address, size_t size) {
// Lock the physical memory lock.
- KScopedLightLock map_phys_mem_lk(m_map_physical_memory_lock);
+ KScopedLightLock phys_lk(m_map_physical_memory_lock);
// Lock the table.
KScopedLightLock lk(m_general_lock);
@@ -948,8 +1795,11 @@ Result KPageTable::UnmapPhysicalMemory(VAddr address, size_t size) {
const VAddr last_address = address + size - 1;
// Define iteration variables.
- VAddr cur_address = 0;
- size_t mapped_size = 0;
+ VAddr map_start_address = 0;
+ VAddr map_last_address = 0;
+
+ VAddr cur_address;
+ size_t mapped_size;
size_t num_allocator_blocks = 0;
// Check if the memory is mapped.
@@ -975,27 +1825,27 @@ Result KPageTable::UnmapPhysicalMemory(VAddr address, size_t size) {
if (is_normal) {
R_UNLESS(info.GetAttribute() == KMemoryAttribute::None, ResultInvalidCurrentMemory);
+ if (map_start_address == 0) {
+ map_start_address = cur_address;
+ }
+ map_last_address =
+ (last_address >= info.GetLastAddress()) ? info.GetLastAddress() : last_address;
+
if (info.GetAddress() < address) {
++num_allocator_blocks;
}
if (last_address < info.GetLastAddress()) {
++num_allocator_blocks;
}
+
+ mapped_size += (map_last_address + 1 - cur_address);
}
// Check if we're done.
if (last_address <= info.GetLastAddress()) {
- if (is_normal) {
- mapped_size += (last_address + 1 - cur_address);
- }
break;
}
- // Track the memory if it's mapped.
- if (is_normal) {
- mapped_size += VAddr(info.GetEndAddress()) - cur_address;
- }
-
// Advance.
cur_address = info.GetEndAddress();
++it;
@@ -1005,125 +1855,22 @@ Result KPageTable::UnmapPhysicalMemory(VAddr address, size_t size) {
R_SUCCEED_IF(mapped_size == 0);
}
- // Make a page group for the unmap region.
- KPageGroup pg;
- {
- auto& impl = this->PageTableImpl();
-
- // Begin traversal.
- Common::PageTable::TraversalContext context;
- Common::PageTable::TraversalEntry cur_entry = {.phys_addr = 0, .block_size = 0};
- bool cur_valid = false;
- Common::PageTable::TraversalEntry next_entry;
- bool next_valid = false;
- size_t tot_size = 0;
-
- cur_address = address;
- next_valid = impl.BeginTraversal(next_entry, context, cur_address);
- next_entry.block_size =
- (next_entry.block_size - (next_entry.phys_addr & (next_entry.block_size - 1)));
-
- // Iterate, building the group.
- while (true) {
- if ((!next_valid && !cur_valid) ||
- (next_valid && cur_valid &&
- next_entry.phys_addr == cur_entry.phys_addr + cur_entry.block_size)) {
- cur_entry.block_size += next_entry.block_size;
- } else {
- if (cur_valid) {
- // ASSERT(IsHeapPhysicalAddress(cur_entry.phys_addr));
- R_TRY(pg.AddBlock(cur_entry.phys_addr, cur_entry.block_size / PageSize));
- }
-
- // Update tracking variables.
- tot_size += cur_entry.block_size;
- cur_entry = next_entry;
- cur_valid = next_valid;
- }
-
- if (cur_entry.block_size + tot_size >= size) {
- break;
- }
-
- next_valid = impl.ContinueTraversal(next_entry, context);
- }
-
- // Add the last block.
- if (cur_valid) {
- // ASSERT(IsHeapPhysicalAddress(cur_entry.phys_addr));
- R_TRY(pg.AddBlock(cur_entry.phys_addr, (size - tot_size) / PageSize));
- }
- }
- ASSERT(pg.GetNumPages() == mapped_size / PageSize);
-
// Create an update allocator.
ASSERT(num_allocator_blocks <= KMemoryBlockManagerUpdateAllocator::MaxBlocks);
- Result allocator_result{ResultSuccess};
+ Result allocator_result;
KMemoryBlockManagerUpdateAllocator allocator(std::addressof(allocator_result),
m_memory_block_slab_manager, num_allocator_blocks);
R_TRY(allocator_result);
- // Reset the current tracking address, and make sure we clean up on failure.
- cur_address = address;
- auto remap_guard = detail::ScopeExit([&] {
- if (cur_address > address) {
- const VAddr last_map_address = cur_address - 1;
- cur_address = address;
-
- // Iterate over the memory we unmapped.
- auto it = m_memory_block_manager.FindIterator(cur_address);
- auto pg_it = pg.Nodes().begin();
- PAddr pg_phys_addr = pg_it->GetAddress();
- size_t pg_pages = pg_it->GetNumPages();
-
- while (true) {
- // Get the memory info for the pages we unmapped, convert to property.
- const KMemoryInfo info = it->GetMemoryInfo();
-
- // If the memory is normal, we unmapped it and need to re-map it.
- if (info.GetState() == KMemoryState::Normal) {
- // Determine the range to map.
- size_t map_pages = std::min(VAddr(info.GetEndAddress()) - cur_address,
- last_map_address + 1 - cur_address) /
- PageSize;
-
- // While we have pages to map, map them.
- while (map_pages > 0) {
- // Check if we're at the end of the physical block.
- if (pg_pages == 0) {
- // Ensure there are more pages to map.
- ASSERT(pg_it != pg.Nodes().end());
-
- // Advance our physical block.
- ++pg_it;
- pg_phys_addr = pg_it->GetAddress();
- pg_pages = pg_it->GetNumPages();
- }
-
- // Map whatever we can.
- const size_t cur_pages = std::min(pg_pages, map_pages);
- ASSERT(this->Operate(cur_address, cur_pages, info.GetPermission(),
- OperationType::Map, pg_phys_addr) == ResultSuccess);
+ // We're going to perform an update, so create a helper.
+ KScopedPageTableUpdater updater(this);
- // Advance.
- cur_address += cur_pages * PageSize;
- map_pages -= cur_pages;
+ // Separate the mapping.
+ R_TRY(Operate(map_start_address, (map_last_address + 1 - map_start_address) / PageSize,
+ KMemoryPermission::None, OperationType::Separate));
- pg_phys_addr += cur_pages * PageSize;
- pg_pages -= cur_pages;
- }
- }
-
- // Check if we're done.
- if (last_map_address <= info.GetLastAddress()) {
- break;
- }
-
- // Advance.
- ++it;
- }
- }
- });
+ // Reset the current tracking address, and make sure we clean up on failure.
+ cur_address = address;
// Iterate over the memory, unmapping as we go.
auto it = m_memory_block_manager.FindIterator(cur_address);
@@ -1141,8 +1888,12 @@ Result KPageTable::UnmapPhysicalMemory(VAddr address, size_t size) {
last_address + 1 - cur_address) /
PageSize;
+ // HACK: Manually close the pages.
+ HACK_ClosePages(cur_address, cur_pages);
+
// Unmap.
- R_TRY(Operate(cur_address, cur_pages, KMemoryPermission::None, OperationType::Unmap));
+ ASSERT(Operate(cur_address, cur_pages, KMemoryPermission::None, OperationType::Unmap)
+ .IsSuccess());
}
// Check if we're done.
@@ -1157,8 +1908,7 @@ Result KPageTable::UnmapPhysicalMemory(VAddr address, size_t size) {
// Release the memory resource.
m_mapped_physical_memory_size -= mapped_size;
- auto process{m_system.Kernel().CurrentProcess()};
- process->GetResourceLimit()->Release(LimitableResource::PhysicalMemory, mapped_size);
+ m_resource_limit->Release(LimitableResource::PhysicalMemory, mapped_size);
// Update memory blocks.
m_memory_block_manager.Update(std::addressof(allocator), address, size / PageSize,
@@ -1166,14 +1916,7 @@ Result KPageTable::UnmapPhysicalMemory(VAddr address, size_t size) {
KMemoryAttribute::None, KMemoryBlockDisableMergeAttribute::None,
KMemoryBlockDisableMergeAttribute::None);
- // TODO(bunnei): This is a workaround until the next set of changes, where we add reference
- // counting for mapped pages. Until then, we must manually close the reference to the page
- // group.
- m_system.Kernel().MemoryManager().Close(pg);
-
// We succeeded.
- remap_guard.Cancel();
-
R_SUCCEED();
}
@@ -1749,8 +2492,7 @@ Result KPageTable::SetHeapSize(VAddr* out, size_t size) {
OperationType::Unmap));
// Release the memory from the resource limit.
- m_system.Kernel().CurrentProcess()->GetResourceLimit()->Release(
- LimitableResource::PhysicalMemory, num_pages * PageSize);
+ m_resource_limit->Release(LimitableResource::PhysicalMemory, num_pages * PageSize);
// Apply the memory block update.
m_memory_block_manager.Update(std::addressof(allocator), m_heap_region_start + size,
@@ -1780,8 +2522,7 @@ Result KPageTable::SetHeapSize(VAddr* out, size_t size) {
// Reserve memory for the heap extension.
KScopedResourceReservation memory_reservation(
- m_system.Kernel().CurrentProcess()->GetResourceLimit(), LimitableResource::PhysicalMemory,
- allocation_size);
+ m_resource_limit, LimitableResource::PhysicalMemory, allocation_size);
R_UNLESS(memory_reservation.Succeeded(), ResultLimitReached);
// Allocate pages for the heap extension.
@@ -1869,7 +2610,7 @@ ResultVal<VAddr> KPageTable::AllocateAndMapMemory(size_t needed_num_pages, size_
R_TRY(Operate(addr, needed_num_pages, perm, OperationType::Map, map_addr));
} else {
KPageGroup page_group;
- R_TRY(m_system.Kernel().MemoryManager().AllocateAndOpenForProcess(
+ R_TRY(m_system.Kernel().MemoryManager().AllocateForProcess(
&page_group, needed_num_pages,
KMemoryManager::EncodeOption(m_memory_pool, m_allocation_option), 0, 0));
R_TRY(Operate(addr, needed_num_pages, page_group, OperationType::MapGroup));
@@ -1883,8 +2624,9 @@ ResultVal<VAddr> KPageTable::AllocateAndMapMemory(size_t needed_num_pages, size_
return addr;
}
-Result KPageTable::LockForMapDeviceAddressSpace(VAddr address, size_t size, KMemoryPermission perm,
- bool is_aligned) {
+Result KPageTable::LockForMapDeviceAddressSpace(bool* out_is_io, VAddr address, size_t size,
+ KMemoryPermission perm, bool is_aligned,
+ bool check_heap) {
// Lightly validate the range before doing anything else.
const size_t num_pages = size / PageSize;
R_UNLESS(this->Contains(address, size), ResultInvalidCurrentMemory);
@@ -1894,15 +2636,18 @@ Result KPageTable::LockForMapDeviceAddressSpace(VAddr address, size_t size, KMem
// Check the memory state.
const auto test_state =
- (is_aligned ? KMemoryState::FlagCanAlignedDeviceMap : KMemoryState::FlagCanDeviceMap);
+ (is_aligned ? KMemoryState::FlagCanAlignedDeviceMap : KMemoryState::FlagCanDeviceMap) |
+ (check_heap ? KMemoryState::FlagReferenceCounted : KMemoryState::None);
size_t num_allocator_blocks;
- R_TRY(this->CheckMemoryState(std::addressof(num_allocator_blocks), address, size, test_state,
+ KMemoryState old_state;
+ R_TRY(this->CheckMemoryState(std::addressof(old_state), nullptr, nullptr,
+ std::addressof(num_allocator_blocks), address, size, test_state,
test_state, perm, perm,
KMemoryAttribute::IpcLocked | KMemoryAttribute::Locked,
KMemoryAttribute::None, KMemoryAttribute::DeviceShared));
// Create an update allocator.
- Result allocator_result{ResultSuccess};
+ Result allocator_result;
KMemoryBlockManagerUpdateAllocator allocator(std::addressof(allocator_result),
m_memory_block_slab_manager, num_allocator_blocks);
R_TRY(allocator_result);
@@ -1911,10 +2656,13 @@ Result KPageTable::LockForMapDeviceAddressSpace(VAddr address, size_t size, KMem
m_memory_block_manager.UpdateLock(std::addressof(allocator), address, num_pages,
&KMemoryBlock::ShareToDevice, KMemoryPermission::None);
+ // Set whether the locked memory was io.
+ *out_is_io = old_state == KMemoryState::Io;
+
R_SUCCEED();
}
-Result KPageTable::LockForUnmapDeviceAddressSpace(VAddr address, size_t size) {
+Result KPageTable::LockForUnmapDeviceAddressSpace(VAddr address, size_t size, bool check_heap) {
// Lightly validate the range before doing anything else.
const size_t num_pages = size / PageSize;
R_UNLESS(this->Contains(address, size), ResultInvalidCurrentMemory);
@@ -1923,16 +2671,16 @@ Result KPageTable::LockForUnmapDeviceAddressSpace(VAddr address, size_t size) {
KScopedLightLock lk(m_general_lock);
// Check the memory state.
+ const auto test_state = KMemoryState::FlagCanDeviceMap |
+ (check_heap ? KMemoryState::FlagReferenceCounted : KMemoryState::None);
size_t num_allocator_blocks;
R_TRY(this->CheckMemoryStateContiguous(
- std::addressof(num_allocator_blocks), address, size,
- KMemoryState::FlagReferenceCounted | KMemoryState::FlagCanDeviceMap,
- KMemoryState::FlagReferenceCounted | KMemoryState::FlagCanDeviceMap,
+ std::addressof(num_allocator_blocks), address, size, test_state, test_state,
KMemoryPermission::None, KMemoryPermission::None,
KMemoryAttribute::DeviceShared | KMemoryAttribute::Locked, KMemoryAttribute::DeviceShared));
// Create an update allocator.
- Result allocator_result{ResultSuccess};
+ Result allocator_result;
KMemoryBlockManagerUpdateAllocator allocator(std::addressof(allocator_result),
m_memory_block_slab_manager, num_allocator_blocks);
R_TRY(allocator_result);
@@ -1976,13 +2724,28 @@ Result KPageTable::UnlockForDeviceAddressSpace(VAddr address, size_t size) {
R_SUCCEED();
}
+Result KPageTable::LockForIpcUserBuffer(PAddr* out, VAddr address, size_t size) {
+ R_RETURN(this->LockMemoryAndOpen(
+ nullptr, out, address, size, KMemoryState::FlagCanIpcUserBuffer,
+ KMemoryState::FlagCanIpcUserBuffer, KMemoryPermission::All,
+ KMemoryPermission::UserReadWrite, KMemoryAttribute::All, KMemoryAttribute::None,
+ KMemoryPermission::NotMapped | KMemoryPermission::KernelReadWrite,
+ KMemoryAttribute::Locked));
+}
+
+Result KPageTable::UnlockForIpcUserBuffer(VAddr address, size_t size) {
+ R_RETURN(this->UnlockMemory(address, size, KMemoryState::FlagCanIpcUserBuffer,
+ KMemoryState::FlagCanIpcUserBuffer, KMemoryPermission::None,
+ KMemoryPermission::None, KMemoryAttribute::All,
+ KMemoryAttribute::Locked, KMemoryPermission::UserReadWrite,
+ KMemoryAttribute::Locked, nullptr));
+}
+
Result KPageTable::LockForCodeMemory(KPageGroup* out, VAddr addr, size_t size) {
R_RETURN(this->LockMemoryAndOpen(
out, nullptr, addr, size, KMemoryState::FlagCanCodeMemory, KMemoryState::FlagCanCodeMemory,
KMemoryPermission::All, KMemoryPermission::UserReadWrite, KMemoryAttribute::All,
- KMemoryAttribute::None,
- static_cast<KMemoryPermission>(KMemoryPermission::NotMapped |
- KMemoryPermission::KernelReadWrite),
+ KMemoryAttribute::None, KMemoryPermission::NotMapped | KMemoryPermission::KernelReadWrite,
KMemoryAttribute::Locked));
}
@@ -2066,6 +2829,10 @@ Result KPageTable::Operate(VAddr addr, size_t num_pages, KMemoryPermission perm,
m_system.Memory().MapMemoryRegion(*m_page_table_impl, addr, num_pages * PageSize, map_addr);
break;
}
+ case OperationType::Separate: {
+ // HACK: Unimplemented.
+ break;
+ }
case OperationType::ChangePermissions:
case OperationType::ChangePermissionsAndRefresh:
break;
@@ -2075,6 +2842,17 @@ Result KPageTable::Operate(VAddr addr, size_t num_pages, KMemoryPermission perm,
R_SUCCEED();
}
+void KPageTable::FinalizeUpdate(PageLinkedList* page_list) {
+ while (page_list->Peek()) {
+ [[maybe_unused]] auto page = page_list->Pop();
+
+ // TODO(bunnei): Free pages once they are allocated in guest memory
+ // ASSERT(this->GetPageTableManager().IsInPageTableHeap(page));
+ // ASSERT(this->GetPageTableManager().GetRefCount(page) == 0);
+ // this->GetPageTableManager().Free(page);
+ }
+}
+
VAddr KPageTable::GetRegionAddress(KMemoryState state) const {
switch (state) {
case KMemoryState::Free:
@@ -2101,6 +2879,7 @@ VAddr KPageTable::GetRegionAddress(KMemoryState state) const {
case KMemoryState::GeneratedCode:
case KMemoryState::CodeOut:
case KMemoryState::Coverage:
+ case KMemoryState::Insecure:
return m_alias_code_region_start;
case KMemoryState::Code:
case KMemoryState::CodeData:
@@ -2136,6 +2915,7 @@ size_t KPageTable::GetRegionSize(KMemoryState state) const {
case KMemoryState::GeneratedCode:
case KMemoryState::CodeOut:
case KMemoryState::Coverage:
+ case KMemoryState::Insecure:
return m_alias_code_region_end - m_alias_code_region_start;
case KMemoryState::Code:
case KMemoryState::CodeData:
@@ -2177,6 +2957,7 @@ bool KPageTable::CanContain(VAddr addr, size_t size, KMemoryState state) const {
case KMemoryState::GeneratedCode:
case KMemoryState::CodeOut:
case KMemoryState::Coverage:
+ case KMemoryState::Insecure:
return is_in_region && !is_in_heap && !is_in_alias;
case KMemoryState::Normal:
ASSERT(is_in_heap);
diff --git a/src/core/hle/kernel/k_page_table.h b/src/core/hle/kernel/k_page_table.h
index c6aeacd96..950850291 100644
--- a/src/core/hle/kernel/k_page_table.h
+++ b/src/core/hle/kernel/k_page_table.h
@@ -16,6 +16,7 @@
#include "core/hle/kernel/k_memory_layout.h"
#include "core/hle/kernel/k_memory_manager.h"
#include "core/hle/result.h"
+#include "core/memory.h"
namespace Core {
class System;
@@ -23,7 +24,10 @@ class System;
namespace Kernel {
+class KBlockInfoManager;
class KMemoryBlockManager;
+class KResourceLimit;
+class KSystemResource;
class KPageTable final {
public:
@@ -36,9 +40,9 @@ public:
~KPageTable();
Result InitializeForProcess(FileSys::ProgramAddressSpaceType as_type, bool enable_aslr,
- VAddr code_addr, size_t code_size,
- KMemoryBlockSlabManager* mem_block_slab_manager,
- KMemoryManager::Pool pool);
+ bool enable_das_merge, bool from_back, KMemoryManager::Pool pool,
+ VAddr code_addr, size_t code_size, KSystemResource* system_resource,
+ KResourceLimit* resource_limit);
void Finalize();
@@ -74,12 +78,20 @@ public:
KMemoryState state, KMemoryPermission perm,
PAddr map_addr = 0);
- Result LockForMapDeviceAddressSpace(VAddr address, size_t size, KMemoryPermission perm,
- bool is_aligned);
- Result LockForUnmapDeviceAddressSpace(VAddr address, size_t size);
+ Result LockForMapDeviceAddressSpace(bool* out_is_io, VAddr address, size_t size,
+ KMemoryPermission perm, bool is_aligned, bool check_heap);
+ Result LockForUnmapDeviceAddressSpace(VAddr address, size_t size, bool check_heap);
Result UnlockForDeviceAddressSpace(VAddr addr, size_t size);
+ Result LockForIpcUserBuffer(PAddr* out, VAddr address, size_t size);
+ Result UnlockForIpcUserBuffer(VAddr address, size_t size);
+
+ Result SetupForIpc(VAddr* out_dst_addr, size_t size, VAddr src_addr, KPageTable& src_page_table,
+ KMemoryPermission test_perm, KMemoryState dst_state, bool send);
+ Result CleanupForIpcServer(VAddr address, size_t size, KMemoryState dst_state);
+ Result CleanupForIpcClient(VAddr address, size_t size, KMemoryState dst_state);
+
Result LockForCodeMemory(KPageGroup* out, VAddr addr, size_t size);
Result UnlockForCodeMemory(VAddr addr, size_t size, const KPageGroup& pg);
Result MakeAndOpenPageGroup(KPageGroup* out, VAddr address, size_t num_pages,
@@ -97,13 +109,54 @@ public:
bool CanContain(VAddr addr, size_t size, KMemoryState state) const;
+protected:
+ struct PageLinkedList {
+ private:
+ struct Node {
+ Node* m_next;
+ std::array<u8, PageSize - sizeof(Node*)> m_buffer;
+ };
+
+ public:
+ constexpr PageLinkedList() = default;
+
+ void Push(Node* n) {
+ ASSERT(Common::IsAligned(reinterpret_cast<uintptr_t>(n), PageSize));
+ n->m_next = m_root;
+ m_root = n;
+ }
+
+ void Push(Core::Memory::Memory& memory, VAddr addr) {
+ this->Push(memory.GetPointer<Node>(addr));
+ }
+
+ Node* Peek() const {
+ return m_root;
+ }
+
+ Node* Pop() {
+ Node* const r = m_root;
+
+ m_root = r->m_next;
+ r->m_next = nullptr;
+
+ return r;
+ }
+
+ private:
+ Node* m_root{};
+ };
+ static_assert(std::is_trivially_destructible<PageLinkedList>::value);
+
private:
enum class OperationType : u32 {
- Map,
- MapGroup,
- Unmap,
- ChangePermissions,
- ChangePermissionsAndRefresh,
+ Map = 0,
+ MapFirst = 1,
+ MapGroup = 2,
+ Unmap = 3,
+ ChangePermissions = 4,
+ ChangePermissionsAndRefresh = 5,
+ Separate = 6,
};
static constexpr KMemoryAttribute DefaultMemoryIgnoreAttr =
@@ -123,6 +176,7 @@ private:
OperationType operation);
Result Operate(VAddr addr, size_t num_pages, KMemoryPermission perm, OperationType operation,
PAddr map_addr = 0);
+ void FinalizeUpdate(PageLinkedList* page_list);
VAddr GetRegionAddress(KMemoryState state) const;
size_t GetRegionSize(KMemoryState state) const;
@@ -199,6 +253,18 @@ private:
return *out != 0;
}
+ Result SetupForIpcClient(PageLinkedList* page_list, size_t* out_blocks_needed, VAddr address,
+ size_t size, KMemoryPermission test_perm, KMemoryState dst_state);
+ Result SetupForIpcServer(VAddr* out_addr, size_t size, VAddr src_addr,
+ KMemoryPermission test_perm, KMemoryState dst_state,
+ KPageTable& src_page_table, bool send);
+ void CleanupForIpcClientOnServerSetupFailure(PageLinkedList* page_list, VAddr address,
+ size_t size, KMemoryPermission prot_perm);
+
+ // HACK: These will be removed once we automatically manage page reference counts.
+ void HACK_OpenPages(PAddr phys_addr, size_t num_pages);
+ void HACK_ClosePages(VAddr virt_addr, size_t num_pages);
+
mutable KLightLock m_general_lock;
mutable KLightLock m_map_physical_memory_lock;
@@ -316,6 +382,31 @@ public:
addr + size - 1 <= m_address_space_end - 1;
}
+public:
+ static VAddr GetLinearMappedVirtualAddress(const KMemoryLayout& layout, PAddr addr) {
+ return layout.GetLinearVirtualAddress(addr);
+ }
+
+ static PAddr GetLinearMappedPhysicalAddress(const KMemoryLayout& layout, VAddr addr) {
+ return layout.GetLinearPhysicalAddress(addr);
+ }
+
+ static VAddr GetHeapVirtualAddress(const KMemoryLayout& layout, PAddr addr) {
+ return GetLinearMappedVirtualAddress(layout, addr);
+ }
+
+ static PAddr GetHeapPhysicalAddress(const KMemoryLayout& layout, VAddr addr) {
+ return GetLinearMappedPhysicalAddress(layout, addr);
+ }
+
+ static VAddr GetPageTableVirtualAddress(const KMemoryLayout& layout, PAddr addr) {
+ return GetLinearMappedVirtualAddress(layout, addr);
+ }
+
+ static PAddr GetPageTablePhysicalAddress(const KMemoryLayout& layout, VAddr addr) {
+ return GetLinearMappedPhysicalAddress(layout, addr);
+ }
+
private:
constexpr bool IsKernel() const {
return m_is_kernel;
@@ -331,6 +422,24 @@ private:
}
private:
+ class KScopedPageTableUpdater {
+ private:
+ KPageTable* m_pt{};
+ PageLinkedList m_ll;
+
+ public:
+ explicit KScopedPageTableUpdater(KPageTable* pt) : m_pt(pt) {}
+ explicit KScopedPageTableUpdater(KPageTable& pt) : KScopedPageTableUpdater(&pt) {}
+ ~KScopedPageTableUpdater() {
+ m_pt->FinalizeUpdate(this->GetPageList());
+ }
+
+ PageLinkedList* GetPageList() {
+ return &m_ll;
+ }
+ };
+
+private:
VAddr m_address_space_start{};
VAddr m_address_space_end{};
VAddr m_heap_region_start{};
@@ -347,20 +456,27 @@ private:
VAddr m_alias_code_region_start{};
VAddr m_alias_code_region_end{};
- size_t m_mapped_physical_memory_size{};
size_t m_max_heap_size{};
- size_t m_max_physical_memory_size{};
+ size_t m_mapped_physical_memory_size{};
+ size_t m_mapped_unsafe_physical_memory{};
+ size_t m_mapped_insecure_memory{};
+ size_t m_mapped_ipc_server_memory{};
size_t m_address_space_width{};
KMemoryBlockManager m_memory_block_manager;
+ u32 m_allocate_option{};
bool m_is_kernel{};
bool m_enable_aslr{};
bool m_enable_device_address_space_merge{};
KMemoryBlockSlabManager* m_memory_block_slab_manager{};
+ KBlockInfoManager* m_block_info_manager{};
+ KResourceLimit* m_resource_limit{};
u32 m_heap_fill_value{};
+ u32 m_ipc_fill_value{};
+ u32 m_stack_fill_value{};
const KMemoryRegion* m_cached_physical_heap_region{};
KMemoryManager::Pool m_memory_pool{KMemoryManager::Pool::Application};
diff --git a/src/core/hle/kernel/k_page_table_manager.h b/src/core/hle/kernel/k_page_table_manager.h
new file mode 100644
index 000000000..91a45cde3
--- /dev/null
+++ b/src/core/hle/kernel/k_page_table_manager.h
@@ -0,0 +1,55 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <atomic>
+
+#include "common/common_types.h"
+#include "core/hle/kernel/k_dynamic_resource_manager.h"
+#include "core/hle/kernel/k_page_table_slab_heap.h"
+
+namespace Kernel {
+
+class KPageTableManager : public KDynamicResourceManager<impl::PageTablePage, true> {
+public:
+ using RefCount = KPageTableSlabHeap::RefCount;
+ static constexpr size_t PageTableSize = KPageTableSlabHeap::PageTableSize;
+
+public:
+ KPageTableManager() = default;
+
+ void Initialize(KDynamicPageManager* page_allocator, KPageTableSlabHeap* pt_heap) {
+ m_pt_heap = pt_heap;
+
+ static_assert(std::derived_from<KPageTableSlabHeap, DynamicSlabType>);
+ BaseHeap::Initialize(page_allocator, pt_heap);
+ }
+
+ VAddr Allocate() {
+ return VAddr(BaseHeap::Allocate());
+ }
+
+ RefCount GetRefCount(VAddr addr) const {
+ return m_pt_heap->GetRefCount(addr);
+ }
+
+ void Open(VAddr addr, int count) {
+ return m_pt_heap->Open(addr, count);
+ }
+
+ bool Close(VAddr addr, int count) {
+ return m_pt_heap->Close(addr, count);
+ }
+
+ bool IsInPageTableHeap(VAddr addr) const {
+ return m_pt_heap->IsInRange(addr);
+ }
+
+private:
+ using BaseHeap = KDynamicResourceManager<impl::PageTablePage, true>;
+
+ KPageTableSlabHeap* m_pt_heap{};
+};
+
+} // namespace Kernel
diff --git a/src/core/hle/kernel/k_page_table_slab_heap.h b/src/core/hle/kernel/k_page_table_slab_heap.h
new file mode 100644
index 000000000..a9543cbd0
--- /dev/null
+++ b/src/core/hle/kernel/k_page_table_slab_heap.h
@@ -0,0 +1,93 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <array>
+#include <vector>
+
+#include "common/common_types.h"
+#include "core/hle/kernel/k_dynamic_slab_heap.h"
+#include "core/hle/kernel/slab_helpers.h"
+
+namespace Kernel {
+
+namespace impl {
+
+class PageTablePage {
+public:
+ // Do not initialize anything.
+ PageTablePage() = default;
+
+private:
+ std::array<u8, PageSize> m_buffer{};
+};
+static_assert(sizeof(PageTablePage) == PageSize);
+
+} // namespace impl
+
+class KPageTableSlabHeap : public KDynamicSlabHeap<impl::PageTablePage, true> {
+public:
+ using RefCount = u16;
+ static constexpr size_t PageTableSize = sizeof(impl::PageTablePage);
+ static_assert(PageTableSize == PageSize);
+
+public:
+ KPageTableSlabHeap() = default;
+
+ static constexpr size_t CalculateReferenceCountSize(size_t size) {
+ return (size / PageSize) * sizeof(RefCount);
+ }
+
+ void Initialize(KDynamicPageManager* page_allocator, size_t object_count, RefCount* rc) {
+ BaseHeap::Initialize(page_allocator, object_count);
+ this->Initialize(rc);
+ }
+
+ RefCount GetRefCount(VAddr addr) {
+ ASSERT(this->IsInRange(addr));
+ return *this->GetRefCountPointer(addr);
+ }
+
+ void Open(VAddr addr, int count) {
+ ASSERT(this->IsInRange(addr));
+
+ *this->GetRefCountPointer(addr) += static_cast<RefCount>(count);
+
+ ASSERT(this->GetRefCount(addr) > 0);
+ }
+
+ bool Close(VAddr addr, int count) {
+ ASSERT(this->IsInRange(addr));
+ ASSERT(this->GetRefCount(addr) >= count);
+
+ *this->GetRefCountPointer(addr) -= static_cast<RefCount>(count);
+ return this->GetRefCount(addr) == 0;
+ }
+
+ bool IsInPageTableHeap(VAddr addr) const {
+ return this->IsInRange(addr);
+ }
+
+private:
+ void Initialize([[maybe_unused]] RefCount* rc) {
+ // TODO(bunnei): Use rc once we support kernel virtual memory allocations.
+ const auto count = this->GetSize() / PageSize;
+ m_ref_counts.resize(count);
+
+ for (size_t i = 0; i < count; i++) {
+ m_ref_counts[i] = 0;
+ }
+ }
+
+ RefCount* GetRefCountPointer(VAddr addr) {
+ return m_ref_counts.data() + ((addr - this->GetAddress()) / PageSize);
+ }
+
+private:
+ using BaseHeap = KDynamicSlabHeap<impl::PageTablePage, true>;
+
+ std::vector<RefCount> m_ref_counts;
+};
+
+} // namespace Kernel
diff --git a/src/core/hle/kernel/k_port.cpp b/src/core/hle/kernel/k_port.cpp
index 7a5a9dc2a..77d00ae2c 100644
--- a/src/core/hle/kernel/k_port.cpp
+++ b/src/core/hle/kernel/k_port.cpp
@@ -57,12 +57,6 @@ Result KPort::EnqueueSession(KServerSession* session) {
server.EnqueueSession(session);
- if (auto session_ptr = server.GetSessionRequestHandler().lock()) {
- session_ptr->ClientConnected(server.AcceptSession());
- } else {
- ASSERT(false);
- }
-
return ResultSuccess;
}
diff --git a/src/core/hle/kernel/k_process.cpp b/src/core/hle/kernel/k_process.cpp
index 8c3495e5a..4ddeea73b 100644
--- a/src/core/hle/kernel/k_process.cpp
+++ b/src/core/hle/kernel/k_process.cpp
@@ -358,8 +358,8 @@ Result KProcess::LoadFromMetadata(const FileSys::ProgramMetadata& metadata, std:
}
// Initialize proces address space
if (const Result result{page_table.InitializeForProcess(
- metadata.GetAddressSpaceType(), false, 0x8000000, code_size,
- &kernel.GetApplicationMemoryBlockManager(), KMemoryManager::Pool::Application)};
+ metadata.GetAddressSpaceType(), false, false, false, KMemoryManager::Pool::Application,
+ 0x8000000, code_size, &kernel.GetSystemSystemResource(), resource_limit)};
result.IsError()) {
R_RETURN(result);
}
diff --git a/src/core/hle/kernel/k_server_port.cpp b/src/core/hle/kernel/k_server_port.cpp
index e968f26ad..16968ba97 100644
--- a/src/core/hle/kernel/k_server_port.cpp
+++ b/src/core/hle/kernel/k_server_port.cpp
@@ -61,12 +61,6 @@ void KServerPort::Destroy() {
// Close our reference to our parent.
parent->Close();
-
- // Release host emulation members.
- session_handler.reset();
-
- // Ensure that the global list tracking server objects does not hold on to a reference.
- kernel.UnregisterServerObject(this);
}
bool KServerPort::IsSignaled() const {
diff --git a/src/core/hle/kernel/k_server_port.h b/src/core/hle/kernel/k_server_port.h
index fd4f4bd20..5fc7ee683 100644
--- a/src/core/hle/kernel/k_server_port.h
+++ b/src/core/hle/kernel/k_server_port.h
@@ -27,24 +27,6 @@ public:
void Initialize(KPort* parent_port_, std::string&& name_);
- /// Whether or not this server port has an HLE handler available.
- bool HasSessionRequestHandler() const {
- return !session_handler.expired();
- }
-
- /// Gets the HLE handler for this port.
- SessionRequestHandlerWeakPtr GetSessionRequestHandler() const {
- return session_handler;
- }
-
- /**
- * Sets the HLE handler template for the port. ServerSessions crated by connecting to this port
- * will inherit a reference to this handler.
- */
- void SetSessionHandler(SessionRequestHandlerWeakPtr&& handler) {
- session_handler = std::move(handler);
- }
-
void EnqueueSession(KServerSession* pending_session);
KServerSession* AcceptSession();
@@ -65,7 +47,6 @@ private:
void CleanupSessions();
SessionList session_list;
- SessionRequestHandlerWeakPtr session_handler;
KPort* parent{};
};
diff --git a/src/core/hle/kernel/k_server_session.cpp b/src/core/hle/kernel/k_server_session.cpp
index faf03fcc8..aa1941f01 100644
--- a/src/core/hle/kernel/k_server_session.cpp
+++ b/src/core/hle/kernel/k_server_session.cpp
@@ -1,4 +1,4 @@
-// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <tuple>
@@ -33,12 +33,10 @@ KServerSession::KServerSession(KernelCore& kernel_)
KServerSession::~KServerSession() = default;
-void KServerSession::Initialize(KSession* parent_session_, std::string&& name_,
- std::shared_ptr<SessionRequestManager> manager_) {
+void KServerSession::Initialize(KSession* parent_session_, std::string&& name_) {
// Set member variables.
parent = parent_session_;
name = std::move(name_);
- manager = manager_;
}
void KServerSession::Destroy() {
@@ -47,18 +45,99 @@ void KServerSession::Destroy() {
this->CleanupRequests();
parent->Close();
-
- // Release host emulation members.
- manager.reset();
-
- // Ensure that the global list tracking server objects does not hold on to a reference.
- kernel.UnregisterServerObject(this);
}
void KServerSession::OnClientClosed() {
- if (manager && manager->HasSessionHandler()) {
- manager->SessionHandler().ClientDisconnected(this);
+ KScopedLightLock lk{m_lock};
+
+ // Handle any pending requests.
+ KSessionRequest* prev_request = nullptr;
+ while (true) {
+ // Declare variables for processing the request.
+ KSessionRequest* request = nullptr;
+ KEvent* event = nullptr;
+ KThread* thread = nullptr;
+ bool cur_request = false;
+ bool terminate = false;
+
+ // Get the next request.
+ {
+ KScopedSchedulerLock sl{kernel};
+
+ if (m_current_request != nullptr && m_current_request != prev_request) {
+ // Set the request, open a reference as we process it.
+ request = m_current_request;
+ request->Open();
+ cur_request = true;
+
+ // Get thread and event for the request.
+ thread = request->GetThread();
+ event = request->GetEvent();
+
+ // If the thread is terminating, handle that.
+ if (thread->IsTerminationRequested()) {
+ request->ClearThread();
+ request->ClearEvent();
+ terminate = true;
+ }
+
+ prev_request = request;
+ } else if (!m_request_list.empty()) {
+ // Pop the request from the front of the list.
+ request = std::addressof(m_request_list.front());
+ m_request_list.pop_front();
+
+ // Get thread and event for the request.
+ thread = request->GetThread();
+ event = request->GetEvent();
+ }
+ }
+
+ // If there are no requests, we're done.
+ if (request == nullptr) {
+ break;
+ }
+
+ // All requests must have threads.
+ ASSERT(thread != nullptr);
+
+ // Ensure that we close the request when done.
+ SCOPE_EXIT({ request->Close(); });
+
+ // If we're terminating, close a reference to the thread and event.
+ if (terminate) {
+ thread->Close();
+ if (event != nullptr) {
+ event->Close();
+ }
+ }
+
+ // If we need to, reply.
+ if (event != nullptr && !cur_request) {
+ // There must be no mappings.
+ ASSERT(request->GetSendCount() == 0);
+ ASSERT(request->GetReceiveCount() == 0);
+ ASSERT(request->GetExchangeCount() == 0);
+
+ // // Get the process and page table.
+ // KProcess *client_process = thread->GetOwnerProcess();
+ // auto &client_pt = client_process->GetPageTable();
+
+ // // Reply to the request.
+ // ReplyAsyncError(client_process, request->GetAddress(), request->GetSize(),
+ // ResultSessionClosed);
+
+ // // Unlock the buffer.
+ // // NOTE: Nintendo does not check the result of this.
+ // client_pt.UnlockForIpcUserBuffer(request->GetAddress(), request->GetSize());
+
+ // Signal the event.
+ event->Signal();
+ }
}
+
+ // Notify.
+ this->NotifyAvailable(ResultSessionClosed);
}
bool KServerSession::IsSignaled() const {
@@ -73,24 +152,6 @@ bool KServerSession::IsSignaled() const {
return !m_request_list.empty() && m_current_request == nullptr;
}
-Result KServerSession::QueueSyncRequest(KThread* thread, Core::Memory::Memory& memory) {
- u32* cmd_buf{reinterpret_cast<u32*>(memory.GetPointer(thread->GetTLSAddress()))};
- auto context = std::make_shared<HLERequestContext>(kernel, memory, this, thread);
-
- context->PopulateFromIncomingCommandBuffer(kernel.CurrentProcess()->GetHandleTable(), cmd_buf);
-
- return manager->QueueSyncRequest(parent, std::move(context));
-}
-
-Result KServerSession::CompleteSyncRequest(HLERequestContext& context) {
- Result result = manager->CompleteSyncRequest(this, context);
-
- // The calling thread is waiting for this request to complete, so wake it up.
- context.GetThread().EndWait(result);
-
- return result;
-}
-
Result KServerSession::OnRequest(KSessionRequest* request) {
// Create the wait queue.
ThreadQueueImplForKServerSessionRequest wait_queue{kernel};
@@ -105,24 +166,16 @@ Result KServerSession::OnRequest(KSessionRequest* request) {
// Check that we're not terminating.
R_UNLESS(!GetCurrentThread(kernel).IsTerminationRequested(), ResultTerminationRequested);
- if (manager) {
- // HLE request.
- auto& memory{kernel.System().Memory()};
- this->QueueSyncRequest(GetCurrentThreadPointer(kernel), memory);
- } else {
- // Non-HLE request.
-
- // Get whether we're empty.
- const bool was_empty = m_request_list.empty();
+ // Get whether we're empty.
+ const bool was_empty = m_request_list.empty();
- // Add the request to the list.
- request->Open();
- m_request_list.push_back(*request);
+ // Add the request to the list.
+ request->Open();
+ m_request_list.push_back(*request);
- // If we were empty, signal.
- if (was_empty) {
- this->NotifyAvailable();
- }
+ // If we were empty, signal.
+ if (was_empty) {
+ this->NotifyAvailable();
}
// If we have a request event, this is asynchronous, and we don't need to wait.
@@ -136,7 +189,7 @@ Result KServerSession::OnRequest(KSessionRequest* request) {
return GetCurrentThread(kernel).GetWaitResult();
}
-Result KServerSession::SendReply() {
+Result KServerSession::SendReply(bool is_hle) {
// Lock the session.
KScopedLightLock lk{m_lock};
@@ -171,13 +224,18 @@ Result KServerSession::SendReply() {
Result result = ResultSuccess;
if (!closed) {
// If we're not closed, send the reply.
- Core::Memory::Memory& memory{kernel.System().Memory()};
- KThread* server_thread{GetCurrentThreadPointer(kernel)};
- UNIMPLEMENTED_IF(server_thread->GetOwnerProcess() != client_thread->GetOwnerProcess());
+ if (is_hle) {
+ // HLE servers write directly to a pointer to the thread command buffer. Therefore
+ // the reply has already been written in this case.
+ } else {
+ Core::Memory::Memory& memory{kernel.System().Memory()};
+ KThread* server_thread{GetCurrentThreadPointer(kernel)};
+ UNIMPLEMENTED_IF(server_thread->GetOwnerProcess() != client_thread->GetOwnerProcess());
- auto* src_msg_buffer = memory.GetPointer(server_thread->GetTLSAddress());
- auto* dst_msg_buffer = memory.GetPointer(client_message);
- std::memcpy(dst_msg_buffer, src_msg_buffer, client_buffer_size);
+ auto* src_msg_buffer = memory.GetPointer(server_thread->GetTLSAddress());
+ auto* dst_msg_buffer = memory.GetPointer(client_message);
+ std::memcpy(dst_msg_buffer, src_msg_buffer, client_buffer_size);
+ }
} else {
result = ResultSessionClosed;
}
@@ -223,7 +281,8 @@ Result KServerSession::SendReply() {
return result;
}
-Result KServerSession::ReceiveRequest() {
+Result KServerSession::ReceiveRequest(std::shared_ptr<HLERequestContext>* out_context,
+ std::weak_ptr<SessionRequestManager> manager) {
// Lock the session.
KScopedLightLock lk{m_lock};
@@ -267,12 +326,22 @@ Result KServerSession::ReceiveRequest() {
// Receive the message.
Core::Memory::Memory& memory{kernel.System().Memory()};
- KThread* server_thread{GetCurrentThreadPointer(kernel)};
- UNIMPLEMENTED_IF(server_thread->GetOwnerProcess() != client_thread->GetOwnerProcess());
+ if (out_context != nullptr) {
+ // HLE request.
+ u32* cmd_buf{reinterpret_cast<u32*>(memory.GetPointer(client_message))};
+ *out_context = std::make_shared<HLERequestContext>(kernel, memory, this, client_thread);
+ (*out_context)->SetSessionRequestManager(manager);
+ (*out_context)
+ ->PopulateFromIncomingCommandBuffer(client_thread->GetOwnerProcess()->GetHandleTable(),
+ cmd_buf);
+ } else {
+ KThread* server_thread{GetCurrentThreadPointer(kernel)};
+ UNIMPLEMENTED_IF(server_thread->GetOwnerProcess() != client_thread->GetOwnerProcess());
- auto* src_msg_buffer = memory.GetPointer(client_message);
- auto* dst_msg_buffer = memory.GetPointer(server_thread->GetTLSAddress());
- std::memcpy(dst_msg_buffer, src_msg_buffer, client_buffer_size);
+ auto* src_msg_buffer = memory.GetPointer(client_message);
+ auto* dst_msg_buffer = memory.GetPointer(server_thread->GetTLSAddress());
+ std::memcpy(dst_msg_buffer, src_msg_buffer, client_buffer_size);
+ }
// We succeeded.
return ResultSuccess;
diff --git a/src/core/hle/kernel/k_server_session.h b/src/core/hle/kernel/k_server_session.h
index 188aef4af..6e189af8b 100644
--- a/src/core/hle/kernel/k_server_session.h
+++ b/src/core/hle/kernel/k_server_session.h
@@ -1,4 +1,4 @@
-// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -16,21 +16,11 @@
#include "core/hle/kernel/k_synchronization_object.h"
#include "core/hle/result.h"
-namespace Core::Memory {
-class Memory;
-}
-
-namespace Core::Timing {
-class CoreTiming;
-struct EventType;
-} // namespace Core::Timing
-
namespace Kernel {
class HLERequestContext;
class KernelCore;
class KSession;
-class SessionRequestHandler;
class SessionRequestManager;
class KThread;
@@ -46,8 +36,7 @@ public:
void Destroy() override;
- void Initialize(KSession* parent_session_, std::string&& name_,
- std::shared_ptr<SessionRequestManager> manager_);
+ void Initialize(KSession* parent_session_, std::string&& name_);
KSession* GetParent() {
return parent;
@@ -60,32 +49,20 @@ public:
bool IsSignaled() const override;
void OnClientClosed();
- /// Gets the session request manager, which forwards requests to the underlying service
- std::shared_ptr<SessionRequestManager>& GetSessionRequestManager() {
- return manager;
- }
-
/// TODO: flesh these out to match the real kernel
Result OnRequest(KSessionRequest* request);
- Result SendReply();
- Result ReceiveRequest();
+ Result SendReply(bool is_hle = false);
+ Result ReceiveRequest(std::shared_ptr<HLERequestContext>* out_context = nullptr,
+ std::weak_ptr<SessionRequestManager> manager = {});
+
+ Result SendReplyHLE() {
+ return SendReply(true);
+ }
private:
/// Frees up waiting client sessions when this server session is about to die
void CleanupRequests();
- /// Queues a sync request from the emulated application.
- Result QueueSyncRequest(KThread* thread, Core::Memory::Memory& memory);
-
- /// Completes a sync request from the emulated application.
- Result CompleteSyncRequest(HLERequestContext& context);
-
- /// This session's HLE request handlers; if nullptr, this is not an HLE server
- std::shared_ptr<SessionRequestManager> manager;
-
- /// When set to True, converts the session to a domain at the end of the command
- bool convert_to_domain{};
-
/// KSession that owns this KServerSession
KSession* parent{};
diff --git a/src/core/hle/kernel/k_session.cpp b/src/core/hle/kernel/k_session.cpp
index ee05aa282..7a6534ac3 100644
--- a/src/core/hle/kernel/k_session.cpp
+++ b/src/core/hle/kernel/k_session.cpp
@@ -13,8 +13,7 @@ KSession::KSession(KernelCore& kernel_)
: KAutoObjectWithSlabHeapAndContainer{kernel_}, server{kernel_}, client{kernel_} {}
KSession::~KSession() = default;
-void KSession::Initialize(KClientPort* port_, const std::string& name_,
- std::shared_ptr<SessionRequestManager> manager_) {
+void KSession::Initialize(KClientPort* port_, const std::string& name_) {
// Increment reference count.
// Because reference count is one on creation, this will result
// in a reference count of two. Thus, when both server and client are closed
@@ -26,7 +25,7 @@ void KSession::Initialize(KClientPort* port_, const std::string& name_,
KAutoObject::Create(std::addressof(client));
// Initialize our sub sessions.
- server.Initialize(this, name_ + ":Server", manager_);
+ server.Initialize(this, name_ + ":Server");
client.Initialize(this, name_ + ":Client");
// Set state and name.
diff --git a/src/core/hle/kernel/k_session.h b/src/core/hle/kernel/k_session.h
index c6ead403b..93e5e6f71 100644
--- a/src/core/hle/kernel/k_session.h
+++ b/src/core/hle/kernel/k_session.h
@@ -21,8 +21,7 @@ public:
explicit KSession(KernelCore& kernel_);
~KSession() override;
- void Initialize(KClientPort* port_, const std::string& name_,
- std::shared_ptr<SessionRequestManager> manager_ = nullptr);
+ void Initialize(KClientPort* port_, const std::string& name_);
void Finalize() override;
diff --git a/src/core/hle/kernel/k_system_resource.cpp b/src/core/hle/kernel/k_system_resource.cpp
new file mode 100644
index 000000000..4cc377a6c
--- /dev/null
+++ b/src/core/hle/kernel/k_system_resource.cpp
@@ -0,0 +1,26 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "core/hle/kernel/k_system_resource.h"
+
+namespace Kernel {
+
+Result KSecureSystemResource::Initialize([[maybe_unused]] size_t size,
+ [[maybe_unused]] KResourceLimit* resource_limit,
+ [[maybe_unused]] KMemoryManager::Pool pool) {
+ // Unimplemented
+ UNREACHABLE();
+}
+
+void KSecureSystemResource::Finalize() {
+ // Unimplemented
+ UNREACHABLE();
+}
+
+size_t KSecureSystemResource::CalculateRequiredSecureMemorySize(
+ [[maybe_unused]] size_t size, [[maybe_unused]] KMemoryManager::Pool pool) {
+ // Unimplemented
+ UNREACHABLE();
+}
+
+} // namespace Kernel
diff --git a/src/core/hle/kernel/k_system_resource.h b/src/core/hle/kernel/k_system_resource.h
new file mode 100644
index 000000000..9a991f725
--- /dev/null
+++ b/src/core/hle/kernel/k_system_resource.h
@@ -0,0 +1,137 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "common/assert.h"
+#include "common/common_types.h"
+#include "core/hle/kernel/k_auto_object.h"
+#include "core/hle/kernel/k_dynamic_resource_manager.h"
+#include "core/hle/kernel/k_memory_manager.h"
+#include "core/hle/kernel/k_page_table_manager.h"
+#include "core/hle/kernel/k_resource_limit.h"
+#include "core/hle/kernel/slab_helpers.h"
+
+namespace Kernel {
+
+// NOTE: Nintendo's implementation does not have the "is_secure_resource" field, and instead uses
+// virtual IsSecureResource().
+
+class KSystemResource : public KAutoObject {
+ KERNEL_AUTOOBJECT_TRAITS(KSystemResource, KAutoObject);
+
+public:
+ explicit KSystemResource(KernelCore& kernel_) : KAutoObject(kernel_) {}
+
+protected:
+ void SetSecureResource() {
+ m_is_secure_resource = true;
+ }
+
+public:
+ virtual void Destroy() override {
+ UNREACHABLE_MSG("KSystemResource::Destroy() was called");
+ }
+
+ bool IsSecureResource() const {
+ return m_is_secure_resource;
+ }
+
+ void SetManagers(KMemoryBlockSlabManager& mb, KBlockInfoManager& bi, KPageTableManager& pt) {
+ ASSERT(m_p_memory_block_slab_manager == nullptr);
+ ASSERT(m_p_block_info_manager == nullptr);
+ ASSERT(m_p_page_table_manager == nullptr);
+
+ m_p_memory_block_slab_manager = std::addressof(mb);
+ m_p_block_info_manager = std::addressof(bi);
+ m_p_page_table_manager = std::addressof(pt);
+ }
+
+ const KMemoryBlockSlabManager& GetMemoryBlockSlabManager() const {
+ return *m_p_memory_block_slab_manager;
+ }
+ const KBlockInfoManager& GetBlockInfoManager() const {
+ return *m_p_block_info_manager;
+ }
+ const KPageTableManager& GetPageTableManager() const {
+ return *m_p_page_table_manager;
+ }
+
+ KMemoryBlockSlabManager& GetMemoryBlockSlabManager() {
+ return *m_p_memory_block_slab_manager;
+ }
+ KBlockInfoManager& GetBlockInfoManager() {
+ return *m_p_block_info_manager;
+ }
+ KPageTableManager& GetPageTableManager() {
+ return *m_p_page_table_manager;
+ }
+
+ KMemoryBlockSlabManager* GetMemoryBlockSlabManagerPointer() {
+ return m_p_memory_block_slab_manager;
+ }
+ KBlockInfoManager* GetBlockInfoManagerPointer() {
+ return m_p_block_info_manager;
+ }
+ KPageTableManager* GetPageTableManagerPointer() {
+ return m_p_page_table_manager;
+ }
+
+private:
+ KMemoryBlockSlabManager* m_p_memory_block_slab_manager{};
+ KBlockInfoManager* m_p_block_info_manager{};
+ KPageTableManager* m_p_page_table_manager{};
+ bool m_is_secure_resource{false};
+};
+
+class KSecureSystemResource final
+ : public KAutoObjectWithSlabHeap<KSecureSystemResource, KSystemResource> {
+public:
+ explicit KSecureSystemResource(KernelCore& kernel_)
+ : KAutoObjectWithSlabHeap<KSecureSystemResource, KSystemResource>(kernel_) {
+ // Mark ourselves as being a secure resource.
+ this->SetSecureResource();
+ }
+
+ Result Initialize(size_t size, KResourceLimit* resource_limit, KMemoryManager::Pool pool);
+ void Finalize();
+
+ bool IsInitialized() const {
+ return m_is_initialized;
+ }
+ static void PostDestroy([[maybe_unused]] uintptr_t arg) {}
+
+ size_t CalculateRequiredSecureMemorySize() const {
+ return CalculateRequiredSecureMemorySize(m_resource_size, m_resource_pool);
+ }
+
+ size_t GetSize() const {
+ return m_resource_size;
+ }
+ size_t GetUsedSize() const {
+ return m_dynamic_page_manager.GetUsed() * PageSize;
+ }
+
+ const KDynamicPageManager& GetDynamicPageManager() const {
+ return m_dynamic_page_manager;
+ }
+
+public:
+ static size_t CalculateRequiredSecureMemorySize(size_t size, KMemoryManager::Pool pool);
+
+private:
+ bool m_is_initialized{};
+ KMemoryManager::Pool m_resource_pool{};
+ KDynamicPageManager m_dynamic_page_manager;
+ KMemoryBlockSlabManager m_memory_block_slab_manager;
+ KBlockInfoManager m_block_info_manager;
+ KPageTableManager m_page_table_manager;
+ KMemoryBlockSlabHeap m_memory_block_heap;
+ KBlockInfoSlabHeap m_block_info_heap;
+ KPageTableSlabHeap m_page_table_heap;
+ KResourceLimit* m_resource_limit{};
+ VAddr m_resource_address{};
+ size_t m_resource_size{};
+};
+
+} // namespace Kernel
diff --git a/src/core/hle/kernel/kernel.cpp b/src/core/hle/kernel/kernel.cpp
index fdc774e30..47b760a9c 100644
--- a/src/core/hle/kernel/kernel.cpp
+++ b/src/core/hle/kernel/kernel.cpp
@@ -28,10 +28,12 @@
#include "core/hle/kernel/k_handle_table.h"
#include "core/hle/kernel/k_memory_layout.h"
#include "core/hle/kernel/k_memory_manager.h"
+#include "core/hle/kernel/k_page_buffer.h"
#include "core/hle/kernel/k_process.h"
#include "core/hle/kernel/k_resource_limit.h"
#include "core/hle/kernel/k_scheduler.h"
#include "core/hle/kernel/k_shared_memory.h"
+#include "core/hle/kernel/k_system_resource.h"
#include "core/hle/kernel/k_thread.h"
#include "core/hle/kernel/k_worker_task_manager.h"
#include "core/hle/kernel/kernel.h"
@@ -47,6 +49,11 @@ MICROPROFILE_DEFINE(Kernel_SVC, "Kernel", "SVC", MP_RGB(70, 200, 70));
namespace Kernel {
struct KernelCore::Impl {
+ static constexpr size_t ApplicationMemoryBlockSlabHeapSize = 20000;
+ static constexpr size_t SystemMemoryBlockSlabHeapSize = 10000;
+ static constexpr size_t BlockInfoSlabHeapSize = 4000;
+ static constexpr size_t ReservedDynamicPageCount = 64;
+
explicit Impl(Core::System& system_, KernelCore& kernel_)
: time_manager{system_}, service_threads_manager{1, "ServiceThreadsManager"},
service_thread_barrier{2}, system{system_} {}
@@ -60,7 +67,6 @@ struct KernelCore::Impl {
global_scheduler_context = std::make_unique<Kernel::GlobalSchedulerContext>(kernel);
global_handle_table = std::make_unique<Kernel::KHandleTable>(kernel);
global_handle_table->Initialize(KHandleTable::MaxTableSize);
- default_service_thread = CreateServiceThread(kernel, "DefaultServiceThread");
is_phantom_mode_for_singlecore = false;
@@ -72,7 +78,6 @@ struct KernelCore::Impl {
// Initialize kernel memory and resources.
InitializeSystemResourceLimit(kernel, system.CoreTiming());
InitializeMemoryLayout();
- Init::InitializeKPageBufferSlabHeap(system);
InitializeShutdownThreads();
InitializePhysicalCores();
InitializePreemption(kernel);
@@ -82,10 +87,13 @@ struct KernelCore::Impl {
const auto& pt_heap_region = memory_layout->GetPageTableHeapRegion();
ASSERT(pt_heap_region.GetEndAddress() != 0);
- InitializeResourceManagers(pt_heap_region.GetAddress(), pt_heap_region.GetSize());
+ InitializeResourceManagers(kernel, pt_heap_region.GetAddress(),
+ pt_heap_region.GetSize());
}
RegisterHostThread();
+
+ default_service_thread = CreateServiceThread(kernel, "DefaultServiceThread");
}
void InitializeCores() {
@@ -184,17 +192,6 @@ struct KernelCore::Impl {
}
void CloseServices() {
- // Close all open server sessions and ports.
- std::unordered_set<KAutoObject*> server_objects_;
- {
- std::scoped_lock lk(server_objects_lock);
- server_objects_ = server_objects;
- server_objects.clear();
- }
- for (auto* server_object : server_objects_) {
- server_object->Close();
- }
-
// Ensures all service threads gracefully shutdown.
ClearServiceThreads();
}
@@ -263,16 +260,82 @@ struct KernelCore::Impl {
system.CoreTiming().ScheduleLoopingEvent(time_interval, time_interval, preemption_event);
}
- void InitializeResourceManagers(VAddr address, size_t size) {
- dynamic_page_manager = std::make_unique<KDynamicPageManager>();
- memory_block_heap = std::make_unique<KMemoryBlockSlabHeap>();
+ void InitializeResourceManagers(KernelCore& kernel, VAddr address, size_t size) {
+ // Ensure that the buffer is suitable for our use.
+ ASSERT(Common::IsAligned(address, PageSize));
+ ASSERT(Common::IsAligned(size, PageSize));
+
+ // Ensure that we have space for our reference counts.
+ const size_t rc_size =
+ Common::AlignUp(KPageTableSlabHeap::CalculateReferenceCountSize(size), PageSize);
+ ASSERT(rc_size < size);
+ size -= rc_size;
+
+ // Initialize the resource managers' shared page manager.
+ resource_manager_page_manager = std::make_unique<KDynamicPageManager>();
+ resource_manager_page_manager->Initialize(
+ address, size, std::max<size_t>(PageSize, KPageBufferSlabHeap::BufferSize));
+
+ // Initialize the KPageBuffer slab heap.
+ page_buffer_slab_heap.Initialize(system);
+
+ // Initialize the fixed-size slabheaps.
+ app_memory_block_heap = std::make_unique<KMemoryBlockSlabHeap>();
+ sys_memory_block_heap = std::make_unique<KMemoryBlockSlabHeap>();
+ block_info_heap = std::make_unique<KBlockInfoSlabHeap>();
+ app_memory_block_heap->Initialize(resource_manager_page_manager.get(),
+ ApplicationMemoryBlockSlabHeapSize);
+ sys_memory_block_heap->Initialize(resource_manager_page_manager.get(),
+ SystemMemoryBlockSlabHeapSize);
+ block_info_heap->Initialize(resource_manager_page_manager.get(), BlockInfoSlabHeapSize);
+
+ // Reserve all but a fixed number of remaining pages for the page table heap.
+ const size_t num_pt_pages = resource_manager_page_manager->GetCount() -
+ resource_manager_page_manager->GetUsed() -
+ ReservedDynamicPageCount;
+ page_table_heap = std::make_unique<KPageTableSlabHeap>();
+
+ // TODO(bunnei): Pass in address once we support kernel virtual memory allocations.
+ page_table_heap->Initialize(
+ resource_manager_page_manager.get(), num_pt_pages,
+ /*GetPointer<KPageTableManager::RefCount>(address + size)*/ nullptr);
+
+ // Setup the slab managers.
+ KDynamicPageManager* const app_dynamic_page_manager = nullptr;
+ KDynamicPageManager* const sys_dynamic_page_manager =
+ /*KTargetSystem::IsDynamicResourceLimitsEnabled()*/ true
+ ? resource_manager_page_manager.get()
+ : nullptr;
app_memory_block_manager = std::make_unique<KMemoryBlockSlabManager>();
-
- dynamic_page_manager->Initialize(address, size);
- static constexpr size_t ApplicationMemoryBlockSlabHeapSize = 20000;
- memory_block_heap->Initialize(dynamic_page_manager.get(),
- ApplicationMemoryBlockSlabHeapSize);
- app_memory_block_manager->Initialize(nullptr, memory_block_heap.get());
+ sys_memory_block_manager = std::make_unique<KMemoryBlockSlabManager>();
+ app_block_info_manager = std::make_unique<KBlockInfoManager>();
+ sys_block_info_manager = std::make_unique<KBlockInfoManager>();
+ app_page_table_manager = std::make_unique<KPageTableManager>();
+ sys_page_table_manager = std::make_unique<KPageTableManager>();
+
+ app_memory_block_manager->Initialize(app_dynamic_page_manager, app_memory_block_heap.get());
+ sys_memory_block_manager->Initialize(sys_dynamic_page_manager, sys_memory_block_heap.get());
+
+ app_block_info_manager->Initialize(app_dynamic_page_manager, block_info_heap.get());
+ sys_block_info_manager->Initialize(sys_dynamic_page_manager, block_info_heap.get());
+
+ app_page_table_manager->Initialize(app_dynamic_page_manager, page_table_heap.get());
+ sys_page_table_manager->Initialize(sys_dynamic_page_manager, page_table_heap.get());
+
+ // Check that we have the correct number of dynamic pages available.
+ ASSERT(resource_manager_page_manager->GetCount() -
+ resource_manager_page_manager->GetUsed() ==
+ ReservedDynamicPageCount);
+
+ // Create the system page table managers.
+ app_system_resource = std::make_unique<KSystemResource>(kernel);
+ sys_system_resource = std::make_unique<KSystemResource>(kernel);
+
+ // Set the managers for the system resources.
+ app_system_resource->SetManagers(*app_memory_block_manager, *app_block_info_manager,
+ *app_page_table_manager);
+ sys_system_resource->SetManagers(*sys_memory_block_manager, *sys_block_info_manager,
+ *sys_page_table_manager);
}
void InitializeShutdownThreads() {
@@ -346,6 +409,8 @@ struct KernelCore::Impl {
return this_id;
}
+ static inline thread_local bool is_phantom_mode_for_singlecore{false};
+
bool IsPhantomModeForSingleCore() const {
return is_phantom_mode_for_singlecore;
}
@@ -454,6 +519,9 @@ struct KernelCore::Impl {
ASSERT(memory_layout->GetVirtualMemoryRegionTree().Insert(
misc_region_start, misc_region_size, KMemoryRegionType_KernelMisc));
+ // Determine if we'll use extra thread resources.
+ const bool use_extra_resources = KSystemControl::Init::ShouldIncreaseThreadResourceLimit();
+
// Setup the stack region.
constexpr size_t StackRegionSize = 14_MiB;
constexpr size_t StackRegionAlign = KernelAslrAlignment;
@@ -464,7 +532,8 @@ struct KernelCore::Impl {
stack_region_start, StackRegionSize, KMemoryRegionType_KernelStack));
// Determine the size of the resource region.
- const size_t resource_region_size = memory_layout->GetResourceRegionSizeForInit();
+ const size_t resource_region_size =
+ memory_layout->GetResourceRegionSizeForInit(use_extra_resources);
// Determine the size of the slab region.
const size_t slab_region_size =
@@ -698,24 +767,21 @@ struct KernelCore::Impl {
return {};
}
- KClientPort* port = &search->second(system.ServiceManager(), system);
- RegisterServerObject(&port->GetParent()->GetServerPort());
- return port;
+ return &search->second(system.ServiceManager(), system);
}
- void RegisterServerObject(KAutoObject* server_object) {
- std::scoped_lock lk(server_objects_lock);
- server_objects.insert(server_object);
- }
+ void RegisterNamedServiceHandler(std::string name, KServerPort* server_port) {
+ auto search = service_interface_handlers.find(name);
+ if (search == service_interface_handlers.end()) {
+ return;
+ }
- void UnregisterServerObject(KAutoObject* server_object) {
- std::scoped_lock lk(server_objects_lock);
- server_objects.erase(server_object);
+ search->second(system.ServiceManager(), server_port);
}
std::weak_ptr<Kernel::ServiceThread> CreateServiceThread(KernelCore& kernel,
const std::string& name) {
- auto service_thread = std::make_shared<Kernel::ServiceThread>(kernel, 1, name);
+ auto service_thread = std::make_shared<Kernel::ServiceThread>(kernel, name);
service_threads_manager.QueueWork(
[this, service_thread]() { service_threads.emplace(service_thread); });
@@ -745,7 +811,6 @@ struct KernelCore::Impl {
service_thread_barrier.Sync();
}
- std::mutex server_objects_lock;
std::mutex registered_objects_lock;
std::mutex registered_in_use_objects_lock;
@@ -763,6 +828,8 @@ struct KernelCore::Impl {
Init::KSlabResourceCounts slab_resource_counts{};
KResourceLimit* system_resource_limit{};
+ KPageBufferSlabHeap page_buffer_slab_heap;
+
std::shared_ptr<Core::Timing::EventType> preemption_event;
// This is the kernel's handle table or supervisor handle table which
@@ -774,8 +841,8 @@ struct KernelCore::Impl {
/// Map of named ports managed by the kernel, which can be retrieved using
/// the ConnectToPort SVC.
std::unordered_map<std::string, ServiceInterfaceFactory> service_interface_factory;
+ std::unordered_map<std::string, ServiceInterfaceHandlerFn> service_interface_handlers;
NamedPortTable named_ports;
- std::unordered_set<KAutoObject*> server_objects;
std::unordered_set<KAutoObject*> registered_objects;
std::unordered_set<KAutoObject*> registered_in_use_objects;
@@ -788,10 +855,20 @@ struct KernelCore::Impl {
// Kernel memory management
std::unique_ptr<KMemoryManager> memory_manager;
- // Dynamic slab managers
- std::unique_ptr<KDynamicPageManager> dynamic_page_manager;
- std::unique_ptr<KMemoryBlockSlabHeap> memory_block_heap;
+ // Resource managers
+ std::unique_ptr<KDynamicPageManager> resource_manager_page_manager;
+ std::unique_ptr<KPageTableSlabHeap> page_table_heap;
+ std::unique_ptr<KMemoryBlockSlabHeap> app_memory_block_heap;
+ std::unique_ptr<KMemoryBlockSlabHeap> sys_memory_block_heap;
+ std::unique_ptr<KBlockInfoSlabHeap> block_info_heap;
+ std::unique_ptr<KPageTableManager> app_page_table_manager;
+ std::unique_ptr<KPageTableManager> sys_page_table_manager;
std::unique_ptr<KMemoryBlockSlabManager> app_memory_block_manager;
+ std::unique_ptr<KMemoryBlockSlabManager> sys_memory_block_manager;
+ std::unique_ptr<KBlockInfoManager> app_block_info_manager;
+ std::unique_ptr<KBlockInfoManager> sys_block_info_manager;
+ std::unique_ptr<KSystemResource> app_system_resource;
+ std::unique_ptr<KSystemResource> sys_system_resource;
// Shared memory for services
Kernel::KSharedMemory* hid_shared_mem{};
@@ -814,7 +891,6 @@ struct KernelCore::Impl {
bool is_multicore{};
std::atomic_bool is_shutting_down{};
- bool is_phantom_mode_for_singlecore{};
u32 single_core_thread_id{};
std::array<u64, Core::Hardware::NUM_CPU_CORES> svc_ticks{};
@@ -981,16 +1057,17 @@ void KernelCore::RegisterNamedService(std::string name, ServiceInterfaceFactory&
impl->service_interface_factory.emplace(std::move(name), factory);
}
-KClientPort* KernelCore::CreateNamedServicePort(std::string name) {
- return impl->CreateNamedServicePort(std::move(name));
+void KernelCore::RegisterInterfaceForNamedService(std::string name,
+ ServiceInterfaceHandlerFn&& handler) {
+ impl->service_interface_handlers.emplace(std::move(name), handler);
}
-void KernelCore::RegisterServerObject(KAutoObject* server_object) {
- impl->RegisterServerObject(server_object);
+KClientPort* KernelCore::CreateNamedServicePort(std::string name) {
+ return impl->CreateNamedServicePort(std::move(name));
}
-void KernelCore::UnregisterServerObject(KAutoObject* server_object) {
- impl->UnregisterServerObject(server_object);
+void KernelCore::RegisterNamedServiceHandler(std::string name, KServerPort* server_port) {
+ impl->RegisterNamedServiceHandler(std::move(name), server_port);
}
void KernelCore::RegisterKernelObject(KAutoObject* object) {
@@ -1069,12 +1146,12 @@ const KMemoryManager& KernelCore::MemoryManager() const {
return *impl->memory_manager;
}
-KMemoryBlockSlabManager& KernelCore::GetApplicationMemoryBlockManager() {
- return *impl->app_memory_block_manager;
+KSystemResource& KernelCore::GetSystemSystemResource() {
+ return *impl->sys_system_resource;
}
-const KMemoryBlockSlabManager& KernelCore::GetApplicationMemoryBlockManager() const {
- return *impl->app_memory_block_manager;
+const KSystemResource& KernelCore::GetSystemSystemResource() const {
+ return *impl->sys_system_resource;
}
Kernel::KSharedMemory& KernelCore::GetHidSharedMem() {
diff --git a/src/core/hle/kernel/kernel.h b/src/core/hle/kernel/kernel.h
index 266be2bc4..caca60586 100644
--- a/src/core/hle/kernel/kernel.h
+++ b/src/core/hle/kernel/kernel.h
@@ -34,22 +34,27 @@ class KClientPort;
class GlobalSchedulerContext;
class KAutoObjectWithListContainer;
class KClientSession;
+class KDebug;
+class KDynamicPageManager;
class KEvent;
+class KEventInfo;
class KHandleTable;
class KLinkedListNode;
-class KMemoryBlockSlabManager;
class KMemoryLayout;
class KMemoryManager;
class KPageBuffer;
+class KPageBufferSlabHeap;
class KPort;
class KProcess;
class KResourceLimit;
class KScheduler;
+class KServerPort;
class KServerSession;
class KSession;
class KSessionRequest;
class KSharedMemory;
class KSharedMemoryInfo;
+class KSecureSystemResource;
class KThread;
class KThreadLocalPage;
class KTransferMemory;
@@ -63,6 +68,8 @@ class TimeManager;
using ServiceInterfaceFactory =
std::function<KClientPort&(Service::SM::ServiceManager&, Core::System&)>;
+using ServiceInterfaceHandlerFn = std::function<void(Service::SM::ServiceManager&, KServerPort*)>;
+
namespace Init {
struct KSlabResourceCounts;
}
@@ -192,16 +199,14 @@ public:
/// Registers a named HLE service, passing a factory used to open a port to that service.
void RegisterNamedService(std::string name, ServiceInterfaceFactory&& factory);
+ /// Registers a setup function for the named HLE service.
+ void RegisterInterfaceForNamedService(std::string name, ServiceInterfaceHandlerFn&& handler);
+
/// Opens a port to a service previously registered with RegisterNamedService.
KClientPort* CreateNamedServicePort(std::string name);
- /// Registers a server session or port with the gobal emulation state, to be freed on shutdown.
- /// This is necessary because we do not emulate processes for HLE sessions and ports.
- void RegisterServerObject(KAutoObject* server_object);
-
- /// Unregisters a server session or port previously registered with RegisterServerSession when
- /// it was destroyed during the current emulation session.
- void UnregisterServerObject(KAutoObject* server_object);
+ /// Accepts a session on a port created by CreateNamedServicePort.
+ void RegisterNamedServiceHandler(std::string name, KServerPort* server_port);
/// Registers all kernel objects with the global emulation state, this is purely for tracking
/// leaks after emulation has been shutdown.
@@ -243,11 +248,11 @@ public:
/// Gets the virtual memory manager for the kernel.
const KMemoryManager& MemoryManager() const;
- /// Gets the application memory block manager for the kernel.
- KMemoryBlockSlabManager& GetApplicationMemoryBlockManager();
+ /// Gets the system resource manager.
+ KSystemResource& GetSystemSystemResource();
- /// Gets the application memory block manager for the kernel.
- const KMemoryBlockSlabManager& GetApplicationMemoryBlockManager() const;
+ /// Gets the system resource manager.
+ const KSystemResource& GetSystemSystemResource() const;
/// Gets the shared memory object for HID services.
Kernel::KSharedMemory& GetHidSharedMem();
@@ -363,6 +368,12 @@ public:
return slab_heap_container->thread_local_page;
} else if constexpr (std::is_same_v<T, KSessionRequest>) {
return slab_heap_container->session_request;
+ } else if constexpr (std::is_same_v<T, KSecureSystemResource>) {
+ return slab_heap_container->secure_system_resource;
+ } else if constexpr (std::is_same_v<T, KEventInfo>) {
+ return slab_heap_container->event_info;
+ } else if constexpr (std::is_same_v<T, KDebug>) {
+ return slab_heap_container->debug;
}
}
@@ -426,6 +437,9 @@ private:
KSlabHeap<KPageBuffer> page_buffer;
KSlabHeap<KThreadLocalPage> thread_local_page;
KSlabHeap<KSessionRequest> session_request;
+ KSlabHeap<KSecureSystemResource> secure_system_resource;
+ KSlabHeap<KEventInfo> event_info;
+ KSlabHeap<KDebug> debug;
};
std::unique_ptr<SlabHeapContainer> slab_heap_container;
diff --git a/src/core/hle/kernel/service_thread.cpp b/src/core/hle/kernel/service_thread.cpp
index d23d76706..c8fe42537 100644
--- a/src/core/hle/kernel/service_thread.cpp
+++ b/src/core/hle/kernel/service_thread.cpp
@@ -1,15 +1,18 @@
-// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
-#include <condition_variable>
#include <functional>
+#include <map>
#include <mutex>
#include <thread>
#include <vector>
-#include <queue>
#include "common/scope_exit.h"
#include "common/thread.h"
+#include "core/hle/ipc_helpers.h"
+#include "core/hle/kernel/hle_ipc.h"
+#include "core/hle/kernel/k_event.h"
+#include "core/hle/kernel/k_scoped_resource_reservation.h"
#include "core/hle/kernel/k_session.h"
#include "core/hle/kernel/k_thread.h"
#include "core/hle/kernel/kernel.h"
@@ -19,101 +22,198 @@ namespace Kernel {
class ServiceThread::Impl final {
public:
- explicit Impl(KernelCore& kernel, std::size_t num_threads, const std::string& name);
+ explicit Impl(KernelCore& kernel, const std::string& service_name);
~Impl();
- void QueueSyncRequest(KSession& session, std::shared_ptr<HLERequestContext>&& context);
+ void WaitAndProcessImpl();
+ void SessionClosed(KServerSession* server_session,
+ std::shared_ptr<SessionRequestManager> manager);
+ void LoopProcess();
+
+ void RegisterServerSession(KServerSession* session,
+ std::shared_ptr<SessionRequestManager> manager);
private:
- std::vector<std::jthread> threads;
- std::queue<std::function<void()>> requests;
- std::mutex queue_mutex;
- std::condition_variable_any condition;
- const std::string service_name;
+ KernelCore& kernel;
+
+ std::jthread m_thread;
+ std::mutex m_session_mutex;
+ std::map<KServerSession*, std::shared_ptr<SessionRequestManager>> m_sessions;
+ KEvent* m_wakeup_event;
+ KProcess* m_process;
+ std::atomic<bool> m_shutdown_requested;
+ const std::string m_service_name;
};
-ServiceThread::Impl::Impl(KernelCore& kernel, std::size_t num_threads, const std::string& name)
- : service_name{name} {
- for (std::size_t i = 0; i < num_threads; ++i) {
- threads.emplace_back([this, &kernel](std::stop_token stop_token) {
- Common::SetCurrentThreadName(std::string{service_name}.c_str());
+void ServiceThread::Impl::WaitAndProcessImpl() {
+ // Create local list of waitable sessions.
+ std::vector<KSynchronizationObject*> objs;
+ std::vector<std::shared_ptr<SessionRequestManager>> managers;
- // Wait for first request before trying to acquire a render context
- {
- std::unique_lock lock{queue_mutex};
- condition.wait(lock, stop_token, [this] { return !requests.empty(); });
- }
+ {
+ // Lock to get the set.
+ std::scoped_lock lk{m_session_mutex};
- if (stop_token.stop_requested()) {
- return;
- }
+ // Reserve the needed quantity.
+ objs.reserve(m_sessions.size() + 1);
+ managers.reserve(m_sessions.size());
- // Allocate a dummy guest thread for this host thread.
- kernel.RegisterHostThread();
+ // Copy to our local list.
+ for (const auto& [session, manager] : m_sessions) {
+ objs.push_back(session);
+ managers.push_back(manager);
+ }
- while (true) {
- std::function<void()> task;
+ // Insert the wakeup event at the end.
+ objs.push_back(&m_wakeup_event->GetReadableEvent());
+ }
- {
- std::unique_lock lock{queue_mutex};
- condition.wait(lock, stop_token, [this] { return !requests.empty(); });
+ // Wait on the list of sessions.
+ s32 index{-1};
+ Result rc = KSynchronizationObject::Wait(kernel, &index, objs.data(),
+ static_cast<s32>(objs.size()), -1);
+ ASSERT(!rc.IsFailure());
+
+ // If this was the wakeup event, clear it and finish.
+ if (index >= static_cast<s64>(objs.size() - 1)) {
+ m_wakeup_event->Clear();
+ return;
+ }
- if (stop_token.stop_requested()) {
- return;
- }
+ // This event is from a server session.
+ auto* server_session = static_cast<KServerSession*>(objs[index]);
+ auto& manager = managers[index];
- if (requests.empty()) {
- continue;
- }
+ // Fetch the HLE request context.
+ std::shared_ptr<HLERequestContext> context;
+ rc = server_session->ReceiveRequest(&context, manager);
- task = std::move(requests.front());
- requests.pop();
- }
+ // If the session was closed, handle that.
+ if (rc == ResultSessionClosed) {
+ SessionClosed(server_session, manager);
- task();
- }
- });
+ // Finish.
+ return;
}
+
+ // TODO: handle other cases
+ ASSERT(rc == ResultSuccess);
+
+ // Perform the request.
+ Result service_rc = manager->CompleteSyncRequest(server_session, *context);
+
+ // Reply to the client.
+ rc = server_session->SendReplyHLE();
+
+ if (rc == ResultSessionClosed || service_rc == IPC::ERR_REMOTE_PROCESS_DEAD) {
+ SessionClosed(server_session, manager);
+ return;
+ }
+
+ // TODO: handle other cases
+ ASSERT(rc == ResultSuccess);
+ ASSERT(service_rc == ResultSuccess);
}
-void ServiceThread::Impl::QueueSyncRequest(KSession& session,
- std::shared_ptr<HLERequestContext>&& context) {
+void ServiceThread::Impl::SessionClosed(KServerSession* server_session,
+ std::shared_ptr<SessionRequestManager> manager) {
{
- std::unique_lock lock{queue_mutex};
+ // Lock to get the set.
+ std::scoped_lock lk{m_session_mutex};
+
+ // Erase the session.
+ ASSERT(m_sessions.erase(server_session) == 1);
+ }
- auto* server_session{&session.GetServerSession()};
+ // Close our reference to the server session.
+ server_session->Close();
+}
- // Open a reference to the session to ensure it is not closes while the service request
- // completes asynchronously.
- server_session->Open();
+void ServiceThread::Impl::LoopProcess() {
+ Common::SetCurrentThreadName(m_service_name.c_str());
- requests.emplace([server_session, context{std::move(context)}]() {
- // Close the reference.
- SCOPE_EXIT({ server_session->Close(); });
+ kernel.RegisterHostThread();
- // Complete the service request.
- server_session->CompleteSyncRequest(*context);
- });
+ while (!m_shutdown_requested.load()) {
+ WaitAndProcessImpl();
}
- condition.notify_one();
+}
+
+void ServiceThread::Impl::RegisterServerSession(KServerSession* server_session,
+ std::shared_ptr<SessionRequestManager> manager) {
+ // Open the server session.
+ server_session->Open();
+
+ {
+ // Lock to get the set.
+ std::scoped_lock lk{m_session_mutex};
+
+ // Insert the session and manager.
+ m_sessions[server_session] = manager;
+ }
+
+ // Signal the wakeup event.
+ m_wakeup_event->Signal();
}
ServiceThread::Impl::~Impl() {
- condition.notify_all();
- for (auto& thread : threads) {
- thread.request_stop();
- thread.join();
+ // Shut down the processing thread.
+ m_shutdown_requested.store(true);
+ m_wakeup_event->Signal();
+ m_thread.join();
+
+ // Lock mutex.
+ m_session_mutex.lock();
+
+ // Close all remaining sessions.
+ for (const auto& [server_session, manager] : m_sessions) {
+ server_session->Close();
}
+
+ // Destroy remaining managers.
+ m_sessions.clear();
+
+ // Close event.
+ m_wakeup_event->GetReadableEvent().Close();
+ m_wakeup_event->Close();
+
+ // Close process.
+ m_process->Close();
+}
+
+ServiceThread::Impl::Impl(KernelCore& kernel_, const std::string& service_name)
+ : kernel{kernel_}, m_service_name{service_name} {
+ // Initialize process.
+ m_process = KProcess::Create(kernel);
+ KProcess::Initialize(m_process, kernel.System(), service_name,
+ KProcess::ProcessType::KernelInternal, kernel.GetSystemResourceLimit());
+
+ // Reserve a new event from the process resource limit
+ KScopedResourceReservation event_reservation(m_process, LimitableResource::Events);
+ ASSERT(event_reservation.Succeeded());
+
+ // Initialize event.
+ m_wakeup_event = KEvent::Create(kernel);
+ m_wakeup_event->Initialize(m_process);
+
+ // Commit the event reservation.
+ event_reservation.Commit();
+
+ // Register the event.
+ KEvent::Register(kernel, m_wakeup_event);
+
+ // Start thread.
+ m_thread = std::jthread([this] { LoopProcess(); });
}
-ServiceThread::ServiceThread(KernelCore& kernel, std::size_t num_threads, const std::string& name)
- : impl{std::make_unique<Impl>(kernel, num_threads, name)} {}
+ServiceThread::ServiceThread(KernelCore& kernel, const std::string& name)
+ : impl{std::make_unique<Impl>(kernel, name)} {}
ServiceThread::~ServiceThread() = default;
-void ServiceThread::QueueSyncRequest(KSession& session,
- std::shared_ptr<HLERequestContext>&& context) {
- impl->QueueSyncRequest(session, std::move(context));
+void ServiceThread::RegisterServerSession(KServerSession* session,
+ std::shared_ptr<SessionRequestManager> manager) {
+ impl->RegisterServerSession(session, manager);
}
} // namespace Kernel
diff --git a/src/core/hle/kernel/service_thread.h b/src/core/hle/kernel/service_thread.h
index c5896f2bd..fb4325531 100644
--- a/src/core/hle/kernel/service_thread.h
+++ b/src/core/hle/kernel/service_thread.h
@@ -11,13 +11,15 @@ namespace Kernel {
class HLERequestContext;
class KernelCore;
class KSession;
+class SessionRequestManager;
class ServiceThread final {
public:
- explicit ServiceThread(KernelCore& kernel, std::size_t num_threads, const std::string& name);
+ explicit ServiceThread(KernelCore& kernel, const std::string& name);
~ServiceThread();
- void QueueSyncRequest(KSession& session, std::shared_ptr<HLERequestContext>&& context);
+ void RegisterServerSession(KServerSession* session,
+ std::shared_ptr<SessionRequestManager> manager);
private:
class Impl;
diff --git a/src/core/hle/kernel/slab_helpers.h b/src/core/hle/kernel/slab_helpers.h
index 06b51e919..0228ce188 100644
--- a/src/core/hle/kernel/slab_helpers.h
+++ b/src/core/hle/kernel/slab_helpers.h
@@ -53,6 +53,84 @@ public:
};
template <typename Derived, typename Base>
+class KAutoObjectWithSlabHeap : public Base {
+ static_assert(std::is_base_of<KAutoObject, Base>::value);
+
+private:
+ static Derived* Allocate(KernelCore& kernel) {
+ return kernel.SlabHeap<Derived>().Allocate(kernel);
+ }
+
+ static void Free(KernelCore& kernel, Derived* obj) {
+ kernel.SlabHeap<Derived>().Free(obj);
+ }
+
+public:
+ explicit KAutoObjectWithSlabHeap(KernelCore& kernel_) : Base(kernel_), kernel(kernel_) {}
+ virtual ~KAutoObjectWithSlabHeap() = default;
+
+ virtual void Destroy() override {
+ const bool is_initialized = this->IsInitialized();
+ uintptr_t arg = 0;
+ if (is_initialized) {
+ arg = this->GetPostDestroyArgument();
+ this->Finalize();
+ }
+ Free(kernel, static_cast<Derived*>(this));
+ if (is_initialized) {
+ Derived::PostDestroy(arg);
+ }
+ }
+
+ virtual bool IsInitialized() const {
+ return true;
+ }
+ virtual uintptr_t GetPostDestroyArgument() const {
+ return 0;
+ }
+
+ size_t GetSlabIndex() const {
+ return SlabHeap<Derived>(kernel).GetObjectIndex(static_cast<const Derived*>(this));
+ }
+
+public:
+ static void InitializeSlabHeap(KernelCore& kernel, void* memory, size_t memory_size) {
+ kernel.SlabHeap<Derived>().Initialize(memory, memory_size);
+ }
+
+ static Derived* Create(KernelCore& kernel) {
+ Derived* obj = Allocate(kernel);
+ if (obj != nullptr) {
+ KAutoObject::Create(obj);
+ }
+ return obj;
+ }
+
+ static size_t GetObjectSize(KernelCore& kernel) {
+ return kernel.SlabHeap<Derived>().GetObjectSize();
+ }
+
+ static size_t GetSlabHeapSize(KernelCore& kernel) {
+ return kernel.SlabHeap<Derived>().GetSlabHeapSize();
+ }
+
+ static size_t GetPeakIndex(KernelCore& kernel) {
+ return kernel.SlabHeap<Derived>().GetPeakIndex();
+ }
+
+ static uintptr_t GetSlabHeapAddress(KernelCore& kernel) {
+ return kernel.SlabHeap<Derived>().GetSlabHeapAddress();
+ }
+
+ static size_t GetNumRemaining(KernelCore& kernel) {
+ return kernel.SlabHeap<Derived>().GetNumRemaining();
+ }
+
+protected:
+ KernelCore& kernel;
+};
+
+template <typename Derived, typename Base>
class KAutoObjectWithSlabHeapAndContainer : public Base {
static_assert(std::is_base_of<KAutoObjectWithList, Base>::value);
diff --git a/src/core/hle/kernel/svc.cpp b/src/core/hle/kernel/svc.cpp
index 4aca5b27d..ecac97a52 100644
--- a/src/core/hle/kernel/svc.cpp
+++ b/src/core/hle/kernel/svc.cpp
@@ -24,6 +24,7 @@
#include "core/hle/kernel/k_memory_block.h"
#include "core/hle/kernel/k_memory_layout.h"
#include "core/hle/kernel/k_page_table.h"
+#include "core/hle/kernel/k_port.h"
#include "core/hle/kernel/k_process.h"
#include "core/hle/kernel/k_readable_event.h"
#include "core/hle/kernel/k_resource_limit.h"
@@ -382,9 +383,9 @@ static Result ConnectToNamedPort(Core::System& system, Handle* out, VAddr port_n
// Create a session.
KClientSession* session{};
- R_TRY(port->CreateSession(std::addressof(session),
- std::make_shared<SessionRequestManager>(kernel)));
- port->Close();
+ R_TRY(port->CreateSession(std::addressof(session)));
+
+ kernel.RegisterNamedServiceHandler(port_name, &port->GetParent()->GetServerPort());
// Register the session in the table, close the extra reference.
handle_table.Register(*out, session);
@@ -2246,7 +2247,7 @@ static u64 GetSystemTick(Core::System& system) {
auto& core_timing = system.CoreTiming();
// Returns the value of cntpct_el0 (https://switchbrew.org/wiki/SVC#svcGetSystemTick)
- const u64 result{system.CoreTiming().GetClockTicks()};
+ const u64 result{core_timing.GetClockTicks()};
if (!system.Kernel().IsMulticore()) {
core_timing.AddTicks(400U);
diff --git a/src/core/hle/kernel/svc_results.h b/src/core/hle/kernel/svc_results.h
index f27cade33..b7ca53085 100644
--- a/src/core/hle/kernel/svc_results.h
+++ b/src/core/hle/kernel/svc_results.h
@@ -37,6 +37,7 @@ constexpr Result ResultInvalidState{ErrorModule::Kernel, 125};
constexpr Result ResultReservedUsed{ErrorModule::Kernel, 126};
constexpr Result ResultPortClosed{ErrorModule::Kernel, 131};
constexpr Result ResultLimitReached{ErrorModule::Kernel, 132};
+constexpr Result ResultOutOfAddressSpace{ErrorModule::Kernel, 259};
constexpr Result ResultInvalidId{ErrorModule::Kernel, 519};
} // namespace Kernel
diff --git a/src/core/hle/kernel/svc_types.h b/src/core/hle/kernel/svc_types.h
index abb9847fe..9b0305552 100644
--- a/src/core/hle/kernel/svc_types.h
+++ b/src/core/hle/kernel/svc_types.h
@@ -22,8 +22,8 @@ enum class MemoryState : u32 {
Ipc = 0x0A,
Stack = 0x0B,
ThreadLocal = 0x0C,
- Transferred = 0x0D,
- SharedTransferred = 0x0E,
+ Transfered = 0x0D,
+ SharedTransfered = 0x0E,
SharedCode = 0x0F,
Inaccessible = 0x10,
NonSecureIpc = 0x11,
@@ -32,6 +32,7 @@ enum class MemoryState : u32 {
GeneratedCode = 0x14,
CodeOut = 0x15,
Coverage = 0x16,
+ Insecure = 0x17,
};
DECLARE_ENUM_FLAG_OPERATORS(MemoryState);
@@ -83,6 +84,13 @@ enum class YieldType : s64 {
ToAnyThread = -2,
};
+enum class ThreadExitReason : u32 {
+ ExitThread = 0,
+ TerminateThread = 1,
+ ExitProcess = 2,
+ TerminateProcess = 3,
+};
+
enum class ThreadActivity : u32 {
Runnable = 0,
Paused = 1,
@@ -108,6 +116,34 @@ enum class ProcessState : u32 {
DebugBreak = 7,
};
+enum class ProcessExitReason : u32 {
+ ExitProcess = 0,
+ TerminateProcess = 1,
+ Exception = 2,
+};
+
constexpr inline size_t ThreadLocalRegionSize = 0x200;
+// Debug types.
+enum class DebugEvent : u32 {
+ CreateProcess = 0,
+ CreateThread = 1,
+ ExitProcess = 2,
+ ExitThread = 3,
+ Exception = 4,
+};
+
+enum class DebugException : u32 {
+ UndefinedInstruction = 0,
+ InstructionAbort = 1,
+ DataAbort = 2,
+ AlignmentFault = 3,
+ DebuggerAttached = 4,
+ BreakPoint = 5,
+ UserBreak = 6,
+ DebuggerBreak = 7,
+ UndefinedSystemCall = 8,
+ MemorySystemError = 9,
+};
+
} // namespace Kernel::Svc
diff --git a/src/core/hle/result.h b/src/core/hle/result.h
index ef4b2d417..56c990728 100644
--- a/src/core/hle/result.h
+++ b/src/core/hle/result.h
@@ -423,16 +423,17 @@ constexpr void UpdateCurrentResultReference<const Result>(Result result_referenc
} // namespace ResultImpl
#define DECLARE_CURRENT_RESULT_REFERENCE_AND_STORAGE(COUNTER_VALUE) \
- [[maybe_unused]] constexpr bool HasPrevRef_##COUNTER_VALUE = \
+ [[maybe_unused]] constexpr bool CONCAT2(HasPrevRef_, COUNTER_VALUE) = \
std::same_as<decltype(__TmpCurrentResultReference), Result&>; \
- [[maybe_unused]] auto& PrevRef_##COUNTER_VALUE = __TmpCurrentResultReference; \
- [[maybe_unused]] Result __tmp_result_##COUNTER_VALUE = ResultSuccess; \
- Result& __TmpCurrentResultReference = \
- HasPrevRef_##COUNTER_VALUE ? PrevRef_##COUNTER_VALUE : __tmp_result_##COUNTER_VALUE
+ [[maybe_unused]] Result CONCAT2(PrevRef_, COUNTER_VALUE) = __TmpCurrentResultReference; \
+ [[maybe_unused]] Result CONCAT2(__tmp_result_, COUNTER_VALUE) = ResultSuccess; \
+ Result& __TmpCurrentResultReference = CONCAT2(HasPrevRef_, COUNTER_VALUE) \
+ ? CONCAT2(PrevRef_, COUNTER_VALUE) \
+ : CONCAT2(__tmp_result_, COUNTER_VALUE)
#define ON_RESULT_RETURN_IMPL(...) \
static_assert(std::same_as<decltype(__TmpCurrentResultReference), Result&>); \
- auto RESULT_GUARD_STATE_##__COUNTER__ = \
+ auto CONCAT2(RESULT_GUARD_STATE_, __COUNTER__) = \
ResultImpl::ResultReferenceForScopedResultGuard<__VA_ARGS__>( \
__TmpCurrentResultReference) + \
[&]()
diff --git a/src/core/hle/service/acc/acc.cpp b/src/core/hle/service/acc/acc.cpp
index bb838e285..85a3f0802 100644
--- a/src/core/hle/service/acc/acc.cpp
+++ b/src/core/hle/service/acc/acc.cpp
@@ -512,10 +512,11 @@ protected:
class IManagerForApplication final : public ServiceFramework<IManagerForApplication> {
public:
- explicit IManagerForApplication(Core::System& system_, Common::UUID user_id_)
+ explicit IManagerForApplication(Core::System& system_,
+ const std::shared_ptr<ProfileManager>& profile_manager_)
: ServiceFramework{system_, "IManagerForApplication"},
ensure_token_id{std::make_shared<EnsureTokenIdCacheAsyncInterface>(system)},
- user_id{user_id_} {
+ profile_manager{profile_manager_} {
// clang-format off
static const FunctionInfo functions[] = {
{0, &IManagerForApplication::CheckAvailability, "CheckAvailability"},
@@ -545,7 +546,7 @@ private:
IPC::ResponseBuilder rb{ctx, 4};
rb.Push(ResultSuccess);
- rb.PushRaw<u64>(user_id.Hash());
+ rb.PushRaw<u64>(profile_manager->GetLastOpenedUser().Hash());
}
void EnsureIdTokenCacheAsync(Kernel::HLERequestContext& ctx) {
@@ -575,17 +576,20 @@ private:
IPC::ResponseBuilder rb{ctx, 4};
rb.Push(ResultSuccess);
- rb.PushRaw<u64>(user_id.Hash());
+ rb.PushRaw<u64>(profile_manager->GetLastOpenedUser().Hash());
}
void StoreOpenContext(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service_ACC, "(STUBBED) called");
+ LOG_DEBUG(Service_ACC, "called");
+
+ profile_manager->StoreOpenedUsers();
+
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ResultSuccess);
}
std::shared_ptr<EnsureTokenIdCacheAsyncInterface> ensure_token_id{};
- Common::UUID user_id{};
+ std::shared_ptr<ProfileManager> profile_manager;
};
// 6.0.0+
@@ -790,7 +794,7 @@ void Module::Interface::GetBaasAccountManagerForApplication(Kernel::HLERequestCo
LOG_DEBUG(Service_ACC, "called");
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(ResultSuccess);
- rb.PushIpcInterface<IManagerForApplication>(system, profile_manager->GetLastOpenedUser());
+ rb.PushIpcInterface<IManagerForApplication>(system, profile_manager);
}
void Module::Interface::IsUserAccountSwitchLocked(Kernel::HLERequestContext& ctx) {
@@ -849,22 +853,10 @@ void Module::Interface::ListQualifiedUsers(Kernel::HLERequestContext& ctx) {
rb.Push(ResultSuccess);
}
-void Module::Interface::LoadOpenContext(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service_ACC, "(STUBBED) called");
-
- // This is similar to GetBaasAccountManagerForApplication
- // This command is used concurrently with ListOpenContextStoredUsers
- // TODO: Find the differences between this and GetBaasAccountManagerForApplication
- IPC::ResponseBuilder rb{ctx, 2, 0, 1};
- rb.Push(ResultSuccess);
- rb.PushIpcInterface<IManagerForApplication>(system, profile_manager->GetLastOpenedUser());
-}
-
void Module::Interface::ListOpenContextStoredUsers(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service_ACC, "(STUBBED) called");
+ LOG_DEBUG(Service_ACC, "called");
- // TODO(ogniK): Handle open contexts
- ctx.WriteBuffer(profile_manager->GetOpenUsers());
+ ctx.WriteBuffer(profile_manager->GetStoredOpenedUsers());
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ResultSuccess);
}
diff --git a/src/core/hle/service/acc/acc.h b/src/core/hle/service/acc/acc.h
index 1621e7c0a..9411b0b92 100644
--- a/src/core/hle/service/acc/acc.h
+++ b/src/core/hle/service/acc/acc.h
@@ -35,7 +35,6 @@ public:
void InitializeApplicationInfoV2(Kernel::HLERequestContext& ctx);
void GetProfileEditor(Kernel::HLERequestContext& ctx);
void ListQualifiedUsers(Kernel::HLERequestContext& ctx);
- void LoadOpenContext(Kernel::HLERequestContext& ctx);
void ListOpenContextStoredUsers(Kernel::HLERequestContext& ctx);
void StoreSaveDataThumbnailApplication(Kernel::HLERequestContext& ctx);
void StoreSaveDataThumbnailSystem(Kernel::HLERequestContext& ctx);
diff --git a/src/core/hle/service/acc/acc_u0.cpp b/src/core/hle/service/acc/acc_u0.cpp
index 65023b8c2..54844bfe7 100644
--- a/src/core/hle/service/acc/acc_u0.cpp
+++ b/src/core/hle/service/acc/acc_u0.cpp
@@ -28,7 +28,7 @@ ACC_U0::ACC_U0(std::shared_ptr<Module> module_, std::shared_ptr<ProfileManager>
{110, &ACC_U0::StoreSaveDataThumbnailApplication, "StoreSaveDataThumbnail"},
{111, nullptr, "ClearSaveDataThumbnail"},
{120, nullptr, "CreateGuestLoginRequest"},
- {130, &ACC_U0::LoadOpenContext, "LoadOpenContext"}, // 5.0.0+
+ {130, nullptr, "LoadOpenContext"}, // 5.0.0+
{131, &ACC_U0::ListOpenContextStoredUsers, "ListOpenContextStoredUsers"}, // 6.0.0+
{140, &ACC_U0::InitializeApplicationInfoRestricted, "InitializeApplicationInfoRestricted"}, // 6.0.0+
{141, &ACC_U0::ListQualifiedUsers, "ListQualifiedUsers"}, // 6.0.0+
diff --git a/src/core/hle/service/acc/profile_manager.cpp b/src/core/hle/service/acc/profile_manager.cpp
index a58da4d5f..481e0d141 100644
--- a/src/core/hle/service/acc/profile_manager.cpp
+++ b/src/core/hle/service/acc/profile_manager.cpp
@@ -261,6 +261,31 @@ UUID ProfileManager::GetLastOpenedUser() const {
return last_opened_user;
}
+/// Gets the list of stored opened users.
+UserIDArray ProfileManager::GetStoredOpenedUsers() const {
+ UserIDArray output{};
+ std::ranges::transform(stored_opened_profiles, output.begin(), [](const ProfileInfo& p) {
+ if (p.is_open)
+ return p.user_uuid;
+ return Common::InvalidUUID;
+ });
+ std::stable_partition(output.begin(), output.end(),
+ [](const UUID& uuid) { return uuid.IsValid(); });
+ return output;
+}
+
+/// Captures the opened users, which can be queried across process launches with
+/// ListOpenContextStoredUsers.
+void ProfileManager::StoreOpenedUsers() {
+ size_t profile_index{};
+ stored_opened_profiles = {};
+ std::for_each(profiles.begin(), profiles.end(), [&](const auto& profile) {
+ if (profile.is_open) {
+ stored_opened_profiles[profile_index++] = profile;
+ }
+ });
+}
+
/// Return the users profile base and the unknown arbitary data.
bool ProfileManager::GetProfileBaseAndData(std::optional<std::size_t> index, ProfileBase& profile,
UserData& data) const {
diff --git a/src/core/hle/service/acc/profile_manager.h b/src/core/hle/service/acc/profile_manager.h
index 135f7d0d5..993a5a57a 100644
--- a/src/core/hle/service/acc/profile_manager.h
+++ b/src/core/hle/service/acc/profile_manager.h
@@ -86,6 +86,8 @@ public:
UserIDArray GetOpenUsers() const;
UserIDArray GetAllUsers() const;
Common::UUID GetLastOpenedUser() const;
+ UserIDArray GetStoredOpenedUsers() const;
+ void StoreOpenedUsers();
bool CanSystemRegisterUser() const;
@@ -101,6 +103,7 @@ private:
bool RemoveProfileAtIndex(std::size_t index);
std::array<ProfileInfo, MAX_USERS> profiles{};
+ std::array<ProfileInfo, MAX_USERS> stored_opened_profiles{};
std::size_t user_count{};
Common::UUID last_opened_user{};
};
diff --git a/src/core/hle/service/nvdrv/devices/nvmap.cpp b/src/core/hle/service/nvdrv/devices/nvmap.cpp
index 44388655d..fa29db758 100644
--- a/src/core/hle/service/nvdrv/devices/nvmap.cpp
+++ b/src/core/hle/service/nvdrv/devices/nvmap.cpp
@@ -126,10 +126,12 @@ NvResult nvmap::IocAlloc(const std::vector<u8>& input, std::vector<u8>& output)
LOG_CRITICAL(Service_NVDRV, "Object failed to allocate, handle={:08X}", params.handle);
return result;
}
+ bool is_out_io{};
ASSERT(system.CurrentProcess()
->PageTable()
- .LockForMapDeviceAddressSpace(handle_description->address, handle_description->size,
- Kernel::KMemoryPermission::None, true)
+ .LockForMapDeviceAddressSpace(&is_out_io, handle_description->address,
+ handle_description->size,
+ Kernel::KMemoryPermission::None, true, false)
.IsSuccess());
std::memcpy(output.data(), &params, sizeof(params));
return result;
diff --git a/src/core/hle/service/service.cpp b/src/core/hle/service/service.cpp
index 5db6588e4..5ab41c0c4 100644
--- a/src/core/hle/service/service.cpp
+++ b/src/core/hle/service/service.cpp
@@ -99,6 +99,12 @@ ServiceFrameworkBase::ServiceFrameworkBase(Core::System& system_, const char* se
ServiceFrameworkBase::~ServiceFrameworkBase() {
// Wait for other threads to release access before destroying
const auto guard = LockService();
+
+ if (named_port != nullptr) {
+ named_port->GetClientPort().Close();
+ named_port->GetServerPort().Close();
+ named_port = nullptr;
+ }
}
void ServiceFrameworkBase::InstallAsService(SM::ServiceManager& service_manager) {
@@ -113,15 +119,16 @@ void ServiceFrameworkBase::InstallAsService(SM::ServiceManager& service_manager)
Kernel::KClientPort& ServiceFrameworkBase::CreatePort() {
const auto guard = LockService();
- ASSERT(!service_registered);
+ if (named_port == nullptr) {
+ ASSERT(!service_registered);
- auto* port = Kernel::KPort::Create(kernel);
- port->Initialize(max_sessions, false, service_name);
- port->GetServerPort().SetSessionHandler(shared_from_this());
+ named_port = Kernel::KPort::Create(kernel);
+ named_port->Initialize(max_sessions, false, service_name);
- service_registered = true;
+ service_registered = true;
+ }
- return port->GetClientPort();
+ return named_port->GetClientPort();
}
void ServiceFrameworkBase::RegisterHandlersBase(const FunctionInfoBase* functions, std::size_t n) {
@@ -199,7 +206,6 @@ Result ServiceFrameworkBase::HandleSyncRequest(Kernel::KServerSession& session,
switch (ctx.GetCommandType()) {
case IPC::CommandType::Close:
case IPC::CommandType::TIPC_Close: {
- session.Close();
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ResultSuccess);
result = IPC::ERR_REMOTE_PROCESS_DEAD;
@@ -244,6 +250,7 @@ Services::Services(std::shared_ptr<SM::ServiceManager>& sm, Core::System& system
system.GetFileSystemController().CreateFactories(*system.GetFilesystem(), false);
system.Kernel().RegisterNamedService("sm:", SM::ServiceManager::InterfaceFactory);
+ system.Kernel().RegisterInterfaceForNamedService("sm:", SM::ServiceManager::SessionHandler);
Account::InstallInterfaces(system);
AM::InstallInterfaces(*sm, *nv_flinger, system);
diff --git a/src/core/hle/service/service.h b/src/core/hle/service/service.h
index ec9deeee4..22e2119d7 100644
--- a/src/core/hle/service/service.h
+++ b/src/core/hle/service/service.h
@@ -20,6 +20,7 @@ class System;
namespace Kernel {
class HLERequestContext;
class KClientPort;
+class KPort;
class KServerSession;
class ServiceThread;
} // namespace Kernel
@@ -98,6 +99,9 @@ protected:
/// Identifier string used to connect to the service.
std::string service_name;
+ /// Port used by ManageNamedPort.
+ Kernel::KPort* named_port{};
+
private:
template <typename T>
friend class ServiceFramework;
diff --git a/src/core/hle/service/sm/sm.cpp b/src/core/hle/service/sm/sm.cpp
index e2b8d8720..84720094f 100644
--- a/src/core/hle/service/sm/sm.cpp
+++ b/src/core/hle/service/sm/sm.cpp
@@ -23,7 +23,13 @@ constexpr Result ERR_INVALID_NAME(ErrorModule::SM, 6);
constexpr Result ERR_SERVICE_NOT_REGISTERED(ErrorModule::SM, 7);
ServiceManager::ServiceManager(Kernel::KernelCore& kernel_) : kernel{kernel_} {}
-ServiceManager::~ServiceManager() = default;
+
+ServiceManager::~ServiceManager() {
+ for (auto& [name, port] : service_ports) {
+ port->GetClientPort().Close();
+ port->GetServerPort().Close();
+ }
+}
void ServiceManager::InvokeControlRequest(Kernel::HLERequestContext& context) {
controller_interface->InvokeRequest(context);
@@ -43,6 +49,10 @@ Kernel::KClientPort& ServiceManager::InterfaceFactory(ServiceManager& self, Core
return self.sm_interface->CreatePort();
}
+void ServiceManager::SessionHandler(ServiceManager& self, Kernel::KServerPort* server_port) {
+ self.sm_interface->AcceptSession(server_port);
+}
+
Result ServiceManager::RegisterService(std::string name, u32 max_sessions,
Kernel::SessionRequestHandlerPtr handler) {
@@ -53,7 +63,11 @@ Result ServiceManager::RegisterService(std::string name, u32 max_sessions,
return ERR_ALREADY_REGISTERED;
}
- registered_services.emplace(std::move(name), handler);
+ auto* port = Kernel::KPort::Create(kernel);
+ port->Initialize(ServerSessionCountMax, false, name);
+
+ service_ports.emplace(name, port);
+ registered_services.emplace(name, handler);
return ResultSuccess;
}
@@ -68,24 +82,20 @@ Result ServiceManager::UnregisterService(const std::string& name) {
}
registered_services.erase(iter);
+ service_ports.erase(name);
+
return ResultSuccess;
}
ResultVal<Kernel::KPort*> ServiceManager::GetServicePort(const std::string& name) {
CASCADE_CODE(ValidateServiceName(name));
- auto it = registered_services.find(name);
- if (it == registered_services.end()) {
+ auto it = service_ports.find(name);
+ if (it == service_ports.end()) {
LOG_ERROR(Service_SM, "Server is not registered! service={}", name);
return ERR_SERVICE_NOT_REGISTERED;
}
- auto* port = Kernel::KPort::Create(kernel);
-
- port->Initialize(ServerSessionCountMax, false, name);
- auto handler = it->second;
- port->GetServerPort().SetSessionHandler(std::move(handler));
-
- return port;
+ return it->second;
}
/**
@@ -144,23 +154,20 @@ ResultVal<Kernel::KClientSession*> SM::GetServiceImpl(Kernel::HLERequestContext&
// Find the named port.
auto port_result = service_manager.GetServicePort(name);
- if (port_result.Failed()) {
+ auto service = service_manager.GetService<Kernel::SessionRequestHandler>(name);
+ if (port_result.Failed() || !service) {
LOG_ERROR(Service_SM, "called service={} -> error 0x{:08X}", name, port_result.Code().raw);
return port_result.Code();
}
auto& port = port_result.Unwrap();
- SCOPE_EXIT({ port->GetClientPort().Close(); });
-
- kernel.RegisterServerObject(&port->GetServerPort());
// Create a new session.
Kernel::KClientSession* session{};
- if (const auto result = port->GetClientPort().CreateSession(
- std::addressof(session), std::make_shared<Kernel::SessionRequestManager>(kernel));
- result.IsError()) {
+ if (const auto result = port->GetClientPort().CreateSession(&session); result.IsError()) {
LOG_ERROR(Service_SM, "called service={} -> error 0x{:08X}", name, result.raw);
return result;
}
+ service->AcceptSession(&port->GetServerPort());
LOG_DEBUG(Service_SM, "called service={} -> session={}", name, session->GetId());
diff --git a/src/core/hle/service/sm/sm.h b/src/core/hle/service/sm/sm.h
index 878decc6f..02a5dde9e 100644
--- a/src/core/hle/service/sm/sm.h
+++ b/src/core/hle/service/sm/sm.h
@@ -51,6 +51,7 @@ private:
class ServiceManager {
public:
static Kernel::KClientPort& InterfaceFactory(ServiceManager& self, Core::System& system);
+ static void SessionHandler(ServiceManager& self, Kernel::KServerPort* server_port);
explicit ServiceManager(Kernel::KernelCore& kernel_);
~ServiceManager();
@@ -78,6 +79,7 @@ private:
/// Map of registered services, retrieved using GetServicePort.
std::unordered_map<std::string, Kernel::SessionRequestHandlerPtr> registered_services;
+ std::unordered_map<std::string, Kernel::KPort*> service_ports;
/// Kernel context
Kernel::KernelCore& kernel;
diff --git a/src/core/hle/service/sm/sm_controller.cpp b/src/core/hle/service/sm/sm_controller.cpp
index 273f79568..69e0fe808 100644
--- a/src/core/hle/service/sm/sm_controller.cpp
+++ b/src/core/hle/service/sm/sm_controller.cpp
@@ -15,10 +15,9 @@
namespace Service::SM {
void Controller::ConvertCurrentObjectToDomain(Kernel::HLERequestContext& ctx) {
- ASSERT_MSG(!ctx.Session()->GetSessionRequestManager()->IsDomain(),
- "Session is already a domain");
+ ASSERT_MSG(!ctx.GetManager()->IsDomain(), "Session is already a domain");
LOG_DEBUG(Service, "called, server_session={}", ctx.Session()->GetId());
- ctx.Session()->GetSessionRequestManager()->ConvertToDomainOnRequestEnd();
+ ctx.GetManager()->ConvertToDomainOnRequestEnd();
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(ResultSuccess);
@@ -28,23 +27,35 @@ void Controller::ConvertCurrentObjectToDomain(Kernel::HLERequestContext& ctx) {
void Controller::CloneCurrentObject(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service, "called");
- auto& parent_session = *ctx.Session()->GetParent();
- auto& parent_port = parent_session.GetParent()->GetParent()->GetClientPort();
- auto& session_manager = parent_session.GetServerSession().GetSessionRequestManager();
+ auto& process = *ctx.GetThread().GetOwnerProcess();
+ auto session_manager = ctx.GetManager();
- // Create a session.
- Kernel::KClientSession* session{};
- const Result result = parent_port.CreateSession(std::addressof(session), session_manager);
- if (result.IsError()) {
- LOG_CRITICAL(Service, "CreateSession failed with error 0x{:08X}", result.raw);
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(result);
- }
+ // FIXME: this is duplicated from the SVC, it should just call it instead
+ // once this is a proper process
+
+ // Reserve a new session from the process resource limit.
+ Kernel::KScopedResourceReservation session_reservation(&process,
+ Kernel::LimitableResource::Sessions);
+ ASSERT(session_reservation.Succeeded());
+
+ // Create the session.
+ Kernel::KSession* session = Kernel::KSession::Create(system.Kernel());
+ ASSERT(session != nullptr);
+
+ // Initialize the session.
+ session->Initialize(nullptr, "");
+
+ // Commit the session reservation.
+ session_reservation.Commit();
+
+ // Register with manager.
+ session_manager->SessionHandler().RegisterSession(&session->GetServerSession(),
+ session_manager);
// We succeeded.
IPC::ResponseBuilder rb{ctx, 2, 0, 1, IPC::ResponseBuilder::Flags::AlwaysMoveHandles};
rb.Push(ResultSuccess);
- rb.PushMoveObjects(session);
+ rb.PushMoveObjects(session->GetClientSession());
}
void Controller::CloneCurrentObjectEx(Kernel::HLERequestContext& ctx) {
diff --git a/src/core/internal_network/socket_proxy.cpp b/src/core/internal_network/socket_proxy.cpp
index 7d5d37bbc..1e1c42cea 100644
--- a/src/core/internal_network/socket_proxy.cpp
+++ b/src/core/internal_network/socket_proxy.cpp
@@ -11,6 +11,10 @@
#include "core/internal_network/network_interface.h"
#include "core/internal_network/socket_proxy.h"
+#if YUZU_UNIX
+#include <sys/socket.h>
+#endif
+
namespace Network {
ProxySocket::ProxySocket(RoomNetwork& room_network_) noexcept : room_network{room_network_} {}
diff --git a/src/shader_recompiler/CMakeLists.txt b/src/shader_recompiler/CMakeLists.txt
index bcdd60db9..545d69c7e 100644
--- a/src/shader_recompiler/CMakeLists.txt
+++ b/src/shader_recompiler/CMakeLists.txt
@@ -224,6 +224,7 @@ add_library(shader_recompiler STATIC
ir_opt/lower_fp16_to_fp32.cpp
ir_opt/lower_int64_to_int32.cpp
ir_opt/passes.h
+ ir_opt/position_pass.cpp
ir_opt/rescaling_pass.cpp
ir_opt/ssa_rewrite_pass.cpp
ir_opt/texture_pass.cpp
diff --git a/src/shader_recompiler/backend/glasm/emit_glasm.cpp b/src/shader_recompiler/backend/glasm/emit_glasm.cpp
index 01f9abc71..3b0176bf6 100644
--- a/src/shader_recompiler/backend/glasm/emit_glasm.cpp
+++ b/src/shader_recompiler/backend/glasm/emit_glasm.cpp
@@ -450,6 +450,9 @@ std::string EmitGLASM(const Profile& profile, const RuntimeInfo& runtime_info, I
if (program.info.uses_rescaling_uniform) {
header += "PARAM scaling[1]={program.local[0..0]};";
}
+ if (program.info.uses_render_area) {
+ header += "PARAM render_area[1]={program.local[1..1]};";
+ }
header += "TEMP ";
for (size_t index = 0; index < ctx.reg_alloc.NumUsedRegisters(); ++index) {
header += fmt::format("R{},", index);
diff --git a/src/shader_recompiler/backend/glasm/emit_glasm_bitwise_conversion.cpp b/src/shader_recompiler/backend/glasm/emit_glasm_bitwise_conversion.cpp
index 2fc2a0ac6..5bfdecc09 100644
--- a/src/shader_recompiler/backend/glasm/emit_glasm_bitwise_conversion.cpp
+++ b/src/shader_recompiler/backend/glasm/emit_glasm_bitwise_conversion.cpp
@@ -43,6 +43,10 @@ void EmitBitCastU64F64(EmitContext&, IR::Inst& inst, const IR::Value& value) {
Alias(inst, value);
}
+void EmitBitCastS32F32(EmitContext&, IR::Inst& inst, const IR::Value& value) {
+ Alias(inst, value);
+}
+
void EmitBitCastF16U16(EmitContext&, IR::Inst& inst, const IR::Value& value) {
Alias(inst, value);
}
diff --git a/src/shader_recompiler/backend/glasm/emit_glasm_context_get_set.cpp b/src/shader_recompiler/backend/glasm/emit_glasm_context_get_set.cpp
index 7e8f37563..0a7d42dda 100644
--- a/src/shader_recompiler/backend/glasm/emit_glasm_context_get_set.cpp
+++ b/src/shader_recompiler/backend/glasm/emit_glasm_context_get_set.cpp
@@ -396,6 +396,10 @@ void EmitResolutionDownFactor(EmitContext& ctx, IR::Inst& inst) {
ctx.Add("MOV.F {}.x,scaling[0].z;", inst);
}
+void EmitRenderArea(EmitContext& ctx, IR::Inst& inst) {
+ ctx.Add("MOV.F {},render_area[0];", inst);
+}
+
void EmitLoadLocal(EmitContext& ctx, IR::Inst& inst, ScalarU32 word_offset) {
ctx.Add("MOV.U {},lmem[{}].x;", inst, word_offset);
}
diff --git a/src/shader_recompiler/backend/glasm/emit_glasm_instructions.h b/src/shader_recompiler/backend/glasm/emit_glasm_instructions.h
index 8b0ac3031..d645fd532 100644
--- a/src/shader_recompiler/backend/glasm/emit_glasm_instructions.h
+++ b/src/shader_recompiler/backend/glasm/emit_glasm_instructions.h
@@ -73,6 +73,7 @@ void EmitSampleId(EmitContext& ctx, IR::Inst& inst);
void EmitIsHelperInvocation(EmitContext& ctx, IR::Inst& inst);
void EmitYDirection(EmitContext& ctx, IR::Inst& inst);
void EmitResolutionDownFactor(EmitContext& ctx, IR::Inst& inst);
+void EmitRenderArea(EmitContext& ctx, IR::Inst& inst);
void EmitLoadLocal(EmitContext& ctx, IR::Inst& inst, ScalarU32 word_offset);
void EmitWriteLocal(EmitContext& ctx, ScalarU32 word_offset, ScalarU32 value);
void EmitUndefU1(EmitContext& ctx, IR::Inst& inst);
@@ -195,6 +196,7 @@ void EmitSelectF64(EmitContext& ctx, ScalarS32 cond, Register true_value, Regist
void EmitBitCastU16F16(EmitContext& ctx, IR::Inst& inst, const IR::Value& value);
void EmitBitCastU32F32(EmitContext& ctx, IR::Inst& inst, const IR::Value& value);
void EmitBitCastU64F64(EmitContext& ctx, IR::Inst& inst, const IR::Value& value);
+void EmitBitCastS32F32(EmitContext& ctx, IR::Inst& inst, const IR::Value& value);
void EmitBitCastF16U16(EmitContext& ctx, IR::Inst& inst, const IR::Value& value);
void EmitBitCastF32U32(EmitContext& ctx, IR::Inst& inst, const IR::Value& value);
void EmitBitCastF64U64(EmitContext& ctx, IR::Inst& inst, const IR::Value& value);
diff --git a/src/shader_recompiler/backend/glsl/emit_glsl_bitwise_conversion.cpp b/src/shader_recompiler/backend/glsl/emit_glsl_bitwise_conversion.cpp
index 1be4a0f59..8e5e6cf1f 100644
--- a/src/shader_recompiler/backend/glsl/emit_glsl_bitwise_conversion.cpp
+++ b/src/shader_recompiler/backend/glsl/emit_glsl_bitwise_conversion.cpp
@@ -48,6 +48,10 @@ void EmitBitCastU64F64(EmitContext& ctx, IR::Inst& inst, std::string_view value)
ctx.AddU64("{}=doubleBitsToUint64({});", inst, value);
}
+void EmitBitCastS32F32(EmitContext& ctx, IR::Inst& inst, std::string_view value) {
+ ctx.AddF32("{}=ftoi({});", inst, value);
+}
+
void EmitBitCastF16U16([[maybe_unused]] EmitContext& ctx, [[maybe_unused]] IR::Inst& inst) {
NotImplemented();
}
diff --git a/src/shader_recompiler/backend/glsl/emit_glsl_context_get_set.cpp b/src/shader_recompiler/backend/glsl/emit_glsl_context_get_set.cpp
index fad8d1e30..d7c845469 100644
--- a/src/shader_recompiler/backend/glsl/emit_glsl_context_get_set.cpp
+++ b/src/shader_recompiler/backend/glsl/emit_glsl_context_get_set.cpp
@@ -416,6 +416,10 @@ void EmitResolutionDownFactor(EmitContext& ctx, IR::Inst& inst) {
ctx.AddF32("{}=scaling.z;", inst);
}
+void EmitRenderArea(EmitContext& ctx, IR::Inst& inst) {
+ ctx.AddF32x4("{}=render_area;", inst);
+}
+
void EmitLoadLocal(EmitContext& ctx, IR::Inst& inst, std::string_view word_offset) {
ctx.AddU32("{}=lmem[{}];", inst, word_offset);
}
diff --git a/src/shader_recompiler/backend/glsl/emit_glsl_instructions.h b/src/shader_recompiler/backend/glsl/emit_glsl_instructions.h
index 639691ba6..96e683b5e 100644
--- a/src/shader_recompiler/backend/glsl/emit_glsl_instructions.h
+++ b/src/shader_recompiler/backend/glsl/emit_glsl_instructions.h
@@ -87,6 +87,7 @@ void EmitSampleId(EmitContext& ctx, IR::Inst& inst);
void EmitIsHelperInvocation(EmitContext& ctx, IR::Inst& inst);
void EmitYDirection(EmitContext& ctx, IR::Inst& inst);
void EmitResolutionDownFactor(EmitContext& ctx, IR::Inst& inst);
+void EmitRenderArea(EmitContext& ctx, IR::Inst& inst);
void EmitLoadLocal(EmitContext& ctx, IR::Inst& inst, std::string_view word_offset);
void EmitWriteLocal(EmitContext& ctx, std::string_view word_offset, std::string_view value);
void EmitUndefU1(EmitContext& ctx, IR::Inst& inst);
@@ -229,6 +230,7 @@ void EmitSelectF64(EmitContext& ctx, IR::Inst& inst, std::string_view cond,
void EmitBitCastU16F16(EmitContext& ctx, IR::Inst& inst);
void EmitBitCastU32F32(EmitContext& ctx, IR::Inst& inst, std::string_view value);
void EmitBitCastU64F64(EmitContext& ctx, IR::Inst& inst, std::string_view value);
+void EmitBitCastS32F32(EmitContext& ctx, IR::Inst& inst, std::string_view value);
void EmitBitCastF16U16(EmitContext& ctx, IR::Inst& inst);
void EmitBitCastF32U32(EmitContext& ctx, IR::Inst& inst, std::string_view value);
void EmitBitCastF64U64(EmitContext& ctx, IR::Inst& inst, std::string_view value);
diff --git a/src/shader_recompiler/backend/glsl/glsl_emit_context.cpp b/src/shader_recompiler/backend/glsl/glsl_emit_context.cpp
index c767a9dc3..5d01ec0cd 100644
--- a/src/shader_recompiler/backend/glsl/glsl_emit_context.cpp
+++ b/src/shader_recompiler/backend/glsl/glsl_emit_context.cpp
@@ -358,6 +358,9 @@ EmitContext::EmitContext(IR::Program& program, Bindings& bindings, const Profile
if (info.uses_rescaling_uniform) {
header += "layout(location=0) uniform vec4 scaling;";
}
+ if (info.uses_render_area) {
+ header += "layout(location=1) uniform vec4 render_area;";
+ }
DefineConstantBuffers(bindings);
DefineConstantBufferIndirect();
DefineStorageBuffers(bindings);
diff --git a/src/shader_recompiler/backend/spirv/emit_spirv.h b/src/shader_recompiler/backend/spirv/emit_spirv.h
index 7567b6fc9..937881484 100644
--- a/src/shader_recompiler/backend/spirv/emit_spirv.h
+++ b/src/shader_recompiler/backend/spirv/emit_spirv.h
@@ -23,8 +23,12 @@ struct RescalingLayout {
alignas(16) std::array<u32, NUM_IMAGE_SCALING_WORDS> rescaling_images;
u32 down_factor;
};
+struct RenderAreaLayout {
+ std::array<f32, 4> render_area;
+};
constexpr u32 RESCALING_LAYOUT_WORDS_OFFSET = offsetof(RescalingLayout, rescaling_textures);
constexpr u32 RESCALING_LAYOUT_DOWN_FACTOR_OFFSET = offsetof(RescalingLayout, down_factor);
+constexpr u32 RENDERAREA_LAYOUT_OFFSET = offsetof(RenderAreaLayout, render_area);
[[nodiscard]] std::vector<u32> EmitSPIRV(const Profile& profile, const RuntimeInfo& runtime_info,
IR::Program& program, Bindings& bindings);
diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_bitwise_conversion.cpp b/src/shader_recompiler/backend/spirv/emit_spirv_bitwise_conversion.cpp
index c4ca28d11..50daacd95 100644
--- a/src/shader_recompiler/backend/spirv/emit_spirv_bitwise_conversion.cpp
+++ b/src/shader_recompiler/backend/spirv/emit_spirv_bitwise_conversion.cpp
@@ -18,6 +18,10 @@ void EmitBitCastU64F64(EmitContext&) {
throw NotImplementedException("SPIR-V Instruction");
}
+void EmitBitCastS32F32(EmitContext&) {
+ throw NotImplementedException("SPIR-V Instruction");
+}
+
void EmitBitCastF16U16(EmitContext&) {
throw NotImplementedException("SPIR-V Instruction");
}
diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_context_get_set.cpp b/src/shader_recompiler/backend/spirv/emit_spirv_context_get_set.cpp
index 2c68aba39..a4751b42d 100644
--- a/src/shader_recompiler/backend/spirv/emit_spirv_context_get_set.cpp
+++ b/src/shader_recompiler/backend/spirv/emit_spirv_context_get_set.cpp
@@ -353,7 +353,6 @@ Id EmitGetAttribute(EmitContext& ctx, IR::Attribute attr, Id vertex) {
case IR::Attribute::TessellationEvaluationPointV:
return ctx.OpLoad(ctx.F32[1],
ctx.OpAccessChain(ctx.input_f32, ctx.tess_coord, ctx.Const(1U)));
-
default:
throw NotImplementedException("Read attribute {}", attr);
}
@@ -537,6 +536,17 @@ Id EmitResolutionDownFactor(EmitContext& ctx) {
}
}
+Id EmitRenderArea(EmitContext& ctx) {
+ if (ctx.profile.unified_descriptor_binding) {
+ const Id pointer_type{ctx.TypePointer(spv::StorageClass::PushConstant, ctx.F32[4])};
+ const Id index{ctx.Const(ctx.render_are_member_index)};
+ const Id pointer{ctx.OpAccessChain(pointer_type, ctx.render_area_push_constant, index)};
+ return ctx.OpLoad(ctx.F32[4], pointer);
+ } else {
+ throw NotImplementedException("SPIR-V Instruction");
+ }
+}
+
Id EmitLoadLocal(EmitContext& ctx, Id word_offset) {
const Id pointer{ctx.OpAccessChain(ctx.private_u32, ctx.local_memory, word_offset)};
return ctx.OpLoad(ctx.U32[1], pointer);
diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_instructions.h b/src/shader_recompiler/backend/spirv/emit_spirv_instructions.h
index 984d072b4..7070c8fda 100644
--- a/src/shader_recompiler/backend/spirv/emit_spirv_instructions.h
+++ b/src/shader_recompiler/backend/spirv/emit_spirv_instructions.h
@@ -76,6 +76,7 @@ Id EmitSampleId(EmitContext& ctx);
Id EmitIsHelperInvocation(EmitContext& ctx);
Id EmitYDirection(EmitContext& ctx);
Id EmitResolutionDownFactor(EmitContext& ctx);
+Id EmitRenderArea(EmitContext& ctx);
Id EmitLoadLocal(EmitContext& ctx, Id word_offset);
void EmitWriteLocal(EmitContext& ctx, Id word_offset, Id value);
Id EmitUndefU1(EmitContext& ctx);
@@ -177,7 +178,8 @@ Id EmitSelectF64(EmitContext& ctx, Id cond, Id true_value, Id false_value);
void EmitBitCastU16F16(EmitContext& ctx);
Id EmitBitCastU32F32(EmitContext& ctx, Id value);
void EmitBitCastU64F64(EmitContext& ctx);
-void EmitBitCastF16U16(EmitContext& ctx);
+void EmitBitCastS32F32(EmitContext& ctx);
+void EmitBitCastF16U16(EmitContext&);
Id EmitBitCastF32U32(EmitContext& ctx, Id value);
void EmitBitCastF64U64(EmitContext& ctx);
Id EmitPackUint2x32(EmitContext& ctx, Id value);
diff --git a/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp b/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp
index aecc4c612..c26ad8f93 100644
--- a/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp
+++ b/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp
@@ -473,6 +473,7 @@ EmitContext::EmitContext(const Profile& profile_, const RuntimeInfo& runtime_inf
DefineAttributeMemAccess(program.info);
DefineGlobalMemoryFunctions(program.info);
DefineRescalingInput(program.info);
+ DefineRenderArea(program.info);
}
EmitContext::~EmitContext() = default;
@@ -982,6 +983,36 @@ void EmitContext::DefineRescalingInputUniformConstant() {
}
}
+void EmitContext::DefineRenderArea(const Info& info) {
+ if (!info.uses_render_area) {
+ return;
+ }
+
+ if (profile.unified_descriptor_binding) {
+ boost::container::static_vector<Id, 1> members{};
+ u32 member_index{0};
+
+ members.push_back(F32[4]);
+ render_are_member_index = member_index++;
+
+ const Id push_constant_struct{TypeStruct(std::span(members.data(), members.size()))};
+ Decorate(push_constant_struct, spv::Decoration::Block);
+ Name(push_constant_struct, "RenderAreaInfo");
+
+ MemberDecorate(push_constant_struct, render_are_member_index, spv::Decoration::Offset, 0);
+ MemberName(push_constant_struct, render_are_member_index, "render_area");
+
+ const Id pointer_type{TypePointer(spv::StorageClass::PushConstant, push_constant_struct)};
+ render_area_push_constant =
+ AddGlobalVariable(pointer_type, spv::StorageClass::PushConstant);
+ Name(render_area_push_constant, "render_area_push_constants");
+
+ if (profile.supported_spirv >= 0x00010400) {
+ interfaces.push_back(render_area_push_constant);
+ }
+ }
+}
+
void EmitContext::DefineConstantBuffers(const Info& info, u32& binding) {
if (info.constant_buffer_descriptors.empty()) {
return;
diff --git a/src/shader_recompiler/backend/spirv/spirv_emit_context.h b/src/shader_recompiler/backend/spirv/spirv_emit_context.h
index bc25b8b84..c86e50911 100644
--- a/src/shader_recompiler/backend/spirv/spirv_emit_context.h
+++ b/src/shader_recompiler/backend/spirv/spirv_emit_context.h
@@ -243,6 +243,9 @@ public:
u32 texture_rescaling_index{};
u32 image_rescaling_index{};
+ Id render_area_push_constant{};
+ u32 render_are_member_index{};
+
Id local_memory{};
Id shared_memory_u8{};
@@ -318,6 +321,7 @@ private:
void DefineRescalingInput(const Info& info);
void DefineRescalingInputPushConstant();
void DefineRescalingInputUniformConstant();
+ void DefineRenderArea(const Info& info);
void DefineInputs(const IR::Program& program);
void DefineOutputs(const IR::Program& program);
diff --git a/src/shader_recompiler/environment.h b/src/shader_recompiler/environment.h
index 9729d48c6..402f2664f 100644
--- a/src/shader_recompiler/environment.h
+++ b/src/shader_recompiler/environment.h
@@ -22,6 +22,10 @@ public:
[[nodiscard]] virtual TextureType ReadTextureType(u32 raw_handle) = 0;
+ [[nodiscard]] virtual TexturePixelFormat ReadTexturePixelFormat(u32 raw_handle) = 0;
+
+ [[nodiscard]] virtual u32 ReadViewportTransformState() = 0;
+
[[nodiscard]] virtual u32 TextureBoundBuffer() const = 0;
[[nodiscard]] virtual u32 LocalMemorySize() const = 0;
diff --git a/src/shader_recompiler/frontend/ir/ir_emitter.cpp b/src/shader_recompiler/frontend/ir/ir_emitter.cpp
index 11086ed8c..d4425f06d 100644
--- a/src/shader_recompiler/frontend/ir/ir_emitter.cpp
+++ b/src/shader_recompiler/frontend/ir/ir_emitter.cpp
@@ -378,6 +378,14 @@ F32 IREmitter::ResolutionDownFactor() {
return Inst<F32>(Opcode::ResolutionDownFactor);
}
+F32 IREmitter::RenderAreaWidth() {
+ return F32(CompositeExtract(Inst<Value>(Opcode::RenderArea), 0));
+}
+
+F32 IREmitter::RenderAreaHeight() {
+ return F32(CompositeExtract(Inst<Value>(Opcode::RenderArea), 1));
+}
+
U32 IREmitter::LaneId() {
return Inst<U32>(Opcode::LaneId);
}
@@ -684,6 +692,11 @@ IR::U32 IREmitter::BitCast<IR::U32, IR::F32>(const IR::F32& value) {
}
template <>
+IR::S32 IREmitter::BitCast<IR::S32, IR::F32>(const IR::F32& value) {
+ return Inst<IR::S32>(Opcode::BitCastS32F32, value);
+}
+
+template <>
IR::F32 IREmitter::BitCast<IR::F32, IR::U32>(const IR::U32& value) {
return Inst<IR::F32>(Opcode::BitCastF32U32, value);
}
diff --git a/src/shader_recompiler/frontend/ir/ir_emitter.h b/src/shader_recompiler/frontend/ir/ir_emitter.h
index 25839a371..f163c18d9 100644
--- a/src/shader_recompiler/frontend/ir/ir_emitter.h
+++ b/src/shader_recompiler/frontend/ir/ir_emitter.h
@@ -103,6 +103,9 @@ public:
[[nodiscard]] F32 ResolutionDownFactor();
+ [[nodiscard]] F32 RenderAreaWidth();
+ [[nodiscard]] F32 RenderAreaHeight();
+
[[nodiscard]] U32 LaneId();
[[nodiscard]] U32 LoadGlobalU8(const U64& address);
diff --git a/src/shader_recompiler/frontend/ir/opcodes.h b/src/shader_recompiler/frontend/ir/opcodes.h
index 752879a18..e70d7745c 100644
--- a/src/shader_recompiler/frontend/ir/opcodes.h
+++ b/src/shader_recompiler/frontend/ir/opcodes.h
@@ -37,6 +37,7 @@ constexpr Type U8{Type::U8};
constexpr Type U16{Type::U16};
constexpr Type U32{Type::U32};
constexpr Type U64{Type::U64};
+constexpr Type S32{Type::S32};
constexpr Type F16{Type::F16};
constexpr Type F32{Type::F32};
constexpr Type F64{Type::F64};
diff --git a/src/shader_recompiler/frontend/ir/opcodes.inc b/src/shader_recompiler/frontend/ir/opcodes.inc
index 86410ddfc..88aa077ee 100644
--- a/src/shader_recompiler/frontend/ir/opcodes.inc
+++ b/src/shader_recompiler/frontend/ir/opcodes.inc
@@ -63,6 +63,7 @@ OPCODE(SampleId, U32,
OPCODE(IsHelperInvocation, U1, )
OPCODE(YDirection, F32, )
OPCODE(ResolutionDownFactor, F32, )
+OPCODE(RenderArea, F32x4, )
// Undefined
OPCODE(UndefU1, U1, )
@@ -173,6 +174,7 @@ OPCODE(SelectF64, F64, U1,
OPCODE(BitCastU16F16, U16, F16, )
OPCODE(BitCastU32F32, U32, F32, )
OPCODE(BitCastU64F64, U64, F64, )
+OPCODE(BitCastS32F32, S32, F32, )
OPCODE(BitCastF16U16, F16, U16, )
OPCODE(BitCastF32U32, F32, U32, )
OPCODE(BitCastF64U64, F64, U64, )
diff --git a/src/shader_recompiler/frontend/ir/type.h b/src/shader_recompiler/frontend/ir/type.h
index 04c8c4ddb..5a7c706ad 100644
--- a/src/shader_recompiler/frontend/ir/type.h
+++ b/src/shader_recompiler/frontend/ir/type.h
@@ -24,21 +24,22 @@ enum class Type {
U16 = 1 << 7,
U32 = 1 << 8,
U64 = 1 << 9,
- F16 = 1 << 10,
- F32 = 1 << 11,
- F64 = 1 << 12,
- U32x2 = 1 << 13,
- U32x3 = 1 << 14,
- U32x4 = 1 << 15,
- F16x2 = 1 << 16,
- F16x3 = 1 << 17,
- F16x4 = 1 << 18,
- F32x2 = 1 << 19,
- F32x3 = 1 << 20,
- F32x4 = 1 << 21,
- F64x2 = 1 << 22,
- F64x3 = 1 << 23,
- F64x4 = 1 << 24,
+ S32 = 1 << 10,
+ F16 = 1 << 11,
+ F32 = 1 << 12,
+ F64 = 1 << 13,
+ U32x2 = 1 << 14,
+ U32x3 = 1 << 15,
+ U32x4 = 1 << 16,
+ F16x2 = 1 << 17,
+ F16x3 = 1 << 18,
+ F16x4 = 1 << 19,
+ F32x2 = 1 << 20,
+ F32x3 = 1 << 21,
+ F32x4 = 1 << 22,
+ F64x2 = 1 << 23,
+ F64x3 = 1 << 24,
+ F64x4 = 1 << 25,
};
DECLARE_ENUM_FLAG_OPERATORS(Type)
diff --git a/src/shader_recompiler/frontend/ir/value.cpp b/src/shader_recompiler/frontend/ir/value.cpp
index 346169328..30ba12316 100644
--- a/src/shader_recompiler/frontend/ir/value.cpp
+++ b/src/shader_recompiler/frontend/ir/value.cpp
@@ -23,6 +23,8 @@ Value::Value(u16 value) noexcept : type{Type::U16}, imm_u16{value} {}
Value::Value(u32 value) noexcept : type{Type::U32}, imm_u32{value} {}
+Value::Value(s32 value) noexcept : type{Type::S32}, imm_s32{value} {}
+
Value::Value(f32 value) noexcept : type{Type::F32}, imm_f32{value} {}
Value::Value(u64 value) noexcept : type{Type::U64}, imm_u64{value} {}
@@ -69,6 +71,7 @@ bool Value::operator==(const Value& other) const {
return imm_u16 == other.imm_u16;
case Type::U32:
case Type::F32:
+ case Type::S32:
return imm_u32 == other.imm_u32;
case Type::U64:
case Type::F64:
diff --git a/src/shader_recompiler/frontend/ir/value.h b/src/shader_recompiler/frontend/ir/value.h
index 6a673ca05..e8bbb93a5 100644
--- a/src/shader_recompiler/frontend/ir/value.h
+++ b/src/shader_recompiler/frontend/ir/value.h
@@ -44,6 +44,7 @@ public:
explicit Value(u8 value) noexcept;
explicit Value(u16 value) noexcept;
explicit Value(u32 value) noexcept;
+ explicit Value(s32 value) noexcept;
explicit Value(f32 value) noexcept;
explicit Value(u64 value) noexcept;
explicit Value(f64 value) noexcept;
@@ -66,6 +67,7 @@ public:
[[nodiscard]] u8 U8() const;
[[nodiscard]] u16 U16() const;
[[nodiscard]] u32 U32() const;
+ [[nodiscard]] s32 S32() const;
[[nodiscard]] f32 F32() const;
[[nodiscard]] u64 U64() const;
[[nodiscard]] f64 F64() const;
@@ -85,6 +87,7 @@ private:
u8 imm_u8;
u16 imm_u16;
u32 imm_u32;
+ s32 imm_s32;
f32 imm_f32;
u64 imm_u64;
f64 imm_f64;
@@ -266,6 +269,7 @@ using U8 = TypedValue<Type::U8>;
using U16 = TypedValue<Type::U16>;
using U32 = TypedValue<Type::U32>;
using U64 = TypedValue<Type::U64>;
+using S32 = TypedValue<Type::S32>;
using F16 = TypedValue<Type::F16>;
using F32 = TypedValue<Type::F32>;
using F64 = TypedValue<Type::F64>;
@@ -377,6 +381,14 @@ inline u32 Value::U32() const {
return imm_u32;
}
+inline s32 Value::S32() const {
+ if (IsIdentity()) {
+ return inst->Arg(0).S32();
+ }
+ DEBUG_ASSERT(type == Type::S32);
+ return imm_s32;
+}
+
inline f32 Value::F32() const {
if (IsIdentity()) {
return inst->Arg(0).F32();
diff --git a/src/shader_recompiler/frontend/maxwell/translate_program.cpp b/src/shader_recompiler/frontend/maxwell/translate_program.cpp
index b58741d4d..b7162f719 100644
--- a/src/shader_recompiler/frontend/maxwell/translate_program.cpp
+++ b/src/shader_recompiler/frontend/maxwell/translate_program.cpp
@@ -220,6 +220,8 @@ IR::Program TranslateProgram(ObjectPool<IR::Inst>& inst_pool, ObjectPool<IR::Blo
Optimization::ConstantPropagationPass(program);
+ Optimization::PositionPass(env, program);
+
Optimization::GlobalMemoryToStorageBufferPass(program);
Optimization::TexturePass(env, program);
diff --git a/src/shader_recompiler/ir_opt/passes.h b/src/shader_recompiler/ir_opt/passes.h
index 6ff8e4266..24f609d69 100644
--- a/src/shader_recompiler/ir_opt/passes.h
+++ b/src/shader_recompiler/ir_opt/passes.h
@@ -17,6 +17,7 @@ void LowerFp16ToFp32(IR::Program& program);
void LowerInt64ToInt32(IR::Program& program);
void RescalingPass(IR::Program& program);
void SsaRewritePass(IR::Program& program);
+void PositionPass(Environment& env, IR::Program& program);
void TexturePass(Environment& env, IR::Program& program);
void VerificationPass(const IR::Program& program);
diff --git a/src/shader_recompiler/ir_opt/position_pass.cpp b/src/shader_recompiler/ir_opt/position_pass.cpp
new file mode 100644
index 000000000..3c20b7189
--- /dev/null
+++ b/src/shader_recompiler/ir_opt/position_pass.cpp
@@ -0,0 +1,77 @@
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <boost/container/small_vector.hpp>
+
+#include "shader_recompiler/frontend/ir/basic_block.h"
+#include "shader_recompiler/frontend/ir/ir_emitter.h"
+#include "shader_recompiler/frontend/ir/value.h"
+#include "shader_recompiler/ir_opt/passes.h"
+
+namespace Shader::Optimization {
+
+namespace {
+struct PositionInst {
+ IR::Inst* inst;
+ IR::Block* block;
+ IR::Attribute attr;
+};
+using PositionInstVector = boost::container::small_vector<PositionInst, 24>;
+} // Anonymous namespace
+
+void PositionPass(Environment& env, IR::Program& program) {
+ if (env.ShaderStage() != Stage::VertexB || env.ReadViewportTransformState()) {
+ return;
+ }
+
+ Info& info{program.info};
+ info.uses_render_area = true;
+
+ PositionInstVector to_replace;
+ for (IR::Block* const block : program.post_order_blocks) {
+ for (IR::Inst& inst : block->Instructions()) {
+ switch (inst.GetOpcode()) {
+ case IR::Opcode::SetAttribute: {
+ const IR::Attribute attr{inst.Arg(0).Attribute()};
+ switch (attr) {
+ case IR::Attribute::PositionX:
+ case IR::Attribute::PositionY: {
+ to_replace.push_back(PositionInst{.inst = &inst, .block = block, .attr = attr});
+ break;
+ }
+ default:
+ break;
+ }
+ break;
+ }
+ default:
+ break;
+ }
+ }
+ }
+
+ for (PositionInst& position_inst : to_replace) {
+ IR::IREmitter ir{*position_inst.block,
+ IR::Block::InstructionList::s_iterator_to(*position_inst.inst)};
+ const IR::F32 value(position_inst.inst->Arg(1));
+ const IR::F32F64 scale(ir.Imm32(2.f));
+ const IR::F32 negative_one{ir.Imm32(-1.f)};
+ switch (position_inst.attr) {
+ case IR::Attribute::PositionX: {
+ position_inst.inst->SetArg(
+ 1,
+ ir.FPFma(value, ir.FPMul(ir.FPRecip(ir.RenderAreaWidth()), scale), negative_one));
+ break;
+ }
+ case IR::Attribute::PositionY: {
+ position_inst.inst->SetArg(
+ 1,
+ ir.FPFma(value, ir.FPMul(ir.FPRecip(ir.RenderAreaHeight()), scale), negative_one));
+ break;
+ }
+ default:
+ break;
+ }
+ }
+}
+} // namespace Shader::Optimization
diff --git a/src/shader_recompiler/ir_opt/texture_pass.cpp b/src/shader_recompiler/ir_opt/texture_pass.cpp
index e8be58357..9eff84a3d 100644
--- a/src/shader_recompiler/ir_opt/texture_pass.cpp
+++ b/src/shader_recompiler/ir_opt/texture_pass.cpp
@@ -7,6 +7,7 @@
#include <boost/container/small_vector.hpp>
+#include "common/settings.h"
#include "shader_recompiler/environment.h"
#include "shader_recompiler/frontend/ir/basic_block.h"
#include "shader_recompiler/frontend/ir/breadth_first_search.h"
@@ -363,6 +364,14 @@ TextureType ReadTextureType(Environment& env, const ConstBufferAddr& cbuf) {
return env.ReadTextureType(lhs_raw | rhs_raw);
}
+TexturePixelFormat ReadTexturePixelFormat(Environment& env, const ConstBufferAddr& cbuf) {
+ const u32 secondary_index{cbuf.has_secondary ? cbuf.secondary_index : cbuf.index};
+ const u32 secondary_offset{cbuf.has_secondary ? cbuf.secondary_offset : cbuf.offset};
+ const u32 lhs_raw{env.ReadCbufValue(cbuf.index, cbuf.offset)};
+ const u32 rhs_raw{env.ReadCbufValue(secondary_index, secondary_offset)};
+ return env.ReadTexturePixelFormat(lhs_raw | rhs_raw);
+}
+
class Descriptors {
public:
explicit Descriptors(TextureBufferDescriptors& texture_buffer_descriptors_,
@@ -451,6 +460,38 @@ void PatchImageSampleImplicitLod(IR::Block& block, IR::Inst& inst) {
ir.FPMul(IR::F32(ir.CompositeExtract(coord, 1)),
ir.FPRecip(ir.ConvertUToF(32, 32, ir.CompositeExtract(texture_size, 1))))));
}
+
+void PathTexelFetch(IR::Block& block, IR::Inst& inst, TexturePixelFormat pixel_format) {
+ const auto it{IR::Block::InstructionList::s_iterator_to(inst)};
+ IR::IREmitter ir{block, IR::Block::InstructionList::s_iterator_to(inst)};
+ auto get_max_value = [pixel_format]() -> float {
+ switch (pixel_format) {
+ case TexturePixelFormat::A8B8G8R8_SNORM:
+ case TexturePixelFormat::R8G8_SNORM:
+ case TexturePixelFormat::R8_SNORM:
+ return 1.f / std::numeric_limits<char>::max();
+ case TexturePixelFormat::R16G16B16A16_SNORM:
+ case TexturePixelFormat::R16G16_SNORM:
+ case TexturePixelFormat::R16_SNORM:
+ return 1.f / std::numeric_limits<short>::max();
+ default:
+ throw InvalidArgument("Invalid texture pixel format");
+ }
+ };
+
+ const IR::Value new_inst{&*block.PrependNewInst(it, inst)};
+ const IR::F32 x(ir.CompositeExtract(new_inst, 0));
+ const IR::F32 y(ir.CompositeExtract(new_inst, 1));
+ const IR::F32 z(ir.CompositeExtract(new_inst, 2));
+ const IR::F32 w(ir.CompositeExtract(new_inst, 3));
+ const IR::F16F32F64 max_value(ir.Imm32(get_max_value()));
+ const IR::Value converted =
+ ir.CompositeConstruct(ir.FPMul(ir.ConvertSToF(32, 32, ir.BitCast<IR::S32>(x)), max_value),
+ ir.FPMul(ir.ConvertSToF(32, 32, ir.BitCast<IR::S32>(y)), max_value),
+ ir.FPMul(ir.ConvertSToF(32, 32, ir.BitCast<IR::S32>(z)), max_value),
+ ir.FPMul(ir.ConvertSToF(32, 32, ir.BitCast<IR::S32>(w)), max_value));
+ inst.ReplaceUsesWith(converted);
+}
} // Anonymous namespace
void TexturePass(Environment& env, IR::Program& program) {
@@ -597,6 +638,14 @@ void TexturePass(Environment& env, IR::Program& program) {
} else {
inst->SetArg(0, IR::Value{});
}
+
+ if (Settings::values.renderer_backend.GetValue() == Settings::RendererBackend::OpenGL &&
+ inst->GetOpcode() == IR::Opcode::ImageFetch && flags.type == TextureType::Buffer) {
+ const auto pixel_format = ReadTexturePixelFormat(env, cbuf);
+ if (pixel_format != TexturePixelFormat::OTHER) {
+ PathTexelFetch(*texture_inst.block, *texture_inst.inst, pixel_format);
+ }
+ }
}
}
diff --git a/src/shader_recompiler/shader_info.h b/src/shader_recompiler/shader_info.h
index 81097bf1a..f31e1f821 100644
--- a/src/shader_recompiler/shader_info.h
+++ b/src/shader_recompiler/shader_info.h
@@ -29,6 +29,16 @@ enum class TextureType : u32 {
};
constexpr u32 NUM_TEXTURE_TYPES = 9;
+enum class TexturePixelFormat : u32 {
+ A8B8G8R8_SNORM,
+ R8_SNORM,
+ R8G8_SNORM,
+ R16G16B16A16_SNORM,
+ R16G16_SNORM,
+ R16_SNORM,
+ OTHER
+};
+
enum class ImageFormat : u32 {
Typeless,
R8_UINT,
@@ -182,6 +192,7 @@ struct Info {
bool uses_shadow_lod{};
bool uses_rescaling_uniform{};
bool uses_cbuf_indirect{};
+ bool uses_render_area{};
IR::Type used_constant_buffer_types{};
IR::Type used_storage_buffer_types{};
diff --git a/src/video_core/renderer_opengl/gl_buffer_cache.cpp b/src/video_core/renderer_opengl/gl_buffer_cache.cpp
index 08f4d69ab..6af4ae793 100644
--- a/src/video_core/renderer_opengl/gl_buffer_cache.cpp
+++ b/src/video_core/renderer_opengl/gl_buffer_cache.cpp
@@ -29,17 +29,17 @@ constexpr std::array PROGRAM_LUT{
[[nodiscard]] GLenum GetTextureBufferFormat(GLenum gl_format) {
switch (gl_format) {
case GL_RGBA8_SNORM:
- return GL_RGBA8;
+ return GL_RGBA8I;
case GL_R8_SNORM:
- return GL_R8;
+ return GL_R8I;
case GL_RGBA16_SNORM:
- return GL_RGBA16;
+ return GL_RGBA16I;
case GL_R16_SNORM:
- return GL_R16;
+ return GL_R16I;
case GL_RG16_SNORM:
- return GL_RG16;
+ return GL_RG16I;
case GL_RG8_SNORM:
- return GL_RG8;
+ return GL_RG8I;
default:
return gl_format;
}
@@ -96,9 +96,6 @@ GLuint Buffer::View(u32 offset, u32 size, PixelFormat format) {
texture.Create(GL_TEXTURE_BUFFER);
const GLenum gl_format{MaxwellToGL::GetFormatTuple(format).internal_format};
const GLenum texture_format{GetTextureBufferFormat(gl_format)};
- if (texture_format != gl_format) {
- LOG_WARNING(Render_OpenGL, "Emulating SNORM texture buffer with UNORM.");
- }
glTextureBufferRange(texture.handle, texture_format, buffer.handle, offset, size);
views.push_back({
.offset = offset,
diff --git a/src/video_core/renderer_opengl/gl_graphics_pipeline.cpp b/src/video_core/renderer_opengl/gl_graphics_pipeline.cpp
index 1d20a79ec..c115dabe1 100644
--- a/src/video_core/renderer_opengl/gl_graphics_pipeline.cpp
+++ b/src/video_core/renderer_opengl/gl_graphics_pipeline.cpp
@@ -503,6 +503,17 @@ void GraphicsPipeline::ConfigureImpl(bool is_indexed) {
float_image_scaling_mask, down_factor, 0.0f);
}
}
+ if (info.uses_render_area) {
+ const auto render_area_width(static_cast<GLfloat>(regs.surface_clip.width));
+ const auto render_area_height(static_cast<GLfloat>(regs.surface_clip.height));
+ if (use_assembly) {
+ glProgramLocalParameter4fARB(AssemblyStage(stage), 1, render_area_width,
+ render_area_height, 0.0f, 0.0f);
+ } else {
+ glProgramUniform4f(source_programs[stage].handle, 1, render_area_width,
+ render_area_height, 0.0f, 0.0f);
+ }
+ }
}};
if constexpr (Spec::enabled_stages[0]) {
prepare_stage(0);
diff --git a/src/video_core/renderer_opengl/gl_rasterizer.cpp b/src/video_core/renderer_opengl/gl_rasterizer.cpp
index 1590b21de..72e314d39 100644
--- a/src/video_core/renderer_opengl/gl_rasterizer.cpp
+++ b/src/video_core/renderer_opengl/gl_rasterizer.cpp
@@ -618,6 +618,16 @@ void RasterizerOpenGL::SyncViewport() {
}
flags[Dirty::Viewport0 + index] = false;
+ if (!regs.viewport_scale_offset_enbled) {
+ const auto x = static_cast<GLfloat>(regs.surface_clip.x);
+ const auto y = static_cast<GLfloat>(regs.surface_clip.y);
+ const auto width = static_cast<GLfloat>(regs.surface_clip.width);
+ const auto height = static_cast<GLfloat>(regs.surface_clip.height);
+ glViewportIndexedf(static_cast<GLuint>(index), x, y, width != 0.0f ? width : 1.0f,
+ height != 0.0f ? height : 1.0f);
+ continue;
+ }
+
const auto& src = regs.viewport_transform[index];
GLfloat x = conv(src.translate_x - src.scale_x);
GLfloat y = conv(src.translate_y - src.scale_y);
diff --git a/src/video_core/renderer_opengl/gl_shader_cache.cpp b/src/video_core/renderer_opengl/gl_shader_cache.cpp
index e94cfdb1a..977709518 100644
--- a/src/video_core/renderer_opengl/gl_shader_cache.cpp
+++ b/src/video_core/renderer_opengl/gl_shader_cache.cpp
@@ -49,7 +49,7 @@ using VideoCommon::LoadPipelines;
using VideoCommon::SerializePipeline;
using Context = ShaderContext::Context;
-constexpr u32 CACHE_VERSION = 6;
+constexpr u32 CACHE_VERSION = 7;
template <typename Container>
auto MakeSpan(Container& container) {
diff --git a/src/video_core/renderer_vulkan/pipeline_helper.h b/src/video_core/renderer_vulkan/pipeline_helper.h
index b24f3424a..b7843e995 100644
--- a/src/video_core/renderer_vulkan/pipeline_helper.h
+++ b/src/video_core/renderer_vulkan/pipeline_helper.h
@@ -68,13 +68,15 @@ public:
}
vk::PipelineLayout CreatePipelineLayout(VkDescriptorSetLayout descriptor_set_layout) const {
+ using Shader::Backend::SPIRV::RenderAreaLayout;
using Shader::Backend::SPIRV::RescalingLayout;
const u32 size_offset = is_compute ? sizeof(RescalingLayout::down_factor) : 0u;
const VkPushConstantRange range{
.stageFlags = static_cast<VkShaderStageFlags>(
is_compute ? VK_SHADER_STAGE_COMPUTE_BIT : VK_SHADER_STAGE_ALL_GRAPHICS),
.offset = 0,
- .size = static_cast<u32>(sizeof(RescalingLayout)) - size_offset,
+ .size = static_cast<u32>(sizeof(RescalingLayout)) - size_offset +
+ static_cast<u32>(sizeof(RenderAreaLayout)),
};
return device->GetLogical().CreatePipelineLayout({
.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO,
@@ -167,6 +169,12 @@ private:
u32 image_bit{1u};
};
+class RenderAreaPushConstant {
+public:
+ bool uses_render_area{};
+ std::array<f32, 4> words{};
+};
+
inline void PushImageDescriptors(TextureCache& texture_cache,
UpdateDescriptorQueue& update_descriptor_queue,
const Shader::Info& info, RescalingPushConstant& rescaling,
diff --git a/src/video_core/renderer_vulkan/vk_blit_screen.cpp b/src/video_core/renderer_vulkan/vk_blit_screen.cpp
index cb7fa2078..89426121f 100644
--- a/src/video_core/renderer_vulkan/vk_blit_screen.cpp
+++ b/src/video_core/renderer_vulkan/vk_blit_screen.cpp
@@ -480,11 +480,15 @@ void BlitScreen::RefreshResources(const Tegra::FramebufferConfig& framebuffer) {
fsr.reset();
}
- if (framebuffer.width == raw_width && framebuffer.height == raw_height && !raw_images.empty()) {
+ if (framebuffer.width == raw_width && framebuffer.height == raw_height &&
+ framebuffer.pixel_format == pixel_format && !raw_images.empty()) {
return;
}
+
raw_width = framebuffer.width;
raw_height = framebuffer.height;
+ pixel_format = framebuffer.pixel_format;
+
ReleaseRawImages();
CreateStagingBuffer(framebuffer);
diff --git a/src/video_core/renderer_vulkan/vk_blit_screen.h b/src/video_core/renderer_vulkan/vk_blit_screen.h
index 29e2ea925..a2b73ec54 100644
--- a/src/video_core/renderer_vulkan/vk_blit_screen.h
+++ b/src/video_core/renderer_vulkan/vk_blit_screen.h
@@ -28,6 +28,10 @@ namespace VideoCore {
class RasterizerInterface;
}
+namespace Service::android {
+enum class PixelFormat : u32;
+}
+
namespace Vulkan {
struct ScreenInfo;
@@ -156,6 +160,7 @@ private:
u32 raw_width = 0;
u32 raw_height = 0;
+ Service::android::PixelFormat pixel_format{};
std::unique_ptr<FSR> fsr;
};
diff --git a/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp b/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp
index c3f66c8a3..1aa116cea 100644
--- a/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp
+++ b/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp
@@ -31,6 +31,7 @@ namespace {
using boost::container::small_vector;
using boost::container::static_vector;
using Shader::ImageBufferDescriptor;
+using Shader::Backend::SPIRV::RENDERAREA_LAYOUT_OFFSET;
using Shader::Backend::SPIRV::RESCALING_LAYOUT_DOWN_FACTOR_OFFSET;
using Shader::Backend::SPIRV::RESCALING_LAYOUT_WORDS_OFFSET;
using Tegra::Texture::TexturePair;
@@ -433,12 +434,19 @@ void GraphicsPipeline::ConfigureImpl(bool is_indexed) {
update_descriptor_queue.Acquire();
RescalingPushConstant rescaling;
+ RenderAreaPushConstant render_area;
const VkSampler* samplers_it{samplers.data()};
const VideoCommon::ImageViewInOut* views_it{views.data()};
const auto prepare_stage{[&](size_t stage) LAMBDA_FORCEINLINE {
buffer_cache.BindHostStageBuffers(stage);
PushImageDescriptors(texture_cache, update_descriptor_queue, stage_infos[stage], rescaling,
samplers_it, views_it);
+ const auto& info{stage_infos[0]};
+ if (info.uses_render_area) {
+ render_area.uses_render_area = true;
+ render_area.words = {static_cast<float>(regs.surface_clip.width),
+ static_cast<float>(regs.surface_clip.height)};
+ }
}};
if constexpr (Spec::enabled_stages[0]) {
prepare_stage(0);
@@ -455,10 +463,11 @@ void GraphicsPipeline::ConfigureImpl(bool is_indexed) {
if constexpr (Spec::enabled_stages[4]) {
prepare_stage(4);
}
- ConfigureDraw(rescaling);
+ ConfigureDraw(rescaling, render_area);
}
-void GraphicsPipeline::ConfigureDraw(const RescalingPushConstant& rescaling) {
+void GraphicsPipeline::ConfigureDraw(const RescalingPushConstant& rescaling,
+ const RenderAreaPushConstant& render_area) {
texture_cache.UpdateRenderTargets(false);
scheduler.RequestRenderpass(texture_cache.GetFramebuffer());
@@ -474,7 +483,9 @@ void GraphicsPipeline::ConfigureDraw(const RescalingPushConstant& rescaling) {
const bool bind_pipeline{scheduler.UpdateGraphicsPipeline(this)};
const void* const descriptor_data{update_descriptor_queue.UpdateData()};
scheduler.Record([this, descriptor_data, bind_pipeline, rescaling_data = rescaling.Data(),
- is_rescaling, update_rescaling](vk::CommandBuffer cmdbuf) {
+ is_rescaling, update_rescaling,
+ uses_render_area = render_area.uses_render_area,
+ render_area_data = render_area.words](vk::CommandBuffer cmdbuf) {
if (bind_pipeline) {
cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_GRAPHICS, *pipeline);
}
@@ -488,6 +499,11 @@ void GraphicsPipeline::ConfigureDraw(const RescalingPushConstant& rescaling) {
RESCALING_LAYOUT_DOWN_FACTOR_OFFSET, sizeof(scale_down_factor),
&scale_down_factor);
}
+ if (uses_render_area) {
+ cmdbuf.PushConstants(*pipeline_layout, VK_SHADER_STAGE_ALL_GRAPHICS,
+ RENDERAREA_LAYOUT_OFFSET, sizeof(render_area_data),
+ &render_area_data);
+ }
if (!descriptor_set_layout) {
return;
}
diff --git a/src/video_core/renderer_vulkan/vk_graphics_pipeline.h b/src/video_core/renderer_vulkan/vk_graphics_pipeline.h
index 85602592b..6bf577d25 100644
--- a/src/video_core/renderer_vulkan/vk_graphics_pipeline.h
+++ b/src/video_core/renderer_vulkan/vk_graphics_pipeline.h
@@ -62,6 +62,7 @@ class Device;
class PipelineStatistics;
class RenderPassCache;
class RescalingPushConstant;
+class RenderAreaPushConstant;
class Scheduler;
class UpdateDescriptorQueue;
@@ -119,7 +120,8 @@ private:
template <typename Spec>
void ConfigureImpl(bool is_indexed);
- void ConfigureDraw(const RescalingPushConstant& rescaling);
+ void ConfigureDraw(const RescalingPushConstant& rescaling,
+ const RenderAreaPushConstant& render_are);
void MakePipeline(VkRenderPass render_pass);
diff --git a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp
index 13d5a1f67..b42e5be1e 100644
--- a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp
+++ b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp
@@ -53,7 +53,7 @@ using VideoCommon::FileEnvironment;
using VideoCommon::GenericEnvironment;
using VideoCommon::GraphicsEnvironment;
-constexpr u32 CACHE_VERSION = 6;
+constexpr u32 CACHE_VERSION = 7;
template <typename Container>
auto MakeSpan(Container& container) {
diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.cpp b/src/video_core/renderer_vulkan/vk_rasterizer.cpp
index 6ab68892c..f79fa8313 100644
--- a/src/video_core/renderer_vulkan/vk_rasterizer.cpp
+++ b/src/video_core/renderer_vulkan/vk_rasterizer.cpp
@@ -156,12 +156,10 @@ RasterizerVulkan::RasterizerVulkan(Core::Frontend::EmuWindow& emu_window_, Tegra
staging_pool(device, memory_allocator, scheduler), descriptor_pool(device, scheduler),
update_descriptor_queue(device, scheduler),
blit_image(device, scheduler, state_tracker, descriptor_pool),
- astc_decoder_pass(device, scheduler, descriptor_pool, staging_pool, update_descriptor_queue,
- memory_allocator),
render_pass_cache(device), texture_cache_runtime{device, scheduler,
memory_allocator, staging_pool,
- blit_image, astc_decoder_pass,
- render_pass_cache},
+ blit_image, render_pass_cache,
+ descriptor_pool, update_descriptor_queue},
texture_cache(texture_cache_runtime, *this),
buffer_cache_runtime(device, memory_allocator, scheduler, staging_pool,
update_descriptor_queue, descriptor_pool),
@@ -685,6 +683,22 @@ void RasterizerVulkan::UpdateViewportsState(Tegra::Engines::Maxwell3D::Regs& reg
if (!state_tracker.TouchViewports()) {
return;
}
+ if (!regs.viewport_scale_offset_enbled) {
+ const auto x = static_cast<float>(regs.surface_clip.x);
+ const auto y = static_cast<float>(regs.surface_clip.y);
+ const auto width = static_cast<float>(regs.surface_clip.width);
+ const auto height = static_cast<float>(regs.surface_clip.height);
+ VkViewport viewport{
+ .x = x,
+ .y = y,
+ .width = width != 0.0f ? width : 1.0f,
+ .height = height != 0.0f ? height : 1.0f,
+ .minDepth = 0.0f,
+ .maxDepth = 1.0f,
+ };
+ scheduler.Record([viewport](vk::CommandBuffer cmdbuf) { cmdbuf.SetViewport(0, viewport); });
+ return;
+ }
const bool is_rescaling{texture_cache.IsRescaling()};
const float scale = is_rescaling ? Settings::values.resolution_info.up_factor : 1.0f;
const std::array viewports{
diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.h b/src/video_core/renderer_vulkan/vk_rasterizer.h
index e2fdc7611..b0bc306f5 100644
--- a/src/video_core/renderer_vulkan/vk_rasterizer.h
+++ b/src/video_core/renderer_vulkan/vk_rasterizer.h
@@ -155,7 +155,6 @@ private:
DescriptorPool descriptor_pool;
UpdateDescriptorQueue update_descriptor_queue;
BlitImageHelper blit_image;
- ASTCDecoderPass astc_decoder_pass;
RenderPassCache render_pass_cache;
TextureCacheRuntime texture_cache_runtime;
diff --git a/src/video_core/renderer_vulkan/vk_scheduler.h b/src/video_core/renderer_vulkan/vk_scheduler.h
index c04aad08f..929216749 100644
--- a/src/video_core/renderer_vulkan/vk_scheduler.h
+++ b/src/video_core/renderer_vulkan/vk_scheduler.h
@@ -144,7 +144,6 @@ private:
using FuncType = TypedCommand<T>;
static_assert(sizeof(FuncType) < sizeof(data), "Lambda is too large");
- recorded_counts++;
command_offset = Common::AlignUp(command_offset, alignof(FuncType));
if (command_offset > sizeof(data) - sizeof(FuncType)) {
return false;
@@ -166,7 +165,7 @@ private:
}
bool Empty() const {
- return recorded_counts == 0;
+ return command_offset == 0;
}
bool HasSubmit() const {
@@ -177,7 +176,6 @@ private:
Command* first = nullptr;
Command* last = nullptr;
- size_t recorded_counts = 0;
size_t command_offset = 0;
bool submit = false;
alignas(std::max_align_t) std::array<u8, 0x8000> data{};
diff --git a/src/video_core/renderer_vulkan/vk_texture_cache.cpp b/src/video_core/renderer_vulkan/vk_texture_cache.cpp
index 6ad7efbdf..853b80d8a 100644
--- a/src/video_core/renderer_vulkan/vk_texture_cache.cpp
+++ b/src/video_core/renderer_vulkan/vk_texture_cache.cpp
@@ -791,12 +791,17 @@ TextureCacheRuntime::TextureCacheRuntime(const Device& device_, Scheduler& sched
MemoryAllocator& memory_allocator_,
StagingBufferPool& staging_buffer_pool_,
BlitImageHelper& blit_image_helper_,
- ASTCDecoderPass& astc_decoder_pass_,
- RenderPassCache& render_pass_cache_)
+ RenderPassCache& render_pass_cache_,
+ DescriptorPool& descriptor_pool,
+ UpdateDescriptorQueue& update_descriptor_queue)
: device{device_}, scheduler{scheduler_}, memory_allocator{memory_allocator_},
staging_buffer_pool{staging_buffer_pool_}, blit_image_helper{blit_image_helper_},
- astc_decoder_pass{astc_decoder_pass_}, render_pass_cache{render_pass_cache_},
- resolution{Settings::values.resolution_info} {}
+ render_pass_cache{render_pass_cache_}, resolution{Settings::values.resolution_info} {
+ if (Settings::values.accelerate_astc) {
+ astc_decoder_pass.emplace(device, scheduler, descriptor_pool, staging_buffer_pool,
+ update_descriptor_queue, memory_allocator);
+ }
+}
void TextureCacheRuntime::Finish() {
scheduler.Finish();
@@ -1845,7 +1850,7 @@ void TextureCacheRuntime::AccelerateImageUpload(
Image& image, const StagingBufferRef& map,
std::span<const VideoCommon::SwizzleParameters> swizzles) {
if (IsPixelFormatASTC(image.info.format)) {
- return astc_decoder_pass.Assemble(image, map, swizzles);
+ return astc_decoder_pass->Assemble(image, map, swizzles);
}
ASSERT(false);
}
diff --git a/src/video_core/renderer_vulkan/vk_texture_cache.h b/src/video_core/renderer_vulkan/vk_texture_cache.h
index 0b7ac0df1..7ec0df134 100644
--- a/src/video_core/renderer_vulkan/vk_texture_cache.h
+++ b/src/video_core/renderer_vulkan/vk_texture_cache.h
@@ -6,6 +6,7 @@
#include <span>
#include "shader_recompiler/shader_info.h"
+#include "video_core/renderer_vulkan/vk_compute_pass.h"
#include "video_core/renderer_vulkan/vk_staging_buffer_pool.h"
#include "video_core/texture_cache/image_view_base.h"
#include "video_core/texture_cache/texture_cache_base.h"
@@ -25,14 +26,15 @@ using VideoCommon::RenderTargets;
using VideoCommon::SlotVector;
using VideoCore::Surface::PixelFormat;
-class ASTCDecoderPass;
class BlitImageHelper;
+class DescriptorPool;
class Device;
class Image;
class ImageView;
class Framebuffer;
class RenderPassCache;
class StagingBufferPool;
+class UpdateDescriptorQueue;
class Scheduler;
class TextureCacheRuntime {
@@ -41,8 +43,9 @@ public:
MemoryAllocator& memory_allocator_,
StagingBufferPool& staging_buffer_pool_,
BlitImageHelper& blit_image_helper_,
- ASTCDecoderPass& astc_decoder_pass_,
- RenderPassCache& render_pass_cache_);
+ RenderPassCache& render_pass_cache_,
+ DescriptorPool& descriptor_pool,
+ UpdateDescriptorQueue& update_descriptor_queue);
void Finish();
@@ -97,8 +100,8 @@ public:
MemoryAllocator& memory_allocator;
StagingBufferPool& staging_buffer_pool;
BlitImageHelper& blit_image_helper;
- ASTCDecoderPass& astc_decoder_pass;
RenderPassCache& render_pass_cache;
+ std::optional<ASTCDecoderPass> astc_decoder_pass;
const Settings::ResolutionScalingInfo& resolution;
constexpr static size_t indexing_slots = 8 * sizeof(size_t);
diff --git a/src/video_core/shader_environment.cpp b/src/video_core/shader_environment.cpp
index fbabb3219..37bb76b72 100644
--- a/src/video_core/shader_environment.cpp
+++ b/src/video_core/shader_environment.cpp
@@ -19,6 +19,7 @@
#include "video_core/engines/kepler_compute.h"
#include "video_core/memory_manager.h"
#include "video_core/shader_environment.h"
+#include "video_core/texture_cache/format_lookup_table.h"
#include "video_core/textures/texture.h"
namespace VideoCommon {
@@ -33,7 +34,7 @@ static u64 MakeCbufKey(u32 index, u32 offset) {
return (static_cast<u64>(index) << 32) | offset;
}
-static Shader::TextureType ConvertType(const Tegra::Texture::TICEntry& entry) {
+static Shader::TextureType ConvertTextureType(const Tegra::Texture::TICEntry& entry) {
switch (entry.texture_type) {
case Tegra::Texture::TextureType::Texture1D:
return Shader::TextureType::Color1D;
@@ -59,6 +60,26 @@ static Shader::TextureType ConvertType(const Tegra::Texture::TICEntry& entry) {
}
}
+static Shader::TexturePixelFormat ConvertTexturePixelFormat(const Tegra::Texture::TICEntry& entry) {
+ switch (PixelFormatFromTextureInfo(entry.format, entry.r_type, entry.g_type, entry.b_type,
+ entry.a_type, entry.srgb_conversion)) {
+ case VideoCore::Surface::PixelFormat::A8B8G8R8_SNORM:
+ return Shader::TexturePixelFormat::A8B8G8R8_SNORM;
+ case VideoCore::Surface::PixelFormat::R8_SNORM:
+ return Shader::TexturePixelFormat::R8_SNORM;
+ case VideoCore::Surface::PixelFormat::R8G8_SNORM:
+ return Shader::TexturePixelFormat::R8G8_SNORM;
+ case VideoCore::Surface::PixelFormat::R16G16B16A16_SNORM:
+ return Shader::TexturePixelFormat::R16G16B16A16_SNORM;
+ case VideoCore::Surface::PixelFormat::R16G16_SNORM:
+ return Shader::TexturePixelFormat::R16G16_SNORM;
+ case VideoCore::Surface::PixelFormat::R16_SNORM:
+ return Shader::TexturePixelFormat::R16_SNORM;
+ default:
+ return Shader::TexturePixelFormat::OTHER;
+ }
+}
+
static std::string_view StageToPrefix(Shader::Stage stage) {
switch (stage) {
case Shader::Stage::VertexB:
@@ -178,22 +199,31 @@ void GenericEnvironment::Dump(u64 hash) {
void GenericEnvironment::Serialize(std::ofstream& file) const {
const u64 code_size{static_cast<u64>(CachedSize())};
const u64 num_texture_types{static_cast<u64>(texture_types.size())};
+ const u64 num_texture_pixel_formats{static_cast<u64>(texture_pixel_formats.size())};
const u64 num_cbuf_values{static_cast<u64>(cbuf_values.size())};
file.write(reinterpret_cast<const char*>(&code_size), sizeof(code_size))
.write(reinterpret_cast<const char*>(&num_texture_types), sizeof(num_texture_types))
+ .write(reinterpret_cast<const char*>(&num_texture_pixel_formats),
+ sizeof(num_texture_pixel_formats))
.write(reinterpret_cast<const char*>(&num_cbuf_values), sizeof(num_cbuf_values))
.write(reinterpret_cast<const char*>(&local_memory_size), sizeof(local_memory_size))
.write(reinterpret_cast<const char*>(&texture_bound), sizeof(texture_bound))
.write(reinterpret_cast<const char*>(&start_address), sizeof(start_address))
.write(reinterpret_cast<const char*>(&cached_lowest), sizeof(cached_lowest))
.write(reinterpret_cast<const char*>(&cached_highest), sizeof(cached_highest))
+ .write(reinterpret_cast<const char*>(&viewport_transform_state),
+ sizeof(viewport_transform_state))
.write(reinterpret_cast<const char*>(&stage), sizeof(stage))
.write(reinterpret_cast<const char*>(code.data()), code_size);
for (const auto& [key, type] : texture_types) {
file.write(reinterpret_cast<const char*>(&key), sizeof(key))
.write(reinterpret_cast<const char*>(&type), sizeof(type));
}
+ for (const auto& [key, format] : texture_pixel_formats) {
+ file.write(reinterpret_cast<const char*>(&key), sizeof(key))
+ .write(reinterpret_cast<const char*>(&format), sizeof(format));
+ }
for (const auto& [key, type] : cbuf_values) {
file.write(reinterpret_cast<const char*>(&key), sizeof(key))
.write(reinterpret_cast<const char*>(&type), sizeof(type));
@@ -237,15 +267,13 @@ std::optional<u64> GenericEnvironment::TryFindSize() {
return std::nullopt;
}
-Shader::TextureType GenericEnvironment::ReadTextureTypeImpl(GPUVAddr tic_addr, u32 tic_limit,
- bool via_header_index, u32 raw) {
+Tegra::Texture::TICEntry GenericEnvironment::ReadTextureInfo(GPUVAddr tic_addr, u32 tic_limit,
+ bool via_header_index, u32 raw) {
const auto handle{Tegra::Texture::TexturePair(raw, via_header_index)};
const GPUVAddr descriptor_addr{tic_addr + handle.first * sizeof(Tegra::Texture::TICEntry)};
Tegra::Texture::TICEntry entry;
gpu_memory->ReadBlock(descriptor_addr, &entry, sizeof(entry));
- const Shader::TextureType result{ConvertType(entry)};
- texture_types.emplace(raw, result);
- return result;
+ return entry;
}
GraphicsEnvironment::GraphicsEnvironment(Tegra::Engines::Maxwell3D& maxwell3d_,
@@ -305,8 +333,27 @@ u32 GraphicsEnvironment::ReadCbufValue(u32 cbuf_index, u32 cbuf_offset) {
Shader::TextureType GraphicsEnvironment::ReadTextureType(u32 handle) {
const auto& regs{maxwell3d->regs};
const bool via_header_index{regs.sampler_binding == Maxwell::SamplerBinding::ViaHeaderBinding};
- return ReadTextureTypeImpl(regs.tex_header.Address(), regs.tex_header.limit, via_header_index,
- handle);
+ auto entry =
+ ReadTextureInfo(regs.tex_header.Address(), regs.tex_header.limit, via_header_index, handle);
+ const Shader::TextureType result{ConvertTextureType(entry)};
+ texture_types.emplace(handle, result);
+ return result;
+}
+
+Shader::TexturePixelFormat GraphicsEnvironment::ReadTexturePixelFormat(u32 handle) {
+ const auto& regs{maxwell3d->regs};
+ const bool via_header_index{regs.sampler_binding == Maxwell::SamplerBinding::ViaHeaderBinding};
+ auto entry =
+ ReadTextureInfo(regs.tex_header.Address(), regs.tex_header.limit, via_header_index, handle);
+ const Shader::TexturePixelFormat result(ConvertTexturePixelFormat(entry));
+ texture_pixel_formats.emplace(handle, result);
+ return result;
+}
+
+u32 GraphicsEnvironment::ReadViewportTransformState() {
+ const auto& regs{maxwell3d->regs};
+ viewport_transform_state = regs.viewport_scale_offset_enbled;
+ return viewport_transform_state;
}
ComputeEnvironment::ComputeEnvironment(Tegra::Engines::KeplerCompute& kepler_compute_,
@@ -337,21 +384,41 @@ u32 ComputeEnvironment::ReadCbufValue(u32 cbuf_index, u32 cbuf_offset) {
Shader::TextureType ComputeEnvironment::ReadTextureType(u32 handle) {
const auto& regs{kepler_compute->regs};
const auto& qmd{kepler_compute->launch_description};
- return ReadTextureTypeImpl(regs.tic.Address(), regs.tic.limit, qmd.linked_tsc != 0, handle);
+ auto entry = ReadTextureInfo(regs.tic.Address(), regs.tic.limit, qmd.linked_tsc != 0, handle);
+ const Shader::TextureType result{ConvertTextureType(entry)};
+ texture_types.emplace(handle, result);
+ return result;
+}
+
+Shader::TexturePixelFormat ComputeEnvironment::ReadTexturePixelFormat(u32 handle) {
+ const auto& regs{kepler_compute->regs};
+ const auto& qmd{kepler_compute->launch_description};
+ auto entry = ReadTextureInfo(regs.tic.Address(), regs.tic.limit, qmd.linked_tsc != 0, handle);
+ const Shader::TexturePixelFormat result(ConvertTexturePixelFormat(entry));
+ texture_pixel_formats.emplace(handle, result);
+ return result;
+}
+
+u32 ComputeEnvironment::ReadViewportTransformState() {
+ return viewport_transform_state;
}
void FileEnvironment::Deserialize(std::ifstream& file) {
u64 code_size{};
u64 num_texture_types{};
+ u64 num_texture_pixel_formats{};
u64 num_cbuf_values{};
file.read(reinterpret_cast<char*>(&code_size), sizeof(code_size))
.read(reinterpret_cast<char*>(&num_texture_types), sizeof(num_texture_types))
+ .read(reinterpret_cast<char*>(&num_texture_pixel_formats),
+ sizeof(num_texture_pixel_formats))
.read(reinterpret_cast<char*>(&num_cbuf_values), sizeof(num_cbuf_values))
.read(reinterpret_cast<char*>(&local_memory_size), sizeof(local_memory_size))
.read(reinterpret_cast<char*>(&texture_bound), sizeof(texture_bound))
.read(reinterpret_cast<char*>(&start_address), sizeof(start_address))
.read(reinterpret_cast<char*>(&read_lowest), sizeof(read_lowest))
.read(reinterpret_cast<char*>(&read_highest), sizeof(read_highest))
+ .read(reinterpret_cast<char*>(&viewport_transform_state), sizeof(viewport_transform_state))
.read(reinterpret_cast<char*>(&stage), sizeof(stage));
code = std::make_unique<u64[]>(Common::DivCeil(code_size, sizeof(u64)));
file.read(reinterpret_cast<char*>(code.get()), code_size);
@@ -362,6 +429,13 @@ void FileEnvironment::Deserialize(std::ifstream& file) {
.read(reinterpret_cast<char*>(&type), sizeof(type));
texture_types.emplace(key, type);
}
+ for (size_t i = 0; i < num_texture_pixel_formats; ++i) {
+ u32 key;
+ Shader::TexturePixelFormat format;
+ file.read(reinterpret_cast<char*>(&key), sizeof(key))
+ .read(reinterpret_cast<char*>(&format), sizeof(format));
+ texture_pixel_formats.emplace(key, format);
+ }
for (size_t i = 0; i < num_cbuf_values; ++i) {
u64 key;
u32 value;
@@ -409,6 +483,18 @@ Shader::TextureType FileEnvironment::ReadTextureType(u32 handle) {
return it->second;
}
+Shader::TexturePixelFormat FileEnvironment::ReadTexturePixelFormat(u32 handle) {
+ const auto it{texture_pixel_formats.find(handle)};
+ if (it == texture_pixel_formats.end()) {
+ throw Shader::LogicError("Uncached read texture pixel format");
+ }
+ return it->second;
+}
+
+u32 FileEnvironment::ReadViewportTransformState() {
+ return viewport_transform_state;
+}
+
u32 FileEnvironment::LocalMemorySize() const {
return local_memory_size;
}
diff --git a/src/video_core/shader_environment.h b/src/video_core/shader_environment.h
index 8b3b8e9f5..bb55b029f 100644
--- a/src/video_core/shader_environment.h
+++ b/src/video_core/shader_environment.h
@@ -63,14 +63,15 @@ public:
protected:
std::optional<u64> TryFindSize();
- Shader::TextureType ReadTextureTypeImpl(GPUVAddr tic_addr, u32 tic_limit, bool via_header_index,
- u32 raw);
+ Tegra::Texture::TICEntry ReadTextureInfo(GPUVAddr tic_addr, u32 tic_limit,
+ bool via_header_index, u32 raw);
Tegra::MemoryManager* gpu_memory{};
GPUVAddr program_base{};
std::vector<u64> code;
std::unordered_map<u32, Shader::TextureType> texture_types;
+ std::unordered_map<u32, Shader::TexturePixelFormat> texture_pixel_formats;
std::unordered_map<u64, u32> cbuf_values;
u32 local_memory_size{};
@@ -85,6 +86,8 @@ protected:
u32 cached_highest = 0;
u32 initial_offset = 0;
+ u32 viewport_transform_state = 1;
+
bool has_unbound_instructions = false;
};
@@ -102,6 +105,10 @@ public:
Shader::TextureType ReadTextureType(u32 handle) override;
+ Shader::TexturePixelFormat ReadTexturePixelFormat(u32 handle) override;
+
+ u32 ReadViewportTransformState() override;
+
private:
Tegra::Engines::Maxwell3D* maxwell3d{};
size_t stage_index{};
@@ -120,6 +127,10 @@ public:
Shader::TextureType ReadTextureType(u32 handle) override;
+ Shader::TexturePixelFormat ReadTexturePixelFormat(u32 handle) override;
+
+ u32 ReadViewportTransformState() override;
+
private:
Tegra::Engines::KeplerCompute* kepler_compute{};
};
@@ -143,6 +154,10 @@ public:
[[nodiscard]] Shader::TextureType ReadTextureType(u32 handle) override;
+ [[nodiscard]] Shader::TexturePixelFormat ReadTexturePixelFormat(u32 handle) override;
+
+ [[nodiscard]] u32 ReadViewportTransformState() override;
+
[[nodiscard]] u32 LocalMemorySize() const override;
[[nodiscard]] u32 SharedMemorySize() const override;
@@ -156,6 +171,7 @@ public:
private:
std::unique_ptr<u64[]> code;
std::unordered_map<u32, Shader::TextureType> texture_types;
+ std::unordered_map<u32, Shader::TexturePixelFormat> texture_pixel_formats;
std::unordered_map<u64, u32> cbuf_values;
std::array<u32, 3> workgroup_size{};
u32 local_memory_size{};
@@ -164,6 +180,7 @@ private:
u32 read_lowest{};
u32 read_highest{};
u32 initial_offset{};
+ u32 viewport_transform_state = 1;
};
void SerializePipeline(std::span<const char> key, std::span<const GenericEnvironment* const> envs,
diff --git a/src/video_core/texture_cache/util.cpp b/src/video_core/texture_cache/util.cpp
index 1223df5a0..e8c908b42 100644
--- a/src/video_core/texture_cache/util.cpp
+++ b/src/video_core/texture_cache/util.cpp
@@ -516,7 +516,6 @@ void SwizzleBlockLinearImage(Tegra::MemoryManager& gpu_memory, GPUVAddr gpu_addr
const u32 num_blocks_per_layer = NumBlocks(level_size, tile_size);
const u32 host_bytes_per_layer = num_blocks_per_layer * bytes_per_block;
- UNIMPLEMENTED_IF(info.tile_width_spacing > 0);
UNIMPLEMENTED_IF(copy.image_offset.x != 0);
UNIMPLEMENTED_IF(copy.image_offset.y != 0);
UNIMPLEMENTED_IF(copy.image_offset.z != 0);
diff --git a/src/yuzu/CMakeLists.txt b/src/yuzu/CMakeLists.txt
index 29d506c47..239f12382 100644
--- a/src/yuzu/CMakeLists.txt
+++ b/src/yuzu/CMakeLists.txt
@@ -315,7 +315,7 @@ target_include_directories(yuzu PRIVATE ../../externals/Vulkan-Headers/include)
if (NOT WIN32)
target_include_directories(yuzu PRIVATE ${Qt5Gui_PRIVATE_INCLUDE_DIRS})
endif()
-if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
+if (UNIX AND NOT APPLE)
target_link_libraries(yuzu PRIVATE Qt::DBus)
endif()
diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp
index 927dd1069..343f3b8e5 100644
--- a/src/yuzu/configuration/config.cpp
+++ b/src/yuzu/configuration/config.cpp
@@ -820,6 +820,8 @@ void Config::ReadUIGamelistValues() {
ReadBasicSetting(UISettings::values.show_add_ons);
ReadBasicSetting(UISettings::values.show_compat);
+ ReadBasicSetting(UISettings::values.show_size);
+ ReadBasicSetting(UISettings::values.show_types);
ReadBasicSetting(UISettings::values.game_icon_size);
ReadBasicSetting(UISettings::values.folder_icon_size);
ReadBasicSetting(UISettings::values.row_1_text_id);
@@ -1416,6 +1418,8 @@ void Config::SaveUIGamelistValues() {
WriteBasicSetting(UISettings::values.show_add_ons);
WriteBasicSetting(UISettings::values.show_compat);
+ WriteBasicSetting(UISettings::values.show_size);
+ WriteBasicSetting(UISettings::values.show_types);
WriteBasicSetting(UISettings::values.game_icon_size);
WriteBasicSetting(UISettings::values.folder_icon_size);
WriteBasicSetting(UISettings::values.row_1_text_id);
diff --git a/src/yuzu/configuration/configure_ui.cpp b/src/yuzu/configuration/configure_ui.cpp
index 92e6da6ee..2ebb80302 100644
--- a/src/yuzu/configuration/configure_ui.cpp
+++ b/src/yuzu/configuration/configure_ui.cpp
@@ -73,6 +73,8 @@ ConfigureUi::ConfigureUi(Core::System& system_, QWidget* parent)
// Force game list reload if any of the relevant settings are changed.
connect(ui->show_add_ons, &QCheckBox::stateChanged, this, &ConfigureUi::RequestGameListUpdate);
connect(ui->show_compat, &QCheckBox::stateChanged, this, &ConfigureUi::RequestGameListUpdate);
+ connect(ui->show_size, &QCheckBox::stateChanged, this, &ConfigureUi::RequestGameListUpdate);
+ connect(ui->show_types, &QCheckBox::stateChanged, this, &ConfigureUi::RequestGameListUpdate);
connect(ui->game_icon_size_combobox, QOverload<int>::of(&QComboBox::currentIndexChanged), this,
&ConfigureUi::RequestGameListUpdate);
connect(ui->folder_icon_size_combobox, QOverload<int>::of(&QComboBox::currentIndexChanged),
@@ -111,6 +113,8 @@ void ConfigureUi::ApplyConfiguration() {
ui->theme_combobox->itemData(ui->theme_combobox->currentIndex()).toString();
UISettings::values.show_add_ons = ui->show_add_ons->isChecked();
UISettings::values.show_compat = ui->show_compat->isChecked();
+ UISettings::values.show_size = ui->show_size->isChecked();
+ UISettings::values.show_types = ui->show_types->isChecked();
UISettings::values.game_icon_size = ui->game_icon_size_combobox->currentData().toUInt();
UISettings::values.folder_icon_size = ui->folder_icon_size_combobox->currentData().toUInt();
UISettings::values.row_1_text_id = ui->row_1_text_combobox->currentData().toUInt();
@@ -132,6 +136,8 @@ void ConfigureUi::SetConfiguration() {
ui->language_combobox->findData(UISettings::values.language));
ui->show_add_ons->setChecked(UISettings::values.show_add_ons.GetValue());
ui->show_compat->setChecked(UISettings::values.show_compat.GetValue());
+ ui->show_size->setChecked(UISettings::values.show_size.GetValue());
+ ui->show_types->setChecked(UISettings::values.show_types.GetValue());
ui->game_icon_size_combobox->setCurrentIndex(
ui->game_icon_size_combobox->findData(UISettings::values.game_icon_size.GetValue()));
ui->folder_icon_size_combobox->setCurrentIndex(
diff --git a/src/yuzu/configuration/configure_ui.ui b/src/yuzu/configuration/configure_ui.ui
index f0b719ba3..10bb27312 100644
--- a/src/yuzu/configuration/configure_ui.ui
+++ b/src/yuzu/configuration/configure_ui.ui
@@ -7,7 +7,7 @@
<x>0</x>
<y>0</y>
<width>363</width>
- <height>507</height>
+ <height>562</height>
</rect>
</property>
<property name="windowTitle">
@@ -91,6 +91,20 @@
</widget>
</item>
<item>
+ <widget class="QCheckBox" name="show_size">
+ <property name="text">
+ <string>Show Size Column</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="show_types">
+ <property name="text">
+ <string>Show File Types Column</string>
+ </property>
+ </widget>
+ </item>
+ <item>
<layout class="QHBoxLayout" name="game_icon_size_qhbox_layout_2">
<item>
<widget class="QLabel" name="game_icon_size_label">
diff --git a/src/yuzu/game_list.cpp b/src/yuzu/game_list.cpp
index d6adfca16..5c33c1b0f 100644
--- a/src/yuzu/game_list.cpp
+++ b/src/yuzu/game_list.cpp
@@ -788,6 +788,8 @@ void GameList::PopulateAsync(QVector<UISettings::GameDir>& game_dirs) {
// Update the columns in case UISettings has changed
tree_view->setColumnHidden(COLUMN_ADD_ONS, !UISettings::values.show_add_ons);
tree_view->setColumnHidden(COLUMN_COMPATIBILITY, !UISettings::values.show_compat);
+ tree_view->setColumnHidden(COLUMN_FILE_TYPE, !UISettings::values.show_types);
+ tree_view->setColumnHidden(COLUMN_SIZE, !UISettings::values.show_size);
// Delete any rows that might already exist if we're repopulating
item_model->removeRows(0, item_model->rowCount());
diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp
index 7b16d7f7e..59e56633a 100644
--- a/src/yuzu/main.cpp
+++ b/src/yuzu/main.cpp
@@ -9,7 +9,7 @@
#ifdef __APPLE__
#include <unistd.h> // for chdir
#endif
-#ifdef __linux__
+#ifdef __unix__
#include <csignal>
#include <sys/socket.h>
#endif
@@ -275,7 +275,7 @@ static void OverrideWindowsFont() {
#endif
bool GMainWindow::CheckDarkMode() {
-#ifdef __linux__
+#ifdef __unix__
const QPalette test_palette(qApp->palette());
const QColor text_color = test_palette.color(QPalette::Active, QPalette::Text);
const QColor window_color = test_palette.color(QPalette::Active, QPalette::Window);
@@ -283,7 +283,7 @@ bool GMainWindow::CheckDarkMode() {
#else
// TODO: Windows
return false;
-#endif // __linux__
+#endif // __unix__
}
GMainWindow::GMainWindow(std::unique_ptr<Config> config_, bool has_broken_vulkan)
@@ -291,7 +291,7 @@ GMainWindow::GMainWindow(std::unique_ptr<Config> config_, bool has_broken_vulkan
input_subsystem{std::make_shared<InputCommon::InputSubsystem>()}, config{std::move(config_)},
vfs{std::make_shared<FileSys::RealVfsFilesystem>()},
provider{std::make_unique<FileSys::ManualContentProvider>()} {
-#ifdef __linux__
+#ifdef __unix__
SetupSigInterrupts();
#endif
system->Initialize();
@@ -509,7 +509,7 @@ GMainWindow::~GMainWindow() {
delete render_window;
}
-#ifdef __linux__
+#ifdef __unix__
::close(sig_interrupt_fds[0]);
::close(sig_interrupt_fds[1]);
#endif
@@ -1379,7 +1379,7 @@ void GMainWindow::OnDisplayTitleBars(bool show) {
}
void GMainWindow::SetupPrepareForSleep() {
-#ifdef __linux__
+#ifdef __unix__
auto bus = QDBusConnection::systemBus();
if (bus.isConnected()) {
const bool success = bus.connect(
@@ -1393,7 +1393,7 @@ void GMainWindow::SetupPrepareForSleep() {
} else {
LOG_WARNING(Frontend, "QDBusConnection system bus is not connected");
}
-#endif // __linux__
+#endif // __unix__
}
void GMainWindow::OnPrepareForSleep(bool prepare_sleep) {
@@ -1415,7 +1415,7 @@ void GMainWindow::OnPrepareForSleep(bool prepare_sleep) {
}
}
-#ifdef __linux__
+#ifdef __unix__
static std::optional<QDBusObjectPath> HoldWakeLockLinux(u32 window_id = 0) {
if (!QDBusConnection::sessionBus().isConnected()) {
return {};
@@ -1500,14 +1500,14 @@ void GMainWindow::OnSigInterruptNotifierActivated() {
emit SigInterrupt();
}
-#endif // __linux__
+#endif // __unix__
void GMainWindow::PreventOSSleep() {
#ifdef _WIN32
SetThreadExecutionState(ES_CONTINUOUS | ES_SYSTEM_REQUIRED | ES_DISPLAY_REQUIRED);
#elif defined(HAVE_SDL2)
SDL_DisableScreenSaver();
-#ifdef __linux__
+#ifdef __unix__
auto reply = HoldWakeLockLinux(winId());
if (reply) {
wake_lock = std::move(reply.value());
@@ -1521,7 +1521,7 @@ void GMainWindow::AllowOSSleep() {
SetThreadExecutionState(ES_CONTINUOUS);
#elif defined(HAVE_SDL2)
SDL_EnableScreenSaver();
-#ifdef __linux__
+#ifdef __unix__
if (!wake_lock.path().isEmpty()) {
ReleaseWakeLockLinux(wake_lock);
}
@@ -4070,7 +4070,7 @@ void GMainWindow::SetDiscordEnabled([[maybe_unused]] bool state) {
}
void GMainWindow::changeEvent(QEvent* event) {
-#ifdef __linux__
+#ifdef __unix__
// PaletteChange event appears to only reach so far into the GUI, explicitly asking to
// UpdateUITheme is a decent work around
if (event->type() == QEvent::PaletteChange) {
@@ -4085,7 +4085,7 @@ void GMainWindow::changeEvent(QEvent* event) {
}
last_window_color = window_color;
}
-#endif // __linux__
+#endif // __unix__
QWidget::changeEvent(event);
}
diff --git a/src/yuzu/main.h b/src/yuzu/main.h
index f7aa8e417..150ada84c 100644
--- a/src/yuzu/main.h
+++ b/src/yuzu/main.h
@@ -15,7 +15,7 @@
#include "yuzu/compatibility_list.h"
#include "yuzu/hotkeys.h"
-#ifdef __linux__
+#ifdef __unix__
#include <QVariant>
#include <QtDBus/QDBusInterface>
#include <QtDBus/QtDBus>
@@ -255,7 +255,7 @@ private:
void changeEvent(QEvent* event) override;
void closeEvent(QCloseEvent* event) override;
-#ifdef __linux__
+#ifdef __unix__
void SetupSigInterrupts();
static void HandleSigInterrupt(int);
void OnSigInterruptNotifierActivated();
@@ -435,7 +435,7 @@ private:
// True if TAS recording dialog is visible
bool is_tas_recording_dialog_active{};
-#ifdef __linux__
+#ifdef __unix__
QSocketNotifier* sig_interrupt_notifier;
static std::array<int, 3> sig_interrupt_fds;
diff --git a/src/yuzu/main.ui b/src/yuzu/main.ui
index 74d49dbd4..e670acc30 100644
--- a/src/yuzu/main.ui
+++ b/src/yuzu/main.ui
@@ -55,7 +55,6 @@
<addaction name="separator"/>
<addaction name="menu_recent_files"/>
<addaction name="separator"/>
- <addaction name="separator"/>
<addaction name="action_Load_Amiibo"/>
<addaction name="separator"/>
<addaction name="action_Open_yuzu_Folder"/>
diff --git a/src/yuzu/uisettings.h b/src/yuzu/uisettings.h
index 4f5b2a99d..452038cd9 100644
--- a/src/yuzu/uisettings.h
+++ b/src/yuzu/uisettings.h
@@ -132,6 +132,10 @@ struct Values {
// Compatibility List
Settings::Setting<bool> show_compat{false, "show_compat"};
+ // Size & File Types Column
+ Settings::Setting<bool> show_size{true, "show_size"};
+ Settings::Setting<bool> show_types{true, "show_types"};
+
bool configuration_applied;
bool reset_to_defaults;
Settings::Setting<bool> disable_web_applet{true, "disable_web_applet"};