mirror of
https://git.naxdy.org/Mirror/Ryujinx.git
synced 2025-03-14 22:40:18 +00:00
Debugger: Initial implementation
This commit is contained in:
parent
fb4b9a3003
commit
1118d82205
9 changed files with 532 additions and 0 deletions
410
Ryujinx.HLE/Debugger/Debugger.cs
Normal file
410
Ryujinx.HLE/Debugger/Debugger.cs
Normal file
|
@ -0,0 +1,410 @@
|
||||||
|
using Ryujinx.Common.Logging;
|
||||||
|
using Ryujinx.Memory;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Concurrent;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Net;
|
||||||
|
using System.Net.Sockets;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading;
|
||||||
|
|
||||||
|
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<IMessage> Messages = new BlockingCollection<IMessage>(1);
|
||||||
|
private Thread SocketThread;
|
||||||
|
private Thread HandlerThread;
|
||||||
|
|
||||||
|
private long cThread;
|
||||||
|
private long gThread;
|
||||||
|
|
||||||
|
public Debugger(Switch device, ushort port)
|
||||||
|
{
|
||||||
|
Device = device;
|
||||||
|
GdbStubPort = port;
|
||||||
|
|
||||||
|
ARMeilleure.Optimizations.EnableDebugging = true;
|
||||||
|
|
||||||
|
SocketThread = new Thread(SocketReaderThreadMain);
|
||||||
|
HandlerThread = new Thread(HandlerThreadMain);
|
||||||
|
SocketThread.Start();
|
||||||
|
HandlerThread.Start();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void HaltApplication() => Device.System.DebugGetApplicationProcess().DebugStopAllThreads();
|
||||||
|
private long[] GetThreadIds() => Device.System.DebugGetApplicationProcess().DebugGetThreadUids();
|
||||||
|
private ARMeilleure.State.ExecutionContext GetThread(long threadUid) => Device.System.DebugGetApplicationProcess().DebugGetThreadContext(threadUid);
|
||||||
|
private ARMeilleure.State.ExecutionContext[] GetThreads() => GetThreadIds().Select(x => GetThread(x)).ToArray();
|
||||||
|
private IVirtualMemoryManager GetMemory() => Device.System.DebugGetApplicationProcess().CpuMemory;
|
||||||
|
|
||||||
|
const int GdbRegisterCount = 34;
|
||||||
|
|
||||||
|
private int GdbRegisterHexSize(int gdbRegId)
|
||||||
|
{
|
||||||
|
switch (gdbRegId)
|
||||||
|
{
|
||||||
|
case >= 0 and <= 31:
|
||||||
|
return 16;
|
||||||
|
case 32:
|
||||||
|
return 16;
|
||||||
|
case 33:
|
||||||
|
return 8;
|
||||||
|
default:
|
||||||
|
throw new ArgumentException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GdbReadRegister(ARMeilleure.State.ExecutionContext state, int gdbRegId)
|
||||||
|
{
|
||||||
|
switch (gdbRegId)
|
||||||
|
{
|
||||||
|
case >= 0 and <= 31:
|
||||||
|
return $"{state.GetX(gdbRegId):x16}";
|
||||||
|
case 32:
|
||||||
|
return $"{state.DebugPc:x16}";
|
||||||
|
case 33:
|
||||||
|
return $"{state.GetPstate():x8}";
|
||||||
|
default:
|
||||||
|
throw new ArgumentException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void GdbWriteRegister(ARMeilleure.State.ExecutionContext state, int gdbRegId, ulong value)
|
||||||
|
{
|
||||||
|
switch (gdbRegId)
|
||||||
|
{
|
||||||
|
case >= 0 and <= 31:
|
||||||
|
state.SetX(gdbRegId, value);
|
||||||
|
return;
|
||||||
|
case 32:
|
||||||
|
state.DebugPc = value;
|
||||||
|
return;
|
||||||
|
case 33:
|
||||||
|
state.SetPstate((uint)value);
|
||||||
|
return;
|
||||||
|
default:
|
||||||
|
throw new ArgumentException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void HandlerThreadMain()
|
||||||
|
{
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
switch (Messages.Take())
|
||||||
|
{
|
||||||
|
case AbortMessage _:
|
||||||
|
return;
|
||||||
|
|
||||||
|
case BreakInMessage _:
|
||||||
|
Logger.Notice.Print(LogClass.GdbStub, "Break-in requested");
|
||||||
|
// TODO
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ProcessCommand(string cmd)
|
||||||
|
{
|
||||||
|
StringStream ss = new StringStream(cmd);
|
||||||
|
|
||||||
|
switch (ss.ReadChar())
|
||||||
|
{
|
||||||
|
case '!':
|
||||||
|
if (!ss.IsEmpty())
|
||||||
|
{
|
||||||
|
goto default;
|
||||||
|
}
|
||||||
|
// Enable extended mode
|
||||||
|
Reply("OK");
|
||||||
|
break;
|
||||||
|
case '?':
|
||||||
|
if (!ss.IsEmpty())
|
||||||
|
{
|
||||||
|
goto default;
|
||||||
|
}
|
||||||
|
CommandQuery();
|
||||||
|
break;
|
||||||
|
case 'c':
|
||||||
|
CommandContinue(ss.IsEmpty() ? null : ss.ReadRemainingAsHex());
|
||||||
|
break;
|
||||||
|
case 'D':
|
||||||
|
if (!ss.IsEmpty())
|
||||||
|
{
|
||||||
|
goto default;
|
||||||
|
}
|
||||||
|
CommandDetach();
|
||||||
|
break;
|
||||||
|
case 'g':
|
||||||
|
if (!ss.IsEmpty())
|
||||||
|
{
|
||||||
|
goto default;
|
||||||
|
}
|
||||||
|
CommandReadGeneralRegisters();
|
||||||
|
break;
|
||||||
|
case 'G':
|
||||||
|
CommandWriteGeneralRegisters(ss);
|
||||||
|
break;
|
||||||
|
case 'H':
|
||||||
|
{
|
||||||
|
char op = ss.ReadChar();
|
||||||
|
ulong threadId = ss.ReadRemainingAsHex();
|
||||||
|
CommandSetThread(op, (long)threadId);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'k':
|
||||||
|
Logger.Notice.Print(LogClass.GdbStub, "Kill request received");
|
||||||
|
Reply("");
|
||||||
|
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();
|
||||||
|
CommandReadGeneralRegister((int)gdbRegId);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'P':
|
||||||
|
{
|
||||||
|
ulong gdbRegId = ss.ReadUntilAsHex('=');
|
||||||
|
ulong value = ss.ReadRemainingAsHex();
|
||||||
|
CommandWriteGeneralRegister((int)gdbRegId, value);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
Logger.Notice.Print(LogClass.GdbStub, $"Unknown command: {cmd}");
|
||||||
|
Reply("");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CommandQuery()
|
||||||
|
{
|
||||||
|
// GDB is performing initial contact. Stop everything.
|
||||||
|
HaltApplication();
|
||||||
|
gThread = cThread = GetThreadIds().First();
|
||||||
|
Reply($"T05thread:{cThread:x}");
|
||||||
|
}
|
||||||
|
|
||||||
|
void CommandContinue(ulong? newPc)
|
||||||
|
{
|
||||||
|
if (newPc.HasValue)
|
||||||
|
{
|
||||||
|
GetThread(cThread).DebugPc = newPc.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var thread in GetThreads())
|
||||||
|
{
|
||||||
|
thread.DebugContinue();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CommandDetach()
|
||||||
|
{
|
||||||
|
// TODO: Remove all breakpoints
|
||||||
|
CommandContinue(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CommandReadGeneralRegisters()
|
||||||
|
{
|
||||||
|
var ctx = GetThread(gThread);
|
||||||
|
string registers = "";
|
||||||
|
for (int i = 0; i < GdbRegisterCount; i++)
|
||||||
|
{
|
||||||
|
registers += GdbReadRegister(ctx, i);
|
||||||
|
}
|
||||||
|
Reply(registers);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CommandWriteGeneralRegisters(StringStream ss)
|
||||||
|
{
|
||||||
|
var ctx = GetThread(gThread);
|
||||||
|
for (int i = 0; i < GdbRegisterCount; i++)
|
||||||
|
{
|
||||||
|
GdbWriteRegister(ctx, i, ss.ReadLengthAsHex(GdbRegisterHexSize(i)));
|
||||||
|
}
|
||||||
|
Reply(ss.IsEmpty() ? "OK" : "E99");
|
||||||
|
}
|
||||||
|
|
||||||
|
void CommandSetThread(char op, long threadId)
|
||||||
|
{
|
||||||
|
switch (op)
|
||||||
|
{
|
||||||
|
case 'c':
|
||||||
|
cThread = threadId;
|
||||||
|
Reply("OK");
|
||||||
|
return;
|
||||||
|
case 'g':
|
||||||
|
gThread = threadId;
|
||||||
|
Reply("OK");
|
||||||
|
return;
|
||||||
|
default:
|
||||||
|
Reply("E99");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CommandReadMemory(ulong addr, ulong len)
|
||||||
|
{
|
||||||
|
var data = new byte[len];
|
||||||
|
GetMemory().Read(addr, data);
|
||||||
|
Reply(string.Join("", data.Select(x => $"{x:x2}")));
|
||||||
|
}
|
||||||
|
|
||||||
|
void CommandWriteMemory(ulong addr, ulong len, StringStream ss)
|
||||||
|
{
|
||||||
|
var data = new byte[len];
|
||||||
|
for (ulong i = 0; i < len; i++)
|
||||||
|
{
|
||||||
|
data[i] = (byte)ss.ReadLengthAsHex(2);
|
||||||
|
}
|
||||||
|
GetMemory().Write(addr, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CommandReadGeneralRegister(int gdbRegId)
|
||||||
|
{
|
||||||
|
var ctx = GetThread(gThread);
|
||||||
|
Reply(GdbReadRegister(ctx, gdbRegId));
|
||||||
|
}
|
||||||
|
|
||||||
|
void CommandWriteGeneralRegister(int gdbRegId, ulong value)
|
||||||
|
{
|
||||||
|
var ctx = GetThread(gThread);
|
||||||
|
GdbWriteRegister(ctx, gdbRegId, value);
|
||||||
|
Reply("OK");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Reply(string cmd)
|
||||||
|
{
|
||||||
|
WriteStream.Write(Encoding.ASCII.GetBytes($"${cmd}#{CalculateChecksum(cmd):x2}"));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SocketReaderThreadMain()
|
||||||
|
{
|
||||||
|
restartListen:
|
||||||
|
try
|
||||||
|
{
|
||||||
|
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");
|
||||||
|
|
||||||
|
ClientSocket = ListenerSocket.AcceptSocket();
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
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()}";
|
||||||
|
// Debug.Assert(checksum == $"{CalculateChecksum(cmd):x2}");
|
||||||
|
|
||||||
|
Messages.Add(new CommandMessage(cmd));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
eof:
|
||||||
|
Logger.Notice.Print(LogClass.GdbStub, "GDB client lost connection");
|
||||||
|
goto restartListen;
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
Logger.Notice.Print(LogClass.GdbStub, "GDB stub socket closed");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private byte CalculateChecksum(string cmd)
|
||||||
|
{
|
||||||
|
byte checksum = 0;
|
||||||
|
foreach (char x in cmd)
|
||||||
|
{
|
||||||
|
unchecked
|
||||||
|
{
|
||||||
|
checksum += (byte)x;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return checksum;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
Dispose(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
if (disposing)
|
||||||
|
{
|
||||||
|
if (HandlerThread.IsAlive)
|
||||||
|
{
|
||||||
|
Messages.Add(new AbortMessage());
|
||||||
|
}
|
||||||
|
ListenerSocket.Stop();
|
||||||
|
ClientSocket?.Shutdown(SocketShutdown.Both);
|
||||||
|
ClientSocket?.Close();
|
||||||
|
ReadStream?.Close();
|
||||||
|
WriteStream?.Close();
|
||||||
|
SocketThread.Join();
|
||||||
|
HandlerThread.Join();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
15
Ryujinx.HLE/Debugger/GdbSignal.cs
Normal file
15
Ryujinx.HLE/Debugger/GdbSignal.cs
Normal file
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
6
Ryujinx.HLE/Debugger/Message/AbortMessage.cs
Normal file
6
Ryujinx.HLE/Debugger/Message/AbortMessage.cs
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
namespace Ryujinx.HLE.Debugger
|
||||||
|
{
|
||||||
|
struct AbortMessage : IMessage
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
6
Ryujinx.HLE/Debugger/Message/BreakInMessage.cs
Normal file
6
Ryujinx.HLE/Debugger/Message/BreakInMessage.cs
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
namespace Ryujinx.HLE.Debugger
|
||||||
|
{
|
||||||
|
struct BreakInMessage : IMessage
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
12
Ryujinx.HLE/Debugger/Message/CommandMessage.cs
Normal file
12
Ryujinx.HLE/Debugger/Message/CommandMessage.cs
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
namespace Ryujinx.HLE.Debugger
|
||||||
|
{
|
||||||
|
struct CommandMessage : IMessage
|
||||||
|
{
|
||||||
|
public string Command;
|
||||||
|
|
||||||
|
public CommandMessage(string cmd)
|
||||||
|
{
|
||||||
|
Command = cmd;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
6
Ryujinx.HLE/Debugger/Message/IMessage.cs
Normal file
6
Ryujinx.HLE/Debugger/Message/IMessage.cs
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
namespace Ryujinx.HLE.Debugger
|
||||||
|
{
|
||||||
|
interface IMessage
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
6
Ryujinx.HLE/Debugger/Message/SendNackMessage.cs
Normal file
6
Ryujinx.HLE/Debugger/Message/SendNackMessage.cs
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
namespace Ryujinx.HLE.Debugger
|
||||||
|
{
|
||||||
|
struct SendNackMessage : IMessage
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
68
Ryujinx.HLE/Debugger/StringStream.cs
Normal file
68
Ryujinx.HLE/Debugger/StringStream.cs
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
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 bool IsEmpty()
|
||||||
|
{
|
||||||
|
return Position >= Data.Length;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -23,6 +23,7 @@ namespace Ryujinx.HLE
|
||||||
public PerformanceStatistics Statistics { get; }
|
public PerformanceStatistics Statistics { get; }
|
||||||
public Hid Hid { get; }
|
public Hid Hid { get; }
|
||||||
public TamperMachine TamperMachine { get; }
|
public TamperMachine TamperMachine { get; }
|
||||||
|
public Debugger.Debugger Debugger { get; }
|
||||||
public IHostUiHandler UiHandler { get; }
|
public IHostUiHandler UiHandler { get; }
|
||||||
|
|
||||||
public bool EnableDeviceVsync { get; set; } = true;
|
public bool EnableDeviceVsync { get; set; } = true;
|
||||||
|
@ -55,6 +56,7 @@ namespace Ryujinx.HLE
|
||||||
Statistics = new PerformanceStatistics();
|
Statistics = new PerformanceStatistics();
|
||||||
Hid = new Hid(this, System.HidStorage);
|
Hid = new Hid(this, System.HidStorage);
|
||||||
Application = new ApplicationLoader(this);
|
Application = new ApplicationLoader(this);
|
||||||
|
Debugger = Configuration.EnableGdbStub ? new Debugger.Debugger(this, configuration.GdbStubPort) : null;
|
||||||
TamperMachine = new TamperMachine();
|
TamperMachine = new TamperMachine();
|
||||||
|
|
||||||
System.State.SetLanguage(Configuration.SystemLanguage);
|
System.State.SetLanguage(Configuration.SystemLanguage);
|
||||||
|
@ -153,6 +155,7 @@ namespace Ryujinx.HLE
|
||||||
AudioDeviceDriver.Dispose();
|
AudioDeviceDriver.Dispose();
|
||||||
FileSystem.Dispose();
|
FileSystem.Dispose();
|
||||||
Memory.Dispose();
|
Memory.Dispose();
|
||||||
|
Debugger.Dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue