diff --git a/ChocolArm64/AOptimizations.cs b/ChocolArm64/AOptimizations.cs index a3c82dccd..2627c2368 100644 --- a/ChocolArm64/AOptimizations.cs +++ b/ChocolArm64/AOptimizations.cs @@ -1,4 +1,6 @@ public static class AOptimizations { public static bool DisableMemoryChecks = false; + + public static bool GenerateCallStack = true; } \ No newline at end of file diff --git a/ChocolArm64/Instruction/AInstEmitException.cs b/ChocolArm64/Instruction/AInstEmitException.cs index 3964c9497..041e2890a 100644 --- a/ChocolArm64/Instruction/AInstEmitException.cs +++ b/ChocolArm64/Instruction/AInstEmitException.cs @@ -8,8 +8,6 @@ namespace ChocolArm64.Instruction { static partial class AInstEmit { - private const BindingFlags Binding = BindingFlags.NonPublic | BindingFlags.Instance; - public static void Brk(AILEmitterCtx Context) { EmitExceptionCall(Context, nameof(AThreadState.OnBreak)); @@ -30,9 +28,7 @@ namespace ChocolArm64.Instruction Context.EmitLdc_I4(Op.Id); - MethodInfo MthdInfo = typeof(AThreadState).GetMethod(MthdName, Binding); - - Context.EmitCall(MthdInfo); + Context.EmitPrivateCall(typeof(AThreadState), MthdName); //Check if the thread should still be running, if it isn't then we return 0 //to force a return to the dispatcher and then exit the thread. @@ -73,11 +69,7 @@ namespace ChocolArm64.Instruction Context.EmitLdc_I8(Op.Position); Context.EmitLdc_I4(Op.RawOpCode); - string MthdName = nameof(AThreadState.OnUndefined); - - MethodInfo MthdInfo = typeof(AThreadState).GetMethod(MthdName, Binding); - - Context.EmitCall(MthdInfo); + Context.EmitPrivateCall(typeof(AThreadState), nameof(AThreadState.OnUndefined)); if (Context.CurrBlock.Next != null) { diff --git a/ChocolArm64/Instruction/AInstEmitFlow.cs b/ChocolArm64/Instruction/AInstEmitFlow.cs index 91262834f..89979d050 100644 --- a/ChocolArm64/Instruction/AInstEmitFlow.cs +++ b/ChocolArm64/Instruction/AInstEmitFlow.cs @@ -35,6 +35,14 @@ namespace ChocolArm64.Instruction { AOpCodeBImmAl Op = (AOpCodeBImmAl)Context.CurrOp; + if (AOptimizations.GenerateCallStack) + { + Context.EmitLdarg(ATranslatedSub.StateArgIdx); + Context.EmitLdc_I8(Op.Imm); + + Context.EmitPrivateCall(typeof(AThreadState), nameof(AThreadState.EnterMethod)); + } + Context.EmitLdc_I(Op.Position + 4); Context.EmitStint(AThreadState.LRIndex); Context.EmitStoreState(); @@ -72,6 +80,14 @@ namespace ChocolArm64.Instruction { AOpCodeBReg Op = (AOpCodeBReg)Context.CurrOp; + if (AOptimizations.GenerateCallStack) + { + Context.EmitLdarg(ATranslatedSub.StateArgIdx); + Context.EmitLdintzr(Op.Rn); + + Context.EmitPrivateCall(typeof(AThreadState), nameof(AThreadState.EnterMethod)); + } + Context.EmitLdc_I(Op.Position + 4); Context.EmitStint(AThreadState.LRIndex); Context.EmitStoreState(); @@ -84,6 +100,14 @@ namespace ChocolArm64.Instruction { AOpCodeBReg Op = (AOpCodeBReg)Context.CurrOp; + if (AOptimizations.GenerateCallStack) + { + Context.EmitLdarg(ATranslatedSub.StateArgIdx); + Context.EmitLdintzr(Op.Rn); + + Context.EmitPrivateCall(typeof(AThreadState), nameof(AThreadState.JumpMethod)); + } + Context.EmitStoreState(); Context.EmitLdintzr(Op.Rn); @@ -105,6 +129,13 @@ namespace ChocolArm64.Instruction public static void Ret(AILEmitterCtx Context) { + if (AOptimizations.GenerateCallStack) + { + Context.EmitLdarg(ATranslatedSub.StateArgIdx); + + Context.EmitPrivateCall(typeof(AThreadState), nameof(AThreadState.ExitMethod)); + } + Context.EmitStoreState(); Context.EmitLdint(AThreadState.LRIndex); diff --git a/ChocolArm64/State/AThreadState.cs b/ChocolArm64/State/AThreadState.cs index 6f3f62f69..ce1278866 100644 --- a/ChocolArm64/State/AThreadState.cs +++ b/ChocolArm64/State/AThreadState.cs @@ -1,5 +1,6 @@ using ChocolArm64.Events; using System; +using System.Collections.Generic; using System.Diagnostics; namespace ChocolArm64.State @@ -56,10 +57,17 @@ namespace ChocolArm64.State public event EventHandler SvcCall; public event EventHandler Undefined; + private Stack CallStack; + private static Stopwatch TickCounter; private static double HostTickFreq; + public AThreadState() + { + CallStack = new Stack(); + } + static AThreadState() { HostTickFreq = 1.0 / Stopwatch.Frequency; @@ -83,5 +91,27 @@ namespace ChocolArm64.State { Undefined?.Invoke(this, new AInstUndefinedEventArgs(Position, RawOpCode)); } + + internal void EnterMethod(long Position) + { + CallStack.Push(Position); + } + + internal void ExitMethod() + { + CallStack.TryPop(out _); + } + + internal void JumpMethod(long Position) + { + CallStack.TryPop(out _); + + CallStack.Push(Position); + } + + public long[] GetCallStack() + { + return CallStack.ToArray(); + } } } \ No newline at end of file diff --git a/ChocolArm64/Translation/AILEmitterCtx.cs b/ChocolArm64/Translation/AILEmitterCtx.cs index 2f4a67e1b..a004a9665 100644 --- a/ChocolArm64/Translation/AILEmitterCtx.cs +++ b/ChocolArm64/Translation/AILEmitterCtx.cs @@ -461,6 +461,21 @@ namespace ChocolArm64.Translation EmitCall(ObjType.GetMethod(MthdName)); } + public void EmitPrivateCall(Type ObjType, string MthdName) + { + if (ObjType == null) + { + throw new ArgumentNullException(nameof(ObjType)); + } + + if (MthdName == null) + { + throw new ArgumentNullException(nameof(MthdName)); + } + + EmitCall(ObjType.GetMethod(MthdName, BindingFlags.Instance | BindingFlags.NonPublic)); + } + public void EmitCall(MethodInfo MthdInfo) { if (MthdInfo == null) diff --git a/Ryujinx.Core/Loaders/Executable.cs b/Ryujinx.Core/Loaders/Executable.cs index 943b8e510..39ee9618b 100644 --- a/Ryujinx.Core/Loaders/Executable.cs +++ b/Ryujinx.Core/Loaders/Executable.cs @@ -7,14 +7,16 @@ namespace Ryujinx.Core.Loaders { class Executable { - private AMemory Memory; - private List Dynamic; private Dictionary m_SymbolTable; public IReadOnlyDictionary SymbolTable => m_SymbolTable; + public string Name { get; private set; } + + private AMemory Memory; + public long ImageBase { get; private set; } public long ImageEnd { get; private set; } @@ -24,6 +26,8 @@ namespace Ryujinx.Core.Loaders m_SymbolTable = new Dictionary(); + Name = Exe.Name; + this.Memory = Memory; this.ImageBase = ImageBase; this.ImageEnd = ImageBase; diff --git a/Ryujinx.Core/Loaders/Executables/IExecutable.cs b/Ryujinx.Core/Loaders/Executables/IExecutable.cs index 09d0aab23..412058d88 100644 --- a/Ryujinx.Core/Loaders/Executables/IExecutable.cs +++ b/Ryujinx.Core/Loaders/Executables/IExecutable.cs @@ -2,6 +2,8 @@ namespace Ryujinx.Core.Loaders.Executables { public interface IExecutable { + string Name { get; } + byte[] Text { get; } byte[] RO { get; } byte[] Data { get; } diff --git a/Ryujinx.Core/Loaders/Executables/Nro.cs b/Ryujinx.Core/Loaders/Executables/Nro.cs index 9f4ef59f5..c3411d226 100644 --- a/Ryujinx.Core/Loaders/Executables/Nro.cs +++ b/Ryujinx.Core/Loaders/Executables/Nro.cs @@ -4,6 +4,8 @@ namespace Ryujinx.Core.Loaders.Executables { class Nro : IExecutable { + public string Name { get; private set; } + public byte[] Text { get; private set; } public byte[] RO { get; private set; } public byte[] Data { get; private set; } @@ -14,8 +16,10 @@ namespace Ryujinx.Core.Loaders.Executables public int DataOffset { get; private set; } public int BssSize { get; private set; } - public Nro(Stream Input) + public Nro(Stream Input, string Name) { + this.Name = Name; + BinaryReader Reader = new BinaryReader(Input); Input.Seek(4, SeekOrigin.Begin); diff --git a/Ryujinx.Core/Loaders/Executables/Nso.cs b/Ryujinx.Core/Loaders/Executables/Nso.cs index 7341ba622..bfe159d7c 100644 --- a/Ryujinx.Core/Loaders/Executables/Nso.cs +++ b/Ryujinx.Core/Loaders/Executables/Nso.cs @@ -6,6 +6,8 @@ namespace Ryujinx.Core.Loaders.Executables { class Nso : IExecutable { + public string Name { get; private set; } + public byte[] Text { get; private set; } public byte[] RO { get; private set; } public byte[] Data { get; private set; } @@ -27,8 +29,10 @@ namespace Ryujinx.Core.Loaders.Executables HasDataHash = 1 << 5 } - public Nso(Stream Input) + public Nso(Stream Input, string Name) { + this.Name = Name; + BinaryReader Reader = new BinaryReader(Input); Input.Seek(0, SeekOrigin.Begin); diff --git a/Ryujinx.Core/LogClass.cs b/Ryujinx.Core/LogClass.cs index 60a8de787..014b57325 100644 --- a/Ryujinx.Core/LogClass.cs +++ b/Ryujinx.Core/LogClass.cs @@ -16,6 +16,7 @@ ServiceApm, ServiceAudio, ServiceBsd, + ServiceCaps, ServiceFriend, ServiceFs, ServiceHid, diff --git a/Ryujinx.Core/OsHle/Horizon.cs b/Ryujinx.Core/OsHle/Horizon.cs index 9e113080c..6c625b09d 100644 --- a/Ryujinx.Core/OsHle/Horizon.cs +++ b/Ryujinx.Core/OsHle/Horizon.cs @@ -58,7 +58,9 @@ namespace Ryujinx.Core.OsHle using (FileStream Input = new FileStream(File, FileMode.Open)) { - Nso Program = new Nso(Input); + string Name = Path.GetFileNameWithoutExtension(File); + + Nso Program = new Nso(Input, Name); MainProcess.LoadProgram(Program); } @@ -80,13 +82,15 @@ namespace Ryujinx.Core.OsHle { bool IsNro = Path.GetExtension(FileName).ToLower() == ".nro"; + string Name = Path.GetFileNameWithoutExtension(FileName); + Process MainProcess = MakeProcess(); using (FileStream Input = new FileStream(FileName, FileMode.Open)) { MainProcess.LoadProgram(IsNro - ? (IExecutable)new Nro(Input) - : (IExecutable)new Nso(Input)); + ? (IExecutable)new Nro(Input, Name) + : (IExecutable)new Nso(Input, Name)); } MainProcess.SetEmptyArgs(); diff --git a/Ryujinx.Core/OsHle/Kernel/SvcThread.cs b/Ryujinx.Core/OsHle/Kernel/SvcThread.cs index 1e4d61b4f..e1300b739 100644 --- a/Ryujinx.Core/OsHle/Kernel/SvcThread.cs +++ b/Ryujinx.Core/OsHle/Kernel/SvcThread.cs @@ -156,13 +156,13 @@ namespace Ryujinx.Core.OsHle.Kernel private void SvcSetThreadActivity(AThreadState ThreadState) { int Handle = (int)ThreadState.X0; - bool Active = (int)ThreadState.X1 != 0; + bool Active = (int)ThreadState.X1 == 0; - KThread CurrThread = Process.HandleTable.GetData(Handle); + KThread Thread = Process.HandleTable.GetData(Handle); - if (CurrThread != null) + if (Thread != null) { - Process.Scheduler.SetThreadActivity(CurrThread, Active); + Process.Scheduler.SetThreadActivity(Thread, Active); ThreadState.X0 = 0; } diff --git a/Ryujinx.Core/OsHle/Process.cs b/Ryujinx.Core/OsHle/Process.cs index bd4ff1ff2..0f8c726c6 100644 --- a/Ryujinx.Core/OsHle/Process.cs +++ b/Ryujinx.Core/OsHle/Process.cs @@ -1,6 +1,7 @@ using ChocolArm64; using ChocolArm64.Events; using ChocolArm64.Memory; +using ChocolArm64.State; using Ryujinx.Core.Loaders; using Ryujinx.Core.Loaders.Executables; using Ryujinx.Core.OsHle.Exceptions; @@ -10,6 +11,7 @@ using Ryujinx.Core.OsHle.Services.Nv; using System; using System.Collections.Concurrent; using System.Collections.Generic; +using System.Text; namespace Ryujinx.Core.OsHle { @@ -47,9 +49,11 @@ namespace Ryujinx.Core.OsHle private ConcurrentDictionary Threads; + private KThread MainThread; + private List Executables; - private KThread MainThread; + private Dictionary SymbolTable; private long ImageBase; @@ -121,6 +125,8 @@ namespace Ryujinx.Core.OsHle return false; } + MakeSymbolTable(); + MapRWMemRegion( MemoryRegions.MainStackAddress, MemoryRegions.MainStackSize, @@ -227,20 +233,23 @@ namespace Ryujinx.Core.OsHle throw new UndefinedInstructionException(e.Position, e.RawOpCode); } + private void MakeSymbolTable() + { + SymbolTable = new Dictionary(); + + foreach (Executable Exe in Executables) + { + foreach (KeyValuePair KV in Exe.SymbolTable) + { + SymbolTable.TryAdd(Exe.ImageBase + KV.Key, KV.Value); + } + } + } + private ATranslator GetTranslator() { if (Translator == null) { - Dictionary SymbolTable = new Dictionary(); - - foreach (Executable Exe in Executables) - { - foreach (KeyValuePair KV in Exe.SymbolTable) - { - SymbolTable.TryAdd(Exe.ImageBase + KV.Key, KV.Value); - } - } - Translator = new ATranslator(SymbolTable); Translator.CpuTrace += CpuTraceHandler; @@ -249,6 +258,16 @@ namespace Ryujinx.Core.OsHle return Translator; } + public void EnableCpuTracing() + { + Translator.EnableCpuTrace = true; + } + + public void DisableCpuTracing() + { + Translator.EnableCpuTrace = false; + } + private void CpuTraceHandler(object sender, ACpuTraceEventArgs e) { string NsoName = string.Empty; @@ -263,17 +282,47 @@ namespace Ryujinx.Core.OsHle } } - Logging.Trace(LogClass.Loader, $"Executing at 0x{e.Position:x16} {e.SubName} {NsoName}"); + Logging.Trace(LogClass.CPU, $"Executing at 0x{e.Position:x16} {e.SubName} {NsoName}"); } - public void EnableCpuTracing() + public void PrintStackTrace(AThreadState ThreadState) { - Translator.EnableCpuTrace = true; + long[] Positions = ThreadState.GetCallStack(); + + StringBuilder Trace = new StringBuilder(); + + Trace.AppendLine("Guest stack trace:"); + + foreach (long Position in Positions) + { + if (!SymbolTable.TryGetValue(Position, out string SubName)) + { + SubName = $"Sub{Position:x16}"; + } + + Trace.AppendLine(" " + SubName + " (" + GetNsoNameAndAddress(Position) + ")"); + } + + Logging.Trace(LogClass.CPU, Trace.ToString()); } - public void DisableCpuTracing() + private string GetNsoNameAndAddress(long Position) { - Translator.EnableCpuTrace = false; + string Name = string.Empty; + + for (int Index = Executables.Count - 1; Index >= 0; Index--) + { + if (Position >= Executables[Index].ImageBase) + { + long Offset = Position - Executables[Index].ImageBase; + + Name = $"{Executables[Index].Name}:{Offset:x8}"; + + break; + } + } + + return Name; } private int GetFreeTlsSlot(AThread Thread) diff --git a/Ryujinx.Core/OsHle/Services/Am/IApplicationFunctions.cs b/Ryujinx.Core/OsHle/Services/Am/IApplicationFunctions.cs index ca4e368a6..f1c63fac5 100644 --- a/Ryujinx.Core/OsHle/Services/Am/IApplicationFunctions.cs +++ b/Ryujinx.Core/OsHle/Services/Am/IApplicationFunctions.cs @@ -18,6 +18,7 @@ namespace Ryujinx.Core.OsHle.Services.Am { 20, EnsureSaveData }, { 21, GetDesiredLanguage }, { 22, SetTerminateResult }, + { 23, GetDisplayVersion }, { 40, NotifyRunning } }; } @@ -67,6 +68,15 @@ namespace Ryujinx.Core.OsHle.Services.Am return 0; } + public long GetDisplayVersion(ServiceCtx Context) + { + //FIXME: Need to check correct version on a switch. + Context.ResponseData.Write(1L); + Context.ResponseData.Write(0L); + + return 0; + } + public long NotifyRunning(ServiceCtx Context) { Context.ResponseData.Write(1); diff --git a/Ryujinx.Core/OsHle/Services/Aud/IAudioDeviceService.cs b/Ryujinx.Core/OsHle/Services/Aud/IAudioDevice.cs similarity index 83% rename from Ryujinx.Core/OsHle/Services/Aud/IAudioDeviceService.cs rename to Ryujinx.Core/OsHle/Services/Aud/IAudioDevice.cs index b27d14489..039a4413f 100644 --- a/Ryujinx.Core/OsHle/Services/Aud/IAudioDeviceService.cs +++ b/Ryujinx.Core/OsHle/Services/Aud/IAudioDevice.cs @@ -6,7 +6,7 @@ using System.Text; namespace Ryujinx.Core.OsHle.Services.Aud { - class IAudioDeviceService : IpcService + class IAudioDevice : IpcService { private Dictionary m_Commands; @@ -14,12 +14,13 @@ namespace Ryujinx.Core.OsHle.Services.Aud private KEvent SystemEvent; - public IAudioDeviceService() + public IAudioDevice() { m_Commands = new Dictionary() { { 0, ListAudioDeviceName }, { 1, SetAudioDeviceOutputVolume }, + { 3, GetActiveAudioDeviceName }, { 4, QueryAudioDeviceSystemEvent }, { 5, GetActiveChannelCount } }; @@ -72,6 +73,20 @@ namespace Ryujinx.Core.OsHle.Services.Aud return 0; } + public long GetActiveAudioDeviceName(ServiceCtx Context) + { + string Name = "FIXME"; + + long Position = Context.Request.ReceiveBuff[0].Position; + long Size = Context.Request.ReceiveBuff[0].Size; + + byte[] Buffer = Encoding.ASCII.GetBytes(Name + '\0'); + + AMemoryHelper.WriteBytes(Context.Memory, Position, Buffer); + + return 0; + } + public long QueryAudioDeviceSystemEvent(ServiceCtx Context) { int Handle = Context.Process.HandleTable.OpenHandle(SystemEvent); diff --git a/Ryujinx.Core/OsHle/Services/Aud/IAudioRendererManager.cs b/Ryujinx.Core/OsHle/Services/Aud/IAudioRendererManager.cs index eee47089e..dcf3c7b7c 100644 --- a/Ryujinx.Core/OsHle/Services/Aud/IAudioRendererManager.cs +++ b/Ryujinx.Core/OsHle/Services/Aud/IAudioRendererManager.cs @@ -53,7 +53,7 @@ namespace Ryujinx.Core.OsHle.Services.Aud { long UserId = Context.RequestData.ReadInt64(); - MakeObject(Context, new IAudioDeviceService()); + MakeObject(Context, new IAudioDevice()); return 0; } diff --git a/Ryujinx.Core/OsHle/Services/Caps/IAlbumAccessorService.cs b/Ryujinx.Core/OsHle/Services/Caps/IAlbumAccessorService.cs new file mode 100644 index 000000000..d92f3e53c --- /dev/null +++ b/Ryujinx.Core/OsHle/Services/Caps/IAlbumAccessorService.cs @@ -0,0 +1,20 @@ +using Ryujinx.Core.OsHle.Ipc; +using System.Collections.Generic; + +namespace Ryujinx.Core.OsHle.Services.Caps +{ + class IAlbumAccessorService : IpcService + { + private Dictionary m_Commands; + + public override IReadOnlyDictionary Commands => m_Commands; + + public IAlbumAccessorService() + { + m_Commands = new Dictionary() + { + //... + }; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Core/OsHle/Services/Caps/IScreenshotService.cs b/Ryujinx.Core/OsHle/Services/Caps/IScreenshotService.cs new file mode 100644 index 000000000..af9b53a8b --- /dev/null +++ b/Ryujinx.Core/OsHle/Services/Caps/IScreenshotService.cs @@ -0,0 +1,20 @@ +using Ryujinx.Core.OsHle.Ipc; +using System.Collections.Generic; + +namespace Ryujinx.Core.OsHle.Services.Caps +{ + class IScreenshotService : IpcService + { + private Dictionary m_Commands; + + public override IReadOnlyDictionary Commands => m_Commands; + + public IScreenshotService() + { + m_Commands = new Dictionary() + { + //... + }; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Core/OsHle/Services/ServiceFactory.cs b/Ryujinx.Core/OsHle/Services/ServiceFactory.cs index 11a5e46ab..8e639b949 100644 --- a/Ryujinx.Core/OsHle/Services/ServiceFactory.cs +++ b/Ryujinx.Core/OsHle/Services/ServiceFactory.cs @@ -3,6 +3,7 @@ using Ryujinx.Core.OsHle.Services.Am; using Ryujinx.Core.OsHle.Services.Apm; using Ryujinx.Core.OsHle.Services.Aud; using Ryujinx.Core.OsHle.Services.Bsd; +using Ryujinx.Core.OsHle.Services.Caps; using Ryujinx.Core.OsHle.Services.Friend; using Ryujinx.Core.OsHle.Services.FspSrv; using Ryujinx.Core.OsHle.Services.Hid; @@ -57,9 +58,18 @@ namespace Ryujinx.Core.OsHle.Services case "bsd:u": return new IClient(); + case "caps:a": + return new IAlbumAccessorService(); + + case "caps:ss": + return new IScreenshotService(); + case "friend:a": return new IServiceCreator(); + case "friend:u": + return new IServiceCreator(); + case "fsp-srv": return new IFileSystemProxy(); diff --git a/Ryujinx.Core/OsHle/Services/Time/ITimeZoneService.cs b/Ryujinx.Core/OsHle/Services/Time/ITimeZoneService.cs index 767d3cc74..ec50c82f4 100644 --- a/Ryujinx.Core/OsHle/Services/Time/ITimeZoneService.cs +++ b/Ryujinx.Core/OsHle/Services/Time/ITimeZoneService.cs @@ -16,10 +16,23 @@ namespace Ryujinx.Core.OsHle.Services.Time { m_Commands = new Dictionary() { - { 101, ToCalendarTimeWithMyRule } + { 0, GetDeviceLocationName }, + { 101, ToCalendarTimeWithMyRule } }; } + public long GetDeviceLocationName(ServiceCtx Context) + { + Logging.Stub(LogClass.ServiceTime, "Stubbed"); + + for (int Index = 0; Index < 0x24; Index++) + { + Context.ResponseData.Write((byte)0); + } + + return 0; + } + public long ToCalendarTimeWithMyRule(ServiceCtx Context) { long PosixTime = Context.RequestData.ReadInt64();