From a55395847970ac05fdbbb42ff99d091f9d5dad8a Mon Sep 17 00:00:00 2001
From: Coxxs <58-coxxs@users.noreply.git.ryujinx.app>
Date: Fri, 20 Jun 2025 16:14:12 +0800
Subject: [PATCH 01/51] Add GDB Stub
Author: merry, svc64
---
assets/locales.json | 175 ++++
.../Instructions/NativeInterface.cs | 8 +-
src/ARMeilleure/Optimizations.cs | 1 +
src/ARMeilleure/State/ExecutionContext.cs | 30 +-
src/ARMeilleure/Translation/Translator.cs | 29 +-
src/Ryujinx.Common/Logging/LogClass.cs | 1 +
src/Ryujinx.Cpu/AppleHv/Arm/ExceptionLevel.cs | 10 +
src/Ryujinx.Cpu/AppleHv/HvExecutionContext.cs | 81 +-
.../AppleHv/HvExecutionContextShadow.cs | 11 +
.../AppleHv/HvExecutionContextVcpu.cs | 16 +-
.../AppleHv/IHvExecutionContext.cs | 4 +
src/Ryujinx.Cpu/ExceptionCallbacks.cs | 8 +
src/Ryujinx.Cpu/IExecutionContext.cs | 24 +
src/Ryujinx.Cpu/Jit/JitExecutionContext.cs | 25 +
.../LightningJit/State/ExecutionContext.cs | 19 +
src/Ryujinx.HLE/Debugger/DebugState.cs | 9 +
src/Ryujinx.HLE/Debugger/Debugger.cs | 907 ++++++++++++++++++
src/Ryujinx.HLE/Debugger/GdbSignal.cs | 15 +
.../Debugger/GdbXml/aarch64-core.xml | 93 ++
.../Debugger/GdbXml/aarch64-fpu.xml | 159 +++
src/Ryujinx.HLE/Debugger/GdbXml/arm-core.xml | 27 +
src/Ryujinx.HLE/Debugger/GdbXml/arm-neon.xml | 86 ++
src/Ryujinx.HLE/Debugger/GdbXml/target32.xml | 14 +
src/Ryujinx.HLE/Debugger/GdbXml/target64.xml | 14 +
.../Debugger/IDebuggableProcess.cs | 19 +
.../Debugger/Message/BreakInMessage.cs | 6 +
.../Debugger/Message/CommandMessage.cs | 12 +
src/Ryujinx.HLE/Debugger/Message/IMessage.cs | 6 +
.../Debugger/Message/KillMessage.cs | 6 +
.../Debugger/Message/SendNackMessage.cs | 6 +
.../Debugger/Message/ThreadBreakMessage.cs | 18 +
.../Debugger/RegisterInformation.cs | 28 +
src/Ryujinx.HLE/Debugger/StringStream.cs | 109 +++
src/Ryujinx.HLE/HOS/Horizon.cs | 9 +
.../HOS/Kernel/Process/KProcess.cs | 176 +++-
.../Kernel/Process/ProcessExecutionContext.cs | 9 +
.../HOS/Kernel/Threading/KScheduler.cs | 1 +
.../HOS/Kernel/Threading/KThread.cs | 12 +-
src/Ryujinx.HLE/HleConfiguration.cs | 21 +
src/Ryujinx.HLE/Ryujinx.HLE.csproj | 12 +
src/Ryujinx.HLE/Switch.cs | 4 +
src/Ryujinx/Headless/HeadlessRyujinx.Init.cs | 3 +
src/Ryujinx/Headless/Options.cs | 11 +
src/Ryujinx/Systems/AppHost.cs | 19 +
.../Configuration/ConfigurationFileFormat.cs | 15 +
.../ConfigurationState.Migration.cs | 4 +
.../Configuration/ConfigurationState.Model.cs | 40 +
.../Configuration/ConfigurationState.cs | 6 +
.../UI/ViewModels/SettingsViewModel.cs | 46 +
.../UI/Views/Settings/SettingsDebugView.axaml | 64 ++
.../Views/Settings/SettingsDebugView.axaml.cs | 13 +
src/Ryujinx/UI/Windows/SettingsWindow.axaml | 5 +
.../UI/Windows/SettingsWindow.axaml.cs | 3 +
53 files changed, 2428 insertions(+), 21 deletions(-)
create mode 100644 src/Ryujinx.Cpu/AppleHv/Arm/ExceptionLevel.cs
create mode 100644 src/Ryujinx.HLE/Debugger/DebugState.cs
create mode 100644 src/Ryujinx.HLE/Debugger/Debugger.cs
create mode 100644 src/Ryujinx.HLE/Debugger/GdbSignal.cs
create mode 100644 src/Ryujinx.HLE/Debugger/GdbXml/aarch64-core.xml
create mode 100644 src/Ryujinx.HLE/Debugger/GdbXml/aarch64-fpu.xml
create mode 100644 src/Ryujinx.HLE/Debugger/GdbXml/arm-core.xml
create mode 100644 src/Ryujinx.HLE/Debugger/GdbXml/arm-neon.xml
create mode 100644 src/Ryujinx.HLE/Debugger/GdbXml/target32.xml
create mode 100644 src/Ryujinx.HLE/Debugger/GdbXml/target64.xml
create mode 100644 src/Ryujinx.HLE/Debugger/IDebuggableProcess.cs
create mode 100644 src/Ryujinx.HLE/Debugger/Message/BreakInMessage.cs
create mode 100644 src/Ryujinx.HLE/Debugger/Message/CommandMessage.cs
create mode 100644 src/Ryujinx.HLE/Debugger/Message/IMessage.cs
create mode 100644 src/Ryujinx.HLE/Debugger/Message/KillMessage.cs
create mode 100644 src/Ryujinx.HLE/Debugger/Message/SendNackMessage.cs
create mode 100644 src/Ryujinx.HLE/Debugger/Message/ThreadBreakMessage.cs
create mode 100644 src/Ryujinx.HLE/Debugger/RegisterInformation.cs
create mode 100644 src/Ryujinx.HLE/Debugger/StringStream.cs
create mode 100644 src/Ryujinx/UI/Views/Settings/SettingsDebugView.axaml
create mode 100644 src/Ryujinx/UI/Views/Settings/SettingsDebugView.axaml.cs
diff --git a/assets/locales.json b/assets/locales.json
index 2f52ee71b..eeb882e9c 100644
--- a/assets/locales.json
+++ b/assets/locales.json
@@ -25021,6 +25021,181 @@
"zh_CN": "动态 Rich Presence",
"zh_TW": "動態 Rich Presence"
}
+ },
+ {
+ "ID": "SettingsTabDebug",
+ "Translations": {
+ "ar_SA": "",
+ "de_DE": "",
+ "el_GR": "",
+ "en_US": "Debug",
+ "es_ES": "",
+ "fr_FR": "",
+ "he_IL": "",
+ "it_IT": "",
+ "ja_JP": "",
+ "ko_KR": "",
+ "no_NO": "",
+ "pl_PL": "",
+ "pt_BR": "",
+ "ru_RU": "",
+ "sv_SE": "",
+ "th_TH": "",
+ "tr_TR": "",
+ "uk_UA": "",
+ "zh_CN": "",
+ "zh_TW": ""
+ }
+ },
+ {
+ "ID": "SettingsTabDebugTitle",
+ "Translations": {
+ "ar_SA": "",
+ "de_DE": "",
+ "el_GR": "",
+ "en_US": "Debug (WARNING: For developer use only)",
+ "es_ES": "",
+ "fr_FR": "",
+ "he_IL": "",
+ "it_IT": "",
+ "ja_JP": "",
+ "ko_KR": "",
+ "no_NO": "",
+ "pl_PL": "",
+ "pt_BR": "",
+ "ru_RU": "",
+ "sv_SE": "",
+ "th_TH": "",
+ "tr_TR": "",
+ "uk_UA": "",
+ "zh_CN": "",
+ "zh_TW": ""
+ }
+ },
+ {
+ "ID": "EnableGDBStub",
+ "Translations": {
+ "ar_SA": "",
+ "de_DE": "",
+ "el_GR": "",
+ "en_US": "Enable GDB Stub",
+ "es_ES": "",
+ "fr_FR": "",
+ "he_IL": "",
+ "it_IT": "",
+ "ja_JP": "",
+ "ko_KR": "",
+ "no_NO": "",
+ "pl_PL": "",
+ "pt_BR": "",
+ "ru_RU": "",
+ "sv_SE": "",
+ "th_TH": "",
+ "tr_TR": "",
+ "uk_UA": "",
+ "zh_CN": "",
+ "zh_TW": ""
+ }
+ },
+ {
+ "ID": "GDBStubToggleTooltip",
+ "Translations": {
+ "ar_SA": "",
+ "de_DE": "",
+ "el_GR": "",
+ "en_US": "Enables the GDB stub which makes it possible to debug the running application. For development use only!",
+ "es_ES": "",
+ "fr_FR": "",
+ "he_IL": "",
+ "it_IT": "",
+ "ja_JP": "",
+ "ko_KR": "",
+ "no_NO": "",
+ "pl_PL": "",
+ "pt_BR": "",
+ "ru_RU": "",
+ "sv_SE": "",
+ "th_TH": "",
+ "tr_TR": "",
+ "uk_UA": "",
+ "zh_CN": "",
+ "zh_TW": ""
+ }
+ },
+ {
+ "ID": "GDBStubPort",
+ "Translations": {
+ "ar_SA": "",
+ "de_DE": "",
+ "el_GR": "",
+ "en_US": "GDB stub port:",
+ "es_ES": "",
+ "fr_FR": "",
+ "he_IL": "",
+ "it_IT": "",
+ "ja_JP": "",
+ "ko_KR": "",
+ "no_NO": "",
+ "pl_PL": "",
+ "pt_BR": "",
+ "ru_RU": "",
+ "sv_SE": "",
+ "th_TH": "",
+ "tr_TR": "",
+ "uk_UA": "",
+ "zh_CN": "",
+ "zh_TW": ""
+ }
+ },
+ {
+ "ID": "DebuggerSuspendOnStart",
+ "Translations": {
+ "ar_SA": "",
+ "de_DE": "",
+ "el_GR": "",
+ "en_US": "Suspend application on start",
+ "es_ES": "",
+ "fr_FR": "",
+ "he_IL": "",
+ "it_IT": "",
+ "ja_JP": "",
+ "ko_KR": "",
+ "no_NO": "",
+ "pl_PL": "",
+ "pt_BR": "",
+ "ru_RU": "",
+ "sv_SE": "",
+ "th_TH": "",
+ "tr_TR": "",
+ "uk_UA": "",
+ "zh_CN": "",
+ "zh_TW": ""
+ }
+ },
+ {
+ "ID": "DebuggerSuspendOnStartTooltip",
+ "Translations": {
+ "ar_SA": "",
+ "de_DE": "",
+ "el_GR": "",
+ "en_US": "Suspends the application before executing the first instruction, allowing for debugging from the earliest point.",
+ "es_ES": "",
+ "fr_FR": "",
+ "he_IL": "",
+ "it_IT": "",
+ "ja_JP": "",
+ "ko_KR": "",
+ "no_NO": "",
+ "pl_PL": "",
+ "pt_BR": "",
+ "ru_RU": "",
+ "sv_SE": "",
+ "th_TH": "",
+ "tr_TR": "",
+ "uk_UA": "",
+ "zh_CN": "",
+ "zh_TW": ""
+ }
}
]
}
diff --git a/src/ARMeilleure/Instructions/NativeInterface.cs b/src/ARMeilleure/Instructions/NativeInterface.cs
index b4922629b..c44f24b7c 100644
--- a/src/ARMeilleure/Instructions/NativeInterface.cs
+++ b/src/ARMeilleure/Instructions/NativeInterface.cs
@@ -3,6 +3,8 @@ using ARMeilleure.State;
using ARMeilleure.Translation;
using System;
using System.Runtime.InteropServices;
+using System.Threading;
+using ExecutionContext = ARMeilleure.State.ExecutionContext;
namespace ARMeilleure.Instructions
{
@@ -200,7 +202,11 @@ namespace ARMeilleure.Instructions
ExecutionContext context = GetContext();
- context.CheckInterrupt();
+ // If debugging, we'll handle interrupts outside
+ if (!Optimizations.EnableDebugging)
+ {
+ context.CheckInterrupt();
+ }
Statistics.ResumeTimer();
diff --git a/src/ARMeilleure/Optimizations.cs b/src/ARMeilleure/Optimizations.cs
index 18390de31..6dd7befe7 100644
--- a/src/ARMeilleure/Optimizations.cs
+++ b/src/ARMeilleure/Optimizations.cs
@@ -12,6 +12,7 @@ namespace ARMeilleure
public static bool AllowLcqInFunctionTable { get; set; } = true;
public static bool UseUnmanagedDispatchLoop { get; set; } = true;
+ public static bool EnableDebugging { get; set; } = false;
public static bool UseAdvSimdIfAvailable { get; set; } = true;
public static bool UseArm64AesIfAvailable { get; set; } = true;
diff --git a/src/ARMeilleure/State/ExecutionContext.cs b/src/ARMeilleure/State/ExecutionContext.cs
index 223e59d79..2978bf27a 100644
--- a/src/ARMeilleure/State/ExecutionContext.cs
+++ b/src/ARMeilleure/State/ExecutionContext.cs
@@ -1,4 +1,7 @@
using ARMeilleure.Memory;
+using System.Collections.Concurrent;
+using System.Diagnostics;
+using System.Threading;
namespace ARMeilleure.State
{
@@ -10,7 +13,7 @@ namespace ARMeilleure.State
internal nint NativeContextPtr => _nativeContext.BasePtr;
- private bool _interrupted;
+ internal bool Interrupted { get; private set; }
private readonly ICounter _counter;
@@ -65,6 +68,8 @@ namespace ARMeilleure.State
public bool IsAarch32 { get; set; }
+ public ulong ThreadUid { get; set; }
+
internal ExecutionMode ExecutionMode
{
get
@@ -90,14 +95,19 @@ namespace ARMeilleure.State
private readonly ExceptionCallbackNoArgs _interruptCallback;
private readonly ExceptionCallback _breakCallback;
+ private readonly ExceptionCallbackNoArgs _stepCallback;
private readonly ExceptionCallback _supervisorCallback;
private readonly ExceptionCallback _undefinedCallback;
+ internal int ShouldStep;
+ public ulong DebugPc { get; set; }
+
public ExecutionContext(
IJitMemoryAllocator allocator,
ICounter counter,
ExceptionCallbackNoArgs interruptCallback = null,
ExceptionCallback breakCallback = null,
+ ExceptionCallbackNoArgs stepCallback = null,
ExceptionCallback supervisorCallback = null,
ExceptionCallback undefinedCallback = null)
{
@@ -105,6 +115,7 @@ namespace ARMeilleure.State
_counter = counter;
_interruptCallback = interruptCallback;
_breakCallback = breakCallback;
+ _stepCallback = stepCallback;
_supervisorCallback = supervisorCallback;
_undefinedCallback = undefinedCallback;
@@ -127,9 +138,9 @@ namespace ARMeilleure.State
internal void CheckInterrupt()
{
- if (_interrupted)
+ if (Interrupted)
{
- _interrupted = false;
+ Interrupted = false;
_interruptCallback?.Invoke(this);
}
@@ -139,7 +150,18 @@ namespace ARMeilleure.State
public void RequestInterrupt()
{
- _interrupted = true;
+ Interrupted = true;
+ }
+
+ public void StepHandler()
+ {
+ _stepCallback.Invoke(this);
+ }
+
+ public void RequestDebugStep()
+ {
+ Interlocked.Exchange(ref ShouldStep, 1);
+ RequestInterrupt();
}
internal void OnBreak(ulong address, int imm)
diff --git a/src/ARMeilleure/Translation/Translator.cs b/src/ARMeilleure/Translation/Translator.cs
index d8528cfd6..343e361a5 100644
--- a/src/ARMeilleure/Translation/Translator.cs
+++ b/src/ARMeilleure/Translation/Translator.cs
@@ -119,7 +119,25 @@ namespace ARMeilleure.Translation
NativeInterface.RegisterThread(context, Memory, this);
- if (Optimizations.UseUnmanagedDispatchLoop)
+ if (Optimizations.EnableDebugging)
+ {
+ context.DebugPc = address;
+ do
+ {
+ if (Interlocked.CompareExchange(ref context.ShouldStep, 0, 1) == 1)
+ {
+ context.DebugPc = Step(context, context.DebugPc);
+ context.StepHandler();
+ }
+ else
+ {
+ context.DebugPc = ExecuteSingle(context, context.DebugPc);
+ }
+ context.CheckInterrupt();
+ }
+ while (context.Running && context.DebugPc != 0);
+ }
+ else if (Optimizations.UseUnmanagedDispatchLoop)
{
Stubs.DispatchLoop(context.NativeContextPtr, address);
}
@@ -175,7 +193,7 @@ namespace ARMeilleure.Translation
return nextAddr;
}
- public ulong Step(State.ExecutionContext context, ulong address)
+ private ulong Step(State.ExecutionContext context, ulong address)
{
TranslatedFunction func = Translate(address, context.ExecutionMode, highCq: false, singleStep: true);
@@ -228,7 +246,7 @@ namespace ARMeilleure.Translation
Stubs,
address,
highCq,
- _ptc.State != PtcState.Disabled,
+ _ptc.State != PtcState.Disabled && !Optimizations.EnableDebugging,
mode: Aarch32Mode.User);
Logger.StartPass(PassName.Decoding);
@@ -367,9 +385,8 @@ namespace ARMeilleure.Translation
if (block.Exit)
{
- // Left option here as it may be useful if we need to return to managed rather than tail call in
- // future. (eg. for debug)
- bool useReturns = false;
+ // Return to managed rather than tail call.
+ bool useReturns = Optimizations.EnableDebugging;
InstEmitFlowHelper.EmitVirtualJump(context, Const(block.Address), isReturn: useReturns);
}
diff --git a/src/Ryujinx.Common/Logging/LogClass.cs b/src/Ryujinx.Common/Logging/LogClass.cs
index a4117580e..89f0336dc 100644
--- a/src/Ryujinx.Common/Logging/LogClass.cs
+++ b/src/Ryujinx.Common/Logging/LogClass.cs
@@ -13,6 +13,7 @@ namespace Ryujinx.Common.Logging
Cpu,
Emulation,
FFmpeg,
+ GdbStub,
Font,
Gpu,
Hid,
diff --git a/src/Ryujinx.Cpu/AppleHv/Arm/ExceptionLevel.cs b/src/Ryujinx.Cpu/AppleHv/Arm/ExceptionLevel.cs
new file mode 100644
index 000000000..08114e12a
--- /dev/null
+++ b/src/Ryujinx.Cpu/AppleHv/Arm/ExceptionLevel.cs
@@ -0,0 +1,10 @@
+namespace Ryujinx.Cpu.AppleHv.Arm
+{
+ enum ExceptionLevel : uint
+ {
+ PstateMask = 0xfffffff0,
+ EL1h = 0b0101,
+ El1t = 0b0100,
+ EL0 = 0b0000,
+ }
+}
diff --git a/src/Ryujinx.Cpu/AppleHv/HvExecutionContext.cs b/src/Ryujinx.Cpu/AppleHv/HvExecutionContext.cs
index 53cea5385..f13662e44 100644
--- a/src/Ryujinx.Cpu/AppleHv/HvExecutionContext.cs
+++ b/src/Ryujinx.Cpu/AppleHv/HvExecutionContext.cs
@@ -11,7 +11,18 @@ namespace Ryujinx.Cpu.AppleHv
class HvExecutionContext : IExecutionContext
{
///
- public ulong Pc => _impl.ElrEl1;
+ public ulong Pc
+ {
+ get
+ {
+ uint currentEl = Pstate & ~((uint)ExceptionLevel.PstateMask);
+ if (currentEl == (uint)ExceptionLevel.EL1h)
+ {
+ return _impl.ElrEl1;
+ }
+ return _impl.Pc;
+ }
+ }
///
public long TpidrEl0
@@ -48,6 +59,9 @@ namespace Ryujinx.Cpu.AppleHv
set => _impl.Fpsr = value;
}
+ ///
+ public ulong ThreadUid { get; set; }
+
///
public bool IsAarch32
{
@@ -67,6 +81,7 @@ namespace Ryujinx.Cpu.AppleHv
private readonly ICounter _counter;
private readonly IHvExecutionContext _shadowContext;
private IHvExecutionContext _impl;
+ private int _shouldStep;
private readonly ExceptionCallbacks _exceptionCallbacks;
@@ -103,6 +118,11 @@ namespace Ryujinx.Cpu.AppleHv
_exceptionCallbacks.BreakCallback?.Invoke(this, address, imm);
}
+ private void StepHandler()
+ {
+ _exceptionCallbacks.StepCallback?.Invoke(this);
+ }
+
private void SupervisorCallHandler(ulong address, int imm)
{
_exceptionCallbacks.SupervisorCallback?.Invoke(this, address, imm);
@@ -127,6 +147,30 @@ namespace Ryujinx.Cpu.AppleHv
return Interlocked.Exchange(ref _interruptRequested, 0) != 0;
}
+ ///
+ public void RequestDebugStep()
+ {
+ Interlocked.Exchange(ref _shouldStep, 1);
+ }
+
+ ///
+ public ulong DebugPc
+ {
+ get => Pc;
+ set
+ {
+ uint currentEl = Pstate & ~((uint)ExceptionLevel.PstateMask);
+ if (currentEl == (uint)ExceptionLevel.EL1h)
+ {
+ _impl.ElrEl1 = value;
+ }
+ else
+ {
+ _impl.Pc = value;
+ }
+ }
+ }
+
///
public void StopRunning()
{
@@ -142,6 +186,22 @@ namespace Ryujinx.Cpu.AppleHv
while (Running)
{
+ if (Interlocked.CompareExchange(ref _shouldStep, 0, 1) == 1)
+ {
+ uint currentEl = Pstate & ~((uint)ExceptionLevel.PstateMask);
+ if (currentEl == (uint)ExceptionLevel.EL1h)
+ {
+ HvApi.hv_vcpu_get_sys_reg(vcpu.Handle, HvSysReg.SPSR_EL1, out ulong spsr).ThrowOnError();
+ spsr |= (1 << 21);
+ HvApi.hv_vcpu_set_sys_reg(vcpu.Handle, HvSysReg.SPSR_EL1, spsr);
+ }
+ else
+ {
+ Pstate |= (1 << 21);
+ }
+ HvApi.hv_vcpu_set_sys_reg(vcpu.Handle, HvSysReg.MDSCR_EL1, 1);
+ }
+
HvApi.hv_vcpu_run(vcpu.Handle).ThrowOnError();
HvExitReason reason = vcpu.ExitInfo->Reason;
@@ -209,6 +269,20 @@ namespace Ryujinx.Cpu.AppleHv
SupervisorCallHandler(elr - 4UL, id);
vcpu = RentFromPool(memoryManager.AddressSpace, vcpu);
break;
+ case ExceptionClass.SoftwareStepLowerEl:
+ HvApi.hv_vcpu_get_sys_reg(vcpuHandle, HvSysReg.SPSR_EL1, out ulong spsr).ThrowOnError();
+ spsr &= ~((ulong)(1 << 21));
+ HvApi.hv_vcpu_set_sys_reg(vcpuHandle, HvSysReg.SPSR_EL1, spsr).ThrowOnError();
+ HvApi.hv_vcpu_set_sys_reg(vcpuHandle, HvSysReg.MDSCR_EL1, 0);
+ ReturnToPool(vcpu);
+ StepHandler();
+ vcpu = RentFromPool(memoryManager.AddressSpace, vcpu);
+ break;
+ case ExceptionClass.BrkAarch64:
+ ReturnToPool(vcpu);
+ BreakHandler(elr, (ushort)esr);
+ vcpu = RentFromPool(memoryManager.AddressSpace, vcpu);
+ break;
default:
throw new Exception($"Unhandled guest exception {ec}.");
}
@@ -219,10 +293,7 @@ namespace Ryujinx.Cpu.AppleHv
// TODO: Invalidate only the range that was modified?
return HvAddressSpace.KernelRegionTlbiEretAddress;
}
- else
- {
- return HvAddressSpace.KernelRegionEretAddress;
- }
+ return HvAddressSpace.KernelRegionEretAddress;
}
private static void DataAbort(MemoryTracking tracking, ulong vcpu, uint esr)
diff --git a/src/Ryujinx.Cpu/AppleHv/HvExecutionContextShadow.cs b/src/Ryujinx.Cpu/AppleHv/HvExecutionContextShadow.cs
index 6ce8e1800..4ea5f276d 100644
--- a/src/Ryujinx.Cpu/AppleHv/HvExecutionContextShadow.cs
+++ b/src/Ryujinx.Cpu/AppleHv/HvExecutionContextShadow.cs
@@ -18,6 +18,8 @@ namespace Ryujinx.Cpu.AppleHv
public bool IsAarch32 { get; set; }
+ public ulong ThreadUid { get; set; }
+
private readonly ulong[] _x;
private readonly V128[] _v;
@@ -46,5 +48,14 @@ namespace Ryujinx.Cpu.AppleHv
{
_v[index] = value;
}
+
+ public void RequestInterrupt()
+ {
+ }
+
+ public bool GetAndClearInterruptRequested()
+ {
+ return false;
+ }
}
}
diff --git a/src/Ryujinx.Cpu/AppleHv/HvExecutionContextVcpu.cs b/src/Ryujinx.Cpu/AppleHv/HvExecutionContextVcpu.cs
index 1949cabdf..9ef03e61e 100644
--- a/src/Ryujinx.Cpu/AppleHv/HvExecutionContextVcpu.cs
+++ b/src/Ryujinx.Cpu/AppleHv/HvExecutionContextVcpu.cs
@@ -2,6 +2,7 @@ using ARMeilleure.State;
using Ryujinx.Memory;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
+using System.Threading;
namespace Ryujinx.Cpu.AppleHv
{
@@ -13,6 +14,8 @@ namespace Ryujinx.Cpu.AppleHv
private static readonly SetSimdFpReg _setSimdFpReg;
private static readonly nint _setSimdFpRegNativePtr;
+ public ulong ThreadUid { get; set; }
+
static HvExecutionContextVcpu()
{
// .NET does not support passing vectors by value, so we need to pass a pointer and use a native
@@ -135,6 +138,7 @@ namespace Ryujinx.Cpu.AppleHv
}
private readonly ulong _vcpu;
+ private int _interruptRequested;
public HvExecutionContextVcpu(ulong vcpu)
{
@@ -180,8 +184,16 @@ namespace Ryujinx.Cpu.AppleHv
public void RequestInterrupt()
{
- ulong vcpu = _vcpu;
- HvApi.hv_vcpus_exit(ref vcpu, 1);
+ if (Interlocked.Exchange(ref _interruptRequested, 1) == 0)
+ {
+ ulong vcpu = _vcpu;
+ HvApi.hv_vcpus_exit(ref vcpu, 1);
+ }
+ }
+
+ public bool GetAndClearInterruptRequested()
+ {
+ return Interlocked.Exchange(ref _interruptRequested, 0) != 0;
}
}
}
diff --git a/src/Ryujinx.Cpu/AppleHv/IHvExecutionContext.cs b/src/Ryujinx.Cpu/AppleHv/IHvExecutionContext.cs
index 54b73acc6..134405b5c 100644
--- a/src/Ryujinx.Cpu/AppleHv/IHvExecutionContext.cs
+++ b/src/Ryujinx.Cpu/AppleHv/IHvExecutionContext.cs
@@ -15,6 +15,7 @@ namespace Ryujinx.Cpu.AppleHv
uint Fpcr { get; set; }
uint Fpsr { get; set; }
+ ulong ThreadUid { get; set; }
ulong GetX(int index);
void SetX(int index, ulong value);
@@ -39,5 +40,8 @@ namespace Ryujinx.Cpu.AppleHv
SetV(i, context.GetV(i));
}
}
+
+ void RequestInterrupt();
+ bool GetAndClearInterruptRequested();
}
}
diff --git a/src/Ryujinx.Cpu/ExceptionCallbacks.cs b/src/Ryujinx.Cpu/ExceptionCallbacks.cs
index d9293302b..6e50b4d70 100644
--- a/src/Ryujinx.Cpu/ExceptionCallbacks.cs
+++ b/src/Ryujinx.Cpu/ExceptionCallbacks.cs
@@ -29,6 +29,11 @@ namespace Ryujinx.Cpu
///
public readonly ExceptionCallback BreakCallback;
+ ///
+ /// Handler for CPU software interrupts caused by single-stepping.
+ ///
+ public readonly ExceptionCallbackNoArgs StepCallback;
+
///
/// Handler for CPU software interrupts caused by the Arm SVC instruction.
///
@@ -47,16 +52,19 @@ namespace Ryujinx.Cpu
///
/// Handler for CPU interrupts triggered using
/// Handler for CPU software interrupts caused by the Arm BRK instruction
+ /// Handler for CPU software interrupts caused by single-stepping
/// Handler for CPU software interrupts caused by the Arm SVC instruction
/// Handler for CPU software interrupts caused by any undefined Arm instruction
public ExceptionCallbacks(
ExceptionCallbackNoArgs interruptCallback = null,
ExceptionCallback breakCallback = null,
+ ExceptionCallbackNoArgs stepCallback = null,
ExceptionCallback supervisorCallback = null,
ExceptionCallback undefinedCallback = null)
{
InterruptCallback = interruptCallback;
BreakCallback = breakCallback;
+ StepCallback = stepCallback;
SupervisorCallback = supervisorCallback;
UndefinedCallback = undefinedCallback;
}
diff --git a/src/Ryujinx.Cpu/IExecutionContext.cs b/src/Ryujinx.Cpu/IExecutionContext.cs
index c38210800..df0c94278 100644
--- a/src/Ryujinx.Cpu/IExecutionContext.cs
+++ b/src/Ryujinx.Cpu/IExecutionContext.cs
@@ -1,5 +1,6 @@
using ARMeilleure.State;
using System;
+using System.Threading;
namespace Ryujinx.Cpu
{
@@ -46,6 +47,11 @@ namespace Ryujinx.Cpu
///
bool IsAarch32 { get; set; }
+ ///
+ /// Thread UID.
+ ///
+ public ulong ThreadUid { get; set; }
+
///
/// Indicates whenever the CPU is still running code.
///
@@ -108,5 +114,23 @@ namespace Ryujinx.Cpu
/// If you only need to pause the thread temporarily, use instead.
///
void StopRunning();
+
+ ///
+ /// Requests the thread to stop running temporarily and call .
+ ///
+ ///
+ /// The thread might not pause immediately.
+ /// One must not assume that guest code is no longer being executed by the thread after calling this function.
+ /// After single stepping, the thread should call call .
+ ///
+ void RequestDebugStep();
+
+ ///
+ /// Current Program Counter (for debugging).
+ ///
+ ///
+ /// PC register for the debugger. Must not be accessed while the thread isn't stopped for debugging.
+ ///
+ ulong DebugPc { get; set; }
}
}
diff --git a/src/Ryujinx.Cpu/Jit/JitExecutionContext.cs b/src/Ryujinx.Cpu/Jit/JitExecutionContext.cs
index f15486e68..d4775f3ed 100644
--- a/src/Ryujinx.Cpu/Jit/JitExecutionContext.cs
+++ b/src/Ryujinx.Cpu/Jit/JitExecutionContext.cs
@@ -1,5 +1,7 @@
using ARMeilleure.Memory;
using ARMeilleure.State;
+using System.Threading;
+using ExecutionContext = ARMeilleure.State.ExecutionContext;
namespace Ryujinx.Cpu.Jit
{
@@ -53,6 +55,13 @@ namespace Ryujinx.Cpu.Jit
set => _impl.IsAarch32 = value;
}
+ ///
+ public ulong ThreadUid
+ {
+ get => _impl.ThreadUid;
+ set => _impl.ThreadUid = value;
+ }
+
///
public bool Running => _impl.Running;
@@ -65,6 +74,7 @@ namespace Ryujinx.Cpu.Jit
counter,
InterruptHandler,
BreakHandler,
+ StepHandler,
SupervisorCallHandler,
UndefinedHandler);
@@ -93,6 +103,11 @@ namespace Ryujinx.Cpu.Jit
_exceptionCallbacks.BreakCallback?.Invoke(this, address, imm);
}
+ private void StepHandler(ExecutionContext context)
+ {
+ _exceptionCallbacks.StepCallback?.Invoke(this);
+ }
+
private void SupervisorCallHandler(ExecutionContext context, ulong address, int imm)
{
_exceptionCallbacks.SupervisorCallback?.Invoke(this, address, imm);
@@ -109,6 +124,16 @@ namespace Ryujinx.Cpu.Jit
_impl.RequestInterrupt();
}
+ ///
+ public void RequestDebugStep() => _impl.RequestDebugStep();
+
+ ///
+ public ulong DebugPc
+ {
+ get => _impl.DebugPc;
+ set => _impl.DebugPc = value;
+ }
+
///
public void StopRunning()
{
diff --git a/src/Ryujinx.Cpu/LightningJit/State/ExecutionContext.cs b/src/Ryujinx.Cpu/LightningJit/State/ExecutionContext.cs
index a366dcca6..fc75e5185 100644
--- a/src/Ryujinx.Cpu/LightningJit/State/ExecutionContext.cs
+++ b/src/Ryujinx.Cpu/LightningJit/State/ExecutionContext.cs
@@ -1,6 +1,7 @@
using ARMeilleure.Memory;
using ARMeilleure.State;
using System;
+using System.Threading;
namespace Ryujinx.Cpu.LightningJit.State
{
@@ -51,6 +52,8 @@ namespace Ryujinx.Cpu.LightningJit.State
}
public bool IsAarch32 { get; set; }
+
+ public ulong ThreadUid { get; set; }
internal ExecutionMode ExecutionMode
{
@@ -77,15 +80,20 @@ namespace Ryujinx.Cpu.LightningJit.State
private readonly ExceptionCallbackNoArgs _interruptCallback;
private readonly ExceptionCallback _breakCallback;
+ private readonly ExceptionCallbackNoArgs _stepCallback;
private readonly ExceptionCallback _supervisorCallback;
private readonly ExceptionCallback _undefinedCallback;
+ internal int ShouldStep;
+ public ulong DebugPc { get; set; }
+
public ExecutionContext(IJitMemoryAllocator allocator, ICounter counter, ExceptionCallbacks exceptionCallbacks)
{
_nativeContext = new NativeContext(allocator);
_counter = counter;
_interruptCallback = exceptionCallbacks.InterruptCallback;
_breakCallback = exceptionCallbacks.BreakCallback;
+ _stepCallback = exceptionCallbacks.StepCallback;
_supervisorCallback = exceptionCallbacks.SupervisorCallback;
_undefinedCallback = exceptionCallbacks.UndefinedCallback;
@@ -117,6 +125,17 @@ namespace Ryujinx.Cpu.LightningJit.State
_interrupted = true;
}
+ public void StepHandler()
+ {
+ _stepCallback.Invoke(this);
+ }
+
+ public void RequestDebugStep()
+ {
+ Interlocked.Exchange(ref ShouldStep, 1);
+ RequestInterrupt();
+ }
+
internal void OnBreak(ulong address, int imm)
{
_breakCallback?.Invoke(this, address, imm);
diff --git a/src/Ryujinx.HLE/Debugger/DebugState.cs b/src/Ryujinx.HLE/Debugger/DebugState.cs
new file mode 100644
index 000000000..d2efa2bff
--- /dev/null
+++ b/src/Ryujinx.HLE/Debugger/DebugState.cs
@@ -0,0 +1,9 @@
+namespace Ryujinx.HLE.Debugger
+{
+ public enum DebugState
+ {
+ Running,
+ Stopping,
+ Stopped,
+ }
+}
diff --git a/src/Ryujinx.HLE/Debugger/Debugger.cs b/src/Ryujinx.HLE/Debugger/Debugger.cs
new file mode 100644
index 000000000..33c380175
--- /dev/null
+++ b/src/Ryujinx.HLE/Debugger/Debugger.cs
@@ -0,0 +1,907 @@
+using ARMeilleure.State;
+using Ryujinx.Common;
+using Ryujinx.Common.Logging;
+using Ryujinx.HLE.HOS.Kernel;
+using Ryujinx.HLE.HOS.Kernel.Threading;
+using Ryujinx.Memory;
+using System;
+using System.Collections.Concurrent;
+using System.IO;
+using System.Linq;
+using System.Net;
+using System.Net.Sockets;
+using System.Text;
+using System.Threading;
+using IExecutionContext = Ryujinx.Cpu.IExecutionContext;
+
+namespace Ryujinx.HLE.Debugger
+{
+ public class Debugger : IDisposable
+ {
+ internal Switch Device { get; private set; }
+
+ public ushort GdbStubPort { get; private set; }
+
+ private TcpListener ListenerSocket;
+ private Socket ClientSocket = null;
+ private NetworkStream ReadStream = null;
+ private NetworkStream WriteStream = null;
+ private BlockingCollection Messages = new BlockingCollection(1);
+ private Thread DebuggerThread;
+ private Thread MessageHandlerThread;
+ private bool _shuttingDown = false;
+
+ private ulong? cThread;
+ private ulong? gThread;
+
+ public Debugger(Switch device, ushort port)
+ {
+ Device = device;
+ GdbStubPort = port;
+
+ ARMeilleure.Optimizations.EnableDebugging = true;
+
+ DebuggerThread = new Thread(DebuggerThreadMain);
+ DebuggerThread.Start();
+ MessageHandlerThread = new Thread(MessageHandlerMain);
+ MessageHandlerThread.Start();
+ }
+
+ private IDebuggableProcess DebugProcess => Device.System.DebugGetApplicationProcess();
+ private KThread[] GetThreads() => DebugProcess.GetThreadUids().Select(x => DebugProcess.GetThread(x)).ToArray();
+ private bool IsProcessAarch32 => DebugProcess.GetThread(gThread.Value).Context.IsAarch32;
+ private KernelContext KernelContext => Device.System.KernelContext;
+
+ const int GdbRegisterCount64 = 68;
+ const int GdbRegisterCount32 = 66;
+ /* FPCR = FPSR & ~FpcrMask
+ All of FPCR's bits are reserved in FPCR and vice versa,
+ see ARM's documentation. */
+ private const uint FpcrMask = 0xfc1fffff;
+
+ private string GdbReadRegister64(IExecutionContext state, int gdbRegId)
+ {
+ switch (gdbRegId)
+ {
+ case >= 0 and <= 31:
+ return ToHex(BitConverter.GetBytes(state.GetX(gdbRegId)));
+ case 32:
+ return ToHex(BitConverter.GetBytes(state.DebugPc));
+ case 33:
+ return ToHex(BitConverter.GetBytes(state.Pstate));
+ case >= 34 and <= 65:
+ return ToHex(state.GetV(gdbRegId - 34).ToArray());
+ case 66:
+ return ToHex(BitConverter.GetBytes((uint)state.Fpsr));
+ case 67:
+ return ToHex(BitConverter.GetBytes((uint)state.Fpcr));
+ default:
+ return null;
+ }
+ }
+
+ private bool GdbWriteRegister64(IExecutionContext state, int gdbRegId, StringStream ss)
+ {
+ switch (gdbRegId)
+ {
+ case >= 0 and <= 31:
+ {
+ ulong value = ss.ReadLengthAsHex(16);
+ state.SetX(gdbRegId, value);
+ return true;
+ }
+ case 32:
+ {
+ ulong value = ss.ReadLengthAsHex(16);
+ state.DebugPc = value;
+ return true;
+ }
+ case 33:
+ {
+ ulong value = ss.ReadLengthAsHex(8);
+ state.Pstate = (uint)value;
+ return true;
+ }
+ case >= 34 and <= 65:
+ {
+ ulong value0 = ss.ReadLengthAsHex(16);
+ ulong value1 = ss.ReadLengthAsHex(16);
+ state.SetV(gdbRegId - 34, new V128(value0, value1));
+ return true;
+ }
+ case 66:
+ {
+ ulong value = ss.ReadLengthAsHex(8);
+ state.Fpsr = (uint)value;
+ return true;
+ }
+ case 67:
+ {
+ ulong value = ss.ReadLengthAsHex(8);
+ state.Fpcr = (uint)value;
+ return true;
+ }
+ default:
+ return false;
+ }
+ }
+
+ private string GdbReadRegister32(IExecutionContext state, int gdbRegId)
+ {
+ switch (gdbRegId)
+ {
+ case >= 0 and <= 14:
+ return ToHex(BitConverter.GetBytes((uint)state.GetX(gdbRegId)));
+ case 15:
+ return ToHex(BitConverter.GetBytes((uint)state.DebugPc));
+ case 16:
+ return ToHex(BitConverter.GetBytes((uint)state.Pstate));
+ case >= 17 and <= 32:
+ return ToHex(state.GetV(gdbRegId - 17).ToArray());
+ case >= 33 and <= 64:
+ int reg = (gdbRegId - 33);
+ int n = reg / 2;
+ int shift = reg % 2;
+ ulong value = state.GetV(n).Extract(shift);
+ return ToHex(BitConverter.GetBytes(value));
+ case 65:
+ uint fpscr = (uint)state.Fpsr | (uint)state.Fpcr;
+ return ToHex(BitConverter.GetBytes(fpscr));
+ default:
+ return null;
+ }
+ }
+
+ private bool GdbWriteRegister32(IExecutionContext state, int gdbRegId, StringStream ss)
+ {
+ switch (gdbRegId)
+ {
+ case >= 0 and <= 14:
+ {
+ ulong value = ss.ReadLengthAsHex(8);
+ state.SetX(gdbRegId, value);
+ return true;
+ }
+ case 15:
+ {
+ ulong value = ss.ReadLengthAsHex(8);
+ state.DebugPc = value;
+ return true;
+ }
+ case 16:
+ {
+ ulong value = ss.ReadLengthAsHex(8);
+ state.Pstate = (uint)value;
+ return true;
+ }
+ case >= 17 and <= 32:
+ {
+ ulong value0 = ss.ReadLengthAsHex(16);
+ ulong value1 = ss.ReadLengthAsHex(16);
+ state.SetV(gdbRegId - 17, new V128(value0, value1));
+ return true;
+ }
+ case >= 33 and <= 64:
+ {
+ ulong value = ss.ReadLengthAsHex(16);
+ int regId = (gdbRegId - 33);
+ int regNum = regId / 2;
+ int shift = regId % 2;
+ V128 reg = state.GetV(regNum);
+ reg.Insert(shift, value);
+ return true;
+ }
+ case 65:
+ {
+ ulong value = ss.ReadLengthAsHex(8);
+ state.Fpsr = (uint)value & FpcrMask;
+ state.Fpcr = (uint)value & ~FpcrMask;
+ return true;
+ }
+ default:
+ return false;
+ }
+ }
+
+ private void MessageHandlerMain()
+ {
+ while (!_shuttingDown)
+ {
+ IMessage msg = Messages.Take();
+ switch (msg)
+ {
+ case BreakInMessage:
+ Logger.Notice.Print(LogClass.GdbStub, "Break-in requested");
+ CommandQuery();
+ break;
+
+ case SendNackMessage:
+ WriteStream.WriteByte((byte)'-');
+ break;
+
+ case CommandMessage { Command: var cmd }:
+ Logger.Debug?.Print(LogClass.GdbStub, $"Received Command: {cmd}");
+ WriteStream.WriteByte((byte)'+');
+ ProcessCommand(cmd);
+ break;
+
+ case ThreadBreakMessage { Context: var ctx }:
+ DebugProcess.DebugStop();
+ Reply($"T05thread:{ctx.ThreadUid:x};");
+ break;
+
+ case KillMessage:
+ return;
+ }
+ }
+ }
+
+ private void ProcessCommand(string cmd)
+ {
+ Logger.Debug?.Print(LogClass.GdbStub, $"Receive: {cmd}");
+ StringStream ss = new StringStream(cmd);
+
+ switch (ss.ReadChar())
+ {
+ case '!':
+ if (!ss.IsEmpty())
+ {
+ goto unknownCommand;
+ }
+
+ // Enable extended mode
+ ReplyOK();
+ break;
+ case '?':
+ if (!ss.IsEmpty())
+ {
+ goto unknownCommand;
+ }
+
+ CommandQuery();
+ break;
+ case 'c':
+ CommandContinue(ss.IsEmpty() ? null : ss.ReadRemainingAsHex());
+ break;
+ case 'D':
+ if (!ss.IsEmpty())
+ {
+ goto unknownCommand;
+ }
+
+ CommandDetach();
+ break;
+ case 'g':
+ if (!ss.IsEmpty())
+ {
+ goto unknownCommand;
+ }
+
+ CommandReadRegisters();
+ break;
+ case 'G':
+ CommandWriteRegisters(ss);
+ break;
+ case 'H':
+ {
+ char op = ss.ReadChar();
+ ulong? threadId = ss.ReadRemainingAsThreadUid();
+ CommandSetThread(op, threadId);
+ break;
+ }
+ case 'k':
+ Logger.Notice.Print(LogClass.GdbStub, "Kill request received, detach instead");
+ Reply("");
+ CommandDetach();
+ break;
+ case 'm':
+ {
+ ulong addr = ss.ReadUntilAsHex(',');
+ ulong len = ss.ReadRemainingAsHex();
+ CommandReadMemory(addr, len);
+ break;
+ }
+ case 'M':
+ {
+ ulong addr = ss.ReadUntilAsHex(',');
+ ulong len = ss.ReadUntilAsHex(':');
+ CommandWriteMemory(addr, len, ss);
+ break;
+ }
+ case 'p':
+ {
+ ulong gdbRegId = ss.ReadRemainingAsHex();
+ CommandReadRegister((int)gdbRegId);
+ break;
+ }
+ case 'P':
+ {
+ ulong gdbRegId = ss.ReadUntilAsHex('=');
+ CommandWriteRegister((int)gdbRegId, ss);
+ break;
+ }
+ case 'q':
+ if (ss.ConsumeRemaining("GDBServerVersion"))
+ {
+ Reply($"name:Ryujinx;version:{ReleaseInformation.Version};");
+ break;
+ }
+
+ if (ss.ConsumeRemaining("HostInfo"))
+ {
+ if (IsProcessAarch32)
+ {
+ Reply(
+ $"triple:{ToHex("arm-unknown-linux-android")};endian:little;ptrsize:4;hostname:{ToHex("Ryujinx")};");
+ }
+ else
+ {
+ Reply(
+ $"triple:{ToHex("aarch64-unknown-linux-android")};endian:little;ptrsize:8;hostname:{ToHex("Ryujinx")};");
+ }
+ break;
+ }
+
+ if (ss.ConsumeRemaining("ProcessInfo"))
+ {
+ if (IsProcessAarch32)
+ {
+ Reply(
+ $"pid:1;cputype:12;cpusubtype:0;triple:{ToHex("arm-unknown-linux-android")};ostype:unknown;vendor:none;endian:little;ptrsize:4;");
+ }
+ else
+ {
+ Reply(
+ $"pid:1;cputype:100000c;cpusubtype:0;triple:{ToHex("aarch64-unknown-linux-android")};ostype:unknown;vendor:none;endian:little;ptrsize:8;");
+ }
+ break;
+ }
+
+ if (ss.ConsumePrefix("Supported:") || ss.ConsumeRemaining("Supported"))
+ {
+ Reply("PacketSize=10000;qXfer:features:read+");
+ break;
+ }
+
+ if (ss.ConsumeRemaining("fThreadInfo"))
+ {
+ Reply($"m{string.Join(",", DebugProcess.GetThreadUids().Select(x => $"{x:x}"))}");
+ break;
+ }
+
+ if (ss.ConsumeRemaining("sThreadInfo"))
+ {
+ Reply("l");
+ break;
+ }
+
+ if (ss.ConsumePrefix("ThreadExtraInfo,"))
+ {
+ ulong? threadId = ss.ReadRemainingAsThreadUid();
+ if (threadId == null)
+ {
+ ReplyError();
+ break;
+ }
+
+ if (DebugProcess.GetDebugState() == DebugState.Stopped)
+ {
+ Reply(ToHex("Stopped"));
+ }
+ else
+ {
+ Reply(ToHex("Not stopped"));
+ }
+ break;
+ }
+
+ if (ss.ConsumePrefix("Xfer:features:read:"))
+ {
+ string feature = ss.ReadUntil(':');
+ ulong addr = ss.ReadUntilAsHex(',');
+ ulong len = ss.ReadRemainingAsHex();
+
+ if (feature == "target.xml")
+ {
+ feature = IsProcessAarch32 ? "target32.xml" : "target64.xml";
+ }
+
+ string data;
+ if (RegisterInformation.Features.TryGetValue(feature, out data))
+ {
+ if (addr >= (ulong)data.Length)
+ {
+ Reply("l");
+ break;
+ }
+
+ if (len >= (ulong)data.Length - addr)
+ {
+ Reply("l" + ToBinaryFormat(data.Substring((int)addr)));
+ break;
+ }
+ else
+ {
+ Reply("m" + ToBinaryFormat(data.Substring((int)addr, (int)len)));
+ break;
+ }
+ }
+ else
+ {
+ Reply("E00"); // Invalid annex
+ break;
+ }
+ }
+
+ goto unknownCommand;
+ case 'Q':
+ goto unknownCommand;
+ case 's':
+ CommandStep(ss.IsEmpty() ? null : ss.ReadRemainingAsHex());
+ break;
+ case 'T':
+ {
+ ulong? threadId = ss.ReadRemainingAsThreadUid();
+ CommandIsAlive(threadId);
+ break;
+ }
+ case 'v':
+ if (ss.ConsumeRemaining("MustReplyEmpty"))
+ {
+ Reply("");
+ break;
+ }
+ goto unknownCommand;
+ default:
+ unknownCommand:
+ Logger.Notice.Print(LogClass.GdbStub, $"Unknown command: {cmd}");
+ Reply("");
+ break;
+ }
+ }
+
+ void CommandQuery()
+ {
+ // GDB is performing initial contact. Stop everything.
+ DebugProcess.DebugStop();
+ gThread = cThread = DebugProcess.GetThreadUids().First();
+ Reply($"T05thread:{cThread:x};");
+ }
+
+ void CommandContinue(ulong? newPc)
+ {
+ if (newPc.HasValue)
+ {
+ if (cThread == null)
+ {
+ ReplyError();
+ return;
+ }
+
+ DebugProcess.GetThread(cThread.Value).Context.DebugPc = newPc.Value;
+ }
+
+ DebugProcess.DebugContinue();
+ }
+
+ void CommandDetach()
+ {
+ // TODO: Remove all breakpoints
+ CommandContinue(null);
+ }
+
+ void CommandReadRegisters()
+ {
+ if (gThread == null)
+ {
+ ReplyError();
+ return;
+ }
+
+ var ctx = DebugProcess.GetThread(gThread.Value).Context;
+ string registers = "";
+ if (IsProcessAarch32)
+ {
+ for (int i = 0; i < GdbRegisterCount32; i++)
+ {
+ registers += GdbReadRegister32(ctx, i);
+ }
+ }
+ else
+ {
+ for (int i = 0; i < GdbRegisterCount64; i++)
+ {
+ registers += GdbReadRegister64(ctx, i);
+ }
+ }
+
+ Reply(registers);
+ }
+
+ void CommandWriteRegisters(StringStream ss)
+ {
+ if (gThread == null)
+ {
+ ReplyError();
+ return;
+ }
+
+ var ctx = DebugProcess.GetThread(gThread.Value).Context;
+ if (IsProcessAarch32)
+ {
+ for (int i = 0; i < GdbRegisterCount32; i++)
+ {
+ if (!GdbWriteRegister32(ctx, i, ss))
+ {
+ ReplyError();
+ return;
+ }
+ }
+ }
+ else
+ {
+ for (int i = 0; i < GdbRegisterCount64; i++)
+ {
+ if (!GdbWriteRegister64(ctx, i, ss))
+ {
+ ReplyError();
+ return;
+ }
+ }
+ }
+
+ if (ss.IsEmpty())
+ {
+ ReplyOK();
+ }
+ else
+ {
+ ReplyError();
+ }
+ }
+
+ void CommandSetThread(char op, ulong? threadId)
+ {
+ if (threadId == 0 || threadId == null)
+ {
+ threadId = GetThreads().First().ThreadUid;
+ }
+
+ if (DebugProcess.GetThread(threadId.Value) == null)
+ {
+ ReplyError();
+ return;
+ }
+
+ switch (op)
+ {
+ case 'c':
+ cThread = threadId;
+ ReplyOK();
+ return;
+ case 'g':
+ gThread = threadId;
+ ReplyOK();
+ return;
+ default:
+ ReplyError();
+ return;
+ }
+ }
+
+ void CommandReadMemory(ulong addr, ulong len)
+ {
+ try
+ {
+ var data = new byte[len];
+ DebugProcess.CpuMemory.Read(addr, data);
+ Reply(ToHex(data));
+ }
+ catch (InvalidMemoryRegionException)
+ {
+ ReplyError();
+ }
+ }
+
+ void CommandWriteMemory(ulong addr, ulong len, StringStream ss)
+ {
+ try
+ {
+ var data = new byte[len];
+ for (ulong i = 0; i < len; i++)
+ {
+ data[i] = (byte)ss.ReadLengthAsHex(2);
+ }
+
+ DebugProcess.CpuMemory.Write(addr, data);
+ DebugProcess.InvalidateCacheRegion(addr, len);
+ ReplyOK();
+ }
+ catch (InvalidMemoryRegionException)
+ {
+ ReplyError();
+ }
+ }
+
+ void CommandReadRegister(int gdbRegId)
+ {
+ if (gThread == null)
+ {
+ ReplyError();
+ return;
+ }
+
+ var ctx = DebugProcess.GetThread(gThread.Value).Context;
+ string result;
+ if (IsProcessAarch32)
+ {
+ result = GdbReadRegister32(ctx, gdbRegId);
+ if (result != null)
+ {
+ Reply(result);
+ }
+ else
+ {
+ ReplyError();
+ }
+ }
+ else
+ {
+ result = GdbReadRegister64(ctx, gdbRegId);
+ if (result != null)
+ {
+ Reply(result);
+ }
+ else
+ {
+ ReplyError();
+ }
+ }
+ }
+
+ void CommandWriteRegister(int gdbRegId, StringStream ss)
+ {
+ if (gThread == null)
+ {
+ ReplyError();
+ return;
+ }
+
+ var ctx = DebugProcess.GetThread(gThread.Value).Context;
+ if (IsProcessAarch32)
+ {
+ if (GdbWriteRegister32(ctx, gdbRegId, ss) && ss.IsEmpty())
+ {
+ ReplyOK();
+ }
+ else
+ {
+ ReplyError();
+ }
+ }
+ else
+ {
+ if (GdbWriteRegister64(ctx, gdbRegId, ss) && ss.IsEmpty())
+ {
+ ReplyOK();
+ }
+ else
+ {
+ ReplyError();
+ }
+ }
+ }
+
+ private void CommandStep(ulong? newPc)
+ {
+ if (cThread == null)
+ {
+ ReplyError();
+ return;
+ }
+
+ var thread = DebugProcess.GetThread(cThread.Value);
+
+ if (newPc.HasValue)
+ {
+ thread.Context.DebugPc = newPc.Value;
+ }
+
+ if (!DebugProcess.DebugStep(thread))
+ {
+ ReplyError();
+ }
+ else
+ {
+ Reply($"T05thread:{thread.ThreadUid:x};");
+ }
+ }
+
+ private void CommandIsAlive(ulong? threadId)
+ {
+ if (GetThreads().Any(x => x.ThreadUid == threadId))
+ {
+ ReplyOK();
+ }
+ else
+ {
+ Reply("E00");
+ }
+ }
+
+ private void Reply(string cmd)
+ {
+ Logger.Debug?.Print(LogClass.GdbStub, $"Reply: {cmd}");
+ WriteStream.Write(Encoding.ASCII.GetBytes($"${cmd}#{CalculateChecksum(cmd):x2}"));
+ }
+
+ private void ReplyOK()
+ {
+ Reply("OK");
+ }
+
+ private void ReplyError()
+ {
+ Reply("E01");
+ }
+
+ private void DebuggerThreadMain()
+ {
+ var endpoint = new IPEndPoint(IPAddress.Any, GdbStubPort);
+ ListenerSocket = new TcpListener(endpoint);
+ ListenerSocket.Start();
+ Logger.Notice.Print(LogClass.GdbStub, $"Currently waiting on {endpoint} for GDB client");
+
+ while (!_shuttingDown)
+ {
+ try
+ {
+ ClientSocket = ListenerSocket.AcceptSocket();
+ }
+ catch (SocketException)
+ {
+ return;
+ }
+ ClientSocket.NoDelay = true;
+ ReadStream = new NetworkStream(ClientSocket, System.IO.FileAccess.Read);
+ WriteStream = new NetworkStream(ClientSocket, System.IO.FileAccess.Write);
+ Logger.Notice.Print(LogClass.GdbStub, "GDB client connected");
+
+ while (true)
+ {
+ try
+ {
+ switch (ReadStream.ReadByte())
+ {
+ case -1:
+ goto eof;
+ case '+':
+ continue;
+ case '-':
+ Logger.Notice.Print(LogClass.GdbStub, "NACK received!");
+ continue;
+ case '\x03':
+ Messages.Add(new BreakInMessage());
+ break;
+ case '$':
+ string cmd = "";
+ while (true)
+ {
+ int x = ReadStream.ReadByte();
+ if (x == -1)
+ goto eof;
+ if (x == '#')
+ break;
+ cmd += (char)x;
+ }
+
+ string checksum = $"{(char)ReadStream.ReadByte()}{(char)ReadStream.ReadByte()}";
+ if (checksum == $"{CalculateChecksum(cmd):x2}")
+ {
+ Messages.Add(new CommandMessage(cmd));
+ }
+ else
+ {
+ Messages.Add(new SendNackMessage());
+ }
+
+ break;
+ }
+ }
+ catch (IOException)
+ {
+ goto eof;
+ }
+ }
+
+ eof:
+ Logger.Notice.Print(LogClass.GdbStub, "GDB client lost connection");
+ ReadStream.Close();
+ ReadStream = null;
+ WriteStream.Close();
+ WriteStream = null;
+ ClientSocket.Close();
+ ClientSocket = null;
+ }
+ }
+
+ private byte CalculateChecksum(string cmd)
+ {
+ byte checksum = 0;
+ foreach (char x in cmd)
+ {
+ unchecked
+ {
+ checksum += (byte)x;
+ }
+ }
+
+ return checksum;
+ }
+
+ private string ToHex(byte[] bytes)
+ {
+ return string.Join("", bytes.Select(x => $"{x:x2}"));
+ }
+
+ private string ToHex(string str)
+ {
+ return ToHex(Encoding.ASCII.GetBytes(str));
+ }
+
+ private string ToBinaryFormat(byte[] bytes)
+ {
+ return string.Join("", bytes.Select(x =>
+ x switch
+ {
+ (byte)'#' => "}\x03",
+ (byte)'$' => "}\x04",
+ (byte)'*' => "}\x0a",
+ (byte)'}' => "}\x5d",
+ _ => Convert.ToChar(x).ToString(),
+ }
+ ));
+ }
+
+ private string ToBinaryFormat(string str)
+ {
+ return ToBinaryFormat(Encoding.ASCII.GetBytes(str));
+ }
+
+ public void Dispose()
+ {
+ Dispose(true);
+ }
+
+ protected virtual void Dispose(bool disposing)
+ {
+ if (disposing)
+ {
+ _shuttingDown = true;
+
+ ListenerSocket.Stop();
+ ClientSocket?.Shutdown(SocketShutdown.Both);
+ ClientSocket?.Close();
+ ReadStream?.Close();
+ WriteStream?.Close();
+ DebuggerThread.Join();
+ Messages.Add(new KillMessage());
+ MessageHandlerThread.Join();
+ Messages.Dispose();
+ }
+ }
+
+ public void BreakHandler(IExecutionContext ctx, ulong address, int imm)
+ {
+ Logger.Notice.Print(LogClass.GdbStub, $"Break hit on thread {ctx.ThreadUid} at pc {address:x016}");
+
+ Messages.Add(new ThreadBreakMessage(ctx, address, imm));
+ DebugProcess.DebugInterruptHandler(ctx);
+ }
+
+ public void StepHandler(IExecutionContext ctx)
+ {
+ DebugProcess.DebugInterruptHandler(ctx);
+ }
+ }
+}
diff --git a/src/Ryujinx.HLE/Debugger/GdbSignal.cs b/src/Ryujinx.HLE/Debugger/GdbSignal.cs
new file mode 100644
index 000000000..ee4efbda4
--- /dev/null
+++ b/src/Ryujinx.HLE/Debugger/GdbSignal.cs
@@ -0,0 +1,15 @@
+namespace Ryujinx.HLE.Debugger
+{
+ enum GdbSignal
+ {
+ Zero = 0,
+ Int = 2,
+ Quit = 3,
+ Trap = 5,
+ Abort = 6,
+ Alarm = 14,
+ IO = 23,
+ XCPU = 24,
+ Unknown = 143
+ }
+}
diff --git a/src/Ryujinx.HLE/Debugger/GdbXml/aarch64-core.xml b/src/Ryujinx.HLE/Debugger/GdbXml/aarch64-core.xml
new file mode 100644
index 000000000..9899a0e4a
--- /dev/null
+++ b/src/Ryujinx.HLE/Debugger/GdbXml/aarch64-core.xml
@@ -0,0 +1,93 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Ryujinx.HLE/Debugger/GdbXml/aarch64-fpu.xml b/src/Ryujinx.HLE/Debugger/GdbXml/aarch64-fpu.xml
new file mode 100644
index 000000000..a09120bc4
--- /dev/null
+++ b/src/Ryujinx.HLE/Debugger/GdbXml/aarch64-fpu.xml
@@ -0,0 +1,159 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Ryujinx.HLE/Debugger/GdbXml/arm-core.xml b/src/Ryujinx.HLE/Debugger/GdbXml/arm-core.xml
new file mode 100644
index 000000000..2307d65f9
--- /dev/null
+++ b/src/Ryujinx.HLE/Debugger/GdbXml/arm-core.xml
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Ryujinx.HLE/Debugger/GdbXml/arm-neon.xml b/src/Ryujinx.HLE/Debugger/GdbXml/arm-neon.xml
new file mode 100644
index 000000000..d61f6b854
--- /dev/null
+++ b/src/Ryujinx.HLE/Debugger/GdbXml/arm-neon.xml
@@ -0,0 +1,86 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Ryujinx.HLE/Debugger/GdbXml/target32.xml b/src/Ryujinx.HLE/Debugger/GdbXml/target32.xml
new file mode 100644
index 000000000..890679858
--- /dev/null
+++ b/src/Ryujinx.HLE/Debugger/GdbXml/target32.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+ arm
+
+
+
diff --git a/src/Ryujinx.HLE/Debugger/GdbXml/target64.xml b/src/Ryujinx.HLE/Debugger/GdbXml/target64.xml
new file mode 100644
index 000000000..cfd5bf780
--- /dev/null
+++ b/src/Ryujinx.HLE/Debugger/GdbXml/target64.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+ aarch64
+
+
+
diff --git a/src/Ryujinx.HLE/Debugger/IDebuggableProcess.cs b/src/Ryujinx.HLE/Debugger/IDebuggableProcess.cs
new file mode 100644
index 000000000..273a1147f
--- /dev/null
+++ b/src/Ryujinx.HLE/Debugger/IDebuggableProcess.cs
@@ -0,0 +1,19 @@
+using Ryujinx.Cpu;
+using Ryujinx.HLE.HOS.Kernel.Threading;
+using Ryujinx.Memory;
+
+namespace Ryujinx.HLE.Debugger
+{
+ internal interface IDebuggableProcess
+ {
+ void DebugStop();
+ void DebugContinue();
+ bool DebugStep(KThread thread);
+ KThread GetThread(ulong threadUid);
+ DebugState GetDebugState();
+ ulong[] GetThreadUids();
+ public void DebugInterruptHandler(IExecutionContext ctx);
+ IVirtualMemoryManager CpuMemory { get; }
+ void InvalidateCacheRegion(ulong address, ulong size);
+ }
+}
diff --git a/src/Ryujinx.HLE/Debugger/Message/BreakInMessage.cs b/src/Ryujinx.HLE/Debugger/Message/BreakInMessage.cs
new file mode 100644
index 000000000..81d8784ae
--- /dev/null
+++ b/src/Ryujinx.HLE/Debugger/Message/BreakInMessage.cs
@@ -0,0 +1,6 @@
+namespace Ryujinx.HLE.Debugger
+{
+ struct BreakInMessage : IMessage
+ {
+ }
+}
diff --git a/src/Ryujinx.HLE/Debugger/Message/CommandMessage.cs b/src/Ryujinx.HLE/Debugger/Message/CommandMessage.cs
new file mode 100644
index 000000000..ad265d432
--- /dev/null
+++ b/src/Ryujinx.HLE/Debugger/Message/CommandMessage.cs
@@ -0,0 +1,12 @@
+namespace Ryujinx.HLE.Debugger
+{
+ struct CommandMessage : IMessage
+ {
+ public string Command;
+
+ public CommandMessage(string cmd)
+ {
+ Command = cmd;
+ }
+ }
+}
diff --git a/src/Ryujinx.HLE/Debugger/Message/IMessage.cs b/src/Ryujinx.HLE/Debugger/Message/IMessage.cs
new file mode 100644
index 000000000..4b03183c5
--- /dev/null
+++ b/src/Ryujinx.HLE/Debugger/Message/IMessage.cs
@@ -0,0 +1,6 @@
+namespace Ryujinx.HLE.Debugger
+{
+ interface IMessage
+ {
+ }
+}
diff --git a/src/Ryujinx.HLE/Debugger/Message/KillMessage.cs b/src/Ryujinx.HLE/Debugger/Message/KillMessage.cs
new file mode 100644
index 000000000..43ae0f21e
--- /dev/null
+++ b/src/Ryujinx.HLE/Debugger/Message/KillMessage.cs
@@ -0,0 +1,6 @@
+namespace Ryujinx.HLE.Debugger
+{
+ struct KillMessage : IMessage
+ {
+ }
+}
diff --git a/src/Ryujinx.HLE/Debugger/Message/SendNackMessage.cs b/src/Ryujinx.HLE/Debugger/Message/SendNackMessage.cs
new file mode 100644
index 000000000..ce804c46e
--- /dev/null
+++ b/src/Ryujinx.HLE/Debugger/Message/SendNackMessage.cs
@@ -0,0 +1,6 @@
+namespace Ryujinx.HLE.Debugger
+{
+ struct SendNackMessage : IMessage
+ {
+ }
+}
diff --git a/src/Ryujinx.HLE/Debugger/Message/ThreadBreakMessage.cs b/src/Ryujinx.HLE/Debugger/Message/ThreadBreakMessage.cs
new file mode 100644
index 000000000..027096eeb
--- /dev/null
+++ b/src/Ryujinx.HLE/Debugger/Message/ThreadBreakMessage.cs
@@ -0,0 +1,18 @@
+using IExecutionContext = Ryujinx.Cpu.IExecutionContext;
+
+namespace Ryujinx.HLE.Debugger
+{
+ public class ThreadBreakMessage : IMessage
+ {
+ public IExecutionContext Context { get; }
+ public ulong Address { get; }
+ public int Opcode { get; }
+
+ public ThreadBreakMessage(IExecutionContext context, ulong address, int opcode)
+ {
+ Context = context;
+ Address = address;
+ Opcode = opcode;
+ }
+ }
+}
diff --git a/src/Ryujinx.HLE/Debugger/RegisterInformation.cs b/src/Ryujinx.HLE/Debugger/RegisterInformation.cs
new file mode 100644
index 000000000..b5fd88ea5
--- /dev/null
+++ b/src/Ryujinx.HLE/Debugger/RegisterInformation.cs
@@ -0,0 +1,28 @@
+using System.Collections.Generic;
+using System.IO;
+
+namespace Ryujinx.HLE.Debugger
+{
+ class RegisterInformation
+ {
+ public static readonly Dictionary Features = new()
+ {
+ { "target64.xml", GetEmbeddedResourceContent("target64.xml") },
+ { "target32.xml", GetEmbeddedResourceContent("target32.xml") },
+ { "aarch64-core.xml", GetEmbeddedResourceContent("aarch64-core.xml") },
+ { "aarch64-fpu.xml", GetEmbeddedResourceContent("aarch64-fpu.xml") },
+ { "arm-core.xml", GetEmbeddedResourceContent("arm-core.xml") },
+ { "arm-neon.xml", GetEmbeddedResourceContent("arm-neon.xml") },
+ };
+
+ private static string GetEmbeddedResourceContent(string resourceName)
+ {
+ Stream stream = System.Reflection.Assembly.GetExecutingAssembly().GetManifestResourceStream("Ryujinx.HLE.Debugger.GdbXml." + resourceName);
+ StreamReader reader = new StreamReader(stream);
+ string result = reader.ReadToEnd();
+ reader.Dispose();
+ stream.Dispose();
+ return result;
+ }
+ }
+}
diff --git a/src/Ryujinx.HLE/Debugger/StringStream.cs b/src/Ryujinx.HLE/Debugger/StringStream.cs
new file mode 100644
index 000000000..d8148a9c2
--- /dev/null
+++ b/src/Ryujinx.HLE/Debugger/StringStream.cs
@@ -0,0 +1,109 @@
+using System.Diagnostics;
+using System.Globalization;
+
+namespace Ryujinx.HLE.Debugger
+{
+ class StringStream
+ {
+ private readonly string Data;
+ private int Position;
+
+ public StringStream(string s)
+ {
+ Data = s;
+ }
+
+ public char ReadChar()
+ {
+ return Data[Position++];
+ }
+
+ public string ReadUntil(char needle)
+ {
+ int needlePos = Data.IndexOf(needle, Position);
+
+ if (needlePos == -1)
+ {
+ needlePos = Data.Length;
+ }
+
+ string result = Data.Substring(Position, needlePos - Position);
+ Position = needlePos + 1;
+ return result;
+ }
+
+ public string ReadLength(int len)
+ {
+ string result = Data.Substring(Position, len);
+ Position += len;
+ return result;
+ }
+
+ public string ReadRemaining()
+ {
+ string result = Data.Substring(Position);
+ Position = Data.Length;
+ return result;
+ }
+
+ public ulong ReadRemainingAsHex()
+ {
+ return ulong.Parse(ReadRemaining(), NumberStyles.HexNumber);
+ }
+
+ public ulong ReadUntilAsHex(char needle)
+ {
+ return ulong.Parse(ReadUntil(needle), NumberStyles.HexNumber);
+ }
+
+ public ulong ReadLengthAsHex(int len)
+ {
+ return ulong.Parse(ReadLength(len), NumberStyles.HexNumber);
+ }
+
+ public ulong ReadLengthAsLEHex(int len)
+ {
+ Debug.Assert(len % 2 == 0);
+
+ ulong result = 0;
+ int pos = 0;
+ while (pos < len)
+ {
+ result += ReadLengthAsHex(2) << (4 * pos);
+ pos += 2;
+ }
+ return result;
+ }
+
+ public ulong? ReadRemainingAsThreadUid()
+ {
+ string s = ReadRemaining();
+ return s == "-1" ? null : ulong.Parse(s, NumberStyles.HexNumber);
+ }
+
+ public bool ConsumePrefix(string prefix)
+ {
+ if (Data.Substring(Position).StartsWith(prefix))
+ {
+ Position += prefix.Length;
+ return true;
+ }
+ return false;
+ }
+
+ public bool ConsumeRemaining(string match)
+ {
+ if (Data.Substring(Position) == match)
+ {
+ Position += match.Length;
+ return true;
+ }
+ return false;
+ }
+
+ public bool IsEmpty()
+ {
+ return Position >= Data.Length;
+ }
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Horizon.cs b/src/Ryujinx.HLE/HOS/Horizon.cs
index 5063b4329..44adb9674 100644
--- a/src/Ryujinx.HLE/HOS/Horizon.cs
+++ b/src/Ryujinx.HLE/HOS/Horizon.cs
@@ -5,6 +5,7 @@ using LibHac.Fs.Shim;
using LibHac.FsSystem;
using LibHac.Tools.FsSystem;
using Ryujinx.Cpu;
+using Ryujinx.HLE.Debugger;
using Ryujinx.HLE.FileSystem;
using Ryujinx.HLE.HOS.Kernel;
using Ryujinx.HLE.HOS.Kernel.Memory;
@@ -500,5 +501,13 @@ namespace Ryujinx.HLE.HOS
IsPaused = pause;
}
+
+ internal IDebuggableProcess DebugGetApplicationProcess()
+ {
+ lock (KernelContext.Processes)
+ {
+ return KernelContext.Processes.Values.FirstOrDefault(x => x.IsApplication)?.DebugInterface;
+ }
+ }
}
}
diff --git a/src/Ryujinx.HLE/HOS/Kernel/Process/KProcess.cs b/src/Ryujinx.HLE/HOS/Kernel/Process/KProcess.cs
index 478b4e864..0c400b425 100644
--- a/src/Ryujinx.HLE/HOS/Kernel/Process/KProcess.cs
+++ b/src/Ryujinx.HLE/HOS/Kernel/Process/KProcess.cs
@@ -1,6 +1,7 @@
using Ryujinx.Common;
using Ryujinx.Common.Logging;
using Ryujinx.Cpu;
+using Ryujinx.HLE.Debugger;
using Ryujinx.HLE.Exceptions;
using Ryujinx.HLE.HOS.Kernel.Common;
using Ryujinx.HLE.HOS.Kernel.Memory;
@@ -11,6 +12,8 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
+using ExceptionCallback = Ryujinx.Cpu.ExceptionCallback;
+using ExceptionCallbackNoArgs = Ryujinx.Cpu.ExceptionCallbackNoArgs;
namespace Ryujinx.HLE.HOS.Kernel.Process
{
@@ -89,6 +92,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
public IVirtualMemoryManager CpuMemory => Context.AddressSpace;
public HleProcessDebugger Debugger { get; private set; }
+ public IDebuggableProcess DebugInterface { get; private set; }
+ protected int debugState = (int)DebugState.Running;
public KProcess(KernelContext context, bool allowCodeMemoryForJit = false) : base(context)
{
@@ -110,6 +115,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
_threads = [];
Debugger = new HleProcessDebugger(this);
+ DebugInterface = new DebuggerInterface(this);
}
public Result InitializeKip(
@@ -679,6 +685,12 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
SetState(newState);
+ if (KernelContext.Device.Configuration.DebuggerSuspendOnStart && IsApplication)
+ {
+ mainThread.Suspend(ThreadSchedState.ThreadPauseFlag);
+ debugState = (int)DebugState.Stopped;
+ }
+
result = mainThread.Start();
if (result != Result.Success)
@@ -727,9 +739,19 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
public IExecutionContext CreateExecutionContext()
{
+ ExceptionCallback breakCallback = null;
+ ExceptionCallbackNoArgs stepCallback = null;
+
+ if (KernelContext.Device.Configuration.EnableGdbStub)
+ {
+ breakCallback = KernelContext.Device.Debugger.BreakHandler;
+ stepCallback = KernelContext.Device.Debugger.StepHandler;
+ }
+
return Context?.CreateExecutionContext(new ExceptionCallbacks(
InterruptHandler,
- null,
+ breakCallback,
+ stepCallback,
KernelContext.SyscallHandler.SvcCall,
UndefinedInstructionHandler));
}
@@ -1174,5 +1196,157 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
{
return Capabilities.IsSvcPermitted(svcId);
}
+
+ private class DebuggerInterface : IDebuggableProcess
+ {
+ private Barrier StepBarrier;
+ private readonly KProcess _parent;
+ private readonly KernelContext _kernelContext;
+ private KThread steppingThread;
+
+ public DebuggerInterface(KProcess p)
+ {
+ _parent = p;
+ _kernelContext = p.KernelContext;
+ StepBarrier = new(2);
+ }
+
+ public void DebugStop()
+ {
+ if (Interlocked.CompareExchange(ref _parent.debugState, (int)DebugState.Stopping,
+ (int)DebugState.Running) != (int)DebugState.Running)
+ {
+ return;
+ }
+
+ _kernelContext.CriticalSection.Enter();
+ lock (_parent._threadingLock)
+ {
+ foreach (KThread thread in _parent._threads)
+ {
+ thread.Suspend(ThreadSchedState.ThreadPauseFlag);
+ thread.Context.RequestInterrupt();
+ if (!thread.DebugHalt.WaitOne(TimeSpan.FromMilliseconds(50)))
+ {
+ Logger.Warning?.Print(LogClass.Kernel, $"Failed to suspend thread {thread.ThreadUid} in time.");
+ }
+ }
+ }
+
+ _parent.debugState = (int)DebugState.Stopped;
+ _kernelContext.CriticalSection.Leave();
+ }
+
+ public void DebugContinue()
+ {
+ if (Interlocked.CompareExchange(ref _parent.debugState, (int)DebugState.Running,
+ (int)DebugState.Stopped) != (int)DebugState.Stopped)
+ {
+ return;
+ }
+
+ _kernelContext.CriticalSection.Enter();
+ lock (_parent._threadingLock)
+ {
+ foreach (KThread thread in _parent._threads)
+ {
+ thread.Resume(ThreadSchedState.ThreadPauseFlag);
+ }
+ }
+ _kernelContext.CriticalSection.Leave();
+ }
+
+ public bool DebugStep(KThread target)
+ {
+ if (_parent.debugState != (int)DebugState.Stopped)
+ {
+ return false;
+ }
+ _kernelContext.CriticalSection.Enter();
+ steppingThread = target;
+ bool waiting = target.MutexOwner != null || target.WaitingSync || target.WaitingInArbitration;
+ target.Context.RequestDebugStep();
+ if (waiting)
+ {
+ lock (_parent._threadingLock)
+ {
+ foreach (KThread thread in _parent._threads)
+ {
+ thread.Resume(ThreadSchedState.ThreadPauseFlag);
+ }
+ }
+ }
+ else
+ {
+ target.Resume(ThreadSchedState.ThreadPauseFlag);
+ }
+ _kernelContext.CriticalSection.Leave();
+
+ StepBarrier.SignalAndWait();
+
+ _kernelContext.CriticalSection.Enter();
+ steppingThread = null;
+ if (waiting)
+ {
+ lock (_parent._threadingLock)
+ {
+ foreach (KThread thread in _parent._threads)
+ {
+ thread.Suspend(ThreadSchedState.ThreadPauseFlag);
+ }
+ }
+ }
+ else
+ {
+ target.Suspend(ThreadSchedState.ThreadPauseFlag);
+ }
+ _kernelContext.CriticalSection.Leave();
+ StepBarrier.SignalAndWait();
+ return true;
+ }
+
+ public DebugState GetDebugState()
+ {
+ return (DebugState)_parent.debugState;
+ }
+
+ public ulong[] GetThreadUids()
+ {
+ lock (_parent._threadingLock)
+ {
+ var threads = _parent._threads.Where(x => !x.TerminationRequested).ToArray();
+ return threads.Select(x => x.ThreadUid).ToArray();
+ }
+ }
+
+ public KThread GetThread(ulong threadUid)
+ {
+ lock (_parent._threadingLock)
+ {
+ var threads = _parent._threads.Where(x => !x.TerminationRequested).ToArray();
+ return threads.FirstOrDefault(x => x.ThreadUid == threadUid);
+ }
+ }
+
+ public void DebugInterruptHandler(IExecutionContext ctx)
+ {
+ _kernelContext.CriticalSection.Enter();
+ bool stepping = steppingThread != null;
+ _kernelContext.CriticalSection.Leave();
+ if (stepping)
+ {
+ StepBarrier.SignalAndWait();
+ StepBarrier.SignalAndWait();
+ }
+ _parent.InterruptHandler(ctx);
+ }
+
+ public IVirtualMemoryManager CpuMemory { get { return _parent.CpuMemory; } }
+
+ public void InvalidateCacheRegion(ulong address, ulong size)
+ {
+ _parent.Context.InvalidateCacheRegion(address, size);
+ }
+ }
}
}
diff --git a/src/Ryujinx.HLE/HOS/Kernel/Process/ProcessExecutionContext.cs b/src/Ryujinx.HLE/HOS/Kernel/Process/ProcessExecutionContext.cs
index b8118fbb4..f0e44c4b7 100644
--- a/src/Ryujinx.HLE/HOS/Kernel/Process/ProcessExecutionContext.cs
+++ b/src/Ryujinx.HLE/HOS/Kernel/Process/ProcessExecutionContext.cs
@@ -1,5 +1,6 @@
using ARMeilleure.State;
using Ryujinx.Cpu;
+using System.Threading;
namespace Ryujinx.HLE.HOS.Kernel.Process
{
@@ -17,10 +18,14 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
public bool IsAarch32 { get => false; set { } }
+ public ulong ThreadUid { get; set; }
+
public bool Running { get; private set; } = true;
private readonly ulong[] _x = new ulong[32];
+ public ulong DebugPc { get; set; }
+
public ulong GetX(int index) => _x[index];
public void SetX(int index, ulong value) => _x[index] = value;
@@ -31,6 +36,10 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
{
}
+ public void RequestDebugStep()
+ {
+ }
+
public void StopRunning()
{
Running = false;
diff --git a/src/Ryujinx.HLE/HOS/Kernel/Threading/KScheduler.cs b/src/Ryujinx.HLE/HOS/Kernel/Threading/KScheduler.cs
index 7471702c3..54b20ff99 100644
--- a/src/Ryujinx.HLE/HOS/Kernel/Threading/KScheduler.cs
+++ b/src/Ryujinx.HLE/HOS/Kernel/Threading/KScheduler.cs
@@ -301,6 +301,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
currentThread.SchedulerWaitEvent.Reset();
currentThread.ThreadContext.Unlock();
+ currentThread.DebugHalt.Set();
// Wake all the threads that might be waiting until this thread context is unlocked.
for (int core = 0; core < CpuCoresCount; core++)
diff --git a/src/Ryujinx.HLE/HOS/Kernel/Threading/KThread.cs b/src/Ryujinx.HLE/HOS/Kernel/Threading/KThread.cs
index b5a14ad5b..2b5d11244 100644
--- a/src/Ryujinx.HLE/HOS/Kernel/Threading/KThread.cs
+++ b/src/Ryujinx.HLE/HOS/Kernel/Threading/KThread.cs
@@ -1,5 +1,6 @@
using Ryujinx.Common.Logging;
using Ryujinx.Cpu;
+using Ryujinx.HLE.Debugger;
using Ryujinx.HLE.HOS.Kernel.Common;
using Ryujinx.HLE.HOS.Kernel.Process;
using Ryujinx.HLE.HOS.Kernel.SupervisorCall;
@@ -114,6 +115,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
private readonly Lock _activityOperationLock = new();
+ internal readonly ManualResetEvent DebugHalt = new(false);
+
public KThread(KernelContext context) : base(context)
{
WaitSyncObjects = new KSynchronizationObject[MaxWaitSyncObjects];
@@ -202,8 +205,10 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
}
Context.TpidrroEl0 = (long)_tlsAddress;
+ Context.DebugPc = _entrypoint;
ThreadUid = KernelContext.NewThreadUid();
+ Context.ThreadUid = ThreadUid;
HostThread.Name = customThreadStart != null ? $"HLE.OsThread.{ThreadUid}" : $"HLE.GuestThread.{ThreadUid}";
@@ -307,7 +312,9 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
{
KernelContext.CriticalSection.Enter();
- if (Owner != null && Owner.PinnedThreads[KernelStatic.GetCurrentThread().CurrentCore] == this)
+ KThread currentThread = KernelStatic.GetCurrentThread();
+
+ if (Owner != null && currentThread != null && Owner.PinnedThreads[currentThread.CurrentCore] == this)
{
Owner.UnpinThread(this);
}
@@ -362,7 +369,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
{
ThreadSchedState state = PrepareForTermination();
- if (state != ThreadSchedState.TerminationPending)
+ if (KernelStatic.GetCurrentThread() == this && state != ThreadSchedState.TerminationPending)
{
KernelContext.Synchronization.WaitFor(new KSynchronizationObject[] { this }, -1, out _);
}
@@ -1248,6 +1255,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
private void ThreadStart()
{
_schedulerWaitEvent.WaitOne();
+ DebugHalt.Reset();
KernelStatic.SetKernelContext(KernelContext, this);
if (_customThreadStart != null)
diff --git a/src/Ryujinx.HLE/HleConfiguration.cs b/src/Ryujinx.HLE/HleConfiguration.cs
index 10c2a1f30..e2f95ede7 100644
--- a/src/Ryujinx.HLE/HleConfiguration.cs
+++ b/src/Ryujinx.HLE/HleConfiguration.cs
@@ -194,6 +194,21 @@ namespace Ryujinx.HLE
///
public Action RefreshInputConfig { internal get; set; }
+ ///
+ /// Enables gdbstub to allow for debugging of the guest .
+ ///
+ public bool EnableGdbStub { internal get; set; }
+
+ ///
+ /// A TCP port to use to expose a gdbstub for a debugger to connect to.
+ ///
+ public ushort GdbStubPort { internal get; set; }
+
+ ///
+ /// Suspend execution when starting an application
+ ///
+ public bool DebuggerSuspendOnStart { internal get; set; }
+
///
/// The desired hacky workarounds.
///
@@ -222,6 +237,9 @@ namespace Ryujinx.HLE
bool multiplayerDisableP2p,
string multiplayerLdnPassphrase,
string multiplayerLdnServer,
+ bool enableGdbStub,
+ ushort gdbStubPort,
+ bool debuggerSuspendOnStart,
int customVSyncInterval,
EnabledDirtyHack[] dirtyHacks = null)
{
@@ -248,6 +266,9 @@ namespace Ryujinx.HLE
MultiplayerDisableP2p = multiplayerDisableP2p;
MultiplayerLdnPassphrase = multiplayerLdnPassphrase;
MultiplayerLdnServer = multiplayerLdnServer;
+ EnableGdbStub = enableGdbStub;
+ GdbStubPort = gdbStubPort;
+ DebuggerSuspendOnStart = debuggerSuspendOnStart;
Hacks = dirtyHacks ?? [];
}
diff --git a/src/Ryujinx.HLE/Ryujinx.HLE.csproj b/src/Ryujinx.HLE/Ryujinx.HLE.csproj
index 5139d9276..1938796e8 100644
--- a/src/Ryujinx.HLE/Ryujinx.HLE.csproj
+++ b/src/Ryujinx.HLE/Ryujinx.HLE.csproj
@@ -33,6 +33,12 @@
+
+
+
+
+
+
@@ -42,6 +48,12 @@
+
+
+
+
+
+
diff --git a/src/Ryujinx.HLE/Switch.cs b/src/Ryujinx.HLE/Switch.cs
index bdcbe82c7..ed6d57b2d 100644
--- a/src/Ryujinx.HLE/Switch.cs
+++ b/src/Ryujinx.HLE/Switch.cs
@@ -14,6 +14,7 @@ using Ryujinx.HLE.Loaders.Processes;
using Ryujinx.HLE.UI;
using Ryujinx.Memory;
using System;
+using System.Threading;
namespace Ryujinx.HLE
{
@@ -41,6 +42,7 @@ namespace Ryujinx.HLE
public Hid Hid { get; }
public TamperMachine TamperMachine { get; }
public IHostUIHandler UIHandler { get; }
+ public Debugger.Debugger Debugger { get; }
public int CpuCoresCount = 4; // Switch has a quad-core Tegra X1 SoC
@@ -72,6 +74,7 @@ namespace Ryujinx.HLE
AudioDeviceDriver = new CompatLayerHardwareDeviceDriver(Configuration.AudioDeviceDriver);
Memory = new MemoryBlock(Configuration.MemoryConfiguration.ToDramSize(), memoryAllocationFlags);
Gpu = new GpuContext(Configuration.GpuRenderer, DirtyHacks);
+ Debugger = Configuration.EnableGdbStub ? new Debugger.Debugger(this, Configuration.GdbStubPort) : null;
System = new HOS.Horizon(this);
Statistics = new PerformanceStatistics(this);
Hid = new Hid(this, System.HidStorage);
@@ -173,6 +176,7 @@ namespace Ryujinx.HLE
AudioDeviceDriver.Dispose();
FileSystem.Dispose();
Memory.Dispose();
+ Debugger.Dispose();
TitleIDs.CurrentApplication.Value = null;
Shared = null;
diff --git a/src/Ryujinx/Headless/HeadlessRyujinx.Init.cs b/src/Ryujinx/Headless/HeadlessRyujinx.Init.cs
index f15d24e8a..dc7b8625e 100644
--- a/src/Ryujinx/Headless/HeadlessRyujinx.Init.cs
+++ b/src/Ryujinx/Headless/HeadlessRyujinx.Init.cs
@@ -338,6 +338,9 @@ namespace Ryujinx.Headless
false,
string.Empty,
string.Empty,
+ options.EnableGdbStub,
+ options.GdbStubPort,
+ options.DebuggerSuspendOnStart,
options.CustomVSyncInterval
)
.Configure(
diff --git a/src/Ryujinx/Headless/Options.cs b/src/Ryujinx/Headless/Options.cs
index 49050005c..876d0c936 100644
--- a/src/Ryujinx/Headless/Options.cs
+++ b/src/Ryujinx/Headless/Options.cs
@@ -423,6 +423,17 @@ namespace Ryujinx.Headless
[Option("skip-user-profiles-manager", Required = false, Default = false, HelpText = "Enable skips the Profiles Manager popup during gameplay. Select the desired profile before starting the game")]
public bool SkipUserProfilesManager { get; set; }
+ // Debug
+
+ [Option("enable-gdb-stub", Required = false, Default = false, HelpText = "Enables the GDB stub so that a developer can attach a debugger to the emulated process.")]
+ public bool EnableGdbStub { get; set; }
+
+ [Option("gdb-stub-port", Required = false, Default = 55555, HelpText = "Specifies which TCP port the GDB stub listens on.")]
+ public ushort GdbStubPort { get; set; }
+
+ [Option("suspend-on-start", Required = false, Default = false, HelpText = "Suspend execution when starting an application.")]
+ public bool DebuggerSuspendOnStart { get; set; }
+
// Values
[Value(0, MetaName = "input", HelpText = "Input to load.", Required = true)]
diff --git a/src/Ryujinx/Systems/AppHost.cs b/src/Ryujinx/Systems/AppHost.cs
index 1c5f64309..f11280d62 100644
--- a/src/Ryujinx/Systems/AppHost.cs
+++ b/src/Ryujinx/Systems/AppHost.cs
@@ -218,6 +218,10 @@ namespace Ryujinx.Ava.Systems
ConfigurationState.Instance.Multiplayer.LdnServer.Event += UpdateLdnServerState;
ConfigurationState.Instance.Multiplayer.DisableP2p.Event += UpdateDisableP2pState;
+ ConfigurationState.Instance.Debug.EnableGdbStub.Event += UpdateEnableGdbStubState;
+ ConfigurationState.Instance.Debug.GdbStubPort.Event += UpdateGdbStubPortState;
+ ConfigurationState.Instance.Debug.DebuggerSuspendOnStart.Event += UpdateDebuggerSuspendOnStartState;
+
_gpuCancellationTokenSource = new CancellationTokenSource();
_gpuDoneEvent = new ManualResetEvent(false);
}
@@ -564,6 +568,21 @@ namespace Ryujinx.Ava.Systems
Device.Configuration.MultiplayerDisableP2p = e.NewValue;
}
+ private void UpdateEnableGdbStubState(object sender, ReactiveEventArgs e)
+ {
+ Device.Configuration.EnableGdbStub = e.NewValue;
+ }
+
+ private void UpdateGdbStubPortState(object sender, ReactiveEventArgs e)
+ {
+ Device.Configuration.GdbStubPort = e.NewValue;
+ }
+
+ private void UpdateDebuggerSuspendOnStartState(object sender, ReactiveEventArgs e)
+ {
+ Device.Configuration.DebuggerSuspendOnStart = e.NewValue;
+ }
+
public void Stop()
{
_isActive = false;
diff --git a/src/Ryujinx/Systems/Configuration/ConfigurationFileFormat.cs b/src/Ryujinx/Systems/Configuration/ConfigurationFileFormat.cs
index c21383349..26ea73f73 100644
--- a/src/Ryujinx/Systems/Configuration/ConfigurationFileFormat.cs
+++ b/src/Ryujinx/Systems/Configuration/ConfigurationFileFormat.cs
@@ -464,6 +464,21 @@ namespace Ryujinx.Ava.Systems.Configuration
///
public bool UseHypervisor { get; set; }
+ ///
+ /// Enables or disables the GDB stub
+ ///
+ public bool EnableGdbStub { get; set; }
+
+ ///
+ /// Which TCP port should the GDB stub listen on
+ ///
+ public ushort GdbStubPort { get; set; }
+
+ ///
+ /// Suspend execution when starting an application
+ ///
+ public bool DebuggerSuspendOnStart { get; set; }
+
///
/// Show toggles for dirty hacks in the UI.
///
diff --git a/src/Ryujinx/Systems/Configuration/ConfigurationState.Migration.cs b/src/Ryujinx/Systems/Configuration/ConfigurationState.Migration.cs
index afabdb4e3..58f25b783 100644
--- a/src/Ryujinx/Systems/Configuration/ConfigurationState.Migration.cs
+++ b/src/Ryujinx/Systems/Configuration/ConfigurationState.Migration.cs
@@ -156,6 +156,10 @@ namespace Ryujinx.Ava.Systems.Configuration
Multiplayer.LdnPassphrase.Value = cff.MultiplayerLdnPassphrase;
Multiplayer.LdnServer.Value = cff.LdnServer;
+ Debug.EnableGdbStub.Value = cff.EnableGdbStub;
+ Debug.GdbStubPort.Value = cff.GdbStubPort;
+ Debug.DebuggerSuspendOnStart.Value = cff.DebuggerSuspendOnStart;
+
{
Hacks.ShowDirtyHacks.Value = shouldLoadFromFile ? cff.ShowDirtyHacks : Hacks.ShowDirtyHacks.Value; // Get from global config only
diff --git a/src/Ryujinx/Systems/Configuration/ConfigurationState.Model.cs b/src/Ryujinx/Systems/Configuration/ConfigurationState.Model.cs
index 29a390b26..bc8fdb40a 100644
--- a/src/Ryujinx/Systems/Configuration/ConfigurationState.Model.cs
+++ b/src/Ryujinx/Systems/Configuration/ConfigurationState.Model.cs
@@ -703,6 +703,37 @@ namespace Ryujinx.Ava.Systems.Configuration
}
}
+ ///
+ /// Debug configuration section
+ ///
+ public class DebugSection
+ {
+ ///
+ /// Enables or disables the GDB stub
+ ///
+ public ReactiveObject EnableGdbStub { get; private set; }
+
+ ///
+ /// Which TCP port should the GDB stub listen on
+ ///
+ public ReactiveObject GdbStubPort { get; private set; }
+
+ ///
+ /// Suspend execution when starting an application
+ ///
+ public ReactiveObject DebuggerSuspendOnStart { get; private set; }
+
+ public DebugSection()
+ {
+ EnableGdbStub = new ReactiveObject();
+ EnableGdbStub.LogChangesToValue(nameof(EnableGdbStub));
+ GdbStubPort = new ReactiveObject();
+ GdbStubPort.LogChangesToValue(nameof(GdbStubPort));
+ DebuggerSuspendOnStart = new ReactiveObject();
+ DebuggerSuspendOnStart.LogChangesToValue(nameof(DebuggerSuspendOnStart));
+ }
+ }
+
public class HacksSection
{
///
@@ -801,6 +832,11 @@ namespace Ryujinx.Ava.Systems.Configuration
///
public MultiplayerSection Multiplayer { get; private set; }
+ ///
+ /// The Debug
+ ///
+ public DebugSection Debug { get; private set; }
+
///
/// The Dirty Hacks section
///
@@ -854,6 +890,7 @@ namespace Ryujinx.Ava.Systems.Configuration
Graphics = new GraphicsSection();
Hid = new HidSection();
Multiplayer = new MultiplayerSection();
+ Debug = new DebugSection();
Hacks = new HacksSection();
UpdateCheckerType = new ReactiveObject();
FocusLostActionType = new ReactiveObject();
@@ -893,6 +930,9 @@ namespace Ryujinx.Ava.Systems.Configuration
Multiplayer.DisableP2p,
Multiplayer.LdnPassphrase,
Multiplayer.GetLdnServer(),
+ Debug.EnableGdbStub,
+ Debug.GdbStubPort,
+ Debug.DebuggerSuspendOnStart,
Graphics.CustomVSyncInterval,
Hacks.ShowDirtyHacks ? Hacks.EnabledHacks : null);
}
diff --git a/src/Ryujinx/Systems/Configuration/ConfigurationState.cs b/src/Ryujinx/Systems/Configuration/ConfigurationState.cs
index 4a565d5d3..185aedf64 100644
--- a/src/Ryujinx/Systems/Configuration/ConfigurationState.cs
+++ b/src/Ryujinx/Systems/Configuration/ConfigurationState.cs
@@ -147,6 +147,9 @@ namespace Ryujinx.Ava.Systems.Configuration
MultiplayerDisableP2p = Multiplayer.DisableP2p,
MultiplayerLdnPassphrase = Multiplayer.LdnPassphrase,
LdnServer = Multiplayer.LdnServer,
+ EnableGdbStub = Debug.EnableGdbStub,
+ GdbStubPort = Debug.GdbStubPort,
+ DebuggerSuspendOnStart = Debug.DebuggerSuspendOnStart,
ShowDirtyHacks = Hacks.ShowDirtyHacks,
DirtyHacks = Hacks.EnabledHacks.Select(it => it.Pack()).ToArray(),
};
@@ -324,6 +327,9 @@ namespace Ryujinx.Ava.Systems.Configuration
},
}
];
+ Debug.EnableGdbStub.Value = false;
+ Debug.GdbStubPort.Value = 55555;
+ Debug.DebuggerSuspendOnStart.Value = false;
}
private static GraphicsBackend DefaultGraphicsBackend()
diff --git a/src/Ryujinx/UI/ViewModels/SettingsViewModel.cs b/src/Ryujinx/UI/ViewModels/SettingsViewModel.cs
index 654eb0c43..54fd951fb 100644
--- a/src/Ryujinx/UI/ViewModels/SettingsViewModel.cs
+++ b/src/Ryujinx/UI/ViewModels/SettingsViewModel.cs
@@ -71,6 +71,10 @@ namespace Ryujinx.Ava.UI.ViewModels
private string _ldnPassphrase;
[ObservableProperty] private string _ldnServer;
+ private bool _enableGDBStub;
+ private ushort _gdbStubPort;
+ private bool _debuggerSuspendOnStart;
+
public SettingsHacksViewModel DirtyHacks { get; }
private readonly bool _isGameRunning;
@@ -387,6 +391,36 @@ namespace Ryujinx.Ava.UI.ViewModels
public bool IsInvalidLdnPassphraseVisible { get; set; }
+ public bool EnableGdbStub
+ {
+ get => _enableGDBStub;
+ set
+ {
+ _enableGDBStub = value;
+ ConfigurationState.Instance.Debug.EnableGdbStub.Value = _enableGDBStub;
+ }
+ }
+
+ public ushort GDBStubPort
+ {
+ get => _gdbStubPort;
+ set
+ {
+ _gdbStubPort = value;
+ ConfigurationState.Instance.Debug.GdbStubPort.Value = _gdbStubPort;
+ }
+ }
+
+ public bool DebuggerSuspendOnStart
+ {
+ get => _debuggerSuspendOnStart;
+ set
+ {
+ _debuggerSuspendOnStart = value;
+ ConfigurationState.Instance.Debug.DebuggerSuspendOnStart.Value = _debuggerSuspendOnStart;
+ }
+ }
+
public SettingsViewModel(VirtualFileSystem virtualFileSystem, ContentManager contentManager) : this()
{
_virtualFileSystem = virtualFileSystem;
@@ -680,10 +714,16 @@ namespace Ryujinx.Ava.UI.ViewModels
FsGlobalAccessLogMode = config.System.FsGlobalAccessLogMode;
OpenglDebugLevel = (int)config.Logger.GraphicsDebugLevel.Value;
+ // Multiplayer
MultiplayerModeIndex = (int)config.Multiplayer.Mode.Value;
DisableP2P = config.Multiplayer.DisableP2p;
LdnPassphrase = config.Multiplayer.LdnPassphrase;
LdnServer = config.Multiplayer.LdnServer;
+
+ // Debug
+ EnableGdbStub = config.Debug.EnableGdbStub.Value;
+ GDBStubPort = config.Debug.GdbStubPort.Value;
+ DebuggerSuspendOnStart = config.Debug.DebuggerSuspendOnStart.Value;
}
public void SaveSettings(bool global = false)
@@ -800,12 +840,18 @@ namespace Ryujinx.Ava.UI.ViewModels
config.System.FsGlobalAccessLogMode.Value = FsGlobalAccessLogMode;
config.Logger.GraphicsDebugLevel.Value = (GraphicsDebugLevel)OpenglDebugLevel;
+ // Multiplayer
config.Multiplayer.LanInterfaceId.Value = _networkInterfaces[NetworkInterfaceList[NetworkInterfaceIndex]];
config.Multiplayer.Mode.Value = (MultiplayerMode)MultiplayerModeIndex;
config.Multiplayer.DisableP2p.Value = DisableP2P;
config.Multiplayer.LdnPassphrase.Value = LdnPassphrase;
config.Multiplayer.LdnServer.Value = LdnServer;
+ // Debug
+ config.Debug.EnableGdbStub.Value = EnableGdbStub;
+ config.Debug.GdbStubPort.Value = GDBStubPort;
+ config.Debug.DebuggerSuspendOnStart.Value = DebuggerSuspendOnStart;
+
// Dirty Hacks
config.Hacks.Xc2MenuSoftlockFix.Value = DirtyHacks.Xc2MenuSoftlockFix;
config.Hacks.DisableNifmIsAnyInternetRequestAccepted.Value =
diff --git a/src/Ryujinx/UI/Views/Settings/SettingsDebugView.axaml b/src/Ryujinx/UI/Views/Settings/SettingsDebugView.axaml
new file mode 100644
index 000000000..036471059
--- /dev/null
+++ b/src/Ryujinx/UI/Views/Settings/SettingsDebugView.axaml
@@ -0,0 +1,64 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Ryujinx/UI/Views/Settings/SettingsDebugView.axaml.cs b/src/Ryujinx/UI/Views/Settings/SettingsDebugView.axaml.cs
new file mode 100644
index 000000000..14a65b8b2
--- /dev/null
+++ b/src/Ryujinx/UI/Views/Settings/SettingsDebugView.axaml.cs
@@ -0,0 +1,13 @@
+using Avalonia.Controls;
+
+namespace Ryujinx.Ava.UI.Views.Settings
+{
+ public partial class SettingsDebugView : UserControl
+ {
+ public SettingsDebugView()
+ {
+ InitializeComponent();
+ }
+ }
+}
+
diff --git a/src/Ryujinx/UI/Windows/SettingsWindow.axaml b/src/Ryujinx/UI/Windows/SettingsWindow.axaml
index 15d174123..9bfe0a9db 100644
--- a/src/Ryujinx/UI/Windows/SettingsWindow.axaml
+++ b/src/Ryujinx/UI/Windows/SettingsWindow.axaml
@@ -46,6 +46,7 @@
+
+
Date: Sat, 21 Jun 2025 00:02:31 +0800
Subject: [PATCH 02/51] gdb: Remove redundant log
---
src/Ryujinx.HLE/Debugger/Debugger.cs | 1 -
1 file changed, 1 deletion(-)
diff --git a/src/Ryujinx.HLE/Debugger/Debugger.cs b/src/Ryujinx.HLE/Debugger/Debugger.cs
index 33c380175..3eebb0b38 100644
--- a/src/Ryujinx.HLE/Debugger/Debugger.cs
+++ b/src/Ryujinx.HLE/Debugger/Debugger.cs
@@ -238,7 +238,6 @@ namespace Ryujinx.HLE.Debugger
private void ProcessCommand(string cmd)
{
- Logger.Debug?.Print(LogClass.GdbStub, $"Receive: {cmd}");
StringStream ss = new StringStream(cmd);
switch (ss.ReadChar())
From 2cf9c53c050c733c26b663f2ce9b5a5ca420911e Mon Sep 17 00:00:00 2001
From: Coxxs <58-coxxs@users.noreply.git.ryujinx.app>
Date: Sat, 21 Jun 2025 01:04:44 +0800
Subject: [PATCH 03/51] gdb: Fix crash on exit when not using Debugger
---
src/Ryujinx.HLE/Switch.cs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/Ryujinx.HLE/Switch.cs b/src/Ryujinx.HLE/Switch.cs
index ed6d57b2d..e1aa8e0e4 100644
--- a/src/Ryujinx.HLE/Switch.cs
+++ b/src/Ryujinx.HLE/Switch.cs
@@ -176,7 +176,7 @@ namespace Ryujinx.HLE
AudioDeviceDriver.Dispose();
FileSystem.Dispose();
Memory.Dispose();
- Debugger.Dispose();
+ Debugger?.Dispose();
TitleIDs.CurrentApplication.Value = null;
Shared = null;
From fceff066c588d819ac0510e0045626cddfd82032 Mon Sep 17 00:00:00 2001
From: Coxxs <58-coxxs@users.noreply.git.ryujinx.app>
Date: Sat, 21 Jun 2025 01:05:25 +0800
Subject: [PATCH 04/51] gdb: Wait for the application to start if user connect
gdb too early
---
src/Ryujinx.HLE/Debugger/Debugger.cs | 16 +++++++++++++++-
1 file changed, 15 insertions(+), 1 deletion(-)
diff --git a/src/Ryujinx.HLE/Debugger/Debugger.cs b/src/Ryujinx.HLE/Debugger/Debugger.cs
index 3eebb0b38..3ec4dbb93 100644
--- a/src/Ryujinx.HLE/Debugger/Debugger.cs
+++ b/src/Ryujinx.HLE/Debugger/Debugger.cs
@@ -47,7 +47,7 @@ namespace Ryujinx.HLE.Debugger
MessageHandlerThread.Start();
}
- private IDebuggableProcess DebugProcess => Device.System.DebugGetApplicationProcess();
+ private IDebuggableProcess DebugProcess => Device.System?.DebugGetApplicationProcess();
private KThread[] GetThreads() => DebugProcess.GetThreadUids().Select(x => DebugProcess.GetThread(x)).ToArray();
private bool IsProcessAarch32 => DebugProcess.GetThread(gThread.Value).Context.IsAarch32;
private KernelContext KernelContext => Device.System.KernelContext;
@@ -761,6 +761,20 @@ namespace Ryujinx.HLE.Debugger
{
return;
}
+
+ // If the user connects before the application is running, wait for the application to start.
+ int retries = 10;
+ while (DebugProcess == null && retries-- > 0)
+ {
+ Thread.Sleep(200);
+ }
+ if (DebugProcess == null)
+ {
+ Logger.Warning?.Print(LogClass.GdbStub, "Application is not running, cannot accept GDB client connection");
+ ClientSocket.Close();
+ continue;
+ }
+
ClientSocket.NoDelay = true;
ReadStream = new NetworkStream(ClientSocket, System.IO.FileAccess.Read);
WriteStream = new NetworkStream(ClientSocket, System.IO.FileAccess.Write);
From b94c083b1ac00adf38564b17b00fe30a6824725a Mon Sep 17 00:00:00 2001
From: Coxxs <58-coxxs@users.noreply.git.ryujinx.app>
Date: Sat, 21 Jun 2025 01:05:43 +0800
Subject: [PATCH 05/51] gdb: Add notice when application is suspended on start
---
src/Ryujinx.HLE/HOS/Kernel/Process/KProcess.cs | 1 +
1 file changed, 1 insertion(+)
diff --git a/src/Ryujinx.HLE/HOS/Kernel/Process/KProcess.cs b/src/Ryujinx.HLE/HOS/Kernel/Process/KProcess.cs
index 0c400b425..249e9720a 100644
--- a/src/Ryujinx.HLE/HOS/Kernel/Process/KProcess.cs
+++ b/src/Ryujinx.HLE/HOS/Kernel/Process/KProcess.cs
@@ -689,6 +689,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
{
mainThread.Suspend(ThreadSchedState.ThreadPauseFlag);
debugState = (int)DebugState.Stopped;
+ Logger.Notice.Print(LogClass.Kernel, $"Application is suspended on start for debugging.");
}
result = mainThread.Start();
From 2a85bfd28a7c9a8076882dfb82ec51671079fd76 Mon Sep 17 00:00:00 2001
From: Coxxs <58-coxxs@users.noreply.git.ryujinx.app>
Date: Sat, 21 Jun 2025 01:24:06 +0800
Subject: [PATCH 06/51] gdb: Remove unused using
---
src/ARMeilleure/Instructions/NativeInterface.cs | 1 -
src/ARMeilleure/State/ExecutionContext.cs | 2 --
src/Ryujinx.Cpu/Jit/JitExecutionContext.cs | 1 -
3 files changed, 4 deletions(-)
diff --git a/src/ARMeilleure/Instructions/NativeInterface.cs b/src/ARMeilleure/Instructions/NativeInterface.cs
index c44f24b7c..d43e20d83 100644
--- a/src/ARMeilleure/Instructions/NativeInterface.cs
+++ b/src/ARMeilleure/Instructions/NativeInterface.cs
@@ -3,7 +3,6 @@ using ARMeilleure.State;
using ARMeilleure.Translation;
using System;
using System.Runtime.InteropServices;
-using System.Threading;
using ExecutionContext = ARMeilleure.State.ExecutionContext;
namespace ARMeilleure.Instructions
diff --git a/src/ARMeilleure/State/ExecutionContext.cs b/src/ARMeilleure/State/ExecutionContext.cs
index 2978bf27a..0b0a83c9f 100644
--- a/src/ARMeilleure/State/ExecutionContext.cs
+++ b/src/ARMeilleure/State/ExecutionContext.cs
@@ -1,6 +1,4 @@
using ARMeilleure.Memory;
-using System.Collections.Concurrent;
-using System.Diagnostics;
using System.Threading;
namespace ARMeilleure.State
diff --git a/src/Ryujinx.Cpu/Jit/JitExecutionContext.cs b/src/Ryujinx.Cpu/Jit/JitExecutionContext.cs
index d4775f3ed..f00acc1d7 100644
--- a/src/Ryujinx.Cpu/Jit/JitExecutionContext.cs
+++ b/src/Ryujinx.Cpu/Jit/JitExecutionContext.cs
@@ -1,6 +1,5 @@
using ARMeilleure.Memory;
using ARMeilleure.State;
-using System.Threading;
using ExecutionContext = ARMeilleure.State.ExecutionContext;
namespace Ryujinx.Cpu.Jit
From 16ad91d05e38e824eae7999fb92cb9e107216d0f Mon Sep 17 00:00:00 2001
From: Coxxs <58-coxxs@users.noreply.git.ryujinx.app>
Date: Sat, 21 Jun 2025 01:47:19 +0800
Subject: [PATCH 07/51] gdb: Fix crash on stop emulation if gdb stub is enabled
with app running
---
src/Ryujinx.HLE/HOS/Kernel/Process/KProcess.cs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/Ryujinx.HLE/HOS/Kernel/Process/KProcess.cs b/src/Ryujinx.HLE/HOS/Kernel/Process/KProcess.cs
index 249e9720a..7edf263e2 100644
--- a/src/Ryujinx.HLE/HOS/Kernel/Process/KProcess.cs
+++ b/src/Ryujinx.HLE/HOS/Kernel/Process/KProcess.cs
@@ -743,7 +743,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
ExceptionCallback breakCallback = null;
ExceptionCallbackNoArgs stepCallback = null;
- if (KernelContext.Device.Configuration.EnableGdbStub)
+ if (KernelContext.Device.Configuration.EnableGdbStub && KernelContext.Device.Debugger != null)
{
breakCallback = KernelContext.Device.Debugger.BreakHandler;
stepCallback = KernelContext.Device.Debugger.StepHandler;
From 9c5d81201d6884b1336917c5752895fa8d97c86b Mon Sep 17 00:00:00 2001
From: Coxxs <58-coxxs@users.noreply.git.ryujinx.app>
Date: Sat, 21 Jun 2025 05:25:22 +0800
Subject: [PATCH 08/51] gdb: Fix GdbWriteRegister endianness
---
src/Ryujinx.HLE/Debugger/Debugger.cs | 28 ++++++++++++++--------------
1 file changed, 14 insertions(+), 14 deletions(-)
diff --git a/src/Ryujinx.HLE/Debugger/Debugger.cs b/src/Ryujinx.HLE/Debugger/Debugger.cs
index 3ec4dbb93..4350aab53 100644
--- a/src/Ryujinx.HLE/Debugger/Debugger.cs
+++ b/src/Ryujinx.HLE/Debugger/Debugger.cs
@@ -86,38 +86,38 @@ namespace Ryujinx.HLE.Debugger
{
case >= 0 and <= 31:
{
- ulong value = ss.ReadLengthAsHex(16);
+ ulong value = ss.ReadLengthAsLEHex(16);
state.SetX(gdbRegId, value);
return true;
}
case 32:
{
- ulong value = ss.ReadLengthAsHex(16);
+ ulong value = ss.ReadLengthAsLEHex(16);
state.DebugPc = value;
return true;
}
case 33:
{
- ulong value = ss.ReadLengthAsHex(8);
+ ulong value = ss.ReadLengthAsLEHex(8);
state.Pstate = (uint)value;
return true;
}
case >= 34 and <= 65:
{
- ulong value0 = ss.ReadLengthAsHex(16);
- ulong value1 = ss.ReadLengthAsHex(16);
+ ulong value0 = ss.ReadLengthAsLEHex(16);
+ ulong value1 = ss.ReadLengthAsLEHex(16);
state.SetV(gdbRegId - 34, new V128(value0, value1));
return true;
}
case 66:
{
- ulong value = ss.ReadLengthAsHex(8);
+ ulong value = ss.ReadLengthAsLEHex(8);
state.Fpsr = (uint)value;
return true;
}
case 67:
{
- ulong value = ss.ReadLengthAsHex(8);
+ ulong value = ss.ReadLengthAsLEHex(8);
state.Fpcr = (uint)value;
return true;
}
@@ -158,32 +158,32 @@ namespace Ryujinx.HLE.Debugger
{
case >= 0 and <= 14:
{
- ulong value = ss.ReadLengthAsHex(8);
+ ulong value = ss.ReadLengthAsLEHex(8);
state.SetX(gdbRegId, value);
return true;
}
case 15:
{
- ulong value = ss.ReadLengthAsHex(8);
+ ulong value = ss.ReadLengthAsLEHex(8);
state.DebugPc = value;
return true;
}
case 16:
{
- ulong value = ss.ReadLengthAsHex(8);
+ ulong value = ss.ReadLengthAsLEHex(8);
state.Pstate = (uint)value;
return true;
}
case >= 17 and <= 32:
{
- ulong value0 = ss.ReadLengthAsHex(16);
- ulong value1 = ss.ReadLengthAsHex(16);
+ ulong value0 = ss.ReadLengthAsLEHex(16);
+ ulong value1 = ss.ReadLengthAsLEHex(16);
state.SetV(gdbRegId - 17, new V128(value0, value1));
return true;
}
case >= 33 and <= 64:
{
- ulong value = ss.ReadLengthAsHex(16);
+ ulong value = ss.ReadLengthAsLEHex(16);
int regId = (gdbRegId - 33);
int regNum = regId / 2;
int shift = regId % 2;
@@ -193,7 +193,7 @@ namespace Ryujinx.HLE.Debugger
}
case 65:
{
- ulong value = ss.ReadLengthAsHex(8);
+ ulong value = ss.ReadLengthAsLEHex(8);
state.Fpsr = (uint)value & FpcrMask;
state.Fpcr = (uint)value & ~FpcrMask;
return true;
From a2b35ecce36767173a9e3db55096e441d550aeef Mon Sep 17 00:00:00 2001
From: Coxxs <58-coxxs@users.noreply.git.ryujinx.app>
Date: Sat, 21 Jun 2025 05:26:48 +0800
Subject: [PATCH 09/51] gdb: Add timeout to prevent deadlock in DebugStep
Deadlock can happen when step at some svc instructions.
---
src/Ryujinx.HLE/HOS/Kernel/Process/KProcess.cs | 13 ++++++++++++-
1 file changed, 12 insertions(+), 1 deletion(-)
diff --git a/src/Ryujinx.HLE/HOS/Kernel/Process/KProcess.cs b/src/Ryujinx.HLE/HOS/Kernel/Process/KProcess.cs
index 7edf263e2..c24d5c3cc 100644
--- a/src/Ryujinx.HLE/HOS/Kernel/Process/KProcess.cs
+++ b/src/Ryujinx.HLE/HOS/Kernel/Process/KProcess.cs
@@ -1283,7 +1283,12 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
}
_kernelContext.CriticalSection.Leave();
- StepBarrier.SignalAndWait();
+ bool stepTimedOut = false;
+ if (!StepBarrier.SignalAndWait(TimeSpan.FromMilliseconds(2000)))
+ {
+ Logger.Warning?.Print(LogClass.Kernel, $"Failed to step thread {target.ThreadUid} in time.");
+ stepTimedOut = true;
+ }
_kernelContext.CriticalSection.Enter();
steppingThread = null;
@@ -1302,6 +1307,12 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
target.Suspend(ThreadSchedState.ThreadPauseFlag);
}
_kernelContext.CriticalSection.Leave();
+
+ if (stepTimedOut)
+ {
+ return false;
+ }
+
StepBarrier.SignalAndWait();
return true;
}
From eb8b35b1704fe5f6aa0d98e79ddc007f3c0b7e64 Mon Sep 17 00:00:00 2001
From: Coxxs <58-coxxs@users.noreply.git.ryujinx.app>
Date: Sat, 21 Jun 2025 05:27:39 +0800
Subject: [PATCH 10/51] gdb: Fix crash when gdb client disconnected in some
cases
---
src/Ryujinx.HLE/Debugger/Debugger.cs | 56 ++++++++++++++++------------
1 file changed, 33 insertions(+), 23 deletions(-)
diff --git a/src/Ryujinx.HLE/Debugger/Debugger.cs b/src/Ryujinx.HLE/Debugger/Debugger.cs
index 4350aab53..608ab977a 100644
--- a/src/Ryujinx.HLE/Debugger/Debugger.cs
+++ b/src/Ryujinx.HLE/Debugger/Debugger.cs
@@ -208,30 +208,40 @@ namespace Ryujinx.HLE.Debugger
while (!_shuttingDown)
{
IMessage msg = Messages.Take();
- switch (msg)
+ try {
+ switch (msg)
+ {
+ case BreakInMessage:
+ Logger.Notice.Print(LogClass.GdbStub, "Break-in requested");
+ CommandQuery();
+ break;
+
+ case SendNackMessage:
+ WriteStream.WriteByte((byte)'-');
+ break;
+
+ case CommandMessage { Command: var cmd }:
+ Logger.Debug?.Print(LogClass.GdbStub, $"Received Command: {cmd}");
+ WriteStream.WriteByte((byte)'+');
+ ProcessCommand(cmd);
+ break;
+
+ case ThreadBreakMessage { Context: var ctx }:
+ DebugProcess.DebugStop();
+ Reply($"T05thread:{ctx.ThreadUid:x};");
+ break;
+
+ case KillMessage:
+ return;
+ }
+ }
+ catch (IOException e)
{
- case BreakInMessage:
- Logger.Notice.Print(LogClass.GdbStub, "Break-in requested");
- CommandQuery();
- break;
-
- case SendNackMessage:
- WriteStream.WriteByte((byte)'-');
- break;
-
- case CommandMessage { Command: var cmd }:
- Logger.Debug?.Print(LogClass.GdbStub, $"Received Command: {cmd}");
- WriteStream.WriteByte((byte)'+');
- ProcessCommand(cmd);
- break;
-
- case ThreadBreakMessage { Context: var ctx }:
- DebugProcess.DebugStop();
- Reply($"T05thread:{ctx.ThreadUid:x};");
- break;
-
- case KillMessage:
- return;
+ Logger.Error?.Print(LogClass.GdbStub, "Error while processing GDB messages", e);
+ }
+ catch (NullReferenceException e)
+ {
+ Logger.Error?.Print(LogClass.GdbStub, "Error while processing GDB messages", e);
}
}
}
From 066a3c9e565c927b69a7d84591ed673c1717dfac Mon Sep 17 00:00:00 2001
From: Coxxs <58-coxxs@users.noreply.git.ryujinx.app>
Date: Sat, 21 Jun 2025 09:37:36 +0800
Subject: [PATCH 11/51] gdb: Update DebugPc during SVC call and break
---
src/ARMeilleure/State/ExecutionContext.cs | 10 ++++++++++
1 file changed, 10 insertions(+)
diff --git a/src/ARMeilleure/State/ExecutionContext.cs b/src/ARMeilleure/State/ExecutionContext.cs
index 0b0a83c9f..c44c6e062 100644
--- a/src/ARMeilleure/State/ExecutionContext.cs
+++ b/src/ARMeilleure/State/ExecutionContext.cs
@@ -164,11 +164,21 @@ namespace ARMeilleure.State
internal void OnBreak(ulong address, int imm)
{
+ if (Optimizations.EnableDebugging)
+ {
+ DebugPc = Pc; // TODO: Is this the best place to update DebugPc?
+ }
+
_breakCallback?.Invoke(this, address, imm);
}
internal void OnSupervisorCall(ulong address, int imm)
{
+ if (Optimizations.EnableDebugging)
+ {
+ DebugPc = Pc; // TODO: Is this the best place to update DebugPc?
+ }
+
_supervisorCallback?.Invoke(this, address, imm);
}
From 9552cafaaa320f9f7b62225f467a831dde8b5a9c Mon Sep 17 00:00:00 2001
From: Coxxs <58-coxxs@users.noreply.git.ryujinx.app>
Date: Sat, 21 Jun 2025 09:43:26 +0800
Subject: [PATCH 12/51] gdb: Set correct gThread and cThread when break
---
src/Ryujinx.HLE/Debugger/Debugger.cs | 1 +
1 file changed, 1 insertion(+)
diff --git a/src/Ryujinx.HLE/Debugger/Debugger.cs b/src/Ryujinx.HLE/Debugger/Debugger.cs
index 608ab977a..dd6394e60 100644
--- a/src/Ryujinx.HLE/Debugger/Debugger.cs
+++ b/src/Ryujinx.HLE/Debugger/Debugger.cs
@@ -916,6 +916,7 @@ namespace Ryujinx.HLE.Debugger
public void BreakHandler(IExecutionContext ctx, ulong address, int imm)
{
+ gThread = cThread = ctx.ThreadUid;
Logger.Notice.Print(LogClass.GdbStub, $"Break hit on thread {ctx.ThreadUid} at pc {address:x016}");
Messages.Add(new ThreadBreakMessage(ctx, address, imm));
From 92d8f0eb1c6435cd4f205c8c8acc9c1d72b4a98b Mon Sep 17 00:00:00 2001
From: Coxxs <58-coxxs@users.noreply.git.ryujinx.app>
Date: Sat, 21 Jun 2025 10:22:08 +0800
Subject: [PATCH 13/51] gdb: Show thread names
Reference: https://github.com/Atmosphere-NX/Atmosphere/blob/d8a37b4b7184b80ba979bcceb98365b8365a1c3a/libraries/libstratosphere/source/osdbg/impl/osdbg_thread_type.os.horizon.hpp
---
src/Ryujinx.HLE/Debugger/Debugger.cs | 62 ++++++++++--
.../HOS/Kernel/Threading/KThread.cs | 95 +++++++++++++++++++
2 files changed, 151 insertions(+), 6 deletions(-)
diff --git a/src/Ryujinx.HLE/Debugger/Debugger.cs b/src/Ryujinx.HLE/Debugger/Debugger.cs
index dd6394e60..9b9850e58 100644
--- a/src/Ryujinx.HLE/Debugger/Debugger.cs
+++ b/src/Ryujinx.HLE/Debugger/Debugger.cs
@@ -34,6 +34,8 @@ namespace Ryujinx.HLE.Debugger
private ulong? cThread;
private ulong? gThread;
+ private string previousThreadListXml = "";
+
public Debugger(Switch device, ushort port)
{
Device = device;
@@ -368,7 +370,7 @@ namespace Ryujinx.HLE.Debugger
if (ss.ConsumePrefix("Supported:") || ss.ConsumeRemaining("Supported"))
{
- Reply("PacketSize=10000;qXfer:features:read+");
+ Reply("PacketSize=10000;qXfer:features:read+;qXfer:threads:read+");
break;
}
@@ -404,10 +406,43 @@ namespace Ryujinx.HLE.Debugger
break;
}
+ if (ss.ConsumePrefix("Xfer:threads:read:"))
+ {
+ ss.ReadUntil(':');
+ ulong offset = ss.ReadUntilAsHex(',');
+ ulong len = ss.ReadRemainingAsHex();
+
+ var data = "";
+ if (offset > 0)
+ {
+ data = previousThreadListXml;
+ } else
+ {
+ previousThreadListXml = data = GetThreadListXml();
+ }
+
+ if (offset >= (ulong)data.Length)
+ {
+ Reply("l");
+ break;
+ }
+
+ if (len >= (ulong)data.Length - offset)
+ {
+ Reply("l" + ToBinaryFormat(data.Substring((int)offset)));
+ break;
+ }
+ else
+ {
+ Reply("m" + ToBinaryFormat(data.Substring((int)offset, (int)len)));
+ break;
+ }
+ }
+
if (ss.ConsumePrefix("Xfer:features:read:"))
{
string feature = ss.ReadUntil(':');
- ulong addr = ss.ReadUntilAsHex(',');
+ ulong offset = ss.ReadUntilAsHex(',');
ulong len = ss.ReadRemainingAsHex();
if (feature == "target.xml")
@@ -418,20 +453,20 @@ namespace Ryujinx.HLE.Debugger
string data;
if (RegisterInformation.Features.TryGetValue(feature, out data))
{
- if (addr >= (ulong)data.Length)
+ if (offset >= (ulong)data.Length)
{
Reply("l");
break;
}
- if (len >= (ulong)data.Length - addr)
+ if (len >= (ulong)data.Length - offset)
{
- Reply("l" + ToBinaryFormat(data.Substring((int)addr)));
+ Reply("l" + ToBinaryFormat(data.Substring((int)offset)));
break;
}
else
{
- Reply("m" + ToBinaryFormat(data.Substring((int)addr, (int)len)));
+ Reply("m" + ToBinaryFormat(data.Substring((int)offset, (int)len)));
break;
}
}
@@ -469,6 +504,21 @@ namespace Ryujinx.HLE.Debugger
}
}
+ private string GetThreadListXml()
+ {
+ var sb = new StringBuilder();
+ sb.Append("\n");
+
+ foreach (var thread in GetThreads())
+ {
+ string threadName = System.Security.SecurityElement.Escape(thread.GetThreadName());
+ sb.Append($"\n");
+ }
+
+ sb.Append("");
+ return sb.ToString();
+ }
+
void CommandQuery()
{
// GDB is performing initial contact. Stop everything.
diff --git a/src/Ryujinx.HLE/HOS/Kernel/Threading/KThread.cs b/src/Ryujinx.HLE/HOS/Kernel/Threading/KThread.cs
index 2b5d11244..bb0548d19 100644
--- a/src/Ryujinx.HLE/HOS/Kernel/Threading/KThread.cs
+++ b/src/Ryujinx.HLE/HOS/Kernel/Threading/KThread.cs
@@ -5,9 +5,11 @@ using Ryujinx.HLE.HOS.Kernel.Common;
using Ryujinx.HLE.HOS.Kernel.Process;
using Ryujinx.HLE.HOS.Kernel.SupervisorCall;
using Ryujinx.Horizon.Common;
+using Ryujinx.Memory;
using System;
using System.Collections.Generic;
using System.Numerics;
+using System.Text;
using System.Threading;
namespace Ryujinx.HLE.HOS.Kernel.Threading
@@ -17,6 +19,23 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
private const int TlsUserDisableCountOffset = 0x100;
private const int TlsUserInterruptFlagOffset = 0x102;
+ // Tls -> ThreadType
+ private const int TlsThreadTypeOffsetAArch64 = 0x1F8;
+ private const int TlsThreadTypeOffsetAArch32 = 0x1FC;
+
+ // Tls -> ThreadType -> Version
+ private const int TlsThreadTypeVersionOffsetAArch64 = 0x46;
+ private const int TlsThreadTypeVersionOffsetAArch32 = 0x26;
+
+ // Tls -> ThreadType (Version 0) -> ThreadNamePointer
+ private const int TlsThreadTypeVersion0ThreadNamePointerOffsetAArch64 = 0x1A8;
+ private const int TlsThreadTypeVersion0ThreadNamePointerOffsetAArch32 = 0xE8;
+
+ // Tls -> ThreadType (Version 1) -> ThreadNamePointer
+ private const int TlsThreadTypeThreadNamePointerOffsetAArch64 = 0x1A0;
+ private const int TlsThreadTypeThreadNamePointerOffsetAArch32 = 0xE4;
+
+
public const int MaxWaitSyncObjects = 64;
private ManualResetEvent _schedulerWaitEvent;
@@ -1439,5 +1458,81 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
{
Owner.CpuMemory.Write(_tlsAddress + TlsUserInterruptFlagOffset, 0);
}
+
+ public string GetThreadName()
+ {
+ try
+ {
+ ulong threadNamePtr = 0;
+ if (Context.IsAarch32)
+ {
+ uint threadTypePtr32 = Owner.CpuMemory.Read(_tlsAddress + TlsThreadTypeOffsetAArch32);
+ if (threadTypePtr32 == 0)
+ {
+ return "";
+ }
+
+ ushort version = Owner.CpuMemory.Read(threadTypePtr32 + TlsThreadTypeVersionOffsetAArch32);
+ switch (version)
+ {
+ case 0x0000:
+ case 0xFFFF:
+ threadNamePtr = Owner.CpuMemory.Read(threadTypePtr32 + TlsThreadTypeVersion0ThreadNamePointerOffsetAArch32);
+ break;
+ case 0x0001:
+ threadNamePtr = Owner.CpuMemory.Read(threadTypePtr32 + TlsThreadTypeThreadNamePointerOffsetAArch32);
+ break;
+ default:
+ Logger.Warning?.Print(LogClass.Kernel, $"Unknown ThreadType struct version: {version}");
+ break;
+ }
+ }
+ else
+ {
+ ulong threadTypePtr64 = Owner.CpuMemory.Read(_tlsAddress + TlsThreadTypeOffsetAArch64);
+ if (threadTypePtr64 == 0)
+ {
+ return "";
+ }
+
+ ushort version = Owner.CpuMemory.Read(threadTypePtr64 + TlsThreadTypeVersionOffsetAArch64);
+ switch (version)
+ {
+ case 0x0000:
+ case 0xFFFF:
+ threadNamePtr = Owner.CpuMemory.Read(threadTypePtr64 + TlsThreadTypeVersion0ThreadNamePointerOffsetAArch64);
+ break;
+ case 0x0001:
+ threadNamePtr = Owner.CpuMemory.Read(threadTypePtr64 + TlsThreadTypeThreadNamePointerOffsetAArch64);
+ break;
+ default:
+ Logger.Warning?.Print(LogClass.Kernel, $"Unknown ThreadType struct version: {version}");
+ break;
+ }
+ }
+
+ if (threadNamePtr == 0)
+ {
+ return "";
+ }
+
+ List nameBytes = new();
+ for (int i = 0; i < 0x20; i++)
+ {
+ byte b = Owner.CpuMemory.Read(threadNamePtr + (ulong)i);
+ if (b == 0)
+ {
+ break;
+ }
+ nameBytes.Add(b);
+ }
+ return Encoding.UTF8.GetString(nameBytes.ToArray());
+
+ } catch (InvalidMemoryRegionException)
+ {
+ Logger.Warning?.Print(LogClass.Kernel, "Failed to get thread name.");
+ return "";
+ }
+ }
}
}
From 55a43dea456e1da01c24266d62f7d7130fa41575 Mon Sep 17 00:00:00 2001
From: Coxxs <58-coxxs@users.noreply.git.ryujinx.app>
Date: Sat, 21 Jun 2025 14:31:00 +0800
Subject: [PATCH 14/51] gdb: Fix ExecutionContext
---
src/ARMeilleure/State/ExecutionContext.cs | 6 +++---
.../LightningJit/State/ExecutionContext.cs | 13 ++++++++++++-
2 files changed, 15 insertions(+), 4 deletions(-)
diff --git a/src/ARMeilleure/State/ExecutionContext.cs b/src/ARMeilleure/State/ExecutionContext.cs
index c44c6e062..fa1a4a032 100644
--- a/src/ARMeilleure/State/ExecutionContext.cs
+++ b/src/ARMeilleure/State/ExecutionContext.cs
@@ -153,7 +153,7 @@ namespace ARMeilleure.State
public void StepHandler()
{
- _stepCallback.Invoke(this);
+ _stepCallback?.Invoke(this);
}
public void RequestDebugStep()
@@ -166,7 +166,7 @@ namespace ARMeilleure.State
{
if (Optimizations.EnableDebugging)
{
- DebugPc = Pc; // TODO: Is this the best place to update DebugPc?
+ DebugPc = Pc;
}
_breakCallback?.Invoke(this, address, imm);
@@ -176,7 +176,7 @@ namespace ARMeilleure.State
{
if (Optimizations.EnableDebugging)
{
- DebugPc = Pc; // TODO: Is this the best place to update DebugPc?
+ DebugPc = Pc;
}
_supervisorCallback?.Invoke(this, address, imm);
diff --git a/src/Ryujinx.Cpu/LightningJit/State/ExecutionContext.cs b/src/Ryujinx.Cpu/LightningJit/State/ExecutionContext.cs
index fc75e5185..cb3c6c2af 100644
--- a/src/Ryujinx.Cpu/LightningJit/State/ExecutionContext.cs
+++ b/src/Ryujinx.Cpu/LightningJit/State/ExecutionContext.cs
@@ -1,3 +1,4 @@
+using ARMeilleure;
using ARMeilleure.Memory;
using ARMeilleure.State;
using System;
@@ -127,7 +128,7 @@ namespace Ryujinx.Cpu.LightningJit.State
public void StepHandler()
{
- _stepCallback.Invoke(this);
+ _stepCallback?.Invoke(this);
}
public void RequestDebugStep()
@@ -138,11 +139,21 @@ namespace Ryujinx.Cpu.LightningJit.State
internal void OnBreak(ulong address, int imm)
{
+ if (Optimizations.EnableDebugging)
+ {
+ DebugPc = Pc;
+ }
+
_breakCallback?.Invoke(this, address, imm);
}
internal void OnSupervisorCall(ulong address, int imm)
{
+ if (Optimizations.EnableDebugging)
+ {
+ DebugPc = Pc;
+ }
+
_supervisorCallback?.Invoke(this, address, imm);
}
From 6afce6308cbb9833afcd8f5d7067ae00a419141e Mon Sep 17 00:00:00 2001
From: Coxxs <58-coxxs@users.noreply.git.ryujinx.app>
Date: Sat, 21 Jun 2025 15:38:32 +0800
Subject: [PATCH 15/51] gdb: Do not use LightningJitEngine when GDB Stub is
enabled
---
src/Ryujinx.HLE/HOS/ArmProcessContextFactory.cs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/Ryujinx.HLE/HOS/ArmProcessContextFactory.cs b/src/Ryujinx.HLE/HOS/ArmProcessContextFactory.cs
index 759780c42..28f7ef25f 100644
--- a/src/Ryujinx.HLE/HOS/ArmProcessContextFactory.cs
+++ b/src/Ryujinx.HLE/HOS/ArmProcessContextFactory.cs
@@ -69,7 +69,7 @@ namespace Ryujinx.HLE.HOS
mode = MemoryManagerMode.SoftwarePageTable;
}
- ICpuEngine cpuEngine = isArm64Host && (mode == MemoryManagerMode.HostMapped || mode == MemoryManagerMode.HostMappedUnsafe)
+ ICpuEngine cpuEngine = isArm64Host && (mode == MemoryManagerMode.HostMapped || mode == MemoryManagerMode.HostMappedUnsafe) && !context.Device.Configuration.EnableGdbStub
? new LightningJitEngine(_tickSource)
: new JitEngine(_tickSource);
From ee947ae5920ef33f758d0ef317cccd4990b4f2f7 Mon Sep 17 00:00:00 2001
From: Coxxs <58-coxxs@users.noreply.git.ryujinx.app>
Date: Sun, 22 Jun 2025 02:31:04 +0800
Subject: [PATCH 16/51] gdb: Implement vCont to support step on AArch32
---
src/Ryujinx.HLE/Debugger/Debugger.cs | 125 ++++++++++++++++++++++++++-
1 file changed, 123 insertions(+), 2 deletions(-)
diff --git a/src/Ryujinx.HLE/Debugger/Debugger.cs b/src/Ryujinx.HLE/Debugger/Debugger.cs
index 9b9850e58..d1b41c7f7 100644
--- a/src/Ryujinx.HLE/Debugger/Debugger.cs
+++ b/src/Ryujinx.HLE/Debugger/Debugger.cs
@@ -6,6 +6,7 @@ using Ryujinx.HLE.HOS.Kernel.Threading;
using Ryujinx.Memory;
using System;
using System.Collections.Concurrent;
+using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
@@ -230,6 +231,7 @@ namespace Ryujinx.HLE.Debugger
case ThreadBreakMessage { Context: var ctx }:
DebugProcess.DebugStop();
+ gThread = cThread = ctx.ThreadUid;
Reply($"T05thread:{ctx.ThreadUid:x};");
break;
@@ -370,7 +372,7 @@ namespace Ryujinx.HLE.Debugger
if (ss.ConsumePrefix("Supported:") || ss.ConsumeRemaining("Supported"))
{
- Reply("PacketSize=10000;qXfer:features:read+;qXfer:threads:read+");
+ Reply("PacketSize=10000;qXfer:features:read+;qXfer:threads:read+;vContSupported+");
break;
}
@@ -490,6 +492,22 @@ namespace Ryujinx.HLE.Debugger
break;
}
case 'v':
+ if (ss.ConsumePrefix("Cont"))
+ {
+ if (ss.ConsumeRemaining("?"))
+ {
+ Reply("vCont;c;C;s;S");
+ break;
+ }
+
+ if (ss.ConsumePrefix(";"))
+ {
+ HandleVContCommand(ss);
+ break;
+ }
+
+ goto unknownCommand;
+ }
if (ss.ConsumeRemaining("MustReplyEmpty"))
{
Reply("");
@@ -504,6 +522,109 @@ namespace Ryujinx.HLE.Debugger
}
}
+ enum VContAction
+ {
+ None,
+ Continue,
+ Stop,
+ Step
+ }
+
+ record VContPendingAction(VContAction Action, ushort? Signal = null);
+
+ private void HandleVContCommand(StringStream ss)
+ {
+ string[] rawActions = ss.ReadRemaining().Split(';', StringSplitOptions.RemoveEmptyEntries);
+
+ var threadActionMap = new Dictionary();
+ foreach (var thread in GetThreads())
+ {
+ threadActionMap[thread.ThreadUid] = new VContPendingAction(VContAction.None);
+ }
+
+ // For each inferior thread, the *leftmost* action with a matching thread-id is applied.
+ for (int i = rawActions.Length - 1; i >= 0; i--)
+ {
+ var rawAction = rawActions[i];
+ var stream = new StringStream(rawAction);
+
+ char cmd = stream.ReadChar();
+ VContAction action = cmd switch
+ {
+ 'c' => VContAction.Continue,
+ 'C' => VContAction.Continue,
+ 's' => VContAction.Step,
+ 'S' => VContAction.Step,
+ 't' => VContAction.Stop,
+ _ => VContAction.None
+ };
+
+ ushort? signal = null;
+ if (cmd == 'C' || cmd == 'S')
+ {
+ signal = (ushort)stream.ReadLengthAsHex(2);
+ }
+
+ ulong? threadId = null;
+ if (stream.ConsumePrefix(":"))
+ {
+ threadId = stream.ReadRemainingAsThreadUid();
+ }
+
+ if (threadId.HasValue)
+ {
+ if (threadActionMap.ContainsKey(threadId.Value)) {
+ threadActionMap[threadId.Value] = new VContPendingAction(action, signal);
+ }
+ }
+ else
+ {
+ foreach (var row in threadActionMap.ToList())
+ {
+ threadActionMap[row.Key] = new VContPendingAction(action, signal);
+ }
+ }
+ }
+
+ bool hasError = false;
+
+ // TODO: We don't support stop or continue yet, and we don't support signals.
+ foreach (var (threadUid, action) in threadActionMap)
+ {
+ if (action.Action == VContAction.Step)
+ {
+ var thread = DebugProcess.GetThread(threadUid);
+ if (!DebugProcess.DebugStep(thread)) {
+ hasError = true;
+ }
+ }
+ }
+
+ // If all threads are set to continue, continue the process.
+ if (threadActionMap.Values.All(a => a.Action == VContAction.Continue))
+ {
+ DebugProcess.DebugContinue();
+ }
+
+ if (hasError)
+ {
+ ReplyError();
+ }
+ else
+ {
+ ReplyOK();
+ }
+
+ foreach (var (threadUid, action) in threadActionMap)
+ {
+ if (action.Action == VContAction.Step)
+ {
+ gThread = cThread = threadUid;
+ Reply($"T05thread:{threadUid:x};");
+ }
+ }
+ }
+
private string GetThreadListXml()
{
var sb = new StringBuilder();
@@ -772,6 +893,7 @@ namespace Ryujinx.HLE.Debugger
}
else
{
+ gThread = cThread = thread.ThreadUid;
Reply($"T05thread:{thread.ThreadUid:x};");
}
}
@@ -966,7 +1088,6 @@ namespace Ryujinx.HLE.Debugger
public void BreakHandler(IExecutionContext ctx, ulong address, int imm)
{
- gThread = cThread = ctx.ThreadUid;
Logger.Notice.Print(LogClass.GdbStub, $"Break hit on thread {ctx.ThreadUid} at pc {address:x016}");
Messages.Add(new ThreadBreakMessage(ctx, address, imm));
From 29f248121395ca72929ec3fcc24da004b578b1c3 Mon Sep 17 00:00:00 2001
From: Coxxs <58-coxxs@users.noreply.git.ryujinx.app>
Date: Sun, 22 Jun 2025 05:28:31 +0800
Subject: [PATCH 17/51] gdb: Implement z0/Z0 software breakpoints
---
src/Ryujinx.HLE/Debugger/BreakpointManager.cs | 203 ++++++++++++++++++
src/Ryujinx.HLE/Debugger/Debugger.cs | 80 ++++++-
2 files changed, 280 insertions(+), 3 deletions(-)
create mode 100644 src/Ryujinx.HLE/Debugger/BreakpointManager.cs
diff --git a/src/Ryujinx.HLE/Debugger/BreakpointManager.cs b/src/Ryujinx.HLE/Debugger/BreakpointManager.cs
new file mode 100644
index 000000000..bf462a781
--- /dev/null
+++ b/src/Ryujinx.HLE/Debugger/BreakpointManager.cs
@@ -0,0 +1,203 @@
+using Ryujinx.Common;
+using Ryujinx.Common.Logging;
+using Ryujinx.HLE.HOS.Kernel.Threading;
+using Ryujinx.Memory;
+using System.Collections.Concurrent;
+using System.Linq;
+
+namespace Ryujinx.HLE.Debugger
+{
+ internal class Breakpoint
+ {
+ public byte[] OriginalData { get; }
+
+ public bool IsStep { get; }
+
+ public Breakpoint(byte[] originalData, bool isStep)
+ {
+ OriginalData = originalData;
+ IsStep = isStep;
+ }
+ }
+
+ ///
+ /// Manages software breakpoints for the debugger.
+ ///
+ public class BreakpointManager
+ {
+ private readonly Debugger _debugger;
+ private readonly ConcurrentDictionary _breakpoints = new();
+
+ private static readonly byte[] _aarch64BreakInstruction = { 0x00, 0x00, 0x20, 0xD4 }; // BRK #0
+ private static readonly byte[] _aarch32BreakInstruction = { 0xFE, 0xDE, 0xFF, 0xE7 }; // TRAP
+ private static readonly byte[] _aarch32ThumbBreakInstruction = { 0x80, 0xB6 };
+
+ public BreakpointManager(Debugger debugger)
+ {
+ _debugger = debugger;
+ }
+
+ ///
+ /// Sets a software breakpoint at a specified address.
+ ///
+ /// The memory address to set the breakpoint at.
+ /// The length of the instruction to replace.
+ /// Indicates if this is a single-step breakpoint.
+ /// True if the breakpoint was set successfully; otherwise, false.
+ public bool SetBreakPoint(ulong address, ulong length, bool isStep = false)
+ {
+ if (_breakpoints.ContainsKey(address))
+ {
+ return false;
+ }
+
+ byte[] breakInstruction = GetBreakInstruction(length);
+ if (breakInstruction == null)
+ {
+ Logger.Error?.Print(LogClass.GdbStub, $"Unsupported instruction length for breakpoint: {length}");
+ return false;
+ }
+
+ var originalInstruction = new byte[length];
+ if (!ReadMemory(address, originalInstruction))
+ {
+ Logger.Error?.Print(LogClass.GdbStub, $"Failed to read memory at 0x{address:X16} to set breakpoint.");
+ return false;
+ }
+
+ if (!WriteMemory(address, breakInstruction))
+ {
+ Logger.Error?.Print(LogClass.GdbStub, $"Failed to write breakpoint at 0x{address:X16}.");
+ return false;
+ }
+
+ var breakpoint = new Breakpoint(originalInstruction, isStep);
+ if (_breakpoints.TryAdd(address, breakpoint))
+ {
+ Logger.Debug?.Print(LogClass.GdbStub, $"Breakpoint set at 0x{address:X16}");
+ return true;
+ }
+
+ Logger.Error?.Print(LogClass.GdbStub, $"Failed to add breakpoint at 0x{address:X16}.");
+ return false;
+ }
+
+ ///
+ /// Clears a software breakpoint at a specified address.
+ ///
+ /// The memory address of the breakpoint to clear.
+ /// The length of the instruction (unused).
+ /// True if the breakpoint was cleared successfully; otherwise, false.
+ public bool ClearBreakPoint(ulong address, ulong length)
+ {
+ if (_breakpoints.TryGetValue(address, out Breakpoint breakpoint))
+ {
+ if (!WriteMemory(address, breakpoint.OriginalData))
+ {
+ Logger.Error?.Print(LogClass.GdbStub, $"Failed to restore original instruction at 0x{address:X16} to clear breakpoint.");
+ return false;
+ }
+
+ _breakpoints.TryRemove(address, out _);
+ Logger.Debug?.Print(LogClass.GdbStub, $"Breakpoint cleared at 0x{address:X16}");
+ return true;
+ }
+
+ Logger.Warning?.Print(LogClass.GdbStub, $"No breakpoint found at address 0x{address:X16}");
+ return false;
+ }
+
+ ///
+ /// Clears all currently set software breakpoints.
+ ///
+ public void ClearAll()
+ {
+ foreach (var bp in _breakpoints)
+ {
+ if (!WriteMemory(bp.Key, bp.Value.OriginalData))
+ {
+ Logger.Error?.Print(LogClass.GdbStub, $"Failed to restore original instruction at 0x{bp.Key:X16} while clearing all breakpoints.");
+ }
+
+ }
+ _breakpoints.Clear();
+ Logger.Debug?.Print(LogClass.GdbStub, "All breakpoints cleared.");
+ }
+
+ ///
+ /// Clears all currently set single-step software breakpoints.
+ ///
+ public void ClearAllStepBreakpoints()
+ {
+ var stepBreakpoints = _breakpoints.Where(p => p.Value.IsStep).ToList();
+
+ if (stepBreakpoints.Count == 0)
+ {
+ return;
+ }
+
+ foreach (var bp in stepBreakpoints)
+ {
+ if (_breakpoints.TryRemove(bp.Key, out Breakpoint removedBreakpoint))
+ {
+ WriteMemory(bp.Key, removedBreakpoint.OriginalData);
+ }
+ }
+
+ Logger.Debug?.Print(LogClass.GdbStub, "All step breakpoints cleared.");
+ }
+
+
+ private byte[] GetBreakInstruction(ulong length)
+ {
+ if (_debugger.IsProcessAarch32)
+ {
+ if (length == 2)
+ {
+ return _aarch32ThumbBreakInstruction;
+ }
+
+ if (length == 4)
+ {
+ return _aarch32BreakInstruction;
+ }
+ }
+ else
+ {
+ if (length == 4)
+ {
+ return _aarch64BreakInstruction;
+ }
+ }
+
+ return null;
+ }
+
+ private bool ReadMemory(ulong address, byte[] data)
+ {
+ try
+ {
+ _debugger.DebugProcess.CpuMemory.Read(address, data);
+ return true;
+ }
+ catch (InvalidMemoryRegionException)
+ {
+ return false;
+ }
+ }
+
+ private bool WriteMemory(ulong address, byte[] data)
+ {
+ try
+ {
+ _debugger.DebugProcess.CpuMemory.Write(address, data);
+ _debugger.DebugProcess.InvalidateCacheRegion(address, (ulong)data.Length);
+ return true;
+ }
+ catch (InvalidMemoryRegionException)
+ {
+ return false;
+ }
+ }
+ }
+}
diff --git a/src/Ryujinx.HLE/Debugger/Debugger.cs b/src/Ryujinx.HLE/Debugger/Debugger.cs
index d1b41c7f7..3c051639d 100644
--- a/src/Ryujinx.HLE/Debugger/Debugger.cs
+++ b/src/Ryujinx.HLE/Debugger/Debugger.cs
@@ -35,6 +35,8 @@ namespace Ryujinx.HLE.Debugger
private ulong? cThread;
private ulong? gThread;
+ private BreakpointManager BreakpointManager;
+
private string previousThreadListXml = "";
public Debugger(Switch device, ushort port)
@@ -48,11 +50,12 @@ namespace Ryujinx.HLE.Debugger
DebuggerThread.Start();
MessageHandlerThread = new Thread(MessageHandlerMain);
MessageHandlerThread.Start();
+ BreakpointManager = new BreakpointManager(this);
}
- private IDebuggableProcess DebugProcess => Device.System?.DebugGetApplicationProcess();
+ internal IDebuggableProcess DebugProcess => Device.System?.DebugGetApplicationProcess();
private KThread[] GetThreads() => DebugProcess.GetThreadUids().Select(x => DebugProcess.GetThread(x)).ToArray();
- private bool IsProcessAarch32 => DebugProcess.GetThread(gThread.Value).Context.IsAarch32;
+ internal bool IsProcessAarch32 => DebugProcess.GetThread(gThread.Value).Context.IsAarch32;
private KernelContext KernelContext => Device.System.KernelContext;
const int GdbRegisterCount64 = 68;
@@ -514,6 +517,75 @@ namespace Ryujinx.HLE.Debugger
break;
}
goto unknownCommand;
+ case 'Z':
+ {
+ string type = ss.ReadUntil(',');
+ ulong addr = ss.ReadUntilAsHex(',');
+ ulong len = ss.ReadLengthAsHex(1);
+ string extra = ss.ReadRemaining();
+
+ if (extra.Length > 0)
+ {
+ Logger.Notice.Print(LogClass.GdbStub, $"Unsupported Z command extra data: {extra}");
+ ReplyError();
+ return;
+ }
+
+ switch (type)
+ {
+ case "0": // Software breakpoint
+ if (!BreakpointManager.SetBreakPoint(addr, len, false))
+ {
+ ReplyError();
+ }
+ ReplyOK();
+ return;
+ case "1": // Hardware breakpoint
+ case "2": // Write watchpoint
+ case "3": // Read watchpoint
+ case "4": // Access watchpoint
+ ReplyError();
+ return;
+ default:
+ ReplyError();
+ return;
+ }
+ }
+ case 'z':
+ {
+ string type = ss.ReadUntil(',');
+ ss.ConsumePrefix(",");
+ ulong addr = ss.ReadUntilAsHex(',');
+ ulong len = ss.ReadLengthAsHex(1);
+ string extra = ss.ReadRemaining();
+
+ if (extra.Length > 0)
+ {
+ Logger.Notice.Print(LogClass.GdbStub, $"Unsupported z command extra data: {extra}");
+ ReplyError();
+ return;
+ }
+
+ switch (type)
+ {
+ case "0": // Software breakpoint
+ if (!BreakpointManager.ClearBreakPoint(addr, len))
+ {
+ ReplyError();
+ }
+ ReplyOK();
+ return;
+ case "1": // Hardware breakpoint
+ case "2": // Write watchpoint
+ case "3": // Read watchpoint
+ case "4": // Access watchpoint
+ ReplyError();
+ return;
+ default:
+ ReplyError();
+ return;
+ }
+ }
default:
unknownCommand:
Logger.Notice.Print(LogClass.GdbStub, $"Unknown command: {cmd}");
@@ -666,7 +738,7 @@ namespace Ryujinx.HLE.Debugger
void CommandDetach()
{
- // TODO: Remove all breakpoints
+ BreakpointManager.ClearAll();
CommandContinue(null);
}
@@ -1017,6 +1089,8 @@ namespace Ryujinx.HLE.Debugger
WriteStream = null;
ClientSocket.Close();
ClientSocket = null;
+
+ BreakpointManager.ClearAll();
}
}
From 6b50663e99c9b6d2a0890cc3c3c6d967a09e636f Mon Sep 17 00:00:00 2001
From: Coxxs <58-coxxs@users.noreply.git.ryujinx.app>
Date: Sun, 22 Jun 2025 05:28:40 +0800
Subject: [PATCH 18/51] gdb: Revert ExecutionContext for now
Pc isn't reliable either
---
src/ARMeilleure/State/ExecutionContext.cs | 10 ----------
src/Ryujinx.Cpu/LightningJit/State/ExecutionContext.cs | 10 ----------
2 files changed, 20 deletions(-)
diff --git a/src/ARMeilleure/State/ExecutionContext.cs b/src/ARMeilleure/State/ExecutionContext.cs
index fa1a4a032..cdf6f56c5 100644
--- a/src/ARMeilleure/State/ExecutionContext.cs
+++ b/src/ARMeilleure/State/ExecutionContext.cs
@@ -164,21 +164,11 @@ namespace ARMeilleure.State
internal void OnBreak(ulong address, int imm)
{
- if (Optimizations.EnableDebugging)
- {
- DebugPc = Pc;
- }
-
_breakCallback?.Invoke(this, address, imm);
}
internal void OnSupervisorCall(ulong address, int imm)
{
- if (Optimizations.EnableDebugging)
- {
- DebugPc = Pc;
- }
-
_supervisorCallback?.Invoke(this, address, imm);
}
diff --git a/src/Ryujinx.Cpu/LightningJit/State/ExecutionContext.cs b/src/Ryujinx.Cpu/LightningJit/State/ExecutionContext.cs
index cb3c6c2af..a1ba0002e 100644
--- a/src/Ryujinx.Cpu/LightningJit/State/ExecutionContext.cs
+++ b/src/Ryujinx.Cpu/LightningJit/State/ExecutionContext.cs
@@ -139,21 +139,11 @@ namespace Ryujinx.Cpu.LightningJit.State
internal void OnBreak(ulong address, int imm)
{
- if (Optimizations.EnableDebugging)
- {
- DebugPc = Pc;
- }
-
_breakCallback?.Invoke(this, address, imm);
}
internal void OnSupervisorCall(ulong address, int imm)
{
- if (Optimizations.EnableDebugging)
- {
- DebugPc = Pc;
- }
-
_supervisorCallback?.Invoke(this, address, imm);
}
From 8ab9d71931d86e19eb56b41390dd1f90a2f6a15b Mon Sep 17 00:00:00 2001
From: Coxxs <58-coxxs@users.noreply.git.ryujinx.app>
Date: Mon, 23 Jun 2025 06:18:34 +0800
Subject: [PATCH 19/51] gdb: Implement QRcmd (monitor) commands
monitor backtrace (mo bt)
monitor registers (mo reg)
monitor get info
---
src/Ryujinx.HLE/Debugger/Debugger.cs | 111 +++++++++++++++++-
src/Ryujinx.HLE/HOS/Horizon.cs | 10 +-
.../HOS/Kernel/Process/HleProcessDebugger.cs | 24 +++-
.../HOS/Kernel/Threading/KThread.cs | 3 +
4 files changed, 144 insertions(+), 4 deletions(-)
diff --git a/src/Ryujinx.HLE/Debugger/Debugger.cs b/src/Ryujinx.HLE/Debugger/Debugger.cs
index 3c051639d..5bde92aed 100644
--- a/src/Ryujinx.HLE/Debugger/Debugger.cs
+++ b/src/Ryujinx.HLE/Debugger/Debugger.cs
@@ -2,6 +2,7 @@ using ARMeilleure.State;
using Ryujinx.Common;
using Ryujinx.Common.Logging;
using Ryujinx.HLE.HOS.Kernel;
+using Ryujinx.HLE.HOS.Kernel.Process;
using Ryujinx.HLE.HOS.Kernel.Threading;
using Ryujinx.Memory;
using System;
@@ -53,7 +54,8 @@ namespace Ryujinx.HLE.Debugger
BreakpointManager = new BreakpointManager(this);
}
- internal IDebuggableProcess DebugProcess => Device.System?.DebugGetApplicationProcess();
+ internal KProcess Process => Device.System?.DebugGetApplicationProcess();
+ internal IDebuggableProcess DebugProcess => Device.System?.DebugGetApplicationProcessDebugInterface();
private KThread[] GetThreads() => DebugProcess.GetThreadUids().Select(x => DebugProcess.GetThread(x)).ToArray();
internal bool IsProcessAarch32 => DebugProcess.GetThread(gThread.Value).Context.IsAarch32;
private KernelContext KernelContext => Device.System.KernelContext;
@@ -379,6 +381,13 @@ namespace Ryujinx.HLE.Debugger
break;
}
+ if (ss.ConsumePrefix("Rcmd,"))
+ {
+ string hexCommand = ss.ReadRemaining();
+ HandleQRcmdCommand(hexCommand);
+ break;
+ }
+
if (ss.ConsumeRemaining("fThreadInfo"))
{
Reply($"m{string.Join(",", DebugProcess.GetThreadUids().Select(x => $"{x:x}"))}");
@@ -982,6 +991,97 @@ namespace Ryujinx.HLE.Debugger
}
}
+ private void HandleQRcmdCommand(string hexCommand)
+ {
+ try
+ {
+ string command = FromHex(hexCommand);
+ Logger.Debug?.Print(LogClass.GdbStub, $"Received Rcmd: {command}");
+
+ string response = command.Trim().ToLowerInvariant() switch
+ {
+ "help" => "backtrace\nbt\nregisters\nreg\nget info\n",
+ "get info" => GetProcessInfo(),
+ "backtrace" => GetStackTrace(),
+ "bt" => GetStackTrace(),
+ "registers" => GetRegisters(),
+ "reg" => GetRegisters(),
+ _ => $"Unknown command: {command}\n"
+ };
+
+ Reply(ToHex(response));
+ }
+ catch (Exception e)
+ {
+ Logger.Error?.Print(LogClass.GdbStub, $"Error processing Rcmd: {e.Message}");
+ ReplyError();
+ }
+ }
+
+ private string GetStackTrace()
+ {
+ if (gThread == null)
+ return "No thread selected\n";
+
+ if (Process == null)
+ return "No application process found\n";
+
+ return Process.Debugger.GetGuestStackTrace(DebugProcess.GetThread(gThread.Value));
+ }
+
+ private string GetRegisters()
+ {
+ if (gThread == null)
+ return "No thread selected\n";
+
+ if (Process == null)
+ return "No application process found\n";
+
+ return Process.Debugger.GetCpuRegisterPrintout(DebugProcess.GetThread(gThread.Value));
+ }
+
+ private string GetProcessInfo()
+ {
+ try
+ {
+ if (Process == null)
+ return "No application process found\n";
+
+ KProcess kProcess = Process;
+
+ var sb = new StringBuilder();
+
+ sb.AppendLine($"Program Id: 0x{kProcess.TitleId:x16}");
+ sb.AppendLine($"Application: {(kProcess.IsApplication ? 1 : 0)}");
+ sb.AppendLine("Layout:");
+ sb.AppendLine($" Alias: 0x{kProcess.MemoryManager.AliasRegionStart:x10} - 0x{kProcess.MemoryManager.AliasRegionEnd - 1:x10}");
+ sb.AppendLine($" Heap: 0x{kProcess.MemoryManager.HeapRegionStart:x10} - 0x{kProcess.MemoryManager.HeapRegionEnd - 1:x10}");
+ sb.AppendLine($" Aslr: 0x{kProcess.MemoryManager.AslrRegionStart:x10} - 0x{kProcess.MemoryManager.AslrRegionEnd - 1:x10}");
+ sb.AppendLine($" Stack: 0x{kProcess.MemoryManager.StackRegionStart:x10} - 0x{kProcess.MemoryManager.StackRegionEnd - 1:x10}");
+
+ sb.AppendLine("Modules:");
+ var debugger = kProcess.Debugger;
+ if (debugger != null)
+ {
+ var images = debugger.GetLoadedImages();
+ for (int i = 0; i < images.Count; i++)
+ {
+ var image = images[i];
+ ulong endAddress = image.BaseAddress + image.Size - 1;
+ string name = debugger.GetGuessedNsoNameFromIndex(i);
+ sb.AppendLine($" 0x{image.BaseAddress:x10} - 0x{endAddress:x10} {name}");
+ }
+ }
+
+ return sb.ToString();
+ }
+ catch (Exception e)
+ {
+ Logger.Error?.Print(LogClass.GdbStub, $"Error getting process info: {e.Message}");
+ return $"Error getting process info: {e.Message}\n";
+ }
+ }
+
private void Reply(string cmd)
{
Logger.Debug?.Print(LogClass.GdbStub, $"Reply: {cmd}");
@@ -1108,6 +1208,15 @@ namespace Ryujinx.HLE.Debugger
return checksum;
}
+ private string FromHex(string hexString)
+ {
+ if (string.IsNullOrEmpty(hexString))
+ return string.Empty;
+
+ byte[] bytes = Convert.FromHexString(hexString);
+ return Encoding.ASCII.GetString(bytes);
+ }
+
private string ToHex(byte[] bytes)
{
return string.Join("", bytes.Select(x => $"{x:x2}"));
diff --git a/src/Ryujinx.HLE/HOS/Horizon.cs b/src/Ryujinx.HLE/HOS/Horizon.cs
index 44adb9674..517f8ef16 100644
--- a/src/Ryujinx.HLE/HOS/Horizon.cs
+++ b/src/Ryujinx.HLE/HOS/Horizon.cs
@@ -502,12 +502,20 @@ namespace Ryujinx.HLE.HOS
IsPaused = pause;
}
- internal IDebuggableProcess DebugGetApplicationProcess()
+ internal IDebuggableProcess DebugGetApplicationProcessDebugInterface()
{
lock (KernelContext.Processes)
{
return KernelContext.Processes.Values.FirstOrDefault(x => x.IsApplication)?.DebugInterface;
}
}
+
+ internal KProcess DebugGetApplicationProcess()
+ {
+ lock (KernelContext.Processes)
+ {
+ return KernelContext.Processes.Values.FirstOrDefault(x => x.IsApplication);
+ }
+ }
}
}
diff --git a/src/Ryujinx.HLE/HOS/Kernel/Process/HleProcessDebugger.cs b/src/Ryujinx.HLE/HOS/Kernel/Process/HleProcessDebugger.cs
index c4a9835cc..87da9f7a6 100644
--- a/src/Ryujinx.HLE/HOS/Kernel/Process/HleProcessDebugger.cs
+++ b/src/Ryujinx.HLE/HOS/Kernel/Process/HleProcessDebugger.cs
@@ -4,6 +4,7 @@ using Ryujinx.HLE.HOS.Kernel.Memory;
using Ryujinx.HLE.HOS.Kernel.Threading;
using Ryujinx.HLE.Loaders.Elf;
using Ryujinx.Memory;
+using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
@@ -17,7 +18,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
private readonly KProcess _owner;
- private class Image
+ public class Image
{
public ulong BaseAddress { get; }
public ulong Size { get; }
@@ -54,6 +55,15 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
trace.AppendLine($"Process: {_owner.Name}, PID: {_owner.Pid}");
+ string ThreadName = thread.GetThreadName();
+
+ if (!String.IsNullOrEmpty(ThreadName))
+ {
+ trace.AppendLine($"Thread ID: {thread.ThreadUid} ({ThreadName})");
+ } else {
+ trace.AppendLine($"Thread ID: {thread.ThreadUid}");
+ }
+
void AppendTrace(ulong address)
{
if (AnalyzePointer(out PointerInfo info, address, thread))
@@ -283,7 +293,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
return null;
}
- private string GetGuessedNsoNameFromIndex(int index)
+ public string GetGuessedNsoNameFromIndex(int index)
{
if ((uint)index > 11)
{
@@ -316,6 +326,16 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
}
}
+ public List GetLoadedImages()
+ {
+ EnsureLoaded();
+
+ lock (_images)
+ {
+ return [.. _images];
+ }
+ }
+
private void EnsureLoaded()
{
if (Interlocked.CompareExchange(ref _loaded, 1, 0) == 0)
diff --git a/src/Ryujinx.HLE/HOS/Kernel/Threading/KThread.cs b/src/Ryujinx.HLE/HOS/Kernel/Threading/KThread.cs
index bb0548d19..20fb426ba 100644
--- a/src/Ryujinx.HLE/HOS/Kernel/Threading/KThread.cs
+++ b/src/Ryujinx.HLE/HOS/Kernel/Threading/KThread.cs
@@ -1532,6 +1532,9 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
{
Logger.Warning?.Print(LogClass.Kernel, "Failed to get thread name.");
return "";
+ } catch (Exception e) {
+ Logger.Error?.Print(LogClass.Kernel, $"Error getting thread name: {e.Message}");
+ return "";
}
}
}
From 9a67734ea7de3807764749e7cb1c4313ed548a33 Mon Sep 17 00:00:00 2001
From: Coxxs <58-coxxs@users.noreply.git.ryujinx.app>
Date: Mon, 23 Jun 2025 07:00:48 +0800
Subject: [PATCH 20/51] gdb: Support precise tracking of PC value when GDB Stub
is enabled
---
src/ARMeilleure/State/ExecutionContext.cs | 10 ++++++++++
src/ARMeilleure/State/NativeContext.cs | 15 +++++++++++++++
src/ARMeilleure/Translation/Translator.cs | 18 ++++++++++++++++++
3 files changed, 43 insertions(+)
diff --git a/src/ARMeilleure/State/ExecutionContext.cs b/src/ARMeilleure/State/ExecutionContext.cs
index cdf6f56c5..fa1a4a032 100644
--- a/src/ARMeilleure/State/ExecutionContext.cs
+++ b/src/ARMeilleure/State/ExecutionContext.cs
@@ -164,11 +164,21 @@ namespace ARMeilleure.State
internal void OnBreak(ulong address, int imm)
{
+ if (Optimizations.EnableDebugging)
+ {
+ DebugPc = Pc;
+ }
+
_breakCallback?.Invoke(this, address, imm);
}
internal void OnSupervisorCall(ulong address, int imm)
{
+ if (Optimizations.EnableDebugging)
+ {
+ DebugPc = Pc;
+ }
+
_supervisorCallback?.Invoke(this, address, imm);
}
diff --git a/src/ARMeilleure/State/NativeContext.cs b/src/ARMeilleure/State/NativeContext.cs
index c90e522a9..25b5e51c3 100644
--- a/src/ARMeilleure/State/NativeContext.cs
+++ b/src/ARMeilleure/State/NativeContext.cs
@@ -22,6 +22,11 @@ namespace ARMeilleure.State
public ulong ExclusiveValueHigh;
public int Running;
public long Tpidr2El0;
+
+ ///
+ /// This is only set when Optimizations.EnableDebugging is true.
+ ///
+ public ulong CurrentPc;
}
private static NativeCtxStorage _dummyStorage = new();
@@ -39,6 +44,11 @@ namespace ARMeilleure.State
public ulong GetPc()
{
+ if (Optimizations.EnableDebugging)
+ {
+ return GetStorage().CurrentPc;
+ }
+
// TODO: More precise tracking of PC value.
return GetStorage().DispatchAddress;
}
@@ -268,6 +278,11 @@ namespace ARMeilleure.State
return StorageOffset(ref _dummyStorage, ref _dummyStorage.Running);
}
+ public static int GetCurrentPcOffset()
+ {
+ return StorageOffset(ref _dummyStorage, ref _dummyStorage.CurrentPc);
+ }
+
private static int StorageOffset(ref NativeCtxStorage storage, ref T target)
{
return (int)Unsafe.ByteOffset(ref Unsafe.As(ref storage), ref target);
diff --git a/src/ARMeilleure/Translation/Translator.cs b/src/ARMeilleure/Translation/Translator.cs
index 343e361a5..351ee34f1 100644
--- a/src/ARMeilleure/Translation/Translator.cs
+++ b/src/ARMeilleure/Translation/Translator.cs
@@ -388,6 +388,11 @@ namespace ARMeilleure.Translation
// Return to managed rather than tail call.
bool useReturns = Optimizations.EnableDebugging;
+ if (Optimizations.EnableDebugging)
+ {
+ EmitPcUpdate(context, block.Address);
+ }
+
InstEmitFlowHelper.EmitVirtualJump(context, Const(block.Address), isReturn: useReturns);
}
else
@@ -410,6 +415,11 @@ namespace ARMeilleure.Translation
}
}
+ if (Optimizations.EnableDebugging)
+ {
+ EmitPcUpdate(context, opCode.Address);
+ }
+
Operand lblPredicateSkip = default;
if (context.IsInIfThenBlock && context.CurrentIfThenBlockCond != Condition.Al)
@@ -506,6 +516,14 @@ namespace ARMeilleure.Translation
context.MarkLabel(lblExit);
}
+ internal static void EmitPcUpdate(EmitterContext context, ulong address)
+ {
+ long currentPcOffs = NativeContext.GetCurrentPcOffset();
+
+ Operand currentPcAddr = context.Add(context.LoadArgument(OperandType.I64, 0), Const(currentPcOffs));
+ context.Store(currentPcAddr, Const(address));
+ }
+
public void InvalidateJitCacheRegion(ulong address, ulong size)
{
ulong[] overlapAddresses = [];
From 106d528ad018bfb4578e7a3186ffca6796edafa1 Mon Sep 17 00:00:00 2001
From: Coxxs <58-coxxs@users.noreply.git.ryujinx.app>
Date: Mon, 23 Jun 2025 07:03:40 +0800
Subject: [PATCH 21/51] gdb: Invalidate PTC cache when GDB Stub is
enabled/disabled
---
src/ARMeilleure/Translation/PTC/Ptc.cs | 13 +++++++++++--
1 file changed, 11 insertions(+), 2 deletions(-)
diff --git a/src/ARMeilleure/Translation/PTC/Ptc.cs b/src/ARMeilleure/Translation/PTC/Ptc.cs
index f36d4256d..c69ebcadb 100644
--- a/src/ARMeilleure/Translation/PTC/Ptc.cs
+++ b/src/ARMeilleure/Translation/PTC/Ptc.cs
@@ -33,7 +33,7 @@ namespace ARMeilleure.Translation.PTC
private const string OuterHeaderMagicString = "PTCohd\0\0";
private const string InnerHeaderMagicString = "PTCihd\0\0";
- private const uint InternalVersion = 7008; //! To be incremented manually for each change to the ARMeilleure project.
+ private const uint InternalVersion = 7009; //! To be incremented manually for each change to the ARMeilleure project.
private const string ActualDir = "0";
private const string BackupDir = "1";
@@ -303,6 +303,13 @@ namespace ARMeilleure.Translation.PTC
return false;
}
+ if (outerHeader.DebuggerMode != Optimizations.EnableDebugging)
+ {
+ InvalidateCompressedStream(compressedStream);
+
+ return false;
+ }
+
nint intPtr = nint.Zero;
try
@@ -479,6 +486,7 @@ namespace ARMeilleure.Translation.PTC
MemoryManagerMode = GetMemoryManagerMode(),
OSPlatform = GetOSPlatform(),
Architecture = (uint)RuntimeInformation.ProcessArchitecture,
+ DebuggerMode = Optimizations.EnableDebugging,
UncompressedStreamSize =
(long)Unsafe.SizeOf() +
@@ -1068,7 +1076,7 @@ namespace ARMeilleure.Translation.PTC
return osPlatform;
}
- [StructLayout(LayoutKind.Sequential, Pack = 1/*, Size = 86*/)]
+ [StructLayout(LayoutKind.Sequential, Pack = 1/*, Size = 87*/)]
private struct OuterHeader
{
public ulong Magic;
@@ -1080,6 +1088,7 @@ namespace ARMeilleure.Translation.PTC
public byte MemoryManagerMode;
public uint OSPlatform;
public uint Architecture;
+ public bool DebuggerMode;
public long UncompressedStreamSize;
From 91e95f49e82d95186873be46ec4efaccda4947d2 Mon Sep 17 00:00:00 2001
From: Coxxs <58-coxxs@users.noreply.git.ryujinx.app>
Date: Mon, 23 Jun 2025 07:04:20 +0800
Subject: [PATCH 22/51] gdb: Allow PTC cache when GDB Stub is enabled
---
src/ARMeilleure/Translation/Translator.cs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/ARMeilleure/Translation/Translator.cs b/src/ARMeilleure/Translation/Translator.cs
index 351ee34f1..a5678be13 100644
--- a/src/ARMeilleure/Translation/Translator.cs
+++ b/src/ARMeilleure/Translation/Translator.cs
@@ -246,7 +246,7 @@ namespace ARMeilleure.Translation
Stubs,
address,
highCq,
- _ptc.State != PtcState.Disabled && !Optimizations.EnableDebugging,
+ _ptc.State != PtcState.Disabled,
mode: Aarch32Mode.User);
Logger.StartPass(PassName.Decoding);
From 2a392add6190d3972ffb2d494cf31def85accc91 Mon Sep 17 00:00:00 2001
From: Coxxs <58-coxxs@users.noreply.git.ryujinx.app>
Date: Mon, 23 Jun 2025 07:05:45 +0800
Subject: [PATCH 23/51] gdb: Fix single-stepping of branch instructions
---
src/ARMeilleure/Translation/Translator.cs | 104 ++++++++++++++++++++++
1 file changed, 104 insertions(+)
diff --git a/src/ARMeilleure/Translation/Translator.cs b/src/ARMeilleure/Translation/Translator.cs
index a5678be13..0300d51e1 100644
--- a/src/ARMeilleure/Translation/Translator.cs
+++ b/src/ARMeilleure/Translation/Translator.cs
@@ -195,6 +195,22 @@ namespace ARMeilleure.Translation
private ulong Step(State.ExecutionContext context, ulong address)
{
+ try
+ {
+ OpCode opCode = Decoder.DecodeOpCode(Memory, address, context.ExecutionMode);
+
+ // For branch instructions during single-stepping, we handle them manually
+ // func.Execute() will sometimes execute the entire function call, which is not what we want
+ if (opCode.Instruction.Name is InstName.Bl or InstName.Blr or InstName.Blx or InstName.Br)
+ {
+ return ExecuteBranchInstructionForStepping(context, address, opCode);
+ }
+ }
+ catch
+ {
+ // ignore
+ }
+
TranslatedFunction func = Translate(address, context.ExecutionMode, highCq: false, singleStep: true);
address = func.Execute(Stubs.ContextWrapper, context);
@@ -204,6 +220,94 @@ namespace ARMeilleure.Translation
return address;
}
+ private static ulong ExecuteBranchInstructionForStepping(State.ExecutionContext context, ulong address, OpCode opCode)
+ {
+ switch (opCode.Instruction.Name)
+ {
+ case InstName.Bl:
+ if (opCode is IOpCodeBImm opBImm)
+ {
+ // Set link register
+ if (context.ExecutionMode == ExecutionMode.Aarch64)
+ {
+ context.SetX(30, address + (ulong)opCode.OpCodeSizeInBytes); // LR = X30
+ }
+ else
+ {
+ // For ARM32, need to set the appropriate return address
+ uint returnAddr = opCode is OpCode32 op32 && op32.IsThumb
+ ? (uint)address + (uint)opCode.OpCodeSizeInBytes | 1u // Thumb bit set
+ : (uint)address + (uint)opCode.OpCodeSizeInBytes;
+ context.SetX(14, returnAddr); // LR = R14
+ }
+ return (ulong)opBImm.Immediate;
+ }
+ break;
+
+ case InstName.Blr:
+ if (opCode is OpCodeBReg opBReg)
+ {
+ // Set link register
+ if (context.ExecutionMode == ExecutionMode.Aarch64)
+ {
+ context.SetX(30, address + (ulong)opCode.OpCodeSizeInBytes); // LR = X30
+ }
+ else
+ {
+ uint returnAddr = opCode is OpCode32 op32 && op32.IsThumb
+ ? (uint)address + (uint)opCode.OpCodeSizeInBytes | 1u // Thumb bit set
+ : (uint)address + (uint)opCode.OpCodeSizeInBytes;
+ context.SetX(14, returnAddr); // LR = R14
+ }
+ return context.GetX(opBReg.Rn);
+ }
+ break;
+
+ case InstName.Blx:
+ if (opCode is IOpCodeBImm opBlxImm)
+ {
+ // Handle mode switching for BLX
+ if (opCode is OpCode32 op32)
+ {
+ uint returnAddr = op32.IsThumb
+ ? (uint)address + (uint)opCode.OpCodeSizeInBytes | 1u
+ : (uint)address + (uint)opCode.OpCodeSizeInBytes;
+ context.SetX(14, returnAddr);
+
+ // BLX switches between ARM and Thumb modes
+ context.SetPstateFlag(PState.TFlag, !op32.IsThumb);
+ }
+ return (ulong)opBlxImm.Immediate;
+ }
+ else if (opCode is IOpCode32BReg opBlxReg)
+ {
+ if (opCode is OpCode32 op32)
+ {
+ uint returnAddr = op32.IsThumb
+ ? (uint)address + (uint)opCode.OpCodeSizeInBytes | 1u
+ : (uint)address + (uint)opCode.OpCodeSizeInBytes;
+ context.SetX(14, returnAddr);
+
+ // For BLX register, the target address determines the mode
+ ulong targetAddr = context.GetX(opBlxReg.Rm);
+ context.SetPstateFlag(PState.TFlag, (targetAddr & 1) != 0);
+ return targetAddr & ~1UL; // Clear the Thumb bit for the actual address
+ }
+ }
+ break;
+
+ case InstName.Br:
+ if (opCode is OpCodeBReg opBr)
+ {
+ // BR doesn't set link register, just branches to the target
+ return context.GetX(opBr.Rn);
+ }
+ break;
+ }
+
+ throw new InvalidOperationException($"Unhandled branch instruction: {opCode.Instruction.Name}");
+ }
+
internal TranslatedFunction GetOrTranslate(ulong address, ExecutionMode mode)
{
if (!Functions.TryGetValue(address, out TranslatedFunction func))
From 8fa675de7b34b91651f056250a04f61cd6690865 Mon Sep 17 00:00:00 2001
From: Coxxs <58-coxxs@users.noreply.git.ryujinx.app>
Date: Mon, 23 Jun 2025 17:59:53 +0800
Subject: [PATCH 24/51] gdb: Adjust Settings UI
---
assets/locales.json | 41 +++++++++++++++----
.../UI/Views/Settings/SettingsDebugView.axaml | 12 +++---
2 files changed, 40 insertions(+), 13 deletions(-)
diff --git a/assets/locales.json b/assets/locales.json
index eeb882e9c..5177577b3 100644
--- a/assets/locales.json
+++ b/assets/locales.json
@@ -25053,7 +25053,7 @@
"ar_SA": "",
"de_DE": "",
"el_GR": "",
- "en_US": "Debug (WARNING: For developer use only)",
+ "en_US": "Debug",
"es_ES": "",
"fr_FR": "",
"he_IL": "",
@@ -25073,7 +25073,32 @@
}
},
{
- "ID": "EnableGDBStub",
+ "ID": "SettingsTabDebugNote",
+ "Translations": {
+ "ar_SA": "",
+ "de_DE": "",
+ "el_GR": "",
+ "en_US": "WARNING: For developer use only, will reduce performance",
+ "es_ES": "",
+ "fr_FR": "",
+ "he_IL": "",
+ "it_IT": "",
+ "ja_JP": "",
+ "ko_KR": "",
+ "no_NO": "",
+ "pl_PL": "",
+ "pt_BR": "",
+ "ru_RU": "",
+ "sv_SE": "",
+ "th_TH": "",
+ "tr_TR": "",
+ "uk_UA": "",
+ "zh_CN": "",
+ "zh_TW": ""
+ }
+ },
+ {
+ "ID": "SettingsTabDebugEnableGDBStub",
"Translations": {
"ar_SA": "",
"de_DE": "",
@@ -25098,7 +25123,7 @@
}
},
{
- "ID": "GDBStubToggleTooltip",
+ "ID": "SettingsTabDebugGDBStubToggleTooltip",
"Translations": {
"ar_SA": "",
"de_DE": "",
@@ -25123,12 +25148,12 @@
}
},
{
- "ID": "GDBStubPort",
+ "ID": "SettingsTabDebugGDBStubPort",
"Translations": {
"ar_SA": "",
"de_DE": "",
"el_GR": "",
- "en_US": "GDB stub port:",
+ "en_US": "GDB Stub Port:",
"es_ES": "",
"fr_FR": "",
"he_IL": "",
@@ -25148,12 +25173,12 @@
}
},
{
- "ID": "DebuggerSuspendOnStart",
+ "ID": "SettingsTabDebugSuspendOnStart",
"Translations": {
"ar_SA": "",
"de_DE": "",
"el_GR": "",
- "en_US": "Suspend application on start",
+ "en_US": "Suspend Application on Start",
"es_ES": "",
"fr_FR": "",
"he_IL": "",
@@ -25173,7 +25198,7 @@
}
},
{
- "ID": "DebuggerSuspendOnStartTooltip",
+ "ID": "SettingsTabDebugSuspendOnStartTooltip",
"Translations": {
"ar_SA": "",
"de_DE": "",
diff --git a/src/Ryujinx/UI/Views/Settings/SettingsDebugView.axaml b/src/Ryujinx/UI/Views/Settings/SettingsDebugView.axaml
index 036471059..f491dda24 100644
--- a/src/Ryujinx/UI/Views/Settings/SettingsDebugView.axaml
+++ b/src/Ryujinx/UI/Views/Settings/SettingsDebugView.axaml
@@ -26,19 +26,21 @@
Orientation="Vertical"
Spacing="10">
+
-
+
-
+
From 0cc94fdf37928c7197b83f904f2339fc34522594 Mon Sep 17 00:00:00 2001
From: Neo
Date: Mon, 23 Jun 2025 14:50:47 -0500
Subject: [PATCH 25/51] Update French Translation (ryubing/ryujinx!67)
See merge request ryubing/ryujinx!67
---
assets/locales.json | 552 ++++++++++++++++++++++----------------------
1 file changed, 276 insertions(+), 276 deletions(-)
diff --git a/assets/locales.json b/assets/locales.json
index 2f52ee71b..d931017aa 100644
--- a/assets/locales.json
+++ b/assets/locales.json
@@ -30,7 +30,7 @@
"el_GR": "Ελληνικά",
"en_US": "English (US)",
"es_ES": "Español (ES)",
- "fr_FR": "Français",
+ "fr_FR": "Français (FR)",
"he_IL": "עִברִית",
"it_IT": "Italiano",
"ja_JP": "日本語",
@@ -55,7 +55,7 @@
"el_GR": "Άνοιγμα Applet",
"en_US": "Open Applet",
"es_ES": "Abrir applet",
- "fr_FR": "Ouvrir un programme",
+ "fr_FR": "Ouvrir une applet",
"he_IL": "פתח יישומון",
"it_IT": "Apri applet",
"ja_JP": "アプレットを開く",
@@ -105,7 +105,7 @@
"el_GR": "Άνοιγμα του Mii Editor Applet σε Αυτόνομη λειτουργία",
"en_US": "Open Mii Editor Applet in Standalone mode",
"es_ES": "Abre el editor de Mii en modo autónomo",
- "fr_FR": "Ouvrir l'éditeur Mii en mode Standalone",
+ "fr_FR": "Ouvrir l’applet Éditeur Mii en mode autonome",
"he_IL": "פתח את יישומון עורך ה- Mii במצב עצמאי",
"it_IT": "Apri l'applet Editor Mii in modalità Standalone",
"ja_JP": "スタンドアロンモードで Mii エディタアプレットを開きます",
@@ -305,7 +305,7 @@
"el_GR": "_Φόρτωση Αρχείου Εφαρμογής",
"en_US": "_Load Application From File",
"es_ES": "_Cargar aplicación desde un archivo",
- "fr_FR": "_Charger un jeu depuis un fichier",
+ "fr_FR": "_Charger une application à partir d’un fichier",
"he_IL": "_טען יישום מקובץ",
"it_IT": "_Carica applicazione da un file",
"ja_JP": "ファイルからアプリケーションをロード(_L)",
@@ -330,7 +330,7 @@
"el_GR": "",
"en_US": "No applications found in selected file.",
"es_ES": "No se encontraron aplicaciones en el archivo seleccionado.",
- "fr_FR": "Aucun jeu trouvé dans le fichier sélectionné",
+ "fr_FR": "Aucune application trouvée dans le fichier sélectionné.",
"he_IL": "",
"it_IT": "Nessuna applicazione trovata nel file selezionato.",
"ja_JP": "",
@@ -355,7 +355,7 @@
"el_GR": "Φόρτωση Απακετάριστου _Παιχνιδιού",
"en_US": "Load _Unpacked Game",
"es_ES": "Cargar juego _desempaquetado",
- "fr_FR": "Charger un jeu extrait",
+ "fr_FR": "Charger un jeu décompressé",
"he_IL": "טען משחק _שאינו ארוז",
"it_IT": "Carica _gioco estratto",
"ja_JP": "展開されたゲームをロード",
@@ -380,7 +380,7 @@
"el_GR": "",
"en_US": "Load DLC From Folder",
"es_ES": "Cargar DLC Desde Carpeta",
- "fr_FR": "Charger les DLC depuis le dossier des DLC",
+ "fr_FR": "Charger les DLC à partir d’un dossier",
"he_IL": "",
"it_IT": "Carica DLC da una cartella",
"ja_JP": "",
@@ -405,7 +405,7 @@
"el_GR": "",
"en_US": "Load Title Updates From Folder",
"es_ES": "Cargar Actualizaciones de Títulos Desde Carpeta",
- "fr_FR": "Charger les mises à jour depuis le dossier des mises à jour",
+ "fr_FR": "Charger les mises à jour du titre à partir d’un dossier",
"he_IL": "",
"it_IT": "Carica aggiornamenti da una cartella",
"ja_JP": "",
@@ -455,7 +455,7 @@
"el_GR": "",
"en_US": "Open Screenshots Folder",
"es_ES": "",
- "fr_FR": "",
+ "fr_FR": "Ouvrir le dossier des captures d’écran",
"he_IL": "",
"it_IT": "",
"ja_JP": "",
@@ -580,7 +580,7 @@
"el_GR": "Εκκίνηση Παιχνιδιών σε Πλήρη Οθόνη",
"en_US": "Start Games in Fullscreen Mode",
"es_ES": "Iniciar juegos en pantalla completa",
- "fr_FR": "Démarrer le jeu en plein écran",
+ "fr_FR": "Démarrer les jeux en mode plein écran",
"he_IL": "התחל משחקים במסך מלא",
"it_IT": "Avvia i giochi a schermo intero",
"ja_JP": "全画面モードでゲームを開始",
@@ -605,7 +605,7 @@
"el_GR": "",
"en_US": "Start Games with UI Hidden",
"es_ES": "",
- "fr_FR": "",
+ "fr_FR": "Démarrer les jeux avec l’interface cachée",
"he_IL": "",
"it_IT": "",
"ja_JP": "",
@@ -680,7 +680,7 @@
"el_GR": "Διαχείριση Προφίλ _Χρηστών",
"en_US": "_Manage User Profiles",
"es_ES": "_Gestionar perfiles de usuario",
- "fr_FR": "_Gérer les profils d'utilisateurs",
+ "fr_FR": "_Gérer les profils utilisateur",
"he_IL": "_נהל פרופילי משתמש",
"it_IT": "_Gestisci i profili utente",
"ja_JP": "ユーザプロファイルを管理(_M)",
@@ -855,7 +855,7 @@
"el_GR": "Εγκατάσταση Firmware από τοποθεσία",
"en_US": "Install a firmware from a directory",
"es_ES": "Instalar firmware desde una carpeta",
- "fr_FR": "Installer un firmware depuis un dossier",
+ "fr_FR": "Installer un firmware à partir d’un dossier",
"he_IL": "התקן קושחה מתוך תקייה",
"it_IT": "Installa un firmware da una cartella",
"ja_JP": "ディレクトリからファームウェアをインストール",
@@ -905,7 +905,7 @@
"el_GR": "",
"en_US": "Install keys from KEYS or ZIP",
"es_ES": "Instalar keys de KEYS o ZIP",
- "fr_FR": "Installer des clés à partir de .KEYS or .ZIP",
+ "fr_FR": "Installer des clés à partir de .KEYS ou .ZIP",
"he_IL": "",
"it_IT": "Installa chiavi da file KEYS o ZIP",
"ja_JP": "",
@@ -980,7 +980,7 @@
"el_GR": "Εγκαταστήσετε τύπους αρχείων.",
"en_US": "Install file types",
"es_ES": "Instalar tipos de archivo",
- "fr_FR": "Installer les types de fichiers",
+ "fr_FR": "Installer des types de fichiers",
"he_IL": "סוגי קבצי התקנה",
"it_IT": "Installa i tipi di file",
"ja_JP": "ファイル形式をインストール",
@@ -1005,7 +1005,7 @@
"el_GR": "Απεγκαταστήσετε τύπους αρχείων",
"en_US": "Uninstall file types",
"es_ES": "Desinstalar tipos de archivo",
- "fr_FR": "Désinstaller les types de fichiers",
+ "fr_FR": "Désinstaller des types de fichiers",
"he_IL": "סוגי קבצי הסרה",
"it_IT": "Disinstalla i tipi di file",
"ja_JP": "ファイル形式をアンインストール",
@@ -1280,7 +1280,7 @@
"el_GR": "",
"en_US": "FAQ & Troubleshooting Page",
"es_ES": "",
- "fr_FR": "Page de FAQ et de dépannage",
+ "fr_FR": "FAQ et Dépannage",
"he_IL": "",
"it_IT": "Domande frequenti e risoluzione dei problemi",
"ja_JP": "",
@@ -1305,7 +1305,7 @@
"el_GR": "",
"en_US": "Opens the FAQ and Troubleshooting page on the official Ryujinx wiki",
"es_ES": "",
- "fr_FR": "Ouvre la page de FAQ et de dépannage sur le wiki officiel de Ryujinx",
+ "fr_FR": "Ouvre la page FAQ et Dépannage sur le wiki officiel de Ryujinx",
"he_IL": "",
"it_IT": "Apre la pagina della wiki ufficiale di Ryujinx relativa alle domande frequenti e alla risoluzione dei problemi",
"ja_JP": "",
@@ -1555,7 +1555,7 @@
"el_GR": "",
"en_US": "Developed by {0}",
"es_ES": "",
- "fr_FR": "",
+ "fr_FR": "Développé par {0}",
"he_IL": "",
"it_IT": "",
"ja_JP": "",
@@ -1655,7 +1655,7 @@
"el_GR": "Κατάληξη: {0}",
"en_US": "Extension: {0}",
"es_ES": "Extensión: {0}",
- "fr_FR": "Extension du Fichier: {0}",
+ "fr_FR": "Extension du fichier: {0}",
"he_IL": "",
"it_IT": "Estensione: {0}",
"ja_JP": "ファイル拡張子: {0}",
@@ -1680,7 +1680,7 @@
"el_GR": "Μέγεθος Αρχείου: {0}",
"en_US": "File Size: {0}",
"es_ES": "Tamaño del archivo: {0}",
- "fr_FR": "Taille du Fichier: {0}",
+ "fr_FR": "Taille du fichier: {0}",
"he_IL": "",
"it_IT": "Dimensione file: {0}",
"ja_JP": "ファイルサイズ: {0}",
@@ -1780,7 +1780,7 @@
"el_GR": "Κατάληξη",
"en_US": "File Ext",
"es_ES": "Extensión",
- "fr_FR": "Extension du Fichier",
+ "fr_FR": "Extension du fichier",
"he_IL": "סיומת קובץ",
"it_IT": "Estensione",
"ja_JP": "ファイル拡張子",
@@ -1805,7 +1805,7 @@
"el_GR": "Μέγεθος Αρχείου",
"en_US": "File Size",
"es_ES": "Tamaño del archivo",
- "fr_FR": "Taille du Fichier",
+ "fr_FR": "Taille du fichier",
"he_IL": "גודל הקובץ",
"it_IT": "Dimensione file",
"ja_JP": "ファイルサイズ",
@@ -1980,7 +1980,7 @@
"el_GR": "",
"en_US": "Compatibility:",
"es_ES": "",
- "fr_FR": "",
+ "fr_FR": "Compatibilité :",
"he_IL": "",
"it_IT": "",
"ja_JP": "",
@@ -2005,7 +2005,7 @@
"el_GR": "",
"en_US": "Title ID:",
"es_ES": "",
- "fr_FR": "",
+ "fr_FR": "ID du titre :",
"he_IL": "",
"it_IT": "",
"ja_JP": "",
@@ -2030,7 +2030,7 @@
"el_GR": "",
"en_US": "Hosted Games: {0}",
"es_ES": "",
- "fr_FR": "",
+ "fr_FR": "Jeux hébergés : {0}",
"he_IL": "",
"it_IT": "",
"ja_JP": "",
@@ -2055,7 +2055,7 @@
"el_GR": "",
"en_US": "Online Players: {0}",
"es_ES": "",
- "fr_FR": "",
+ "fr_FR": "Joueurs en ligne : {0}",
"he_IL": "",
"it_IT": "",
"ja_JP": "",
@@ -2130,7 +2130,7 @@
"el_GR": "Ανοίγει την τοποθεσία που περιέχει την Αποθήκευση Χρήστη της εφαρμογής",
"en_US": "Opens the directory which contains Application's User Save",
"es_ES": "Abre la carpeta que contiene la partida guardada del usuario para esta aplicación",
- "fr_FR": "Ouvre le dossier contenant la sauvegarde utilisateur du jeu",
+ "fr_FR": "Ouvre le dossier contenant la sauvegarde utilisateur de l’application",
"he_IL": "פותח את תקיית השמור של המשתמש ביישום הנוכחי",
"it_IT": "Apre la cartella che contiene i dati di salvataggio dell'utente",
"ja_JP": "アプリケーションのユーザセーブデータを格納するディレクトリを開きます",
@@ -2155,7 +2155,7 @@
"el_GR": "Άνοιγμα Τοποθεσίας Συσκευής Χρήστη",
"en_US": "Open Device Save Directory",
"es_ES": "Abrir carpeta de guardado del sistema para el usuario actual",
- "fr_FR": "Ouvrir le dossier de sauvegarde console",
+ "fr_FR": "Ouvrir le dossier de sauvegarde de l’appareil",
"he_IL": "פתח את תקיית השמור של המכשיר",
"it_IT": "Apri la cartella dei salvataggi del dispositivo",
"ja_JP": "デバイスディレクトリを開く",
@@ -2180,7 +2180,7 @@
"el_GR": "Ανοίγει την τοποθεσία που περιέχει την Αποθήκευση Συσκευής της εφαρμογής",
"en_US": "Opens the directory which contains Application's Device Save",
"es_ES": "Abre la carpeta que contiene la partida guardada del sistema para esta aplicación",
- "fr_FR": "Ouvre le dossier contenant la sauvegarde console du jeu",
+ "fr_FR": "Ouvre le dossier contenant la sauvegarde locale de l’application",
"he_IL": "פותח את הספרייה המכילה את שמור המכשיר של היישום",
"it_IT": "Apre la cartella che contiene i dati di salvataggio del dispositivo",
"ja_JP": "アプリケーションのデバイスセーブデータを格納するディレクトリを開きます",
@@ -2230,7 +2230,7 @@
"el_GR": "Ανοίγει την τοποθεσία που περιέχει την Αποθήκευση BCAT της εφαρμογής",
"en_US": "Opens the directory which contains Application's BCAT Save",
"es_ES": "Abrir la carpeta que contiene el guardado BCAT de esta aplicación",
- "fr_FR": "Ouvre le dossier contenant la sauvegarde BCAT du jeu",
+ "fr_FR": "Ouvre le dossier contenant la sauvegarde BCAT de l’application",
"he_IL": "פותח את תקיית שמור ה-BCAT של היישום",
"it_IT": "Apre la cartella che contiene il salvataggio BCAT dell'applicazione",
"ja_JP": "アプリケーションの BCAT セーブデータを格納するディレクトリを開きます",
@@ -2255,7 +2255,7 @@
"el_GR": "Διαχείριση Ενημερώσεων Παιχνιδιού",
"en_US": "Manage Title Updates",
"es_ES": "Gestionar actualizaciones del juego",
- "fr_FR": "Gérer les mises à jour",
+ "fr_FR": "Gérer les mises à jour du titre",
"he_IL": "מנהל עדכוני משחקים",
"it_IT": "Gestisci aggiornamenti del gioco",
"ja_JP": "アップデートを管理",
@@ -2280,7 +2280,7 @@
"el_GR": "Ανοίγει το παράθυρο διαχείρισης Ενημερώσεων Παιχνιδιού",
"en_US": "Opens the Title Update management window",
"es_ES": "Abrir la ventana de gestión de actualizaciones de esta aplicación",
- "fr_FR": "Ouvre la fenêtre de gestion des mises à jour du jeu",
+ "fr_FR": "Ouvre la fenêtre de gestion des mises à jour du titre",
"he_IL": "פותח את חלון מנהל עדכוני המשחקים",
"it_IT": "Apre la finestra di gestione aggiornamenti del gioco",
"ja_JP": "タイトルのアップデート管理ウインドウを開きます",
@@ -2380,7 +2380,7 @@
"el_GR": "Εκκαθάριση Προσωρινής Μνήμης PPTC",
"en_US": "Queue PPTC Rebuild",
"es_ES": "Reconstruir PPTC en cola",
- "fr_FR": "Reconstruction du PPTC",
+ "fr_FR": "Reconstruire le cache PPTC",
"he_IL": "הוסף PPTC לתור בנייה מחדש",
"it_IT": "Accoda rigenerazione della cache PPTC",
"ja_JP": "PPTC を再構築",
@@ -2405,7 +2405,7 @@
"el_GR": "Διαγράφει την προσωρινή μνήμη PPTC της εφαρμογής",
"en_US": "Trigger PPTC to rebuild at boot time on the next game launch",
"es_ES": "Elimina la caché de PPTC de esta aplicación",
- "fr_FR": "Effectuer une reconstruction du PPTC au prochain démarrage du jeu",
+ "fr_FR": "Déclencher la reconstruction du cache PPTC au prochain démarrage du jeu",
"he_IL": "גרום ל-PPTC להבנות מחדש בפתיחה הבאה של המשחק",
"it_IT": "Esegue la rigenerazione della cache PPTC al prossimo avvio del gioco",
"ja_JP": "次回のゲーム起動時に PPTC を再構築します",
@@ -2430,7 +2430,7 @@
"el_GR": "",
"en_US": "Purge PPTC cache",
"es_ES": "",
- "fr_FR": "",
+ "fr_FR": "Purger le cache PPTC",
"he_IL": "",
"it_IT": "",
"ja_JP": "",
@@ -2455,7 +2455,7 @@
"el_GR": "",
"en_US": "Deletes all PPTC cache files for the Application",
"es_ES": "",
- "fr_FR": "",
+ "fr_FR": "Supprime tous les fichiers de cache PPTC de l’application",
"he_IL": "",
"it_IT": "",
"ja_JP": "",
@@ -2480,7 +2480,7 @@
"el_GR": "Εκκαθάριση Προσωρινής Μνήμης Shader",
"en_US": "Purge Shader Cache",
"es_ES": "Limpiar caché de sombreadores",
- "fr_FR": "Purger les shaders",
+ "fr_FR": "Purger le cache des shaders",
"he_IL": "ניקוי מטמון הצללות",
"it_IT": "Elimina la cache degli shader",
"ja_JP": "シェーダーキャッシュを破棄",
@@ -2505,7 +2505,7 @@
"el_GR": "Διαγράφει την προσωρινή μνήμη Shader της εφαρμογής",
"en_US": "Deletes Application's shader cache",
"es_ES": "Eliminar la caché de sombreadores de esta aplicación",
- "fr_FR": "Supprime les shaders du jeu",
+ "fr_FR": "Supprime le cache des shaders de l’application",
"he_IL": "מוחק את מטמון ההצללות של היישום",
"it_IT": "Elimina la cache degli shader dell'applicazione",
"ja_JP": "アプリケーションのシェーダーキャッシュを破棄します",
@@ -2530,7 +2530,7 @@
"el_GR": "Άνοιγμα Τοποθεσίας PPTC",
"en_US": "Open PPTC Directory",
"es_ES": "Abrir carpeta de PPTC",
- "fr_FR": "Ouvrir le dossier du PPTC",
+ "fr_FR": "Ouvrir le dossier PPTC",
"he_IL": "פתח את תקיית PPTC",
"it_IT": "Apri la cartella della cache PPTC",
"ja_JP": "PPTC ディレクトリを開く",
@@ -2555,7 +2555,7 @@
"el_GR": "Ανοίγει την τοποθεσία που περιέχει τη προσωρινή μνήμη PPTC της εφαρμογής",
"en_US": "Opens the directory which contains Application's PPTC cache",
"es_ES": "Abrir la carpeta que contiene la caché de PPTC de esta aplicación",
- "fr_FR": "Ouvre le dossier contenant le PPTC du jeu",
+ "fr_FR": "Ouvre le dossier contenant le cache PPTC de l’application",
"he_IL": "פותח את התקייה של מטמון ה-PPTC של האפליקציה",
"it_IT": "Apre la cartella che contiene la cache PPTC dell'applicazione",
"ja_JP": "アプリケーションの PPTC キャッシュを格納するディレクトリを開きます",
@@ -2580,7 +2580,7 @@
"el_GR": "Άνοιγμα τοποθεσίας προσωρινής μνήμης Shader",
"en_US": "Open Shader Cache Directory",
"es_ES": "Abrir carpeta de caché de sombreadores",
- "fr_FR": "Ouvrir le dossier des shaders",
+ "fr_FR": "Ouvrir le dossier du cache des shaders",
"he_IL": "פתח את תקיית המטמון של ההצללות",
"it_IT": "Apri la cartella della cache degli shader",
"ja_JP": "シェーダーキャッシュディレクトリを開く",
@@ -2605,7 +2605,7 @@
"el_GR": "Ανοίγει την τοποθεσία που περιέχει την προσωρινή μνήμη Shader της εφαρμογής",
"en_US": "Opens the directory which contains Application's shader cache",
"es_ES": "Abrir la carpeta que contiene la caché de sombreadores de esta aplicación",
- "fr_FR": "Ouvre le dossier contenant les shaders du jeu",
+ "fr_FR": "Ouvre le dossier contenant le cache des shaders de l’application",
"he_IL": "פותח את תקיית מטמון ההצללות של היישום",
"it_IT": "Apre la cartella che contiene la cache degli shader dell'applicazione",
"ja_JP": "アプリケーションのシェーダーキャッシュを格納するディレクトリを開きます",
@@ -2680,7 +2680,7 @@
"el_GR": "Εξαγωγή της ενότητας ExeFS από την τρέχουσα διαμόρφωση της εφαρμογής (συμπεριλαμβανομένου ενημερώσεων)",
"en_US": "Extract the ExeFS section from Application's current config (including updates)",
"es_ES": "Extraer la sección ExeFS de la configuración actual de la aplicación (incluyendo actualizaciones)",
- "fr_FR": "Extrait la section ExeFS du jeu (mise à jour incluse)",
+ "fr_FR": "Extraire la section ExeFS de la configuration actuelle de l’application (mises à jour incluses)",
"he_IL": "חלץ את קטע ה-ExeFS מתצורת היישום הנוכחית (כולל עדכונים)",
"it_IT": "Estrae la sezione ExeFS dall'attuale configurazione dell'applicazione (inclusi gli aggiornamenti)",
"ja_JP": "現在のアプリケーション設定(アップデート含む)から ExeFS セクションを展開します",
@@ -2730,7 +2730,7 @@
"el_GR": "Εξαγωγή της ενότητας RomFS από την τρέχουσα διαμόρφωση της εφαρμογής (συμπεριλαμβανομένου ενημερώσεων)",
"en_US": "Extract the RomFS section from Application's current config (including updates)",
"es_ES": "Extraer la sección RomFS de la configuración actual de la aplicación (incluyendo actualizaciones)",
- "fr_FR": "Extrait la section RomFS du jeu (mise à jour incluse)",
+ "fr_FR": "Extraire la section RomFS de la configuration actuelle de l’application (mises à jour incluses)",
"he_IL": "חלץ את קטע ה-RomFS מתצורת היישום הנוכחית (כולל עדכונים)",
"it_IT": "Estrae la sezione RomFS dall'attuale configurazione dell'applicazione (inclusi gli aggiornamenti)",
"ja_JP": "現在のアプリケーション設定(アップデート含む)から RomFS セクションを展開します",
@@ -2755,7 +2755,7 @@
"el_GR": "",
"en_US": "DLC RomFS",
"es_ES": "",
- "fr_FR": "RomFS de DLC",
+ "fr_FR": "RomFS du DLC",
"he_IL": "",
"it_IT": "",
"ja_JP": "",
@@ -2780,7 +2780,7 @@
"el_GR": "",
"en_US": "Extract the RomFS from a selected DLC file",
"es_ES": "",
- "fr_FR": "Extraire les RomFS d'un fichier DLC choisi",
+ "fr_FR": "Extraire la RomFS du fichier DLC sélectionné",
"he_IL": "",
"it_IT": "",
"ja_JP": "",
@@ -2830,7 +2830,7 @@
"el_GR": "Εξαγωγή της ενότητας Logo από την τρέχουσα διαμόρφωση της εφαρμογής (συμπεριλαμβανομένου ενημερώσεων)",
"en_US": "Extract the Logo section from Application's current config (including updates)",
"es_ES": "Extraer la sección Logo de la configuración actual de la aplicación (incluyendo actualizaciones)",
- "fr_FR": "Extrait la section Logo du jeu (mise à jour incluse)",
+ "fr_FR": "Extraire la section Logo de la configuration actuelle de l’application (mises à jour incluses)",
"he_IL": "חלץ את קטע ה-Logo מתצורת היישום הנוכחית (כולל עדכונים)",
"it_IT": "Estrae la sezione Logo dall'attuale configurazione dell'applicazione (inclusi gli aggiornamenti)",
"ja_JP": "現在のアプリケーション設定(アップデート含む)からロゴセクションを展開します",
@@ -2855,7 +2855,7 @@
"el_GR": "Δημιουργία Συντόμευσης Εφαρμογής",
"en_US": "Create Application Shortcut",
"es_ES": "Crear acceso directo de aplicación",
- "fr_FR": "Créer un raccourci",
+ "fr_FR": "Créer un raccourci de l’application",
"he_IL": "ליצור קיצור דרך לאפליקציה",
"it_IT": "Crea collegamento",
"ja_JP": "アプリケーションのショートカットを作成",
@@ -2880,7 +2880,7 @@
"el_GR": "Δημιουργία συντόμευσης επιφάνειας εργασίας που ανοίγει την επιλεγμένη εφαρμογή",
"en_US": "Create a Desktop Shortcut that launches the selected Application",
"es_ES": "Crear un acceso directo en el escritorio que lance la aplicación seleccionada",
- "fr_FR": "Créer un raccourci sur le bureau qui lance le jeu sélectionné",
+ "fr_FR": "Créer un raccourci sur le bureau qui lance l’application sélectionnée",
"he_IL": "ליצור קיצור דרך בשולחן העבודה שיפתח את אפליקציה זו",
"it_IT": "Crea un collegamento sul desktop che avvia l'applicazione selezionata",
"ja_JP": "選択したアプリケーションを起動するデスクトップショートカットを作成します",
@@ -2905,7 +2905,7 @@
"el_GR": "",
"en_US": "Create Custom Configuration",
"es_ES": "",
- "fr_FR": "",
+ "fr_FR": "Créer une configuration personnalisée",
"he_IL": "",
"it_IT": "",
"ja_JP": "",
@@ -2930,7 +2930,7 @@
"el_GR": "",
"en_US": "Edit Custom Configuration",
"es_ES": "",
- "fr_FR": "",
+ "fr_FR": "Modifier la configuration personnalisée",
"he_IL": "",
"it_IT": "",
"ja_JP": "",
@@ -2955,7 +2955,7 @@
"el_GR": "",
"en_US": "Create a shortcut in macOS's Applications folder that launches the selected Application",
"es_ES": "Crea un acceso directo en la carpeta de Aplicaciones de macOS que inicie la Aplicación seleccionada",
- "fr_FR": "Créer un raccourci dans le dossier Applications de macOS qui lance le jeu sélectionné",
+ "fr_FR": "Créer un raccourci dans le dossier Applications de macOS qui lance l’application sélectionnée",
"he_IL": "ליצור קיצור דרך בתיקיית האפליקציות של macOS שיפתח את אפליקציה זו",
"it_IT": "Crea un collegamento nella cartella Applicazioni di macOS che avvia l'applicazione selezionata",
"ja_JP": "選択したアプリケーションを起動する ショートカットを macOS の Applications フォルダに作成します",
@@ -2980,7 +2980,7 @@
"el_GR": "Δημιουργεί μια ανεξάρτητη διαμόρφωση για το τρέχον παιχνίδι",
"en_US": "Creates an independent configuration for the selected game",
"es_ES": "Crea una configuración independiente para el juego actual",
- "fr_FR": "Crée une configuration indépendante pour le jeu en cours",
+ "fr_FR": "Crée une configuration indépendante pour le jeu sélectionné",
"he_IL": "יוצר תצורה עצמאית למשחק הנוכחי",
"it_IT": "Crea una configurazione indipendente per il gioco attuale",
"ja_JP": "現在のゲーム用の独立した設定を作成します",
@@ -3005,7 +3005,7 @@
"el_GR": "",
"en_US": "Edit your existing independent configuration for the selected game",
"es_ES": "",
- "fr_FR": "",
+ "fr_FR": "Modifier votre configuration indépendante existante pour le jeu sélectionné",
"he_IL": "",
"it_IT": "",
"ja_JP": "",
@@ -3030,7 +3030,7 @@
"el_GR": "",
"en_US": "Show Compatibility Entry",
"es_ES": "",
- "fr_FR": "",
+ "fr_FR": "Afficher l’entrée de compatibilité",
"he_IL": "",
"it_IT": "",
"ja_JP": "",
@@ -3055,7 +3055,7 @@
"el_GR": "",
"en_US": "Show the selected game in the Compatibility List you can normally access via the Help menu.",
"es_ES": "",
- "fr_FR": "",
+ "fr_FR": "Afficher le jeu sélectionné dans la liste de compatibilité accessible normalement via le menu Aide.",
"he_IL": "",
"it_IT": "",
"ja_JP": "",
@@ -3080,7 +3080,7 @@
"el_GR": "",
"en_US": "Show Game Info",
"es_ES": "",
- "fr_FR": "",
+ "fr_FR": "Afficher les informations du jeu",
"he_IL": "",
"it_IT": "",
"ja_JP": "",
@@ -3105,7 +3105,7 @@
"el_GR": "",
"en_US": "Show stats & details about the currently selected game.",
"es_ES": "",
- "fr_FR": "",
+ "fr_FR": "Afficher les statistiques et les détails sur le jeu actuellement sélectionné.",
"he_IL": "",
"it_IT": "",
"ja_JP": "",
@@ -3155,7 +3155,7 @@
"el_GR": "",
"en_US": "Opens the directory which contains Application's Mods",
"es_ES": "Abre el directorio que contiene los Mods de la Aplicación.",
- "fr_FR": "Ouvre le dossier contenant les mods du jeu",
+ "fr_FR": "Ouvre le dossier contenant les mods de l’application",
"he_IL": "פותח את התיקייה שמכילה מודים של האפליקציה",
"it_IT": "Apre la cartella che contiene le mod dell'applicazione",
"ja_JP": "アプリケーションの Mod データを格納するディレクトリを開きます",
@@ -3205,7 +3205,7 @@
"el_GR": "",
"en_US": "Opens the alternative SD card Atmosphere directory which contains Application's Mods. Useful for mods that are packaged for real hardware.",
"es_ES": "Abre el directorio alternativo de la tarjeta SD de Atmosphere que contiene los Mods de la Aplicación. Útil para los mods que están empaquetados para el hardware real.",
- "fr_FR": "Ouvre le dossier alternatif de la carte SD Atmosphère qui contient les mods de l'application. Utile pour les mods conçus pour console.",
+ "fr_FR": "Ouvre le dossier Atmosphere d’une carte SD alternative contenant les mods de l’application. Utile pour les mods conçus pour fonctionner sur un matériel réel.",
"he_IL": "פותח את תיקיית כרטיס ה-SD החלופית של Atmosphere המכילה את המודים של האפליקציה. שימושי עבור מודים שארוזים עבור חומרה אמיתית.",
"it_IT": "Apre la cartella alternativa di Atmosphere sulla scheda SD che contiene le mod dell'applicazione. Utile per le mod create per funzionare sull'hardware reale.",
"ja_JP": "アプリケーションの Mod データを格納する SD カードの Atmosphere ディレクトリを開きます. 実際のハードウェア用に作成された Mod データに有用です.",
@@ -3230,7 +3230,7 @@
"el_GR": "",
"en_US": "Check and Trim XCI File",
"es_ES": "Verificar y recortar archivo XCI",
- "fr_FR": "Vérifier et réduire les fichiers XCI",
+ "fr_FR": "Vérifier et réduire le fichier XCI",
"he_IL": "",
"it_IT": "Controlla e riduci la dimensione del file XCI",
"ja_JP": "",
@@ -3255,7 +3255,7 @@
"el_GR": "",
"en_US": "Check and Trim XCI File to Save Disk Space",
"es_ES": "Verificar y recortar archivo XCI para ahorrar espacio en disco",
- "fr_FR": "Vérifier et réduire les fichiers XCI pour économiser de l'espace",
+ "fr_FR": "Vérifier et découper le fichier XCI pour économiser de l’espace disque",
"he_IL": "",
"it_IT": "Controlla e riduci la dimensione del file XCI per risparmiare spazio su disco",
"ja_JP": "",
@@ -3305,7 +3305,7 @@
"el_GR": "",
"en_US": "Firmware Version: {0}",
"es_ES": "",
- "fr_FR": "Version du Firmware: {0}",
+ "fr_FR": "Version du firmware: {0}",
"he_IL": "",
"it_IT": "Versione firmware: {0}",
"ja_JP": "",
@@ -3355,7 +3355,7 @@
"el_GR": "Εντοπίστηκε χαμηλό όριο για αντιστοιχίσεις μνήμης",
"en_US": "Low limit for memory mappings detected",
"es_ES": "Límite inferior para mapeos de memoria detectado",
- "fr_FR": "Limite basse pour les mappings mémoire détectée",
+ "fr_FR": "Limite basse pour les mappages mémoire détectée",
"he_IL": "זוהתה מגבלה נמוכה עבור מיפויי זיכרון",
"it_IT": "Rilevato limite basso per le mappature di memoria",
"ja_JP": "メモリマッピング上限値が小さすぎます",
@@ -3405,7 +3405,7 @@
"el_GR": "Μερικά παιχνίδια μπορεί να προσπαθήσουν να δημιουργήσουν περισσότερες αντιστοιχίσεις μνήμης από αυτές που επιτρέπονται τώρα. Ο Ryujinx θα καταρρεύσει μόλις ξεπεραστεί αυτό το όριο.",
"en_US": "Some games might try to create more memory mappings than currently allowed. Ryujinx will crash as soon as this limit gets exceeded.",
"es_ES": "Algunos juegos podrían intentar crear más mapeos de memoria de los permitidos. Ryujinx se bloqueará tan pronto como se supere este límite.",
- "fr_FR": "Certains jeux peuvent essayer de créer plus de mappings mémoire que ce qui est actuellement autorisé. Ryujinx plantera dès que cette limite sera dépassée.",
+ "fr_FR": "Certains jeux peuvent tenter de créer plus de mappages mémoire que ce qui est actuellement autorisé. Ryujinx plantera dès que cette limite sera dépassée.",
"he_IL": "משחקים מסוימים עלולים לייצר עוד מיפויי זיכרון ממה שמתאפשר. Ryujinx יקרוס ברגע שהמגבלה תחרוג.",
"it_IT": "Alcuni giochi potrebbero provare a creare più mappature di memoria di quanto sia attualmente consentito. Ryujinx si bloccherà non appena questo limite viene superato.",
"ja_JP": "ゲームによっては, 現在許可されているサイズより大きなメモリマッピングを作成しようとすることがあります. この制限を超えると, Ryjinx はすぐにクラッシュします.",
@@ -3480,7 +3480,7 @@
"el_GR": "Ο μέγιστος αριθμός αντιστοιχίσεων μνήμης είναι μικρότερος από τον συνιστώμενο.",
"en_US": "Max amount of memory mappings is lower than recommended.",
"es_ES": "La cantidad máxima de mapeos de memoria es menor de lo recomendado.",
- "fr_FR": "La quantité maximale de mappings mémoire est inférieure à la valeur recommandée.",
+ "fr_FR": "La quantité maximale de mappages mémoire est inférieure à la valeur recommandée.",
"he_IL": "הכמות המירבית של מיפויי הזיכרון נמוכה מהמומלץ.",
"it_IT": "La quantità massima di mappature di memoria è inferiore a quella consigliata.",
"ja_JP": "メモリマッピングの最大量が推奨値よりも小さいです.",
@@ -3505,7 +3505,7 @@
"el_GR": "Η τρέχουσα τιμή του vm.max_map_count ({0}) είναι χαμηλότερη από {1}. Ορισμένα παιχνίδια μπορεί να προσπαθήσουν να δημιουργήσουν περισσότερες αντιστοιχίσεις μνήμης από αυτές που επιτρέπονται τώρα. Ο Ryujinx θα συντριβεί μόλις ξεπεραστεί το όριο.\n\nΜπορεί να θέλετε είτε να αυξήσετε χειροκίνητα το όριο ή να εγκαταστήσετε το pkexec, το οποίο επιτρέπει Ryujinx να βοηθήσει με αυτό.",
"en_US": "The current value of vm.max_map_count ({0}) is lower than {1}. Some games might try to create more memory mappings than currently allowed. Ryujinx will crash as soon as this limit gets exceeded.\n\nYou might want to either manually increase the limit or install pkexec, which allows Ryujinx to assist with that.",
"es_ES": "El valor actual de vm.max_map_count ({0}) es menor que {1}. Algunos juegos podrían intentar crear más mapeos de memoria de los permitidos actualmente. Ryujinx se bloqueará tan pronto como se supere este límite.\n\nPuede que desee aumentar manualmente el límite o instalar pkexec, lo que permite a Ryujinx ayudar con eso.",
- "fr_FR": "La valeur actuelle de vm.max_map_count ({0}) est inférieure à {1}. Certains jeux peuvent essayer de créer plus de mappings mémoire que ceux actuellement autorisés. Ryujinx plantera dès que cette limite sera dépassée.\n\nVous pouvez soit augmenter manuellement la limite, soit installer pkexec, ce qui permet à Ryujinx de l'aider.",
+ "fr_FR": "La valeur actuelle de vm.max_map_count ({0}) est inférieure à {1}. Certains jeux pourraient tenter de créer plus de mappages mémoire que ce qui est actuellement autorisé. Ryujinx plantera dès que cette limite sera dépassée.\n\nVous pouvez soit augmenter manuellement cette limite, soit installer pkexec, qui permet à Ryujinx de vous aider dans cette opération.",
"he_IL": "הערך הנוכחי של vm.max_map_count {0} נמוך מ{1}. משחקים מסוימים עלולים לייצר עוד מיפוי זיכרון ממה שמתאפשר.Ryujinx יקרוס ברגע שהמגבלה תחרוג.\n\nיתכן ותרצה להעלות את המגבלה הנוכחית או להתקין את pkexec, אשר יאפשר לRyujinx לסייע בכך.",
"it_IT": "Il valore corrente di vm.max_map_count ({0}) è inferiore a {1}. Alcuni giochi potrebbero provare a creare più mappature di memoria di quanto sia attualmente consentito. Ryujinx si bloccherà non appena questo limite viene superato.\n\nPotresti voler aumentare manualmente il limite o installare pkexec, il che permette a Ryujinx di assisterlo.",
"ja_JP": "vm.max_map_count の現在値 {0} は {1} よりも小さいです. ゲームによっては現在許可されている値よりも大きなメモリマッピングを作成しようとする場合があります. 上限を越えた場合, Ryujinx はクラッシュします.",
@@ -3655,7 +3655,7 @@
"el_GR": "",
"en_US": "Check for Updates:",
"es_ES": "",
- "fr_FR": "",
+ "fr_FR": "Vérifier les mises à jour : ",
"he_IL": "",
"it_IT": "",
"ja_JP": "",
@@ -3680,7 +3680,7 @@
"el_GR": "",
"en_US": "Off",
"es_ES": "",
- "fr_FR": "",
+ "fr_FR": "Désactivé",
"he_IL": "",
"it_IT": "",
"ja_JP": "",
@@ -3730,7 +3730,7 @@
"el_GR": "",
"en_US": "Background",
"es_ES": "",
- "fr_FR": "",
+ "fr_FR": "En arrière-plan",
"he_IL": "",
"it_IT": "",
"ja_JP": "",
@@ -3755,7 +3755,7 @@
"el_GR": "",
"en_US": "On Emulator Focus Lost:",
"es_ES": "",
- "fr_FR": "",
+ "fr_FR": "Lorsque l’émulateur perd le focus :",
"he_IL": "",
"it_IT": "",
"ja_JP": "",
@@ -3780,7 +3780,7 @@
"el_GR": "",
"en_US": "Do Nothing",
"es_ES": "",
- "fr_FR": "",
+ "fr_FR": "Ne rien faire",
"he_IL": "",
"it_IT": "",
"ja_JP": "",
@@ -3805,7 +3805,7 @@
"el_GR": "",
"en_US": "Block Input",
"es_ES": "",
- "fr_FR": "",
+ "fr_FR": "Bloquer la saisie",
"he_IL": "",
"it_IT": "",
"ja_JP": "",
@@ -3830,7 +3830,7 @@
"el_GR": "",
"en_US": "Mute Volume",
"es_ES": "",
- "fr_FR": "",
+ "fr_FR": "Couper le son",
"he_IL": "",
"it_IT": "",
"ja_JP": "",
@@ -3855,7 +3855,7 @@
"el_GR": "",
"en_US": "Block Input & Mute Volume",
"es_ES": "",
- "fr_FR": "",
+ "fr_FR": "Bloquer la saisie & couper le son",
"he_IL": "",
"it_IT": "",
"ja_JP": "",
@@ -3880,7 +3880,7 @@
"el_GR": "",
"en_US": "Pause Emulation",
"es_ES": "",
- "fr_FR": "",
+ "fr_FR": "Mettre l’émulation en pause",
"he_IL": "",
"it_IT": "",
"ja_JP": "",
@@ -3955,7 +3955,7 @@
"el_GR": "",
"en_US": "Disable Input when Out of Focus",
"es_ES": "",
- "fr_FR": "",
+ "fr_FR": "Désactiver la saisie en cas de perte de focus",
"he_IL": "",
"it_IT": "",
"ja_JP": "",
@@ -3980,7 +3980,7 @@
"el_GR": "",
"en_US": "Show Original UI Style (Requires restart)",
"es_ES": "",
- "fr_FR": "",
+ "fr_FR": "Afficher le style d’interface original (redémarrage requis)",
"he_IL": "",
"it_IT": "",
"ja_JP": "",
@@ -4005,7 +4005,7 @@
"el_GR": "",
"en_US": "Show the older Avalonia Ryujinx UI reminiscent of Ryujinx 1.1.1403. This is enabled by default on platforms that are not Windows.\nThe classic-style title bar is back and major window layout reworkings are reversed; such as the settings navigation placement above this tooltip.",
"es_ES": "",
- "fr_FR": "",
+ "fr_FR": "Afficher l’ancienne interface Avalonia Ryujinx, rappelant Ryujinx 1.1.1403. Cette option est activée par défaut sur les plateformes autres que Windows.\nLa barre de titre au style classique est de retour et les modifications majeures de la disposition des fenêtres sont annulées, comme le placement de la navigation des paramètres au-dessus de cette infobulle.",
"he_IL": "",
"it_IT": "",
"ja_JP": "",
@@ -4080,7 +4080,7 @@
"el_GR": "Απόκρυψη Δρομέα στην Αδράνεια",
"en_US": "On Idle",
"es_ES": "Ocultar cursor cuando esté inactivo",
- "fr_FR": "Masquer le curseur si inactif",
+ "fr_FR": "Si inactif",
"he_IL": "במצב סרק",
"it_IT": "Quando è inattivo",
"ja_JP": "アイドル時",
@@ -4155,7 +4155,7 @@
"el_GR": "",
"en_US": "Autoload DLC/Updates Directories",
"es_ES": "Carpetas de DLC/Actualizaciones para Carga Automática",
- "fr_FR": "Dossiers des mises à jour/DLC",
+ "fr_FR": "Charger automatiquement les dossiers de DLC/mises à jour",
"he_IL": "",
"it_IT": "Cartelle di caricamento automatico di DLC/aggiornamenti",
"ja_JP": "",
@@ -4180,7 +4180,7 @@
"el_GR": "",
"en_US": "DLC and Updates which refer to missing files will be unloaded automatically",
"es_ES": "DLC y Actualizaciones que hacen referencia a archivos ausentes serán desactivado automáticamente",
- "fr_FR": "Les DLC et les mises à jour faisant référence aux fichiers manquants seront automatiquement déchargés.",
+ "fr_FR": "Les DLC et mises à jour faisant référence à des fichiers manquants seront déchargés automatiquement.",
"he_IL": "",
"it_IT": "Aggiornamenti e DLC che fanno riferimento a file mancanti verranno disabilitati automaticamente",
"ja_JP": "",
@@ -4280,7 +4280,7 @@
"el_GR": "Πυρήνας",
"en_US": "Core",
"es_ES": "Núcleo",
- "fr_FR": "Cœur",
+ "fr_FR": "Paramètres généraux",
"he_IL": "ליבה",
"it_IT": "Principale",
"ja_JP": "コア",
@@ -5005,7 +5005,7 @@
"el_GR": "",
"en_US": "Match System Time",
"es_ES": "",
- "fr_FR": "",
+ "fr_FR": "Synchroniser avec l’heure du système",
"he_IL": "",
"it_IT": "",
"ja_JP": "",
@@ -5055,7 +5055,7 @@
"el_GR": "Low-power PPTC",
"en_US": "Low-power PPTC cache",
"es_ES": "Cache PPTC de bajo consumo",
- "fr_FR": "PPTC à faible puissance",
+ "fr_FR": "Cache PPTC de faible puissance",
"he_IL": "Low-power PPTC",
"it_IT": "Caricamento PPTC a basso consumo energetico",
"ja_JP": "Low-power PPTC",
@@ -5105,7 +5105,7 @@
"el_GR": "",
"en_US": "The Turbo mode multiplier target value.\n\nLeave at 200 if unsure.",
"es_ES": "",
- "fr_FR": "La valeur souhaitée du multiplicateur du Mode Turbo.\n\nGarder à 200 si incertain.",
+ "fr_FR": "La valeur souhaitée du multiplicateur du mode Turbo.\n\nLaissez à 200 si vous n'êtes pas sûr.",
"he_IL": "",
"it_IT": "",
"ja_JP": "",
@@ -5130,7 +5130,7 @@
"el_GR": "",
"en_US": "Turbo mode is an emulator feature which effectively causes speed up or slow down when a game is not frame-rate sensitive.\nYou can toggle this feature in-game with a hotkey, configurable in Ryujinx Keyboard Hotkeys settings.\n\nLeave at 200 if unsure.",
"es_ES": "",
- "fr_FR": "Le Mode Turbo est une fonctionnalité de l'émulateur qui accélère ou ralentit le jeu lorsque ce dernier n'est pas sensible au framerate.\nVous pouvez changer cette option en jeu avec un raccourci clavier, configurable dans les paramètres de Raccourcis clavier de Ryujinx.\n\nGarder à 200 si incertain.",
+ "fr_FR": "Le Mode Turbo est une fonctionnalité de l’émulateur qui permet d’accélérer ou de ralentir le jeu lorsque celui-ci n’est pas sensible au taux de rafraîchissement.\nVous pouvez activer ou désactiver cette fonction en jeu via un raccourci clavier, configurable dans les paramètres des raccourcis clavier de Ryujinx.\n\nLaissez à 200 si vous n’êtes pas sûr.",
"he_IL": "",
"it_IT": "",
"ja_JP": "",
@@ -5155,7 +5155,7 @@
"el_GR": "Ενεργοποίηση Ελέγχων Ακεραιότητας FS",
"en_US": "FS Integrity Checks",
"es_ES": "Comprobar integridad de los archivos",
- "fr_FR": "Activer la vérification de l'intégrité du système de fichiers",
+ "fr_FR": "Vérification de l’intégrité du système de fichiers (FS)",
"he_IL": "FS בדיקות תקינות",
"it_IT": "Attiva controlli d'integrità FS",
"ja_JP": "ファイルシステム整合性チェック",
@@ -5330,7 +5330,7 @@
"el_GR": " (Μπορεί να προκαλέσουν αστάθεια)",
"en_US": "May cause instability",
"es_ES": " (Pueden causar inestabilidad)",
- "fr_FR": "Cela peut causer des instabilités",
+ "fr_FR": "Peut causer des instabilités",
"he_IL": "עלול לגרום לאי יציבות",
"it_IT": "Possono causare instabilità",
"ja_JP": " (挙動が不安定になる可能性があります)",
@@ -5505,7 +5505,7 @@
"el_GR": "",
"en_US": "Ignore Controller Applet",
"es_ES": "",
- "fr_FR": "",
+ "fr_FR": "Ignorer l’applet de contrôleur",
"he_IL": "",
"it_IT": "",
"ja_JP": "",
@@ -5530,7 +5530,7 @@
"el_GR": "Παράκαμψη διαλόγου 'Διαχείριση Προφίλ _Χρηστών'",
"en_US": "Skip dialog 'Manage User Profiles'",
"es_ES": "Omitir el diálogo 'Gestionar perfiles de usuario'",
- "fr_FR": "Ignorer le dialogue 'Gérer les profils d'utilisateurs'",
+ "fr_FR": "Ignorer la boîte de dialogue « Gérer les profils utilisateur »",
"he_IL": "דילוג על הדיאלוג 'נהל פרופילי משתמש'",
"it_IT": "Salta la finestra di dialogo 'Gestisci i profili utente'",
"ja_JP": "「ユーザプロファイルを管理」ダイアログをスキップ",
@@ -5805,7 +5805,7 @@
"el_GR": "Προσαρμοσμένο (Δεν συνιστάται)",
"en_US": "Custom (Not recommended)",
"es_ES": "Personalizada (no recomendado)",
- "fr_FR": "Personnalisée (Non recommandée)",
+ "fr_FR": "Personnalisée (non recommandée)",
"he_IL": "מותאם אישית (לא מומלץ)",
"it_IT": "Personalizzata (Non raccomandata)",
"ja_JP": "カスタム (非推奨)",
@@ -5905,7 +5905,7 @@
"el_GR": "",
"en_US": "4x (2880p/4320p) (Not recommended)",
"es_ES": "4x (2880p/4320p) (no recomendado)",
- "fr_FR": "x4 (2880p/4320p) (Non recommandé)",
+ "fr_FR": "x4 (2880p/4320p) (non recommandé)",
"he_IL": "4x (2880p/4320p) (לא מומלץ)",
"it_IT": "4x (2880p/4320p) (Non consigliato)",
"ja_JP": "4x (2880p/4320p) (非推奨)",
@@ -5930,7 +5930,7 @@
"el_GR": "Αναλογία Απεικόνισης:",
"en_US": "Aspect Ratio:",
"es_ES": "Relación de aspecto:",
- "fr_FR": "Format d'affichage :",
+ "fr_FR": "Format d'image :",
"he_IL": "יחס גובה-רוחב:",
"it_IT": "Rapporto d'aspetto:",
"ja_JP": "アスペクト比:",
@@ -6080,7 +6080,7 @@
"el_GR": "Έκταση σε όλο το παράθυρο",
"en_US": "Stretch to Fit Window",
"es_ES": "Estirar a la ventana",
- "fr_FR": "Étirer pour remplir la fenêtre",
+ "fr_FR": "Ajuster à la taille de la fenêtre",
"he_IL": "מתח לגודל חלון",
"it_IT": "Adatta alla finestra",
"ja_JP": "ウインドウサイズに合わせる",
@@ -6105,7 +6105,7 @@
"el_GR": "Επιλογές Προγραμματιστή",
"en_US": "Developer Options",
"es_ES": "Opciones de desarrollador",
- "fr_FR": "Options développeur",
+ "fr_FR": "Options pour les développeurs",
"he_IL": "אפשרויות מפתח",
"it_IT": "Opzioni per sviluppatori",
"ja_JP": "開発者向けオプション",
@@ -6130,7 +6130,7 @@
"el_GR": "Τοποθεσία Shaders Γραφικών:",
"en_US": "Graphics Shader Dump Path:",
"es_ES": "Directorio de volcado de sombreadores:",
- "fr_FR": "Chemin du dossier de copie des shaders :",
+ "fr_FR": "Chemin de vidage des shaders graphiques :",
"he_IL": "",
"it_IT": "Percorso di dump degli shader:",
"ja_JP": "グラフィックス シェーダー ダンプパス:",
@@ -6155,7 +6155,7 @@
"el_GR": "Καταγραφή",
"en_US": "Logging",
"es_ES": "Registros",
- "fr_FR": "Journaux",
+ "fr_FR": "Journalisation",
"he_IL": "רישום",
"it_IT": "Log",
"ja_JP": "ロギング",
@@ -6180,7 +6180,7 @@
"el_GR": "Καταγραφή",
"en_US": "Logging",
"es_ES": "Registros",
- "fr_FR": "Journaux",
+ "fr_FR": "Journalisation",
"he_IL": "רישום",
"it_IT": "Log",
"ja_JP": "ロギング",
@@ -6205,7 +6205,7 @@
"el_GR": "Ενεργοποίηση Καταγραφής Αρχείου",
"en_US": "Enable Logging to File",
"es_ES": "Habilitar registro a archivo",
- "fr_FR": "Activer la sauvegarde des journaux vers un fichier",
+ "fr_FR": "Activer la journalisation dans un fichier",
"he_IL": "אפשר רישום לקובץ",
"it_IT": "Salva i log su file",
"ja_JP": "ファイルへのロギングを有効にする",
@@ -6330,7 +6330,7 @@
"el_GR": "Ενεργοποίηση Καταγραφής Ιχνών",
"en_US": "Enable Trace Logs",
"es_ES": "Habilitar registros de Rastro",
- "fr_FR": "Activer les journaux d'erreurs Trace",
+ "fr_FR": "Activer les journaux de trace",
"he_IL": "הפעל רישום מעקבי",
"it_IT": "Attiva log di trace",
"ja_JP": "Trace ログを有効にする",
@@ -6355,7 +6355,7 @@
"el_GR": "Ενεργοποίηση Καταγραφής Επισκεπτών",
"en_US": "Enable Guest Logs",
"es_ES": "Habilitar registros de Guest",
- "fr_FR": "Activer les journaux du programme simulé",
+ "fr_FR": "Activer les journaux invités",
"he_IL": "הפעל רישום מארח",
"it_IT": "Attiva log del guest",
"ja_JP": "Guest ログを有効にする",
@@ -6380,7 +6380,7 @@
"el_GR": "",
"en_US": "Enable UI Logs",
"es_ES": "",
- "fr_FR": "",
+ "fr_FR": "Activer les journaux UI",
"he_IL": "",
"it_IT": "",
"ja_JP": "",
@@ -6430,7 +6430,7 @@
"el_GR": "Λειτουργία Καταγραφής Καθολικής Πρόσβασης FS:",
"en_US": "Fs Global Access Log Mode:",
"es_ES": "Modo de registros Fs Global Access:",
- "fr_FR": "Niveau des journaux d'accès au système de fichiers :",
+ "fr_FR": "Mode global des journaux d’accès FS",
"he_IL": "מצב רישום גלובלי של גישת קבצי מערכת",
"it_IT": "Modalità log di accesso globale FS:",
"ja_JP": "Fs グローバルアクセスログモード:",
@@ -6455,7 +6455,7 @@
"el_GR": "Επιλογές Προγραμματιστή (ΠΡΟΕΙΔΟΠΟΙΗΣΗ: Η απόδοση Θα μειωθεί)",
"en_US": "Developer Options",
"es_ES": "Opciones de desarrollador (ADVERTENCIA: empeorarán el rendimiento)",
- "fr_FR": "Options développeur",
+ "fr_FR": "Options pour les développeurs",
"he_IL": "אפשרויות מפתח",
"it_IT": "Opzioni per sviluppatori",
"ja_JP": "開発者オプション",
@@ -6505,7 +6505,7 @@
"el_GR": "Επίπεδο Καταγραφής Διεπαφής Γραφικών:",
"en_US": "Graphics Backend Log Level:",
"es_ES": "Nivel de registro de backend gráficos:",
- "fr_FR": "Niveau du journal du backend graphique :",
+ "fr_FR": "Niveau de journalisation du backend graphique :",
"he_IL": "רישום גרפיקת קצה אחורי:",
"it_IT": "Livello di log del backend grafico:",
"ja_JP": "グラフィックスバックエンド ログレベル:",
@@ -6630,7 +6630,7 @@
"el_GR": "Ενεργοποίηση Αρχείων Καταγραφής Εντοπισμού Σφαλμάτων",
"en_US": "Enable Debug Logs",
"es_ES": "Habilitar registros de debug",
- "fr_FR": "Activer les journaux de debug",
+ "fr_FR": "Activer les journaux de débogage",
"he_IL": "אפשר רישום ניפוי באגים",
"it_IT": "Attiva log di debug",
"ja_JP": "デバッグログを有効にする",
@@ -6705,7 +6705,7 @@
"el_GR": "Ενεργοποίηση Docked Mode",
"en_US": "Docked Mode",
"es_ES": "Modo dock/TV",
- "fr_FR": "Active le mode station d'accueil",
+ "fr_FR": "Mode Docké",
"he_IL": "מצב עגינה",
"it_IT": "Attiva modalità TV",
"ja_JP": "ドッキングモード",
@@ -6805,7 +6805,7 @@
"el_GR": "",
"en_US": "Reset Settings",
"es_ES": "",
- "fr_FR": "",
+ "fr_FR": "Réinitialiser les paramètres",
"he_IL": "",
"it_IT": "",
"ja_JP": "",
@@ -6830,7 +6830,7 @@
"el_GR": "",
"en_US": "I want to reset my settings.",
"es_ES": "",
- "fr_FR": "",
+ "fr_FR": "Je veux réinitialiser mes paramètres.",
"he_IL": "",
"it_IT": "",
"ja_JP": "",
@@ -7180,7 +7180,7 @@
"el_GR": "Συσκευή Χειρισμού",
"en_US": "Input Device",
"es_ES": "Dispositivo de entrada",
- "fr_FR": "Périphériques",
+ "fr_FR": "Périphérique",
"he_IL": "מכשיר קלט",
"it_IT": "Dispositivo di input",
"ja_JP": "入力デバイス",
@@ -7205,7 +7205,7 @@
"el_GR": "",
"en_US": "Configuration found:\n\nName:\t{0}\nGUID:\t{1}\n\n Waiting for controller connection...",
"es_ES": "",
- "fr_FR": "",
+ "fr_FR": "Configuration trouvée:\n\nNom:\t{0}\nGUID:\t{1}\n\nEn attente de la connexion de la manette...",
"he_IL": "",
"it_IT": "",
"ja_JP": "",
@@ -7555,7 +7555,7 @@
"el_GR": "Η διαχείριση έχει ρυθμιστεί.\n\nΑναμένεται σύνδεση του χειριστηρίου...",
"en_US": "Control configured.\n\nWaiting for controller connection...",
"es_ES": "Control configurado.\n\nEsperando la conexión del controlador...",
- "fr_FR": "Contrôle configuré.\n\nEn attente de la connexion du contrôleur...",
+ "fr_FR": "Contrôle configuré.\n\nEn attente de la connexion de la manette...",
"he_IL": "השליטה הוגדרה.\n\nממתין לחיבור הבקר...",
"it_IT": "Controllo configurato.\n\nIn attesa della connessione del controller...",
"ja_JP": "コントロールが設定されました。\n\nコントローラーの接続を待っています...",
@@ -8205,7 +8205,7 @@
"el_GR": "Αριστερή Σκανδάλη",
"en_US": "Triggers Left",
"es_ES": "Gatillos izquierdos",
- "fr_FR": "Gachettes Gauche",
+ "fr_FR": "Gâchettes Gauche",
"he_IL": "הדק שמאלי",
"it_IT": "Grilletto sinistro",
"ja_JP": "左トリガー",
@@ -8230,7 +8230,7 @@
"el_GR": "Δεξιά Σκανδάλη",
"en_US": "Triggers Right",
"es_ES": "Gatillos derechos",
- "fr_FR": "Gachettes Droite",
+ "fr_FR": "Gâchettes Droite",
"he_IL": "הדק ימני",
"it_IT": "Grilletto destro",
"ja_JP": "右トリガー",
@@ -8255,7 +8255,7 @@
"el_GR": "Αριστερά Κουμπιά Σκανδάλης",
"en_US": "Trigger Buttons Left",
"es_ES": "Botones de gatillo izquierdos",
- "fr_FR": "Boutons Gachettes Gauche",
+ "fr_FR": "Boutons Gâchettes Gauche",
"he_IL": "כפתור הדק שמאלי",
"it_IT": "Pulsante dorsale sinistro",
"ja_JP": "左トリガーボタン",
@@ -8280,7 +8280,7 @@
"el_GR": "Δεξιά Κουμπιά Σκανδάλης",
"en_US": "Trigger Buttons Right",
"es_ES": "Botones de gatillo derechos",
- "fr_FR": "Boutons Gachettes Droite",
+ "fr_FR": "Boutons Gâchettes Droite",
"he_IL": "כפתור הדק ימני",
"it_IT": "Pulsante dorsale destro",
"ja_JP": "右トリガーボタン",
@@ -8305,7 +8305,7 @@
"el_GR": "Σκανδάλες",
"en_US": "Triggers",
"es_ES": "Gatillos",
- "fr_FR": "Gachettes",
+ "fr_FR": "Gâchettes",
"he_IL": "הדקים",
"it_IT": "Grilletti",
"ja_JP": "トリガー",
@@ -8605,7 +8605,7 @@
"el_GR": "Κατώφλι Σκανδάλης:",
"en_US": "Trigger Threshold:",
"es_ES": "Límite de gatillos:",
- "fr_FR": "Seuil de gachettes :",
+ "fr_FR": "Seuil de déclenchement :",
"he_IL": "סף הדק:",
"it_IT": "Sensibilità dei grilletti:",
"ja_JP": "トリガーしきい値:",
@@ -8655,7 +8655,7 @@
"el_GR": "Κίνηση συμβατή με CemuHook",
"en_US": "Use CemuHook compatible motion",
"es_ES": "Usar movimiento compatible con CemuHook",
- "fr_FR": "Utiliser un capteur de mouvements CemuHook",
+ "fr_FR": "Utiliser la détection de mouvements compatible CemuHook",
"he_IL": "השתמש בתנועת CemuHook תואמת ",
"it_IT": "Usa sensore compatibile con CemuHook",
"ja_JP": "CemuHook 互換モーションを使用",
@@ -8855,7 +8855,7 @@
"el_GR": "",
"en_US": "Disable",
"es_ES": "",
- "fr_FR": "",
+ "fr_FR": "Désactiver",
"he_IL": "",
"it_IT": "",
"ja_JP": "",
@@ -8880,7 +8880,7 @@
"el_GR": "",
"en_US": "Rainbow",
"es_ES": "",
- "fr_FR": "",
+ "fr_FR": "Arc-en-ciel",
"he_IL": "",
"it_IT": "",
"ja_JP": "",
@@ -8905,7 +8905,7 @@
"el_GR": "",
"en_US": "Rainbow Speed",
"es_ES": "",
- "fr_FR": "",
+ "fr_FR": "Vitesse des couleurs",
"he_IL": "",
"it_IT": "",
"ja_JP": "",
@@ -8930,7 +8930,7 @@
"el_GR": "",
"en_US": "Color",
"es_ES": "",
- "fr_FR": "",
+ "fr_FR": "Couleur",
"he_IL": "",
"it_IT": "",
"ja_JP": "",
@@ -9005,7 +9005,7 @@
"el_GR": "",
"en_US": "Unknown",
"es_ES": "Desconocido",
- "fr_FR": "Touche inconnue",
+ "fr_FR": "Inconnu",
"he_IL": "",
"it_IT": "Sconosciuto",
"ja_JP": "",
@@ -9605,7 +9605,7 @@
"el_GR": "",
"en_US": "Backspace",
"es_ES": "",
- "fr_FR": "Supprimer",
+ "fr_FR": "Retour arrière",
"he_IL": "",
"it_IT": "",
"ja_JP": "",
@@ -9630,7 +9630,7 @@
"el_GR": "",
"en_US": "Insert",
"es_ES": "",
- "fr_FR": "Ins",
+ "fr_FR": "Inser",
"he_IL": "",
"it_IT": "Ins",
"ja_JP": "",
@@ -9655,7 +9655,7 @@
"el_GR": "",
"en_US": "Delete",
"es_ES": "",
- "fr_FR": "Sup",
+ "fr_FR": "Suppr",
"he_IL": "",
"it_IT": "Canc",
"ja_JP": "",
@@ -9680,7 +9680,7 @@
"el_GR": "",
"en_US": "Page Up",
"es_ES": "",
- "fr_FR": "",
+ "fr_FR": "Pg.Préc",
"he_IL": "",
"it_IT": "Pag. Su",
"ja_JP": "",
@@ -9705,7 +9705,7 @@
"el_GR": "",
"en_US": "Page Down",
"es_ES": "",
- "fr_FR": "",
+ "fr_FR": "Pg.Suiv",
"he_IL": "",
"it_IT": "Pag. Giù",
"ja_JP": "",
@@ -10955,7 +10955,7 @@
"el_GR": "",
"en_US": "Left Shoulder",
"es_ES": "",
- "fr_FR": "Bouton Gachette G.",
+ "fr_FR": "Bouton Gâchette G.",
"he_IL": "",
"it_IT": "Pulsante dorsale sinistro",
"ja_JP": "",
@@ -10980,7 +10980,7 @@
"el_GR": "",
"en_US": "Right Shoulder",
"es_ES": "",
- "fr_FR": "Bouton Gachette D.",
+ "fr_FR": "Bouton Gâchette D.",
"he_IL": "",
"it_IT": "Pulsante dorsale destro",
"ja_JP": "",
@@ -11005,7 +11005,7 @@
"el_GR": "",
"en_US": "Left Trigger",
"es_ES": "",
- "fr_FR": "Gachette Gauche",
+ "fr_FR": "Gâchette Gauche",
"he_IL": "",
"it_IT": "Grilletto sinistro",
"ja_JP": "",
@@ -11030,7 +11030,7 @@
"el_GR": "",
"en_US": "Right Trigger",
"es_ES": "",
- "fr_FR": "Gachette Droite",
+ "fr_FR": "Gâchette Droite",
"he_IL": "",
"it_IT": "Grilletto destro",
"ja_JP": "",
@@ -11355,7 +11355,7 @@
"el_GR": "",
"en_US": "Touchpad",
"es_ES": "",
- "fr_FR": "",
+ "fr_FR": "Pavé tactile",
"he_IL": "",
"it_IT": "",
"ja_JP": "",
@@ -11380,7 +11380,7 @@
"el_GR": "",
"en_US": "Left Trigger 0",
"es_ES": "",
- "fr_FR": "Gachette Gauche 0",
+ "fr_FR": "Gâchette Gauche 0",
"he_IL": "",
"it_IT": "Grilletto sinistro 0",
"ja_JP": "",
@@ -11405,7 +11405,7 @@
"el_GR": "",
"en_US": "Right Trigger 0",
"es_ES": "",
- "fr_FR": "Gachette Droite 0",
+ "fr_FR": "Gâchette Droite 0",
"he_IL": "",
"it_IT": "Grilletto destro 0",
"ja_JP": "",
@@ -11430,7 +11430,7 @@
"el_GR": "",
"en_US": "Left Trigger 1",
"es_ES": "",
- "fr_FR": "Gachette Gauche 1",
+ "fr_FR": "Gâchette Gauche 1",
"he_IL": "",
"it_IT": "Grilletto sinistro 1",
"ja_JP": "",
@@ -11455,7 +11455,7 @@
"el_GR": "",
"en_US": "Right Trigger 1",
"es_ES": "",
- "fr_FR": "Gachette Droite 1",
+ "fr_FR": "Gâchette Droite 1",
"he_IL": "",
"it_IT": "Grilletto destro 1",
"ja_JP": "",
@@ -11780,7 +11780,7 @@
"el_GR": "Μπορείτε να εισαγάγετε μία προσαρμοσμένη εικόνα προφίλ ή να επιλέξετε ένα avatar από το Firmware",
"en_US": "You may import a custom profile image, or select an avatar from system firmware",
"es_ES": "Puedes importar una imagen de perfil personalizada, o seleccionar un avatar del firmware de sistema",
- "fr_FR": "Vous pouvez importer une image de profil personnalisée ou sélectionner un avatar à partir du firmware",
+ "fr_FR": "Vous pouvez importer une image de profil personnalisée ou sélectionner un avatar depuis le firmware système",
"he_IL": "אתם יכולים לייבא תמונת פרופיל מותאמת אישית, או לבחור אווטאר מקושחת המערכת",
"it_IT": "Puoi importare un'immagine profilo personalizzata o selezionare un avatar dal firmware del sistema",
"ja_JP": "カスタム画像をインポート, またはファームウェア内のアバターを選択できます",
@@ -11855,7 +11855,7 @@
"el_GR": "Διάλογος Εισαγωγής",
"en_US": "Input Dialog",
"es_ES": "Cuadro de diálogo de entrada",
- "fr_FR": "Fenêtre d'entrée de texte",
+ "fr_FR": "Boîte de saisie",
"he_IL": "דיאלוג קלט",
"it_IT": "Finestra di input",
"ja_JP": "入力ダイアログ",
@@ -12005,7 +12005,7 @@
"el_GR": "Εισαγωγή Ονόματος Προφίλ",
"en_US": "Please Enter a Profile Name",
"es_ES": "Por favor elige un nombre de usuario",
- "fr_FR": "Merci d'entrer un nom de profil",
+ "fr_FR": "Veuillez saisir un nom de profil.",
"he_IL": "אנא הזינו שם לפרופיל",
"it_IT": "Digita un nome profilo",
"ja_JP": "プロファイル名を入力してください",
@@ -12355,7 +12355,7 @@
"el_GR": "Εναλλαγή Αγαπημένου",
"en_US": "Toggle Favorite",
"es_ES": "Marcar favorito",
- "fr_FR": "Ajouter/Retirer des favoris",
+ "fr_FR": "Basculer favori",
"he_IL": "למתג העדפה",
"it_IT": "Preferito",
"ja_JP": "お気に入りを切り替え",
@@ -12380,7 +12380,7 @@
"el_GR": "Εναλλαγή της Κατάστασης Αγαπημένο του Παιχνιδιού",
"en_US": "Toggle Favorite status of Game",
"es_ES": "Marca o desmarca el juego como favorito",
- "fr_FR": "Définis un jeu comme faisant parti des favoris ou non",
+ "fr_FR": "Basculer le statut favori du jeu",
"he_IL": "למתג סטטוס העדפה של משחק",
"it_IT": "Segna il gioco come preferito",
"ja_JP": "ゲームをお気に入りに含めるかどうかを切り替えます",
@@ -12530,7 +12530,7 @@
"el_GR": "Δόνηση",
"en_US": "Rumble",
"es_ES": "Vibración",
- "fr_FR": "Vibreur",
+ "fr_FR": "Vibration",
"he_IL": "רטט",
"it_IT": "Vibrazione",
"ja_JP": "振動",
@@ -13105,7 +13105,7 @@
"el_GR": "Χρησιμοποιείτε ήδη την πιο ενημερωμένη έκδοση του Ryujinx!",
"en_US": "You are already using the latest version of Ryujinx!",
"es_ES": "¡Ya tienes la versión más reciente de Ryujinx!",
- "fr_FR": "Vous utilisez déjà la version la plus récente de Ryujinx !",
+ "fr_FR": "Vous utilisez déjà la dernière version de Ryujinx !",
"he_IL": "אתם כבר משתמשים בגרסה המעודכנת ביותר של ריוג'ינקס!",
"it_IT": "Stai già usando la versione più recente di Ryujinx!",
"ja_JP": "最新バージョンの Ryujinx を使用中です!",
@@ -13130,7 +13130,7 @@
"el_GR": "",
"en_US": "Failed to convert the Ryujinx version received from the update server.",
"es_ES": "",
- "fr_FR": "La conversion de la version de Ryujinx reçue du serveur a échoué.",
+ "fr_FR": "Échec de la conversion de la version de Ryujinx reçue du serveur de mise à jour.",
"he_IL": "",
"it_IT": "",
"ja_JP": "",
@@ -13180,7 +13180,7 @@
"el_GR": "Παρακαλώ περιμένετε, ο εξομοιωτής επανεκκινείται",
"en_US": "Please wait, the emulator is restarting",
"es_ES": "Por favor, espere, el emulador se está reiniciando",
- "fr_FR": "Veuillez patienter, l'émulateur est en train de redémarrer",
+ "fr_FR": "Veuillez patienter, l’émulateur redémarre",
"he_IL": "אנא המתן, המחקה מתארגן מחדש",
"it_IT": "Attendere prego, l'emulatore si sta riavviando",
"ja_JP": "お待ちください、エミュレーターが再起動しています",
@@ -13380,7 +13380,7 @@
"el_GR": "Επαληθεύστε ότι έχετε σύνδεση στο Διαδίκτυο που λειτουργεί!",
"en_US": "Please verify that you have a working Internet connection!",
"es_ES": "¡Por favor, verifica que tu conexión a Internet funciona!",
- "fr_FR": "Veuillez vérifier que vous disposez d'une connexion Internet fonctionnelle !",
+ "fr_FR": "Veuillez vérifier que vous avez une connexion Internet fonctionnelle !",
"he_IL": "אנא ודא שיש לך חיבור אינטרנט תקין!",
"it_IT": "Verifica di avere una connessione ad Internet funzionante!",
"ja_JP": "インターネット接続が正常動作しているか確認してください!",
@@ -13505,7 +13505,7 @@
"el_GR": "Θέλετε να κάνετε επανεκκίνηση",
"en_US": "Do you want to restart",
"es_ES": "¿Quieres reiniciar?",
- "fr_FR": "Voulez-vous redémarrer",
+ "fr_FR": "Voulez-vous redémarrer ?",
"he_IL": "האם ברצונך להפעיל מחדש?",
"it_IT": "Vuoi riavviare?",
"ja_JP": "再起動しますか",
@@ -13730,7 +13730,7 @@
"el_GR": "Άνοιγμα Παραθύρου Ρυθμίσεων",
"en_US": "Open Settings Window",
"es_ES": "Abrir ventana de opciones",
- "fr_FR": "Ouvrir la fenêtre de configuration",
+ "fr_FR": "Ouvrir la fenêtre des paramètres",
"he_IL": "פתח את חלון ההגדרות",
"it_IT": "Apri finestra delle impostazioni",
"ja_JP": "設定ウインドウを開く",
@@ -14080,7 +14080,7 @@
"el_GR": "Διαγραφή Προφίλ",
"en_US": "Deleting Profile",
"es_ES": "Eliminando perfil",
- "fr_FR": "Supprimer le profil",
+ "fr_FR": "Suppression du profil",
"he_IL": "מוחק פרופיל",
"it_IT": "Eliminazione profilo",
"ja_JP": "プロファイルを削除中",
@@ -14155,7 +14155,7 @@
"el_GR": "Πρόκειται να διαγράψετε την προσωρινή μνήμη PPTC για :\n\n{0}\n\nΕίστε βέβαιοι ότι θέλετε να συνεχίσετε;",
"en_US": "You are about to queue a PPTC rebuild on the next boot of:\n\n{0}\n\nAre you sure you want to proceed?",
"es_ES": "Vas a borrar la caché de PPTC para:\n\n{0}\n\n¿Estás seguro de querer continuar?",
- "fr_FR": "Vous êtes sur le point de mettre en file d'attente une reconstruction PPTC au prochain démarrage de :\n\n{0}\n\nÊtes-vous sûr de vouloir continuer ?",
+ "fr_FR": "Vous êtes sur le point de programmer une reconstruction PPTC au prochain démarrage de :\n\n{0}\n\nÊtes-vous sûr de vouloir continuer ?",
"he_IL": "אם תמשיכו אתם עומדים לגרום לבנייה מחדש של מטמון ה-PPTC עבור:\n\n{0}",
"it_IT": "Stai per accodare la rigenerazione della cache PPTC al prossimo avvio per:\n\n{0}\n\nSei sicuro di voler proseguire?",
"ja_JP": "次回起動時に PPTC を再構築します:\n\n{0}\n\n実行してよろしいですか?",
@@ -14205,7 +14205,7 @@
"el_GR": "",
"en_US": "You are about to purge all PPTC data from:\n\n{0}\n\nAre you sure you want to proceed?",
"es_ES": "",
- "fr_FR": "",
+ "fr_FR": "Vous êtes sur le point de supprimer toutes les données PPTC de :\n\n{0}\n\nÊtes-vous sûr de vouloir continuer ?",
"he_IL": "",
"it_IT": "",
"ja_JP": "",
@@ -14230,7 +14230,7 @@
"el_GR": "Πρόκειται να διαγράψετε την προσωρινή μνήμη Shader για :\n\n{0}\n\nΕίστε βέβαιοι ότι θέλετε να συνεχίσετε;",
"en_US": "You are about to delete the Shader cache for :\n\n{0}\n\nAre you sure you want to proceed?",
"es_ES": "Vas a borrar la caché de sombreadores para:\n\n{0}\n\n¿Estás seguro de querer continuar?",
- "fr_FR": "Vous êtes sur le point de supprimer le cache du Shader pour :\n\n{0}\n\nÊtes-vous sûr de vouloir continuer ?",
+ "fr_FR": "Vous êtes sur le point de supprimer le cache des shaders pour :\n\n{0}\n\nÊtes-vous sûr de vouloir continuer ?",
"he_IL": "אם תמשיכו אתם עומדים למחוק את מטמון ההצללות עבור:\n\n{0}",
"it_IT": "Stai per eliminare la cache degli shader per:\n\n{0}\n\nSei sicuro di voler proseguire?",
"ja_JP": "シェーダーキャッシュを破棄しようとしています:\n\n{0}\n\n実行してよろしいですか?",
@@ -14255,7 +14255,7 @@
"el_GR": "Σφάλμα κατά την εκκαθάριση προσωρινής μνήμης Shader στο {0}: {1}",
"en_US": "Error purging Shader cache at {0}: {1}",
"es_ES": "Error purgando la caché de sombreadores en {0}: {1}",
- "fr_FR": "Erreur lors de la purge du cache du Shader à {0}: {1}",
+ "fr_FR": "Erreur lors de l’élimination du cache des shaders à {0} : {1}",
"he_IL": "שגיאה בניקוי מטמון ההצללות ב-{0}: {1}",
"it_IT": "Errore nell'eliminazione della cache degli shader a {0}: {1}",
"ja_JP": "シェーダーキャッシュ破棄エラー {0}: {1}",
@@ -14580,7 +14580,7 @@
"el_GR": "",
"en_US": "\n\nThis may replace some of the current installed Keys.",
"es_ES": "\n\nEsto puede reemplazar algunas de las Keys actualmente instaladas.",
- "fr_FR": "\n\nCela pourrait remplacer les clés qui sont installés.",
+ "fr_FR": "\n\nCela peut remplacer certaines des clés actuellement installées.",
"he_IL": "",
"it_IT": "\n\nAlcune delle chiavi già installate potrebbero essere sovrascritte.",
"ja_JP": "",
@@ -14605,7 +14605,7 @@
"el_GR": "",
"en_US": "\n\nDo you want to continue?",
"es_ES": "\n\nDeseas continuar?",
- "fr_FR": "\n\nVoulez-vous continuez ?",
+ "fr_FR": "\n\nVoulez-vous continuer ?",
"he_IL": "",
"it_IT": "\n\nVuoi continuare?",
"ja_JP": "",
@@ -14755,7 +14755,7 @@
"el_GR": "Έχετε κάνει αλλαγές σε αυτό το προφίλ χρήστη που δεν έχουν αποθηκευτεί.",
"en_US": "You have made changes to this user profile that have not been saved.",
"es_ES": "Ha realizado cambios en este perfil de usuario que no han sido guardados.",
- "fr_FR": "Vous avez effectué des modifications sur ce profil d'utilisateur qui n'ont pas été enregistrées.",
+ "fr_FR": "Vous avez apporté des modifications à ce profil utilisateur qui n'ont pas été enregistrées.",
"he_IL": "ביצעת שינויים בפרופיל משתמש זה שלא נשמרו.",
"it_IT": "Hai apportato modifiche a questo profilo utente che non sono state salvate.",
"ja_JP": "保存されていないユーザプロファイルを変更しました.",
@@ -14780,7 +14780,7 @@
"el_GR": "Θέλετε να απορρίψετε τις αλλαγές σας;",
"en_US": "Do you want to discard your changes?",
"es_ES": "¿Quieres descartar los cambios realizados?",
- "fr_FR": "Voulez-vous annuler les modifications ?",
+ "fr_FR": "Voulez-vous annuler vos modifications ?",
"he_IL": "האם ברצונך למחוק את השינויים האחרונים?",
"it_IT": "Vuoi scartare le modifiche?",
"ja_JP": "変更を破棄しますか?",
@@ -14855,7 +14855,7 @@
"el_GR": "",
"en_US": "{0}. Errored File: {1}",
"es_ES": "{0}. Archivo con error: {1}",
- "fr_FR": "{0}. Fichier erroné : {1}",
+ "fr_FR": "{0}. Fichier avec erreur : {1}",
"he_IL": "{0}. קובץ שגוי: {1}",
"it_IT": "{0}. Errore file: {1}",
"ja_JP": "{0}. エラー発生ファイル: {1}",
@@ -14930,7 +14930,7 @@
"el_GR": "",
"en_US": "Failed to Delete: Could not find the parent directory for mod \"{0}\"!",
"es_ES": "Error al eliminar: ¡No se pudo encontrar el directorio principal para el mod \"{0}\"!",
- "fr_FR": "Impossible de supprimer : impossible de trouver le répertoire parent pour le mod \"{0} \" !",
+ "fr_FR": "Échec de la suppression : impossible de trouver le répertoire parent du mod \"{0} \" !",
"he_IL": "נכשל למחוק: לא היה ניתן למצוא את תיקיית האב למוד \"{0}\"!\n",
"it_IT": "Eliminazione non riuscita: impossibile trovare la directory superiore per la mod \"{0}\"!",
"ja_JP": "削除に失敗しました: Mod \"{0}\" の親ディレクトリが見つかりませんでした!",
@@ -15005,7 +15005,7 @@
"el_GR": "Για βέλτιστη απόδοση, συνιστάται η απενεργοποίηση καταγραφής εντοπισμού σφαλμάτων. Θέλετε να απενεργοποιήσετε την καταγραφή τώρα;",
"en_US": "For optimal performance, it's recommended to disable trace logging. Would you like to disable trace logging now?",
"es_ES": "Para un rendimiento óptimo, se recomienda deshabilitar los registros debug. ¿Quieres deshabilitarlos ahora?",
- "fr_FR": "Pour des performances optimales, il est recommandé de désactiver la journalisation des traces. Souhaitez-vous désactiver la journalisation des traces maintenant ?",
+ "fr_FR": "Pour des performances optimales, il est recommandé de désactiver la journalisation de trace. Souhaitez-vous désactiver la journalisation de trace maintenant ?",
"he_IL": "לביצועים מיטביים, מומלץ להשבית את רישום המעקב. האם ברצונך להשבית את רישום המעקב כעת?",
"it_IT": "Per prestazioni ottimali, si raccomanda di disabilitare il trace logging. Vuoi disabilitarlo adesso?",
"ja_JP": "パフォーマンス最適化のためには,トレースロギングを無効にすることを推奨します. トレースロギングを無効にしてよろしいですか?",
@@ -15180,7 +15180,7 @@
"el_GR": "Το Ryujinx πρέπει να επανεκκινηθεί αφού αλλάξει αυτή η επιλογή για να εφαρμοστεί πλήρως. Ανάλογα με την πλατφόρμα σας, μπορεί να χρειαστεί να απενεργοποιήσετε με μη αυτόματο τρόπο το multithreading του ίδιου του προγράμματος οδήγησης όταν χρησιμοποιείτε το Ryujinx.",
"en_US": "Ryujinx must be restarted after changing this option for it to apply fully. Depending on your platform, you may need to manually disable your driver's own multithreading when using Ryujinx's.",
"es_ES": "Ryujinx debe reiniciarse para aplicar este cambio. Dependiendo de tu plataforma, puede que tengas que desactivar manualmente la optimización enlazada de tus controladores gráficos para usar el multihilo de Ryujinx.",
- "fr_FR": "Ryujinx doit être redémarré après avoir changé cette option pour qu'elle s'applique complètement. Selon votre plate-forme, vous devrez peut-être désactiver manuellement le multithreading de votre pilote lorsque vous utilisez Ryujinx.",
+ "fr_FR": "Ryujinx doit être redémarré après avoir modifié cette option pour que les changements soient pleinement pris en compte. Selon votre plateforme, il se peut que vous deviez désactiver manuellement le multithreading propre à votre pilote lorsque vous utilisez celui de Ryujinx",
"he_IL": "יש להפעיל מחדש את ריוג'ינקס לאחר שינוי אפשרות זו כדי שהיא תחול במלואה. בהתאם לפלטפורמה שלך, ייתכן שיהיה עליך להשבית ידנית את ריבוי ההליכים של ההתקן שלך בעת השימוש ב-ריוג'ינקס.",
"it_IT": "Ryujinx deve essere riavviato dopo aver cambiato questa opzione per applicarla completamente. A seconda della tua piattaforma, potrebbe essere necessario disabilitare manualmente il multithreading del driver quando usi quello di Ryujinx.",
"ja_JP": "このオプションの変更を完全に適用するには Ryujinx の再起動が必要です. プラットフォームによっては, Ryujinx のものを使用する前に手動でドライバ自身のマルチスレッディングを無効にする必要があるかもしれません.",
@@ -15455,7 +15455,7 @@
"el_GR": "Παύση",
"en_US": "Pause",
"es_ES": "Pausar",
- "fr_FR": "Suspendre",
+ "fr_FR": "",
"he_IL": "הפסק",
"it_IT": "Metti in pausa",
"ja_JP": "一時停止",
@@ -15505,7 +15505,7 @@
"el_GR": "Κάντε κλικ για να ανοίξετε τον ιστότοπο Ryujinx στο προεπιλεγμένο πρόγραμμα περιήγησης.",
"en_US": "Click to open the Ryujinx website in your default browser.",
"es_ES": "Haz clic para abrir el sitio web de Ryujinx en tu navegador predeterminado.",
- "fr_FR": "Cliquez pour ouvrir le site de Ryujinx dans votre navigateur par défaut.",
+ "fr_FR": "Cliquez pour ouvrir le site de Ryujinx dans votre navigateur par défaut",
"he_IL": "לחץ כדי לפתוח את אתר ריוג'ינקס בדפדפן ברירת המחדל שלך.",
"it_IT": "Clicca per aprire il sito web di Ryujinx nel tuo browser predefinito.",
"ja_JP": "クリックするとデフォルトのブラウザで Ryujinx のウェブサイトを開きます.",
@@ -15530,7 +15530,7 @@
"el_GR": "Το Ryujinx δεν είναι συνδεδεμένο με τη Nintendo™,\nούτε με κανέναν από τους συνεργάτες της, με οποιονδήποτε τρόπο.",
"en_US": "Ryujinx is not affiliated with Nintendo™,\nor any of its partners, in any way.",
"es_ES": "Ryujinx no tiene afiliación alguna con Nintendo™,\nni con ninguno de sus socios.",
- "fr_FR": "Ryujinx n'est pas affilié à Nintendo™,\nou à aucun de ses partenaires, de quelque manière que ce soit.",
+ "fr_FR": "Ryujinx n’est en aucun cas affilié à Nintendo™,\nni à aucun de ses partenaires, de quelque manière que ce soit.",
"he_IL": "ריוג'ינקס אינה מזוהת עם נינטנדו,\nאו שוטפייה בכל דרך שהיא.",
"it_IT": "Ryujinx non è affiliato con Nintendo™,\no i suoi partner, in alcun modo.",
"ja_JP": "Ryujinx は Nintendo™ および\nそのパートナー企業とは一切関係ありません.",
@@ -15655,7 +15655,7 @@
"el_GR": "",
"en_US": "Ryujinx is an emulator for the Nintendo Switch™ 1.\nGet all the latest news in our Discord.\nDevelopers interested in contributing can find out more on our GitLab or Discord.",
"es_ES": "",
- "fr_FR": "Ryujinx est un émulateur pour la Nintendo Switch™ 1.\nObtenez le dernières nouvelles sur le Discord.\nLes développeurs qui veulent contribuer peuvent en savoir plus sur notre GitLab ou Discord.",
+ "fr_FR": "Ryujinx est un émulateur pour la Nintendo Switch™ 1.\nObtenez les dernières nouvelles sur notre Discord.\nLes développeurs souhaitant contribuer peuvent en savoir plus sur notre GitLab ou Discord.",
"he_IL": "",
"it_IT": "Ryujinx è un emulatore della console Nintendo Switch™ 1.\nRimani aggiornato sulle ultime novità nel nostro server Discord.\nGli sviluppatori interessati a contribuire possono trovare maggiori informazioni su Discord o sulla nostra pagina GitLab.",
"ja_JP": "",
@@ -16305,7 +16305,7 @@
"el_GR": "Ενεργοποιεί ή απενεργοποιεί την Εμπλουτισμένη Παρουσία σας στο Discord",
"en_US": "Choose whether or not to display Ryujinx on your \"currently playing\" Discord activity",
"es_ES": "Elige si muestras Ryujinx o no en tu actividad de Discord cuando lo estés usando",
- "fr_FR": "Choisissez d'afficher ou non Ryujinx sur votre activité « en cours de jeu » Discord",
+ "fr_FR": "Choisissez d’afficher ou non Ryujinx dans votre activité Discord « en cours de jeu »",
"he_IL": "בחרו להציג את ריוג'ינקס או לא בפעילות הדיסקורד שלכם \"משוחק כרגע\".",
"it_IT": "Scegli se mostrare o meno Ryujinx nella tua attività su Discord",
"ja_JP": "Discord の \"現在プレイ中\" アクティビティに Ryujinx を表示するかどうかを選択します",
@@ -16405,7 +16405,7 @@
"el_GR": "",
"en_US": "Enter an autoload directory to add to the list",
"es_ES": "Elige un directorio de carga automática para agregar a la lista",
- "fr_FR": "Entrez un répertoire de mises à jour/DLC à ajouter à la liste",
+ "fr_FR": "Saisissez un répertoire d’autochargement à ajouter à la liste",
"he_IL": "",
"it_IT": "Inserisci una cartella di caricamento automatico da aggiungere alla lista",
"ja_JP": "",
@@ -16430,7 +16430,7 @@
"el_GR": "",
"en_US": "Add an autoload directory to the list",
"es_ES": "Agregar un directorio de carga automática a la lista",
- "fr_FR": "Ajouter un répertoire de mises à jour/DLC à la liste",
+ "fr_FR": "Ajouter un répertoire d’autochargement à la liste",
"he_IL": "",
"it_IT": "Aggiungi una cartella di caricamento automatico alla lista",
"ja_JP": "",
@@ -16455,7 +16455,7 @@
"el_GR": "",
"en_US": "Remove selected autoload directory",
"es_ES": "Eliminar el directorio de carga automática seleccionado",
- "fr_FR": "Supprimer le répertoire de mises à jour/DLC sélectionné",
+ "fr_FR": "Supprimer le répertoire d’autochargement sélectionné",
"he_IL": "",
"it_IT": "Rimuovi la cartella di caricamento automatico selezionata",
"ja_JP": "",
@@ -16480,7 +16480,7 @@
"el_GR": "Ενεργοποίηση ή απενεργοποίηση προσαρμοσμένων θεμάτων στο GUI",
"en_US": "Use a custom Avalonia theme for the GUI to change the appearance of the emulator menus",
"es_ES": "Activa o desactiva los temas personalizados para la interfaz",
- "fr_FR": "Utilisez un thème personnalisé Avalonia pour modifier l'apparence des menus de l'émulateur",
+ "fr_FR": "Utilisez un thème Avalonia personnalisé pour modifier l'apparence des menus de l'émulateur",
"he_IL": "השתמש בעיצוב מותאם אישית של אבלוניה עבור ה-ממשק הגראפי כדי לשנות את המראה של תפריטי האמולטור",
"it_IT": "Utilizza un tema di Avalonia personalizzato per cambiare l'aspetto dei menù dell'emulatore",
"ja_JP": "エミュレータのメニュー外観を変更するためカスタム Avalonia テーマを使用します",
@@ -16530,7 +16530,7 @@
"el_GR": "Αναζητήστε ένα προσαρμοσμένο θέμα GUI",
"en_US": "Browse for a custom GUI theme",
"es_ES": "Busca un tema personalizado para la interfaz",
- "fr_FR": "Parcourir vers un thème personnalisé pour l'interface utilisateur",
+ "fr_FR": "Parcourir un thème personnalisé pour l’interface graphique",
"he_IL": "חפש עיצוב ממשק גראפי מותאם אישית",
"it_IT": "Scegli un tema dell'interfaccia personalizzato",
"ja_JP": "カスタム GUI テーマを参照します",
@@ -16555,7 +16555,7 @@
"el_GR": "",
"en_US": "If this option is enabled in custom settings, the global input configuration will be used.\n\nIn the global settings: you can enable or disable it as needed; this setting will be inherited by any new custom configurations created.",
"es_ES": "",
- "fr_FR": "",
+ "fr_FR": "Si cette option est activée dans les paramètres personnalisés, la configuration d’entrée globale sera utilisée.\n\nDans les paramètres globaux, vous pouvez l’activer ou la désactiver selon vos besoins ; ce réglage sera alors hérité par toutes les nouvelles configurations personnalisées créées",
"he_IL": "",
"it_IT": "",
"ja_JP": "",
@@ -16580,7 +16580,7 @@
"el_GR": "Ενεργοποιήστε ή απενεργοποιήστε τη λειτουργία σύνδεσης",
"en_US": "Docked mode makes the emulated system behave as a docked Nintendo Switch. This improves graphical fidelity in most games. Conversely, disabling this will make the emulated system behave as a handheld Nintendo Switch, reducing graphics quality.\n\nConfigure player 1 controls if planning to use docked mode; configure handheld controls if planning to use handheld mode.\n\nLeave ON if unsure.",
"es_ES": "El modo dock o modo TV hace que la consola emulada se comporte como una Nintendo Switch en su dock. Esto mejora la calidad gráfica en la mayoría de los juegos. Del mismo modo, si lo desactivas, el sistema emulado se comportará como una Nintendo Switch en modo portátil, reduciendo la cálidad de los gráficos.\n\nConfigura los controles de \"Jugador\" 1 si planeas jugar en modo dock/TV; configura los controles de \"Portátil\" si planeas jugar en modo portátil.\n\nActívalo si no sabes qué hacer.",
- "fr_FR": "Le mode station d'accueil permet à la console émulée de se comporter comme une Nintendo Switch en mode station d'accueil, ce qui améliore la fidélité graphique dans la plupart des jeux. Inversement, la désactivation de cette option rendra la console émulée comme une console Nintendo Switch portable, réduisant la qualité graphique.\n\nConfigurer les controles du joueur 1 si vous prévoyez d'utiliser le mode station d'accueil; configurez les commandes portable si vous prévoyez d'utiliser le mode portable.\n\nLaissez ACTIVER si vous n'êtes pas sûr.",
+ "fr_FR": "Le mode docké fait que le système émulé se comporte comme une Nintendo Switch en mode dock. Cela améliore la qualité graphique dans la plupart des jeux. À l’inverse, désactiver ce mode fera que le système émulé se comporte comme une Nintendo Switch en mode portable, ce qui réduit la qualité graphique.\n\nConfigurez les contrôles du joueur 1 si vous prévoyez d’utiliser le mode docké ; configurez les contrôles pour le mode portable si vous comptez utiliser ce dernier.\n\nLaissez ACTIVÉ si vous n’êtes pas sûr..",
"he_IL": "מצב עגינה גורם למערכת המדומה להתנהג כ-נינטנדו סוויץ' בתחנת עגינתו. זה משפר את הנאמנות הגרפית ברוב המשחקים.\n לעומת זאת, השבתה של תכונה זו תגרום למערכת המדומה להתנהג כ- נינטנדו סוויץ' נייד, ולהפחית את איכות הגרפיקה.\n\nהגדירו את שלט שחקן 1 אם אתם מתכננים להשתמש במצב עגינה; הגדירו את פקדי כף היד אם אתם מתכננים להשתמש במצב נייד.\n\nמוטב להשאיר דלוק אם אתם לא בטוחים.",
"it_IT": "La modalità TV fa sì che il sistema emulato si comporti come una Nintendo Switch posizionata nella sua base. Ciò migliora la qualità grafica nella maggior parte dei giochi. Al contrario, disabilitandola il sistema emulato si comporterà come una Nintendo Switch in modalità portatile, riducendo la qualità grafica.\n\nConfigura i controlli del giocatore 1 se intendi usare la modalità TV; configura i controlli della modalità portatile se intendi usare quest'ultima.\n\nNel dubbio, lascia l'opzione attiva.",
"ja_JP": "有効にすると,ドッキングされた Nintendo Switch をエミュレートします.多くのゲームではグラフィックス品質が向上します.\n無効にすると,携帯モードの Nintendo Switch をエミュレートします.グラフィックスの品質は低下します.\n\nドッキングモード有効ならプレイヤー1の,無効なら携帯の入力を設定してください.\n\nよくわからない場合はオンのままにしてください.",
@@ -16605,7 +16605,7 @@
"el_GR": "",
"en_US": "Direct keyboard access (HID) support. Provides games access to your keyboard as a text entry device.\n\nOnly works with games that natively support keyboard usage on Switch hardware.\n\nLeave OFF if unsure.",
"es_ES": "Soporte de acceso directo al teclado (HID). Proporciona a los juegos acceso a su teclado como dispositivo de entrada de texto.\n\nSolo funciona con juegos que permiten de forma nativa el uso del teclado en el hardware de Switch.\n\nDesactívalo si no sabes qué hacer.",
- "fr_FR": "Prise en charge de l'accès direct au clavier (HID). Permet aux jeux d'accéder à votre clavier comme périphérique de saisie de texte.\n\nFonctionne uniquement avec les jeux prenant en charge nativement l'utilisation du clavier sur le matériel Switch.\n\nLaissez désactiver si vous n'êtes pas sûr.",
+ "fr_FR": "Prise en charge de l’accès direct au clavier (HID). Permet aux jeux d’accéder à votre clavier comme périphérique de saisie de texte.\n\nFonctionne uniquement avec les jeux qui supportent nativement l’utilisation du clavier sur la console Switch.\n\nLaissez DÉSACTIVÉ si vous n'êtes pas sûr.",
"he_IL": "",
"it_IT": "Supporto per l'accesso diretto alla tastiera (HID). Fornisce ai giochi l'accesso alla tastiera come dispositivo di inserimento del testo.\n\nFunziona solo con i giochi che supportano nativamente l'utilizzo della tastiera su hardware Switch.\n\nNel dubbio, lascia l'opzione disattivata.",
"ja_JP": "直接キーボード アクセス (HID) のサポートです. テキスト入力デバイスとしてキーボードへのゲームアクセスを提供します.\n\nSwitchハードウェアでキーボードの使用をネイティブにサポートしているゲームでのみ動作します.\n\nわからない場合はオフのままにしてください.",
@@ -16630,7 +16630,7 @@
"el_GR": "",
"en_US": "Direct mouse access (HID) support. Provides games access to your mouse as a pointing device.\n\nOnly works with games that natively support mouse controls on Switch hardware, which are few and far between.\n\nWhen enabled, touch screen functionality may not work.\n\nLeave OFF if unsure.",
"es_ES": "Soporte de acceso directo al mouse (HID). Proporciona a los juegos acceso a su mouse como puntero.\n\nSolo funciona con juegos que permiten de forma nativa el uso de controles con mouse en el hardware de switch, lo cual son pocos.\n\nCuando esté activado, la funcionalidad de pantalla táctil puede no funcionar.\n\nDesactívalo si no sabes qué hacer.",
- "fr_FR": "Prise en charge de l'accès direct à la souris (HID). Permet aux jeux d'accéder à votre souris en tant que dispositif de pointage.\n\nFonctionne uniquement avec les jeux qui prennent en charge nativement les contrôles de souris sur le matériel Switch, ce qui est rare.\n\nLorsqu'il est activé, la fonctionnalité de l'écran tactile peut ne pas fonctionner.\n\nLaissez désactiver si vous n'êtes pas sûr.",
+ "fr_FR": "Prise en charge de l’accès direct à la souris (HID). Permet aux jeux d’accéder à votre souris comme périphérique de pointage.\n\nNe fonctionne qu’avec les jeux qui supportent nativement les contrôles souris sur la console Switch, ce qui est rare.\n\nLorsque cette option est activée, la fonctionnalité de l’écran tactile peut ne pas fonctionner.\n\nLaissez DÉSACTIVÉ si vous n'êtes pas sûr.",
"he_IL": "",
"it_IT": "Supporto per l'accesso diretto al mouse (HID). Fornisce ai giochi l'accesso al mouse come dispositivo di puntamento.\n\nFunziona solo con i rari giochi che supportano nativamente l'utilizzo del mouse su hardware Switch.\n\nQuando questa opzione è attivata, il touchscreen potrebbe non funzionare.\n\nNel dubbio, lascia l'opzione disattivata.",
"ja_JP": "直接マウスアクセス (HID) のサポートです. ポインティングデバイスとしてマウスへのゲームアクセスを提供します.\n\nSwitchハードウェアでマウスの使用をネイティブにサポートしているゲームでのみ動作します.\n\n有効にしている場合, タッチスクリーン機能は動作しない場合があります.\n\nわからない場合はオフのままにしてください.",
@@ -16755,7 +16755,7 @@
"el_GR": "",
"en_US": "Sync System Time to match your PC's current date & time.",
"es_ES": "",
- "fr_FR": "Resynchronise la Date du Système pour qu'elle soit la même que celle du PC.",
+ "fr_FR": "Synchroniser l’heure système avec la date et l’heure actuelles de votre PC.",
"he_IL": "",
"it_IT": "Sincronizza data e ora del sistema con quelle del PC.",
"ja_JP": "",
@@ -16780,7 +16780,7 @@
"el_GR": "",
"en_US": "Emulated console's Vertical Sync. Essentially a frame-limiter for the majority of games; disabling it may cause games to run at higher speed or make loading screens take longer or get stuck.\n\nCan be toggled in-game with a hotkey of your preference (F1 by default). We recommend doing this if you plan on disabling it.\n\nLeave ON if unsure.",
"es_ES": "Sincronización vertical de la consola emulada. En práctica un limitador del framerate para la mayoría de los juegos; desactivando puede causar que juegos corran a mayor velocidad o que las pantallas de carga tarden más o queden atascados.\n\nSe puede alternar en juego utilizando una tecla de acceso rápido configurable (F1 by default). Recomendamos hacer esto en caso de querer desactivar sincroniziación vertical.\n\nDesactívalo si no sabes qué hacer.",
- "fr_FR": "La synchronisation verticale de la console émulée. Essentiellement un limiteur de trame pour la majorité des jeux ; le désactiver peut entraîner un fonctionnement plus rapide des jeux ou prolonger ou bloquer les écrans de chargement.\n\nPeut être activé ou désactivé en jeu avec un raccourci clavier de votre choix (F1 par défaut). Nous recommandons de le faire si vous envisagez de le désactiver.\n\nLaissez activé si vous n'êtes pas sûr.",
+ "fr_FR": "Synchronisation verticale de la console émulée. Il s'agit essentiellement d'un limiteur de fréquence d'images pour la majorité des jeux ; la désactivation peut entraîner une accélération du jeu ou provoquer des temps de chargement plus longs, voire des blocages..\n\nPeut être activée/désactivée en jeu via un raccourci clavier de votre choix (F1 par défaut). Nous recommandons d’utiliser ce raccourci si vous prévoyez de la désactiver.\n\nLaissez ACTIVÉE si vous n’êtes pas sûr.",
"he_IL": "",
"it_IT": "Sincronizzazione verticale della console emulata. Funziona essenzialmente come un limitatore del framerate per la maggior parte dei giochi; disabilitarla può far girare giochi a velocità più alta, allungare le schermate di caricamento o farle bloccare.\n\nPuò essere attivata mentre giochi con un tasto di scelta rapida (F1 per impostazione predefinita). Ti consigliamo di farlo se hai intenzione di disabilitarla.\n\nNel dubbio, lascia l'opzione attiva.",
"ja_JP": "エミュレートされたゲーム機の垂直同期です. 多くのゲームにおいて, フレームリミッタとして機能します. 無効にすると, ゲームが高速で実行されたり, ロード中に時間がかかったり, 止まったりすることがあります.\n\n設定したホットキー(デフォルトではF1)で, ゲーム内で切り替え可能です. 無効にする場合は, この操作を行うことをおすすめします.\n\nよくわからない場合はオンのままにしてください.",
@@ -16805,7 +16805,7 @@
"el_GR": "Ενεργοποιεί ή απενεργοποιεί το PPTC",
"en_US": "Saves translated JIT functions so that they do not need to be translated every time the game loads.\n\nReduces stuttering and significantly speeds up boot times after the first boot of a game.\n\nLeave ON if unsure.",
"es_ES": "Guarda funciones de JIT traducidas para que no sea necesario traducirlas cada vez que el juego carga.\n\nReduce los tirones y acelera significativamente el tiempo de inicio de los juegos después de haberlos ejecutado al menos una vez.\n\nActívalo si no sabes qué hacer.",
- "fr_FR": "Sauvegarde les fonctions JIT afin qu'elles n'aient pas besoin d'être à chaque fois recompiler lorsque le jeu se charge.\n\nRéduit les lags et accélère considérablement le temps de chargement après le premier lancement d'un jeu.\n\nLaissez par défaut si vous n'êtes pas sûr.",
+ "fr_FR": "Sauvegarde les fonctions JIT traduites afin qu’elles n’aient pas besoin d’être retraduites à chaque chargement du jeu.\n\nRéduit les lags et accélère considérablement le temps de chargement après le premier lancement d'un jeu.\n\nLaissez ACTIVÉ si vous n’êtes pas sûr.",
"he_IL": "שומר את פונקציות ה-JIT המתורגמות כך שלא יצטרכו לעבור תרגום שוב כאשר משחק עולה.\n\nמפחית תקיעות ומשפר מהירות עלייה של המערכת אחרי הפתיחה הראשונה של המשחק.\n\nמוטב להשאיר דלוק אם לא בטוחים.",
"it_IT": "Salva le funzioni JIT tradotte in modo che non debbano essere tradotte tutte le volte che si avvia un determinato gioco.\n\nRiduce i fenomeni di stuttering e velocizza sensibilmente gli avvii successivi del gioco.\n\nNel dubbio, lascia l'opzione attiva.",
"ja_JP": "翻訳されたJIT関数をセーブすることで, ゲームをロードするたびに毎回翻訳する処理を不要とします.\n\n一度ゲームを起動すれば,二度目以降の起動時遅延を大きく軽減できます.\n\nよくわからない場合はオンのままにしてください.",
@@ -16830,7 +16830,7 @@
"el_GR": "",
"en_US": "Load the PPTC using a third of the amount of cores.",
"es_ES": "Cargue el PPTC utilizando un tercio de la cantidad de núcleos.",
- "fr_FR": "Charger le PPTC en utilisant un tiers des coeurs.",
+ "fr_FR": "Charger le PPTC en utilisant un tiers des cœurs disponibles.",
"he_IL": "",
"it_IT": "Carica la cache PPTC usando un terzo dei core del processore.",
"ja_JP": "",
@@ -16855,7 +16855,7 @@
"el_GR": "Ενεργοποιεί τους ελέγχους ακεραιότητας σε αρχεία περιεχομένου παιχνιδιού",
"en_US": "Checks for corrupt files when booting a game, and if corrupt files are detected, displays a hash error in the log.\n\nHas no impact on performance and is meant to help troubleshooting.\n\nLeave ON if unsure.",
"es_ES": "Comprueba si hay archivos corruptos en los juegos que ejecutes al abrirlos, y si detecta archivos corruptos, muestra un error de Hash en los registros.\n\nEsto no tiene impacto alguno en el rendimiento y está pensado para ayudar a resolver problemas.\n\nActívalo si no sabes qué hacer.",
- "fr_FR": "Vérifie si des fichiers sont corrompus lors du lancement d'un jeu, et si des fichiers corrompus sont détectés, affiche une erreur de hachage dans la console.\n\nN'a aucun impact sur les performances et est destiné à aider le dépannage.\n\nLaissez activé en cas d'incertitude.",
+ "fr_FR": "Vérifie la présence de fichiers corrompus au démarrage d’un jeu. En cas de fichiers corrompus détectés, un message d’erreur de hachage s’affiche dans le journal.\n\nN’a aucun impact sur les performances et est destiné à faciliter le dépannage.\n\nLaissez ACTIVÉ si vous n’êtes pas sûr.",
"he_IL": "בודק לקבצים שגויים כאשר משחק עולה, ואם מתגלים כאלו, מציג את מזהה השגיאה שלהם לקובץ הלוג.\n\nאין לכך השפעה על הביצועים ונועד לעזור לבדיקה וניפוי שגיאות של האמולטור.\n\nמוטב להשאיר דלוק אם לא בטוחים.",
"it_IT": "Controlla la presenza di file corrotti quando si avvia un gioco. Se vengono rilevati dei file corrotti, verrà mostrato un errore di hash nel log.\n\nQuesta opzione non influisce sulle prestazioni ed è pensata per facilitare la risoluzione dei problemi.\n\nNel dubbio, lascia l'opzione attiva.",
"ja_JP": "ゲーム起動時にファイル破損をチェックし,破損が検出されたらログにハッシュエラーを表示します..\n\nパフォーマンスには影響なく, トラブルシューティングに役立ちます.\n\nよくわからない場合はオンのままにしてください.",
@@ -16880,7 +16880,7 @@
"el_GR": "Αλλαγή ήχου υποστήριξης",
"en_US": "Changes the backend used to render audio.\n\nSDL2 is the preferred one, while OpenAL and SoundIO are used as fallbacks. Dummy will have no sound.\n\nSet to SDL2 if unsure.",
"es_ES": "Cambia el motor usado para renderizar audio.\n\nSDL2 es el preferido, mientras que OpenAL y SoundIO se usan si hay problemas con este. Dummy no produce audio.\n\nSelecciona SDL2 si no sabes qué hacer.",
- "fr_FR": "Modifie le backend utilisé pour donnée un rendu audio.\n\nSDL2 est recommandé, tandis que OpenAL et SoundIO sont utilisés comme backend secondaire. Le backend Dummy (Désactivé) ne rend aucun son.\n\nLaissez sur SDL2 si vous n'êtes pas sûr.",
+ "fr_FR": "Change le moteur utilisé pour le rendu audio.\n\nSDL2 est le moteur recommandé, tandis qu’OpenAL et SoundIO sont utilisés en secours. Dummy ne produit aucun son.\n\nLaissez sur SDL2 si vous n'êtes pas sûr.",
"he_IL": "משנה את אחראי השמע.\n\nSDL2 הוא הנבחר, למראת שOpenAL וגם SoundIO משומשים כאפשרויות חלופיות. אפשרות הDummy לא תשמיע קול כלל.\n\nמוטב להשאיר על SDL2 אם לא בטוחים.",
"it_IT": "Cambia il backend usato per riprodurre l'audio.\n\nSDL2 è quello preferito, mentre OpenAL e SoundIO sono usati come ripiego. Dummy non riprodurrà alcun suono.\n\nNel dubbio, imposta l'opzione su SDL2.",
"ja_JP": "音声レンダリングに使用するバックエンドを変更します.\n\nSDL2 が優先され, OpenAL と SoundIO はフォールバックとして使用されます. ダミーは音声出力しません.\n\nよくわからない場合は SDL2 を設定してください.",
@@ -16905,7 +16905,7 @@
"el_GR": "Αλλάξτε τον τρόπο αντιστοίχισης και πρόσβασης στη μνήμη επισκέπτη. Επηρεάζει σε μεγάλο βαθμό την απόδοση της προσομοίωσης της CPU.",
"en_US": "Change how guest memory is mapped and accessed. Greatly affects emulated CPU performance.\n\nSet to HOST UNCHECKED if unsure.",
"es_ES": "Cambia la forma de mapear y acceder a la memoria del guest. Afecta en gran medida al rendimiento de la CPU emulada.\n\nSelecciona \"Host sin verificación\" si no sabes qué hacer.",
- "fr_FR": "Change la façon dont la mémoire émulée est mappée et utilisée. Cela affecte grandement les performances du processeur.\n\nRéglez sur Hôte non vérifié en cas d'incertitude.",
+ "fr_FR": "Modifie la manière dont la mémoire émulée est mappée et accédée. Impacte fortement les performances du processeur émulé.\n\nLaissez sur HÔTE NON VÉRIFIÉ si vous n'êtes pas sûr.",
"he_IL": "שנה איך שזיכרון מארח מיוחד ומונגד. משפיע מאוד על ביצועי המעבד המדומה.\n\nמוטב להשאיר על מארח לא מבוקר אם לא בטוחים.",
"it_IT": "Cambia il modo in cui la memoria guest è mappata e vi si accede. Influisce notevolmente sulle prestazioni della CPU emulata.\n\nNel dubbio, imposta l'opzione su Host Unchecked.",
"ja_JP": "ゲストメモリのマップ/アクセス方式を変更します. エミュレートされるCPUのパフォーマンスに大きな影響を与えます.\n\nよくわからない場合は「ホスト,チェックなし」を設定してください.",
@@ -16930,7 +16930,7 @@
"el_GR": "Χρησιμοποιήστε έναν πίνακα σελίδων λογισμικού για τη μετάφραση διευθύνσεων. Υψηλότερη ακρίβεια αλλά πιο αργή απόδοση.",
"en_US": "Use a software page table for address translation. Highest accuracy but slowest performance.",
"es_ES": "Usa una tabla de paginación de software para traducir direcciones. Ofrece la precisión más exacta pero el rendimiento más lento.",
- "fr_FR": "Utilisez une table logicielle pour la traduction d'adresses. La plus grande précision est fournie, mais les performances en seront impactées.",
+ "fr_FR": "Utilise une table des pages logicielle pour la traduction des adresses. Offre la plus grande précision mais les performances sont les plus lentes.",
"he_IL": "השתמש בתוכנת ה-page table בכדי להתייחס לתרגומים. דיוק מרבי לקונסולה אך המימוש הכי איטי.",
"it_IT": "Usa una software page table per la traduzione degli indirizzi. Massima precisione ma prestazioni più lente.",
"ja_JP": "アドレス変換にソフトウェアページテーブルを使用します. 非常に正確ですがパフォーマンスが大きく低下します.",
@@ -16955,7 +16955,7 @@
"el_GR": "Απευθείας αντιστοίχιση της μνήμης στον χώρο διευθύνσεων υπολογιστή υποδοχής. Πολύ πιο γρήγορη μεταγλώττιση και εκτέλεση JIT.",
"en_US": "Directly map memory in the host address space. Much faster JIT compilation and execution.",
"es_ES": "Mapea la memoria directamente en la dirección de espacio del host. Compilación y ejecución JIT mucho más rápida.",
- "fr_FR": "Mappez directement la mémoire dans l'espace d'adresses de l'hôte. Compilation et exécution JIT beaucoup plus rapides.",
+ "fr_FR": "Mappe directement la mémoire dans l’espace d’adressage de l’hôte. Compilation JIT et exécution beaucoup plus rapides.",
"he_IL": "ממפה זיכרון ישירות לכתובת המארח. מהיר בהרבה ביכולות קימפול ה-JIT והריצה.",
"it_IT": "Mappa direttamente la memoria nello spazio degli indirizzi dell'host. Compilazione ed esecuzione JIT molto più veloce.",
"ja_JP": "ホストのアドレス空間にメモリを直接マップします.JITのコンパイルと実行速度が大きく向上します.",
@@ -16980,7 +16980,7 @@
"el_GR": "Απευθείας χαρτογράφηση της μνήμης, αλλά μην καλύπτετε τη διεύθυνση εντός του χώρου διευθύνσεων επισκέπτη πριν από την πρόσβαση. Πιο γρήγορα, αλλά με κόστος ασφάλειας. Η εφαρμογή μπορεί να έχει πρόσβαση στη μνήμη από οπουδήποτε στο Ryujinx, επομένως εκτελείτε μόνο προγράμματα που εμπιστεύεστε με αυτήν τη λειτουργία.",
"en_US": "Directly map memory, but do not mask the address within the guest address space before access. Faster, but at the cost of safety. The guest application can access memory from anywhere in Ryujinx, so only run programs you trust with this mode.",
"es_ES": "Mapea la memoria directamente, pero no enmascara la dirección dentro del espacio de dirección del guest antes del acceso. El modo más rápido, pero a costa de seguridad. La aplicación guest puede acceder a la memoria desde cualquier parte en Ryujinx, así que ejecuta solo programas en los que confíes cuando uses este modo.",
- "fr_FR": "Mapper directement la mémoire dans la carte, mais ne pas masquer l'adresse dans l'espace d'adressage du client avant l'accès. Plus rapide, mais la sécurité sera négligée. L'application peut accéder à la mémoire depuis n'importe où dans Ryujinx, donc exécutez uniquement les programmes en qui vous avez confiance avec ce mode.",
+ "fr_FR": "Mappe directement la mémoire, mais sans masquer l’adresse dans l’espace d’adressage invité avant l’accès. Plus rapide, mais au détriment de la sécurité. L’application invitée peut accéder à n’importe quelle zone mémoire de Ryujinx ; n’utilisez ce mode qu’avec des programmes de confiance.",
"he_IL": "ממפה זיכרון ישירות, אך לא ממסך את הכתובת בתוך כתובת המארח לפני הגישה. מהיר, אך במחיר של הגנה. יישום המארח בעל גישה לזיכרון מכל מקום בריוג'ינקס, לכן הריצו איתו רק קבצים שאתם סומכים עליהם.",
"it_IT": "Mappa direttamente la memoria, ma non maschera l'indirizzo all'interno dello spazio degli indirizzi guest prima dell'accesso. Più veloce, ma a costo della sicurezza. L'applicazione guest può accedere alla memoria da qualsiasi punto di Ryujinx, quindi esegui solo programmi di cui ti fidi con questa modalità.",
"ja_JP": "メモリを直接マップしますが, アクセス前にゲストのアドレス空間内のアドレスをマスクしません. より高速になりますが, 安全性が犠牲になります. ゲストアプリケーションは Ryujinx のどこからでもメモリにアクセスできるので,このモードでは信頼できるプログラムだけを実行するようにしてください.",
@@ -17005,7 +17005,7 @@
"el_GR": "Χρησιμοποιήστε Hypervisor αντί για JIT. Βελτιώνει σημαντικά την απόδοση όταν διατίθεται, αλλά μπορεί να είναι ασταθής στην τρέχουσα κατάστασή του.",
"en_US": "Use Hypervisor instead of JIT. Greatly improves performance when available, but can be unstable in its current state.",
"es_ES": "Usar Hypervisor en lugar de JIT. Mejora enormemente el rendimiento cuando está disponible, pero puede ser inestable en su estado actual.",
- "fr_FR": "Utiliser l'Hyperviseur au lieu du JIT. Améliore considérablement les performances lorsqu'il est disponible, mais peut être instable dans son état actuel.",
+ "fr_FR": "Utiliser l'Hyperviseur au lieu du JIT. Améliore considérablement les performances lorsque disponible, mais peut être instable dans son état actuel.",
"he_IL": "השתמש ב- Hypervisor במקום JIT. משפר מאוד ביצועים כשניתן, אבל יכול להיות לא יציב במצבו הנוכחי.",
"it_IT": "Usa Hypervisor invece di JIT. Migliora notevolmente le prestazioni quando disponibile, ma può essere instabile nel suo stato attuale.",
"ja_JP": "JIT の代わりにハイパーバイザーを使用します. 利用可能な場合, パフォーマンスが大幅に向上しますが, 現在の状態では不安定になる可能性があります.",
@@ -17030,7 +17030,7 @@
"el_GR": "Επεκτείνει την ποσότητα της μνήμης στο εξομοιούμενο σύστημα από 4 GiB σε 6 GiB",
"en_US": "Utilizes an alternative memory mode with 8GiB of DRAM to mimic a Switch development model.\n\nThis is only useful for higher-resolution texture packs or 4k resolution mods. Does NOT improve performance.\n\nLeave OFF if unsure.",
"es_ES": "Expande la memoria DRAM del sistema emulado de 4GiB a 6GiB.\n\nUtilizar solo con packs de texturas HD o mods de resolución 4K. NO mejora el rendimiento.\n\nDesactívalo si no sabes qué hacer.",
- "fr_FR": "Change le montant de DRAM qui est alloué.\n\nActivez cette option pour les packs de textures 4k ou les mods à résolution 4k.\nN'améliore pas les performances.\n\nLaissez à 4GiO en cas de doute.",
+ "fr_FR": "Utilise un mode mémoire alternatif avec 4, 6, 8 ou 12GiO de DRAM pour simuler un modèle de développement Switch.\n\nUtile uniquement pour les packs de textures haute résolution ou les mods en 4K.\nN’améliore PAS les performances.\n\nLaissez à 4GiO si vous n’êtes pas sûr.",
"he_IL": "מנצל תצורת מצב-זיכרון חלופית לחכות את מכשיר הפיתוח של הסוויץ'.\n\nזה שימושי להחלפת חבילות מרקמים באיכותיים יותר או כאלו ברזולוציית 4k. לא משפר ביצועים.\n\nמוטב להשאיר כבוי אם לא בטוחים.",
"it_IT": "Utilizza un layout di memoria alternativo per imitare un'unità di sviluppo di Switch.\n\nQuesta opzione è utile soltanto per i pacchetti di texture ad alta risoluzione o per le mod che aumentano la risoluzione a 4K. NON migliora le prestazioni.\n\nNel dubbio, lascia l'opzione disattivata.",
"ja_JP": "エミュレートされたシステムのメモリ容量を 4GiB から 6GiB に増加します.\n\n高解像度のテクスチャパックや 4K解像度の mod を使用する場合に有用です. パフォーマンスを改善するものではありません.\n\nよくわからない場合はオフのままにしてください.",
@@ -17055,7 +17055,7 @@
"el_GR": "Ενεργοποίηση ή απενεργοποίηση της αγνοώησης για υπηρεσίες που λείπουν",
"en_US": "Ignores unimplemented Horizon OS services. This may help in bypassing crashes when booting certain games.\n\nLeave OFF if unsure.",
"es_ES": "Hack para ignorar servicios no implementados del Horizon OS. Esto puede ayudar a sobrepasar crasheos cuando inicies ciertos juegos.\n\nDesactívalo si no sabes qué hacer.",
- "fr_FR": "Ignore les services Horizon OS non-intégrés. Cela peut aider à contourner les plantages lors du démarrage de certains jeux.\n\nLaissez désactivé en cas d'incertitude.",
+ "fr_FR": "Ignore les services Horizon OS non implémentés. Cela peut aider à éviter les plantages au démarrage de certains jeux.\n\nLaissez DÉSACTIVÉ si vous n’êtes pas sûr.",
"he_IL": "מתעלם מפעולות שלא קיבלו מימוש במערכת ההפעלה Horizon OS. זה עלול לעזור לעקוף קריסות של היישום במשחקים מסויימים.\n\nמוטב להשאיר כבוי אם לא בטוחים.",
"it_IT": "Ignora i servizi non implementati del sistema operativo Horizon. Può aiutare ad aggirare gli arresti anomali che si verificano avviando alcuni giochi.\n\nNel dubbio, lascia l'opzione disattivata.",
"ja_JP": "未実装の Horizon OS サービスを無視します. 特定のゲームにおいて起動時のクラッシュを回避できる場合があります.\n\nよくわからない場合はオフのままにしてください.",
@@ -17080,7 +17080,7 @@
"el_GR": "",
"en_US": "The Controller Applet dialog will not appear if the gamepad is disconnected while an application is running.\n\nLeave OFF if unsure.",
"es_ES": "",
- "fr_FR": "",
+ "fr_FR": "La fenêtre de l’applet manette ne s’affichera pas si la manette est déconnectée pendant l’exécution d’une application\n\nLaissez DÉSACTIVÉ si vous n’êtes pas sûr.",
"he_IL": "",
"it_IT": "",
"ja_JP": "",
@@ -17105,7 +17105,7 @@
"el_GR": "Αυτή η επιλογή παρακάμπτει το παράθυρο διαλόγου 'Διαχειριστής Προφίλ Χρήστη' κατά τη διάρκεια του παιχνιδιού, χρησιμοποιώντας ένα προεπιλεγμένο προφίλ.\n\nΗ εναλλαγή προφίλ βρίσκεται στις 'Ρυθμίσεις' - 'Διαχειριστής Προφίλ Χρήστη'. Επιλέξτε το επιθυμητό προφίλ πριν φορτώσετε το παιχνίδι.",
"en_US": "This option skips the 'Manage User Profiles' dialog during gameplay, using a pre-selected profile.\n\nProfile switching is found in 'Settings' - 'Manager User Profiles'. Select the desired profile before loading the game.",
"es_ES": "Esta opción omite el diálogo de 'Gestionar perfiles de usuario' durante el juego, utilizando un perfil preseleccionado.\n\nEl cambio de perfil se encuentra en 'Configuración' - 'Gestionar perfiles de usuario'. Seleccione el perfil deseado antes de cargar el juego.",
- "fr_FR": "Cette option permet d'éviter le dialogue du 'Gérer les profils d'utilisateurs' pendant le jeu, en utilisant un profil pré-sélectionné.\n\nLa sélection du profil se trouve dans 'Paramètres' - 'Gérer les profils d'utilisateurs'. Sélectionnez le profil souhaité avant de charger la partie.",
+ "fr_FR": "Cette option permet d'éviter le dialogue du 'Gérer les profils d'utilisateurs' pendant le jeu, en utilisant un profil pré-sélectionné.\n\nLa sélection du profil se trouve dans 'Paramètres' - 'Gérer les profils d'utilisateurs'. Sélectionnez le profil souhaité avant de lancer le jeu.",
"he_IL": "",
"it_IT": "Questa opzione salta la finestra di dialogo 'Gestisci i profili utente' durante il gioco, utilizzando un profilo pre-selezionato.\n\nIl cambio del profilo si trova in 'Impostazioni' - 'Gestisci i profili utente'. Seleziona il profilo desiderato prima di caricare il gioco.",
"ja_JP": "このオプションは、ゲームプレイ中に「ユーザプロファイルを管理」ダイアログをスキップし、事前に選択されたプロファイルを使用します。\n\nプロファイルの切り替えは、「設定」-「ユーザプロファイルを管理」で見つけることができます。ゲームのロード前に目的のプロファイルをを選択してください。",
@@ -17130,7 +17130,7 @@
"el_GR": "Ενεργοποίηση Πολυνηματικής Επεξεργασίας Γραφικών",
"en_US": "Executes graphics backend commands on a second thread.\n\nSpeeds up shader compilation, reduces stuttering, and improves performance on GPU drivers without multithreading support of their own. Slightly better performance on drivers with multithreading.\n\nSet to AUTO if unsure.",
"es_ES": "Ejecuta los comandos del motor gráfico en un segundo hilo. Acelera la compilación de sombreadores, reduce los tirones, y mejora el rendimiento en controladores gráficos que no realicen su propio procesamiento con múltiples hilos. Rendimiento ligeramente superior en controladores gráficos que soporten múltiples hilos.\n\nSelecciona \"Auto\" si no sabes qué hacer.",
- "fr_FR": "Exécute des commandes du backend graphiques sur un second thread.\n\nAccélère la compilation des shaders, réduit les crashs et les lags, améliore les performances sur les pilotes GPU sans support de multithreading. Légère augementation des performances sur les pilotes avec multithreading intégrer.\n\nRéglez sur Auto en cas d'incertitude.",
+ "fr_FR": "Exécute des commandes du backend graphiques sur un second thread.\n\nAccélère la compilation des shaders, réduit les crashs et les lags, améliore les performances sur les pilotes GPU sans support natif du multithreading. Offre une légère amélioration des performances sur les pilotes multithreadés.\n\nRéglez sur AUTO si vous n’êtes pas sûr.",
"he_IL": "מריץ פקודות גראפיקה בתהליך שני נפרד.\n\nמאיץ עיבוד הצללות, מפחית תקיעות ומשפר ביצועים של דרייבר כרטיסי מסך אשר לא תומכים בהרצה רב-תהליכית.\n\nמוטב להשאיר על אוטומטי אם לא בטוחים.",
"it_IT": "Esegue i comandi del backend grafico su un secondo thread.\n\nVelocizza la compilazione degli shader, riduce lo stuttering e migliora le prestazioni sui driver grafici senza il supporto integrato al multithreading. Migliora leggermente le prestazioni sui driver che supportano il multithreading.\n\nNel dubbio, imposta l'opzione su Automatico.",
"ja_JP": "グラフィックスバックエンドのコマンドを別スレッドで実行します.\n\nシェーダのコンパイルを高速化し, 遅延を軽減し, マルチスレッド非対応の GPU ドライバにおいてパフォーマンスを改善します. マルチスレッド対応のドライバでも若干パフォーマンス改善が見られます.\n\nよくわからない場合は自動に設定してください.",
@@ -17155,7 +17155,7 @@
"el_GR": "Εκτελεί εντολές γραφικών σε ένα δεύτερο νήμα. Επιτρέπει την πολυνηματική μεταγλώττιση Shader σε χρόνο εκτέλεσης, μειώνει το τρεμόπαιγμα και βελτιώνει την απόδοση των προγραμμάτων οδήγησης χωρίς τη δική τους υποστήριξη πολλαπλών νημάτων. Ποικίλες κορυφαίες επιδόσεις σε προγράμματα οδήγησης με multithreading. Μπορεί να χρειαστεί επανεκκίνηση του Ryujinx για να απενεργοποιήσετε σωστά την ενσωματωμένη λειτουργία πολλαπλών νημάτων του προγράμματος οδήγησης ή ίσως χρειαστεί να το κάνετε χειροκίνητα για να έχετε την καλύτερη απόδοση.",
"en_US": "Executes graphics backend commands on a second thread.\n\nSpeeds up shader compilation, reduces stuttering, and improves performance on GPU drivers without multithreading support of their own. Slightly better performance on drivers with multithreading.\n\nSet to AUTO if unsure.",
"es_ES": "Ejecuta los comandos del motor gráfico en un segundo hilo. Acelera la compilación de sombreadores, reduce los tirones, y mejora el rendimiento en controladores gráficos que no realicen su propio procesamiento con múltiples hilos. Rendimiento ligeramente superior en controladores gráficos que soporten múltiples hilos.\n\nSelecciona \"Auto\" si no sabes qué hacer.",
- "fr_FR": "Exécute des commandes du backend graphiques sur un second thread.\n\nAccélère la compilation des shaders, réduit les crashs et les lags, améliore les performances sur les pilotes GPU sans support de multithreading. Légère augementation des performances sur les pilotes avec multithreading intégrer.\n\nRéglez sur Auto en cas d'incertitude.",
+ "fr_FR": "Exécute des commandes du backend graphiques sur un second thread.\n\nAccélère la compilation des shaders, réduit les crashs et les lags, améliore les performances sur les pilotes GPU sans support natif du multithreading. Offre une légère amélioration des performances sur les pilotes multithreadés.\n\nRéglez sur AUTO si vous n’êtes pas sûr.",
"he_IL": "מריץ פקודות גראפיקה בתהליך שני נפרד.\n\nמאיץ עיבוד הצללות, מפחית תקיעות ומשפר ביצועים של דרייבר כרטיסי מסך אשר לא תומכים בהרצה רב-תהליכית.\n\nמוטב להשאיר על אוטומטי אם לא בטוחים.",
"it_IT": "Esegue i comandi del backend grafico su un secondo thread.\n\nVelocizza la compilazione degli shader, riduce lo stuttering e migliora le prestazioni sui driver grafici senza il supporto integrato al multithreading. Migliora leggermente le prestazioni sui driver che supportano il multithreading.\n\nNel dubbio, imposta l'opzione su Automatico.",
"ja_JP": "グラフィックスバックエンドのコマンドを別スレッドで実行します.\n\nシェーダのコンパイルを高速化し, 遅延を軽減し, マルチスレッド非対応の GPU ドライバにおいてパフォーマンスを改善します. マルチスレッド対応のドライバでも若干パフォーマンス改善が見られます.\n\nよくわからない場合は自動に設定してください.",
@@ -17180,7 +17180,7 @@
"el_GR": "Ενεργοποιεί ή απενεργοποιεί την Προσωρινή Μνήμη Shader",
"en_US": "Saves a disk shader cache which reduces stuttering in subsequent runs.\n\nLeave ON if unsure.",
"es_ES": "Guarda una caché de sombreadores en disco, la cual reduce los tirones a medida que vas jugando.\n\nActívalo si no sabes qué hacer.",
- "fr_FR": "Enregistre un cache de shaders sur le disque dur, réduit le lag lors de multiples exécutions.\n\nLaissez activé si vous n'êtes pas sûr.",
+ "fr_FR": "Sauvegarde un cache de shaders sur disque, ce qui réduit les saccades lors des exécutions suivantes.\n\nLaissez ACTIVÉ si vous n'êtes pas sûr.",
"he_IL": "שומר זכרון מטמון של הצללות, דבר שמפחית תקיעות בריצות מסוימות.\n\nמוטב להשאיר דלוק אם לא בטוחים.",
"it_IT": "Salva una cache degli shader su disco che riduce i fenomeni di stuttering nelle esecuzioni successive.\n\nNel dubbio, lascia l'opzione attiva.",
"ja_JP": "ディスクシェーダーキャッシュをセーブし,次回以降の実行時遅延を軽減します.\n\nよくわからない場合はオンのままにしてください.",
@@ -17205,7 +17205,7 @@
"el_GR": "",
"en_US": "Multiplies the game's rendering resolution.\n\nA few games may not work with this and look pixelated even when the resolution is increased; for those games, you may need to find mods that remove anti-aliasing or that increase their internal rendering resolution. For using the latter, you'll likely want to select Native.\n\nThis option can be changed while a game is running by clicking \"Apply\" below; you can simply move the settings window aside and experiment until you find your preferred look for a game.\n\nKeep in mind 4x is overkill for virtually any setup.",
"es_ES": "Multiplica la resolución de rendereo del juego.\n\nAlgunos juegos podrían no funcionar con esto y verse pixelado al aumentar la resolución; en esos casos, quizás sería necesario buscar mods que de anti-aliasing o que aumenten la resolución interna. Para usar este último, probablemente necesitarás seleccionar Nativa.\n\nEsta opción puede ser modificada mientras que un juego este corriendo haciendo click en \"Aplicar\" más abajo; simplemente puedes mover la ventana de configuración a un lado y experimentar hasta que encuentres tu estilo preferido para un juego.\n\nTener en cuenta que 4x es excesivo para prácticamente cualquier configuración.",
- "fr_FR": "Multiplie la résolution de rendu du jeu.\n\nQuelques jeux peuvent ne pas fonctionner avec cette fonctionnalité et sembler pixelisés même lorsque la résolution est augmentée ; pour ces jeux, vous devrez peut-être trouver des mods qui suppriment l'anti-aliasing ou qui augmentent leur résolution de rendu interne. Pour utiliser cette dernière option, vous voudrez probablement sélectionner \"Natif\".\n\nCette option peut être modifiée pendant qu'un jeu est en cours d'exécution en cliquant sur \"Appliquer\" ci-dessous ; vous pouvez simplement déplacer la fenêtre des paramètres sur le côté et expérimenter jusqu'à ce que vous trouviez l'apparence souhaitée pour un jeu.\n\nGardez à l'esprit que 4x est excessif pour pratiquement n'importe quelle configuration.",
+ "fr_FR": "Multiplie la résolution de rendu du jeu.\n\nQuelques jeux peuvent ne pas fonctionner avec cette fonctionnalité et apparaître pixelisés même lorsque la résolution est augmentée ; pour ces jeux, vous devrez peut-être trouver des mods qui suppriment l'anti-aliasing ou qui augmentent leur résolution de rendu interne. Pour utiliser cette dernière option, vous voudrez probablement sélectionner \"Natif\".\n\nCette option peut être modifiée pendant qu'un jeu est en cours d'exécution en cliquant sur \"Appliquer\" ci-dessous ; vous pouvez simplement déplacer la fenêtre des paramètres sur le côté et expérimenter jusqu'à ce que vous trouviez l'apparence souhaitée pour un jeu.\n\nGardez à l'esprit que 4x est excessif pour pratiquement n'importe quelle configuration.",
"he_IL": "",
"it_IT": "Moltiplica la risoluzione di rendering del gioco.\n\nAlcuni giochi potrebbero non funzionare con questa opzione e sembrare pixelati anche quando la risoluzione è aumentata; per quei giochi, potrebbe essere necessario trovare mod che rimuovono l'anti-aliasing o che aumentano la risoluzione di rendering interna. Per quest'ultimo caso, probabilmente dovrai selezionare Nativo (1x).\n\nQuesta opzione può essere modificata mentre un gioco è in esecuzione facendo clic su \"Applica\" qui sotto; puoi semplicemente spostare la finestra delle impostazioni da parte e sperimentare fino a quando non trovi il tuo look preferito.\n\nTieni presente che \"4x\" è eccessivo per praticamente qualsiasi configurazione.",
"ja_JP": "ゲームのレンダリング解像度倍率を設定します.\n\n解像度を上げてもピクセルのように見えるゲームもあります. そのようなゲームでは, アンチエイリアスを削除するか, 内部レンダリング解像度を上げる mod を見つける必要があるかもしれません. その場合, ようなゲームでは、ネイティブを選択してください.\n\nこのオプションはゲーム実行中に下の「適用」をクリックすることで変更できます. 設定ウィンドウを脇に移動して, ゲームが好みの表示になるよう試してみてください.\n\nどのような設定でも, \"4x\" はやり過ぎであることを覚えておいてください.",
@@ -17305,7 +17305,7 @@
"el_GR": "Τοποθεσία Εναπόθεσης Προσωρινής Μνήμης Shaders",
"en_US": "Graphics Shaders Dump Path",
"es_ES": "Directorio en el cual se volcarán los sombreadores de los gráficos",
- "fr_FR": "Chemin de copie des Shaders :",
+ "fr_FR": "Chemin de sauvegarde des shaders graphiques",
"he_IL": "נתיב השלכת הצללות גראפיות",
"it_IT": "Percorso di dump degli shader",
"ja_JP": "グラフィックス シェーダー ダンプのパスです",
@@ -17330,7 +17330,7 @@
"el_GR": "Ενεργοποιεί ή απενεργοποιεί την καταγραφή σε ένα αρχείο στο δίσκο",
"en_US": "Saves console logging to a log file on disk. Does not affect performance.",
"es_ES": "Guarda los registros de la consola en archivos en disco. No afectan al rendimiento.",
- "fr_FR": "Sauvegarde le journal de la console dans un fichier journal sur le disque. Cela n'affecte pas les performances.",
+ "fr_FR": "Enregistre les journaux de la console dans un fichier sur le disque. N’affecte pas les performances.",
"he_IL": "שומר את רישומי שורת הפקודות לזיכרון, לא משפיע על ביצועי היישום.",
"it_IT": "Salva il log della console in un file su disco. Non influisce sulle prestazioni.",
"ja_JP": "コンソール出力されるログをディスク上のログファイルにセーブします. パフォーマンスには影響を与えません.",
@@ -17355,7 +17355,7 @@
"el_GR": "Ενεργοποιεί την εκτύπωση μηνυμάτων καταγραφής ατελειών",
"en_US": "Prints stub log messages in the console. Does not affect performance.",
"es_ES": "Escribe mensajes de Stub en la consola. No afectan al rendimiento.",
- "fr_FR": "Affiche les messages de journaux dans la console. N'affecte pas les performances.",
+ "fr_FR": "Affiche dans la console les journaux stub. N’affecte pas les performances.",
"he_IL": "מדפיס רישומים כושלים לשורת הפקודות. לא משפיע על ביצועי היישום.",
"it_IT": "Stampa i messaggi di log relativi alle stub nella console. Non influisce sulle prestazioni.",
"ja_JP": "stub ログメッセージをコンソールに出力します. パフォーマンスには影響を与えません.",
@@ -17380,7 +17380,7 @@
"el_GR": "Ενεργοποιεί την εκτύπωση μηνυμάτων αρχείου καταγραφής πληροφοριών",
"en_US": "Prints info log messages in the console. Does not affect performance.",
"es_ES": "Escribe mensajes de Info en la consola. No afectan al rendimiento.",
- "fr_FR": "Affiche les messages de journaux d'informations dans la console. N'affecte pas les performances.",
+ "fr_FR": "Affiche dans la console les journaux info. N’affecte pas les performances.",
"he_IL": "מדפיק רישומי מידע לשורת הפקודות. לא משפיע על ביצועי היישום.",
"it_IT": "Stampa i messaggi di log informativi nella console. Non influisce sulle prestazioni.",
"ja_JP": "info ログメッセージをコンソールに出力します. パフォーマンスには影響を与えません.",
@@ -17405,7 +17405,7 @@
"el_GR": "Ενεργοποιεί την εκτύπωση μηνυμάτων καταγραφής προειδοποιήσεων",
"en_US": "Prints warning log messages in the console. Does not affect performance.",
"es_ES": "Escribe mensajes de Advertencia en la consola. No afectan al rendimiento.",
- "fr_FR": "Affiche les messages d'avertissement dans la console. N'affecte pas les performances.",
+ "fr_FR": "Affiche dans la console les journaux d’avertissement. N’affecte pas les performances.",
"he_IL": "מדפיק רישומי הערות לשורת הפקודות. לא משפיע על ביצועי היישום.",
"it_IT": "Stampa i messaggi di log relativi agli avvisi nella console. Non influisce sulle prestazioni.",
"ja_JP": "warning ログメッセージをコンソールに出力します. パフォーマンスには影響を与えません.",
@@ -17430,7 +17430,7 @@
"el_GR": "Ενεργοποιεί την εκτύπωση μηνυμάτων αρχείου καταγραφής σφαλμάτων",
"en_US": "Prints error log messages in the console. Does not affect performance.",
"es_ES": "Escribe mensajes de Error en la consola. No afectan al rendimiento.",
- "fr_FR": "Affiche les messages de journaux d'erreur dans la console. N'affecte pas les performances.",
+ "fr_FR": "Affiche dans la console les journaux d’erreur. N’affecte pas les performances.",
"he_IL": "מדפיס רישומי שגיאות לשורת הפקודות. לא משפיע על ביצועי היישום.",
"it_IT": "Stampa i messaggi di log relativi agli errori nella console. Non influisce sulle prestazioni.",
"ja_JP": "error ログメッセージをコンソールに出力します. パフォーマンスには影響を与えません.",
@@ -17455,7 +17455,7 @@
"el_GR": "Ενεργοποιεί την εκτύπωση μηνυμάτων αρχείου καταγραφής ιχνών",
"en_US": "Prints trace log messages in the console. Does not affect performance.",
"es_ES": "Escribe mensajes de Rastro en la consola. No afectan al rendimiento.",
- "fr_FR": "Affiche la trace des messages de journaux dans la console. N'affecte pas les performances.",
+ "fr_FR": "Affiche dans la console les journaux de trace. N’affecte pas les performances.",
"he_IL": "מדפיק רישומי זיכרון לשורת הפקודות. לא משפיע על ביצועי היישום.",
"it_IT": "Stampa i messaggi di log relativi al trace nella console. Non influisce sulle prestazioni.",
"ja_JP": "trace ログメッセージをコンソールに出力します. パフォーマンスには影響を与えません.",
@@ -17480,7 +17480,7 @@
"el_GR": "Ενεργοποιεί την εκτύπωση μηνυμάτων καταγραφής επισκεπτών",
"en_US": "Prints guest log messages in the console. Does not affect performance.",
"es_ES": "Escribe mensajes de Guest en la consola. No afectan al rendimiento.",
- "fr_FR": "Affiche les messages de journaux des invités dans la console. N'affecte pas les performances.",
+ "fr_FR": "Affiche dans la console les journaux du système invité. N’affecte pas les performances.",
"he_IL": "מדפיס רישומי אורח לשורת הפקודות. לא משפיע על ביצועי היישום.",
"it_IT": "Stampa i messaggi di log del guest nella console. Non influisce sulle prestazioni.",
"ja_JP": "guest ログメッセージをコンソールに出力します. パフォーマンスには影響を与えません.",
@@ -17505,7 +17505,7 @@
"el_GR": "Ενεργοποιεί την εκτύπωση μηνυμάτων αρχείου καταγραφής πρόσβασης",
"en_US": "Prints file access log messages in the console.",
"es_ES": "Activa mensajes de acceso a archivo en la consola",
- "fr_FR": "Affiche les messages de journaux d'accès aux fichiers dans la console.",
+ "fr_FR": "Affiche dans la console les journaux d’accès aux fichiers.",
"he_IL": "מדפיס גישות לקבצי רישום לשורת הפקודות.",
"it_IT": "Stampa i messaggi di log relativi all'accesso ai file nella console.",
"ja_JP": "ファイルアクセスログメッセージをコンソールに出力します.",
@@ -17530,7 +17530,7 @@
"el_GR": "Ενεργοποιεί την έξοδο καταγραφής πρόσβασης FS στην κονσόλα. Οι πιθανοί τρόποι λειτουργίας είναι 0-3",
"en_US": "Enables FS access log output to the console. Possible modes are 0-3",
"es_ES": "Activa registros FS Access en la consola. Los modos posibles son entre 0 y 3",
- "fr_FR": "Active la sortie du journal d'accès FS de la console. Les modes possibles sont 0-3",
+ "fr_FR": "Active l’affichage des journaux d’accès FS dans la console. Les modes possibles sont de 0 à 3.",
"he_IL": "מאפשר גישה לרישומי FS ליציאת שורת הפקודות. האפשרויות הינן 0-3.",
"it_IT": "Attiva l'output dei log di accesso FS nella console. Le modalità possibili vanno da 0 a 3",
"ja_JP": "コンソールへのファイルシステムアクセスログ出力を有効にします.0-3 のモードが有効です",
@@ -17580,7 +17580,7 @@
"el_GR": "",
"en_US": "Prints Avalonia (UI) log messages in the console.",
"es_ES": "",
- "fr_FR": "",
+ "fr_FR": "Affiche les journaux Avalonia (UI) dans la console.",
"he_IL": "",
"it_IT": "",
"ja_JP": "",
@@ -17630,7 +17630,7 @@
"el_GR": "Ενεργοποιεί την εκτύπωση μηνυμάτων αρχείου καταγραφής εντοπισμού σφαλμάτων",
"en_US": "Prints debug log messages in the console.\n\nOnly use this if specifically instructed by a staff member, as it will make logs difficult to read and worsen emulator performance.",
"es_ES": "Escribe mensajes de debug en la consola\n\nActiva esto solo si un miembro del equipo te lo pide expresamente, pues hará que el registro sea difícil de leer y empeorará el rendimiento del emulador.",
- "fr_FR": "Affiche les messages de débogage dans la console.\n\nN'utilisez ceci que si un développeur le demande, car cela rendra les logs difficiles à lire et réduit les performances de l'émulateur.",
+ "fr_FR": "Affiche dans la console les journaux de débogage.\n\nN’utilisez cette option que si un membre du personnel vous l’a expressément demandé, car cela rendra les journaux difficiles à lire et dégradera les performances de l’émulateur.",
"he_IL": "מדפיס הודעות יומן ניפוי באגים בשורת הפקודות.",
"it_IT": "Stampa i messaggi di log per il debug nella console.\n\nUsa questa opzione solo se specificatamente richiesto da un membro del team, dal momento che rende i log difficili da leggere e riduce le prestazioni dell'emulatore.",
"ja_JP": "デバッグログメッセージをコンソールに出力します.\n\nログが読みづらくなり,エミュレータのパフォーマンスが低下するため,開発者から特別な指示がある場合のみ使用してください.",
@@ -17655,7 +17655,7 @@
"el_GR": "Ανοίξτε έναν επιλογέα αρχείων για να επιλέξετε ένα αρχείο συμβατό με το Switch για φόρτωση",
"en_US": "Open a file explorer to choose a Switch compatible file to load",
"es_ES": "Abre el explorador de archivos para elegir un archivo compatible con Switch para cargar",
- "fr_FR": "Ouvre l'explorateur de fichiers pour choisir un fichier compatible Switch à charger",
+ "fr_FR": "Ouvre un explorateur de fichiers pour choisir un fichier compatible Switch à charger.",
"he_IL": "פתח סייר קבצים כדי לבחור קובץ תואם סוויץ' לטעינה",
"it_IT": "Apri un selettore file per scegliere un file compatibile con Switch da caricare",
"ja_JP": "ロードする Switch 互換のファイルを選択するためファイルエクスプローラを開きます",
@@ -17680,7 +17680,7 @@
"el_GR": "Ανοίξτε έναν επιλογέα αρχείων για να επιλέξετε μία μη συσκευασμένη εφαρμογή, συμβατή με το Switch για φόρτωση",
"en_US": "Open a file explorer to choose a Switch compatible, unpacked application to load",
"es_ES": "Abre el explorador de archivos para elegir un archivo desempaquetado y compatible con Switch para cargar",
- "fr_FR": "Ouvre l'explorateur de fichiers pour choisir une application Switch compatible et décompressée à charger",
+ "fr_FR": "Ouvre un explorateur de fichiers pour choisir une application dépaquetée compatible Switch à charger.",
"he_IL": "פתח סייר קבצים כדי לבחור יישום תואם סוויץ', לא ארוז לטעינה.",
"it_IT": "Apri un selettore file per scegliere un'applicazione estratta compatibile con Switch da caricare",
"ja_JP": "ロードする Switch 互換の展開済みアプリケーションを選択するためファイルエクスプローラを開きます",
@@ -17705,7 +17705,7 @@
"el_GR": "",
"en_US": "Open a file explorer to choose one or more folders to bulk load DLC from",
"es_ES": "Abrir un explorador de archivos para seleccionar una o más carpetas para cargar DLC de forma masiva",
- "fr_FR": "Ouvre l'explorateur de fichier pour choisir un ou plusieurs dossiers duquel charger les DLC",
+ "fr_FR": "Ouvre un explorateur de fichiers pour choisir un ou plusieurs dossiers afin de charger en masse des DLC",
"he_IL": "",
"it_IT": "Apri un selettore file per scegliere una o più cartelle dalle quali caricare DLC in blocco",
"ja_JP": "",
@@ -17730,7 +17730,7 @@
"el_GR": "",
"en_US": "Open a file explorer to choose one or more folders to bulk load title updates from",
"es_ES": "Abrir un explorador de archivos para seleccionar una o más carpetas para cargar actualizaciones de título de forma masiva",
- "fr_FR": "Ouvre l'explorateur de fichier pour choisir un ou plusieurs dossiers duquel charger les mises à jour",
+ "fr_FR": "Ouvre un explorateur de fichiers pour choisir un ou plusieurs dossiers afin de charger en masse des mises à jour de titre.",
"he_IL": "",
"it_IT": "Apri un selettore file per scegliere una o più cartelle dalle quali caricare aggiornamenti in blocco",
"ja_JP": "",
@@ -17780,7 +17780,7 @@
"el_GR": "",
"en_US": "Open Ryujinx screenshots folder",
"es_ES": "",
- "fr_FR": "",
+ "fr_FR": "Ouvrir le dossier des captures d’écran Ryujinx",
"he_IL": "",
"it_IT": "",
"ja_JP": "",
@@ -17805,7 +17805,7 @@
"el_GR": "Ανοίξτε το φάκελο στον οποίο διατηρούνται τα αρχεία καταγραφής",
"en_US": "Opens the folder where logs are written to",
"es_ES": "Abre la carpeta en la que se guardan los registros",
- "fr_FR": "Ouvre le dossier dans lequel les journaux sont écrits",
+ "fr_FR": "Ouvre le dossier où les journaux sont enregistrés",
"he_IL": "פותח את התיקיה שאליה נכתבים רישומים",
"it_IT": "Apre la cartella dove vengono salvati i log",
"ja_JP": "ログが格納されるフォルダを開きます",
@@ -17855,7 +17855,7 @@
"el_GR": "Ανοίξτε το παράθυρο Ρυθμίσεων",
"en_US": "Open settings window",
"es_ES": "Abre la ventana de configuración",
- "fr_FR": "Ouvrir la fenêtre de configuration",
+ "fr_FR": "Ouvrir la fenêtre des paramètres",
"he_IL": "פתח את חלון ההגדרות",
"it_IT": "Apri la finestra delle impostazioni",
"ja_JP": "設定ウインドウを開きます",
@@ -18580,7 +18580,7 @@
"el_GR": "",
"en_US": "Update Available!",
"es_ES": "",
- "fr_FR": "",
+ "fr_FR": "Mise à jour disponible !",
"he_IL": "",
"it_IT": "",
"ja_JP": "",
@@ -18705,7 +18705,7 @@
"el_GR": "Σφάλμα κατά την εκκαθάριση του shader cache στο {0}: {1}",
"en_US": "Error purging shader cache at {0}: {1}",
"es_ES": "Error al eliminar la caché de sombreadores en {0}: {1}",
- "fr_FR": "Erreur lors de la purge des Shaders à {0}: {1}",
+ "fr_FR": "Erreur lors de la purge du cache des shaders à {0} : {1}",
"he_IL": "שגיאה בניקוי מטמון ההצללות ב-{0}: {1}",
"it_IT": "Errore nell'eliminazione della cache degli shader a {0}: {1}",
"ja_JP": "シェーダーキャッシュの破棄エラー {0}: {1}",
@@ -18730,7 +18730,7 @@
"el_GR": "Τα κλειδιά δεν βρέθηκαν",
"en_US": "Keys not found",
"es_ES": "No se encontraron keys",
- "fr_FR": "Clés introuvables",
+ "fr_FR": "Clés non trouvées",
"he_IL": "המפתחות לא נמצאו",
"it_IT": "Chiavi non trovate",
"ja_JP": "Keys がありません",
@@ -18805,7 +18805,7 @@
"el_GR": "Η εφαρμογή δε βρέθηκε",
"en_US": "Application not found",
"es_ES": "No se encontró la aplicación",
- "fr_FR": " Application introuvable",
+ "fr_FR": "Application introuvable",
"he_IL": "יישום לא נמצא",
"it_IT": "Applicazione non trovata",
"ja_JP": "アプリケーションがありません",
@@ -19105,7 +19105,7 @@
"el_GR": "",
"en_US": "Bundled: Version {0}",
"es_ES": "Incorporado: Versión {0}",
- "fr_FR": "Inclus avec le jeu: Version {0}",
+ "fr_FR": "Inclus : Version {0}",
"he_IL": "",
"it_IT": "Incluso: Versione {0}",
"ja_JP": "",
@@ -19130,7 +19130,7 @@
"el_GR": "",
"en_US": "Bundled:",
"es_ES": "Incorporado:",
- "fr_FR": "Inclus avec le jeu :",
+ "fr_FR": "Inclus :",
"he_IL": "",
"it_IT": "Incluso:",
"ja_JP": "",
@@ -19255,7 +19255,7 @@
"el_GR": "",
"en_US": "Save {0:n0} Mb",
"es_ES": "Ahorra {0:n0} Mb",
- "fr_FR": "Sauvegarde de {0:n0} Mo",
+ "fr_FR": "Économiser {0:n0} Mo",
"he_IL": "",
"it_IT": "Risparmia {0:n0} MB",
"ja_JP": "",
@@ -19280,7 +19280,7 @@
"el_GR": "",
"en_US": "Saved {0:n0} Mb",
"es_ES": "{0:n0} Mb ahorrado(s)",
- "fr_FR": "Sauvegardé {0:n0} Mo",
+ "fr_FR": "Économisé {0:n0} Mo",
"he_IL": "",
"it_IT": "Risparmiati {0:n0} MB",
"ja_JP": "",
@@ -19855,7 +19855,7 @@
"el_GR": "Προσκολλημένο",
"en_US": "Docked",
"es_ES": "Dock/TV",
- "fr_FR": "Mode station d'accueil",
+ "fr_FR": "Mode Docké",
"he_IL": "בתחנת עגינה",
"it_IT": "TV",
"ja_JP": "ドッキング",
@@ -20080,7 +20080,7 @@
"el_GR": "Επιλέξτε ένα υποστηριζόμενο αρχείο για άνοιγμα",
"en_US": "Select a supported file to open",
"es_ES": "Selecciona un archivo soportado para cargar",
- "fr_FR": "Sélectionnez un fichier supporté à ouvrir",
+ "fr_FR": "Sélectionnez un fichier compatible à ouvrir",
"he_IL": "בחר קובץ נתמך לפתיחה",
"it_IT": "Seleziona un file supportato da aprire",
"ja_JP": "開くファイルを選択",
@@ -20355,7 +20355,7 @@
"el_GR": "Ρυθμίσεις Ελέγχου Κίνησης",
"en_US": "Motion Control Settings",
"es_ES": "Opciones de controles de movimiento",
- "fr_FR": "Réglages des contrôles par mouvement",
+ "fr_FR": "Paramètres du contrôle de mouvement",
"he_IL": "הגדרות שליטת תנועות ג'ירוסקופ",
"it_IT": "Impostazioni dei sensori di movimento",
"ja_JP": "モーションコントロール設定",
@@ -20405,7 +20405,7 @@
"el_GR": "",
"en_US": "LED Settings",
"es_ES": "",
- "fr_FR": "",
+ "fr_FR": "Paramètres LED",
"he_IL": "",
"it_IT": "",
"ja_JP": "",
@@ -20430,7 +20430,7 @@
"el_GR": "Επιλογή Αρχείου Θέματος",
"en_US": "Select Theme File",
"es_ES": "Selecciona un archivo de tema",
- "fr_FR": "Sélectionner un fichier de thème",
+ "fr_FR": "Sélectionnez un fichier de thème",
"he_IL": "בחרו קובץ ערכת נושא",
"it_IT": "Seleziona file del tema",
"ja_JP": "テーマファイルを選択",
@@ -20605,7 +20605,7 @@
"el_GR": "Επιλογή αρχείων DLC",
"en_US": "Select DLC files",
"es_ES": "Selecciona archivo(s) de DLC",
- "fr_FR": "Sélectionner les fichiers DLC",
+ "fr_FR": "Sélectionnez les fichiers DLC",
"he_IL": "בחרו קבצי הרחבות משחק",
"it_IT": "Seleziona file dei DLC",
"ja_JP": "DLC ファイルを選択",
@@ -20630,7 +20630,7 @@
"el_GR": "Επιλογή αρχείων ενημέρωσης",
"en_US": "Select update files",
"es_ES": "Selecciona archivo(s) de actualización",
- "fr_FR": "Sélectionner les fichiers de mise à jour",
+ "fr_FR": "Sélectionnez les fichiers de mise à jour",
"he_IL": "בחרו קבצי עדכון",
"it_IT": "Seleziona file di aggiornamento",
"ja_JP": "アップデートファイルを選択",
@@ -20655,7 +20655,7 @@
"el_GR": "",
"en_US": "Select mod directory",
"es_ES": "Seleccionar un directorio de Mods",
- "fr_FR": "Sélectionner le répertoire du mod",
+ "fr_FR": "Sélectionnez le répertoire du mod",
"he_IL": "בחר תיקיית מודים",
"it_IT": "Seleziona cartella delle mod",
"ja_JP": "modディレクトリを選択",
@@ -20705,7 +20705,7 @@
"el_GR": "",
"en_US": "This function will first check the empty space and then trim the XCI File to save disk space.",
"es_ES": "Esta función verificará el espacio vacío y después recortará el archivo XCI para ahorrar espacio en disco",
- "fr_FR": "Cette fonction va vérifier l'espace vide, puis réduire le fichier XCI pour économiser de l'espace de disque dur.",
+ "fr_FR": "Cette fonction vérifie d’abord l’espace libre, puis réduit le fichier XCI pour économiser de l’espace disque.",
"he_IL": "",
"it_IT": "Questa funzionalità controllerà prima lo spazio libero e poi ridurrà la dimensione del file XCI per risparmiare spazio su disco.",
"ja_JP": "",
@@ -20730,7 +20730,7 @@
"el_GR": "",
"en_US": "Current File Size: {0:n} MB\nGame Data Size: {1:n} MB\nDisk Space Savings: {2:n} MB",
"es_ES": "Tamaño de archivo actual: {0:n} MB\nTamaño de datos de juego: {1:n} MB\nAhorro de espacio en disco: {2:n} MB",
- "fr_FR": "Taille actuelle du fichier: {0:n} MB\nTaille des données de jeux: {1:n} MB\nÉconomie d'espaces sur le disque: {2:n} MB",
+ "fr_FR": "Taille actuelle du fichier: {0:n} MB\nTaille des données du jeu: {1:n} MB\nEspace disque économisé: {2:n} MB",
"he_IL": "",
"it_IT": "Dimensione attuale del file: {0:n} MB\nDimensione dei dati del gioco: {1:n} MB\nRisparmio spazio su disco: {2:n} MB",
"ja_JP": "",
@@ -20755,7 +20755,7 @@
"el_GR": "",
"en_US": "XCI File does not need to be trimmed. Check logs for further details",
"es_ES": "El archivo XCI no necesita ser recortado. Verifica los logs para más detalles.",
- "fr_FR": "Fichier XCI n'a pas besoin d'être réduit. Regarder les journaux pour plus de détails",
+ "fr_FR": "Le fichier XCI n’a pas besoin d’être réduit. Référez-vous aux journaux pour plus de détails.",
"he_IL": "",
"it_IT": "Non è necessario ridurre la dimensione del file XCI. Controlla i log per ulteriori dettagli",
"ja_JP": "",
@@ -20780,7 +20780,7 @@
"el_GR": "",
"en_US": "XCI File cannot be untrimmed. Check logs for further details",
"es_ES": "El recorte del archivo XCI no puede ser deshecho. Verifica los registros para más detalles.",
- "fr_FR": "Fichier XCI ne peut pas être dé-réduit. Regarder les journaux pour plus de détails",
+ "fr_FR": "Fichier XCI ne peut pas être dé-réduit. Référez-vous aux journaux pour plus de détails",
"he_IL": "",
"it_IT": "Il file XCI non può essere riportato alla sua dimensione originale. Controlla i log per ulteriori dettagli",
"ja_JP": "",
@@ -20805,7 +20805,7 @@
"el_GR": "",
"en_US": "XCI File is Read Only and could not be made writable. Check logs for further details",
"es_ES": "El archivo XCI es de solo Lectura y no se le puede escribir. Lee el registro para más información.",
- "fr_FR": "Fichier XCI est en Lecture Seule et n'a pas pu être rendu accessible en écriture. Regarder les journaux pour plus de détails",
+ "fr_FR": "Fichier XCI est en Lecture Seule et n'a pas pu être rendu accessible en écriture. Référez-vous aux journaux pour plus de détails",
"he_IL": "",
"it_IT": "Il file XCI è in sola lettura e non può essere reso accessibile in scrittura. Controlla i log per ulteriori dettagli",
"ja_JP": "",
@@ -20830,7 +20830,7 @@
"el_GR": "",
"en_US": "XCI File has changed in size since it was scanned. Please check the file is not being written to and try again.",
"es_ES": "El archivo XCI ha cambiado de tamaño desde que fue escaneado. Verifica que no se esté escribiendo al archivo y vuelve a intentarlo.",
- "fr_FR": "Fichier XCI a changé en taille depuis qu'il a été scanné. Vérifier que le fichier n'est pas en cours d'écriture et réessayer.",
+ "fr_FR": "La taille du fichier XCI a changé depuis son analyse. Veuillez vérifier que le fichier n’est pas en cours d’écriture, puis réessayez.",
"he_IL": "",
"it_IT": "La dimensione del file XCI è cambiata da quando è stato scansionato. Controlla che il file non stia venendo scritto da qualche altro programma e poi riprova.",
"ja_JP": "",
@@ -21130,7 +21130,7 @@
"el_GR": "",
"en_US": "XCI File Trimmer",
"es_ES": "Recortador de archivos XCI",
- "fr_FR": "Rogneur de fichier XCI",
+ "fr_FR": "Réducteur de fichiers XCI",
"he_IL": "",
"it_IT": "Riduci dimensioni dei file XCI",
"ja_JP": "",
@@ -21280,7 +21280,7 @@
"el_GR": "",
"en_US": "Potential Savings",
"es_ES": "Ahorro potencial",
- "fr_FR": "Économies potentielles d'espace de disque dur",
+ "fr_FR": "Économies Potentielles",
"he_IL": "",
"it_IT": "Risparmio potenziale",
"ja_JP": "",
@@ -21305,7 +21305,7 @@
"el_GR": "",
"en_US": "Actual Savings",
"es_ES": "Ahorro real",
- "fr_FR": "Économies actualles d'espace de disque dur",
+ "fr_FR": "Économies Réelles",
"he_IL": "",
"it_IT": "Risparmio effettivo",
"ja_JP": "",
@@ -21605,7 +21605,7 @@
"el_GR": "",
"en_US": "Bundled DLC cannot be removed, only disabled.",
"es_ES": "",
- "fr_FR": "Les DLC inclus avec le jeu ne peuvent pas être supprimés mais peuvent être désactivés.",
+ "fr_FR": "Le DLC inclus ne peut pas être supprimé, seulement désactivé.",
"he_IL": "",
"it_IT": "I DLC inclusi non possono essere rimossi, ma solo disabilitati.",
"ja_JP": "",
@@ -22130,7 +22130,7 @@
"el_GR": "User Id:",
"en_US": "User ID:",
"es_ES": "Id de Usuario:",
- "fr_FR": "Identifiant de l'utilisateur :",
+ "fr_FR": "Identifiant utilisateur : ",
"he_IL": "מזהה משתמש:",
"it_IT": "ID utente:",
"ja_JP": "ユーザID:",
@@ -22180,7 +22180,7 @@
"el_GR": "",
"en_US": "Select the graphics backend that will be used in the emulator.\n\nVulkan is overall better for all modern graphics cards, as long as their drivers are up to date. Vulkan also features faster shader compilation (less stuttering) on all GPU vendors.\n\nOpenGL may achieve better results on old Nvidia GPUs, on old AMD GPUs on Linux, or on GPUs with lower VRAM, though shader compilation stutters will be greater.\n\nSet to Vulkan if unsure. Set to OpenGL if your GPU does not support Vulkan even with the latest graphics drivers.",
"es_ES": "Seleccione el backend gráfico que utilizará el emulador.\n\nVulkan, en general, es mejor para todas las tarjetas gráficas modernas, mientras que sus controladores estén actualizados. Vulkan también cuenta con complicación más rápida de sombreadores (menos tirones) en todos los proveredores de GPU.\n\nOpenGL puede lograr mejores resultados en GPU Nvidia antiguas, GPU AMD antiguas en Linux o en GPUs con menor VRAM, aunque tirones de compilación de sombreadores serán mayores.\n\nSetear en Vulkan si no sabe que hacer. Setear en OpenGL si su GPU no tiene soporte para Vulkan aún con los últimos controladores gráficos.",
- "fr_FR": "Sélectionnez le moteur graphique qui sera utilisé dans l'émulateur.\n\nVulkan est globalement meilleur pour toutes les cartes graphiques modernes, tant que leurs pilotes sont à jour. Vulkan offre également une compilation de shaders plus rapide (moins de saccades) sur tous les fournisseurs de GPU.\n\nOpenGL peut obtenir de meilleurs résultats sur d'anciennes cartes graphiques Nvidia, sur d'anciennes cartes graphiques AMD sous Linux, ou sur des GPU avec moins de VRAM, bien que les saccades dues à la compilation des shaders soient plus importantes.\n\nRéglez sur Vulkan si vous n'êtes pas sûr. Réglez sur OpenGL si votre GPU ne prend pas en charge Vulkan même avec les derniers pilotes graphiques.",
+ "fr_FR": "Sélectionnez le moteur graphique à utiliser dans l’émulateur.\n\nVulkan est généralement préférable pour toutes les cartes graphiques modernes, à condition que leurs pilotes soient à jour. Vulkan offre également une compilation des shaders plus rapide (moins de saccades) chez tous les fabricants de GPU.\n\nOpenGL peut donner de meilleurs résultats sur d’anciennes cartes Nvidia, sur de vieux GPU AMD sous Linux, ou sur des GPU avec moins de mémoire vidéo, mais la compilation des shaders provoquera davantage de saccade\n\nChoisissez Vulkan si vous n’êtes pas sûr. Optez pour OpenGL si votre GPU ne prend pas en charge Vulkan, même avec les derniers pilotes graphiques.",
"he_IL": "",
"it_IT": "Seleziona il backend grafico che verrà utilizzato nell'emulatore.\n\nVulkan è nel complesso migliore per tutte le schede grafiche moderne, a condizione che i relativi driver siano aggiornati. Vulkan dispone anche di una compilazione degli shader più veloce (con minore stuttering) su tutte le marche di GPU.\n\nOpenGL può ottenere risultati migliori su vecchie GPU Nvidia, su vecchie GPU AMD su Linux, o su GPU con poca VRAM, anche se lo stuttering dovuto alla compilazione degli shader sarà maggiore.\n\nNel dubbio, scegli Vulkan. Seleziona OpenGL se la GPU non supporta Vulkan nemmeno con i driver grafici più recenti.",
"ja_JP": "エミュレーションに使用するグラフィックスバックエンドを選択します.\n\nVulkanは, 最近のグラフィックカードでドライバが最新であれば, 全体的に優れています. すべてのGPUベンダーで, シェーダーコンパイルがより高速で, スタッタリングが少ないのが特徴です.\n\n古いNvidia GPU, Linuxでの古いAMD GPU, VRAMの少ないGPUなどでは, OpenGLの方が良い結果が得られるかもしれません. ですが, シェーダーコンパイルのスタッターは大きくなります.\n\n不明な場合はVulkanに設定してください。最新のグラフィックドライバでもVulkanをサポートしていないGPUの場合は, OpenGLに設定してください.",
@@ -22230,7 +22230,7 @@
"el_GR": "",
"en_US": "Uses Vulkan.\nOn an ARM Mac, and when playing a game that runs well under it, uses the Metal backend.",
"es_ES": "",
- "fr_FR": "",
+ "fr_FR": "Utilise Vulkan.\nSur un Mac ARM, et lorsqu’un jeu fonctionne bien avec, utilise le moteur Metal.",
"he_IL": "",
"it_IT": "Utilizza Vulkan.\nSu un Mac con processore ARM, utilizza il backend Metal nei giochi che funzionano bene con quest'ultimo.",
"ja_JP": "",
@@ -22280,7 +22280,7 @@
"el_GR": "",
"en_US": "Compresses ASTC textures in order to reduce VRAM usage.\n\nGames using this texture format include Astral Chain, Bayonetta 3, Fire Emblem Engage, Metroid Prime Remastered, Super Mario Bros. Wonder and The Legend of Zelda: Tears of the Kingdom.\n\nGraphics cards with 4GiB VRAM or less will likely crash at some point while running these games.\n\nEnable only if you're running out of VRAM on the aforementioned games. Leave OFF if unsure.",
"es_ES": "Comprimir texturas ASTC para reducir uso de VRAM.\n\nJuegos que utilizan este formato de textura incluyen Astral Chain, Bayonetta 3, Fire Emblem Engage, Metroid Prime Remastered, Super Mario Bros. Wonder y The Legend of Zelda: Tears of the Kingdom.\n\nTarjetas gráficas con 4GiB de VRAM o menos probalemente se caeran en algún momento mientras que estén corriendo estos juegos.\n\nActivar solo si está quedan sin VRAM en los juegos antes mencionados. Desactívalo si no sabes qué hacer.",
- "fr_FR": "Les jeux utilisant ce format de texture incluent Astral Chain, Bayonetta 3, Fire Emblem Engage, Metroid Prime Remastered, Super Mario Bros. Wonder et The Legend of Zelda: Tears of the Kingdom.\n\nLes cartes graphiques avec 4 Go ou moins de VRAM risquent probablement de planter à un moment donné lors de l'exécution de ces jeux.\n\nActivez uniquement si vous manquez de VRAM sur les jeux mentionnés ci-dessus. Laissez DÉSACTIVÉ si vous n'êtes pas sûr.",
+ "fr_FR": "Compresse les textures ASTC pour réduire l’utilisation de la VRAM.\n\nLes jeux utilisant ce format de texture incluent Astral Chain, Bayonetta 3, Fire Emblem Engage, Metroid Prime Remastered, Super Mario Bros. Wonder et The Legend of Zelda: Tears of the Kingdom.\n\nLes cartes graphiques avec 4GiO de VRAM ou moins risquent de planter à un moment donné en jouant à ces jeux.\n\nActivez cette option uniquement si vous manquez de VRAM sur ces jeux. Laissez DÉSACTIVÉ si vous n'êtes pas sûr.",
"he_IL": "",
"it_IT": "Comprime le texture ASTC per ridurre l'utilizzo di VRAM.\n\nI giochi che utilizzano questo formato di texture includono Astral Chain, Bayonetta 3, Fire Emblem Engage, Metroid Prime Remastered, Super Mario Bros. Wonder e The Legend of Zelda: Tears of the Kingdom.\n\nLe schede grafiche con 4GiB o meno di VRAM probabilmente si bloccheranno ad un certo punto durante l'esecuzione di questi giochi.\n\nAttiva questa opzione solo se sei a corto di VRAM nei giochi sopra menzionati. Nel dubbio, lascia l'opzione disattivata.",
"ja_JP": "VRAM使用量を減らすためにASTCテクスチャを圧縮します.\n\nこのテクスチャフォーマットを使用するゲームには, Astral Chain, Bayonetta 3, Fire Emblem Engage, Metroid Prime Remastered, Super Mario Bros. Wonder, The Legend of Zelda: Tears of the Kingdomが含まれます.\n\nVRAMが4GB以下のグラフィックカードでは, これらのゲームを実行中にクラッシュする可能性があります.\n\n前述のゲームでVRAMが不足している場合のみ有効にしてください. 不明な場合はオフにしてください.",
@@ -22330,7 +22330,7 @@
"el_GR": "Επιλέξτε την κάρτα γραφικών η οποία θα χρησιμοποιηθεί από το Vulkan.\n\nΔεν επηρεάζει το OpenGL.\n\nΔιαλέξτε την GPU που διαθέτει την υπόδειξη \"dGPU\" αν δεν είστε βέβαιοι. Αν δεν υπάρχει κάποιαν, το πειράξετε",
"en_US": "Select the graphics card that will be used with the Vulkan graphics backend.\n\nDoes not affect the GPU that OpenGL will use.\n\nSet to the GPU flagged as \"dGPU\" if unsure. If there isn't one, leave untouched.",
"es_ES": "Selecciona la tarjeta gráfica que se utilizará con los back-end de gráficos Vulkan.\n\nNo afecta la GPU que utilizará OpenGL.\n\nFije a la GPU marcada como \"dGUP\" ante dudas. Si no hay una, no haga modificaciones.",
- "fr_FR": "Sélectionnez la carte graphique qui sera utilisée avec l'interface graphique Vulkan.\n\nCela ne change pas le GPU qu'OpenGL utilisera.\n\nChoisissez le GPU noté \"dGPU\" si vous n'êtes pas sûr. S'il n'y en a pas, ne pas modifier.",
+ "fr_FR": "Sélectionnez la carte graphique qui sera utilisée avec le moteur graphique Vulkan.\n\nCela n’affecte pas le GPU utilisé par OpenGL.\n\nChoisissez le GPU identifié comme « dGPU » si vous n’êtes pas sûr. S’il n’y en a pas, laissez tel quel.",
"he_IL": "בחר את הכרטיס הגראפי שישומש עם הגראפיקה של וולקאן.\n\nדבר זה לא משפיע על הכרטיס הגראפי שישומש עם OpenGL.\n\nמוטב לבחור את ה-GPU המסומן כ-\"dGPU\" אם אינכם בטוחים, אם זו לא אופצייה, אל תשנו דבר.",
"it_IT": "Seleziona la scheda grafica che verrà usata con il backend grafico Vulkan.\n\nL'opzione non modifica la GPU usata da OpenGL.\n\nNel dubbio, seleziona la GPU contrassegnata come \"dGPU\". Se non ce n'è una, lascia intatta questa opzione.",
"ja_JP": "Vulkanグラフィックスバックエンドで使用されるグラフィックスカードを選択します.\n\nOpenGLが使用するGPUには影響しません.\n\n不明な場合は, \"dGPU\" としてフラグが立っているGPUに設定します. ない場合はそのままにします.",
@@ -22930,7 +22930,7 @@
"el_GR": "",
"en_US": "Applies anti-aliasing to the game render.\n\nFXAA will blur most of the image, while SMAA will attempt to find jagged edges and smooth them out.\n\nNot recommended to use in conjunction with the FSR scaling filter.\n\nThis option can be changed while a game is running by clicking \"Apply\" below; you can simply move the settings window aside and experiment until you find your preferred look for a game.\n\nLeave on NONE if unsure.",
"es_ES": "Aplica antia-aliasing al rendereo del juego.\n\nFXAA desenfocará la mayor parte del la iamgen, mientras que SMAA intentará encontrar bordes irregulares y suavizarlos.\n\nNo se recomienda usar en conjunto con filtro de escala FSR.\n\nEsta opción puede ser modificada mientras que esté corriendo el juego haciendo click en \"Aplicar\" más abajo; simplemente puedes mover la ventana de configuración a un lado y experimentar hasta que encuentres tu estilo preferido para un juego.\n\nDejar en NADA si no está seguro.",
- "fr_FR": "FXAA floute la plupart de l'image, tandis que SMAA tente de détecter les contours dentelés et de les lisser.\n\nIl n'est pas recommandé de l'utiliser en conjonction avec le filtre de mise à l'échelle FSR.\n\nCette option peut être modifiée pendant qu'un jeu est en cours d'exécution en cliquant sur \"Appliquer\" ci-dessous ; vous pouvez simplement déplacer la fenêtre des paramètres sur le côté et expérimenter jusqu'à ce que vous trouviez l'apparence souhaitée pour un jeu.\n\nLaissez sur AUCUN si vous n'êtes pas sûr.",
+ "fr_FR": "Applique l’anticrénelage au rendu du jeu.\n\nFXAA floute la plupart de l’image, tandis que SMAA tente de détecter les contours irréguliers pour les lisser.\n\nIl n’est pas recommandé d’utiliser cette option avec le filtre de mise à l’échelle FSR.\n\nCette option peut être modifiée pendant que le jeu tourne en cliquant sur « Appliquer » ci-dessous ; vous pouvez simplement déplacer la fenêtre des paramètres pour tester jusqu’à trouver l’apparence qui vous convient.\n\nLaissez sur AUCUN si vous n’êtes pas sûr.",
"he_IL": "",
"it_IT": "Applica l'anti-aliasing al rendering del gioco.\n\nFXAA rende la maggior parte dell'immagine sfocata, mentre SMAA tenta di rilevare e smussare i bordi frastagliati.\n\nSi consiglia di non usarlo in combinazione con il filtro di scaling FSR.\n\nQuesta opzione può essere modificata mentre un gioco è in esecuzione facendo clic su \"Applica\" qui sotto; puoi semplicemente spostare la finestra delle impostazioni da parte e sperimentare fino a quando non trovi il tuo look preferito per un gioco.\n\nNel dubbio, lascia su Nessuno.",
"ja_JP": "ゲームレンダリングにアンチエイリアスを適用します.\n\nFXAAは画像の大部分をぼかし, SMAAはギザギザのエッジを見つけて滑らかにします.\n\nFSRスケーリングフィルタとの併用は推奨しません.\n\nこのオプションは, ゲーム実行中に下の「適用」をクリックして変更できます. 設定ウィンドウを脇に移動し, ゲームが好みの表示になるように試してみてください.\n\n不明な場合は「なし」のままにしておいてください.",
@@ -23055,7 +23055,7 @@
"el_GR": "",
"en_US": "Nearest",
"es_ES": "Cercano",
- "fr_FR": "Le plus proche",
+ "fr_FR": "Plus proche",
"he_IL": "",
"it_IT": "",
"ja_JP": "",
@@ -23155,7 +23155,7 @@
"el_GR": "",
"en_US": "Set FSR 1.0 sharpening level. Higher is sharper.",
"es_ES": "Ajuste el nivel de nitidez FSR 1.0. Mayor es más nítido.",
- "fr_FR": "Définissez le niveau de netteté FSR 1.0. Plus élevé signifie plus net.",
+ "fr_FR": "Définir le niveau de netteté FSR 1.0. Plus la valeur est élevée, plus l'image est nette.",
"he_IL": "",
"it_IT": "Imposta il livello di nitidezza di FSR 1.0. Valori più alti comportano una maggiore nitidezza.",
"ja_JP": "FSR 1.0のシャープ化レベルを設定します. 高い値ほどシャープになります.",
@@ -23205,7 +23205,7 @@
"el_GR": " Μεσαίο SMAA",
"en_US": "SMAA Medium",
"es_ES": "SMAA Medio",
- "fr_FR": "SMAA moyen",
+ "fr_FR": "SMAA Moyenne",
"he_IL": "SMAA בינוני",
"it_IT": "SMAA Medio",
"ja_JP": "",
@@ -23230,7 +23230,7 @@
"el_GR": "Υψηλό SMAA",
"en_US": "SMAA High",
"es_ES": "SMAA Alto",
- "fr_FR": "SMAA Élevé",
+ "fr_FR": "SMAA Élevée",
"he_IL": "SMAA גבוהה",
"it_IT": "SMAA Alto",
"ja_JP": "",
@@ -23355,7 +23355,7 @@
"el_GR": "",
"en_US": "The network interface used for LAN/LDN features.\n\nIn conjunction with a VPN or XLink Kai and a game with LAN support, can be used to spoof a same-network connection over the Internet.\n\nLeave on DEFAULT if unsure.",
"es_ES": "Interfaz de red usada para características LAN/LDN.\n\njunto con una VPN o XLink Kai y un juego con soporte LAN, puede usarse para suplantar una conexión de la misma red a través de Internet.\n\nDeje en DEFAULT si no está seguro.",
- "fr_FR": "L'interface réseau utilisée pour les fonctionnalités LAN/LDN.\n\nEn conjonction avec un VPN ou XLink Kai et un jeu prenant en charge le LAN, peut être utilisée pour simuler une connexion sur le même réseau via Internet.\n\nLaissez sur PAR DÉFAUT si vous n'êtes pas sûr.",
+ "fr_FR": "L'interface réseau utilisée pour les fonctionnalités LAN/LDN.\n\nEn conjonction avec un VPN ou XLink Kai et un jeu prenant en charge le LAN, peut être utilisée pour simuler une connexion sur le même réseau via Internet.\n\nLaissez sur DÉFAUT si vous n'êtes pas sûr.",
"he_IL": "",
"it_IT": "L'interfaccia di rete utilizzata per le funzionalità LAN/LDN.\n\nIn combinazione con una VPN o XLink Kai e un gioco che supporta la modalità LAN, questa opzione può essere usata per simulare la connessione alla stessa rete attraverso Internet.\n\nNel dubbio, lascia l'opzione su Predefinito.",
"ja_JP": "LAN/LDN機能に使用されるネットワークインタフェースです.\n\nVPNやXLink Kai、LAN対応のゲームと併用することで, インターネット上の同一ネットワーク接続になりすますことができます.\n\n不明な場合はデフォルトのままにしてください.",
@@ -23380,7 +23380,7 @@
"el_GR": "Προεπιλογή",
"en_US": "Default",
"es_ES": "Predeterminado",
- "fr_FR": "Par défaut",
+ "fr_FR": "Défaut",
"he_IL": "ברירת המחדל",
"it_IT": "Predefinito",
"ja_JP": "デフォルト",
@@ -23430,7 +23430,7 @@
"el_GR": "",
"en_US": "View Changelog",
"es_ES": "",
- "fr_FR": "",
+ "fr_FR": "Voir Changelog",
"he_IL": "",
"it_IT": "",
"ja_JP": "",
@@ -24155,7 +24155,7 @@
"el_GR": "",
"en_US": "Custom Refresh Rate %:",
"es_ES": "",
- "fr_FR": "Pourcentage du Taux de Rafraîchissement Customisé :",
+ "fr_FR": "% du taux de rafraîchissement personnalisé :",
"he_IL": "",
"it_IT": "Frequenza di aggiornamento personalizzata (%):",
"ja_JP": "",
@@ -24230,7 +24230,7 @@
"el_GR": "",
"en_US": "Toggle VSync mode:",
"es_ES": "",
- "fr_FR": "Activer/Désactiver mode VSync :",
+ "fr_FR": "Basculer le mode VSync :",
"he_IL": "",
"it_IT": "Cambia modalità VSync:",
"ja_JP": "",
@@ -24355,7 +24355,7 @@
"el_GR": "",
"en_US": "Only while pressed",
"es_ES": "",
- "fr_FR": "Seulement quand le raccourci est maintenu",
+ "fr_FR": "Uniquement en appuyant",
"he_IL": "",
"it_IT": "",
"ja_JP": "",
@@ -24380,7 +24380,7 @@
"el_GR": "",
"en_US": "Last updated: {0}",
"es_ES": "",
- "fr_FR": "",
+ "fr_FR": "Dernière mise à jour : {0}",
"he_IL": "",
"it_IT": "",
"ja_JP": "",
@@ -24405,7 +24405,7 @@
"el_GR": "",
"en_US": "Compatibility List - {0} entries",
"es_ES": "",
- "fr_FR": "",
+ "fr_FR": "Liste de Compatibilité – {0} entrées",
"he_IL": "",
"it_IT": "",
"ja_JP": "",
@@ -24430,7 +24430,7 @@
"el_GR": "",
"en_US": "This compatibility list might contain out of date entries.\nDo not be opposed to testing games in the \"Ingame\" status.",
"es_ES": "",
- "fr_FR": "",
+ "fr_FR": "Cette liste de compatibilité peut contenir des entrées obsolètes.N’hésitez pas à tester les jeux dont le statut est « En cours ».",
"he_IL": "",
"it_IT": "",
"ja_JP": "",
@@ -24455,7 +24455,7 @@
"el_GR": "",
"en_US": "Search compatibility entries...",
"es_ES": "",
- "fr_FR": "",
+ "fr_FR": "Rechercher des entrées de compatibilité...",
"he_IL": "",
"it_IT": "",
"ja_JP": "",
@@ -24480,7 +24480,7 @@
"el_GR": "",
"en_US": "Search {0} compatibility entries...",
"es_ES": "",
- "fr_FR": "",
+ "fr_FR": "Rechercher parmi {0} entrées de compatibilité...",
"he_IL": "",
"it_IT": "",
"ja_JP": "",
@@ -24505,7 +24505,7 @@
"el_GR": "",
"en_US": "Open Compatibility List",
"es_ES": "",
- "fr_FR": "",
+ "fr_FR": "Ouvrir la liste de compatibilité",
"he_IL": "",
"it_IT": "",
"ja_JP": "",
@@ -24630,7 +24630,7 @@
"el_GR": "",
"en_US": "Only show owned games",
"es_ES": "",
- "fr_FR": "",
+ "fr_FR": "Afficher uniquement les jeux possédés",
"he_IL": "",
"it_IT": "",
"ja_JP": "",
@@ -24655,7 +24655,7 @@
"el_GR": "",
"en_US": "Playable",
"es_ES": "",
- "fr_FR": "",
+ "fr_FR": "Jouable",
"he_IL": "",
"it_IT": "",
"ja_JP": "",
@@ -24680,7 +24680,7 @@
"el_GR": "",
"en_US": "Ingame",
"es_ES": "",
- "fr_FR": "",
+ "fr_FR": "En Jeu",
"he_IL": "",
"it_IT": "",
"ja_JP": "",
@@ -24730,7 +24730,7 @@
"el_GR": "",
"en_US": "Boots",
"es_ES": "",
- "fr_FR": "",
+ "fr_FR": "Démarre",
"he_IL": "",
"it_IT": "",
"ja_JP": "",
@@ -24755,7 +24755,7 @@
"el_GR": "",
"en_US": "Nothing",
"es_ES": "",
- "fr_FR": "",
+ "fr_FR": "Rien",
"he_IL": "",
"it_IT": "",
"ja_JP": "",
@@ -24780,7 +24780,7 @@
"el_GR": "",
"en_US": "Boots and plays without any crashes or GPU bugs of any kind, and at a speed fast enough to reasonably enjoy on an average PC.",
"es_ES": "",
- "fr_FR": "",
+ "fr_FR": "Démarre et fonctionne sans aucun plantage ni bug graphique, à une vitesse suffisante pour être apprécié raisonnablement sur un PC moyen.",
"he_IL": "",
"it_IT": "",
"ja_JP": "",
@@ -24805,7 +24805,7 @@
"el_GR": "",
"en_US": "Boots and goes in-game but suffers from one or more of the following: crashes, deadlocks, GPU bugs, distractingly bad audio, or is simply too slow. Game still might able to be played all the way through, but not as the game is intended to play.",
"es_ES": "",
- "fr_FR": "",
+ "fr_FR": "Démarre et lance le jeu, mais souffre d’un ou plusieurs des problèmes suivants : plantages, blocages, bugs graphiques, audio très désagréable, ou performances trop lentes. Le jeu peut encore être jouable jusqu’au bout, mais pas dans les conditions prévues.",
"he_IL": "",
"it_IT": "",
"ja_JP": "",
@@ -24830,7 +24830,7 @@
"el_GR": "",
"en_US": "Boots and goes past the title screen but does not make it into main gameplay.",
"es_ES": "",
- "fr_FR": "",
+ "fr_FR": "Démarre et dépasse l’écran titre, mais n’atteint pas le gameplay principal.",
"he_IL": "",
"it_IT": "",
"ja_JP": "",
@@ -24855,7 +24855,7 @@
"el_GR": "",
"en_US": "Boots but does not make it past the title screen.",
"es_ES": "",
- "fr_FR": "",
+ "fr_FR": "Démarre mais ne dépasse pas l’écran titre.",
"he_IL": "",
"it_IT": "",
"ja_JP": "",
@@ -24880,7 +24880,7 @@
"el_GR": "",
"en_US": "Does not boot or shows no signs of activity.",
"es_ES": "",
- "fr_FR": "",
+ "fr_FR": "Ne démarre pas et ne montre aucun signe d’activité.",
"he_IL": "",
"it_IT": "",
"ja_JP": "",
@@ -24905,7 +24905,7 @@
"el_GR": "",
"en_US": "Custom Config",
"es_ES": "",
- "fr_FR": "",
+ "fr_FR": "Configuration personnalisée",
"he_IL": "",
"it_IT": "",
"ja_JP": "",
From 890165707a4f68371f1df131a402c1bd32b1b297 Mon Sep 17 00:00:00 2001
From: Coxxs <58-coxxs@users.noreply.git.ryujinx.app>
Date: Fri, 20 Jun 2025 16:14:12 +0800
Subject: [PATCH 26/51] Add GDB Stub
Author: merry, svc64
---
assets/locales.json | 175 ++++
.../Instructions/NativeInterface.cs | 8 +-
src/ARMeilleure/Optimizations.cs | 1 +
src/ARMeilleure/State/ExecutionContext.cs | 30 +-
src/ARMeilleure/Translation/Translator.cs | 29 +-
src/Ryujinx.Common/Logging/LogClass.cs | 1 +
src/Ryujinx.Cpu/AppleHv/Arm/ExceptionLevel.cs | 10 +
src/Ryujinx.Cpu/AppleHv/HvExecutionContext.cs | 81 +-
.../AppleHv/HvExecutionContextShadow.cs | 11 +
.../AppleHv/HvExecutionContextVcpu.cs | 16 +-
.../AppleHv/IHvExecutionContext.cs | 4 +
src/Ryujinx.Cpu/ExceptionCallbacks.cs | 8 +
src/Ryujinx.Cpu/IExecutionContext.cs | 24 +
src/Ryujinx.Cpu/Jit/JitExecutionContext.cs | 25 +
.../LightningJit/State/ExecutionContext.cs | 19 +
src/Ryujinx.HLE/Debugger/DebugState.cs | 9 +
src/Ryujinx.HLE/Debugger/Debugger.cs | 907 ++++++++++++++++++
src/Ryujinx.HLE/Debugger/GdbSignal.cs | 15 +
.../Debugger/GdbXml/aarch64-core.xml | 93 ++
.../Debugger/GdbXml/aarch64-fpu.xml | 159 +++
src/Ryujinx.HLE/Debugger/GdbXml/arm-core.xml | 27 +
src/Ryujinx.HLE/Debugger/GdbXml/arm-neon.xml | 86 ++
src/Ryujinx.HLE/Debugger/GdbXml/target32.xml | 14 +
src/Ryujinx.HLE/Debugger/GdbXml/target64.xml | 14 +
.../Debugger/IDebuggableProcess.cs | 19 +
.../Debugger/Message/BreakInMessage.cs | 6 +
.../Debugger/Message/CommandMessage.cs | 12 +
src/Ryujinx.HLE/Debugger/Message/IMessage.cs | 6 +
.../Debugger/Message/KillMessage.cs | 6 +
.../Debugger/Message/SendNackMessage.cs | 6 +
.../Debugger/Message/ThreadBreakMessage.cs | 18 +
.../Debugger/RegisterInformation.cs | 28 +
src/Ryujinx.HLE/Debugger/StringStream.cs | 109 +++
src/Ryujinx.HLE/HOS/Horizon.cs | 9 +
.../HOS/Kernel/Process/KProcess.cs | 176 +++-
.../Kernel/Process/ProcessExecutionContext.cs | 9 +
.../HOS/Kernel/Threading/KScheduler.cs | 1 +
.../HOS/Kernel/Threading/KThread.cs | 12 +-
src/Ryujinx.HLE/HleConfiguration.cs | 21 +
src/Ryujinx.HLE/Ryujinx.HLE.csproj | 12 +
src/Ryujinx.HLE/Switch.cs | 4 +
src/Ryujinx/Headless/HeadlessRyujinx.Init.cs | 3 +
src/Ryujinx/Headless/Options.cs | 11 +
src/Ryujinx/Systems/AppHost.cs | 19 +
.../Configuration/ConfigurationFileFormat.cs | 15 +
.../ConfigurationState.Migration.cs | 4 +
.../Configuration/ConfigurationState.Model.cs | 40 +
.../Configuration/ConfigurationState.cs | 6 +
.../UI/ViewModels/SettingsViewModel.cs | 46 +
.../UI/Views/Settings/SettingsDebugView.axaml | 64 ++
.../Views/Settings/SettingsDebugView.axaml.cs | 13 +
src/Ryujinx/UI/Windows/SettingsWindow.axaml | 5 +
.../UI/Windows/SettingsWindow.axaml.cs | 3 +
53 files changed, 2428 insertions(+), 21 deletions(-)
create mode 100644 src/Ryujinx.Cpu/AppleHv/Arm/ExceptionLevel.cs
create mode 100644 src/Ryujinx.HLE/Debugger/DebugState.cs
create mode 100644 src/Ryujinx.HLE/Debugger/Debugger.cs
create mode 100644 src/Ryujinx.HLE/Debugger/GdbSignal.cs
create mode 100644 src/Ryujinx.HLE/Debugger/GdbXml/aarch64-core.xml
create mode 100644 src/Ryujinx.HLE/Debugger/GdbXml/aarch64-fpu.xml
create mode 100644 src/Ryujinx.HLE/Debugger/GdbXml/arm-core.xml
create mode 100644 src/Ryujinx.HLE/Debugger/GdbXml/arm-neon.xml
create mode 100644 src/Ryujinx.HLE/Debugger/GdbXml/target32.xml
create mode 100644 src/Ryujinx.HLE/Debugger/GdbXml/target64.xml
create mode 100644 src/Ryujinx.HLE/Debugger/IDebuggableProcess.cs
create mode 100644 src/Ryujinx.HLE/Debugger/Message/BreakInMessage.cs
create mode 100644 src/Ryujinx.HLE/Debugger/Message/CommandMessage.cs
create mode 100644 src/Ryujinx.HLE/Debugger/Message/IMessage.cs
create mode 100644 src/Ryujinx.HLE/Debugger/Message/KillMessage.cs
create mode 100644 src/Ryujinx.HLE/Debugger/Message/SendNackMessage.cs
create mode 100644 src/Ryujinx.HLE/Debugger/Message/ThreadBreakMessage.cs
create mode 100644 src/Ryujinx.HLE/Debugger/RegisterInformation.cs
create mode 100644 src/Ryujinx.HLE/Debugger/StringStream.cs
create mode 100644 src/Ryujinx/UI/Views/Settings/SettingsDebugView.axaml
create mode 100644 src/Ryujinx/UI/Views/Settings/SettingsDebugView.axaml.cs
diff --git a/assets/locales.json b/assets/locales.json
index d931017aa..6269cb5dd 100644
--- a/assets/locales.json
+++ b/assets/locales.json
@@ -25021,6 +25021,181 @@
"zh_CN": "动态 Rich Presence",
"zh_TW": "動態 Rich Presence"
}
+ },
+ {
+ "ID": "SettingsTabDebug",
+ "Translations": {
+ "ar_SA": "",
+ "de_DE": "",
+ "el_GR": "",
+ "en_US": "Debug",
+ "es_ES": "",
+ "fr_FR": "",
+ "he_IL": "",
+ "it_IT": "",
+ "ja_JP": "",
+ "ko_KR": "",
+ "no_NO": "",
+ "pl_PL": "",
+ "pt_BR": "",
+ "ru_RU": "",
+ "sv_SE": "",
+ "th_TH": "",
+ "tr_TR": "",
+ "uk_UA": "",
+ "zh_CN": "",
+ "zh_TW": ""
+ }
+ },
+ {
+ "ID": "SettingsTabDebugTitle",
+ "Translations": {
+ "ar_SA": "",
+ "de_DE": "",
+ "el_GR": "",
+ "en_US": "Debug (WARNING: For developer use only)",
+ "es_ES": "",
+ "fr_FR": "",
+ "he_IL": "",
+ "it_IT": "",
+ "ja_JP": "",
+ "ko_KR": "",
+ "no_NO": "",
+ "pl_PL": "",
+ "pt_BR": "",
+ "ru_RU": "",
+ "sv_SE": "",
+ "th_TH": "",
+ "tr_TR": "",
+ "uk_UA": "",
+ "zh_CN": "",
+ "zh_TW": ""
+ }
+ },
+ {
+ "ID": "EnableGDBStub",
+ "Translations": {
+ "ar_SA": "",
+ "de_DE": "",
+ "el_GR": "",
+ "en_US": "Enable GDB Stub",
+ "es_ES": "",
+ "fr_FR": "",
+ "he_IL": "",
+ "it_IT": "",
+ "ja_JP": "",
+ "ko_KR": "",
+ "no_NO": "",
+ "pl_PL": "",
+ "pt_BR": "",
+ "ru_RU": "",
+ "sv_SE": "",
+ "th_TH": "",
+ "tr_TR": "",
+ "uk_UA": "",
+ "zh_CN": "",
+ "zh_TW": ""
+ }
+ },
+ {
+ "ID": "GDBStubToggleTooltip",
+ "Translations": {
+ "ar_SA": "",
+ "de_DE": "",
+ "el_GR": "",
+ "en_US": "Enables the GDB stub which makes it possible to debug the running application. For development use only!",
+ "es_ES": "",
+ "fr_FR": "",
+ "he_IL": "",
+ "it_IT": "",
+ "ja_JP": "",
+ "ko_KR": "",
+ "no_NO": "",
+ "pl_PL": "",
+ "pt_BR": "",
+ "ru_RU": "",
+ "sv_SE": "",
+ "th_TH": "",
+ "tr_TR": "",
+ "uk_UA": "",
+ "zh_CN": "",
+ "zh_TW": ""
+ }
+ },
+ {
+ "ID": "GDBStubPort",
+ "Translations": {
+ "ar_SA": "",
+ "de_DE": "",
+ "el_GR": "",
+ "en_US": "GDB stub port:",
+ "es_ES": "",
+ "fr_FR": "",
+ "he_IL": "",
+ "it_IT": "",
+ "ja_JP": "",
+ "ko_KR": "",
+ "no_NO": "",
+ "pl_PL": "",
+ "pt_BR": "",
+ "ru_RU": "",
+ "sv_SE": "",
+ "th_TH": "",
+ "tr_TR": "",
+ "uk_UA": "",
+ "zh_CN": "",
+ "zh_TW": ""
+ }
+ },
+ {
+ "ID": "DebuggerSuspendOnStart",
+ "Translations": {
+ "ar_SA": "",
+ "de_DE": "",
+ "el_GR": "",
+ "en_US": "Suspend application on start",
+ "es_ES": "",
+ "fr_FR": "",
+ "he_IL": "",
+ "it_IT": "",
+ "ja_JP": "",
+ "ko_KR": "",
+ "no_NO": "",
+ "pl_PL": "",
+ "pt_BR": "",
+ "ru_RU": "",
+ "sv_SE": "",
+ "th_TH": "",
+ "tr_TR": "",
+ "uk_UA": "",
+ "zh_CN": "",
+ "zh_TW": ""
+ }
+ },
+ {
+ "ID": "DebuggerSuspendOnStartTooltip",
+ "Translations": {
+ "ar_SA": "",
+ "de_DE": "",
+ "el_GR": "",
+ "en_US": "Suspends the application before executing the first instruction, allowing for debugging from the earliest point.",
+ "es_ES": "",
+ "fr_FR": "",
+ "he_IL": "",
+ "it_IT": "",
+ "ja_JP": "",
+ "ko_KR": "",
+ "no_NO": "",
+ "pl_PL": "",
+ "pt_BR": "",
+ "ru_RU": "",
+ "sv_SE": "",
+ "th_TH": "",
+ "tr_TR": "",
+ "uk_UA": "",
+ "zh_CN": "",
+ "zh_TW": ""
+ }
}
]
}
diff --git a/src/ARMeilleure/Instructions/NativeInterface.cs b/src/ARMeilleure/Instructions/NativeInterface.cs
index b4922629b..c44f24b7c 100644
--- a/src/ARMeilleure/Instructions/NativeInterface.cs
+++ b/src/ARMeilleure/Instructions/NativeInterface.cs
@@ -3,6 +3,8 @@ using ARMeilleure.State;
using ARMeilleure.Translation;
using System;
using System.Runtime.InteropServices;
+using System.Threading;
+using ExecutionContext = ARMeilleure.State.ExecutionContext;
namespace ARMeilleure.Instructions
{
@@ -200,7 +202,11 @@ namespace ARMeilleure.Instructions
ExecutionContext context = GetContext();
- context.CheckInterrupt();
+ // If debugging, we'll handle interrupts outside
+ if (!Optimizations.EnableDebugging)
+ {
+ context.CheckInterrupt();
+ }
Statistics.ResumeTimer();
diff --git a/src/ARMeilleure/Optimizations.cs b/src/ARMeilleure/Optimizations.cs
index 18390de31..6dd7befe7 100644
--- a/src/ARMeilleure/Optimizations.cs
+++ b/src/ARMeilleure/Optimizations.cs
@@ -12,6 +12,7 @@ namespace ARMeilleure
public static bool AllowLcqInFunctionTable { get; set; } = true;
public static bool UseUnmanagedDispatchLoop { get; set; } = true;
+ public static bool EnableDebugging { get; set; } = false;
public static bool UseAdvSimdIfAvailable { get; set; } = true;
public static bool UseArm64AesIfAvailable { get; set; } = true;
diff --git a/src/ARMeilleure/State/ExecutionContext.cs b/src/ARMeilleure/State/ExecutionContext.cs
index 223e59d79..2978bf27a 100644
--- a/src/ARMeilleure/State/ExecutionContext.cs
+++ b/src/ARMeilleure/State/ExecutionContext.cs
@@ -1,4 +1,7 @@
using ARMeilleure.Memory;
+using System.Collections.Concurrent;
+using System.Diagnostics;
+using System.Threading;
namespace ARMeilleure.State
{
@@ -10,7 +13,7 @@ namespace ARMeilleure.State
internal nint NativeContextPtr => _nativeContext.BasePtr;
- private bool _interrupted;
+ internal bool Interrupted { get; private set; }
private readonly ICounter _counter;
@@ -65,6 +68,8 @@ namespace ARMeilleure.State
public bool IsAarch32 { get; set; }
+ public ulong ThreadUid { get; set; }
+
internal ExecutionMode ExecutionMode
{
get
@@ -90,14 +95,19 @@ namespace ARMeilleure.State
private readonly ExceptionCallbackNoArgs _interruptCallback;
private readonly ExceptionCallback _breakCallback;
+ private readonly ExceptionCallbackNoArgs _stepCallback;
private readonly ExceptionCallback _supervisorCallback;
private readonly ExceptionCallback _undefinedCallback;
+ internal int ShouldStep;
+ public ulong DebugPc { get; set; }
+
public ExecutionContext(
IJitMemoryAllocator allocator,
ICounter counter,
ExceptionCallbackNoArgs interruptCallback = null,
ExceptionCallback breakCallback = null,
+ ExceptionCallbackNoArgs stepCallback = null,
ExceptionCallback supervisorCallback = null,
ExceptionCallback undefinedCallback = null)
{
@@ -105,6 +115,7 @@ namespace ARMeilleure.State
_counter = counter;
_interruptCallback = interruptCallback;
_breakCallback = breakCallback;
+ _stepCallback = stepCallback;
_supervisorCallback = supervisorCallback;
_undefinedCallback = undefinedCallback;
@@ -127,9 +138,9 @@ namespace ARMeilleure.State
internal void CheckInterrupt()
{
- if (_interrupted)
+ if (Interrupted)
{
- _interrupted = false;
+ Interrupted = false;
_interruptCallback?.Invoke(this);
}
@@ -139,7 +150,18 @@ namespace ARMeilleure.State
public void RequestInterrupt()
{
- _interrupted = true;
+ Interrupted = true;
+ }
+
+ public void StepHandler()
+ {
+ _stepCallback.Invoke(this);
+ }
+
+ public void RequestDebugStep()
+ {
+ Interlocked.Exchange(ref ShouldStep, 1);
+ RequestInterrupt();
}
internal void OnBreak(ulong address, int imm)
diff --git a/src/ARMeilleure/Translation/Translator.cs b/src/ARMeilleure/Translation/Translator.cs
index d8528cfd6..343e361a5 100644
--- a/src/ARMeilleure/Translation/Translator.cs
+++ b/src/ARMeilleure/Translation/Translator.cs
@@ -119,7 +119,25 @@ namespace ARMeilleure.Translation
NativeInterface.RegisterThread(context, Memory, this);
- if (Optimizations.UseUnmanagedDispatchLoop)
+ if (Optimizations.EnableDebugging)
+ {
+ context.DebugPc = address;
+ do
+ {
+ if (Interlocked.CompareExchange(ref context.ShouldStep, 0, 1) == 1)
+ {
+ context.DebugPc = Step(context, context.DebugPc);
+ context.StepHandler();
+ }
+ else
+ {
+ context.DebugPc = ExecuteSingle(context, context.DebugPc);
+ }
+ context.CheckInterrupt();
+ }
+ while (context.Running && context.DebugPc != 0);
+ }
+ else if (Optimizations.UseUnmanagedDispatchLoop)
{
Stubs.DispatchLoop(context.NativeContextPtr, address);
}
@@ -175,7 +193,7 @@ namespace ARMeilleure.Translation
return nextAddr;
}
- public ulong Step(State.ExecutionContext context, ulong address)
+ private ulong Step(State.ExecutionContext context, ulong address)
{
TranslatedFunction func = Translate(address, context.ExecutionMode, highCq: false, singleStep: true);
@@ -228,7 +246,7 @@ namespace ARMeilleure.Translation
Stubs,
address,
highCq,
- _ptc.State != PtcState.Disabled,
+ _ptc.State != PtcState.Disabled && !Optimizations.EnableDebugging,
mode: Aarch32Mode.User);
Logger.StartPass(PassName.Decoding);
@@ -367,9 +385,8 @@ namespace ARMeilleure.Translation
if (block.Exit)
{
- // Left option here as it may be useful if we need to return to managed rather than tail call in
- // future. (eg. for debug)
- bool useReturns = false;
+ // Return to managed rather than tail call.
+ bool useReturns = Optimizations.EnableDebugging;
InstEmitFlowHelper.EmitVirtualJump(context, Const(block.Address), isReturn: useReturns);
}
diff --git a/src/Ryujinx.Common/Logging/LogClass.cs b/src/Ryujinx.Common/Logging/LogClass.cs
index a4117580e..89f0336dc 100644
--- a/src/Ryujinx.Common/Logging/LogClass.cs
+++ b/src/Ryujinx.Common/Logging/LogClass.cs
@@ -13,6 +13,7 @@ namespace Ryujinx.Common.Logging
Cpu,
Emulation,
FFmpeg,
+ GdbStub,
Font,
Gpu,
Hid,
diff --git a/src/Ryujinx.Cpu/AppleHv/Arm/ExceptionLevel.cs b/src/Ryujinx.Cpu/AppleHv/Arm/ExceptionLevel.cs
new file mode 100644
index 000000000..08114e12a
--- /dev/null
+++ b/src/Ryujinx.Cpu/AppleHv/Arm/ExceptionLevel.cs
@@ -0,0 +1,10 @@
+namespace Ryujinx.Cpu.AppleHv.Arm
+{
+ enum ExceptionLevel : uint
+ {
+ PstateMask = 0xfffffff0,
+ EL1h = 0b0101,
+ El1t = 0b0100,
+ EL0 = 0b0000,
+ }
+}
diff --git a/src/Ryujinx.Cpu/AppleHv/HvExecutionContext.cs b/src/Ryujinx.Cpu/AppleHv/HvExecutionContext.cs
index 53cea5385..f13662e44 100644
--- a/src/Ryujinx.Cpu/AppleHv/HvExecutionContext.cs
+++ b/src/Ryujinx.Cpu/AppleHv/HvExecutionContext.cs
@@ -11,7 +11,18 @@ namespace Ryujinx.Cpu.AppleHv
class HvExecutionContext : IExecutionContext
{
///
- public ulong Pc => _impl.ElrEl1;
+ public ulong Pc
+ {
+ get
+ {
+ uint currentEl = Pstate & ~((uint)ExceptionLevel.PstateMask);
+ if (currentEl == (uint)ExceptionLevel.EL1h)
+ {
+ return _impl.ElrEl1;
+ }
+ return _impl.Pc;
+ }
+ }
///
public long TpidrEl0
@@ -48,6 +59,9 @@ namespace Ryujinx.Cpu.AppleHv
set => _impl.Fpsr = value;
}
+ ///
+ public ulong ThreadUid { get; set; }
+
///
public bool IsAarch32
{
@@ -67,6 +81,7 @@ namespace Ryujinx.Cpu.AppleHv
private readonly ICounter _counter;
private readonly IHvExecutionContext _shadowContext;
private IHvExecutionContext _impl;
+ private int _shouldStep;
private readonly ExceptionCallbacks _exceptionCallbacks;
@@ -103,6 +118,11 @@ namespace Ryujinx.Cpu.AppleHv
_exceptionCallbacks.BreakCallback?.Invoke(this, address, imm);
}
+ private void StepHandler()
+ {
+ _exceptionCallbacks.StepCallback?.Invoke(this);
+ }
+
private void SupervisorCallHandler(ulong address, int imm)
{
_exceptionCallbacks.SupervisorCallback?.Invoke(this, address, imm);
@@ -127,6 +147,30 @@ namespace Ryujinx.Cpu.AppleHv
return Interlocked.Exchange(ref _interruptRequested, 0) != 0;
}
+ ///
+ public void RequestDebugStep()
+ {
+ Interlocked.Exchange(ref _shouldStep, 1);
+ }
+
+ ///
+ public ulong DebugPc
+ {
+ get => Pc;
+ set
+ {
+ uint currentEl = Pstate & ~((uint)ExceptionLevel.PstateMask);
+ if (currentEl == (uint)ExceptionLevel.EL1h)
+ {
+ _impl.ElrEl1 = value;
+ }
+ else
+ {
+ _impl.Pc = value;
+ }
+ }
+ }
+
///
public void StopRunning()
{
@@ -142,6 +186,22 @@ namespace Ryujinx.Cpu.AppleHv
while (Running)
{
+ if (Interlocked.CompareExchange(ref _shouldStep, 0, 1) == 1)
+ {
+ uint currentEl = Pstate & ~((uint)ExceptionLevel.PstateMask);
+ if (currentEl == (uint)ExceptionLevel.EL1h)
+ {
+ HvApi.hv_vcpu_get_sys_reg(vcpu.Handle, HvSysReg.SPSR_EL1, out ulong spsr).ThrowOnError();
+ spsr |= (1 << 21);
+ HvApi.hv_vcpu_set_sys_reg(vcpu.Handle, HvSysReg.SPSR_EL1, spsr);
+ }
+ else
+ {
+ Pstate |= (1 << 21);
+ }
+ HvApi.hv_vcpu_set_sys_reg(vcpu.Handle, HvSysReg.MDSCR_EL1, 1);
+ }
+
HvApi.hv_vcpu_run(vcpu.Handle).ThrowOnError();
HvExitReason reason = vcpu.ExitInfo->Reason;
@@ -209,6 +269,20 @@ namespace Ryujinx.Cpu.AppleHv
SupervisorCallHandler(elr - 4UL, id);
vcpu = RentFromPool(memoryManager.AddressSpace, vcpu);
break;
+ case ExceptionClass.SoftwareStepLowerEl:
+ HvApi.hv_vcpu_get_sys_reg(vcpuHandle, HvSysReg.SPSR_EL1, out ulong spsr).ThrowOnError();
+ spsr &= ~((ulong)(1 << 21));
+ HvApi.hv_vcpu_set_sys_reg(vcpuHandle, HvSysReg.SPSR_EL1, spsr).ThrowOnError();
+ HvApi.hv_vcpu_set_sys_reg(vcpuHandle, HvSysReg.MDSCR_EL1, 0);
+ ReturnToPool(vcpu);
+ StepHandler();
+ vcpu = RentFromPool(memoryManager.AddressSpace, vcpu);
+ break;
+ case ExceptionClass.BrkAarch64:
+ ReturnToPool(vcpu);
+ BreakHandler(elr, (ushort)esr);
+ vcpu = RentFromPool(memoryManager.AddressSpace, vcpu);
+ break;
default:
throw new Exception($"Unhandled guest exception {ec}.");
}
@@ -219,10 +293,7 @@ namespace Ryujinx.Cpu.AppleHv
// TODO: Invalidate only the range that was modified?
return HvAddressSpace.KernelRegionTlbiEretAddress;
}
- else
- {
- return HvAddressSpace.KernelRegionEretAddress;
- }
+ return HvAddressSpace.KernelRegionEretAddress;
}
private static void DataAbort(MemoryTracking tracking, ulong vcpu, uint esr)
diff --git a/src/Ryujinx.Cpu/AppleHv/HvExecutionContextShadow.cs b/src/Ryujinx.Cpu/AppleHv/HvExecutionContextShadow.cs
index 6ce8e1800..4ea5f276d 100644
--- a/src/Ryujinx.Cpu/AppleHv/HvExecutionContextShadow.cs
+++ b/src/Ryujinx.Cpu/AppleHv/HvExecutionContextShadow.cs
@@ -18,6 +18,8 @@ namespace Ryujinx.Cpu.AppleHv
public bool IsAarch32 { get; set; }
+ public ulong ThreadUid { get; set; }
+
private readonly ulong[] _x;
private readonly V128[] _v;
@@ -46,5 +48,14 @@ namespace Ryujinx.Cpu.AppleHv
{
_v[index] = value;
}
+
+ public void RequestInterrupt()
+ {
+ }
+
+ public bool GetAndClearInterruptRequested()
+ {
+ return false;
+ }
}
}
diff --git a/src/Ryujinx.Cpu/AppleHv/HvExecutionContextVcpu.cs b/src/Ryujinx.Cpu/AppleHv/HvExecutionContextVcpu.cs
index 1949cabdf..9ef03e61e 100644
--- a/src/Ryujinx.Cpu/AppleHv/HvExecutionContextVcpu.cs
+++ b/src/Ryujinx.Cpu/AppleHv/HvExecutionContextVcpu.cs
@@ -2,6 +2,7 @@ using ARMeilleure.State;
using Ryujinx.Memory;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
+using System.Threading;
namespace Ryujinx.Cpu.AppleHv
{
@@ -13,6 +14,8 @@ namespace Ryujinx.Cpu.AppleHv
private static readonly SetSimdFpReg _setSimdFpReg;
private static readonly nint _setSimdFpRegNativePtr;
+ public ulong ThreadUid { get; set; }
+
static HvExecutionContextVcpu()
{
// .NET does not support passing vectors by value, so we need to pass a pointer and use a native
@@ -135,6 +138,7 @@ namespace Ryujinx.Cpu.AppleHv
}
private readonly ulong _vcpu;
+ private int _interruptRequested;
public HvExecutionContextVcpu(ulong vcpu)
{
@@ -180,8 +184,16 @@ namespace Ryujinx.Cpu.AppleHv
public void RequestInterrupt()
{
- ulong vcpu = _vcpu;
- HvApi.hv_vcpus_exit(ref vcpu, 1);
+ if (Interlocked.Exchange(ref _interruptRequested, 1) == 0)
+ {
+ ulong vcpu = _vcpu;
+ HvApi.hv_vcpus_exit(ref vcpu, 1);
+ }
+ }
+
+ public bool GetAndClearInterruptRequested()
+ {
+ return Interlocked.Exchange(ref _interruptRequested, 0) != 0;
}
}
}
diff --git a/src/Ryujinx.Cpu/AppleHv/IHvExecutionContext.cs b/src/Ryujinx.Cpu/AppleHv/IHvExecutionContext.cs
index 54b73acc6..134405b5c 100644
--- a/src/Ryujinx.Cpu/AppleHv/IHvExecutionContext.cs
+++ b/src/Ryujinx.Cpu/AppleHv/IHvExecutionContext.cs
@@ -15,6 +15,7 @@ namespace Ryujinx.Cpu.AppleHv
uint Fpcr { get; set; }
uint Fpsr { get; set; }
+ ulong ThreadUid { get; set; }
ulong GetX(int index);
void SetX(int index, ulong value);
@@ -39,5 +40,8 @@ namespace Ryujinx.Cpu.AppleHv
SetV(i, context.GetV(i));
}
}
+
+ void RequestInterrupt();
+ bool GetAndClearInterruptRequested();
}
}
diff --git a/src/Ryujinx.Cpu/ExceptionCallbacks.cs b/src/Ryujinx.Cpu/ExceptionCallbacks.cs
index d9293302b..6e50b4d70 100644
--- a/src/Ryujinx.Cpu/ExceptionCallbacks.cs
+++ b/src/Ryujinx.Cpu/ExceptionCallbacks.cs
@@ -29,6 +29,11 @@ namespace Ryujinx.Cpu
///
public readonly ExceptionCallback BreakCallback;
+ ///
+ /// Handler for CPU software interrupts caused by single-stepping.
+ ///
+ public readonly ExceptionCallbackNoArgs StepCallback;
+
///
/// Handler for CPU software interrupts caused by the Arm SVC instruction.
///
@@ -47,16 +52,19 @@ namespace Ryujinx.Cpu
///
/// Handler for CPU interrupts triggered using
/// Handler for CPU software interrupts caused by the Arm BRK instruction
+ /// Handler for CPU software interrupts caused by single-stepping
/// Handler for CPU software interrupts caused by the Arm SVC instruction
/// Handler for CPU software interrupts caused by any undefined Arm instruction
public ExceptionCallbacks(
ExceptionCallbackNoArgs interruptCallback = null,
ExceptionCallback breakCallback = null,
+ ExceptionCallbackNoArgs stepCallback = null,
ExceptionCallback supervisorCallback = null,
ExceptionCallback undefinedCallback = null)
{
InterruptCallback = interruptCallback;
BreakCallback = breakCallback;
+ StepCallback = stepCallback;
SupervisorCallback = supervisorCallback;
UndefinedCallback = undefinedCallback;
}
diff --git a/src/Ryujinx.Cpu/IExecutionContext.cs b/src/Ryujinx.Cpu/IExecutionContext.cs
index c38210800..df0c94278 100644
--- a/src/Ryujinx.Cpu/IExecutionContext.cs
+++ b/src/Ryujinx.Cpu/IExecutionContext.cs
@@ -1,5 +1,6 @@
using ARMeilleure.State;
using System;
+using System.Threading;
namespace Ryujinx.Cpu
{
@@ -46,6 +47,11 @@ namespace Ryujinx.Cpu
///
bool IsAarch32 { get; set; }
+ ///
+ /// Thread UID.
+ ///
+ public ulong ThreadUid { get; set; }
+
///
/// Indicates whenever the CPU is still running code.
///
@@ -108,5 +114,23 @@ namespace Ryujinx.Cpu
/// If you only need to pause the thread temporarily, use instead.
///
void StopRunning();
+
+ ///
+ /// Requests the thread to stop running temporarily and call .
+ ///
+ ///
+ /// The thread might not pause immediately.
+ /// One must not assume that guest code is no longer being executed by the thread after calling this function.
+ /// After single stepping, the thread should call call .
+ ///
+ void RequestDebugStep();
+
+ ///
+ /// Current Program Counter (for debugging).
+ ///
+ ///
+ /// PC register for the debugger. Must not be accessed while the thread isn't stopped for debugging.
+ ///
+ ulong DebugPc { get; set; }
}
}
diff --git a/src/Ryujinx.Cpu/Jit/JitExecutionContext.cs b/src/Ryujinx.Cpu/Jit/JitExecutionContext.cs
index f15486e68..d4775f3ed 100644
--- a/src/Ryujinx.Cpu/Jit/JitExecutionContext.cs
+++ b/src/Ryujinx.Cpu/Jit/JitExecutionContext.cs
@@ -1,5 +1,7 @@
using ARMeilleure.Memory;
using ARMeilleure.State;
+using System.Threading;
+using ExecutionContext = ARMeilleure.State.ExecutionContext;
namespace Ryujinx.Cpu.Jit
{
@@ -53,6 +55,13 @@ namespace Ryujinx.Cpu.Jit
set => _impl.IsAarch32 = value;
}
+ ///
+ public ulong ThreadUid
+ {
+ get => _impl.ThreadUid;
+ set => _impl.ThreadUid = value;
+ }
+
///
public bool Running => _impl.Running;
@@ -65,6 +74,7 @@ namespace Ryujinx.Cpu.Jit
counter,
InterruptHandler,
BreakHandler,
+ StepHandler,
SupervisorCallHandler,
UndefinedHandler);
@@ -93,6 +103,11 @@ namespace Ryujinx.Cpu.Jit
_exceptionCallbacks.BreakCallback?.Invoke(this, address, imm);
}
+ private void StepHandler(ExecutionContext context)
+ {
+ _exceptionCallbacks.StepCallback?.Invoke(this);
+ }
+
private void SupervisorCallHandler(ExecutionContext context, ulong address, int imm)
{
_exceptionCallbacks.SupervisorCallback?.Invoke(this, address, imm);
@@ -109,6 +124,16 @@ namespace Ryujinx.Cpu.Jit
_impl.RequestInterrupt();
}
+ ///
+ public void RequestDebugStep() => _impl.RequestDebugStep();
+
+ ///
+ public ulong DebugPc
+ {
+ get => _impl.DebugPc;
+ set => _impl.DebugPc = value;
+ }
+
///
public void StopRunning()
{
diff --git a/src/Ryujinx.Cpu/LightningJit/State/ExecutionContext.cs b/src/Ryujinx.Cpu/LightningJit/State/ExecutionContext.cs
index a366dcca6..fc75e5185 100644
--- a/src/Ryujinx.Cpu/LightningJit/State/ExecutionContext.cs
+++ b/src/Ryujinx.Cpu/LightningJit/State/ExecutionContext.cs
@@ -1,6 +1,7 @@
using ARMeilleure.Memory;
using ARMeilleure.State;
using System;
+using System.Threading;
namespace Ryujinx.Cpu.LightningJit.State
{
@@ -51,6 +52,8 @@ namespace Ryujinx.Cpu.LightningJit.State
}
public bool IsAarch32 { get; set; }
+
+ public ulong ThreadUid { get; set; }
internal ExecutionMode ExecutionMode
{
@@ -77,15 +80,20 @@ namespace Ryujinx.Cpu.LightningJit.State
private readonly ExceptionCallbackNoArgs _interruptCallback;
private readonly ExceptionCallback _breakCallback;
+ private readonly ExceptionCallbackNoArgs _stepCallback;
private readonly ExceptionCallback _supervisorCallback;
private readonly ExceptionCallback _undefinedCallback;
+ internal int ShouldStep;
+ public ulong DebugPc { get; set; }
+
public ExecutionContext(IJitMemoryAllocator allocator, ICounter counter, ExceptionCallbacks exceptionCallbacks)
{
_nativeContext = new NativeContext(allocator);
_counter = counter;
_interruptCallback = exceptionCallbacks.InterruptCallback;
_breakCallback = exceptionCallbacks.BreakCallback;
+ _stepCallback = exceptionCallbacks.StepCallback;
_supervisorCallback = exceptionCallbacks.SupervisorCallback;
_undefinedCallback = exceptionCallbacks.UndefinedCallback;
@@ -117,6 +125,17 @@ namespace Ryujinx.Cpu.LightningJit.State
_interrupted = true;
}
+ public void StepHandler()
+ {
+ _stepCallback.Invoke(this);
+ }
+
+ public void RequestDebugStep()
+ {
+ Interlocked.Exchange(ref ShouldStep, 1);
+ RequestInterrupt();
+ }
+
internal void OnBreak(ulong address, int imm)
{
_breakCallback?.Invoke(this, address, imm);
diff --git a/src/Ryujinx.HLE/Debugger/DebugState.cs b/src/Ryujinx.HLE/Debugger/DebugState.cs
new file mode 100644
index 000000000..d2efa2bff
--- /dev/null
+++ b/src/Ryujinx.HLE/Debugger/DebugState.cs
@@ -0,0 +1,9 @@
+namespace Ryujinx.HLE.Debugger
+{
+ public enum DebugState
+ {
+ Running,
+ Stopping,
+ Stopped,
+ }
+}
diff --git a/src/Ryujinx.HLE/Debugger/Debugger.cs b/src/Ryujinx.HLE/Debugger/Debugger.cs
new file mode 100644
index 000000000..33c380175
--- /dev/null
+++ b/src/Ryujinx.HLE/Debugger/Debugger.cs
@@ -0,0 +1,907 @@
+using ARMeilleure.State;
+using Ryujinx.Common;
+using Ryujinx.Common.Logging;
+using Ryujinx.HLE.HOS.Kernel;
+using Ryujinx.HLE.HOS.Kernel.Threading;
+using Ryujinx.Memory;
+using System;
+using System.Collections.Concurrent;
+using System.IO;
+using System.Linq;
+using System.Net;
+using System.Net.Sockets;
+using System.Text;
+using System.Threading;
+using IExecutionContext = Ryujinx.Cpu.IExecutionContext;
+
+namespace Ryujinx.HLE.Debugger
+{
+ public class Debugger : IDisposable
+ {
+ internal Switch Device { get; private set; }
+
+ public ushort GdbStubPort { get; private set; }
+
+ private TcpListener ListenerSocket;
+ private Socket ClientSocket = null;
+ private NetworkStream ReadStream = null;
+ private NetworkStream WriteStream = null;
+ private BlockingCollection Messages = new BlockingCollection(1);
+ private Thread DebuggerThread;
+ private Thread MessageHandlerThread;
+ private bool _shuttingDown = false;
+
+ private ulong? cThread;
+ private ulong? gThread;
+
+ public Debugger(Switch device, ushort port)
+ {
+ Device = device;
+ GdbStubPort = port;
+
+ ARMeilleure.Optimizations.EnableDebugging = true;
+
+ DebuggerThread = new Thread(DebuggerThreadMain);
+ DebuggerThread.Start();
+ MessageHandlerThread = new Thread(MessageHandlerMain);
+ MessageHandlerThread.Start();
+ }
+
+ private IDebuggableProcess DebugProcess => Device.System.DebugGetApplicationProcess();
+ private KThread[] GetThreads() => DebugProcess.GetThreadUids().Select(x => DebugProcess.GetThread(x)).ToArray();
+ private bool IsProcessAarch32 => DebugProcess.GetThread(gThread.Value).Context.IsAarch32;
+ private KernelContext KernelContext => Device.System.KernelContext;
+
+ const int GdbRegisterCount64 = 68;
+ const int GdbRegisterCount32 = 66;
+ /* FPCR = FPSR & ~FpcrMask
+ All of FPCR's bits are reserved in FPCR and vice versa,
+ see ARM's documentation. */
+ private const uint FpcrMask = 0xfc1fffff;
+
+ private string GdbReadRegister64(IExecutionContext state, int gdbRegId)
+ {
+ switch (gdbRegId)
+ {
+ case >= 0 and <= 31:
+ return ToHex(BitConverter.GetBytes(state.GetX(gdbRegId)));
+ case 32:
+ return ToHex(BitConverter.GetBytes(state.DebugPc));
+ case 33:
+ return ToHex(BitConverter.GetBytes(state.Pstate));
+ case >= 34 and <= 65:
+ return ToHex(state.GetV(gdbRegId - 34).ToArray());
+ case 66:
+ return ToHex(BitConverter.GetBytes((uint)state.Fpsr));
+ case 67:
+ return ToHex(BitConverter.GetBytes((uint)state.Fpcr));
+ default:
+ return null;
+ }
+ }
+
+ private bool GdbWriteRegister64(IExecutionContext state, int gdbRegId, StringStream ss)
+ {
+ switch (gdbRegId)
+ {
+ case >= 0 and <= 31:
+ {
+ ulong value = ss.ReadLengthAsHex(16);
+ state.SetX(gdbRegId, value);
+ return true;
+ }
+ case 32:
+ {
+ ulong value = ss.ReadLengthAsHex(16);
+ state.DebugPc = value;
+ return true;
+ }
+ case 33:
+ {
+ ulong value = ss.ReadLengthAsHex(8);
+ state.Pstate = (uint)value;
+ return true;
+ }
+ case >= 34 and <= 65:
+ {
+ ulong value0 = ss.ReadLengthAsHex(16);
+ ulong value1 = ss.ReadLengthAsHex(16);
+ state.SetV(gdbRegId - 34, new V128(value0, value1));
+ return true;
+ }
+ case 66:
+ {
+ ulong value = ss.ReadLengthAsHex(8);
+ state.Fpsr = (uint)value;
+ return true;
+ }
+ case 67:
+ {
+ ulong value = ss.ReadLengthAsHex(8);
+ state.Fpcr = (uint)value;
+ return true;
+ }
+ default:
+ return false;
+ }
+ }
+
+ private string GdbReadRegister32(IExecutionContext state, int gdbRegId)
+ {
+ switch (gdbRegId)
+ {
+ case >= 0 and <= 14:
+ return ToHex(BitConverter.GetBytes((uint)state.GetX(gdbRegId)));
+ case 15:
+ return ToHex(BitConverter.GetBytes((uint)state.DebugPc));
+ case 16:
+ return ToHex(BitConverter.GetBytes((uint)state.Pstate));
+ case >= 17 and <= 32:
+ return ToHex(state.GetV(gdbRegId - 17).ToArray());
+ case >= 33 and <= 64:
+ int reg = (gdbRegId - 33);
+ int n = reg / 2;
+ int shift = reg % 2;
+ ulong value = state.GetV(n).Extract(shift);
+ return ToHex(BitConverter.GetBytes(value));
+ case 65:
+ uint fpscr = (uint)state.Fpsr | (uint)state.Fpcr;
+ return ToHex(BitConverter.GetBytes(fpscr));
+ default:
+ return null;
+ }
+ }
+
+ private bool GdbWriteRegister32(IExecutionContext state, int gdbRegId, StringStream ss)
+ {
+ switch (gdbRegId)
+ {
+ case >= 0 and <= 14:
+ {
+ ulong value = ss.ReadLengthAsHex(8);
+ state.SetX(gdbRegId, value);
+ return true;
+ }
+ case 15:
+ {
+ ulong value = ss.ReadLengthAsHex(8);
+ state.DebugPc = value;
+ return true;
+ }
+ case 16:
+ {
+ ulong value = ss.ReadLengthAsHex(8);
+ state.Pstate = (uint)value;
+ return true;
+ }
+ case >= 17 and <= 32:
+ {
+ ulong value0 = ss.ReadLengthAsHex(16);
+ ulong value1 = ss.ReadLengthAsHex(16);
+ state.SetV(gdbRegId - 17, new V128(value0, value1));
+ return true;
+ }
+ case >= 33 and <= 64:
+ {
+ ulong value = ss.ReadLengthAsHex(16);
+ int regId = (gdbRegId - 33);
+ int regNum = regId / 2;
+ int shift = regId % 2;
+ V128 reg = state.GetV(regNum);
+ reg.Insert(shift, value);
+ return true;
+ }
+ case 65:
+ {
+ ulong value = ss.ReadLengthAsHex(8);
+ state.Fpsr = (uint)value & FpcrMask;
+ state.Fpcr = (uint)value & ~FpcrMask;
+ return true;
+ }
+ default:
+ return false;
+ }
+ }
+
+ private void MessageHandlerMain()
+ {
+ while (!_shuttingDown)
+ {
+ IMessage msg = Messages.Take();
+ switch (msg)
+ {
+ case BreakInMessage:
+ Logger.Notice.Print(LogClass.GdbStub, "Break-in requested");
+ CommandQuery();
+ break;
+
+ case SendNackMessage:
+ WriteStream.WriteByte((byte)'-');
+ break;
+
+ case CommandMessage { Command: var cmd }:
+ Logger.Debug?.Print(LogClass.GdbStub, $"Received Command: {cmd}");
+ WriteStream.WriteByte((byte)'+');
+ ProcessCommand(cmd);
+ break;
+
+ case ThreadBreakMessage { Context: var ctx }:
+ DebugProcess.DebugStop();
+ Reply($"T05thread:{ctx.ThreadUid:x};");
+ break;
+
+ case KillMessage:
+ return;
+ }
+ }
+ }
+
+ private void ProcessCommand(string cmd)
+ {
+ Logger.Debug?.Print(LogClass.GdbStub, $"Receive: {cmd}");
+ StringStream ss = new StringStream(cmd);
+
+ switch (ss.ReadChar())
+ {
+ case '!':
+ if (!ss.IsEmpty())
+ {
+ goto unknownCommand;
+ }
+
+ // Enable extended mode
+ ReplyOK();
+ break;
+ case '?':
+ if (!ss.IsEmpty())
+ {
+ goto unknownCommand;
+ }
+
+ CommandQuery();
+ break;
+ case 'c':
+ CommandContinue(ss.IsEmpty() ? null : ss.ReadRemainingAsHex());
+ break;
+ case 'D':
+ if (!ss.IsEmpty())
+ {
+ goto unknownCommand;
+ }
+
+ CommandDetach();
+ break;
+ case 'g':
+ if (!ss.IsEmpty())
+ {
+ goto unknownCommand;
+ }
+
+ CommandReadRegisters();
+ break;
+ case 'G':
+ CommandWriteRegisters(ss);
+ break;
+ case 'H':
+ {
+ char op = ss.ReadChar();
+ ulong? threadId = ss.ReadRemainingAsThreadUid();
+ CommandSetThread(op, threadId);
+ break;
+ }
+ case 'k':
+ Logger.Notice.Print(LogClass.GdbStub, "Kill request received, detach instead");
+ Reply("");
+ CommandDetach();
+ break;
+ case 'm':
+ {
+ ulong addr = ss.ReadUntilAsHex(',');
+ ulong len = ss.ReadRemainingAsHex();
+ CommandReadMemory(addr, len);
+ break;
+ }
+ case 'M':
+ {
+ ulong addr = ss.ReadUntilAsHex(',');
+ ulong len = ss.ReadUntilAsHex(':');
+ CommandWriteMemory(addr, len, ss);
+ break;
+ }
+ case 'p':
+ {
+ ulong gdbRegId = ss.ReadRemainingAsHex();
+ CommandReadRegister((int)gdbRegId);
+ break;
+ }
+ case 'P':
+ {
+ ulong gdbRegId = ss.ReadUntilAsHex('=');
+ CommandWriteRegister((int)gdbRegId, ss);
+ break;
+ }
+ case 'q':
+ if (ss.ConsumeRemaining("GDBServerVersion"))
+ {
+ Reply($"name:Ryujinx;version:{ReleaseInformation.Version};");
+ break;
+ }
+
+ if (ss.ConsumeRemaining("HostInfo"))
+ {
+ if (IsProcessAarch32)
+ {
+ Reply(
+ $"triple:{ToHex("arm-unknown-linux-android")};endian:little;ptrsize:4;hostname:{ToHex("Ryujinx")};");
+ }
+ else
+ {
+ Reply(
+ $"triple:{ToHex("aarch64-unknown-linux-android")};endian:little;ptrsize:8;hostname:{ToHex("Ryujinx")};");
+ }
+ break;
+ }
+
+ if (ss.ConsumeRemaining("ProcessInfo"))
+ {
+ if (IsProcessAarch32)
+ {
+ Reply(
+ $"pid:1;cputype:12;cpusubtype:0;triple:{ToHex("arm-unknown-linux-android")};ostype:unknown;vendor:none;endian:little;ptrsize:4;");
+ }
+ else
+ {
+ Reply(
+ $"pid:1;cputype:100000c;cpusubtype:0;triple:{ToHex("aarch64-unknown-linux-android")};ostype:unknown;vendor:none;endian:little;ptrsize:8;");
+ }
+ break;
+ }
+
+ if (ss.ConsumePrefix("Supported:") || ss.ConsumeRemaining("Supported"))
+ {
+ Reply("PacketSize=10000;qXfer:features:read+");
+ break;
+ }
+
+ if (ss.ConsumeRemaining("fThreadInfo"))
+ {
+ Reply($"m{string.Join(",", DebugProcess.GetThreadUids().Select(x => $"{x:x}"))}");
+ break;
+ }
+
+ if (ss.ConsumeRemaining("sThreadInfo"))
+ {
+ Reply("l");
+ break;
+ }
+
+ if (ss.ConsumePrefix("ThreadExtraInfo,"))
+ {
+ ulong? threadId = ss.ReadRemainingAsThreadUid();
+ if (threadId == null)
+ {
+ ReplyError();
+ break;
+ }
+
+ if (DebugProcess.GetDebugState() == DebugState.Stopped)
+ {
+ Reply(ToHex("Stopped"));
+ }
+ else
+ {
+ Reply(ToHex("Not stopped"));
+ }
+ break;
+ }
+
+ if (ss.ConsumePrefix("Xfer:features:read:"))
+ {
+ string feature = ss.ReadUntil(':');
+ ulong addr = ss.ReadUntilAsHex(',');
+ ulong len = ss.ReadRemainingAsHex();
+
+ if (feature == "target.xml")
+ {
+ feature = IsProcessAarch32 ? "target32.xml" : "target64.xml";
+ }
+
+ string data;
+ if (RegisterInformation.Features.TryGetValue(feature, out data))
+ {
+ if (addr >= (ulong)data.Length)
+ {
+ Reply("l");
+ break;
+ }
+
+ if (len >= (ulong)data.Length - addr)
+ {
+ Reply("l" + ToBinaryFormat(data.Substring((int)addr)));
+ break;
+ }
+ else
+ {
+ Reply("m" + ToBinaryFormat(data.Substring((int)addr, (int)len)));
+ break;
+ }
+ }
+ else
+ {
+ Reply("E00"); // Invalid annex
+ break;
+ }
+ }
+
+ goto unknownCommand;
+ case 'Q':
+ goto unknownCommand;
+ case 's':
+ CommandStep(ss.IsEmpty() ? null : ss.ReadRemainingAsHex());
+ break;
+ case 'T':
+ {
+ ulong? threadId = ss.ReadRemainingAsThreadUid();
+ CommandIsAlive(threadId);
+ break;
+ }
+ case 'v':
+ if (ss.ConsumeRemaining("MustReplyEmpty"))
+ {
+ Reply("");
+ break;
+ }
+ goto unknownCommand;
+ default:
+ unknownCommand:
+ Logger.Notice.Print(LogClass.GdbStub, $"Unknown command: {cmd}");
+ Reply("");
+ break;
+ }
+ }
+
+ void CommandQuery()
+ {
+ // GDB is performing initial contact. Stop everything.
+ DebugProcess.DebugStop();
+ gThread = cThread = DebugProcess.GetThreadUids().First();
+ Reply($"T05thread:{cThread:x};");
+ }
+
+ void CommandContinue(ulong? newPc)
+ {
+ if (newPc.HasValue)
+ {
+ if (cThread == null)
+ {
+ ReplyError();
+ return;
+ }
+
+ DebugProcess.GetThread(cThread.Value).Context.DebugPc = newPc.Value;
+ }
+
+ DebugProcess.DebugContinue();
+ }
+
+ void CommandDetach()
+ {
+ // TODO: Remove all breakpoints
+ CommandContinue(null);
+ }
+
+ void CommandReadRegisters()
+ {
+ if (gThread == null)
+ {
+ ReplyError();
+ return;
+ }
+
+ var ctx = DebugProcess.GetThread(gThread.Value).Context;
+ string registers = "";
+ if (IsProcessAarch32)
+ {
+ for (int i = 0; i < GdbRegisterCount32; i++)
+ {
+ registers += GdbReadRegister32(ctx, i);
+ }
+ }
+ else
+ {
+ for (int i = 0; i < GdbRegisterCount64; i++)
+ {
+ registers += GdbReadRegister64(ctx, i);
+ }
+ }
+
+ Reply(registers);
+ }
+
+ void CommandWriteRegisters(StringStream ss)
+ {
+ if (gThread == null)
+ {
+ ReplyError();
+ return;
+ }
+
+ var ctx = DebugProcess.GetThread(gThread.Value).Context;
+ if (IsProcessAarch32)
+ {
+ for (int i = 0; i < GdbRegisterCount32; i++)
+ {
+ if (!GdbWriteRegister32(ctx, i, ss))
+ {
+ ReplyError();
+ return;
+ }
+ }
+ }
+ else
+ {
+ for (int i = 0; i < GdbRegisterCount64; i++)
+ {
+ if (!GdbWriteRegister64(ctx, i, ss))
+ {
+ ReplyError();
+ return;
+ }
+ }
+ }
+
+ if (ss.IsEmpty())
+ {
+ ReplyOK();
+ }
+ else
+ {
+ ReplyError();
+ }
+ }
+
+ void CommandSetThread(char op, ulong? threadId)
+ {
+ if (threadId == 0 || threadId == null)
+ {
+ threadId = GetThreads().First().ThreadUid;
+ }
+
+ if (DebugProcess.GetThread(threadId.Value) == null)
+ {
+ ReplyError();
+ return;
+ }
+
+ switch (op)
+ {
+ case 'c':
+ cThread = threadId;
+ ReplyOK();
+ return;
+ case 'g':
+ gThread = threadId;
+ ReplyOK();
+ return;
+ default:
+ ReplyError();
+ return;
+ }
+ }
+
+ void CommandReadMemory(ulong addr, ulong len)
+ {
+ try
+ {
+ var data = new byte[len];
+ DebugProcess.CpuMemory.Read(addr, data);
+ Reply(ToHex(data));
+ }
+ catch (InvalidMemoryRegionException)
+ {
+ ReplyError();
+ }
+ }
+
+ void CommandWriteMemory(ulong addr, ulong len, StringStream ss)
+ {
+ try
+ {
+ var data = new byte[len];
+ for (ulong i = 0; i < len; i++)
+ {
+ data[i] = (byte)ss.ReadLengthAsHex(2);
+ }
+
+ DebugProcess.CpuMemory.Write(addr, data);
+ DebugProcess.InvalidateCacheRegion(addr, len);
+ ReplyOK();
+ }
+ catch (InvalidMemoryRegionException)
+ {
+ ReplyError();
+ }
+ }
+
+ void CommandReadRegister(int gdbRegId)
+ {
+ if (gThread == null)
+ {
+ ReplyError();
+ return;
+ }
+
+ var ctx = DebugProcess.GetThread(gThread.Value).Context;
+ string result;
+ if (IsProcessAarch32)
+ {
+ result = GdbReadRegister32(ctx, gdbRegId);
+ if (result != null)
+ {
+ Reply(result);
+ }
+ else
+ {
+ ReplyError();
+ }
+ }
+ else
+ {
+ result = GdbReadRegister64(ctx, gdbRegId);
+ if (result != null)
+ {
+ Reply(result);
+ }
+ else
+ {
+ ReplyError();
+ }
+ }
+ }
+
+ void CommandWriteRegister(int gdbRegId, StringStream ss)
+ {
+ if (gThread == null)
+ {
+ ReplyError();
+ return;
+ }
+
+ var ctx = DebugProcess.GetThread(gThread.Value).Context;
+ if (IsProcessAarch32)
+ {
+ if (GdbWriteRegister32(ctx, gdbRegId, ss) && ss.IsEmpty())
+ {
+ ReplyOK();
+ }
+ else
+ {
+ ReplyError();
+ }
+ }
+ else
+ {
+ if (GdbWriteRegister64(ctx, gdbRegId, ss) && ss.IsEmpty())
+ {
+ ReplyOK();
+ }
+ else
+ {
+ ReplyError();
+ }
+ }
+ }
+
+ private void CommandStep(ulong? newPc)
+ {
+ if (cThread == null)
+ {
+ ReplyError();
+ return;
+ }
+
+ var thread = DebugProcess.GetThread(cThread.Value);
+
+ if (newPc.HasValue)
+ {
+ thread.Context.DebugPc = newPc.Value;
+ }
+
+ if (!DebugProcess.DebugStep(thread))
+ {
+ ReplyError();
+ }
+ else
+ {
+ Reply($"T05thread:{thread.ThreadUid:x};");
+ }
+ }
+
+ private void CommandIsAlive(ulong? threadId)
+ {
+ if (GetThreads().Any(x => x.ThreadUid == threadId))
+ {
+ ReplyOK();
+ }
+ else
+ {
+ Reply("E00");
+ }
+ }
+
+ private void Reply(string cmd)
+ {
+ Logger.Debug?.Print(LogClass.GdbStub, $"Reply: {cmd}");
+ WriteStream.Write(Encoding.ASCII.GetBytes($"${cmd}#{CalculateChecksum(cmd):x2}"));
+ }
+
+ private void ReplyOK()
+ {
+ Reply("OK");
+ }
+
+ private void ReplyError()
+ {
+ Reply("E01");
+ }
+
+ private void DebuggerThreadMain()
+ {
+ var endpoint = new IPEndPoint(IPAddress.Any, GdbStubPort);
+ ListenerSocket = new TcpListener(endpoint);
+ ListenerSocket.Start();
+ Logger.Notice.Print(LogClass.GdbStub, $"Currently waiting on {endpoint} for GDB client");
+
+ while (!_shuttingDown)
+ {
+ try
+ {
+ ClientSocket = ListenerSocket.AcceptSocket();
+ }
+ catch (SocketException)
+ {
+ return;
+ }
+ ClientSocket.NoDelay = true;
+ ReadStream = new NetworkStream(ClientSocket, System.IO.FileAccess.Read);
+ WriteStream = new NetworkStream(ClientSocket, System.IO.FileAccess.Write);
+ Logger.Notice.Print(LogClass.GdbStub, "GDB client connected");
+
+ while (true)
+ {
+ try
+ {
+ switch (ReadStream.ReadByte())
+ {
+ case -1:
+ goto eof;
+ case '+':
+ continue;
+ case '-':
+ Logger.Notice.Print(LogClass.GdbStub, "NACK received!");
+ continue;
+ case '\x03':
+ Messages.Add(new BreakInMessage());
+ break;
+ case '$':
+ string cmd = "";
+ while (true)
+ {
+ int x = ReadStream.ReadByte();
+ if (x == -1)
+ goto eof;
+ if (x == '#')
+ break;
+ cmd += (char)x;
+ }
+
+ string checksum = $"{(char)ReadStream.ReadByte()}{(char)ReadStream.ReadByte()}";
+ if (checksum == $"{CalculateChecksum(cmd):x2}")
+ {
+ Messages.Add(new CommandMessage(cmd));
+ }
+ else
+ {
+ Messages.Add(new SendNackMessage());
+ }
+
+ break;
+ }
+ }
+ catch (IOException)
+ {
+ goto eof;
+ }
+ }
+
+ eof:
+ Logger.Notice.Print(LogClass.GdbStub, "GDB client lost connection");
+ ReadStream.Close();
+ ReadStream = null;
+ WriteStream.Close();
+ WriteStream = null;
+ ClientSocket.Close();
+ ClientSocket = null;
+ }
+ }
+
+ private byte CalculateChecksum(string cmd)
+ {
+ byte checksum = 0;
+ foreach (char x in cmd)
+ {
+ unchecked
+ {
+ checksum += (byte)x;
+ }
+ }
+
+ return checksum;
+ }
+
+ private string ToHex(byte[] bytes)
+ {
+ return string.Join("", bytes.Select(x => $"{x:x2}"));
+ }
+
+ private string ToHex(string str)
+ {
+ return ToHex(Encoding.ASCII.GetBytes(str));
+ }
+
+ private string ToBinaryFormat(byte[] bytes)
+ {
+ return string.Join("", bytes.Select(x =>
+ x switch
+ {
+ (byte)'#' => "}\x03",
+ (byte)'$' => "}\x04",
+ (byte)'*' => "}\x0a",
+ (byte)'}' => "}\x5d",
+ _ => Convert.ToChar(x).ToString(),
+ }
+ ));
+ }
+
+ private string ToBinaryFormat(string str)
+ {
+ return ToBinaryFormat(Encoding.ASCII.GetBytes(str));
+ }
+
+ public void Dispose()
+ {
+ Dispose(true);
+ }
+
+ protected virtual void Dispose(bool disposing)
+ {
+ if (disposing)
+ {
+ _shuttingDown = true;
+
+ ListenerSocket.Stop();
+ ClientSocket?.Shutdown(SocketShutdown.Both);
+ ClientSocket?.Close();
+ ReadStream?.Close();
+ WriteStream?.Close();
+ DebuggerThread.Join();
+ Messages.Add(new KillMessage());
+ MessageHandlerThread.Join();
+ Messages.Dispose();
+ }
+ }
+
+ public void BreakHandler(IExecutionContext ctx, ulong address, int imm)
+ {
+ Logger.Notice.Print(LogClass.GdbStub, $"Break hit on thread {ctx.ThreadUid} at pc {address:x016}");
+
+ Messages.Add(new ThreadBreakMessage(ctx, address, imm));
+ DebugProcess.DebugInterruptHandler(ctx);
+ }
+
+ public void StepHandler(IExecutionContext ctx)
+ {
+ DebugProcess.DebugInterruptHandler(ctx);
+ }
+ }
+}
diff --git a/src/Ryujinx.HLE/Debugger/GdbSignal.cs b/src/Ryujinx.HLE/Debugger/GdbSignal.cs
new file mode 100644
index 000000000..ee4efbda4
--- /dev/null
+++ b/src/Ryujinx.HLE/Debugger/GdbSignal.cs
@@ -0,0 +1,15 @@
+namespace Ryujinx.HLE.Debugger
+{
+ enum GdbSignal
+ {
+ Zero = 0,
+ Int = 2,
+ Quit = 3,
+ Trap = 5,
+ Abort = 6,
+ Alarm = 14,
+ IO = 23,
+ XCPU = 24,
+ Unknown = 143
+ }
+}
diff --git a/src/Ryujinx.HLE/Debugger/GdbXml/aarch64-core.xml b/src/Ryujinx.HLE/Debugger/GdbXml/aarch64-core.xml
new file mode 100644
index 000000000..9899a0e4a
--- /dev/null
+++ b/src/Ryujinx.HLE/Debugger/GdbXml/aarch64-core.xml
@@ -0,0 +1,93 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Ryujinx.HLE/Debugger/GdbXml/aarch64-fpu.xml b/src/Ryujinx.HLE/Debugger/GdbXml/aarch64-fpu.xml
new file mode 100644
index 000000000..a09120bc4
--- /dev/null
+++ b/src/Ryujinx.HLE/Debugger/GdbXml/aarch64-fpu.xml
@@ -0,0 +1,159 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Ryujinx.HLE/Debugger/GdbXml/arm-core.xml b/src/Ryujinx.HLE/Debugger/GdbXml/arm-core.xml
new file mode 100644
index 000000000..2307d65f9
--- /dev/null
+++ b/src/Ryujinx.HLE/Debugger/GdbXml/arm-core.xml
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Ryujinx.HLE/Debugger/GdbXml/arm-neon.xml b/src/Ryujinx.HLE/Debugger/GdbXml/arm-neon.xml
new file mode 100644
index 000000000..d61f6b854
--- /dev/null
+++ b/src/Ryujinx.HLE/Debugger/GdbXml/arm-neon.xml
@@ -0,0 +1,86 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Ryujinx.HLE/Debugger/GdbXml/target32.xml b/src/Ryujinx.HLE/Debugger/GdbXml/target32.xml
new file mode 100644
index 000000000..890679858
--- /dev/null
+++ b/src/Ryujinx.HLE/Debugger/GdbXml/target32.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+ arm
+
+
+
diff --git a/src/Ryujinx.HLE/Debugger/GdbXml/target64.xml b/src/Ryujinx.HLE/Debugger/GdbXml/target64.xml
new file mode 100644
index 000000000..cfd5bf780
--- /dev/null
+++ b/src/Ryujinx.HLE/Debugger/GdbXml/target64.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+ aarch64
+
+
+
diff --git a/src/Ryujinx.HLE/Debugger/IDebuggableProcess.cs b/src/Ryujinx.HLE/Debugger/IDebuggableProcess.cs
new file mode 100644
index 000000000..273a1147f
--- /dev/null
+++ b/src/Ryujinx.HLE/Debugger/IDebuggableProcess.cs
@@ -0,0 +1,19 @@
+using Ryujinx.Cpu;
+using Ryujinx.HLE.HOS.Kernel.Threading;
+using Ryujinx.Memory;
+
+namespace Ryujinx.HLE.Debugger
+{
+ internal interface IDebuggableProcess
+ {
+ void DebugStop();
+ void DebugContinue();
+ bool DebugStep(KThread thread);
+ KThread GetThread(ulong threadUid);
+ DebugState GetDebugState();
+ ulong[] GetThreadUids();
+ public void DebugInterruptHandler(IExecutionContext ctx);
+ IVirtualMemoryManager CpuMemory { get; }
+ void InvalidateCacheRegion(ulong address, ulong size);
+ }
+}
diff --git a/src/Ryujinx.HLE/Debugger/Message/BreakInMessage.cs b/src/Ryujinx.HLE/Debugger/Message/BreakInMessage.cs
new file mode 100644
index 000000000..81d8784ae
--- /dev/null
+++ b/src/Ryujinx.HLE/Debugger/Message/BreakInMessage.cs
@@ -0,0 +1,6 @@
+namespace Ryujinx.HLE.Debugger
+{
+ struct BreakInMessage : IMessage
+ {
+ }
+}
diff --git a/src/Ryujinx.HLE/Debugger/Message/CommandMessage.cs b/src/Ryujinx.HLE/Debugger/Message/CommandMessage.cs
new file mode 100644
index 000000000..ad265d432
--- /dev/null
+++ b/src/Ryujinx.HLE/Debugger/Message/CommandMessage.cs
@@ -0,0 +1,12 @@
+namespace Ryujinx.HLE.Debugger
+{
+ struct CommandMessage : IMessage
+ {
+ public string Command;
+
+ public CommandMessage(string cmd)
+ {
+ Command = cmd;
+ }
+ }
+}
diff --git a/src/Ryujinx.HLE/Debugger/Message/IMessage.cs b/src/Ryujinx.HLE/Debugger/Message/IMessage.cs
new file mode 100644
index 000000000..4b03183c5
--- /dev/null
+++ b/src/Ryujinx.HLE/Debugger/Message/IMessage.cs
@@ -0,0 +1,6 @@
+namespace Ryujinx.HLE.Debugger
+{
+ interface IMessage
+ {
+ }
+}
diff --git a/src/Ryujinx.HLE/Debugger/Message/KillMessage.cs b/src/Ryujinx.HLE/Debugger/Message/KillMessage.cs
new file mode 100644
index 000000000..43ae0f21e
--- /dev/null
+++ b/src/Ryujinx.HLE/Debugger/Message/KillMessage.cs
@@ -0,0 +1,6 @@
+namespace Ryujinx.HLE.Debugger
+{
+ struct KillMessage : IMessage
+ {
+ }
+}
diff --git a/src/Ryujinx.HLE/Debugger/Message/SendNackMessage.cs b/src/Ryujinx.HLE/Debugger/Message/SendNackMessage.cs
new file mode 100644
index 000000000..ce804c46e
--- /dev/null
+++ b/src/Ryujinx.HLE/Debugger/Message/SendNackMessage.cs
@@ -0,0 +1,6 @@
+namespace Ryujinx.HLE.Debugger
+{
+ struct SendNackMessage : IMessage
+ {
+ }
+}
diff --git a/src/Ryujinx.HLE/Debugger/Message/ThreadBreakMessage.cs b/src/Ryujinx.HLE/Debugger/Message/ThreadBreakMessage.cs
new file mode 100644
index 000000000..027096eeb
--- /dev/null
+++ b/src/Ryujinx.HLE/Debugger/Message/ThreadBreakMessage.cs
@@ -0,0 +1,18 @@
+using IExecutionContext = Ryujinx.Cpu.IExecutionContext;
+
+namespace Ryujinx.HLE.Debugger
+{
+ public class ThreadBreakMessage : IMessage
+ {
+ public IExecutionContext Context { get; }
+ public ulong Address { get; }
+ public int Opcode { get; }
+
+ public ThreadBreakMessage(IExecutionContext context, ulong address, int opcode)
+ {
+ Context = context;
+ Address = address;
+ Opcode = opcode;
+ }
+ }
+}
diff --git a/src/Ryujinx.HLE/Debugger/RegisterInformation.cs b/src/Ryujinx.HLE/Debugger/RegisterInformation.cs
new file mode 100644
index 000000000..b5fd88ea5
--- /dev/null
+++ b/src/Ryujinx.HLE/Debugger/RegisterInformation.cs
@@ -0,0 +1,28 @@
+using System.Collections.Generic;
+using System.IO;
+
+namespace Ryujinx.HLE.Debugger
+{
+ class RegisterInformation
+ {
+ public static readonly Dictionary Features = new()
+ {
+ { "target64.xml", GetEmbeddedResourceContent("target64.xml") },
+ { "target32.xml", GetEmbeddedResourceContent("target32.xml") },
+ { "aarch64-core.xml", GetEmbeddedResourceContent("aarch64-core.xml") },
+ { "aarch64-fpu.xml", GetEmbeddedResourceContent("aarch64-fpu.xml") },
+ { "arm-core.xml", GetEmbeddedResourceContent("arm-core.xml") },
+ { "arm-neon.xml", GetEmbeddedResourceContent("arm-neon.xml") },
+ };
+
+ private static string GetEmbeddedResourceContent(string resourceName)
+ {
+ Stream stream = System.Reflection.Assembly.GetExecutingAssembly().GetManifestResourceStream("Ryujinx.HLE.Debugger.GdbXml." + resourceName);
+ StreamReader reader = new StreamReader(stream);
+ string result = reader.ReadToEnd();
+ reader.Dispose();
+ stream.Dispose();
+ return result;
+ }
+ }
+}
diff --git a/src/Ryujinx.HLE/Debugger/StringStream.cs b/src/Ryujinx.HLE/Debugger/StringStream.cs
new file mode 100644
index 000000000..d8148a9c2
--- /dev/null
+++ b/src/Ryujinx.HLE/Debugger/StringStream.cs
@@ -0,0 +1,109 @@
+using System.Diagnostics;
+using System.Globalization;
+
+namespace Ryujinx.HLE.Debugger
+{
+ class StringStream
+ {
+ private readonly string Data;
+ private int Position;
+
+ public StringStream(string s)
+ {
+ Data = s;
+ }
+
+ public char ReadChar()
+ {
+ return Data[Position++];
+ }
+
+ public string ReadUntil(char needle)
+ {
+ int needlePos = Data.IndexOf(needle, Position);
+
+ if (needlePos == -1)
+ {
+ needlePos = Data.Length;
+ }
+
+ string result = Data.Substring(Position, needlePos - Position);
+ Position = needlePos + 1;
+ return result;
+ }
+
+ public string ReadLength(int len)
+ {
+ string result = Data.Substring(Position, len);
+ Position += len;
+ return result;
+ }
+
+ public string ReadRemaining()
+ {
+ string result = Data.Substring(Position);
+ Position = Data.Length;
+ return result;
+ }
+
+ public ulong ReadRemainingAsHex()
+ {
+ return ulong.Parse(ReadRemaining(), NumberStyles.HexNumber);
+ }
+
+ public ulong ReadUntilAsHex(char needle)
+ {
+ return ulong.Parse(ReadUntil(needle), NumberStyles.HexNumber);
+ }
+
+ public ulong ReadLengthAsHex(int len)
+ {
+ return ulong.Parse(ReadLength(len), NumberStyles.HexNumber);
+ }
+
+ public ulong ReadLengthAsLEHex(int len)
+ {
+ Debug.Assert(len % 2 == 0);
+
+ ulong result = 0;
+ int pos = 0;
+ while (pos < len)
+ {
+ result += ReadLengthAsHex(2) << (4 * pos);
+ pos += 2;
+ }
+ return result;
+ }
+
+ public ulong? ReadRemainingAsThreadUid()
+ {
+ string s = ReadRemaining();
+ return s == "-1" ? null : ulong.Parse(s, NumberStyles.HexNumber);
+ }
+
+ public bool ConsumePrefix(string prefix)
+ {
+ if (Data.Substring(Position).StartsWith(prefix))
+ {
+ Position += prefix.Length;
+ return true;
+ }
+ return false;
+ }
+
+ public bool ConsumeRemaining(string match)
+ {
+ if (Data.Substring(Position) == match)
+ {
+ Position += match.Length;
+ return true;
+ }
+ return false;
+ }
+
+ public bool IsEmpty()
+ {
+ return Position >= Data.Length;
+ }
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Horizon.cs b/src/Ryujinx.HLE/HOS/Horizon.cs
index 5063b4329..44adb9674 100644
--- a/src/Ryujinx.HLE/HOS/Horizon.cs
+++ b/src/Ryujinx.HLE/HOS/Horizon.cs
@@ -5,6 +5,7 @@ using LibHac.Fs.Shim;
using LibHac.FsSystem;
using LibHac.Tools.FsSystem;
using Ryujinx.Cpu;
+using Ryujinx.HLE.Debugger;
using Ryujinx.HLE.FileSystem;
using Ryujinx.HLE.HOS.Kernel;
using Ryujinx.HLE.HOS.Kernel.Memory;
@@ -500,5 +501,13 @@ namespace Ryujinx.HLE.HOS
IsPaused = pause;
}
+
+ internal IDebuggableProcess DebugGetApplicationProcess()
+ {
+ lock (KernelContext.Processes)
+ {
+ return KernelContext.Processes.Values.FirstOrDefault(x => x.IsApplication)?.DebugInterface;
+ }
+ }
}
}
diff --git a/src/Ryujinx.HLE/HOS/Kernel/Process/KProcess.cs b/src/Ryujinx.HLE/HOS/Kernel/Process/KProcess.cs
index 478b4e864..0c400b425 100644
--- a/src/Ryujinx.HLE/HOS/Kernel/Process/KProcess.cs
+++ b/src/Ryujinx.HLE/HOS/Kernel/Process/KProcess.cs
@@ -1,6 +1,7 @@
using Ryujinx.Common;
using Ryujinx.Common.Logging;
using Ryujinx.Cpu;
+using Ryujinx.HLE.Debugger;
using Ryujinx.HLE.Exceptions;
using Ryujinx.HLE.HOS.Kernel.Common;
using Ryujinx.HLE.HOS.Kernel.Memory;
@@ -11,6 +12,8 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
+using ExceptionCallback = Ryujinx.Cpu.ExceptionCallback;
+using ExceptionCallbackNoArgs = Ryujinx.Cpu.ExceptionCallbackNoArgs;
namespace Ryujinx.HLE.HOS.Kernel.Process
{
@@ -89,6 +92,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
public IVirtualMemoryManager CpuMemory => Context.AddressSpace;
public HleProcessDebugger Debugger { get; private set; }
+ public IDebuggableProcess DebugInterface { get; private set; }
+ protected int debugState = (int)DebugState.Running;
public KProcess(KernelContext context, bool allowCodeMemoryForJit = false) : base(context)
{
@@ -110,6 +115,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
_threads = [];
Debugger = new HleProcessDebugger(this);
+ DebugInterface = new DebuggerInterface(this);
}
public Result InitializeKip(
@@ -679,6 +685,12 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
SetState(newState);
+ if (KernelContext.Device.Configuration.DebuggerSuspendOnStart && IsApplication)
+ {
+ mainThread.Suspend(ThreadSchedState.ThreadPauseFlag);
+ debugState = (int)DebugState.Stopped;
+ }
+
result = mainThread.Start();
if (result != Result.Success)
@@ -727,9 +739,19 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
public IExecutionContext CreateExecutionContext()
{
+ ExceptionCallback breakCallback = null;
+ ExceptionCallbackNoArgs stepCallback = null;
+
+ if (KernelContext.Device.Configuration.EnableGdbStub)
+ {
+ breakCallback = KernelContext.Device.Debugger.BreakHandler;
+ stepCallback = KernelContext.Device.Debugger.StepHandler;
+ }
+
return Context?.CreateExecutionContext(new ExceptionCallbacks(
InterruptHandler,
- null,
+ breakCallback,
+ stepCallback,
KernelContext.SyscallHandler.SvcCall,
UndefinedInstructionHandler));
}
@@ -1174,5 +1196,157 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
{
return Capabilities.IsSvcPermitted(svcId);
}
+
+ private class DebuggerInterface : IDebuggableProcess
+ {
+ private Barrier StepBarrier;
+ private readonly KProcess _parent;
+ private readonly KernelContext _kernelContext;
+ private KThread steppingThread;
+
+ public DebuggerInterface(KProcess p)
+ {
+ _parent = p;
+ _kernelContext = p.KernelContext;
+ StepBarrier = new(2);
+ }
+
+ public void DebugStop()
+ {
+ if (Interlocked.CompareExchange(ref _parent.debugState, (int)DebugState.Stopping,
+ (int)DebugState.Running) != (int)DebugState.Running)
+ {
+ return;
+ }
+
+ _kernelContext.CriticalSection.Enter();
+ lock (_parent._threadingLock)
+ {
+ foreach (KThread thread in _parent._threads)
+ {
+ thread.Suspend(ThreadSchedState.ThreadPauseFlag);
+ thread.Context.RequestInterrupt();
+ if (!thread.DebugHalt.WaitOne(TimeSpan.FromMilliseconds(50)))
+ {
+ Logger.Warning?.Print(LogClass.Kernel, $"Failed to suspend thread {thread.ThreadUid} in time.");
+ }
+ }
+ }
+
+ _parent.debugState = (int)DebugState.Stopped;
+ _kernelContext.CriticalSection.Leave();
+ }
+
+ public void DebugContinue()
+ {
+ if (Interlocked.CompareExchange(ref _parent.debugState, (int)DebugState.Running,
+ (int)DebugState.Stopped) != (int)DebugState.Stopped)
+ {
+ return;
+ }
+
+ _kernelContext.CriticalSection.Enter();
+ lock (_parent._threadingLock)
+ {
+ foreach (KThread thread in _parent._threads)
+ {
+ thread.Resume(ThreadSchedState.ThreadPauseFlag);
+ }
+ }
+ _kernelContext.CriticalSection.Leave();
+ }
+
+ public bool DebugStep(KThread target)
+ {
+ if (_parent.debugState != (int)DebugState.Stopped)
+ {
+ return false;
+ }
+ _kernelContext.CriticalSection.Enter();
+ steppingThread = target;
+ bool waiting = target.MutexOwner != null || target.WaitingSync || target.WaitingInArbitration;
+ target.Context.RequestDebugStep();
+ if (waiting)
+ {
+ lock (_parent._threadingLock)
+ {
+ foreach (KThread thread in _parent._threads)
+ {
+ thread.Resume(ThreadSchedState.ThreadPauseFlag);
+ }
+ }
+ }
+ else
+ {
+ target.Resume(ThreadSchedState.ThreadPauseFlag);
+ }
+ _kernelContext.CriticalSection.Leave();
+
+ StepBarrier.SignalAndWait();
+
+ _kernelContext.CriticalSection.Enter();
+ steppingThread = null;
+ if (waiting)
+ {
+ lock (_parent._threadingLock)
+ {
+ foreach (KThread thread in _parent._threads)
+ {
+ thread.Suspend(ThreadSchedState.ThreadPauseFlag);
+ }
+ }
+ }
+ else
+ {
+ target.Suspend(ThreadSchedState.ThreadPauseFlag);
+ }
+ _kernelContext.CriticalSection.Leave();
+ StepBarrier.SignalAndWait();
+ return true;
+ }
+
+ public DebugState GetDebugState()
+ {
+ return (DebugState)_parent.debugState;
+ }
+
+ public ulong[] GetThreadUids()
+ {
+ lock (_parent._threadingLock)
+ {
+ var threads = _parent._threads.Where(x => !x.TerminationRequested).ToArray();
+ return threads.Select(x => x.ThreadUid).ToArray();
+ }
+ }
+
+ public KThread GetThread(ulong threadUid)
+ {
+ lock (_parent._threadingLock)
+ {
+ var threads = _parent._threads.Where(x => !x.TerminationRequested).ToArray();
+ return threads.FirstOrDefault(x => x.ThreadUid == threadUid);
+ }
+ }
+
+ public void DebugInterruptHandler(IExecutionContext ctx)
+ {
+ _kernelContext.CriticalSection.Enter();
+ bool stepping = steppingThread != null;
+ _kernelContext.CriticalSection.Leave();
+ if (stepping)
+ {
+ StepBarrier.SignalAndWait();
+ StepBarrier.SignalAndWait();
+ }
+ _parent.InterruptHandler(ctx);
+ }
+
+ public IVirtualMemoryManager CpuMemory { get { return _parent.CpuMemory; } }
+
+ public void InvalidateCacheRegion(ulong address, ulong size)
+ {
+ _parent.Context.InvalidateCacheRegion(address, size);
+ }
+ }
}
}
diff --git a/src/Ryujinx.HLE/HOS/Kernel/Process/ProcessExecutionContext.cs b/src/Ryujinx.HLE/HOS/Kernel/Process/ProcessExecutionContext.cs
index b8118fbb4..f0e44c4b7 100644
--- a/src/Ryujinx.HLE/HOS/Kernel/Process/ProcessExecutionContext.cs
+++ b/src/Ryujinx.HLE/HOS/Kernel/Process/ProcessExecutionContext.cs
@@ -1,5 +1,6 @@
using ARMeilleure.State;
using Ryujinx.Cpu;
+using System.Threading;
namespace Ryujinx.HLE.HOS.Kernel.Process
{
@@ -17,10 +18,14 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
public bool IsAarch32 { get => false; set { } }
+ public ulong ThreadUid { get; set; }
+
public bool Running { get; private set; } = true;
private readonly ulong[] _x = new ulong[32];
+ public ulong DebugPc { get; set; }
+
public ulong GetX(int index) => _x[index];
public void SetX(int index, ulong value) => _x[index] = value;
@@ -31,6 +36,10 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
{
}
+ public void RequestDebugStep()
+ {
+ }
+
public void StopRunning()
{
Running = false;
diff --git a/src/Ryujinx.HLE/HOS/Kernel/Threading/KScheduler.cs b/src/Ryujinx.HLE/HOS/Kernel/Threading/KScheduler.cs
index 7471702c3..54b20ff99 100644
--- a/src/Ryujinx.HLE/HOS/Kernel/Threading/KScheduler.cs
+++ b/src/Ryujinx.HLE/HOS/Kernel/Threading/KScheduler.cs
@@ -301,6 +301,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
currentThread.SchedulerWaitEvent.Reset();
currentThread.ThreadContext.Unlock();
+ currentThread.DebugHalt.Set();
// Wake all the threads that might be waiting until this thread context is unlocked.
for (int core = 0; core < CpuCoresCount; core++)
diff --git a/src/Ryujinx.HLE/HOS/Kernel/Threading/KThread.cs b/src/Ryujinx.HLE/HOS/Kernel/Threading/KThread.cs
index b5a14ad5b..2b5d11244 100644
--- a/src/Ryujinx.HLE/HOS/Kernel/Threading/KThread.cs
+++ b/src/Ryujinx.HLE/HOS/Kernel/Threading/KThread.cs
@@ -1,5 +1,6 @@
using Ryujinx.Common.Logging;
using Ryujinx.Cpu;
+using Ryujinx.HLE.Debugger;
using Ryujinx.HLE.HOS.Kernel.Common;
using Ryujinx.HLE.HOS.Kernel.Process;
using Ryujinx.HLE.HOS.Kernel.SupervisorCall;
@@ -114,6 +115,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
private readonly Lock _activityOperationLock = new();
+ internal readonly ManualResetEvent DebugHalt = new(false);
+
public KThread(KernelContext context) : base(context)
{
WaitSyncObjects = new KSynchronizationObject[MaxWaitSyncObjects];
@@ -202,8 +205,10 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
}
Context.TpidrroEl0 = (long)_tlsAddress;
+ Context.DebugPc = _entrypoint;
ThreadUid = KernelContext.NewThreadUid();
+ Context.ThreadUid = ThreadUid;
HostThread.Name = customThreadStart != null ? $"HLE.OsThread.{ThreadUid}" : $"HLE.GuestThread.{ThreadUid}";
@@ -307,7 +312,9 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
{
KernelContext.CriticalSection.Enter();
- if (Owner != null && Owner.PinnedThreads[KernelStatic.GetCurrentThread().CurrentCore] == this)
+ KThread currentThread = KernelStatic.GetCurrentThread();
+
+ if (Owner != null && currentThread != null && Owner.PinnedThreads[currentThread.CurrentCore] == this)
{
Owner.UnpinThread(this);
}
@@ -362,7 +369,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
{
ThreadSchedState state = PrepareForTermination();
- if (state != ThreadSchedState.TerminationPending)
+ if (KernelStatic.GetCurrentThread() == this && state != ThreadSchedState.TerminationPending)
{
KernelContext.Synchronization.WaitFor(new KSynchronizationObject[] { this }, -1, out _);
}
@@ -1248,6 +1255,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
private void ThreadStart()
{
_schedulerWaitEvent.WaitOne();
+ DebugHalt.Reset();
KernelStatic.SetKernelContext(KernelContext, this);
if (_customThreadStart != null)
diff --git a/src/Ryujinx.HLE/HleConfiguration.cs b/src/Ryujinx.HLE/HleConfiguration.cs
index 10c2a1f30..e2f95ede7 100644
--- a/src/Ryujinx.HLE/HleConfiguration.cs
+++ b/src/Ryujinx.HLE/HleConfiguration.cs
@@ -194,6 +194,21 @@ namespace Ryujinx.HLE
///
public Action RefreshInputConfig { internal get; set; }
+ ///
+ /// Enables gdbstub to allow for debugging of the guest .
+ ///
+ public bool EnableGdbStub { internal get; set; }
+
+ ///
+ /// A TCP port to use to expose a gdbstub for a debugger to connect to.
+ ///
+ public ushort GdbStubPort { internal get; set; }
+
+ ///
+ /// Suspend execution when starting an application
+ ///
+ public bool DebuggerSuspendOnStart { internal get; set; }
+
///
/// The desired hacky workarounds.
///
@@ -222,6 +237,9 @@ namespace Ryujinx.HLE
bool multiplayerDisableP2p,
string multiplayerLdnPassphrase,
string multiplayerLdnServer,
+ bool enableGdbStub,
+ ushort gdbStubPort,
+ bool debuggerSuspendOnStart,
int customVSyncInterval,
EnabledDirtyHack[] dirtyHacks = null)
{
@@ -248,6 +266,9 @@ namespace Ryujinx.HLE
MultiplayerDisableP2p = multiplayerDisableP2p;
MultiplayerLdnPassphrase = multiplayerLdnPassphrase;
MultiplayerLdnServer = multiplayerLdnServer;
+ EnableGdbStub = enableGdbStub;
+ GdbStubPort = gdbStubPort;
+ DebuggerSuspendOnStart = debuggerSuspendOnStart;
Hacks = dirtyHacks ?? [];
}
diff --git a/src/Ryujinx.HLE/Ryujinx.HLE.csproj b/src/Ryujinx.HLE/Ryujinx.HLE.csproj
index 5139d9276..1938796e8 100644
--- a/src/Ryujinx.HLE/Ryujinx.HLE.csproj
+++ b/src/Ryujinx.HLE/Ryujinx.HLE.csproj
@@ -33,6 +33,12 @@
+
+
+
+
+
+
@@ -42,6 +48,12 @@
+
+
+
+
+
+
diff --git a/src/Ryujinx.HLE/Switch.cs b/src/Ryujinx.HLE/Switch.cs
index bdcbe82c7..ed6d57b2d 100644
--- a/src/Ryujinx.HLE/Switch.cs
+++ b/src/Ryujinx.HLE/Switch.cs
@@ -14,6 +14,7 @@ using Ryujinx.HLE.Loaders.Processes;
using Ryujinx.HLE.UI;
using Ryujinx.Memory;
using System;
+using System.Threading;
namespace Ryujinx.HLE
{
@@ -41,6 +42,7 @@ namespace Ryujinx.HLE
public Hid Hid { get; }
public TamperMachine TamperMachine { get; }
public IHostUIHandler UIHandler { get; }
+ public Debugger.Debugger Debugger { get; }
public int CpuCoresCount = 4; // Switch has a quad-core Tegra X1 SoC
@@ -72,6 +74,7 @@ namespace Ryujinx.HLE
AudioDeviceDriver = new CompatLayerHardwareDeviceDriver(Configuration.AudioDeviceDriver);
Memory = new MemoryBlock(Configuration.MemoryConfiguration.ToDramSize(), memoryAllocationFlags);
Gpu = new GpuContext(Configuration.GpuRenderer, DirtyHacks);
+ Debugger = Configuration.EnableGdbStub ? new Debugger.Debugger(this, Configuration.GdbStubPort) : null;
System = new HOS.Horizon(this);
Statistics = new PerformanceStatistics(this);
Hid = new Hid(this, System.HidStorage);
@@ -173,6 +176,7 @@ namespace Ryujinx.HLE
AudioDeviceDriver.Dispose();
FileSystem.Dispose();
Memory.Dispose();
+ Debugger.Dispose();
TitleIDs.CurrentApplication.Value = null;
Shared = null;
diff --git a/src/Ryujinx/Headless/HeadlessRyujinx.Init.cs b/src/Ryujinx/Headless/HeadlessRyujinx.Init.cs
index f15d24e8a..dc7b8625e 100644
--- a/src/Ryujinx/Headless/HeadlessRyujinx.Init.cs
+++ b/src/Ryujinx/Headless/HeadlessRyujinx.Init.cs
@@ -338,6 +338,9 @@ namespace Ryujinx.Headless
false,
string.Empty,
string.Empty,
+ options.EnableGdbStub,
+ options.GdbStubPort,
+ options.DebuggerSuspendOnStart,
options.CustomVSyncInterval
)
.Configure(
diff --git a/src/Ryujinx/Headless/Options.cs b/src/Ryujinx/Headless/Options.cs
index 49050005c..876d0c936 100644
--- a/src/Ryujinx/Headless/Options.cs
+++ b/src/Ryujinx/Headless/Options.cs
@@ -423,6 +423,17 @@ namespace Ryujinx.Headless
[Option("skip-user-profiles-manager", Required = false, Default = false, HelpText = "Enable skips the Profiles Manager popup during gameplay. Select the desired profile before starting the game")]
public bool SkipUserProfilesManager { get; set; }
+ // Debug
+
+ [Option("enable-gdb-stub", Required = false, Default = false, HelpText = "Enables the GDB stub so that a developer can attach a debugger to the emulated process.")]
+ public bool EnableGdbStub { get; set; }
+
+ [Option("gdb-stub-port", Required = false, Default = 55555, HelpText = "Specifies which TCP port the GDB stub listens on.")]
+ public ushort GdbStubPort { get; set; }
+
+ [Option("suspend-on-start", Required = false, Default = false, HelpText = "Suspend execution when starting an application.")]
+ public bool DebuggerSuspendOnStart { get; set; }
+
// Values
[Value(0, MetaName = "input", HelpText = "Input to load.", Required = true)]
diff --git a/src/Ryujinx/Systems/AppHost.cs b/src/Ryujinx/Systems/AppHost.cs
index 1c5f64309..f11280d62 100644
--- a/src/Ryujinx/Systems/AppHost.cs
+++ b/src/Ryujinx/Systems/AppHost.cs
@@ -218,6 +218,10 @@ namespace Ryujinx.Ava.Systems
ConfigurationState.Instance.Multiplayer.LdnServer.Event += UpdateLdnServerState;
ConfigurationState.Instance.Multiplayer.DisableP2p.Event += UpdateDisableP2pState;
+ ConfigurationState.Instance.Debug.EnableGdbStub.Event += UpdateEnableGdbStubState;
+ ConfigurationState.Instance.Debug.GdbStubPort.Event += UpdateGdbStubPortState;
+ ConfigurationState.Instance.Debug.DebuggerSuspendOnStart.Event += UpdateDebuggerSuspendOnStartState;
+
_gpuCancellationTokenSource = new CancellationTokenSource();
_gpuDoneEvent = new ManualResetEvent(false);
}
@@ -564,6 +568,21 @@ namespace Ryujinx.Ava.Systems
Device.Configuration.MultiplayerDisableP2p = e.NewValue;
}
+ private void UpdateEnableGdbStubState(object sender, ReactiveEventArgs e)
+ {
+ Device.Configuration.EnableGdbStub = e.NewValue;
+ }
+
+ private void UpdateGdbStubPortState(object sender, ReactiveEventArgs e)
+ {
+ Device.Configuration.GdbStubPort = e.NewValue;
+ }
+
+ private void UpdateDebuggerSuspendOnStartState(object sender, ReactiveEventArgs e)
+ {
+ Device.Configuration.DebuggerSuspendOnStart = e.NewValue;
+ }
+
public void Stop()
{
_isActive = false;
diff --git a/src/Ryujinx/Systems/Configuration/ConfigurationFileFormat.cs b/src/Ryujinx/Systems/Configuration/ConfigurationFileFormat.cs
index c21383349..26ea73f73 100644
--- a/src/Ryujinx/Systems/Configuration/ConfigurationFileFormat.cs
+++ b/src/Ryujinx/Systems/Configuration/ConfigurationFileFormat.cs
@@ -464,6 +464,21 @@ namespace Ryujinx.Ava.Systems.Configuration
///
public bool UseHypervisor { get; set; }
+ ///
+ /// Enables or disables the GDB stub
+ ///
+ public bool EnableGdbStub { get; set; }
+
+ ///
+ /// Which TCP port should the GDB stub listen on
+ ///
+ public ushort GdbStubPort { get; set; }
+
+ ///
+ /// Suspend execution when starting an application
+ ///
+ public bool DebuggerSuspendOnStart { get; set; }
+
///
/// Show toggles for dirty hacks in the UI.
///
diff --git a/src/Ryujinx/Systems/Configuration/ConfigurationState.Migration.cs b/src/Ryujinx/Systems/Configuration/ConfigurationState.Migration.cs
index afabdb4e3..58f25b783 100644
--- a/src/Ryujinx/Systems/Configuration/ConfigurationState.Migration.cs
+++ b/src/Ryujinx/Systems/Configuration/ConfigurationState.Migration.cs
@@ -156,6 +156,10 @@ namespace Ryujinx.Ava.Systems.Configuration
Multiplayer.LdnPassphrase.Value = cff.MultiplayerLdnPassphrase;
Multiplayer.LdnServer.Value = cff.LdnServer;
+ Debug.EnableGdbStub.Value = cff.EnableGdbStub;
+ Debug.GdbStubPort.Value = cff.GdbStubPort;
+ Debug.DebuggerSuspendOnStart.Value = cff.DebuggerSuspendOnStart;
+
{
Hacks.ShowDirtyHacks.Value = shouldLoadFromFile ? cff.ShowDirtyHacks : Hacks.ShowDirtyHacks.Value; // Get from global config only
diff --git a/src/Ryujinx/Systems/Configuration/ConfigurationState.Model.cs b/src/Ryujinx/Systems/Configuration/ConfigurationState.Model.cs
index 29a390b26..bc8fdb40a 100644
--- a/src/Ryujinx/Systems/Configuration/ConfigurationState.Model.cs
+++ b/src/Ryujinx/Systems/Configuration/ConfigurationState.Model.cs
@@ -703,6 +703,37 @@ namespace Ryujinx.Ava.Systems.Configuration
}
}
+ ///
+ /// Debug configuration section
+ ///
+ public class DebugSection
+ {
+ ///
+ /// Enables or disables the GDB stub
+ ///
+ public ReactiveObject EnableGdbStub { get; private set; }
+
+ ///
+ /// Which TCP port should the GDB stub listen on
+ ///
+ public ReactiveObject GdbStubPort { get; private set; }
+
+ ///
+ /// Suspend execution when starting an application
+ ///
+ public ReactiveObject DebuggerSuspendOnStart { get; private set; }
+
+ public DebugSection()
+ {
+ EnableGdbStub = new ReactiveObject();
+ EnableGdbStub.LogChangesToValue(nameof(EnableGdbStub));
+ GdbStubPort = new ReactiveObject();
+ GdbStubPort.LogChangesToValue(nameof(GdbStubPort));
+ DebuggerSuspendOnStart = new ReactiveObject();
+ DebuggerSuspendOnStart.LogChangesToValue(nameof(DebuggerSuspendOnStart));
+ }
+ }
+
public class HacksSection
{
///
@@ -801,6 +832,11 @@ namespace Ryujinx.Ava.Systems.Configuration
///
public MultiplayerSection Multiplayer { get; private set; }
+ ///
+ /// The Debug
+ ///
+ public DebugSection Debug { get; private set; }
+
///
/// The Dirty Hacks section
///
@@ -854,6 +890,7 @@ namespace Ryujinx.Ava.Systems.Configuration
Graphics = new GraphicsSection();
Hid = new HidSection();
Multiplayer = new MultiplayerSection();
+ Debug = new DebugSection();
Hacks = new HacksSection();
UpdateCheckerType = new ReactiveObject();
FocusLostActionType = new ReactiveObject();
@@ -893,6 +930,9 @@ namespace Ryujinx.Ava.Systems.Configuration
Multiplayer.DisableP2p,
Multiplayer.LdnPassphrase,
Multiplayer.GetLdnServer(),
+ Debug.EnableGdbStub,
+ Debug.GdbStubPort,
+ Debug.DebuggerSuspendOnStart,
Graphics.CustomVSyncInterval,
Hacks.ShowDirtyHacks ? Hacks.EnabledHacks : null);
}
diff --git a/src/Ryujinx/Systems/Configuration/ConfigurationState.cs b/src/Ryujinx/Systems/Configuration/ConfigurationState.cs
index 4a565d5d3..185aedf64 100644
--- a/src/Ryujinx/Systems/Configuration/ConfigurationState.cs
+++ b/src/Ryujinx/Systems/Configuration/ConfigurationState.cs
@@ -147,6 +147,9 @@ namespace Ryujinx.Ava.Systems.Configuration
MultiplayerDisableP2p = Multiplayer.DisableP2p,
MultiplayerLdnPassphrase = Multiplayer.LdnPassphrase,
LdnServer = Multiplayer.LdnServer,
+ EnableGdbStub = Debug.EnableGdbStub,
+ GdbStubPort = Debug.GdbStubPort,
+ DebuggerSuspendOnStart = Debug.DebuggerSuspendOnStart,
ShowDirtyHacks = Hacks.ShowDirtyHacks,
DirtyHacks = Hacks.EnabledHacks.Select(it => it.Pack()).ToArray(),
};
@@ -324,6 +327,9 @@ namespace Ryujinx.Ava.Systems.Configuration
},
}
];
+ Debug.EnableGdbStub.Value = false;
+ Debug.GdbStubPort.Value = 55555;
+ Debug.DebuggerSuspendOnStart.Value = false;
}
private static GraphicsBackend DefaultGraphicsBackend()
diff --git a/src/Ryujinx/UI/ViewModels/SettingsViewModel.cs b/src/Ryujinx/UI/ViewModels/SettingsViewModel.cs
index 654eb0c43..54fd951fb 100644
--- a/src/Ryujinx/UI/ViewModels/SettingsViewModel.cs
+++ b/src/Ryujinx/UI/ViewModels/SettingsViewModel.cs
@@ -71,6 +71,10 @@ namespace Ryujinx.Ava.UI.ViewModels
private string _ldnPassphrase;
[ObservableProperty] private string _ldnServer;
+ private bool _enableGDBStub;
+ private ushort _gdbStubPort;
+ private bool _debuggerSuspendOnStart;
+
public SettingsHacksViewModel DirtyHacks { get; }
private readonly bool _isGameRunning;
@@ -387,6 +391,36 @@ namespace Ryujinx.Ava.UI.ViewModels
public bool IsInvalidLdnPassphraseVisible { get; set; }
+ public bool EnableGdbStub
+ {
+ get => _enableGDBStub;
+ set
+ {
+ _enableGDBStub = value;
+ ConfigurationState.Instance.Debug.EnableGdbStub.Value = _enableGDBStub;
+ }
+ }
+
+ public ushort GDBStubPort
+ {
+ get => _gdbStubPort;
+ set
+ {
+ _gdbStubPort = value;
+ ConfigurationState.Instance.Debug.GdbStubPort.Value = _gdbStubPort;
+ }
+ }
+
+ public bool DebuggerSuspendOnStart
+ {
+ get => _debuggerSuspendOnStart;
+ set
+ {
+ _debuggerSuspendOnStart = value;
+ ConfigurationState.Instance.Debug.DebuggerSuspendOnStart.Value = _debuggerSuspendOnStart;
+ }
+ }
+
public SettingsViewModel(VirtualFileSystem virtualFileSystem, ContentManager contentManager) : this()
{
_virtualFileSystem = virtualFileSystem;
@@ -680,10 +714,16 @@ namespace Ryujinx.Ava.UI.ViewModels
FsGlobalAccessLogMode = config.System.FsGlobalAccessLogMode;
OpenglDebugLevel = (int)config.Logger.GraphicsDebugLevel.Value;
+ // Multiplayer
MultiplayerModeIndex = (int)config.Multiplayer.Mode.Value;
DisableP2P = config.Multiplayer.DisableP2p;
LdnPassphrase = config.Multiplayer.LdnPassphrase;
LdnServer = config.Multiplayer.LdnServer;
+
+ // Debug
+ EnableGdbStub = config.Debug.EnableGdbStub.Value;
+ GDBStubPort = config.Debug.GdbStubPort.Value;
+ DebuggerSuspendOnStart = config.Debug.DebuggerSuspendOnStart.Value;
}
public void SaveSettings(bool global = false)
@@ -800,12 +840,18 @@ namespace Ryujinx.Ava.UI.ViewModels
config.System.FsGlobalAccessLogMode.Value = FsGlobalAccessLogMode;
config.Logger.GraphicsDebugLevel.Value = (GraphicsDebugLevel)OpenglDebugLevel;
+ // Multiplayer
config.Multiplayer.LanInterfaceId.Value = _networkInterfaces[NetworkInterfaceList[NetworkInterfaceIndex]];
config.Multiplayer.Mode.Value = (MultiplayerMode)MultiplayerModeIndex;
config.Multiplayer.DisableP2p.Value = DisableP2P;
config.Multiplayer.LdnPassphrase.Value = LdnPassphrase;
config.Multiplayer.LdnServer.Value = LdnServer;
+ // Debug
+ config.Debug.EnableGdbStub.Value = EnableGdbStub;
+ config.Debug.GdbStubPort.Value = GDBStubPort;
+ config.Debug.DebuggerSuspendOnStart.Value = DebuggerSuspendOnStart;
+
// Dirty Hacks
config.Hacks.Xc2MenuSoftlockFix.Value = DirtyHacks.Xc2MenuSoftlockFix;
config.Hacks.DisableNifmIsAnyInternetRequestAccepted.Value =
diff --git a/src/Ryujinx/UI/Views/Settings/SettingsDebugView.axaml b/src/Ryujinx/UI/Views/Settings/SettingsDebugView.axaml
new file mode 100644
index 000000000..036471059
--- /dev/null
+++ b/src/Ryujinx/UI/Views/Settings/SettingsDebugView.axaml
@@ -0,0 +1,64 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Ryujinx/UI/Views/Settings/SettingsDebugView.axaml.cs b/src/Ryujinx/UI/Views/Settings/SettingsDebugView.axaml.cs
new file mode 100644
index 000000000..14a65b8b2
--- /dev/null
+++ b/src/Ryujinx/UI/Views/Settings/SettingsDebugView.axaml.cs
@@ -0,0 +1,13 @@
+using Avalonia.Controls;
+
+namespace Ryujinx.Ava.UI.Views.Settings
+{
+ public partial class SettingsDebugView : UserControl
+ {
+ public SettingsDebugView()
+ {
+ InitializeComponent();
+ }
+ }
+}
+
diff --git a/src/Ryujinx/UI/Windows/SettingsWindow.axaml b/src/Ryujinx/UI/Windows/SettingsWindow.axaml
index 15d174123..9bfe0a9db 100644
--- a/src/Ryujinx/UI/Windows/SettingsWindow.axaml
+++ b/src/Ryujinx/UI/Windows/SettingsWindow.axaml
@@ -46,6 +46,7 @@
+
+
Date: Sat, 21 Jun 2025 00:02:31 +0800
Subject: [PATCH 27/51] gdb: Remove redundant log
---
src/Ryujinx.HLE/Debugger/Debugger.cs | 1 -
1 file changed, 1 deletion(-)
diff --git a/src/Ryujinx.HLE/Debugger/Debugger.cs b/src/Ryujinx.HLE/Debugger/Debugger.cs
index 33c380175..3eebb0b38 100644
--- a/src/Ryujinx.HLE/Debugger/Debugger.cs
+++ b/src/Ryujinx.HLE/Debugger/Debugger.cs
@@ -238,7 +238,6 @@ namespace Ryujinx.HLE.Debugger
private void ProcessCommand(string cmd)
{
- Logger.Debug?.Print(LogClass.GdbStub, $"Receive: {cmd}");
StringStream ss = new StringStream(cmd);
switch (ss.ReadChar())
From 8682c51ef794f0b2643874dd88ff9d1749566bc9 Mon Sep 17 00:00:00 2001
From: Coxxs <58-coxxs@users.noreply.git.ryujinx.app>
Date: Sat, 21 Jun 2025 01:04:44 +0800
Subject: [PATCH 28/51] gdb: Fix crash on exit when not using Debugger
---
src/Ryujinx.HLE/Switch.cs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/Ryujinx.HLE/Switch.cs b/src/Ryujinx.HLE/Switch.cs
index ed6d57b2d..e1aa8e0e4 100644
--- a/src/Ryujinx.HLE/Switch.cs
+++ b/src/Ryujinx.HLE/Switch.cs
@@ -176,7 +176,7 @@ namespace Ryujinx.HLE
AudioDeviceDriver.Dispose();
FileSystem.Dispose();
Memory.Dispose();
- Debugger.Dispose();
+ Debugger?.Dispose();
TitleIDs.CurrentApplication.Value = null;
Shared = null;
From 737afbfa2fce2b0440c7bedaab89019b8ffab8b7 Mon Sep 17 00:00:00 2001
From: Coxxs <58-coxxs@users.noreply.git.ryujinx.app>
Date: Sat, 21 Jun 2025 01:05:25 +0800
Subject: [PATCH 29/51] gdb: Wait for the application to start if user connect
gdb too early
---
src/Ryujinx.HLE/Debugger/Debugger.cs | 16 +++++++++++++++-
1 file changed, 15 insertions(+), 1 deletion(-)
diff --git a/src/Ryujinx.HLE/Debugger/Debugger.cs b/src/Ryujinx.HLE/Debugger/Debugger.cs
index 3eebb0b38..3ec4dbb93 100644
--- a/src/Ryujinx.HLE/Debugger/Debugger.cs
+++ b/src/Ryujinx.HLE/Debugger/Debugger.cs
@@ -47,7 +47,7 @@ namespace Ryujinx.HLE.Debugger
MessageHandlerThread.Start();
}
- private IDebuggableProcess DebugProcess => Device.System.DebugGetApplicationProcess();
+ private IDebuggableProcess DebugProcess => Device.System?.DebugGetApplicationProcess();
private KThread[] GetThreads() => DebugProcess.GetThreadUids().Select(x => DebugProcess.GetThread(x)).ToArray();
private bool IsProcessAarch32 => DebugProcess.GetThread(gThread.Value).Context.IsAarch32;
private KernelContext KernelContext => Device.System.KernelContext;
@@ -761,6 +761,20 @@ namespace Ryujinx.HLE.Debugger
{
return;
}
+
+ // If the user connects before the application is running, wait for the application to start.
+ int retries = 10;
+ while (DebugProcess == null && retries-- > 0)
+ {
+ Thread.Sleep(200);
+ }
+ if (DebugProcess == null)
+ {
+ Logger.Warning?.Print(LogClass.GdbStub, "Application is not running, cannot accept GDB client connection");
+ ClientSocket.Close();
+ continue;
+ }
+
ClientSocket.NoDelay = true;
ReadStream = new NetworkStream(ClientSocket, System.IO.FileAccess.Read);
WriteStream = new NetworkStream(ClientSocket, System.IO.FileAccess.Write);
From d81dca0dcc65349aac2e9dc8063f6337b29d8f59 Mon Sep 17 00:00:00 2001
From: Coxxs <58-coxxs@users.noreply.git.ryujinx.app>
Date: Sat, 21 Jun 2025 01:05:43 +0800
Subject: [PATCH 30/51] gdb: Add notice when application is suspended on start
---
src/Ryujinx.HLE/HOS/Kernel/Process/KProcess.cs | 1 +
1 file changed, 1 insertion(+)
diff --git a/src/Ryujinx.HLE/HOS/Kernel/Process/KProcess.cs b/src/Ryujinx.HLE/HOS/Kernel/Process/KProcess.cs
index 0c400b425..249e9720a 100644
--- a/src/Ryujinx.HLE/HOS/Kernel/Process/KProcess.cs
+++ b/src/Ryujinx.HLE/HOS/Kernel/Process/KProcess.cs
@@ -689,6 +689,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
{
mainThread.Suspend(ThreadSchedState.ThreadPauseFlag);
debugState = (int)DebugState.Stopped;
+ Logger.Notice.Print(LogClass.Kernel, $"Application is suspended on start for debugging.");
}
result = mainThread.Start();
From 785641a40262d1d14ce8fe6574231cf7f30c5b7f Mon Sep 17 00:00:00 2001
From: Coxxs <58-coxxs@users.noreply.git.ryujinx.app>
Date: Sat, 21 Jun 2025 01:24:06 +0800
Subject: [PATCH 31/51] gdb: Remove unused using
---
src/ARMeilleure/Instructions/NativeInterface.cs | 1 -
src/ARMeilleure/State/ExecutionContext.cs | 2 --
src/Ryujinx.Cpu/Jit/JitExecutionContext.cs | 1 -
3 files changed, 4 deletions(-)
diff --git a/src/ARMeilleure/Instructions/NativeInterface.cs b/src/ARMeilleure/Instructions/NativeInterface.cs
index c44f24b7c..d43e20d83 100644
--- a/src/ARMeilleure/Instructions/NativeInterface.cs
+++ b/src/ARMeilleure/Instructions/NativeInterface.cs
@@ -3,7 +3,6 @@ using ARMeilleure.State;
using ARMeilleure.Translation;
using System;
using System.Runtime.InteropServices;
-using System.Threading;
using ExecutionContext = ARMeilleure.State.ExecutionContext;
namespace ARMeilleure.Instructions
diff --git a/src/ARMeilleure/State/ExecutionContext.cs b/src/ARMeilleure/State/ExecutionContext.cs
index 2978bf27a..0b0a83c9f 100644
--- a/src/ARMeilleure/State/ExecutionContext.cs
+++ b/src/ARMeilleure/State/ExecutionContext.cs
@@ -1,6 +1,4 @@
using ARMeilleure.Memory;
-using System.Collections.Concurrent;
-using System.Diagnostics;
using System.Threading;
namespace ARMeilleure.State
diff --git a/src/Ryujinx.Cpu/Jit/JitExecutionContext.cs b/src/Ryujinx.Cpu/Jit/JitExecutionContext.cs
index d4775f3ed..f00acc1d7 100644
--- a/src/Ryujinx.Cpu/Jit/JitExecutionContext.cs
+++ b/src/Ryujinx.Cpu/Jit/JitExecutionContext.cs
@@ -1,6 +1,5 @@
using ARMeilleure.Memory;
using ARMeilleure.State;
-using System.Threading;
using ExecutionContext = ARMeilleure.State.ExecutionContext;
namespace Ryujinx.Cpu.Jit
From 36bb910e67e5cb5826a312b5771fe2fd3d1d9469 Mon Sep 17 00:00:00 2001
From: Coxxs <58-coxxs@users.noreply.git.ryujinx.app>
Date: Sat, 21 Jun 2025 01:47:19 +0800
Subject: [PATCH 32/51] gdb: Fix crash on stop emulation if gdb stub is enabled
with app running
---
src/Ryujinx.HLE/HOS/Kernel/Process/KProcess.cs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/Ryujinx.HLE/HOS/Kernel/Process/KProcess.cs b/src/Ryujinx.HLE/HOS/Kernel/Process/KProcess.cs
index 249e9720a..7edf263e2 100644
--- a/src/Ryujinx.HLE/HOS/Kernel/Process/KProcess.cs
+++ b/src/Ryujinx.HLE/HOS/Kernel/Process/KProcess.cs
@@ -743,7 +743,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
ExceptionCallback breakCallback = null;
ExceptionCallbackNoArgs stepCallback = null;
- if (KernelContext.Device.Configuration.EnableGdbStub)
+ if (KernelContext.Device.Configuration.EnableGdbStub && KernelContext.Device.Debugger != null)
{
breakCallback = KernelContext.Device.Debugger.BreakHandler;
stepCallback = KernelContext.Device.Debugger.StepHandler;
From e547c4f5f4b92b1a3e6af3b80adeb37582f78eef Mon Sep 17 00:00:00 2001
From: Coxxs <58-coxxs@users.noreply.git.ryujinx.app>
Date: Sat, 21 Jun 2025 05:25:22 +0800
Subject: [PATCH 33/51] gdb: Fix GdbWriteRegister endianness
---
src/Ryujinx.HLE/Debugger/Debugger.cs | 28 ++++++++++++++--------------
1 file changed, 14 insertions(+), 14 deletions(-)
diff --git a/src/Ryujinx.HLE/Debugger/Debugger.cs b/src/Ryujinx.HLE/Debugger/Debugger.cs
index 3ec4dbb93..4350aab53 100644
--- a/src/Ryujinx.HLE/Debugger/Debugger.cs
+++ b/src/Ryujinx.HLE/Debugger/Debugger.cs
@@ -86,38 +86,38 @@ namespace Ryujinx.HLE.Debugger
{
case >= 0 and <= 31:
{
- ulong value = ss.ReadLengthAsHex(16);
+ ulong value = ss.ReadLengthAsLEHex(16);
state.SetX(gdbRegId, value);
return true;
}
case 32:
{
- ulong value = ss.ReadLengthAsHex(16);
+ ulong value = ss.ReadLengthAsLEHex(16);
state.DebugPc = value;
return true;
}
case 33:
{
- ulong value = ss.ReadLengthAsHex(8);
+ ulong value = ss.ReadLengthAsLEHex(8);
state.Pstate = (uint)value;
return true;
}
case >= 34 and <= 65:
{
- ulong value0 = ss.ReadLengthAsHex(16);
- ulong value1 = ss.ReadLengthAsHex(16);
+ ulong value0 = ss.ReadLengthAsLEHex(16);
+ ulong value1 = ss.ReadLengthAsLEHex(16);
state.SetV(gdbRegId - 34, new V128(value0, value1));
return true;
}
case 66:
{
- ulong value = ss.ReadLengthAsHex(8);
+ ulong value = ss.ReadLengthAsLEHex(8);
state.Fpsr = (uint)value;
return true;
}
case 67:
{
- ulong value = ss.ReadLengthAsHex(8);
+ ulong value = ss.ReadLengthAsLEHex(8);
state.Fpcr = (uint)value;
return true;
}
@@ -158,32 +158,32 @@ namespace Ryujinx.HLE.Debugger
{
case >= 0 and <= 14:
{
- ulong value = ss.ReadLengthAsHex(8);
+ ulong value = ss.ReadLengthAsLEHex(8);
state.SetX(gdbRegId, value);
return true;
}
case 15:
{
- ulong value = ss.ReadLengthAsHex(8);
+ ulong value = ss.ReadLengthAsLEHex(8);
state.DebugPc = value;
return true;
}
case 16:
{
- ulong value = ss.ReadLengthAsHex(8);
+ ulong value = ss.ReadLengthAsLEHex(8);
state.Pstate = (uint)value;
return true;
}
case >= 17 and <= 32:
{
- ulong value0 = ss.ReadLengthAsHex(16);
- ulong value1 = ss.ReadLengthAsHex(16);
+ ulong value0 = ss.ReadLengthAsLEHex(16);
+ ulong value1 = ss.ReadLengthAsLEHex(16);
state.SetV(gdbRegId - 17, new V128(value0, value1));
return true;
}
case >= 33 and <= 64:
{
- ulong value = ss.ReadLengthAsHex(16);
+ ulong value = ss.ReadLengthAsLEHex(16);
int regId = (gdbRegId - 33);
int regNum = regId / 2;
int shift = regId % 2;
@@ -193,7 +193,7 @@ namespace Ryujinx.HLE.Debugger
}
case 65:
{
- ulong value = ss.ReadLengthAsHex(8);
+ ulong value = ss.ReadLengthAsLEHex(8);
state.Fpsr = (uint)value & FpcrMask;
state.Fpcr = (uint)value & ~FpcrMask;
return true;
From bc68502179111a000c062a356422027583148127 Mon Sep 17 00:00:00 2001
From: Coxxs <58-coxxs@users.noreply.git.ryujinx.app>
Date: Sat, 21 Jun 2025 05:26:48 +0800
Subject: [PATCH 34/51] gdb: Add timeout to prevent deadlock in DebugStep
Deadlock can happen when step at some svc instructions.
---
src/Ryujinx.HLE/HOS/Kernel/Process/KProcess.cs | 13 ++++++++++++-
1 file changed, 12 insertions(+), 1 deletion(-)
diff --git a/src/Ryujinx.HLE/HOS/Kernel/Process/KProcess.cs b/src/Ryujinx.HLE/HOS/Kernel/Process/KProcess.cs
index 7edf263e2..c24d5c3cc 100644
--- a/src/Ryujinx.HLE/HOS/Kernel/Process/KProcess.cs
+++ b/src/Ryujinx.HLE/HOS/Kernel/Process/KProcess.cs
@@ -1283,7 +1283,12 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
}
_kernelContext.CriticalSection.Leave();
- StepBarrier.SignalAndWait();
+ bool stepTimedOut = false;
+ if (!StepBarrier.SignalAndWait(TimeSpan.FromMilliseconds(2000)))
+ {
+ Logger.Warning?.Print(LogClass.Kernel, $"Failed to step thread {target.ThreadUid} in time.");
+ stepTimedOut = true;
+ }
_kernelContext.CriticalSection.Enter();
steppingThread = null;
@@ -1302,6 +1307,12 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
target.Suspend(ThreadSchedState.ThreadPauseFlag);
}
_kernelContext.CriticalSection.Leave();
+
+ if (stepTimedOut)
+ {
+ return false;
+ }
+
StepBarrier.SignalAndWait();
return true;
}
From f630d5ba99e312435fa4868d0066679641f52d08 Mon Sep 17 00:00:00 2001
From: Coxxs <58-coxxs@users.noreply.git.ryujinx.app>
Date: Sat, 21 Jun 2025 05:27:39 +0800
Subject: [PATCH 35/51] gdb: Fix crash when gdb client disconnected in some
cases
---
src/Ryujinx.HLE/Debugger/Debugger.cs | 56 ++++++++++++++++------------
1 file changed, 33 insertions(+), 23 deletions(-)
diff --git a/src/Ryujinx.HLE/Debugger/Debugger.cs b/src/Ryujinx.HLE/Debugger/Debugger.cs
index 4350aab53..608ab977a 100644
--- a/src/Ryujinx.HLE/Debugger/Debugger.cs
+++ b/src/Ryujinx.HLE/Debugger/Debugger.cs
@@ -208,30 +208,40 @@ namespace Ryujinx.HLE.Debugger
while (!_shuttingDown)
{
IMessage msg = Messages.Take();
- switch (msg)
+ try {
+ switch (msg)
+ {
+ case BreakInMessage:
+ Logger.Notice.Print(LogClass.GdbStub, "Break-in requested");
+ CommandQuery();
+ break;
+
+ case SendNackMessage:
+ WriteStream.WriteByte((byte)'-');
+ break;
+
+ case CommandMessage { Command: var cmd }:
+ Logger.Debug?.Print(LogClass.GdbStub, $"Received Command: {cmd}");
+ WriteStream.WriteByte((byte)'+');
+ ProcessCommand(cmd);
+ break;
+
+ case ThreadBreakMessage { Context: var ctx }:
+ DebugProcess.DebugStop();
+ Reply($"T05thread:{ctx.ThreadUid:x};");
+ break;
+
+ case KillMessage:
+ return;
+ }
+ }
+ catch (IOException e)
{
- case BreakInMessage:
- Logger.Notice.Print(LogClass.GdbStub, "Break-in requested");
- CommandQuery();
- break;
-
- case SendNackMessage:
- WriteStream.WriteByte((byte)'-');
- break;
-
- case CommandMessage { Command: var cmd }:
- Logger.Debug?.Print(LogClass.GdbStub, $"Received Command: {cmd}");
- WriteStream.WriteByte((byte)'+');
- ProcessCommand(cmd);
- break;
-
- case ThreadBreakMessage { Context: var ctx }:
- DebugProcess.DebugStop();
- Reply($"T05thread:{ctx.ThreadUid:x};");
- break;
-
- case KillMessage:
- return;
+ Logger.Error?.Print(LogClass.GdbStub, "Error while processing GDB messages", e);
+ }
+ catch (NullReferenceException e)
+ {
+ Logger.Error?.Print(LogClass.GdbStub, "Error while processing GDB messages", e);
}
}
}
From fb1655f1ad33ee610fdf60e52aacb7a132b1e18b Mon Sep 17 00:00:00 2001
From: Coxxs <58-coxxs@users.noreply.git.ryujinx.app>
Date: Sat, 21 Jun 2025 09:37:36 +0800
Subject: [PATCH 36/51] gdb: Update DebugPc during SVC call and break
---
src/ARMeilleure/State/ExecutionContext.cs | 10 ++++++++++
1 file changed, 10 insertions(+)
diff --git a/src/ARMeilleure/State/ExecutionContext.cs b/src/ARMeilleure/State/ExecutionContext.cs
index 0b0a83c9f..c44c6e062 100644
--- a/src/ARMeilleure/State/ExecutionContext.cs
+++ b/src/ARMeilleure/State/ExecutionContext.cs
@@ -164,11 +164,21 @@ namespace ARMeilleure.State
internal void OnBreak(ulong address, int imm)
{
+ if (Optimizations.EnableDebugging)
+ {
+ DebugPc = Pc; // TODO: Is this the best place to update DebugPc?
+ }
+
_breakCallback?.Invoke(this, address, imm);
}
internal void OnSupervisorCall(ulong address, int imm)
{
+ if (Optimizations.EnableDebugging)
+ {
+ DebugPc = Pc; // TODO: Is this the best place to update DebugPc?
+ }
+
_supervisorCallback?.Invoke(this, address, imm);
}
From 5cad23f793403838bcada8e4c192fec2aee4378b Mon Sep 17 00:00:00 2001
From: Coxxs <58-coxxs@users.noreply.git.ryujinx.app>
Date: Sat, 21 Jun 2025 09:43:26 +0800
Subject: [PATCH 37/51] gdb: Set correct gThread and cThread when break
---
src/Ryujinx.HLE/Debugger/Debugger.cs | 1 +
1 file changed, 1 insertion(+)
diff --git a/src/Ryujinx.HLE/Debugger/Debugger.cs b/src/Ryujinx.HLE/Debugger/Debugger.cs
index 608ab977a..dd6394e60 100644
--- a/src/Ryujinx.HLE/Debugger/Debugger.cs
+++ b/src/Ryujinx.HLE/Debugger/Debugger.cs
@@ -916,6 +916,7 @@ namespace Ryujinx.HLE.Debugger
public void BreakHandler(IExecutionContext ctx, ulong address, int imm)
{
+ gThread = cThread = ctx.ThreadUid;
Logger.Notice.Print(LogClass.GdbStub, $"Break hit on thread {ctx.ThreadUid} at pc {address:x016}");
Messages.Add(new ThreadBreakMessage(ctx, address, imm));
From 9506eba98251a029e0e8f147c5dc41983be2734c Mon Sep 17 00:00:00 2001
From: Coxxs <58-coxxs@users.noreply.git.ryujinx.app>
Date: Sat, 21 Jun 2025 10:22:08 +0800
Subject: [PATCH 38/51] gdb: Show thread names
Reference: https://github.com/Atmosphere-NX/Atmosphere/blob/d8a37b4b7184b80ba979bcceb98365b8365a1c3a/libraries/libstratosphere/source/osdbg/impl/osdbg_thread_type.os.horizon.hpp
---
src/Ryujinx.HLE/Debugger/Debugger.cs | 62 ++++++++++--
.../HOS/Kernel/Threading/KThread.cs | 95 +++++++++++++++++++
2 files changed, 151 insertions(+), 6 deletions(-)
diff --git a/src/Ryujinx.HLE/Debugger/Debugger.cs b/src/Ryujinx.HLE/Debugger/Debugger.cs
index dd6394e60..9b9850e58 100644
--- a/src/Ryujinx.HLE/Debugger/Debugger.cs
+++ b/src/Ryujinx.HLE/Debugger/Debugger.cs
@@ -34,6 +34,8 @@ namespace Ryujinx.HLE.Debugger
private ulong? cThread;
private ulong? gThread;
+ private string previousThreadListXml = "";
+
public Debugger(Switch device, ushort port)
{
Device = device;
@@ -368,7 +370,7 @@ namespace Ryujinx.HLE.Debugger
if (ss.ConsumePrefix("Supported:") || ss.ConsumeRemaining("Supported"))
{
- Reply("PacketSize=10000;qXfer:features:read+");
+ Reply("PacketSize=10000;qXfer:features:read+;qXfer:threads:read+");
break;
}
@@ -404,10 +406,43 @@ namespace Ryujinx.HLE.Debugger
break;
}
+ if (ss.ConsumePrefix("Xfer:threads:read:"))
+ {
+ ss.ReadUntil(':');
+ ulong offset = ss.ReadUntilAsHex(',');
+ ulong len = ss.ReadRemainingAsHex();
+
+ var data = "";
+ if (offset > 0)
+ {
+ data = previousThreadListXml;
+ } else
+ {
+ previousThreadListXml = data = GetThreadListXml();
+ }
+
+ if (offset >= (ulong)data.Length)
+ {
+ Reply("l");
+ break;
+ }
+
+ if (len >= (ulong)data.Length - offset)
+ {
+ Reply("l" + ToBinaryFormat(data.Substring((int)offset)));
+ break;
+ }
+ else
+ {
+ Reply("m" + ToBinaryFormat(data.Substring((int)offset, (int)len)));
+ break;
+ }
+ }
+
if (ss.ConsumePrefix("Xfer:features:read:"))
{
string feature = ss.ReadUntil(':');
- ulong addr = ss.ReadUntilAsHex(',');
+ ulong offset = ss.ReadUntilAsHex(',');
ulong len = ss.ReadRemainingAsHex();
if (feature == "target.xml")
@@ -418,20 +453,20 @@ namespace Ryujinx.HLE.Debugger
string data;
if (RegisterInformation.Features.TryGetValue(feature, out data))
{
- if (addr >= (ulong)data.Length)
+ if (offset >= (ulong)data.Length)
{
Reply("l");
break;
}
- if (len >= (ulong)data.Length - addr)
+ if (len >= (ulong)data.Length - offset)
{
- Reply("l" + ToBinaryFormat(data.Substring((int)addr)));
+ Reply("l" + ToBinaryFormat(data.Substring((int)offset)));
break;
}
else
{
- Reply("m" + ToBinaryFormat(data.Substring((int)addr, (int)len)));
+ Reply("m" + ToBinaryFormat(data.Substring((int)offset, (int)len)));
break;
}
}
@@ -469,6 +504,21 @@ namespace Ryujinx.HLE.Debugger
}
}
+ private string GetThreadListXml()
+ {
+ var sb = new StringBuilder();
+ sb.Append("\n");
+
+ foreach (var thread in GetThreads())
+ {
+ string threadName = System.Security.SecurityElement.Escape(thread.GetThreadName());
+ sb.Append($"\n");
+ }
+
+ sb.Append("");
+ return sb.ToString();
+ }
+
void CommandQuery()
{
// GDB is performing initial contact. Stop everything.
diff --git a/src/Ryujinx.HLE/HOS/Kernel/Threading/KThread.cs b/src/Ryujinx.HLE/HOS/Kernel/Threading/KThread.cs
index 2b5d11244..bb0548d19 100644
--- a/src/Ryujinx.HLE/HOS/Kernel/Threading/KThread.cs
+++ b/src/Ryujinx.HLE/HOS/Kernel/Threading/KThread.cs
@@ -5,9 +5,11 @@ using Ryujinx.HLE.HOS.Kernel.Common;
using Ryujinx.HLE.HOS.Kernel.Process;
using Ryujinx.HLE.HOS.Kernel.SupervisorCall;
using Ryujinx.Horizon.Common;
+using Ryujinx.Memory;
using System;
using System.Collections.Generic;
using System.Numerics;
+using System.Text;
using System.Threading;
namespace Ryujinx.HLE.HOS.Kernel.Threading
@@ -17,6 +19,23 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
private const int TlsUserDisableCountOffset = 0x100;
private const int TlsUserInterruptFlagOffset = 0x102;
+ // Tls -> ThreadType
+ private const int TlsThreadTypeOffsetAArch64 = 0x1F8;
+ private const int TlsThreadTypeOffsetAArch32 = 0x1FC;
+
+ // Tls -> ThreadType -> Version
+ private const int TlsThreadTypeVersionOffsetAArch64 = 0x46;
+ private const int TlsThreadTypeVersionOffsetAArch32 = 0x26;
+
+ // Tls -> ThreadType (Version 0) -> ThreadNamePointer
+ private const int TlsThreadTypeVersion0ThreadNamePointerOffsetAArch64 = 0x1A8;
+ private const int TlsThreadTypeVersion0ThreadNamePointerOffsetAArch32 = 0xE8;
+
+ // Tls -> ThreadType (Version 1) -> ThreadNamePointer
+ private const int TlsThreadTypeThreadNamePointerOffsetAArch64 = 0x1A0;
+ private const int TlsThreadTypeThreadNamePointerOffsetAArch32 = 0xE4;
+
+
public const int MaxWaitSyncObjects = 64;
private ManualResetEvent _schedulerWaitEvent;
@@ -1439,5 +1458,81 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
{
Owner.CpuMemory.Write(_tlsAddress + TlsUserInterruptFlagOffset, 0);
}
+
+ public string GetThreadName()
+ {
+ try
+ {
+ ulong threadNamePtr = 0;
+ if (Context.IsAarch32)
+ {
+ uint threadTypePtr32 = Owner.CpuMemory.Read(_tlsAddress + TlsThreadTypeOffsetAArch32);
+ if (threadTypePtr32 == 0)
+ {
+ return "";
+ }
+
+ ushort version = Owner.CpuMemory.Read(threadTypePtr32 + TlsThreadTypeVersionOffsetAArch32);
+ switch (version)
+ {
+ case 0x0000:
+ case 0xFFFF:
+ threadNamePtr = Owner.CpuMemory.Read(threadTypePtr32 + TlsThreadTypeVersion0ThreadNamePointerOffsetAArch32);
+ break;
+ case 0x0001:
+ threadNamePtr = Owner.CpuMemory.Read(threadTypePtr32 + TlsThreadTypeThreadNamePointerOffsetAArch32);
+ break;
+ default:
+ Logger.Warning?.Print(LogClass.Kernel, $"Unknown ThreadType struct version: {version}");
+ break;
+ }
+ }
+ else
+ {
+ ulong threadTypePtr64 = Owner.CpuMemory.Read(_tlsAddress + TlsThreadTypeOffsetAArch64);
+ if (threadTypePtr64 == 0)
+ {
+ return "";
+ }
+
+ ushort version = Owner.CpuMemory.Read(threadTypePtr64 + TlsThreadTypeVersionOffsetAArch64);
+ switch (version)
+ {
+ case 0x0000:
+ case 0xFFFF:
+ threadNamePtr = Owner.CpuMemory.Read(threadTypePtr64 + TlsThreadTypeVersion0ThreadNamePointerOffsetAArch64);
+ break;
+ case 0x0001:
+ threadNamePtr = Owner.CpuMemory.Read(threadTypePtr64 + TlsThreadTypeThreadNamePointerOffsetAArch64);
+ break;
+ default:
+ Logger.Warning?.Print(LogClass.Kernel, $"Unknown ThreadType struct version: {version}");
+ break;
+ }
+ }
+
+ if (threadNamePtr == 0)
+ {
+ return "";
+ }
+
+ List nameBytes = new();
+ for (int i = 0; i < 0x20; i++)
+ {
+ byte b = Owner.CpuMemory.Read(threadNamePtr + (ulong)i);
+ if (b == 0)
+ {
+ break;
+ }
+ nameBytes.Add(b);
+ }
+ return Encoding.UTF8.GetString(nameBytes.ToArray());
+
+ } catch (InvalidMemoryRegionException)
+ {
+ Logger.Warning?.Print(LogClass.Kernel, "Failed to get thread name.");
+ return "";
+ }
+ }
}
}
From fe02ff3a3abc61d2e6ad9aa9c37b2f6bed7938cc Mon Sep 17 00:00:00 2001
From: Coxxs <58-coxxs@users.noreply.git.ryujinx.app>
Date: Sat, 21 Jun 2025 14:31:00 +0800
Subject: [PATCH 39/51] gdb: Fix ExecutionContext
---
src/ARMeilleure/State/ExecutionContext.cs | 6 +++---
.../LightningJit/State/ExecutionContext.cs | 13 ++++++++++++-
2 files changed, 15 insertions(+), 4 deletions(-)
diff --git a/src/ARMeilleure/State/ExecutionContext.cs b/src/ARMeilleure/State/ExecutionContext.cs
index c44c6e062..fa1a4a032 100644
--- a/src/ARMeilleure/State/ExecutionContext.cs
+++ b/src/ARMeilleure/State/ExecutionContext.cs
@@ -153,7 +153,7 @@ namespace ARMeilleure.State
public void StepHandler()
{
- _stepCallback.Invoke(this);
+ _stepCallback?.Invoke(this);
}
public void RequestDebugStep()
@@ -166,7 +166,7 @@ namespace ARMeilleure.State
{
if (Optimizations.EnableDebugging)
{
- DebugPc = Pc; // TODO: Is this the best place to update DebugPc?
+ DebugPc = Pc;
}
_breakCallback?.Invoke(this, address, imm);
@@ -176,7 +176,7 @@ namespace ARMeilleure.State
{
if (Optimizations.EnableDebugging)
{
- DebugPc = Pc; // TODO: Is this the best place to update DebugPc?
+ DebugPc = Pc;
}
_supervisorCallback?.Invoke(this, address, imm);
diff --git a/src/Ryujinx.Cpu/LightningJit/State/ExecutionContext.cs b/src/Ryujinx.Cpu/LightningJit/State/ExecutionContext.cs
index fc75e5185..cb3c6c2af 100644
--- a/src/Ryujinx.Cpu/LightningJit/State/ExecutionContext.cs
+++ b/src/Ryujinx.Cpu/LightningJit/State/ExecutionContext.cs
@@ -1,3 +1,4 @@
+using ARMeilleure;
using ARMeilleure.Memory;
using ARMeilleure.State;
using System;
@@ -127,7 +128,7 @@ namespace Ryujinx.Cpu.LightningJit.State
public void StepHandler()
{
- _stepCallback.Invoke(this);
+ _stepCallback?.Invoke(this);
}
public void RequestDebugStep()
@@ -138,11 +139,21 @@ namespace Ryujinx.Cpu.LightningJit.State
internal void OnBreak(ulong address, int imm)
{
+ if (Optimizations.EnableDebugging)
+ {
+ DebugPc = Pc;
+ }
+
_breakCallback?.Invoke(this, address, imm);
}
internal void OnSupervisorCall(ulong address, int imm)
{
+ if (Optimizations.EnableDebugging)
+ {
+ DebugPc = Pc;
+ }
+
_supervisorCallback?.Invoke(this, address, imm);
}
From 44f4e9af51c1180154369e5950816b21effb214c Mon Sep 17 00:00:00 2001
From: Coxxs <58-coxxs@users.noreply.git.ryujinx.app>
Date: Sat, 21 Jun 2025 15:38:32 +0800
Subject: [PATCH 40/51] gdb: Do not use LightningJitEngine when GDB Stub is
enabled
---
src/Ryujinx.HLE/HOS/ArmProcessContextFactory.cs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/Ryujinx.HLE/HOS/ArmProcessContextFactory.cs b/src/Ryujinx.HLE/HOS/ArmProcessContextFactory.cs
index 759780c42..28f7ef25f 100644
--- a/src/Ryujinx.HLE/HOS/ArmProcessContextFactory.cs
+++ b/src/Ryujinx.HLE/HOS/ArmProcessContextFactory.cs
@@ -69,7 +69,7 @@ namespace Ryujinx.HLE.HOS
mode = MemoryManagerMode.SoftwarePageTable;
}
- ICpuEngine cpuEngine = isArm64Host && (mode == MemoryManagerMode.HostMapped || mode == MemoryManagerMode.HostMappedUnsafe)
+ ICpuEngine cpuEngine = isArm64Host && (mode == MemoryManagerMode.HostMapped || mode == MemoryManagerMode.HostMappedUnsafe) && !context.Device.Configuration.EnableGdbStub
? new LightningJitEngine(_tickSource)
: new JitEngine(_tickSource);
From 7d5f7bc47968095a9b14b7264f36dc64c5cfa89b Mon Sep 17 00:00:00 2001
From: Coxxs <58-coxxs@users.noreply.git.ryujinx.app>
Date: Sun, 22 Jun 2025 02:31:04 +0800
Subject: [PATCH 41/51] gdb: Implement vCont to support step on AArch32
---
src/Ryujinx.HLE/Debugger/Debugger.cs | 125 ++++++++++++++++++++++++++-
1 file changed, 123 insertions(+), 2 deletions(-)
diff --git a/src/Ryujinx.HLE/Debugger/Debugger.cs b/src/Ryujinx.HLE/Debugger/Debugger.cs
index 9b9850e58..d1b41c7f7 100644
--- a/src/Ryujinx.HLE/Debugger/Debugger.cs
+++ b/src/Ryujinx.HLE/Debugger/Debugger.cs
@@ -6,6 +6,7 @@ using Ryujinx.HLE.HOS.Kernel.Threading;
using Ryujinx.Memory;
using System;
using System.Collections.Concurrent;
+using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
@@ -230,6 +231,7 @@ namespace Ryujinx.HLE.Debugger
case ThreadBreakMessage { Context: var ctx }:
DebugProcess.DebugStop();
+ gThread = cThread = ctx.ThreadUid;
Reply($"T05thread:{ctx.ThreadUid:x};");
break;
@@ -370,7 +372,7 @@ namespace Ryujinx.HLE.Debugger
if (ss.ConsumePrefix("Supported:") || ss.ConsumeRemaining("Supported"))
{
- Reply("PacketSize=10000;qXfer:features:read+;qXfer:threads:read+");
+ Reply("PacketSize=10000;qXfer:features:read+;qXfer:threads:read+;vContSupported+");
break;
}
@@ -490,6 +492,22 @@ namespace Ryujinx.HLE.Debugger
break;
}
case 'v':
+ if (ss.ConsumePrefix("Cont"))
+ {
+ if (ss.ConsumeRemaining("?"))
+ {
+ Reply("vCont;c;C;s;S");
+ break;
+ }
+
+ if (ss.ConsumePrefix(";"))
+ {
+ HandleVContCommand(ss);
+ break;
+ }
+
+ goto unknownCommand;
+ }
if (ss.ConsumeRemaining("MustReplyEmpty"))
{
Reply("");
@@ -504,6 +522,109 @@ namespace Ryujinx.HLE.Debugger
}
}
+ enum VContAction
+ {
+ None,
+ Continue,
+ Stop,
+ Step
+ }
+
+ record VContPendingAction(VContAction Action, ushort? Signal = null);
+
+ private void HandleVContCommand(StringStream ss)
+ {
+ string[] rawActions = ss.ReadRemaining().Split(';', StringSplitOptions.RemoveEmptyEntries);
+
+ var threadActionMap = new Dictionary();
+ foreach (var thread in GetThreads())
+ {
+ threadActionMap[thread.ThreadUid] = new VContPendingAction(VContAction.None);
+ }
+
+ // For each inferior thread, the *leftmost* action with a matching thread-id is applied.
+ for (int i = rawActions.Length - 1; i >= 0; i--)
+ {
+ var rawAction = rawActions[i];
+ var stream = new StringStream(rawAction);
+
+ char cmd = stream.ReadChar();
+ VContAction action = cmd switch
+ {
+ 'c' => VContAction.Continue,
+ 'C' => VContAction.Continue,
+ 's' => VContAction.Step,
+ 'S' => VContAction.Step,
+ 't' => VContAction.Stop,
+ _ => VContAction.None
+ };
+
+ ushort? signal = null;
+ if (cmd == 'C' || cmd == 'S')
+ {
+ signal = (ushort)stream.ReadLengthAsHex(2);
+ }
+
+ ulong? threadId = null;
+ if (stream.ConsumePrefix(":"))
+ {
+ threadId = stream.ReadRemainingAsThreadUid();
+ }
+
+ if (threadId.HasValue)
+ {
+ if (threadActionMap.ContainsKey(threadId.Value)) {
+ threadActionMap[threadId.Value] = new VContPendingAction(action, signal);
+ }
+ }
+ else
+ {
+ foreach (var row in threadActionMap.ToList())
+ {
+ threadActionMap[row.Key] = new VContPendingAction(action, signal);
+ }
+ }
+ }
+
+ bool hasError = false;
+
+ // TODO: We don't support stop or continue yet, and we don't support signals.
+ foreach (var (threadUid, action) in threadActionMap)
+ {
+ if (action.Action == VContAction.Step)
+ {
+ var thread = DebugProcess.GetThread(threadUid);
+ if (!DebugProcess.DebugStep(thread)) {
+ hasError = true;
+ }
+ }
+ }
+
+ // If all threads are set to continue, continue the process.
+ if (threadActionMap.Values.All(a => a.Action == VContAction.Continue))
+ {
+ DebugProcess.DebugContinue();
+ }
+
+ if (hasError)
+ {
+ ReplyError();
+ }
+ else
+ {
+ ReplyOK();
+ }
+
+ foreach (var (threadUid, action) in threadActionMap)
+ {
+ if (action.Action == VContAction.Step)
+ {
+ gThread = cThread = threadUid;
+ Reply($"T05thread:{threadUid:x};");
+ }
+ }
+ }
+
private string GetThreadListXml()
{
var sb = new StringBuilder();
@@ -772,6 +893,7 @@ namespace Ryujinx.HLE.Debugger
}
else
{
+ gThread = cThread = thread.ThreadUid;
Reply($"T05thread:{thread.ThreadUid:x};");
}
}
@@ -966,7 +1088,6 @@ namespace Ryujinx.HLE.Debugger
public void BreakHandler(IExecutionContext ctx, ulong address, int imm)
{
- gThread = cThread = ctx.ThreadUid;
Logger.Notice.Print(LogClass.GdbStub, $"Break hit on thread {ctx.ThreadUid} at pc {address:x016}");
Messages.Add(new ThreadBreakMessage(ctx, address, imm));
From bad1dd88993edd667cad30881bf3c2b74bbf3ba2 Mon Sep 17 00:00:00 2001
From: Coxxs <58-coxxs@users.noreply.git.ryujinx.app>
Date: Sun, 22 Jun 2025 05:28:31 +0800
Subject: [PATCH 42/51] gdb: Implement z0/Z0 software breakpoints
---
src/Ryujinx.HLE/Debugger/BreakpointManager.cs | 203 ++++++++++++++++++
src/Ryujinx.HLE/Debugger/Debugger.cs | 80 ++++++-
2 files changed, 280 insertions(+), 3 deletions(-)
create mode 100644 src/Ryujinx.HLE/Debugger/BreakpointManager.cs
diff --git a/src/Ryujinx.HLE/Debugger/BreakpointManager.cs b/src/Ryujinx.HLE/Debugger/BreakpointManager.cs
new file mode 100644
index 000000000..bf462a781
--- /dev/null
+++ b/src/Ryujinx.HLE/Debugger/BreakpointManager.cs
@@ -0,0 +1,203 @@
+using Ryujinx.Common;
+using Ryujinx.Common.Logging;
+using Ryujinx.HLE.HOS.Kernel.Threading;
+using Ryujinx.Memory;
+using System.Collections.Concurrent;
+using System.Linq;
+
+namespace Ryujinx.HLE.Debugger
+{
+ internal class Breakpoint
+ {
+ public byte[] OriginalData { get; }
+
+ public bool IsStep { get; }
+
+ public Breakpoint(byte[] originalData, bool isStep)
+ {
+ OriginalData = originalData;
+ IsStep = isStep;
+ }
+ }
+
+ ///
+ /// Manages software breakpoints for the debugger.
+ ///
+ public class BreakpointManager
+ {
+ private readonly Debugger _debugger;
+ private readonly ConcurrentDictionary _breakpoints = new();
+
+ private static readonly byte[] _aarch64BreakInstruction = { 0x00, 0x00, 0x20, 0xD4 }; // BRK #0
+ private static readonly byte[] _aarch32BreakInstruction = { 0xFE, 0xDE, 0xFF, 0xE7 }; // TRAP
+ private static readonly byte[] _aarch32ThumbBreakInstruction = { 0x80, 0xB6 };
+
+ public BreakpointManager(Debugger debugger)
+ {
+ _debugger = debugger;
+ }
+
+ ///
+ /// Sets a software breakpoint at a specified address.
+ ///
+ /// The memory address to set the breakpoint at.
+ /// The length of the instruction to replace.
+ /// Indicates if this is a single-step breakpoint.
+ /// True if the breakpoint was set successfully; otherwise, false.
+ public bool SetBreakPoint(ulong address, ulong length, bool isStep = false)
+ {
+ if (_breakpoints.ContainsKey(address))
+ {
+ return false;
+ }
+
+ byte[] breakInstruction = GetBreakInstruction(length);
+ if (breakInstruction == null)
+ {
+ Logger.Error?.Print(LogClass.GdbStub, $"Unsupported instruction length for breakpoint: {length}");
+ return false;
+ }
+
+ var originalInstruction = new byte[length];
+ if (!ReadMemory(address, originalInstruction))
+ {
+ Logger.Error?.Print(LogClass.GdbStub, $"Failed to read memory at 0x{address:X16} to set breakpoint.");
+ return false;
+ }
+
+ if (!WriteMemory(address, breakInstruction))
+ {
+ Logger.Error?.Print(LogClass.GdbStub, $"Failed to write breakpoint at 0x{address:X16}.");
+ return false;
+ }
+
+ var breakpoint = new Breakpoint(originalInstruction, isStep);
+ if (_breakpoints.TryAdd(address, breakpoint))
+ {
+ Logger.Debug?.Print(LogClass.GdbStub, $"Breakpoint set at 0x{address:X16}");
+ return true;
+ }
+
+ Logger.Error?.Print(LogClass.GdbStub, $"Failed to add breakpoint at 0x{address:X16}.");
+ return false;
+ }
+
+ ///
+ /// Clears a software breakpoint at a specified address.
+ ///
+ /// The memory address of the breakpoint to clear.
+ /// The length of the instruction (unused).
+ /// True if the breakpoint was cleared successfully; otherwise, false.
+ public bool ClearBreakPoint(ulong address, ulong length)
+ {
+ if (_breakpoints.TryGetValue(address, out Breakpoint breakpoint))
+ {
+ if (!WriteMemory(address, breakpoint.OriginalData))
+ {
+ Logger.Error?.Print(LogClass.GdbStub, $"Failed to restore original instruction at 0x{address:X16} to clear breakpoint.");
+ return false;
+ }
+
+ _breakpoints.TryRemove(address, out _);
+ Logger.Debug?.Print(LogClass.GdbStub, $"Breakpoint cleared at 0x{address:X16}");
+ return true;
+ }
+
+ Logger.Warning?.Print(LogClass.GdbStub, $"No breakpoint found at address 0x{address:X16}");
+ return false;
+ }
+
+ ///
+ /// Clears all currently set software breakpoints.
+ ///
+ public void ClearAll()
+ {
+ foreach (var bp in _breakpoints)
+ {
+ if (!WriteMemory(bp.Key, bp.Value.OriginalData))
+ {
+ Logger.Error?.Print(LogClass.GdbStub, $"Failed to restore original instruction at 0x{bp.Key:X16} while clearing all breakpoints.");
+ }
+
+ }
+ _breakpoints.Clear();
+ Logger.Debug?.Print(LogClass.GdbStub, "All breakpoints cleared.");
+ }
+
+ ///
+ /// Clears all currently set single-step software breakpoints.
+ ///
+ public void ClearAllStepBreakpoints()
+ {
+ var stepBreakpoints = _breakpoints.Where(p => p.Value.IsStep).ToList();
+
+ if (stepBreakpoints.Count == 0)
+ {
+ return;
+ }
+
+ foreach (var bp in stepBreakpoints)
+ {
+ if (_breakpoints.TryRemove(bp.Key, out Breakpoint removedBreakpoint))
+ {
+ WriteMemory(bp.Key, removedBreakpoint.OriginalData);
+ }
+ }
+
+ Logger.Debug?.Print(LogClass.GdbStub, "All step breakpoints cleared.");
+ }
+
+
+ private byte[] GetBreakInstruction(ulong length)
+ {
+ if (_debugger.IsProcessAarch32)
+ {
+ if (length == 2)
+ {
+ return _aarch32ThumbBreakInstruction;
+ }
+
+ if (length == 4)
+ {
+ return _aarch32BreakInstruction;
+ }
+ }
+ else
+ {
+ if (length == 4)
+ {
+ return _aarch64BreakInstruction;
+ }
+ }
+
+ return null;
+ }
+
+ private bool ReadMemory(ulong address, byte[] data)
+ {
+ try
+ {
+ _debugger.DebugProcess.CpuMemory.Read(address, data);
+ return true;
+ }
+ catch (InvalidMemoryRegionException)
+ {
+ return false;
+ }
+ }
+
+ private bool WriteMemory(ulong address, byte[] data)
+ {
+ try
+ {
+ _debugger.DebugProcess.CpuMemory.Write(address, data);
+ _debugger.DebugProcess.InvalidateCacheRegion(address, (ulong)data.Length);
+ return true;
+ }
+ catch (InvalidMemoryRegionException)
+ {
+ return false;
+ }
+ }
+ }
+}
diff --git a/src/Ryujinx.HLE/Debugger/Debugger.cs b/src/Ryujinx.HLE/Debugger/Debugger.cs
index d1b41c7f7..3c051639d 100644
--- a/src/Ryujinx.HLE/Debugger/Debugger.cs
+++ b/src/Ryujinx.HLE/Debugger/Debugger.cs
@@ -35,6 +35,8 @@ namespace Ryujinx.HLE.Debugger
private ulong? cThread;
private ulong? gThread;
+ private BreakpointManager BreakpointManager;
+
private string previousThreadListXml = "";
public Debugger(Switch device, ushort port)
@@ -48,11 +50,12 @@ namespace Ryujinx.HLE.Debugger
DebuggerThread.Start();
MessageHandlerThread = new Thread(MessageHandlerMain);
MessageHandlerThread.Start();
+ BreakpointManager = new BreakpointManager(this);
}
- private IDebuggableProcess DebugProcess => Device.System?.DebugGetApplicationProcess();
+ internal IDebuggableProcess DebugProcess => Device.System?.DebugGetApplicationProcess();
private KThread[] GetThreads() => DebugProcess.GetThreadUids().Select(x => DebugProcess.GetThread(x)).ToArray();
- private bool IsProcessAarch32 => DebugProcess.GetThread(gThread.Value).Context.IsAarch32;
+ internal bool IsProcessAarch32 => DebugProcess.GetThread(gThread.Value).Context.IsAarch32;
private KernelContext KernelContext => Device.System.KernelContext;
const int GdbRegisterCount64 = 68;
@@ -514,6 +517,75 @@ namespace Ryujinx.HLE.Debugger
break;
}
goto unknownCommand;
+ case 'Z':
+ {
+ string type = ss.ReadUntil(',');
+ ulong addr = ss.ReadUntilAsHex(',');
+ ulong len = ss.ReadLengthAsHex(1);
+ string extra = ss.ReadRemaining();
+
+ if (extra.Length > 0)
+ {
+ Logger.Notice.Print(LogClass.GdbStub, $"Unsupported Z command extra data: {extra}");
+ ReplyError();
+ return;
+ }
+
+ switch (type)
+ {
+ case "0": // Software breakpoint
+ if (!BreakpointManager.SetBreakPoint(addr, len, false))
+ {
+ ReplyError();
+ }
+ ReplyOK();
+ return;
+ case "1": // Hardware breakpoint
+ case "2": // Write watchpoint
+ case "3": // Read watchpoint
+ case "4": // Access watchpoint
+ ReplyError();
+ return;
+ default:
+ ReplyError();
+ return;
+ }
+ }
+ case 'z':
+ {
+ string type = ss.ReadUntil(',');
+ ss.ConsumePrefix(",");
+ ulong addr = ss.ReadUntilAsHex(',');
+ ulong len = ss.ReadLengthAsHex(1);
+ string extra = ss.ReadRemaining();
+
+ if (extra.Length > 0)
+ {
+ Logger.Notice.Print(LogClass.GdbStub, $"Unsupported z command extra data: {extra}");
+ ReplyError();
+ return;
+ }
+
+ switch (type)
+ {
+ case "0": // Software breakpoint
+ if (!BreakpointManager.ClearBreakPoint(addr, len))
+ {
+ ReplyError();
+ }
+ ReplyOK();
+ return;
+ case "1": // Hardware breakpoint
+ case "2": // Write watchpoint
+ case "3": // Read watchpoint
+ case "4": // Access watchpoint
+ ReplyError();
+ return;
+ default:
+ ReplyError();
+ return;
+ }
+ }
default:
unknownCommand:
Logger.Notice.Print(LogClass.GdbStub, $"Unknown command: {cmd}");
@@ -666,7 +738,7 @@ namespace Ryujinx.HLE.Debugger
void CommandDetach()
{
- // TODO: Remove all breakpoints
+ BreakpointManager.ClearAll();
CommandContinue(null);
}
@@ -1017,6 +1089,8 @@ namespace Ryujinx.HLE.Debugger
WriteStream = null;
ClientSocket.Close();
ClientSocket = null;
+
+ BreakpointManager.ClearAll();
}
}
From 7d189ab2c0fac2915c8870f613eea3f87d812a98 Mon Sep 17 00:00:00 2001
From: Coxxs <58-coxxs@users.noreply.git.ryujinx.app>
Date: Sun, 22 Jun 2025 05:28:40 +0800
Subject: [PATCH 43/51] gdb: Revert ExecutionContext for now
Pc isn't reliable either
---
src/ARMeilleure/State/ExecutionContext.cs | 10 ----------
src/Ryujinx.Cpu/LightningJit/State/ExecutionContext.cs | 10 ----------
2 files changed, 20 deletions(-)
diff --git a/src/ARMeilleure/State/ExecutionContext.cs b/src/ARMeilleure/State/ExecutionContext.cs
index fa1a4a032..cdf6f56c5 100644
--- a/src/ARMeilleure/State/ExecutionContext.cs
+++ b/src/ARMeilleure/State/ExecutionContext.cs
@@ -164,21 +164,11 @@ namespace ARMeilleure.State
internal void OnBreak(ulong address, int imm)
{
- if (Optimizations.EnableDebugging)
- {
- DebugPc = Pc;
- }
-
_breakCallback?.Invoke(this, address, imm);
}
internal void OnSupervisorCall(ulong address, int imm)
{
- if (Optimizations.EnableDebugging)
- {
- DebugPc = Pc;
- }
-
_supervisorCallback?.Invoke(this, address, imm);
}
diff --git a/src/Ryujinx.Cpu/LightningJit/State/ExecutionContext.cs b/src/Ryujinx.Cpu/LightningJit/State/ExecutionContext.cs
index cb3c6c2af..a1ba0002e 100644
--- a/src/Ryujinx.Cpu/LightningJit/State/ExecutionContext.cs
+++ b/src/Ryujinx.Cpu/LightningJit/State/ExecutionContext.cs
@@ -139,21 +139,11 @@ namespace Ryujinx.Cpu.LightningJit.State
internal void OnBreak(ulong address, int imm)
{
- if (Optimizations.EnableDebugging)
- {
- DebugPc = Pc;
- }
-
_breakCallback?.Invoke(this, address, imm);
}
internal void OnSupervisorCall(ulong address, int imm)
{
- if (Optimizations.EnableDebugging)
- {
- DebugPc = Pc;
- }
-
_supervisorCallback?.Invoke(this, address, imm);
}
From 009d319bc24b5ae7b260da286d8b81f54fbc42ea Mon Sep 17 00:00:00 2001
From: Coxxs <58-coxxs@users.noreply.git.ryujinx.app>
Date: Mon, 23 Jun 2025 06:18:34 +0800
Subject: [PATCH 44/51] gdb: Implement QRcmd (monitor) commands
monitor backtrace (mo bt)
monitor registers (mo reg)
monitor get info
---
src/Ryujinx.HLE/Debugger/Debugger.cs | 111 +++++++++++++++++-
src/Ryujinx.HLE/HOS/Horizon.cs | 10 +-
.../HOS/Kernel/Process/HleProcessDebugger.cs | 24 +++-
.../HOS/Kernel/Threading/KThread.cs | 3 +
4 files changed, 144 insertions(+), 4 deletions(-)
diff --git a/src/Ryujinx.HLE/Debugger/Debugger.cs b/src/Ryujinx.HLE/Debugger/Debugger.cs
index 3c051639d..5bde92aed 100644
--- a/src/Ryujinx.HLE/Debugger/Debugger.cs
+++ b/src/Ryujinx.HLE/Debugger/Debugger.cs
@@ -2,6 +2,7 @@ using ARMeilleure.State;
using Ryujinx.Common;
using Ryujinx.Common.Logging;
using Ryujinx.HLE.HOS.Kernel;
+using Ryujinx.HLE.HOS.Kernel.Process;
using Ryujinx.HLE.HOS.Kernel.Threading;
using Ryujinx.Memory;
using System;
@@ -53,7 +54,8 @@ namespace Ryujinx.HLE.Debugger
BreakpointManager = new BreakpointManager(this);
}
- internal IDebuggableProcess DebugProcess => Device.System?.DebugGetApplicationProcess();
+ internal KProcess Process => Device.System?.DebugGetApplicationProcess();
+ internal IDebuggableProcess DebugProcess => Device.System?.DebugGetApplicationProcessDebugInterface();
private KThread[] GetThreads() => DebugProcess.GetThreadUids().Select(x => DebugProcess.GetThread(x)).ToArray();
internal bool IsProcessAarch32 => DebugProcess.GetThread(gThread.Value).Context.IsAarch32;
private KernelContext KernelContext => Device.System.KernelContext;
@@ -379,6 +381,13 @@ namespace Ryujinx.HLE.Debugger
break;
}
+ if (ss.ConsumePrefix("Rcmd,"))
+ {
+ string hexCommand = ss.ReadRemaining();
+ HandleQRcmdCommand(hexCommand);
+ break;
+ }
+
if (ss.ConsumeRemaining("fThreadInfo"))
{
Reply($"m{string.Join(",", DebugProcess.GetThreadUids().Select(x => $"{x:x}"))}");
@@ -982,6 +991,97 @@ namespace Ryujinx.HLE.Debugger
}
}
+ private void HandleQRcmdCommand(string hexCommand)
+ {
+ try
+ {
+ string command = FromHex(hexCommand);
+ Logger.Debug?.Print(LogClass.GdbStub, $"Received Rcmd: {command}");
+
+ string response = command.Trim().ToLowerInvariant() switch
+ {
+ "help" => "backtrace\nbt\nregisters\nreg\nget info\n",
+ "get info" => GetProcessInfo(),
+ "backtrace" => GetStackTrace(),
+ "bt" => GetStackTrace(),
+ "registers" => GetRegisters(),
+ "reg" => GetRegisters(),
+ _ => $"Unknown command: {command}\n"
+ };
+
+ Reply(ToHex(response));
+ }
+ catch (Exception e)
+ {
+ Logger.Error?.Print(LogClass.GdbStub, $"Error processing Rcmd: {e.Message}");
+ ReplyError();
+ }
+ }
+
+ private string GetStackTrace()
+ {
+ if (gThread == null)
+ return "No thread selected\n";
+
+ if (Process == null)
+ return "No application process found\n";
+
+ return Process.Debugger.GetGuestStackTrace(DebugProcess.GetThread(gThread.Value));
+ }
+
+ private string GetRegisters()
+ {
+ if (gThread == null)
+ return "No thread selected\n";
+
+ if (Process == null)
+ return "No application process found\n";
+
+ return Process.Debugger.GetCpuRegisterPrintout(DebugProcess.GetThread(gThread.Value));
+ }
+
+ private string GetProcessInfo()
+ {
+ try
+ {
+ if (Process == null)
+ return "No application process found\n";
+
+ KProcess kProcess = Process;
+
+ var sb = new StringBuilder();
+
+ sb.AppendLine($"Program Id: 0x{kProcess.TitleId:x16}");
+ sb.AppendLine($"Application: {(kProcess.IsApplication ? 1 : 0)}");
+ sb.AppendLine("Layout:");
+ sb.AppendLine($" Alias: 0x{kProcess.MemoryManager.AliasRegionStart:x10} - 0x{kProcess.MemoryManager.AliasRegionEnd - 1:x10}");
+ sb.AppendLine($" Heap: 0x{kProcess.MemoryManager.HeapRegionStart:x10} - 0x{kProcess.MemoryManager.HeapRegionEnd - 1:x10}");
+ sb.AppendLine($" Aslr: 0x{kProcess.MemoryManager.AslrRegionStart:x10} - 0x{kProcess.MemoryManager.AslrRegionEnd - 1:x10}");
+ sb.AppendLine($" Stack: 0x{kProcess.MemoryManager.StackRegionStart:x10} - 0x{kProcess.MemoryManager.StackRegionEnd - 1:x10}");
+
+ sb.AppendLine("Modules:");
+ var debugger = kProcess.Debugger;
+ if (debugger != null)
+ {
+ var images = debugger.GetLoadedImages();
+ for (int i = 0; i < images.Count; i++)
+ {
+ var image = images[i];
+ ulong endAddress = image.BaseAddress + image.Size - 1;
+ string name = debugger.GetGuessedNsoNameFromIndex(i);
+ sb.AppendLine($" 0x{image.BaseAddress:x10} - 0x{endAddress:x10} {name}");
+ }
+ }
+
+ return sb.ToString();
+ }
+ catch (Exception e)
+ {
+ Logger.Error?.Print(LogClass.GdbStub, $"Error getting process info: {e.Message}");
+ return $"Error getting process info: {e.Message}\n";
+ }
+ }
+
private void Reply(string cmd)
{
Logger.Debug?.Print(LogClass.GdbStub, $"Reply: {cmd}");
@@ -1108,6 +1208,15 @@ namespace Ryujinx.HLE.Debugger
return checksum;
}
+ private string FromHex(string hexString)
+ {
+ if (string.IsNullOrEmpty(hexString))
+ return string.Empty;
+
+ byte[] bytes = Convert.FromHexString(hexString);
+ return Encoding.ASCII.GetString(bytes);
+ }
+
private string ToHex(byte[] bytes)
{
return string.Join("", bytes.Select(x => $"{x:x2}"));
diff --git a/src/Ryujinx.HLE/HOS/Horizon.cs b/src/Ryujinx.HLE/HOS/Horizon.cs
index 44adb9674..517f8ef16 100644
--- a/src/Ryujinx.HLE/HOS/Horizon.cs
+++ b/src/Ryujinx.HLE/HOS/Horizon.cs
@@ -502,12 +502,20 @@ namespace Ryujinx.HLE.HOS
IsPaused = pause;
}
- internal IDebuggableProcess DebugGetApplicationProcess()
+ internal IDebuggableProcess DebugGetApplicationProcessDebugInterface()
{
lock (KernelContext.Processes)
{
return KernelContext.Processes.Values.FirstOrDefault(x => x.IsApplication)?.DebugInterface;
}
}
+
+ internal KProcess DebugGetApplicationProcess()
+ {
+ lock (KernelContext.Processes)
+ {
+ return KernelContext.Processes.Values.FirstOrDefault(x => x.IsApplication);
+ }
+ }
}
}
diff --git a/src/Ryujinx.HLE/HOS/Kernel/Process/HleProcessDebugger.cs b/src/Ryujinx.HLE/HOS/Kernel/Process/HleProcessDebugger.cs
index c4a9835cc..87da9f7a6 100644
--- a/src/Ryujinx.HLE/HOS/Kernel/Process/HleProcessDebugger.cs
+++ b/src/Ryujinx.HLE/HOS/Kernel/Process/HleProcessDebugger.cs
@@ -4,6 +4,7 @@ using Ryujinx.HLE.HOS.Kernel.Memory;
using Ryujinx.HLE.HOS.Kernel.Threading;
using Ryujinx.HLE.Loaders.Elf;
using Ryujinx.Memory;
+using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
@@ -17,7 +18,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
private readonly KProcess _owner;
- private class Image
+ public class Image
{
public ulong BaseAddress { get; }
public ulong Size { get; }
@@ -54,6 +55,15 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
trace.AppendLine($"Process: {_owner.Name}, PID: {_owner.Pid}");
+ string ThreadName = thread.GetThreadName();
+
+ if (!String.IsNullOrEmpty(ThreadName))
+ {
+ trace.AppendLine($"Thread ID: {thread.ThreadUid} ({ThreadName})");
+ } else {
+ trace.AppendLine($"Thread ID: {thread.ThreadUid}");
+ }
+
void AppendTrace(ulong address)
{
if (AnalyzePointer(out PointerInfo info, address, thread))
@@ -283,7 +293,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
return null;
}
- private string GetGuessedNsoNameFromIndex(int index)
+ public string GetGuessedNsoNameFromIndex(int index)
{
if ((uint)index > 11)
{
@@ -316,6 +326,16 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
}
}
+ public List GetLoadedImages()
+ {
+ EnsureLoaded();
+
+ lock (_images)
+ {
+ return [.. _images];
+ }
+ }
+
private void EnsureLoaded()
{
if (Interlocked.CompareExchange(ref _loaded, 1, 0) == 0)
diff --git a/src/Ryujinx.HLE/HOS/Kernel/Threading/KThread.cs b/src/Ryujinx.HLE/HOS/Kernel/Threading/KThread.cs
index bb0548d19..20fb426ba 100644
--- a/src/Ryujinx.HLE/HOS/Kernel/Threading/KThread.cs
+++ b/src/Ryujinx.HLE/HOS/Kernel/Threading/KThread.cs
@@ -1532,6 +1532,9 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
{
Logger.Warning?.Print(LogClass.Kernel, "Failed to get thread name.");
return "";
+ } catch (Exception e) {
+ Logger.Error?.Print(LogClass.Kernel, $"Error getting thread name: {e.Message}");
+ return "";
}
}
}
From 838296ccb63bb76b1d2d672f0794bab8e5678cd7 Mon Sep 17 00:00:00 2001
From: Coxxs <58-coxxs@users.noreply.git.ryujinx.app>
Date: Mon, 23 Jun 2025 07:00:48 +0800
Subject: [PATCH 45/51] gdb: Support precise tracking of PC value when GDB Stub
is enabled
---
src/ARMeilleure/State/ExecutionContext.cs | 10 ++++++++++
src/ARMeilleure/State/NativeContext.cs | 15 +++++++++++++++
src/ARMeilleure/Translation/Translator.cs | 18 ++++++++++++++++++
3 files changed, 43 insertions(+)
diff --git a/src/ARMeilleure/State/ExecutionContext.cs b/src/ARMeilleure/State/ExecutionContext.cs
index cdf6f56c5..fa1a4a032 100644
--- a/src/ARMeilleure/State/ExecutionContext.cs
+++ b/src/ARMeilleure/State/ExecutionContext.cs
@@ -164,11 +164,21 @@ namespace ARMeilleure.State
internal void OnBreak(ulong address, int imm)
{
+ if (Optimizations.EnableDebugging)
+ {
+ DebugPc = Pc;
+ }
+
_breakCallback?.Invoke(this, address, imm);
}
internal void OnSupervisorCall(ulong address, int imm)
{
+ if (Optimizations.EnableDebugging)
+ {
+ DebugPc = Pc;
+ }
+
_supervisorCallback?.Invoke(this, address, imm);
}
diff --git a/src/ARMeilleure/State/NativeContext.cs b/src/ARMeilleure/State/NativeContext.cs
index c90e522a9..25b5e51c3 100644
--- a/src/ARMeilleure/State/NativeContext.cs
+++ b/src/ARMeilleure/State/NativeContext.cs
@@ -22,6 +22,11 @@ namespace ARMeilleure.State
public ulong ExclusiveValueHigh;
public int Running;
public long Tpidr2El0;
+
+ ///
+ /// This is only set when Optimizations.EnableDebugging is true.
+ ///
+ public ulong CurrentPc;
}
private static NativeCtxStorage _dummyStorage = new();
@@ -39,6 +44,11 @@ namespace ARMeilleure.State
public ulong GetPc()
{
+ if (Optimizations.EnableDebugging)
+ {
+ return GetStorage().CurrentPc;
+ }
+
// TODO: More precise tracking of PC value.
return GetStorage().DispatchAddress;
}
@@ -268,6 +278,11 @@ namespace ARMeilleure.State
return StorageOffset(ref _dummyStorage, ref _dummyStorage.Running);
}
+ public static int GetCurrentPcOffset()
+ {
+ return StorageOffset(ref _dummyStorage, ref _dummyStorage.CurrentPc);
+ }
+
private static int StorageOffset(ref NativeCtxStorage storage, ref T target)
{
return (int)Unsafe.ByteOffset(ref Unsafe.As(ref storage), ref target);
diff --git a/src/ARMeilleure/Translation/Translator.cs b/src/ARMeilleure/Translation/Translator.cs
index 343e361a5..351ee34f1 100644
--- a/src/ARMeilleure/Translation/Translator.cs
+++ b/src/ARMeilleure/Translation/Translator.cs
@@ -388,6 +388,11 @@ namespace ARMeilleure.Translation
// Return to managed rather than tail call.
bool useReturns = Optimizations.EnableDebugging;
+ if (Optimizations.EnableDebugging)
+ {
+ EmitPcUpdate(context, block.Address);
+ }
+
InstEmitFlowHelper.EmitVirtualJump(context, Const(block.Address), isReturn: useReturns);
}
else
@@ -410,6 +415,11 @@ namespace ARMeilleure.Translation
}
}
+ if (Optimizations.EnableDebugging)
+ {
+ EmitPcUpdate(context, opCode.Address);
+ }
+
Operand lblPredicateSkip = default;
if (context.IsInIfThenBlock && context.CurrentIfThenBlockCond != Condition.Al)
@@ -506,6 +516,14 @@ namespace ARMeilleure.Translation
context.MarkLabel(lblExit);
}
+ internal static void EmitPcUpdate(EmitterContext context, ulong address)
+ {
+ long currentPcOffs = NativeContext.GetCurrentPcOffset();
+
+ Operand currentPcAddr = context.Add(context.LoadArgument(OperandType.I64, 0), Const(currentPcOffs));
+ context.Store(currentPcAddr, Const(address));
+ }
+
public void InvalidateJitCacheRegion(ulong address, ulong size)
{
ulong[] overlapAddresses = [];
From c5c8647de756ef45337a7ed784af2d14bad6c45c Mon Sep 17 00:00:00 2001
From: Coxxs <58-coxxs@users.noreply.git.ryujinx.app>
Date: Mon, 23 Jun 2025 07:03:40 +0800
Subject: [PATCH 46/51] gdb: Invalidate PTC cache when GDB Stub is
enabled/disabled
---
src/ARMeilleure/Translation/PTC/Ptc.cs | 13 +++++++++++--
1 file changed, 11 insertions(+), 2 deletions(-)
diff --git a/src/ARMeilleure/Translation/PTC/Ptc.cs b/src/ARMeilleure/Translation/PTC/Ptc.cs
index f36d4256d..c69ebcadb 100644
--- a/src/ARMeilleure/Translation/PTC/Ptc.cs
+++ b/src/ARMeilleure/Translation/PTC/Ptc.cs
@@ -33,7 +33,7 @@ namespace ARMeilleure.Translation.PTC
private const string OuterHeaderMagicString = "PTCohd\0\0";
private const string InnerHeaderMagicString = "PTCihd\0\0";
- private const uint InternalVersion = 7008; //! To be incremented manually for each change to the ARMeilleure project.
+ private const uint InternalVersion = 7009; //! To be incremented manually for each change to the ARMeilleure project.
private const string ActualDir = "0";
private const string BackupDir = "1";
@@ -303,6 +303,13 @@ namespace ARMeilleure.Translation.PTC
return false;
}
+ if (outerHeader.DebuggerMode != Optimizations.EnableDebugging)
+ {
+ InvalidateCompressedStream(compressedStream);
+
+ return false;
+ }
+
nint intPtr = nint.Zero;
try
@@ -479,6 +486,7 @@ namespace ARMeilleure.Translation.PTC
MemoryManagerMode = GetMemoryManagerMode(),
OSPlatform = GetOSPlatform(),
Architecture = (uint)RuntimeInformation.ProcessArchitecture,
+ DebuggerMode = Optimizations.EnableDebugging,
UncompressedStreamSize =
(long)Unsafe.SizeOf() +
@@ -1068,7 +1076,7 @@ namespace ARMeilleure.Translation.PTC
return osPlatform;
}
- [StructLayout(LayoutKind.Sequential, Pack = 1/*, Size = 86*/)]
+ [StructLayout(LayoutKind.Sequential, Pack = 1/*, Size = 87*/)]
private struct OuterHeader
{
public ulong Magic;
@@ -1080,6 +1088,7 @@ namespace ARMeilleure.Translation.PTC
public byte MemoryManagerMode;
public uint OSPlatform;
public uint Architecture;
+ public bool DebuggerMode;
public long UncompressedStreamSize;
From c4abaa6cf27c25865a967fdfe0e3ce374cbf0c2e Mon Sep 17 00:00:00 2001
From: Coxxs <58-coxxs@users.noreply.git.ryujinx.app>
Date: Mon, 23 Jun 2025 07:04:20 +0800
Subject: [PATCH 47/51] gdb: Allow PTC cache when GDB Stub is enabled
---
src/ARMeilleure/Translation/Translator.cs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/ARMeilleure/Translation/Translator.cs b/src/ARMeilleure/Translation/Translator.cs
index 351ee34f1..a5678be13 100644
--- a/src/ARMeilleure/Translation/Translator.cs
+++ b/src/ARMeilleure/Translation/Translator.cs
@@ -246,7 +246,7 @@ namespace ARMeilleure.Translation
Stubs,
address,
highCq,
- _ptc.State != PtcState.Disabled && !Optimizations.EnableDebugging,
+ _ptc.State != PtcState.Disabled,
mode: Aarch32Mode.User);
Logger.StartPass(PassName.Decoding);
From b1c1ad54e8ec7a3c0f9e0658a469f51f7334b424 Mon Sep 17 00:00:00 2001
From: Coxxs <58-coxxs@users.noreply.git.ryujinx.app>
Date: Mon, 23 Jun 2025 07:05:45 +0800
Subject: [PATCH 48/51] gdb: Fix single-stepping of branch instructions
---
src/ARMeilleure/Translation/Translator.cs | 104 ++++++++++++++++++++++
1 file changed, 104 insertions(+)
diff --git a/src/ARMeilleure/Translation/Translator.cs b/src/ARMeilleure/Translation/Translator.cs
index a5678be13..0300d51e1 100644
--- a/src/ARMeilleure/Translation/Translator.cs
+++ b/src/ARMeilleure/Translation/Translator.cs
@@ -195,6 +195,22 @@ namespace ARMeilleure.Translation
private ulong Step(State.ExecutionContext context, ulong address)
{
+ try
+ {
+ OpCode opCode = Decoder.DecodeOpCode(Memory, address, context.ExecutionMode);
+
+ // For branch instructions during single-stepping, we handle them manually
+ // func.Execute() will sometimes execute the entire function call, which is not what we want
+ if (opCode.Instruction.Name is InstName.Bl or InstName.Blr or InstName.Blx or InstName.Br)
+ {
+ return ExecuteBranchInstructionForStepping(context, address, opCode);
+ }
+ }
+ catch
+ {
+ // ignore
+ }
+
TranslatedFunction func = Translate(address, context.ExecutionMode, highCq: false, singleStep: true);
address = func.Execute(Stubs.ContextWrapper, context);
@@ -204,6 +220,94 @@ namespace ARMeilleure.Translation
return address;
}
+ private static ulong ExecuteBranchInstructionForStepping(State.ExecutionContext context, ulong address, OpCode opCode)
+ {
+ switch (opCode.Instruction.Name)
+ {
+ case InstName.Bl:
+ if (opCode is IOpCodeBImm opBImm)
+ {
+ // Set link register
+ if (context.ExecutionMode == ExecutionMode.Aarch64)
+ {
+ context.SetX(30, address + (ulong)opCode.OpCodeSizeInBytes); // LR = X30
+ }
+ else
+ {
+ // For ARM32, need to set the appropriate return address
+ uint returnAddr = opCode is OpCode32 op32 && op32.IsThumb
+ ? (uint)address + (uint)opCode.OpCodeSizeInBytes | 1u // Thumb bit set
+ : (uint)address + (uint)opCode.OpCodeSizeInBytes;
+ context.SetX(14, returnAddr); // LR = R14
+ }
+ return (ulong)opBImm.Immediate;
+ }
+ break;
+
+ case InstName.Blr:
+ if (opCode is OpCodeBReg opBReg)
+ {
+ // Set link register
+ if (context.ExecutionMode == ExecutionMode.Aarch64)
+ {
+ context.SetX(30, address + (ulong)opCode.OpCodeSizeInBytes); // LR = X30
+ }
+ else
+ {
+ uint returnAddr = opCode is OpCode32 op32 && op32.IsThumb
+ ? (uint)address + (uint)opCode.OpCodeSizeInBytes | 1u // Thumb bit set
+ : (uint)address + (uint)opCode.OpCodeSizeInBytes;
+ context.SetX(14, returnAddr); // LR = R14
+ }
+ return context.GetX(opBReg.Rn);
+ }
+ break;
+
+ case InstName.Blx:
+ if (opCode is IOpCodeBImm opBlxImm)
+ {
+ // Handle mode switching for BLX
+ if (opCode is OpCode32 op32)
+ {
+ uint returnAddr = op32.IsThumb
+ ? (uint)address + (uint)opCode.OpCodeSizeInBytes | 1u
+ : (uint)address + (uint)opCode.OpCodeSizeInBytes;
+ context.SetX(14, returnAddr);
+
+ // BLX switches between ARM and Thumb modes
+ context.SetPstateFlag(PState.TFlag, !op32.IsThumb);
+ }
+ return (ulong)opBlxImm.Immediate;
+ }
+ else if (opCode is IOpCode32BReg opBlxReg)
+ {
+ if (opCode is OpCode32 op32)
+ {
+ uint returnAddr = op32.IsThumb
+ ? (uint)address + (uint)opCode.OpCodeSizeInBytes | 1u
+ : (uint)address + (uint)opCode.OpCodeSizeInBytes;
+ context.SetX(14, returnAddr);
+
+ // For BLX register, the target address determines the mode
+ ulong targetAddr = context.GetX(opBlxReg.Rm);
+ context.SetPstateFlag(PState.TFlag, (targetAddr & 1) != 0);
+ return targetAddr & ~1UL; // Clear the Thumb bit for the actual address
+ }
+ }
+ break;
+
+ case InstName.Br:
+ if (opCode is OpCodeBReg opBr)
+ {
+ // BR doesn't set link register, just branches to the target
+ return context.GetX(opBr.Rn);
+ }
+ break;
+ }
+
+ throw new InvalidOperationException($"Unhandled branch instruction: {opCode.Instruction.Name}");
+ }
+
internal TranslatedFunction GetOrTranslate(ulong address, ExecutionMode mode)
{
if (!Functions.TryGetValue(address, out TranslatedFunction func))
From 669179ca2e19d6ab9ebead3bad741ea0c300d2a1 Mon Sep 17 00:00:00 2001
From: Coxxs <58-coxxs@users.noreply.git.ryujinx.app>
Date: Mon, 23 Jun 2025 17:59:53 +0800
Subject: [PATCH 49/51] gdb: Adjust Settings UI
---
assets/locales.json | 41 +++++++++++++++----
.../UI/Views/Settings/SettingsDebugView.axaml | 12 +++---
2 files changed, 40 insertions(+), 13 deletions(-)
diff --git a/assets/locales.json b/assets/locales.json
index 6269cb5dd..915fb42e5 100644
--- a/assets/locales.json
+++ b/assets/locales.json
@@ -25053,7 +25053,7 @@
"ar_SA": "",
"de_DE": "",
"el_GR": "",
- "en_US": "Debug (WARNING: For developer use only)",
+ "en_US": "Debug",
"es_ES": "",
"fr_FR": "",
"he_IL": "",
@@ -25073,7 +25073,32 @@
}
},
{
- "ID": "EnableGDBStub",
+ "ID": "SettingsTabDebugNote",
+ "Translations": {
+ "ar_SA": "",
+ "de_DE": "",
+ "el_GR": "",
+ "en_US": "WARNING: For developer use only, will reduce performance",
+ "es_ES": "",
+ "fr_FR": "",
+ "he_IL": "",
+ "it_IT": "",
+ "ja_JP": "",
+ "ko_KR": "",
+ "no_NO": "",
+ "pl_PL": "",
+ "pt_BR": "",
+ "ru_RU": "",
+ "sv_SE": "",
+ "th_TH": "",
+ "tr_TR": "",
+ "uk_UA": "",
+ "zh_CN": "",
+ "zh_TW": ""
+ }
+ },
+ {
+ "ID": "SettingsTabDebugEnableGDBStub",
"Translations": {
"ar_SA": "",
"de_DE": "",
@@ -25098,7 +25123,7 @@
}
},
{
- "ID": "GDBStubToggleTooltip",
+ "ID": "SettingsTabDebugGDBStubToggleTooltip",
"Translations": {
"ar_SA": "",
"de_DE": "",
@@ -25123,12 +25148,12 @@
}
},
{
- "ID": "GDBStubPort",
+ "ID": "SettingsTabDebugGDBStubPort",
"Translations": {
"ar_SA": "",
"de_DE": "",
"el_GR": "",
- "en_US": "GDB stub port:",
+ "en_US": "GDB Stub Port:",
"es_ES": "",
"fr_FR": "",
"he_IL": "",
@@ -25148,12 +25173,12 @@
}
},
{
- "ID": "DebuggerSuspendOnStart",
+ "ID": "SettingsTabDebugSuspendOnStart",
"Translations": {
"ar_SA": "",
"de_DE": "",
"el_GR": "",
- "en_US": "Suspend application on start",
+ "en_US": "Suspend Application on Start",
"es_ES": "",
"fr_FR": "",
"he_IL": "",
@@ -25173,7 +25198,7 @@
}
},
{
- "ID": "DebuggerSuspendOnStartTooltip",
+ "ID": "SettingsTabDebugSuspendOnStartTooltip",
"Translations": {
"ar_SA": "",
"de_DE": "",
diff --git a/src/Ryujinx/UI/Views/Settings/SettingsDebugView.axaml b/src/Ryujinx/UI/Views/Settings/SettingsDebugView.axaml
index 036471059..f491dda24 100644
--- a/src/Ryujinx/UI/Views/Settings/SettingsDebugView.axaml
+++ b/src/Ryujinx/UI/Views/Settings/SettingsDebugView.axaml
@@ -26,19 +26,21 @@
Orientation="Vertical"
Spacing="10">
+
-
+
-
+
From cd2a7c99166c544973ddfa6513f68e663f611870 Mon Sep 17 00:00:00 2001
From: Coxxs <58-coxxs@users.noreply.git.ryujinx.app>
Date: Tue, 24 Jun 2025 07:25:47 +0800
Subject: [PATCH 50/51] gdb: Prevent BreakHandler being called multiple times
from the same breakpoint
---
src/Ryujinx.HLE/Debugger/Debugger.cs | 13 ++++++++++---
1 file changed, 10 insertions(+), 3 deletions(-)
diff --git a/src/Ryujinx.HLE/Debugger/Debugger.cs b/src/Ryujinx.HLE/Debugger/Debugger.cs
index 5bde92aed..21bb32ec2 100644
--- a/src/Ryujinx.HLE/Debugger/Debugger.cs
+++ b/src/Ryujinx.HLE/Debugger/Debugger.cs
@@ -32,6 +32,7 @@ namespace Ryujinx.HLE.Debugger
private Thread DebuggerThread;
private Thread MessageHandlerThread;
private bool _shuttingDown = false;
+ private ManualResetEventSlim _breakHandlerEvent = new ManualResetEventSlim(false);
private ulong? cThread;
private ulong? gThread;
@@ -237,6 +238,7 @@ namespace Ryujinx.HLE.Debugger
case ThreadBreakMessage { Context: var ctx }:
DebugProcess.DebugStop();
gThread = cThread = ctx.ThreadUid;
+ _breakHandlerEvent.Set();
Reply($"T05thread:{ctx.ThreadUid:x};");
break;
@@ -1266,15 +1268,20 @@ namespace Ryujinx.HLE.Debugger
Messages.Add(new KillMessage());
MessageHandlerThread.Join();
Messages.Dispose();
+ _breakHandlerEvent.Dispose();
}
}
public void BreakHandler(IExecutionContext ctx, ulong address, int imm)
{
- Logger.Notice.Print(LogClass.GdbStub, $"Break hit on thread {ctx.ThreadUid} at pc {address:x016}");
-
- Messages.Add(new ThreadBreakMessage(ctx, address, imm));
DebugProcess.DebugInterruptHandler(ctx);
+
+ _breakHandlerEvent.Reset();
+ Messages.Add(new ThreadBreakMessage(ctx, address, imm));
+ // Messages.Add can block, so we log it after adding the message to make sure user can see the log at the same time GDB receives the break message
+ Logger.Notice.Print(LogClass.GdbStub, $"Break hit on thread {ctx.ThreadUid} at pc {address:x016}");
+ // Wait for the process to stop before returning to avoid BreakHander being called multiple times from the same breakpoint
+ _breakHandlerEvent.Wait(5000);
}
public void StepHandler(IExecutionContext ctx)
From 1b37038d5a04d9ebd84431b36e9742bdb048b5a3 Mon Sep 17 00:00:00 2001
From: Coxxs <58-coxxs@users.noreply.git.ryujinx.app>
Date: Tue, 24 Jun 2025 07:26:20 +0800
Subject: [PATCH 51/51] gdb: Log CommandReadMemory failure
---
src/Ryujinx.HLE/Debugger/Debugger.cs | 3 +++
1 file changed, 3 insertions(+)
diff --git a/src/Ryujinx.HLE/Debugger/Debugger.cs b/src/Ryujinx.HLE/Debugger/Debugger.cs
index 21bb32ec2..d32fa2c99 100644
--- a/src/Ryujinx.HLE/Debugger/Debugger.cs
+++ b/src/Ryujinx.HLE/Debugger/Debugger.cs
@@ -862,6 +862,9 @@ namespace Ryujinx.HLE.Debugger
}
catch (InvalidMemoryRegionException)
{
+ // InvalidAccessHandler will show an error message, we log it again to tell user the error is from GDB (which can be ignored)
+ // TODO: Do not let InvalidAccessHandler show the error message
+ Logger.Notice.Print(LogClass.GdbStub, $"GDB failed to read memory at 0x{addr:X16}");
ReplyError();
}
}