Split main project into core,graphics and chocolarm4 subproject (#29)

This commit is contained in:
emmauss 2018-02-20 22:09:23 +02:00 committed by gdkchan
parent cb665bb715
commit 62b827f474
257 changed files with 415 additions and 285 deletions

View file

@ -0,0 +1,138 @@
using Ryujinx.Core.OsHle.Handles;
using System.Collections.Generic;
using System.Threading;
namespace Ryujinx.Core.OsHle
{
public class CondVar
{
private Process Process;
private long CondVarAddress;
private long Timeout;
private bool OwnsCondVarValue;
private List<HThread> WaitingThreads;
public CondVar(Process Process, long CondVarAddress, long Timeout)
{
this.Process = Process;
this.CondVarAddress = CondVarAddress;
this.Timeout = Timeout;
WaitingThreads = new List<HThread>();
}
public void WaitForSignal(HThread Thread)
{
int Count = Process.Memory.ReadInt32(CondVarAddress);
if (Count <= 0)
{
lock (WaitingThreads)
{
WaitingThreads.Add(Thread);
}
if (Timeout == -1)
{
Process.Scheduler.WaitForSignal(Thread);
}
else
{
Process.Scheduler.WaitForSignal(Thread, (int)(Timeout / 1000000));
lock (WaitingThreads)
{
WaitingThreads.Remove(Thread);
}
}
}
AcquireCondVarValue();
Count = Process.Memory.ReadInt32(CondVarAddress);
if (Count > 0)
{
Process.Memory.WriteInt32(CondVarAddress, Count - 1);
}
ReleaseCondVarValue();
}
public void SetSignal(HThread Thread, int Count)
{
lock (WaitingThreads)
{
if (Count == -1)
{
Process.Scheduler.Signal(WaitingThreads.ToArray());
AcquireCondVarValue();
Process.Memory.WriteInt32(CondVarAddress, WaitingThreads.Count);
ReleaseCondVarValue();
WaitingThreads.Clear();
}
else
{
if (WaitingThreads.Count > 0)
{
int HighestPriority = WaitingThreads[0].Priority;
int HighestPrioIndex = 0;
for (int Index = 1; Index < WaitingThreads.Count; Index++)
{
if (HighestPriority > WaitingThreads[Index].Priority)
{
HighestPriority = WaitingThreads[Index].Priority;
HighestPrioIndex = Index;
}
}
Process.Scheduler.Signal(WaitingThreads[HighestPrioIndex]);
WaitingThreads.RemoveAt(HighestPrioIndex);
}
AcquireCondVarValue();
Process.Memory.WriteInt32(CondVarAddress, Count);
ReleaseCondVarValue();
}
}
Process.Scheduler.Suspend(Thread.ProcessorId);
Process.Scheduler.Resume(Thread);
}
private void AcquireCondVarValue()
{
if (!OwnsCondVarValue)
{
while (!Process.Memory.AcquireAddress(CondVarAddress))
{
Thread.Yield();
}
OwnsCondVarValue = true;
}
}
private void ReleaseCondVarValue()
{
if (OwnsCondVarValue)
{
OwnsCondVarValue = false;
Process.Memory.ReleaseAddress(CondVarAddress);
}
}
}
}

View file

@ -0,0 +1,12 @@
namespace Ryujinx.Core.OsHle
{
class Display
{
public string Name { get; private set; }
public Display(string Name)
{
this.Name = Name;
}
}
}

View file

@ -0,0 +1,11 @@
using System;
namespace Ryujinx.Core.OsHle.Exceptions
{
public class GuestBrokeExecutionException : Exception
{
private const string ExMsg = "The guest program broke execution!";
public GuestBrokeExecutionException() : base(ExMsg) { }
}
}

View file

@ -0,0 +1,13 @@
using System;
namespace Ryujinx.Core.OsHle.Exceptions
{
public class UndefinedInstructionException : Exception
{
private const string ExMsg = "The instruction at 0x{0:x16} (opcode 0x{1:x8}) is undefined!";
public UndefinedInstructionException() : base() { }
public UndefinedInstructionException(long Position, int OpCode) : base(string.Format(ExMsg, Position, OpCode)) { }
}
}

View file

@ -0,0 +1,12 @@
namespace Ryujinx.Core.OsHle
{
class FileDesc
{
public string Name { get; private set; }
public FileDesc(string Name)
{
this.Name = Name;
}
}
}

View file

@ -0,0 +1,58 @@
using Ryujinx.Core.OsHle.Utilities;
using System;
using System.Collections.Generic;
namespace Ryujinx.Core.OsHle.Handles
{
class HDomain : HSession
{
private Dictionary<int, object> Objects;
private IdPool ObjIds;
public HDomain(HSession Session) : base(Session)
{
Objects = new Dictionary<int, object>();
ObjIds = new IdPool();
}
public int GenerateObjectId(object Obj)
{
int Id = ObjIds.GenerateId();
if (Id == -1)
{
throw new InvalidOperationException();
}
Objects.Add(Id, Obj);
return Id;
}
public void DeleteObject(int Id)
{
if (Objects.TryGetValue(Id, out object Obj))
{
if (Obj is IDisposable DisposableObj)
{
DisposableObj.Dispose();
}
ObjIds.DeleteId(Id);
Objects.Remove(Id);
}
}
public object GetObject(int Id)
{
if (Objects.TryGetValue(Id, out object Obj))
{
return Obj;
}
return null;
}
}
}

View file

@ -0,0 +1,7 @@
namespace Ryujinx.Core.OsHle.Handles
{
class HEvent
{
}
}

View file

@ -0,0 +1,18 @@
namespace Ryujinx.Core.OsHle.Handles
{
class HNvMap
{
public int Id { get; private set; }
public int Size { get; private set; }
public int Align { get; set; }
public int Kind { get; set; }
public long Address { get; set; }
public HNvMap(int Id, int Size)
{
this.Id = Id;
this.Size = Size;
}
}
}

View file

@ -0,0 +1,27 @@
namespace Ryujinx.Core.OsHle.Handles
{
class HSession
{
public string ServiceName { get; private set; }
public bool IsInitialized { get; private set; }
public int State { get; set; }
public HSession(string ServiceName)
{
this.ServiceName = ServiceName;
}
public HSession(HSession Session)
{
ServiceName = Session.ServiceName;
IsInitialized = Session.IsInitialized;
}
public void Initialize()
{
IsInitialized = true;
}
}
}

View file

@ -0,0 +1,30 @@
using System;
namespace Ryujinx.Core.OsHle.Handles
{
class HSessionObj : HSession, IDisposable
{
public object Obj { get; private set; }
public HSessionObj(HSession Session, object Obj) : base(Session)
{
this.Obj = Obj;
}
public void Dispose()
{
Dispose(true);
}
protected virtual void Dispose(bool Disposing)
{
if (Disposing && Obj != null)
{
if (Obj is IDisposable DisposableObj)
{
DisposableObj.Dispose();
}
}
}
}
}

View file

@ -0,0 +1,70 @@
using System;
using System.Collections.Generic;
namespace Ryujinx.Core.OsHle.Handles
{
class HSharedMem
{
private List<long> Positions;
public int PositionsCount => Positions.Count;
public EventHandler<EventArgs> MemoryMapped;
public EventHandler<EventArgs> MemoryUnmapped;
public HSharedMem(long PhysPos)
{
Positions = new List<long>();
}
public void AddVirtualPosition(long Position)
{
lock (Positions)
{
Positions.Add(Position);
MemoryMapped?.Invoke(this, EventArgs.Empty);
}
}
public void RemoveVirtualPosition(long Position)
{
lock (Positions)
{
Positions.Remove(Position);
MemoryUnmapped?.Invoke(this, EventArgs.Empty);
}
}
public long GetVirtualPosition(int Index)
{
lock (Positions)
{
if (Index < 0 || Index >= Positions.Count)
{
throw new ArgumentOutOfRangeException(nameof(Index));
}
return Positions[Index];
}
}
public bool TryGetLastVirtualPosition(out long Position)
{
lock (Positions)
{
if (Positions.Count > 0)
{
Position = Positions[Positions.Count - 1];
return true;
}
Position = 0;
return false;
}
}
}
}

View file

@ -0,0 +1,21 @@
using ChocolArm64;
namespace Ryujinx.Core.OsHle.Handles
{
public class HThread
{
public AThread Thread { get; private set; }
public int ProcessorId { get; private set; }
public int Priority { get; private set; }
public int ThreadId => Thread.ThreadId;
public HThread(AThread Thread, int ProcessorId, int Priority)
{
this.Thread = Thread;
this.ProcessorId = ProcessorId;
this.Priority = Priority;
}
}
}

View file

@ -0,0 +1,21 @@
using ChocolArm64.Memory;
namespace Ryujinx.Core.OsHle.Handles
{
class HTransferMem
{
public AMemory Memory { get; private set; }
public AMemoryPerm Perm { get; private set; }
public long Position { get; private set; }
public long Size { get; private set; }
public HTransferMem(AMemory Memory, AMemoryPerm Perm, long Position, long Size)
{
this.Memory = Memory;
this.Perm = Perm;
this.Position = Position;
this.Size = Size;
}
}
}

View file

@ -0,0 +1,334 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Threading;
namespace Ryujinx.Core.OsHle.Handles
{
public class KProcessScheduler : IDisposable
{
private class SchedulerThread : IDisposable
{
public HThread Thread { get; private set; }
public AutoResetEvent WaitEvent { get; private set; }
public SchedulerThread(HThread Thread)
{
this.Thread = Thread;
WaitEvent = new AutoResetEvent(false);
}
public void Dispose()
{
Dispose(true);
}
protected virtual void Dispose(bool Disposing)
{
if (Disposing)
{
WaitEvent.Dispose();
}
}
}
private class ThreadQueue
{
private List<SchedulerThread> Threads;
public ThreadQueue()
{
Threads = new List<SchedulerThread>();
}
public void Push(SchedulerThread Thread)
{
lock (Threads)
{
Threads.Add(Thread);
}
}
public SchedulerThread Pop(int MinPriority = 0x40)
{
lock (Threads)
{
SchedulerThread SchedThread;
int HighestPriority = MinPriority;
int HighestPrioIndex = -1;
for (int Index = 0; Index < Threads.Count; Index++)
{
SchedThread = Threads[Index];
if (HighestPriority > SchedThread.Thread.Priority)
{
HighestPriority = SchedThread.Thread.Priority;
HighestPrioIndex = Index;
}
}
if (HighestPrioIndex == -1)
{
return null;
}
SchedThread = Threads[HighestPrioIndex];
Threads.RemoveAt(HighestPrioIndex);
return SchedThread;
}
}
public bool HasThread(SchedulerThread SchedThread)
{
lock (Threads)
{
return Threads.Contains(SchedThread);
}
}
}
private ConcurrentDictionary<HThread, SchedulerThread> AllThreads;
private ThreadQueue[] WaitingToRun;
private HashSet<int> ActiveProcessors;
private object SchedLock;
public KProcessScheduler()
{
AllThreads = new ConcurrentDictionary<HThread, SchedulerThread>();
WaitingToRun = new ThreadQueue[4];
for (int Index = 0; Index < 4; Index++)
{
WaitingToRun[Index] = new ThreadQueue();
}
ActiveProcessors = new HashSet<int>();
SchedLock = new object();
}
public void StartThread(HThread Thread)
{
lock (SchedLock)
{
SchedulerThread SchedThread = new SchedulerThread(Thread);
if (!AllThreads.TryAdd(Thread, SchedThread))
{
return;
}
if (!ActiveProcessors.Contains(Thread.ProcessorId))
{
ActiveProcessors.Add(Thread.ProcessorId);
Thread.Thread.Execute();
Logging.Debug($"{GetDbgThreadInfo(Thread)} running.");
}
else
{
WaitingToRun[Thread.ProcessorId].Push(SchedThread);
Logging.Debug($"{GetDbgThreadInfo(SchedThread.Thread)} waiting to run.");
}
}
}
public void Suspend(int ProcessorId)
{
lock (SchedLock)
{
SchedulerThread SchedThread = WaitingToRun[ProcessorId].Pop();
if (SchedThread != null)
{
RunThread(SchedThread);
}
else
{
ActiveProcessors.Remove(ProcessorId);
}
}
}
public void Resume(HThread CurrThread)
{
SchedulerThread SchedThread;
Logging.Debug($"{GetDbgThreadInfo(CurrThread)} entering ipc delay wait state.");
lock (SchedLock)
{
if (!AllThreads.TryGetValue(CurrThread, out SchedThread))
{
Logging.Error($"{GetDbgThreadInfo(CurrThread)} was not found on the scheduler queue!");
return;
}
}
TryResumingExecution(SchedThread);
}
public void WaitForSignal(HThread Thread, int Timeout = -1)
{
SchedulerThread SchedThread;
Logging.Debug($"{GetDbgThreadInfo(Thread)} entering signal wait state.");
lock (SchedLock)
{
SchedThread = WaitingToRun[Thread.ProcessorId].Pop();
if (SchedThread != null)
{
RunThread(SchedThread);
}
else
{
ActiveProcessors.Remove(Thread.ProcessorId);
}
if (!AllThreads.TryGetValue(Thread, out SchedThread))
{
Logging.Error($"{GetDbgThreadInfo(Thread)} was not found on the scheduler queue!");
return;
}
}
if (Timeout >= 0)
{
Logging.Debug($"{GetDbgThreadInfo(Thread)} has wait timeout of {Timeout}ms.");
SchedThread.WaitEvent.WaitOne(Timeout);
}
else
{
SchedThread.WaitEvent.WaitOne();
}
TryResumingExecution(SchedThread);
}
private void TryResumingExecution(SchedulerThread SchedThread)
{
HThread Thread = SchedThread.Thread;
lock (SchedLock)
{
if (ActiveProcessors.Add(Thread.ProcessorId))
{
Logging.Debug($"{GetDbgThreadInfo(Thread)} resuming execution...");
return;
}
WaitingToRun[Thread.ProcessorId].Push(SchedThread);
}
SchedThread.WaitEvent.WaitOne();
Logging.Debug($"{GetDbgThreadInfo(Thread)} resuming execution...");
}
public void Yield(HThread Thread)
{
SchedulerThread SchedThread;
Logging.Debug($"{GetDbgThreadInfo(Thread)} yielded execution.");
lock (SchedLock)
{
SchedThread = WaitingToRun[Thread.ProcessorId].Pop(Thread.Priority);
if (SchedThread == null)
{
Logging.Debug($"{GetDbgThreadInfo(Thread)} resumed because theres nothing better to run.");
return;
}
RunThread(SchedThread);
if (!AllThreads.TryGetValue(Thread, out SchedThread))
{
Logging.Error($"{GetDbgThreadInfo(Thread)} was not found on the scheduler queue!");
return;
}
WaitingToRun[Thread.ProcessorId].Push(SchedThread);
}
SchedThread.WaitEvent.WaitOne();
Logging.Debug($"{GetDbgThreadInfo(Thread)} resuming execution...");
}
private void RunThread(SchedulerThread SchedThread)
{
if (!SchedThread.Thread.Thread.Execute())
{
SchedThread.WaitEvent.Set();
}
else
{
Logging.Debug($"{GetDbgThreadInfo(SchedThread.Thread)} running.");
}
}
public void Signal(params HThread[] Threads)
{
lock (SchedLock)
{
foreach (HThread Thread in Threads)
{
if (AllThreads.TryGetValue(Thread, out SchedulerThread SchedThread))
{
if (!WaitingToRun[Thread.ProcessorId].HasThread(SchedThread))
{
Logging.Debug($"{GetDbgThreadInfo(Thread)} signaled.");
SchedThread.WaitEvent.Set();
}
}
}
}
}
private string GetDbgThreadInfo(HThread Thread)
{
return $"Thread {Thread.ThreadId} (core {Thread.ProcessorId}) prio {Thread.Priority}";
}
public void Dispose()
{
Dispose(true);
}
protected virtual void Dispose(bool Disposing)
{
if (Disposing)
{
foreach (SchedulerThread SchedThread in AllThreads.Values)
{
SchedThread.Dispose();
}
}
}
}
}

View file

@ -0,0 +1,197 @@
using ChocolArm64.Memory;
using Ryujinx.Core.Loaders.Executables;
using Ryujinx.Core.OsHle.Handles;
using Ryujinx.Core.OsHle.Utilities;
using System;
using System.Collections.Concurrent;
using System.IO;
namespace Ryujinx.Core.OsHle
{
public class Horizon
{
internal const int HidSize = 0x40000;
internal const int FontSize = 0x50;
internal int HidHandle { get; private set; }
internal int FontHandle { get; private set; }
public long HidOffset { get; private set; }
public long FontOffset { get; private set; }
internal IdPool IdGen { get; private set; }
internal IdPool NvMapIds { get; private set; }
internal IdPoolWithObj Handles { get; private set; }
internal IdPoolWithObj Fds { get; private set; }
internal IdPoolWithObj Displays { get; private set; }
public ConcurrentDictionary<long, Mutex> Mutexes { get; private set; }
public ConcurrentDictionary<long, CondVar> CondVars { get; private set; }
private ConcurrentDictionary<int, Process> Processes;
private HSharedMem HidSharedMem;
private AMemoryAlloc Allocator;
private Switch Ns;
public Horizon(Switch Ns)
{
this.Ns = Ns;
IdGen = new IdPool();
NvMapIds = new IdPool();
Handles = new IdPoolWithObj();
Fds = new IdPoolWithObj();
Displays = new IdPoolWithObj();
Mutexes = new ConcurrentDictionary<long, Mutex>();
CondVars = new ConcurrentDictionary<long, CondVar>();
Processes = new ConcurrentDictionary<int, Process>();
Allocator = new AMemoryAlloc();
HidOffset = Allocator.Alloc(HidSize);
FontOffset = Allocator.Alloc(FontSize);
HidSharedMem = new HSharedMem(HidOffset);
HidSharedMem.MemoryMapped += HidInit;
HidHandle = Handles.GenerateId(HidSharedMem);
FontHandle = Handles.GenerateId(new HSharedMem(FontOffset));
}
public void LoadCart(string ExeFsDir, string RomFsFile = null)
{
if (RomFsFile != null)
{
Ns.VFs.LoadRomFs(RomFsFile);
}
int ProcessId = IdGen.GenerateId();
Process MainProcess = new Process(Ns, Allocator, ProcessId);
void LoadNso(string FileName)
{
foreach (string File in Directory.GetFiles(ExeFsDir, FileName))
{
if (Path.GetExtension(File) != string.Empty)
{
continue;
}
Logging.Info($"Loading {Path.GetFileNameWithoutExtension(File)}...");
using (FileStream Input = new FileStream(File, FileMode.Open))
{
Nso Program = new Nso(Input);
MainProcess.LoadProgram(Program);
}
}
}
LoadNso("rtld");
MainProcess.SetEmptyArgs();
LoadNso("main");
LoadNso("subsdk*");
LoadNso("sdk");
MainProcess.InitializeHeap();
MainProcess.Run();
Processes.TryAdd(ProcessId, MainProcess);
}
public void LoadProgram(string FileName)
{
int ProcessId = IdGen.GenerateId();
Process MainProcess = new Process(Ns, Allocator, ProcessId);
using (FileStream Input = new FileStream(FileName, FileMode.Open))
{
if (Path.GetExtension(FileName).ToLower() == ".nro")
{
MainProcess.LoadProgram(new Nro(Input));
}
else
{
MainProcess.LoadProgram(new Nso(Input));
}
}
MainProcess.SetEmptyArgs();
MainProcess.InitializeHeap();
MainProcess.Run();
Processes.TryAdd(ProcessId, MainProcess);
}
public void FinalizeAllProcesses()
{
foreach (Process Process in Processes.Values)
{
Process.StopAllThreads();
Process.Dispose();
}
}
internal bool ExitProcess(int ProcessId)
{
bool Success = Processes.TryRemove(ProcessId, out Process Process);
if (Success)
{
Process.StopAllThreads();
}
if (Processes.Count == 0)
{
Ns.OnFinish(EventArgs.Empty);
}
return Success;
}
internal bool TryGetProcess(int ProcessId, out Process Process)
{
return Processes.TryGetValue(ProcessId, out Process);
}
internal void CloseHandle(int Handle)
{
object HndData = Handles.GetData<object>(Handle);
if (HndData is HTransferMem TransferMem)
{
TransferMem.Memory.Manager.Reprotect(
TransferMem.Position,
TransferMem.Size,
TransferMem.Perm);
}
Handles.Delete(Handle);
}
private void HidInit(object sender, EventArgs e)
{
HSharedMem SharedMem = (HSharedMem)sender;
if (SharedMem.TryGetLastVirtualPosition(out long Position))
{
Logging.Info($"HID shared memory successfully mapped to {Position:x16}!");
Ns.Hid.Init(Position);
}
}
}
}

View file

@ -0,0 +1,27 @@
using System.IO;
namespace Ryujinx.Core.OsHle.Ipc
{
struct IpcBuffDesc
{
public long Position { get; private set; }
public long Size { get; private set; }
public int Flags { get; private set; }
public IpcBuffDesc(BinaryReader Reader)
{
long Word0 = Reader.ReadUInt32();
long Word1 = Reader.ReadUInt32();
long Word2 = Reader.ReadUInt32();
Position = Word1;
Position |= (Word2 << 4) & 0x0f00000000;
Position |= (Word2 << 34) & 0x7000000000;
Size = Word0;
Size |= (Word2 << 8) & 0xf00000000;
Flags = (int)Word2 & 3;
}
}
}

View file

@ -0,0 +1,8 @@
namespace Ryujinx.Core.OsHle.Ipc
{
enum IpcDomCmd
{
SendMsg = 1,
DeleteObj = 2
}
}

View file

@ -0,0 +1,90 @@
using System;
using System.IO;
namespace Ryujinx.Core.OsHle.Ipc
{
class IpcHandleDesc
{
public bool HasPId { get; private set; }
public long PId { get; private set; }
public int[] ToCopy { get; private set; }
public int[] ToMove { get; private set; }
public IpcHandleDesc(BinaryReader Reader)
{
int Word = Reader.ReadInt32();
HasPId = (Word & 1) != 0;
ToCopy = new int[(Word >> 1) & 0xf];
ToMove = new int[(Word >> 5) & 0xf];
PId = HasPId ? Reader.ReadInt64() : 0;
for (int Index = 0; Index < ToCopy.Length; Index++)
{
ToCopy[Index] = Reader.ReadInt32();
}
for (int Index = 0; Index < ToMove.Length; Index++)
{
ToMove[Index] = Reader.ReadInt32();
}
}
public IpcHandleDesc(int[] Copy, int[] Move)
{
ToCopy = Copy ?? throw new ArgumentNullException(nameof(Copy));
ToMove = Move ?? throw new ArgumentNullException(nameof(Move));
}
public IpcHandleDesc(int[] Copy, int[] Move, long PId) : this(Copy, Move)
{
this.PId = PId;
HasPId = true;
}
public static IpcHandleDesc MakeCopy(int Handle) => new IpcHandleDesc(
new int[] { Handle },
new int[0]);
public static IpcHandleDesc MakeMove(int Handle) => new IpcHandleDesc(
new int[0],
new int[] { Handle });
public byte[] GetBytes()
{
using (MemoryStream MS = new MemoryStream())
{
BinaryWriter Writer = new BinaryWriter(MS);
int Word = HasPId ? 1 : 0;
Word |= (ToCopy.Length & 0xf) << 1;
Word |= (ToMove.Length & 0xf) << 5;
Writer.Write(Word);
if (HasPId)
{
Writer.Write((long)PId);
}
foreach (int Handle in ToCopy)
{
Writer.Write(Handle);
}
foreach (int Handle in ToMove)
{
Writer.Write(Handle);
}
return MS.ToArray();
}
}
}
}

View file

@ -0,0 +1,277 @@
using ChocolArm64.Memory;
using Ryujinx.Core.OsHle.Handles;
using Ryujinx.Core.OsHle.Objects;
using Ryujinx.Core.OsHle.Services;
using System;
using System.Collections.Generic;
using System.IO;
namespace Ryujinx.Core.OsHle.Ipc
{
static class IpcHandler
{
private static Dictionary<(string, int), ServiceProcessRequest> ServiceCmds =
new Dictionary<(string, int), ServiceProcessRequest>()
{
{ ( "acc:u0", 3), Service.AccU0ListOpenUsers },
{ ( "acc:u0", 5), Service.AccU0GetProfile },
{ ( "acc:u0", 100), Service.AccU0InitializeApplicationInfo },
{ ( "acc:u0", 101), Service.AccU0GetBaasAccountManagerForApplication },
{ ( "apm", 0), Service.ApmOpenSession },
{ ( "apm:p", 0), Service.ApmOpenSession },
{ ( "appletOE", 0), Service.AppletOpenApplicationProxy },
{ ( "audout:u", 0), Service.AudOutListAudioOuts },
{ ( "audout:u", 1), Service.AudOutOpenAudioOut },
{ ( "audren:u", 0), Service.AudRenOpenAudioRenderer },
{ ( "audren:u", 1), Service.AudRenGetAudioRendererWorkBufferSize },
{ ( "friend:a", 0), Service.FriendCreateFriendService },
{ ( "fsp-srv", 1), Service.FspSrvInitialize },
{ ( "fsp-srv", 18), Service.FspSrvMountSdCard },
{ ( "fsp-srv", 51), Service.FspSrvMountSaveData },
{ ( "fsp-srv", 200), Service.FspSrvOpenDataStorageByCurrentProcess },
{ ( "fsp-srv", 203), Service.FspSrvOpenRomStorage },
{ ( "fsp-srv", 1005), Service.FspSrvGetGlobalAccessLogMode },
{ ( "hid", 0), Service.HidCreateAppletResource },
{ ( "hid", 11), Service.HidActivateTouchScreen },
{ ( "hid", 100), Service.HidSetSupportedNpadStyleSet },
{ ( "hid", 102), Service.HidSetSupportedNpadIdType },
{ ( "hid", 103), Service.HidActivateNpad },
{ ( "hid", 120), Service.HidSetNpadJoyHoldType },
{ ( "lm", 0), Service.LmInitialize },
{ ( "nvdrv", 0), Service.NvDrvOpen },
{ ( "nvdrv", 1), Service.NvDrvIoctl },
{ ( "nvdrv", 2), Service.NvDrvClose },
{ ( "nvdrv", 3), Service.NvDrvInitialize },
{ ( "nvdrv", 4), Service.NvDrvQueryEvent },
{ ( "nvdrv", 8), Service.NvDrvSetClientPid },
{ ( "nvdrv:a", 0), Service.NvDrvOpen },
{ ( "nvdrv:a", 1), Service.NvDrvIoctl },
{ ( "nvdrv:a", 2), Service.NvDrvClose },
{ ( "nvdrv:a", 3), Service.NvDrvInitialize },
{ ( "nvdrv:a", 4), Service.NvDrvQueryEvent },
{ ( "nvdrv:a", 8), Service.NvDrvSetClientPid },
{ ( "pctl:a", 0), Service.PctlCreateService },
{ ( "pl:u", 1), Service.PlGetLoadState },
{ ( "pl:u", 2), Service.PlGetFontSize },
{ ( "pl:u", 3), Service.PlGetSharedMemoryAddressOffset },
{ ( "pl:u", 4), Service.PlGetSharedMemoryNativeHandle },
{ ( "set", 1), Service.SetGetAvailableLanguageCodes },
{ ( "sm:", 0), Service.SmInitialize },
{ ( "sm:", 1), Service.SmGetService },
{ ( "time:u", 0), Service.TimeGetStandardUserSystemClock },
{ ( "time:u", 1), Service.TimeGetStandardNetworkSystemClock },
{ ( "time:u", 2), Service.TimeGetStandardSteadyClock },
{ ( "time:u", 3), Service.TimeGetTimeZoneService },
{ ( "time:u", 4), Service.TimeGetStandardLocalSystemClock },
{ ( "time:s", 0), Service.TimeGetStandardUserSystemClock },
{ ( "time:s", 1), Service.TimeGetStandardNetworkSystemClock },
{ ( "time:s", 2), Service.TimeGetStandardSteadyClock },
{ ( "time:s", 3), Service.TimeGetTimeZoneService },
{ ( "time:s", 4), Service.TimeGetStandardLocalSystemClock },
{ ( "vi:m", 2), Service.ViGetDisplayService },
};
private const long SfciMagic = 'S' << 0 | 'F' << 8 | 'C' << 16 | 'I' << 24;
private const long SfcoMagic = 'S' << 0 | 'F' << 8 | 'C' << 16 | 'O' << 24;
public static void IpcCall(
Switch Ns,
AMemory Memory,
HSession Session,
IpcMessage Request,
long CmdPtr,
int HndId)
{
IpcMessage Response = new IpcMessage(Request.IsDomain);
using (MemoryStream Raw = new MemoryStream(Request.RawData))
{
BinaryReader ReqReader = new BinaryReader(Raw);
if (Request.Type == IpcMessageType.Request)
{
string ServiceName = Session.ServiceName;
ServiceProcessRequest ProcReq = null;
bool IgnoreNullPR = false;
string DbgServiceName = string.Empty;
if (Session is HDomain Dom)
{
if (Request.DomCmd == IpcDomCmd.SendMsg)
{
long Magic = ReqReader.ReadInt64();
int CmdId = (int)ReqReader.ReadInt64();
object Obj = Dom.GetObject(Request.DomObjId);
if (Obj is HDomain)
{
ServiceCmds.TryGetValue((ServiceName, CmdId), out ProcReq);
DbgServiceName = $"{ServiceName} {ProcReq?.Method.Name ?? CmdId.ToString()}";
}
else if (Obj != null)
{
((IIpcInterface)Obj).Commands.TryGetValue(CmdId, out ProcReq);
DbgServiceName = $"{ServiceName} {Obj.GetType().Name} {ProcReq?.Method.Name ?? CmdId.ToString()}";
}
}
else if (Request.DomCmd == IpcDomCmd.DeleteObj)
{
Dom.DeleteObject(Request.DomObjId);
Response = FillResponse(Response, 0);
IgnoreNullPR = true;
}
}
else
{
long Magic = ReqReader.ReadInt64();
int CmdId = (int)ReqReader.ReadInt64();
if (Session is HSessionObj)
{
object Obj = ((HSessionObj)Session).Obj;
((IIpcInterface)Obj).Commands.TryGetValue(CmdId, out ProcReq);
DbgServiceName = $"{ServiceName} {Obj.GetType().Name} {ProcReq?.Method.Name ?? CmdId.ToString()}";
}
else
{
ServiceCmds.TryGetValue((ServiceName, CmdId), out ProcReq);
DbgServiceName = $"{ServiceName} {ProcReq?.Method.Name ?? CmdId.ToString()}";
}
}
Logging.Debug($"IpcMessage: {DbgServiceName}");
if (ProcReq != null)
{
using (MemoryStream ResMS = new MemoryStream())
{
BinaryWriter ResWriter = new BinaryWriter(ResMS);
ServiceCtx Context = new ServiceCtx(
Ns,
Memory,
Session,
Request,
Response,
ReqReader,
ResWriter);
long Result = ProcReq(Context);
Response = FillResponse(Response, Result, ResMS.ToArray());
}
}
else if (!IgnoreNullPR)
{
throw new NotImplementedException(DbgServiceName);
}
}
else if (Request.Type == IpcMessageType.Control)
{
long Magic = ReqReader.ReadInt64();
long CmdId = ReqReader.ReadInt64();
switch (CmdId)
{
case 0: Request = IpcConvertSessionToDomain(Ns, Session, Response, HndId); break;
case 3: Request = IpcQueryBufferPointerSize(Response); break;
case 4: Request = IpcDuplicateSessionEx(Ns, Session, Response, ReqReader); break;
default: throw new NotImplementedException(CmdId.ToString());
}
}
else if (Request.Type == IpcMessageType.Unknown2)
{
//TODO
}
else
{
throw new NotImplementedException(Request.Type.ToString());
}
AMemoryHelper.WriteBytes(Memory, CmdPtr, Response.GetBytes(CmdPtr));
}
}
private static IpcMessage IpcConvertSessionToDomain(
Switch Ns,
HSession Session,
IpcMessage Response,
int HndId)
{
HDomain Dom = new HDomain(Session);
Ns.Os.Handles.ReplaceData(HndId, Dom);
return FillResponse(Response, 0, Dom.GenerateObjectId(Dom));
}
private static IpcMessage IpcDuplicateSessionEx(
Switch Ns,
HSession Session,
IpcMessage Response,
BinaryReader ReqReader)
{
int Unknown = ReqReader.ReadInt32();
int Handle = Ns.Os.Handles.GenerateId(Session);
Response.HandleDesc = IpcHandleDesc.MakeMove(Handle);
return FillResponse(Response, 0);
}
private static IpcMessage IpcQueryBufferPointerSize(IpcMessage Response)
{
return FillResponse(Response, 0, 0x500);
}
private static IpcMessage FillResponse(IpcMessage Response, long Result, params int[] Values)
{
using (MemoryStream MS = new MemoryStream())
{
BinaryWriter Writer = new BinaryWriter(MS);
foreach (int Value in Values)
{
Writer.Write(Value);
}
return FillResponse(Response, Result, MS.ToArray());
}
}
private static IpcMessage FillResponse(IpcMessage Response, long Result, byte[] Data = null)
{
Response.Type = IpcMessageType.Response;
using (MemoryStream MS = new MemoryStream())
{
BinaryWriter Writer = new BinaryWriter(MS);
Writer.Write(SfcoMagic);
Writer.Write(Result);
if (Data != null)
{
Writer.Write(Data);
}
Response.RawData = MS.ToArray();
}
return Response;
}
}
}

View file

@ -0,0 +1,231 @@
using System.Collections.Generic;
using System.IO;
namespace Ryujinx.Core.OsHle.Ipc
{
class IpcMessage
{
public IpcMessageType Type { get; set; }
public IpcHandleDesc HandleDesc { get; set; }
public List<IpcPtrBuffDesc> PtrBuff { get; private set; }
public List<IpcBuffDesc> SendBuff { get; private set; }
public List<IpcBuffDesc> ReceiveBuff { get; private set; }
public List<IpcBuffDesc> ExchangeBuff { get; private set; }
public List<IpcRecvListBuffDesc> RecvListBuff { get; private set; }
public List<int> ResponseObjIds { get; private set; }
public bool IsDomain { get; private set; }
public IpcDomCmd DomCmd { get; private set; }
public int DomObjId { get; private set; }
public byte[] RawData { get; set; }
public IpcMessage()
{
PtrBuff = new List<IpcPtrBuffDesc>();
SendBuff = new List<IpcBuffDesc>();
ReceiveBuff = new List<IpcBuffDesc>();
ExchangeBuff = new List<IpcBuffDesc>();
RecvListBuff = new List<IpcRecvListBuffDesc>();
ResponseObjIds = new List<int>();
}
public IpcMessage(bool Domain) : this()
{
IsDomain = Domain;
}
public IpcMessage(byte[] Data, long CmdPtr, bool Domain) : this()
{
using (MemoryStream MS = new MemoryStream(Data))
{
BinaryReader Reader = new BinaryReader(MS);
Initialize(Reader, CmdPtr, Domain);
}
}
private void Initialize(BinaryReader Reader, long CmdPtr, bool Domain)
{
IsDomain = Domain;
int Word0 = Reader.ReadInt32();
int Word1 = Reader.ReadInt32();
Type = (IpcMessageType)(Word0 & 0xffff);
int PtrBuffCount = (Word0 >> 16) & 0xf;
int SendBuffCount = (Word0 >> 20) & 0xf;
int RecvBuffCount = (Word0 >> 24) & 0xf;
int XchgBuffCount = (Word0 >> 28) & 0xf;
int RawDataSize = (Word1 >> 0) & 0x3ff;
int RecvListFlags = (Word1 >> 10) & 0xf;
bool HndDescEnable = ((Word1 >> 31) & 0x1) != 0;
if (HndDescEnable)
{
HandleDesc = new IpcHandleDesc(Reader);
}
for (int Index = 0; Index < PtrBuffCount; Index++)
{
PtrBuff.Add(new IpcPtrBuffDesc(Reader));
}
void ReadBuff(List<IpcBuffDesc> Buff, int Count)
{
for (int Index = 0; Index < Count; Index++)
{
Buff.Add(new IpcBuffDesc(Reader));
}
}
ReadBuff(SendBuff, SendBuffCount);
ReadBuff(ReceiveBuff, RecvBuffCount);
ReadBuff(ExchangeBuff, XchgBuffCount);
RawDataSize *= 4;
long RecvListPos = Reader.BaseStream.Position + RawDataSize;
long Pad0 = GetPadSize16(Reader.BaseStream.Position + CmdPtr);
Reader.BaseStream.Seek(Pad0, SeekOrigin.Current);
int RecvListCount = RecvListFlags - 2;
if (RecvListCount == 0)
{
RecvListCount = 1;
}
else if (RecvListCount < 0)
{
RecvListCount = 0;
}
if (Domain)
{
int DomWord0 = Reader.ReadInt32();
DomCmd = (IpcDomCmd)(DomWord0 & 0xff);
RawDataSize = (DomWord0 >> 16) & 0xffff;
DomObjId = Reader.ReadInt32();
Reader.ReadInt64(); //Padding
}
RawData = Reader.ReadBytes(RawDataSize);
Reader.BaseStream.Seek(RecvListPos, SeekOrigin.Begin);
for (int Index = 0; Index < RecvListCount; Index++)
{
RecvListBuff.Add(new IpcRecvListBuffDesc(Reader));
}
}
public byte[] GetBytes(long CmdPtr)
{
//todo
using (MemoryStream MS = new MemoryStream())
{
BinaryWriter Writer = new BinaryWriter(MS);
int Word0;
int Word1;
Word0 = (int)Type;
Word0 |= (PtrBuff.Count & 0xf) << 16;
Word0 |= (SendBuff.Count & 0xf) << 20;
Word0 |= (ReceiveBuff.Count & 0xf) << 24;
Word0 |= (ExchangeBuff.Count & 0xf) << 28;
byte[] HandleData = new byte[0];
if (HandleDesc != null)
{
HandleData = HandleDesc.GetBytes();
}
int DataLength = RawData?.Length ?? 0;
int Pad0 = (int)GetPadSize16(CmdPtr + 8 + HandleData.Length);
//Apparently, padding after Raw Data is 16 bytes, however when there is
//padding before Raw Data too, we need to subtract the size of this padding.
//This is the weirdest padding I've seen so far...
int Pad1 = 0x10 - Pad0;
DataLength = (DataLength + Pad0 + Pad1 + (IsDomain ? 0x10 : 0)) / 4;
DataLength += ResponseObjIds.Count;
Word1 = DataLength & 0x3ff;
if (HandleDesc != null)
{
Word1 |= 1 << 31;
}
Writer.Write(Word0);
Writer.Write(Word1);
Writer.Write(HandleData);
MS.Seek(Pad0, SeekOrigin.Current);
if (IsDomain)
{
Writer.Write(ResponseObjIds.Count);
Writer.Write(0);
Writer.Write(0L);
}
if (RawData != null)
{
Writer.Write(RawData);
}
foreach (int Id in ResponseObjIds)
{
Writer.Write(Id);
}
Writer.Write(new byte[Pad1]);
return MS.ToArray();
}
}
private long GetPadSize16(long Position)
{
if ((Position & 0xf) != 0)
{
return 0x10 - (Position & 0xf);
}
return 0;
}
public long GetSendBuffPtr()
{
if (SendBuff.Count > 0 && SendBuff[0].Position != 0)
{
return SendBuff[0].Position;
}
if (PtrBuff.Count > 0 && PtrBuff[0].Position != 0)
{
return PtrBuff[0].Position;
}
return -1;
}
}
}

View file

@ -0,0 +1,10 @@
namespace Ryujinx.Core.OsHle.Ipc
{
enum IpcMessageType
{
Response = 0,
Unknown2 = 2,
Request = 4,
Control = 5
}
}

View file

@ -0,0 +1,26 @@
using System.IO;
namespace Ryujinx.Core.OsHle.Ipc
{
struct IpcPtrBuffDesc
{
public long Position { get; private set; }
public int Index { get; private set; }
public short Size { get; private set; }
public IpcPtrBuffDesc(BinaryReader Reader)
{
long Word0 = Reader.ReadUInt32();
long Word1 = Reader.ReadUInt32();
Position = Word1;
Position |= (Word0 << 20) & 0x0f00000000;
Position |= (Word0 << 30) & 0x7000000000;
Index = ((int)Word0 >> 0) & 0x03f;
Index |= ((int)Word0 >> 3) & 0x1c0;
Size = (short)(Word0 >> 16);
}
}
}

View file

@ -0,0 +1,19 @@
using System.IO;
namespace Ryujinx.Core.OsHle.Ipc
{
struct IpcRecvListBuffDesc
{
public long Position { get; private set; }
public short Size { get; private set; }
public IpcRecvListBuffDesc(BinaryReader Reader)
{
long Value = Reader.ReadInt64();
Position = Value & 0xffffffffffff;
Size = (short)(Value >> 48);
}
}
}

View file

@ -0,0 +1,4 @@
namespace Ryujinx.Core.OsHle.Ipc
{
delegate long ServiceProcessRequest(ServiceCtx Context);
}

View file

@ -0,0 +1,28 @@
using ChocolArm64.Memory;
namespace Ryujinx.Core.OsHle
{
struct MemoryInfo
{
public long BaseAddress;
public long Size;
public int MemType;
public int MemAttr;
public int MemPerm;
public int IpcRefCount;
public int DeviceRefCount;
public int Padding; //SBZ
public MemoryInfo(AMemoryMapInfo MapInfo)
{
BaseAddress = MapInfo.Position;
Size = MapInfo.Size;
MemType = MapInfo.Type;
MemAttr = MapInfo.Attr;
MemPerm = (int)MapInfo.Perm;
IpcRefCount = 0;
DeviceRefCount = 0;
Padding = 0;
}
}
}

View file

@ -0,0 +1,25 @@
namespace Ryujinx.Core.OsHle
{
enum MemoryType
{
Unmapped = 0,
Io = 1,
Normal = 2,
CodeStatic = 3,
CodeMutable = 4,
Heap = 5,
SharedMemory = 6,
ModCodeStatic = 8,
ModCodeMutable = 9,
IpcBuffer0 = 10,
MappedMemory = 11,
ThreadLocal = 12,
TransferMemoryIsolated = 13,
TransferMemory = 14,
ProcessMemory = 15,
Reserved = 16,
IpcBuffer1 = 17,
IpcBuffer3 = 18,
KernelStack = 19
}
}

122
Ryujinx.Core/OsHle/Mutex.cs Normal file
View file

@ -0,0 +1,122 @@
using Ryujinx.Core.OsHle.Handles;
using System.Collections.Concurrent;
using System.Threading;
namespace Ryujinx.Core.OsHle
{
public class Mutex
{
private const int MutexHasListenersMask = 0x40000000;
private Process Process;
private long MutexAddress;
private bool OwnsMutexValue;
private object EnterWaitLock;
private ConcurrentQueue<HThread> WaitingThreads;
public Mutex(Process Process, long MutexAddress, int OwnerThreadHandle)
{
this.Process = Process;
this.MutexAddress = MutexAddress;
//Process.Memory.WriteInt32(MutexAddress, OwnerThreadHandle);
EnterWaitLock = new object();
WaitingThreads = new ConcurrentQueue<HThread>();
}
public void WaitForLock(HThread RequestingThread, int RequestingThreadHandle)
{
AcquireMutexValue();
lock (EnterWaitLock)
{
int CurrentThreadHandle = Process.Memory.ReadInt32(MutexAddress) & ~MutexHasListenersMask;
if (CurrentThreadHandle == RequestingThreadHandle ||
CurrentThreadHandle == 0)
{
return;
}
Process.Memory.WriteInt32(MutexAddress, CurrentThreadHandle | MutexHasListenersMask);
ReleaseMutexValue();
WaitingThreads.Enqueue(RequestingThread);
}
Process.Scheduler.WaitForSignal(RequestingThread);
}
public void GiveUpLock(int ThreadHandle)
{
AcquireMutexValue();
lock (EnterWaitLock)
{
int CurrentThread = Process.Memory.ReadInt32(MutexAddress) & ~MutexHasListenersMask;
if (CurrentThread == ThreadHandle)
{
Unlock();
}
}
ReleaseMutexValue();
}
public void Unlock()
{
AcquireMutexValue();
lock (EnterWaitLock)
{
int HasListeners = WaitingThreads.Count > 1 ? MutexHasListenersMask : 0;
Process.Memory.WriteInt32(MutexAddress, HasListeners);
ReleaseMutexValue();
HThread[] UnlockedThreads = new HThread[WaitingThreads.Count];
int Index = 0;
while (WaitingThreads.TryDequeue(out HThread Thread))
{
UnlockedThreads[Index++] = Thread;
}
Process.Scheduler.Signal(UnlockedThreads);
}
}
private void AcquireMutexValue()
{
if (!OwnsMutexValue)
{
while (!Process.Memory.AcquireAddress(MutexAddress))
{
Thread.Yield();
}
OwnsMutexValue = true;
}
}
private void ReleaseMutexValue()
{
if (OwnsMutexValue)
{
OwnsMutexValue = false;
Process.Memory.ReleaseAddress(MutexAddress);
}
}
}
}

View file

@ -0,0 +1,33 @@
using Ryujinx.Core.OsHle.Ipc;
using System.Collections.Generic;
namespace Ryujinx.Core.OsHle.Objects.Acc
{
class IManagerForApplication : IIpcInterface
{
private Dictionary<int, ServiceProcessRequest> m_Commands;
public IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
public IManagerForApplication()
{
m_Commands = new Dictionary<int, ServiceProcessRequest>()
{
{ 0, CheckAvailability },
{ 1, GetAccountId }
};
}
public long CheckAvailability(ServiceCtx Context)
{
return 0;
}
public long GetAccountId(ServiceCtx Context)
{
Context.ResponseData.Write(0xcafeL);
return 0;
}
}
}

View file

@ -0,0 +1,33 @@
using Ryujinx.Core.OsHle.Ipc;
using System.Collections.Generic;
namespace Ryujinx.Core.OsHle.Objects.Acc
{
class IProfile : IIpcInterface
{
private Dictionary<int, ServiceProcessRequest> m_Commands;
public IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
public IProfile()
{
m_Commands = new Dictionary<int, ServiceProcessRequest>()
{
{ 1, GetBase }
};
}
public long GetBase(ServiceCtx Context)
{
Context.ResponseData.Write(0L);
Context.ResponseData.Write(0L);
Context.ResponseData.Write(0L);
Context.ResponseData.Write(0L);
Context.ResponseData.Write(0L);
Context.ResponseData.Write(0L);
Context.ResponseData.Write(0L);
return 0;
}
}
}

View file

@ -0,0 +1,80 @@
using Ryujinx.Core.OsHle.Ipc;
using System.Collections.Generic;
using System.IO;
using static Ryujinx.Core.OsHle.Objects.ObjHelper;
namespace Ryujinx.Core.OsHle.Objects.Am
{
class IApplicationFunctions : IIpcInterface
{
private Dictionary<int, ServiceProcessRequest> m_Commands;
public IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
public IApplicationFunctions()
{
m_Commands = new Dictionary<int, ServiceProcessRequest>()
{
{ 1, PopLaunchParameter },
{ 20, EnsureSaveData },
{ 21, GetDesiredLanguage },
{ 40, NotifyRunning }
};
}
private const uint LaunchParamsMagic = 0xc79497ca;
public long PopLaunchParameter(ServiceCtx Context)
{
//Only the first 0x18 bytes of the Data seems to be actually used.
MakeObject(Context, new IStorage(MakeLaunchParams()));
return 0;
}
public long EnsureSaveData(ServiceCtx Context)
{
long UIdLow = Context.RequestData.ReadInt64();
long UIdHigh = Context.RequestData.ReadInt64();
Context.ResponseData.Write(0L);
return 0;
}
public long GetDesiredLanguage(ServiceCtx Context)
{
//This is an enumerator where each number is a differnet language.
//0 is Japanese and 1 is English, need to figure out the other codes.
Context.ResponseData.Write(1L);
return 0;
}
public long NotifyRunning(ServiceCtx Context)
{
Context.ResponseData.Write(1);
return 0;
}
private byte[] MakeLaunchParams()
{
//Size needs to be at least 0x88 bytes otherwise application errors.
using (MemoryStream MS = new MemoryStream())
{
BinaryWriter Writer = new BinaryWriter(MS);
MS.SetLength(0x88);
Writer.Write(LaunchParamsMagic);
Writer.Write(1); //IsAccountSelected? Only lower 8 bits actually used.
Writer.Write(1L); //User Id Low (note: User Id needs to be != 0)
Writer.Write(0L); //User Id High
return MS.ToArray();
}
}
}
}

View file

@ -0,0 +1,85 @@
using Ryujinx.Core.OsHle.Ipc;
using System.Collections.Generic;
using static Ryujinx.Core.OsHle.Objects.ObjHelper;
namespace Ryujinx.Core.OsHle.Objects.Am
{
class IApplicationProxy : IIpcInterface
{
private Dictionary<int, ServiceProcessRequest> m_Commands;
public IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
public IApplicationProxy()
{
m_Commands = new Dictionary<int, ServiceProcessRequest>()
{
{ 0, GetCommonStateGetter },
{ 1, GetSelfController },
{ 2, GetWindowController },
{ 3, GetAudioController },
{ 4, GetDisplayController },
{ 11, GetLibraryAppletCreator },
{ 20, GetApplicationFunctions },
{ 1000, GetDebugFunctions }
};
}
public long GetCommonStateGetter(ServiceCtx Context)
{
MakeObject(Context, new ICommonStateGetter());
return 0;
}
public long GetSelfController(ServiceCtx Context)
{
MakeObject(Context, new ISelfController());
return 0;
}
public long GetWindowController(ServiceCtx Context)
{
MakeObject(Context, new IWindowController());
return 0;
}
public long GetAudioController(ServiceCtx Context)
{
MakeObject(Context, new IAudioController());
return 0;
}
public long GetDisplayController(ServiceCtx Context)
{
MakeObject(Context, new IDisplayController());
return 0;
}
public long GetLibraryAppletCreator(ServiceCtx Context)
{
MakeObject(Context, new ILibraryAppletCreator());
return 0;
}
public long GetApplicationFunctions(ServiceCtx Context)
{
MakeObject(Context, new IApplicationFunctions());
return 0;
}
public long GetDebugFunctions(ServiceCtx Context)
{
MakeObject(Context, new IDebugFunctions());
return 0;
}
}
}

View file

@ -0,0 +1,20 @@
using Ryujinx.Core.OsHle.Ipc;
using System.Collections.Generic;
namespace Ryujinx.Core.OsHle.Objects.Am
{
class IAudioController : IIpcInterface
{
private Dictionary<int, ServiceProcessRequest> m_Commands;
public IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
public IAudioController()
{
m_Commands = new Dictionary<int, ServiceProcessRequest>()
{
//...
};
}
}
}

View file

@ -0,0 +1,74 @@
using Ryujinx.Core.OsHle.Ipc;
using System.Collections.Generic;
namespace Ryujinx.Core.OsHle.Objects.Am
{
class ICommonStateGetter : IIpcInterface
{
private Dictionary<int, ServiceProcessRequest> m_Commands;
public IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
public ICommonStateGetter()
{
m_Commands = new Dictionary<int, ServiceProcessRequest>()
{
{ 0, GetEventHandle },
{ 1, ReceiveMessage },
{ 5, GetOperationMode },
{ 6, GetPerformanceMode },
{ 9, GetCurrentFocusState },
};
}
private enum FocusState
{
InFocus = 1,
OutOfFocus = 2
}
private enum OperationMode
{
Handheld = 0,
Docked = 1
}
public long GetEventHandle(ServiceCtx Context)
{
Context.ResponseData.Write(0L);
return 0;
}
public long ReceiveMessage(ServiceCtx Context)
{
//Program expects 0xF at 0x17ae70 on puyo sdk,
//otherwise runs on a infinite loop until it reads said value.
//What it means is still unknown.
Context.ResponseData.Write(0xfL);
return 0; //0x680;
}
public long GetOperationMode(ServiceCtx Context)
{
Context.ResponseData.Write((byte)OperationMode.Handheld);
return 0;
}
public long GetPerformanceMode(ServiceCtx Context)
{
Context.ResponseData.Write((byte)0);
return 0;
}
public long GetCurrentFocusState(ServiceCtx Context)
{
Context.ResponseData.Write((byte)FocusState.InFocus);
return 0;
}
}
}

View file

@ -0,0 +1,20 @@
using Ryujinx.Core.OsHle.Ipc;
using System.Collections.Generic;
namespace Ryujinx.Core.OsHle.Objects.Am
{
class IDebugFunctions : IIpcInterface
{
private Dictionary<int, ServiceProcessRequest> m_Commands;
public IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
public IDebugFunctions()
{
m_Commands = new Dictionary<int, ServiceProcessRequest>()
{
//...
};
}
}
}

View file

@ -0,0 +1,20 @@
using Ryujinx.Core.OsHle.Ipc;
using System.Collections.Generic;
namespace Ryujinx.Core.OsHle.Objects.Am
{
class IDisplayController : IIpcInterface
{
private Dictionary<int, ServiceProcessRequest> m_Commands;
public IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
public IDisplayController()
{
m_Commands = new Dictionary<int, ServiceProcessRequest>()
{
//...
};
}
}
}

View file

@ -0,0 +1,20 @@
using Ryujinx.Core.OsHle.Ipc;
using System.Collections.Generic;
namespace Ryujinx.Core.OsHle.Objects.Am
{
class ILibraryAppletCreator : IIpcInterface
{
private Dictionary<int, ServiceProcessRequest> m_Commands;
public IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
public ILibraryAppletCreator()
{
m_Commands = new Dictionary<int, ServiceProcessRequest>()
{
//...
};
}
}
}

View file

@ -0,0 +1,20 @@
using Ryujinx.Core.OsHle.Ipc;
using System.Collections.Generic;
namespace Ryujinx.Core.OsHle.Objects.Am
{
class IParentalControlService : IIpcInterface
{
private Dictionary<int, ServiceProcessRequest> m_Commands;
public IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
public IParentalControlService()
{
m_Commands = new Dictionary<int, ServiceProcessRequest>()
{
//...
};
}
}
}

View file

@ -0,0 +1,53 @@
using Ryujinx.Core.OsHle.Ipc;
using System.Collections.Generic;
namespace Ryujinx.Core.OsHle.Objects.Am
{
class ISelfController : IIpcInterface
{
private Dictionary<int, ServiceProcessRequest> m_Commands;
public IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
public ISelfController()
{
m_Commands = new Dictionary<int, ServiceProcessRequest>()
{
{ 11, SetOperationModeChangedNotification },
{ 12, SetPerformanceModeChangedNotification },
{ 13, SetFocusHandlingMode },
{ 16, SetOutOfFocusSuspendingEnabled }
};
}
public long SetOperationModeChangedNotification(ServiceCtx Context)
{
bool Enable = Context.RequestData.ReadByte() != 0 ? true : false;
return 0;
}
public long SetPerformanceModeChangedNotification(ServiceCtx Context)
{
bool Enable = Context.RequestData.ReadByte() != 0 ? true : false;
return 0;
}
public long SetFocusHandlingMode(ServiceCtx Context)
{
bool Flag1 = Context.RequestData.ReadByte() != 0 ? true : false;
bool Flag2 = Context.RequestData.ReadByte() != 0 ? true : false;
bool Flag3 = Context.RequestData.ReadByte() != 0 ? true : false;
return 0;
}
public long SetOutOfFocusSuspendingEnabled(ServiceCtx Context)
{
bool Enable = Context.RequestData.ReadByte() != 0 ? true : false;
return 0;
}
}
}

View file

@ -0,0 +1,33 @@
using Ryujinx.Core.OsHle.Ipc;
using System.Collections.Generic;
using static Ryujinx.Core.OsHle.Objects.ObjHelper;
namespace Ryujinx.Core.OsHle.Objects.Am
{
class IStorage : IIpcInterface
{
private Dictionary<int, ServiceProcessRequest> m_Commands;
public IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
public byte[] Data { get; private set; }
public IStorage(byte[] Data)
{
m_Commands = new Dictionary<int, ServiceProcessRequest>()
{
{ 0, Open }
};
this.Data = Data;
}
public long Open(ServiceCtx Context)
{
MakeObject(Context, new IStorageAccessor(this));
return 0;
}
}
}

View file

@ -0,0 +1,62 @@
using ChocolArm64.Memory;
using Ryujinx.Core.OsHle.Ipc;
using System;
using System.Collections.Generic;
namespace Ryujinx.Core.OsHle.Objects.Am
{
class IStorageAccessor : IIpcInterface
{
private Dictionary<int, ServiceProcessRequest> m_Commands;
public IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
private IStorage Storage;
public IStorageAccessor(IStorage Storage)
{
m_Commands = new Dictionary<int, ServiceProcessRequest>()
{
{ 0, GetSize },
{ 11, Read }
};
this.Storage = Storage;
}
public long GetSize(ServiceCtx Context)
{
Context.ResponseData.Write((long)Storage.Data.Length);
return 0;
}
public long Read(ServiceCtx Context)
{
long ReadPosition = Context.RequestData.ReadInt64();
if (Context.Request.RecvListBuff.Count > 0)
{
long Position = Context.Request.RecvListBuff[0].Position;
short Size = Context.Request.RecvListBuff[0].Size;
byte[] Data;
if (Storage.Data.Length > Size)
{
Data = new byte[Size];
Buffer.BlockCopy(Storage.Data, 0, Data, 0, Size);
}
else
{
Data = Storage.Data;
}
AMemoryHelper.WriteBytes(Context.Memory, Position, Data);
}
return 0;
}
}
}

View file

@ -0,0 +1,33 @@
using Ryujinx.Core.OsHle.Ipc;
using System.Collections.Generic;
namespace Ryujinx.Core.OsHle.Objects.Am
{
class IWindowController : IIpcInterface
{
private Dictionary<int, ServiceProcessRequest> m_Commands;
public IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
public IWindowController()
{
m_Commands = new Dictionary<int, ServiceProcessRequest>()
{
{ 1, GetAppletResourceUserId },
{ 10, AcquireForegroundRights }
};
}
public long GetAppletResourceUserId(ServiceCtx Context)
{
Context.ResponseData.Write(0L);
return 0;
}
public long AcquireForegroundRights(ServiceCtx Context)
{
return 0;
}
}
}

View file

@ -0,0 +1,28 @@
using Ryujinx.Core.OsHle.Ipc;
using System.Collections.Generic;
namespace Ryujinx.Core.OsHle.Objects.Apm
{
class ISession : IIpcInterface
{
private Dictionary<int, ServiceProcessRequest> m_Commands;
public IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
public ISession()
{
m_Commands = new Dictionary<int, ServiceProcessRequest>()
{
{ 0, SetPerformanceConfiguration }
};
}
public long SetPerformanceConfiguration(ServiceCtx Context)
{
int PerfMode = Context.RequestData.ReadInt32();
int PerfConfig = Context.RequestData.ReadInt32();
return 0;
}
}
}

View file

@ -0,0 +1,180 @@
using ChocolArm64.Memory;
using Ryujinx.Core.OsHle.Handles;
using Ryujinx.Core.OsHle.Ipc;
using OpenTK.Audio;
using OpenTK.Audio.OpenAL;
using System;
using System.Collections.Generic;
using System.IO;
namespace Ryujinx.Core.OsHle.Objects.Aud
{
class IAudioOut : IIpcInterface
{
private Dictionary<int, ServiceProcessRequest> m_Commands;
public IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
public IAudioOut()
{
m_Commands = new Dictionary<int, ServiceProcessRequest>()
{
{ 0, GetAudioOutState },
{ 1, StartAudioOut },
{ 2, StopAudioOut },
{ 3, AppendAudioOutBuffer },
{ 4, RegisterBufferEvent },
{ 5, GetReleasedAudioOutBuffer },
{ 6, ContainsAudioOutBuffer },
{ 7, AppendAudioOutBuffer_ex },
{ 8, GetReleasedAudioOutBuffer_ex }
};
}
enum AudioOutState
{
Started,
Stopped
};
//IAudioOut
private AudioOutState State = AudioOutState.Stopped;
private Queue<long> KeysQueue = new Queue<long>();
//OpenAL
private bool OpenALInstalled = true;
private AudioContext AudioCtx;
private int Source;
private int Buffer;
//Return State of IAudioOut
public long GetAudioOutState(ServiceCtx Context)
{
Context.ResponseData.Write((int)State);
return 0;
}
public long StartAudioOut(ServiceCtx Context)
{
if (State == AudioOutState.Stopped)
{
State = AudioOutState.Started;
try
{
AudioCtx = new AudioContext(); //Create the audio context
}
catch (Exception)
{
Logging.Warn("OpenAL Error! PS: Install OpenAL Core SDK!");
OpenALInstalled = false;
}
if (OpenALInstalled) AL.Listener(ALListenerf.Gain, (float)8.0); //Add more gain to it
}
return 0;
}
public long StopAudioOut(ServiceCtx Context)
{
if (State == AudioOutState.Started)
{
if (OpenALInstalled)
{
if (AudioCtx == null) //Needed to call the instance of AudioContext()
return 0;
AL.SourceStop(Source);
AL.DeleteSource(Source);
}
State = AudioOutState.Stopped;
}
return 0;
}
public long AppendAudioOutBuffer(ServiceCtx Context)
{
long BufferId = Context.RequestData.ReadInt64();
KeysQueue.Enqueue(BufferId);
byte[] AudioOutBuffer = AMemoryHelper.ReadBytes(Context.Memory, Context.Request.SendBuff[0].Position, sizeof(long) * 5);
using (MemoryStream MS = new MemoryStream(AudioOutBuffer))
{
BinaryReader Reader = new BinaryReader(MS);
long PointerNextBuffer = Reader.ReadInt64();
long PointerSampleBuffer = Reader.ReadInt64();
long CapacitySampleBuffer = Reader.ReadInt64();
long SizeDataInSampleBuffer = Reader.ReadInt64();
long OffsetDataInSampleBuffer = Reader.ReadInt64();
byte[] AudioSampleBuffer = AMemoryHelper.ReadBytes(Context.Memory, PointerSampleBuffer + OffsetDataInSampleBuffer, (int)SizeDataInSampleBuffer);
if (OpenALInstalled)
{
if (AudioCtx == null) //Needed to call the instance of AudioContext()
return 0;
Buffer = AL.GenBuffer();
AL.BufferData(Buffer, ALFormat.Stereo16, AudioSampleBuffer, AudioSampleBuffer.Length, 48000);
Source = AL.GenSource();
AL.SourceQueueBuffer(Source, Buffer);
}
}
return 0;
}
public long RegisterBufferEvent(ServiceCtx Context)
{
int Handle = Context.Ns.Os.Handles.GenerateId(new HEvent());
Context.Response.HandleDesc = IpcHandleDesc.MakeCopy(Handle);
return 0;
}
public long GetReleasedAudioOutBuffer(ServiceCtx Context)
{
long TempKey = 0;
if (KeysQueue.Count > 0) TempKey = KeysQueue.Dequeue();
AMemoryHelper.WriteBytes(Context.Memory, Context.Request.ReceiveBuff[0].Position, BitConverter.GetBytes(TempKey));
int ReleasedBuffersCount = 1;
Context.ResponseData.Write(ReleasedBuffersCount);
if (OpenALInstalled)
{
if (AudioCtx == null) //Needed to call the instance of AudioContext()
return 0;
AL.SourcePlay(Source);
int[] FreeBuffers = AL.SourceUnqueueBuffers(Source, 1);
AL.DeleteBuffers(FreeBuffers);
}
return 0;
}
public long ContainsAudioOutBuffer(ServiceCtx Context)
{
return 0;
}
public long AppendAudioOutBuffer_ex(ServiceCtx Context)
{
return 0;
}
public long GetReleasedAudioOutBuffer_ex(ServiceCtx Context)
{
return 0;
}
}
}

View file

@ -0,0 +1,66 @@
using Ryujinx.Core.OsHle.Handles;
using Ryujinx.Core.OsHle.Ipc;
using System.Collections.Generic;
namespace Ryujinx.Core.OsHle.Objects.Aud
{
class IAudioRenderer : IIpcInterface
{
private Dictionary<int, ServiceProcessRequest> m_Commands;
public IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
public IAudioRenderer()
{
m_Commands = new Dictionary<int, ServiceProcessRequest>()
{
{ 4, RequestUpdateAudioRenderer },
{ 5, StartAudioRenderer },
{ 6, StopAudioRenderer },
{ 7, QuerySystemEvent }
};
}
public long RequestUpdateAudioRenderer(ServiceCtx Context)
{
//(buffer<unknown, 5, 0>) -> (buffer<unknown, 6, 0>, buffer<unknown, 6, 0>)
long Position = Context.Request.ReceiveBuff[0].Position;
//0x40 bytes header
Context.Memory.WriteInt32(Position + 0x4, 0xb0); //Behavior Out State Size? (note: this is the last section)
Context.Memory.WriteInt32(Position + 0x8, 0x18e0); //Memory Pool Out State Size?
Context.Memory.WriteInt32(Position + 0xc, 0x600); //Voice Out State Size?
Context.Memory.WriteInt32(Position + 0x14, 0xe0); //Effect Out State Size?
Context.Memory.WriteInt32(Position + 0x1c, 0x20); //Sink Out State Size?
Context.Memory.WriteInt32(Position + 0x20, 0x10); //Performance Out State Size?
Context.Memory.WriteInt32(Position + 0x3c, 0x20e0); //Total Size (including 0x40 bytes header)
for (int Offset = 0x40; Offset < 0x40 + 0x18e0; Offset += 0x10)
{
Context.Memory.WriteInt32(Position + Offset, 5);
}
return 0;
}
public long StartAudioRenderer(ServiceCtx Context)
{
return 0;
}
public long StopAudioRenderer(ServiceCtx Context)
{
return 0;
}
public long QuerySystemEvent(ServiceCtx Context)
{
int Handle = Context.Ns.Os.Handles.GenerateId(new HEvent());
Context.Response.HandleDesc = IpcHandleDesc.MakeCopy(Handle);
return 0;
}
}
}

View file

@ -0,0 +1,20 @@
using Ryujinx.Core.OsHle.Ipc;
using System.Collections.Generic;
namespace Ryujinx.Core.OsHle.Objects.Friend
{
class IFriendService : IIpcInterface
{
private Dictionary<int, ServiceProcessRequest> m_Commands;
public IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
public IFriendService()
{
m_Commands = new Dictionary<int, ServiceProcessRequest>()
{
//...
};
}
}
}

View file

@ -0,0 +1,133 @@
using ChocolArm64.Memory;
using Ryujinx.Core.OsHle.Ipc;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
namespace Ryujinx.Core.OsHle.Objects.FspSrv
{
[StructLayout(LayoutKind.Sequential, Size = 0x310)]
struct DirectoryEntry
{
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x300)]
public byte[] Name;
public int Unknown;
public byte Type;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x3)]
public byte[] Padding;
public long Size;
}
enum DirectoryEntryType
{
Directory,
File
}
class IDirectory : IIpcInterface
{
private List<DirectoryEntry> DirectoryEntries = new List<DirectoryEntry>();
private Dictionary<int, ServiceProcessRequest> m_Commands;
public IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
private string HostPath;
public IDirectory(string HostPath, int flags)
{
m_Commands = new Dictionary<int, ServiceProcessRequest>()
{
{ 0, Read },
{ 1, GetEntryCount }
};
this.HostPath = HostPath;
if ((flags & 1) == 1)
{
string[] Directories = Directory.GetDirectories(HostPath, "*", SearchOption.TopDirectoryOnly).
Where(x => (new FileInfo(x).Attributes & FileAttributes.Hidden) == 0).ToArray();
foreach (string Directory in Directories)
{
DirectoryEntry Info = new DirectoryEntry
{
Name = Encoding.UTF8.GetBytes(Directory),
Type = (byte)DirectoryEntryType.Directory,
Size = 0
};
Array.Resize(ref Info.Name, 0x300);
DirectoryEntries.Add(Info);
}
}
if ((flags & 2) == 2)
{
string[] Files = Directory.GetFiles(HostPath, "*", SearchOption.TopDirectoryOnly).
Where(x => (new FileInfo(x).Attributes & FileAttributes.Hidden) == 0).ToArray();
foreach (string FileName in Files)
{
DirectoryEntry Info = new DirectoryEntry
{
Name = Encoding.UTF8.GetBytes(Path.GetFileName(FileName)),
Type = (byte)DirectoryEntryType.File,
Size = new FileInfo(Path.Combine(HostPath, FileName)).Length
};
Array.Resize(ref Info.Name, 0x300);
DirectoryEntries.Add(Info);
}
}
}
private int LastItem = 0;
public long Read(ServiceCtx Context)
{
long BufferPosition = Context.Request.ReceiveBuff[0].Position;
long BufferLen = Context.Request.ReceiveBuff[0].Size;
long MaxDirectories = BufferLen / Marshal.SizeOf(typeof(DirectoryEntry));
if (MaxDirectories > DirectoryEntries.Count - LastItem)
{
MaxDirectories = DirectoryEntries.Count - LastItem;
}
int CurrentIndex;
for (CurrentIndex = 0; CurrentIndex < MaxDirectories; CurrentIndex++)
{
int CurrentItem = LastItem + CurrentIndex;
byte[] DirectoryEntry = new byte[Marshal.SizeOf(typeof(DirectoryEntry))];
IntPtr Ptr = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(DirectoryEntry)));
Marshal.StructureToPtr(DirectoryEntries[CurrentItem], Ptr, true);
Marshal.Copy(Ptr, DirectoryEntry, 0, Marshal.SizeOf(typeof(DirectoryEntry)));
Marshal.FreeHGlobal(Ptr);
AMemoryHelper.WriteBytes(Context.Memory, BufferPosition + Marshal.SizeOf(typeof(DirectoryEntry)) * CurrentIndex, DirectoryEntry);
}
if (LastItem < DirectoryEntries.Count)
{
LastItem += CurrentIndex;
Context.ResponseData.Write((long)CurrentIndex); // index = number of entries written this call.
}
else
{
Context.ResponseData.Write((long)0);
}
return 0;
}
public long GetEntryCount(ServiceCtx Context)
{
Context.ResponseData.Write((long)DirectoryEntries.Count);
return 0;
}
}
}

View file

@ -0,0 +1,93 @@
using ChocolArm64.Memory;
using Ryujinx.Core.OsHle.Ipc;
using System;
using System.Collections.Generic;
using System.IO;
namespace Ryujinx.Core.OsHle.Objects.FspSrv
{
class IFile : IIpcInterface, IDisposable
{
private Dictionary<int, ServiceProcessRequest> m_Commands;
public IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
private Stream BaseStream;
public IFile(Stream BaseStream)
{
m_Commands = new Dictionary<int, ServiceProcessRequest>()
{
{ 0, Read },
{ 1, Write },
// { 2, Flush },
{ 3, SetSize },
{ 4, GetSize }
};
this.BaseStream = BaseStream;
}
public long Read(ServiceCtx Context)
{
long Position = Context.Request.ReceiveBuff[0].Position;
long Zero = Context.RequestData.ReadInt64();
long Offset = Context.RequestData.ReadInt64();
long Size = Context.RequestData.ReadInt64();
byte[] Data = new byte[Size];
BaseStream.Seek(Offset, SeekOrigin.Begin);
int ReadSize = BaseStream.Read(Data, 0, (int)Size);
AMemoryHelper.WriteBytes(Context.Memory, Position, Data);
Context.ResponseData.Write((long)ReadSize);
return 0;
}
public long Write(ServiceCtx Context)
{
long Position = Context.Request.SendBuff[0].Position;
long Zero = Context.RequestData.ReadInt64();
long Offset = Context.RequestData.ReadInt64();
long Size = Context.RequestData.ReadInt64();
byte[] Data = AMemoryHelper.ReadBytes(Context.Memory, Position, (int)Size);
BaseStream.Seek(Offset, SeekOrigin.Begin);
BaseStream.Write(Data, 0, (int)Size);
return 0;
}
public long GetSize(ServiceCtx Context)
{
Context.ResponseData.Write(BaseStream.Length);
return 0;
}
public long SetSize(ServiceCtx Context)
{
long Size = Context.RequestData.ReadInt64();
BaseStream.SetLength(Size);
return 0;
}
public void Dispose()
{
Dispose(true);
}
protected virtual void Dispose(bool disposing)
{
if (disposing && BaseStream != null)
{
BaseStream.Dispose();
}
}
}
}

View file

@ -0,0 +1,247 @@
using ChocolArm64.Memory;
using Ryujinx.Core.OsHle.Ipc;
using System.Collections.Generic;
using System.IO;
using static Ryujinx.Core.OsHle.Objects.ObjHelper;
namespace Ryujinx.Core.OsHle.Objects.FspSrv
{
class IFileSystem : IIpcInterface
{
private Dictionary<int, ServiceProcessRequest> m_Commands;
public IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
private string Path;
public IFileSystem(string Path)
{
//TODO: implement.
m_Commands = new Dictionary<int, ServiceProcessRequest>()
{
{ 0, CreateFile },
{ 1, DeleteFile },
{ 2, CreateDirectory },
{ 3, DeleteDirectory },
{ 4, DeleteDirectoryRecursively },
{ 5, RenameFile },
{ 6, RenameDirectory },
{ 7, GetEntryType },
{ 8, OpenFile },
{ 9, OpenDirectory },
{ 10, Commit },
//{ 11, GetFreeSpaceSize },
//{ 12, GetTotalSpaceSize },
//{ 13, CleanDirectoryRecursively },
//{ 14, GetFileTimeStampRaw }
};
this.Path = Path;
}
public long CreateFile(ServiceCtx Context)
{
long Position = Context.Request.PtrBuff[0].Position;
string Name = AMemoryHelper.ReadAsciiString(Context.Memory, Position);
ulong Mode = Context.RequestData.ReadUInt64();
uint Size = Context.RequestData.ReadUInt32();
string FileName = Context.Ns.VFs.GetFullPath(Path, Name);
if (FileName != null)
{
FileStream NewFile = File.Create(FileName);
NewFile.SetLength(Size);
NewFile.Close();
return 0;
}
//TODO: Correct error code.
return -1;
}
public long DeleteFile(ServiceCtx Context)
{
long Position = Context.Request.PtrBuff[0].Position;
string Name = AMemoryHelper.ReadAsciiString(Context.Memory, Position);
string FileName = Context.Ns.VFs.GetFullPath(Path, Name);
if (FileName != null)
{
File.Delete(FileName);
return 0;
}
//TODO: Correct error code.
return -1;
}
public long CreateDirectory(ServiceCtx Context)
{
long Position = Context.Request.PtrBuff[0].Position;
string Name = AMemoryHelper.ReadAsciiString(Context.Memory, Position);
string FileName = Context.Ns.VFs.GetFullPath(Path, Name);
if (FileName != null)
{
Directory.CreateDirectory(FileName);
return 0;
}
//TODO: Correct error code.
return -1;
}
public long DeleteDirectory(ServiceCtx Context)
{
long Position = Context.Request.PtrBuff[0].Position;
string Name = AMemoryHelper.ReadAsciiString(Context.Memory, Position);
string FileName = Context.Ns.VFs.GetFullPath(Path, Name);
if (FileName != null)
{
Directory.Delete(FileName);
return 0;
}
// TODO: Correct error code.
return -1;
}
public long DeleteDirectoryRecursively(ServiceCtx Context)
{
long Position = Context.Request.PtrBuff[0].Position;
string Name = AMemoryHelper.ReadAsciiString(Context.Memory, Position);
string FileName = Context.Ns.VFs.GetFullPath(Path, Name);
if (FileName != null)
{
Directory.Delete(FileName, true); // recursive = true
return 0;
}
// TODO: Correct error code.
return -1;
}
public long RenameFile(ServiceCtx Context)
{
long OldPosition = Context.Request.PtrBuff[0].Position;
long NewPosition = Context.Request.PtrBuff[0].Position;
string OldName = AMemoryHelper.ReadAsciiString(Context.Memory, OldPosition);
string NewName = AMemoryHelper.ReadAsciiString(Context.Memory, NewPosition);
string OldFileName = Context.Ns.VFs.GetFullPath(Path, OldName);
string NewFileName = Context.Ns.VFs.GetFullPath(Path, NewName);
if (OldFileName != null && NewFileName != null)
{
File.Move(OldFileName, NewFileName);
return 0;
}
// TODO: Correct error code.
return -1;
}
public long RenameDirectory(ServiceCtx Context)
{
long OldPosition = Context.Request.PtrBuff[0].Position;
long NewPosition = Context.Request.PtrBuff[0].Position;
string OldName = AMemoryHelper.ReadAsciiString(Context.Memory, OldPosition);
string NewName = AMemoryHelper.ReadAsciiString(Context.Memory, NewPosition);
string OldDirName = Context.Ns.VFs.GetFullPath(Path, OldName);
string NewDirName = Context.Ns.VFs.GetFullPath(Path, NewName);
if (OldDirName != null && NewDirName != null)
{
Directory.Move(OldDirName, NewDirName);
return 0;
}
// TODO: Correct error code.
return -1;
}
public long GetEntryType(ServiceCtx Context)
{
long Position = Context.Request.PtrBuff[0].Position;
string Name = AMemoryHelper.ReadAsciiString(Context.Memory, Position);
string FileName = Context.Ns.VFs.GetFullPath(Path, Name);
if (FileName == null)
{
//TODO: Correct error code.
return -1;
}
bool IsFile = File.Exists(FileName);
Context.ResponseData.Write(IsFile ? 1 : 0);
return 0;
}
public long OpenFile(ServiceCtx Context)
{
long Position = Context.Request.PtrBuff[0].Position;
int FilterFlags = Context.RequestData.ReadInt32();
string Name = AMemoryHelper.ReadAsciiString(Context.Memory, Position);
string FileName = Context.Ns.VFs.GetFullPath(Path, Name);
if (FileName == null)
{
//TODO: Correct error code.
return -1;
}
if (File.Exists(FileName))
{
FileStream Stream = new FileStream(FileName, FileMode.OpenOrCreate);
MakeObject(Context, new IFile(Stream));
return 0;
}
//TODO: Correct error code.
return -1;
}
public long OpenDirectory(ServiceCtx Context)
{
long Position = Context.Request.PtrBuff[0].Position;
int FilterFlags = Context.RequestData.ReadInt32();
string Name = AMemoryHelper.ReadAsciiString(Context.Memory, Position);
string DirName = Context.Ns.VFs.GetFullPath(Path, Name);
if(DirName != null)
{
if (Directory.Exists(DirName))
{
MakeObject(Context, new IDirectory(DirName, FilterFlags));
return 0;
}
else
{
// TODO: correct error code.
return -1;
}
}
// TODO: Correct error code.
return -1;
}
public long Commit(ServiceCtx Context)
{
return 0;
}
}
}

View file

@ -0,0 +1,52 @@
using ChocolArm64.Memory;
using Ryujinx.Core.OsHle.Ipc;
using System.Collections.Generic;
using System.IO;
namespace Ryujinx.Core.OsHle.Objects.FspSrv
{
class IStorage : IIpcInterface
{
private Dictionary<int, ServiceProcessRequest> m_Commands;
public IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
private Stream BaseStream;
public IStorage(Stream BaseStream)
{
m_Commands = new Dictionary<int, ServiceProcessRequest>()
{
{ 0, Read }
};
this.BaseStream = BaseStream;
}
public long Read(ServiceCtx Context)
{
long Offset = Context.RequestData.ReadInt64();
long Size = Context.RequestData.ReadInt64();
if (Context.Request.ReceiveBuff.Count > 0)
{
IpcBuffDesc BuffDesc = Context.Request.ReceiveBuff[0];
//Use smaller length to avoid overflows.
if (Size > BuffDesc.Size)
{
Size = BuffDesc.Size;
}
byte[] Data = new byte[Size];
BaseStream.Seek(Offset, SeekOrigin.Begin);
BaseStream.Read(Data, 0, Data.Length);
AMemoryHelper.WriteBytes(Context.Memory, BuffDesc.Position, Data);
}
return 0;
}
}
}

View file

@ -0,0 +1,32 @@
using Ryujinx.Core.OsHle.Handles;
using Ryujinx.Core.OsHle.Ipc;
using System.Collections.Generic;
namespace Ryujinx.Core.OsHle.Objects.Hid
{
class IAppletResource : IIpcInterface
{
private Dictionary<int, ServiceProcessRequest> m_Commands;
public IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
public HSharedMem Handle;
public IAppletResource(HSharedMem Handle)
{
m_Commands = new Dictionary<int, ServiceProcessRequest>()
{
{ 0, GetSharedMemoryHandle }
};
this.Handle = Handle;
}
public static long GetSharedMemoryHandle(ServiceCtx Context)
{
Context.Response.HandleDesc = IpcHandleDesc.MakeCopy(Context.Ns.Os.HidHandle);
return 0;
}
}
}

View file

@ -0,0 +1,10 @@
using Ryujinx.Core.OsHle.Ipc;
using System.Collections.Generic;
namespace Ryujinx.Core.OsHle.Objects
{
interface IIpcInterface
{
IReadOnlyDictionary<int, ServiceProcessRequest> Commands { get; }
}
}

View file

@ -0,0 +1,24 @@
using Ryujinx.Core.OsHle.Handles;
using Ryujinx.Core.OsHle.Ipc;
namespace Ryujinx.Core.OsHle.Objects
{
static class ObjHelper
{
public static void MakeObject(ServiceCtx Context, object Obj)
{
if (Context.Session is HDomain Dom)
{
Context.Response.ResponseObjIds.Add(Dom.GenerateObjectId(Obj));
}
else
{
HSessionObj HndData = new HSessionObj(Context.Session, Obj);
int VHandle = Context.Ns.Os.Handles.GenerateId(HndData);
Context.Response.HandleDesc = IpcHandleDesc.MakeMove(VHandle);
}
}
}
}

View file

@ -0,0 +1,58 @@
using System;
using System.IO;
namespace Ryujinx.Core.OsHle.Objects.Android
{
static class Parcel
{
public static byte[] GetParcelData(byte[] Parcel)
{
if (Parcel == null)
{
throw new ArgumentNullException(nameof(Parcel));
}
using (MemoryStream MS = new MemoryStream(Parcel))
{
BinaryReader Reader = new BinaryReader(MS);
int DataSize = Reader.ReadInt32();
int DataOffset = Reader.ReadInt32();
int ObjsSize = Reader.ReadInt32();
int ObjsOffset = Reader.ReadInt32();
MS.Seek(DataOffset - 0x10, SeekOrigin.Current);
return Reader.ReadBytes(DataSize);
}
}
public static byte[] MakeParcel(byte[] Data, byte[] Objs)
{
if (Data == null)
{
throw new ArgumentNullException(nameof(Data));
}
if (Objs == null)
{
throw new ArgumentNullException(nameof(Objs));
}
using (MemoryStream MS = new MemoryStream())
{
BinaryWriter Writer = new BinaryWriter(MS);
Writer.Write(Data.Length);
Writer.Write(0x10);
Writer.Write(Objs.Length);
Writer.Write(Data.Length + 0x10);
Writer.Write(Data);
Writer.Write(Objs);
return MS.ToArray();
}
}
}
}

View file

@ -0,0 +1,20 @@
using Ryujinx.Core.OsHle.Ipc;
using System.Collections.Generic;
namespace Ryujinx.Core.OsHle.Objects.Time
{
class ISteadyClock : IIpcInterface
{
private Dictionary<int, ServiceProcessRequest> m_Commands;
public IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
public ISteadyClock()
{
m_Commands = new Dictionary<int, ServiceProcessRequest>()
{
//...
};
}
}
}

View file

@ -0,0 +1,42 @@
using Ryujinx.Core.OsHle.Ipc;
using System;
using System.Collections.Generic;
namespace Ryujinx.Core.OsHle.Objects.Time
{
class ISystemClock : IIpcInterface
{
private Dictionary<int, ServiceProcessRequest> m_Commands;
public IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
private static DateTime Epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
private SystemClockType ClockType;
public ISystemClock(SystemClockType ClockType)
{
m_Commands = new Dictionary<int, ServiceProcessRequest>()
{
{ 0, GetCurrentTime }
};
this.ClockType = ClockType;
}
public long GetCurrentTime(ServiceCtx Context)
{
DateTime CurrentTime = DateTime.Now;
if (ClockType == SystemClockType.User ||
ClockType == SystemClockType.Network)
{
CurrentTime = CurrentTime.ToUniversalTime();
}
Context.ResponseData.Write((long)(DateTime.Now - Epoch).TotalSeconds);
return 0;
}
}
}

View file

@ -0,0 +1,20 @@
using Ryujinx.Core.OsHle.Ipc;
using System.Collections.Generic;
namespace Ryujinx.Core.OsHle.Objects.Time
{
class ITimeZoneService : IIpcInterface
{
private Dictionary<int, ServiceProcessRequest> m_Commands;
public IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
public ITimeZoneService()
{
m_Commands = new Dictionary<int, ServiceProcessRequest>()
{
//...
};
}
}
}

View file

@ -0,0 +1,9 @@
namespace Ryujinx.Core.OsHle.Objects.Time
{
enum SystemClockType
{
User,
Network,
Local
}
}

View file

@ -0,0 +1,176 @@
using ChocolArm64.Memory;
using Ryujinx.Core.OsHle.Handles;
using Ryujinx.Core.OsHle.Ipc;
using System.Collections.Generic;
using System.IO;
using static Ryujinx.Core.OsHle.Objects.Android.Parcel;
using static Ryujinx.Core.OsHle.Objects.ObjHelper;
namespace Ryujinx.Core.OsHle.Objects.Vi
{
class IApplicationDisplayService : IIpcInterface
{
private Dictionary<int, ServiceProcessRequest> m_Commands;
public IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
public IApplicationDisplayService()
{
m_Commands = new Dictionary<int, ServiceProcessRequest>()
{
{ 100, GetRelayService },
{ 101, GetSystemDisplayService },
{ 102, GetManagerDisplayService },
{ 103, GetIndirectDisplayTransactionService },
{ 1010, OpenDisplay },
{ 2020, OpenLayer },
{ 2030, CreateStrayLayer },
{ 2101, SetLayerScalingMode },
{ 5202, GetDisplayVSyncEvent }
};
}
public long GetRelayService(ServiceCtx Context)
{
MakeObject(Context, new IHOSBinderDriver());
return 0;
}
public long GetSystemDisplayService(ServiceCtx Context)
{
MakeObject(Context, new ISystemDisplayService());
return 0;
}
public long GetManagerDisplayService(ServiceCtx Context)
{
MakeObject(Context, new IManagerDisplayService());
return 0;
}
public long GetIndirectDisplayTransactionService(ServiceCtx Context)
{
MakeObject(Context, new IHOSBinderDriver());
return 0;
}
public long OpenDisplay(ServiceCtx Context)
{
string Name = GetDisplayName(Context);
long DisplayId = Context.Ns.Os.Displays.GenerateId(new Display(Name));
Context.ResponseData.Write(DisplayId);
return 0;
}
public long OpenLayer(ServiceCtx Context)
{
long LayerId = Context.RequestData.ReadInt64();
long UserId = Context.RequestData.ReadInt64();
long ParcelPtr = Context.Request.ReceiveBuff[0].Position;
byte[] Parcel = MakeIGraphicsBufferProducer(ParcelPtr);
AMemoryHelper.WriteBytes(Context.Memory, ParcelPtr, Parcel);
Context.ResponseData.Write((long)Parcel.Length);
return 0;
}
public long CreateStrayLayer(ServiceCtx Context)
{
long LayerFlags = Context.RequestData.ReadInt64();
long DisplayId = Context.RequestData.ReadInt64();
long ParcelPtr = Context.Request.ReceiveBuff[0].Position;
Display Disp = Context.Ns.Os.Displays.GetData<Display>((int)DisplayId);
byte[] Parcel = MakeIGraphicsBufferProducer(ParcelPtr);
AMemoryHelper.WriteBytes(Context.Memory, ParcelPtr, Parcel);
Context.ResponseData.Write(0L);
Context.ResponseData.Write((long)Parcel.Length);
return 0;
}
public long SetLayerScalingMode(ServiceCtx Context)
{
int ScalingMode = Context.RequestData.ReadInt32();
long Unknown = Context.RequestData.ReadInt64();
return 0;
}
public long GetDisplayVSyncEvent(ServiceCtx Context)
{
string Name = GetDisplayName(Context);
int Handle = Context.Ns.Os.Handles.GenerateId(new HEvent());
Context.Response.HandleDesc = IpcHandleDesc.MakeCopy(Handle);
return 0;
}
private byte[] MakeIGraphicsBufferProducer(long BasePtr)
{
long Id = 0x20;
long CookiePtr = 0L;
using (MemoryStream MS = new MemoryStream())
{
BinaryWriter Writer = new BinaryWriter(MS);
//flat_binder_object (size is 0x28)
Writer.Write(2); //Type (BINDER_TYPE_WEAK_BINDER)
Writer.Write(0); //Flags
Writer.Write((int)(Id >> 0));
Writer.Write((int)(Id >> 32));
Writer.Write((int)(CookiePtr >> 0));
Writer.Write((int)(CookiePtr >> 32));
Writer.Write((byte)'d');
Writer.Write((byte)'i');
Writer.Write((byte)'s');
Writer.Write((byte)'p');
Writer.Write((byte)'d');
Writer.Write((byte)'r');
Writer.Write((byte)'v');
Writer.Write((byte)'\0');
Writer.Write(0L); //Pad
return MakeParcel(MS.ToArray(), new byte[] { 0, 0, 0, 0 });
}
}
private string GetDisplayName(ServiceCtx Context)
{
string Name = string.Empty;
for (int Index = 0; Index < 8 &&
Context.RequestData.BaseStream.Position <
Context.RequestData.BaseStream.Length; Index++)
{
byte Chr = Context.RequestData.ReadByte();
if (Chr >= 0x20 && Chr < 0x7f)
{
Name += (char)Chr;
}
}
return Name;
}
}
}

View file

@ -0,0 +1,214 @@
using ChocolArm64.Memory;
using Ryujinx.Core.OsHle.Handles;
using Ryujinx.Core.OsHle.Ipc;
using Ryujinx.Core.OsHle.Utilities;
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using static Ryujinx.Core.OsHle.Objects.Android.Parcel;
namespace Ryujinx.Core.OsHle.Objects.Vi
{
class IHOSBinderDriver : IIpcInterface
{
private delegate long ServiceProcessParcel(ServiceCtx Context, byte[] ParcelData);
private Dictionary<int, ServiceProcessRequest> m_Commands;
private Dictionary<(string, int), ServiceProcessParcel> m_Methods;
public IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
private class BufferObj
{
}
private IdPoolWithObj BufferSlots;
private byte[] Gbfr;
public IHOSBinderDriver()
{
m_Commands = new Dictionary<int, ServiceProcessRequest>()
{
{ 0, TransactParcel },
{ 1, AdjustRefcount },
{ 2, GetNativeHandle }
};
m_Methods = new Dictionary<(string, int), ServiceProcessParcel>()
{
{ ("android.gui.IGraphicBufferProducer", 0x1), GraphicBufferProducerRequestBuffer },
{ ("android.gui.IGraphicBufferProducer", 0x3), GraphicBufferProducerDequeueBuffer },
{ ("android.gui.IGraphicBufferProducer", 0x7), GraphicBufferProducerQueueBuffer },
{ ("android.gui.IGraphicBufferProducer", 0x8), GraphicBufferProducerCancelBuffer },
{ ("android.gui.IGraphicBufferProducer", 0x9), GraphicBufferProducerQuery },
{ ("android.gui.IGraphicBufferProducer", 0xa), GraphicBufferProducerConnect },
{ ("android.gui.IGraphicBufferProducer", 0xe), GraphicBufferPreallocateBuffer }
};
BufferSlots = new IdPoolWithObj();
}
public long TransactParcel(ServiceCtx Context)
{
int Id = Context.RequestData.ReadInt32();
int Code = Context.RequestData.ReadInt32();
long DataPos = Context.Request.SendBuff[0].Position;
long DataSize = Context.Request.SendBuff[0].Size;
byte[] Data = AMemoryHelper.ReadBytes(Context.Memory, DataPos, (int)DataSize);
Data = GetParcelData(Data);
using (MemoryStream MS = new MemoryStream(Data))
{
BinaryReader Reader = new BinaryReader(MS);
MS.Seek(4, SeekOrigin.Current);
int StrSize = Reader.ReadInt32();
string InterfaceName = Encoding.Unicode.GetString(Data, 8, StrSize * 2);
if (m_Methods.TryGetValue((InterfaceName, Code), out ServiceProcessParcel ProcReq))
{
return ProcReq(Context, Data);
}
else
{
throw new NotImplementedException($"{InterfaceName} {Code}");
}
}
}
private long GraphicBufferProducerRequestBuffer(ServiceCtx Context, byte[] ParcelData)
{
int GbfrSize = Gbfr?.Length ?? 0;
byte[] Data = new byte[GbfrSize + 4];
if (Gbfr != null)
{
Buffer.BlockCopy(Gbfr, 0, Data, 0, GbfrSize);
}
return MakeReplyParcel(Context, Data);
}
private long GraphicBufferProducerDequeueBuffer(ServiceCtx Context, byte[] ParcelData)
{
//Note: It seems that the maximum number of slots is 64, because if we return
//a Slot number > 63, it seems to cause a buffer overrun and it reads garbage.
//Note 2: The size of each object associated with the slot is 0x30.
int Slot = BufferSlots.GenerateId(new BufferObj());
return MakeReplyParcel(Context, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
}
private long GraphicBufferProducerQueueBuffer(ServiceCtx Context, byte[] ParcelData)
{
return MakeReplyParcel(Context, 1280, 720, 0, 0, 0);
}
private long GraphicBufferProducerCancelBuffer(ServiceCtx Context, byte[] ParcelData)
{
using (MemoryStream MS = new MemoryStream(ParcelData))
{
BinaryReader Reader = new BinaryReader(MS);
MS.Seek(0x50, SeekOrigin.Begin);
int Slot = Reader.ReadInt32();
BufferSlots.Delete(Slot);
return MakeReplyParcel(Context, 0);
}
}
private long GraphicBufferProducerQuery(ServiceCtx Context, byte[] ParcelData)
{
return MakeReplyParcel(Context, 0, 0);
}
private long GraphicBufferProducerConnect(ServiceCtx Context, byte[] ParcelData)
{
return MakeReplyParcel(Context, 1280, 720, 0, 0, 0);
}
private long GraphicBufferPreallocateBuffer(ServiceCtx Context, byte[] ParcelData)
{
int GbfrSize = ParcelData.Length - 0x54;
Gbfr = new byte[GbfrSize];
Buffer.BlockCopy(ParcelData, 0x54, Gbfr, 0, GbfrSize);
using (MemoryStream MS = new MemoryStream(ParcelData))
{
BinaryReader Reader = new BinaryReader(MS);
MS.Seek(0xd4, SeekOrigin.Begin);
int Handle = Reader.ReadInt32();
HNvMap NvMap = Context.Ns.Os.Handles.GetData<HNvMap>(Handle);
Context.Ns.Gpu.Renderer.FrameBufferPtr = NvMap.Address;
}
return MakeReplyParcel(Context, 0);
}
private long MakeReplyParcel(ServiceCtx Context, params int[] Ints)
{
using (MemoryStream MS = new MemoryStream())
{
BinaryWriter Writer = new BinaryWriter(MS);
foreach (int Int in Ints)
{
Writer.Write(Int);
}
return MakeReplyParcel(Context, MS.ToArray());
}
}
private long MakeReplyParcel(ServiceCtx Context, byte[] Data)
{
long ReplyPos = Context.Request.ReceiveBuff[0].Position;
long ReplySize = Context.Request.ReceiveBuff[0].Position;
byte[] Reply = MakeParcel(Data, new byte[0]);
AMemoryHelper.WriteBytes(Context.Memory, ReplyPos, Reply);
return 0;
}
public long AdjustRefcount(ServiceCtx Context)
{
int Id = Context.RequestData.ReadInt32();
int AddVal = Context.RequestData.ReadInt32();
int Type = Context.RequestData.ReadInt32();
return 0;
}
public long GetNativeHandle(ServiceCtx Context)
{
int Id = Context.RequestData.ReadInt32();
uint Unk = Context.RequestData.ReadUInt32();
Context.Response.HandleDesc = IpcHandleDesc.MakeMove(0xbadcafe);
return 0;
}
}
}

View file

@ -0,0 +1,33 @@
using Ryujinx.Core.OsHle.Ipc;
using System.Collections.Generic;
namespace Ryujinx.Core.OsHle.Objects.Vi
{
class IManagerDisplayService : IIpcInterface
{
private Dictionary<int, ServiceProcessRequest> m_Commands;
public IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
public IManagerDisplayService()
{
m_Commands = new Dictionary<int, ServiceProcessRequest>()
{
{ 2010, CreateManagedLayer },
{ 6000, AddToLayerStack }
};
}
public static long CreateManagedLayer(ServiceCtx Context)
{
Context.ResponseData.Write(0L); //LayerId
return 0;
}
public static long AddToLayerStack(ServiceCtx Context)
{
return 0;
}
}
}

View file

@ -0,0 +1,25 @@
using Ryujinx.Core.OsHle.Ipc;
using System.Collections.Generic;
namespace Ryujinx.Core.OsHle.Objects.Vi
{
class ISystemDisplayService : IIpcInterface
{
private Dictionary<int, ServiceProcessRequest> m_Commands;
public IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
public ISystemDisplayService()
{
m_Commands = new Dictionary<int, ServiceProcessRequest>()
{
{ 2205, SetLayerZ }
};
}
public static long SetLayerZ(ServiceCtx Context)
{
return 0;
}
}
}

View file

@ -0,0 +1,257 @@
using ChocolArm64;
using ChocolArm64.Memory;
using ChocolArm64.State;
using Ryujinx.Core.Loaders;
using Ryujinx.Core.Loaders.Executables;
using Ryujinx.Core.OsHle.Exceptions;
using Ryujinx.Core.OsHle.Handles;
using Ryujinx.Core.OsHle.Svc;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Threading;
namespace Ryujinx.Core.OsHle
{
public class Process : IDisposable
{
private const int MaxStackSize = 8 * 1024 * 1024;
private const int TlsSize = 0x200;
private const int TotalTlsSlots = 32;
private const int TlsTotalSize = TotalTlsSlots * TlsSize;
private const long TlsPageAddr = (AMemoryMgr.AddrSize - TlsTotalSize) & ~AMemoryMgr.PageMask;
private Switch Ns;
public int ProcessId { get; private set; }
public AMemory Memory { get; private set; }
public KProcessScheduler Scheduler { get; private set; }
private SvcHandler SvcHandler;
private ConcurrentDictionary<int, AThread> TlsSlots;
private ConcurrentDictionary<long, HThread> ThreadsByTpidr;
private List<Executable> Executables;
private HThread MainThread;
private long ImageBase;
public Process(Switch Ns, AMemoryAlloc Allocator, int ProcessId)
{
this.Ns = Ns;
this.ProcessId = ProcessId;
Memory = new AMemory(Ns.Ram, Allocator);
Scheduler = new KProcessScheduler();
SvcHandler = new SvcHandler(Ns, this);
TlsSlots = new ConcurrentDictionary<int, AThread>();
ThreadsByTpidr = new ConcurrentDictionary<long, HThread>();
Executables = new List<Executable>();
ImageBase = 0x8000000;
Memory.Manager.MapPhys(
TlsPageAddr,
TlsTotalSize,
(int)MemoryType.ThreadLocal,
AMemoryPerm.RW);
}
public void LoadProgram(IExecutable Program)
{
Logging.Info($"Image base at 0x{ImageBase:x16}.");
Executable Executable = new Executable(Program, Memory, ImageBase);
Executables.Add(Executable);
ImageBase = AMemoryHelper.PageRoundUp(Executable.ImageEnd);
}
public void SetEmptyArgs()
{
ImageBase += AMemoryMgr.PageSize;
}
public void InitializeHeap()
{
Memory.Manager.SetHeapAddr((ImageBase + 0x3fffffff) & ~0x3fffffff);
}
public bool Run()
{
if (Executables.Count == 0)
{
return false;
}
long StackBot = TlsPageAddr - MaxStackSize;
Memory.Manager.MapPhys(StackBot, MaxStackSize, (int)MemoryType.Normal, AMemoryPerm.RW);
int Handle = MakeThread(Executables[0].ImageBase, TlsPageAddr, 0, 0, 0);
if (Handle == -1)
{
return false;
}
MainThread = Ns.Os.Handles.GetData<HThread>(Handle);
Scheduler.StartThread(MainThread);
return true;
}
public void StopAllThreads()
{
if (MainThread != null)
{
while (MainThread.Thread.IsAlive)
{
MainThread.Thread.StopExecution();
}
}
foreach (AThread Thread in TlsSlots.Values)
{
while (Thread.IsAlive)
{
Thread.StopExecution();
}
}
}
public int MakeThread(
long EntryPoint,
long StackTop,
long ArgsPtr,
int Priority,
int ProcessorId)
{
ThreadPriority ThreadPrio;
if (Priority < 12)
{
ThreadPrio = ThreadPriority.Highest;
}
else if (Priority < 24)
{
ThreadPrio = ThreadPriority.AboveNormal;
}
else if (Priority < 36)
{
ThreadPrio = ThreadPriority.Normal;
}
else if (Priority < 48)
{
ThreadPrio = ThreadPriority.BelowNormal;
}
else
{
ThreadPrio = ThreadPriority.Lowest;
}
AThread Thread = new AThread(Memory, ThreadPrio, EntryPoint);
HThread ThreadHnd = new HThread(Thread, ProcessorId, Priority);
int Handle = Ns.Os.Handles.GenerateId(ThreadHnd);
int TlsSlot = GetFreeTlsSlot(Thread);
if (TlsSlot == -1 || Handle == -1)
{
return -1;
}
Thread.ThreadState.Break += BreakHandler;
Thread.ThreadState.SvcCall += SvcHandler.SvcCall;
Thread.ThreadState.Undefined += UndefinedHandler;
Thread.ThreadState.ProcessId = ProcessId;
Thread.ThreadState.ThreadId = Ns.Os.IdGen.GenerateId();
Thread.ThreadState.Tpidr = TlsPageAddr + TlsSlot * TlsSize;
Thread.ThreadState.X0 = (ulong)ArgsPtr;
Thread.ThreadState.X1 = (ulong)Handle;
Thread.ThreadState.X31 = (ulong)StackTop;
Thread.WorkFinished += ThreadFinished;
ThreadsByTpidr.TryAdd(Thread.ThreadState.Tpidr, ThreadHnd);
return Handle;
}
private void BreakHandler(object sender, AInstExceptEventArgs e)
{
throw new GuestBrokeExecutionException();
}
private void UndefinedHandler(object sender, AInstUndEventArgs e)
{
throw new UndefinedInstructionException(e.Position, e.RawOpCode);
}
private int GetFreeTlsSlot(AThread Thread)
{
for (int Index = 1; Index < TotalTlsSlots; Index++)
{
if (TlsSlots.TryAdd(Index, Thread))
{
return Index;
}
}
return -1;
}
private void ThreadFinished(object sender, EventArgs e)
{
if (sender is AThread Thread)
{
TlsSlots.TryRemove(GetTlsSlot(Thread.ThreadState.Tpidr), out _);
Ns.Os.IdGen.DeleteId(Thread.ThreadId);
}
}
private int GetTlsSlot(long Position)
{
return (int)((Position - TlsPageAddr) / TlsSize);
}
public HThread GetThread(long Tpidr)
{
if (!ThreadsByTpidr.TryGetValue(Tpidr, out HThread Thread))
{
Logging.Error($"Thread with TPIDR 0x{Tpidr:x16} not found!");
}
return Thread;
}
public void Dispose()
{
Dispose(true);
}
protected virtual void Dispose(bool Disposing)
{
if (Disposing)
{
Scheduler.Dispose();
}
}
}
}

View file

@ -0,0 +1,36 @@
using ChocolArm64.Memory;
using Ryujinx.Core.OsHle.Handles;
using Ryujinx.Core.OsHle.Ipc;
using System.IO;
namespace Ryujinx.Core.OsHle
{
class ServiceCtx
{
public Switch Ns { get; private set; }
public AMemory Memory { get; private set; }
public HSession Session { get; private set; }
public IpcMessage Request { get; private set; }
public IpcMessage Response { get; private set; }
public BinaryReader RequestData { get; private set; }
public BinaryWriter ResponseData { get; private set; }
public ServiceCtx(
Switch Ns,
AMemory Memory,
HSession Session,
IpcMessage Request,
IpcMessage Response,
BinaryReader RequestData,
BinaryWriter ResponseData)
{
this.Ns = Ns;
this.Memory = Memory;
this.Session = Session;
this.Request = Request;
this.Response = Response;
this.RequestData = RequestData;
this.ResponseData = ResponseData;
}
}
}

View file

@ -0,0 +1,33 @@
using Ryujinx.Core.OsHle.Objects.Acc;
using static Ryujinx.Core.OsHle.Objects.ObjHelper;
namespace Ryujinx.Core.OsHle.Services
{
static partial class Service
{
public static long AccU0ListOpenUsers(ServiceCtx Context)
{
return 0;
}
public static long AccU0GetProfile(ServiceCtx Context)
{
MakeObject(Context, new IProfile());
return 0;
}
public static long AccU0InitializeApplicationInfo(ServiceCtx Context)
{
return 0;
}
public static long AccU0GetBaasAccountManagerForApplication(ServiceCtx Context)
{
MakeObject(Context, new IManagerForApplication());
return 0;
}
}
}

View file

@ -0,0 +1,16 @@
using Ryujinx.Core.OsHle.Objects.Apm;
using static Ryujinx.Core.OsHle.Objects.ObjHelper;
namespace Ryujinx.Core.OsHle.Services
{
static partial class Service
{
public static long ApmOpenSession(ServiceCtx Context)
{
MakeObject(Context, new ISession());
return 0;
}
}
}

View file

@ -0,0 +1,16 @@
using Ryujinx.Core.OsHle.Objects.Am;
using static Ryujinx.Core.OsHle.Objects.ObjHelper;
namespace Ryujinx.Core.OsHle.Services
{
static partial class Service
{
public static long AppletOpenApplicationProxy(ServiceCtx Context)
{
MakeObject(Context, new IApplicationProxy());
return 0;
}
}
}

View file

@ -0,0 +1,71 @@
using ChocolArm64.Memory;
using Ryujinx.Core.OsHle.Objects.Aud;
using System.Text;
using static Ryujinx.Core.OsHle.Objects.ObjHelper;
namespace Ryujinx.Core.OsHle.Services
{
static partial class Service
{
public static long AudOutListAudioOuts(ServiceCtx Context)
{
long Position = Context.Request.ReceiveBuff[0].Position;
AMemoryHelper.WriteBytes(Context.Memory, Position, Encoding.ASCII.GetBytes("iface"));
Context.ResponseData.Write(1);
return 0;
}
public static long AudOutOpenAudioOut(ServiceCtx Context)
{
MakeObject(Context, new IAudioOut());
Context.ResponseData.Write(48000); //Sample Rate
Context.ResponseData.Write(2); //Channel Count
Context.ResponseData.Write(2); //PCM Format
/*
0 - Invalid
1 - INT8
2 - INT16
3 - INT24
4 - INT32
5 - PCM Float
6 - ADPCM
*/
Context.ResponseData.Write(0); //Unknown
return 0;
}
public static long AudRenOpenAudioRenderer(ServiceCtx Context)
{
MakeObject(Context, new IAudioRenderer());
return 0;
}
public static long AudRenGetAudioRendererWorkBufferSize(ServiceCtx Context)
{
int SampleRate = Context.RequestData.ReadInt32();
int Unknown4 = Context.RequestData.ReadInt32();
int Unknown8 = Context.RequestData.ReadInt32();
int UnknownC = Context.RequestData.ReadInt32();
int Unknown10 = Context.RequestData.ReadInt32();
int Unknown14 = Context.RequestData.ReadInt32();
int Unknown18 = Context.RequestData.ReadInt32();
int Unknown1c = Context.RequestData.ReadInt32();
int Unknown20 = Context.RequestData.ReadInt32();
int Unknown24 = Context.RequestData.ReadInt32();
int Unknown28 = Context.RequestData.ReadInt32();
int Unknown2c = Context.RequestData.ReadInt32();
int Rev1Magic = Context.RequestData.ReadInt32();
Context.ResponseData.Write(0x400L);
return 0;
}
}
}

View file

@ -0,0 +1,16 @@
using Ryujinx.Core.OsHle.Objects.Friend;
using static Ryujinx.Core.OsHle.Objects.ObjHelper;
namespace Ryujinx.Core.OsHle.Services
{
static partial class Service
{
public static long FriendCreateFriendService(ServiceCtx Context)
{
MakeObject(Context, new IFriendService());
return 0;
}
}
}

View file

@ -0,0 +1,49 @@
using Ryujinx.Core.OsHle.Objects.FspSrv;
using static Ryujinx.Core.OsHle.Objects.ObjHelper;
namespace Ryujinx.Core.OsHle.Services
{
static partial class Service
{
public static long FspSrvInitialize(ServiceCtx Context)
{
return 0;
}
public static long FspSrvMountSdCard(ServiceCtx Context)
{
MakeObject(Context, new IFileSystem(Context.Ns.VFs.GetSdCardPath()));
return 0;
}
public static long FspSrvMountSaveData(ServiceCtx Context)
{
MakeObject(Context, new IFileSystem(Context.Ns.VFs.GetGameSavesPath()));
return 0;
}
public static long FspSrvOpenDataStorageByCurrentProcess(ServiceCtx Context)
{
MakeObject(Context, new IStorage(Context.Ns.VFs.RomFs));
return 0;
}
public static long FspSrvOpenRomStorage(ServiceCtx Context)
{
MakeObject(Context, new IStorage(Context.Ns.VFs.RomFs));
return 0;
}
public static long FspSrvGetGlobalAccessLogMode(ServiceCtx Context)
{
Context.ResponseData.Write(0);
return 0;
}
}
}

View file

@ -0,0 +1,56 @@
using Ryujinx.Core.OsHle.Handles;
using Ryujinx.Core.OsHle.Objects.Hid;
using static Ryujinx.Core.OsHle.Objects.ObjHelper;
namespace Ryujinx.Core.OsHle.Services
{
static partial class Service
{
public static long HidCreateAppletResource(ServiceCtx Context)
{
HSharedMem HidHndData = Context.Ns.Os.Handles.GetData<HSharedMem>(Context.Ns.Os.HidHandle);
MakeObject(Context, new IAppletResource(HidHndData));
return 0;
}
public static long HidActivateTouchScreen(ServiceCtx Context)
{
long Unknown = Context.RequestData.ReadInt64();
return 0;
}
public static long HidSetSupportedNpadStyleSet(ServiceCtx Context)
{
long Unknown0 = Context.RequestData.ReadInt64();
long Unknown8 = Context.RequestData.ReadInt64();
return 0;
}
public static long HidSetSupportedNpadIdType(ServiceCtx Context)
{
long Unknown = Context.RequestData.ReadInt64();
return 0;
}
public static long HidActivateNpad(ServiceCtx Context)
{
long Unknown = Context.RequestData.ReadInt64();
return 0;
}
public static long HidSetNpadJoyHoldType(ServiceCtx Context)
{
long Unknown0 = Context.RequestData.ReadInt64();
long Unknown8 = Context.RequestData.ReadInt64();
return 0;
}
}
}

View file

@ -0,0 +1,12 @@
namespace Ryujinx.Core.OsHle.Services
{
static partial class Service
{
public static long LmInitialize(ServiceCtx Context)
{
Context.Session.Initialize();
return 0;
}
}
}

View file

@ -0,0 +1,610 @@
using ChocolArm64.Memory;
using Ryujinx.Core.OsHle.Handles;
using Ryujinx.Core.OsHle.Ipc;
using Ryujinx.Core.OsHle.Utilities;
using Ryujinx.Graphics.Gpu;
using System;
using System.Collections.Generic;
namespace Ryujinx.Core.OsHle.Services
{
static partial class Service
{
private delegate long ServiceProcessRequest(ServiceCtx Context);
private static Dictionary<(string, int), ServiceProcessRequest> IoctlCmds =
new Dictionary<(string, int), ServiceProcessRequest>()
{
{ ("/dev/nvhost-as-gpu", 0x4101), NvGpuAsIoctlBindChannel },
{ ("/dev/nvhost-as-gpu", 0x4102), NvGpuAsIoctlAllocSpace },
{ ("/dev/nvhost-as-gpu", 0x4106), NvGpuAsIoctlMapBufferEx },
{ ("/dev/nvhost-as-gpu", 0x4108), NvGpuAsIoctlGetVaRegions },
{ ("/dev/nvhost-as-gpu", 0x4109), NvGpuAsIoctlInitializeEx },
{ ("/dev/nvhost-ctrl", 0x001b), NvHostIoctlCtrlGetConfig },
{ ("/dev/nvhost-ctrl", 0x001d), NvHostIoctlCtrlEventWait },
{ ("/dev/nvhost-ctrl-gpu", 0x4701), NvGpuIoctlZcullGetCtxSize },
{ ("/dev/nvhost-ctrl-gpu", 0x4702), NvGpuIoctlZcullGetInfo },
{ ("/dev/nvhost-ctrl-gpu", 0x4705), NvGpuIoctlGetCharacteristics },
{ ("/dev/nvhost-ctrl-gpu", 0x4706), NvGpuIoctlGetTpcMasks },
{ ("/dev/nvhost-ctrl-gpu", 0x4714), NvGpuIoctlZbcGetActiveSlotMask },
{ ("/dev/nvhost-gpu", 0x4714), NvMapIoctlChannelSetUserData },
{ ("/dev/nvhost-gpu", 0x4801), NvMapIoctlChannelSetNvMap },
{ ("/dev/nvhost-gpu", 0x4808), NvMapIoctlChannelSubmitGpFifo },
{ ("/dev/nvhost-gpu", 0x4809), NvMapIoctlChannelAllocObjCtx },
{ ("/dev/nvhost-gpu", 0x480b), NvMapIoctlChannelZcullBind },
{ ("/dev/nvhost-gpu", 0x480c), NvMapIoctlChannelSetErrorNotifier },
{ ("/dev/nvhost-gpu", 0x480d), NvMapIoctlChannelSetPriority },
{ ("/dev/nvhost-gpu", 0x481a), NvMapIoctlChannelAllocGpFifoEx2 },
{ ("/dev/nvmap", 0x0101), NvMapIocCreate },
{ ("/dev/nvmap", 0x0103), NvMapIocFromId },
{ ("/dev/nvmap", 0x0104), NvMapIocAlloc },
{ ("/dev/nvmap", 0x0109), NvMapIocParam },
{ ("/dev/nvmap", 0x010e), NvMapIocGetId },
};
public static long NvDrvOpen(ServiceCtx Context)
{
long NamePtr = Context.Request.SendBuff[0].Position;
string Name = AMemoryHelper.ReadAsciiString(Context.Memory, NamePtr);
int Fd = Context.Ns.Os.Fds.GenerateId(new FileDesc(Name));
Context.ResponseData.Write(Fd);
Context.ResponseData.Write(0);
return 0;
}
public static long NvDrvIoctl(ServiceCtx Context)
{
int Fd = Context.RequestData.ReadInt32();
int Cmd = Context.RequestData.ReadInt32() & 0xffff;
FileDesc FdData = Context.Ns.Os.Fds.GetData<FileDesc>(Fd);
long Position = Context.Request.PtrBuff[0].Position;
Context.ResponseData.Write(0);
if (IoctlCmds.TryGetValue((FdData.Name, Cmd), out ServiceProcessRequest ProcReq))
{
return ProcReq(Context);
}
else
{
throw new NotImplementedException($"{FdData.Name} {Cmd:x4}");
}
}
public static long NvDrvClose(ServiceCtx Context)
{
int Fd = Context.RequestData.ReadInt32();
Context.Ns.Os.Fds.Delete(Fd);
Context.ResponseData.Write(0);
return 0;
}
public static long NvDrvInitialize(ServiceCtx Context)
{
long TransferMemSize = Context.RequestData.ReadInt64();
int TransferMemHandle = Context.Request.HandleDesc.ToCopy[0];
Context.ResponseData.Write(0);
return 0;
}
public static long NvDrvQueryEvent(ServiceCtx Context)
{
int Fd = Context.RequestData.ReadInt32();
int EventId = Context.RequestData.ReadInt32();
Context.Response.HandleDesc = IpcHandleDesc.MakeCopy(0xcafe);
Context.ResponseData.Write(0);
return 0;
}
public static long NvDrvSetClientPid(ServiceCtx Context)
{
long Pid = Context.RequestData.ReadInt64();
Context.ResponseData.Write(0);
return 0;
}
private static long NvGpuAsIoctlBindChannel(ServiceCtx Context)
{
long Position = Context.Request.PtrBuff[0].Position;
int Fd = Context.Memory.ReadInt32(Position);
return 0;
}
private static long NvGpuAsIoctlAllocSpace(ServiceCtx Context)
{
long Position = Context.Request.PtrBuff[0].Position;
MemReader Reader = new MemReader(Context.Memory, Position);
int Pages = Reader.ReadInt32();
int PageSize = Reader.ReadInt32();
int Flags = Reader.ReadInt32();
int Padding = Reader.ReadInt32();
long Align = Reader.ReadInt64();
if ((Flags & 1) != 0)
{
Align = Context.Ns.Gpu.ReserveMemory(Align, (long)Pages * PageSize, 1);
}
else
{
Align = Context.Ns.Gpu.ReserveMemory((long)Pages * PageSize, Align);
}
Context.Memory.WriteInt64(Position + 0x10, Align);
return 0;
}
private static long NvGpuAsIoctlMapBufferEx(ServiceCtx Context)
{
long Position = Context.Request.PtrBuff[0].Position;
MemReader Reader = new MemReader(Context.Memory, Position);
int Flags = Reader.ReadInt32();
int Kind = Reader.ReadInt32();
int Handle = Reader.ReadInt32();
int PageSize = Reader.ReadInt32();
long BuffAddr = Reader.ReadInt64();
long MapSize = Reader.ReadInt64();
long Offset = Reader.ReadInt64();
HNvMap NvMap = Context.Ns.Os.Handles.GetData<HNvMap>(Handle);
if (NvMap != null)
{
if ((Flags & 1) != 0)
{
Offset = Context.Ns.Gpu.MapMemory(NvMap.Address, Offset, NvMap.Size);
}
else
{
Offset = Context.Ns.Gpu.MapMemory(NvMap.Address, NvMap.Size);
}
}
Context.Memory.WriteInt64(Position + 0x20, Offset);
return 0;
}
private static long NvGpuAsIoctlGetVaRegions(ServiceCtx Context)
{
long Position = Context.Request.PtrBuff[0].Position;
MemReader Reader = new MemReader(Context.Memory, Position);
MemWriter Writer = new MemWriter(Context.Memory, Position);
long Unused = Reader.ReadInt64();
int BuffSize = Reader.ReadInt32();
int Padding = Reader.ReadInt32();
BuffSize = 0x30;
Writer.WriteInt64(Unused);
Writer.WriteInt32(BuffSize);
Writer.WriteInt32(Padding);
Writer.WriteInt64(0);
Writer.WriteInt32(0);
Writer.WriteInt32(0);
Writer.WriteInt64(0);
Writer.WriteInt64(0);
Writer.WriteInt32(0);
Writer.WriteInt32(0);
Writer.WriteInt64(0);
return 0;
}
private static long NvGpuAsIoctlInitializeEx(ServiceCtx Context)
{
long Position = Context.Request.PtrBuff[0].Position;
MemReader Reader = new MemReader(Context.Memory, Position);
int BigPageSize = Reader.ReadInt32();
int AsFd = Reader.ReadInt32();
int Flags = Reader.ReadInt32();
int Reserved = Reader.ReadInt32();
long Unknown10 = Reader.ReadInt64();
long Unknown18 = Reader.ReadInt64();
long Unknown20 = Reader.ReadInt64();
return 0;
}
private static long NvHostIoctlCtrlGetConfig(ServiceCtx Context)
{
long Position = Context.Request.PtrBuff[0].Position;
MemReader Reader = new MemReader(Context.Memory, Position);
MemWriter Writer = new MemWriter(Context.Memory, Position + 0x82);
for (int Index = 0; Index < 0x101; Index++)
{
Writer.WriteByte(0);
}
return 0;
}
private static long NvHostIoctlCtrlEventWait(ServiceCtx Context)
{
long Position = Context.Request.PtrBuff[0].Position;
MemReader Reader = new MemReader(Context.Memory, Position);
int SyncPtId = Reader.ReadInt32();
int Threshold = Reader.ReadInt32();
int Timeout = Reader.ReadInt32();
int Value = Reader.ReadInt32();
Context.Memory.WriteInt32(Position + 0xc, 0xcafe);
return 0;
}
private static long NvGpuIoctlZcullGetCtxSize(ServiceCtx Context)
{
long Position = Context.Request.PtrBuff[0].Position;
Context.Memory.WriteInt32(Position, 1);
return 0;
}
private static long NvGpuIoctlZcullGetInfo(ServiceCtx Context)
{
long Position = Context.Request.PtrBuff[0].Position;
MemWriter Writer = new MemWriter(Context.Memory, Position);
Writer.WriteInt32(0);
Writer.WriteInt32(0);
Writer.WriteInt32(0);
Writer.WriteInt32(0);
Writer.WriteInt32(0);
Writer.WriteInt32(0);
Writer.WriteInt32(0);
Writer.WriteInt32(0);
Writer.WriteInt32(0);
Writer.WriteInt32(0);
return 0;
}
private static long NvGpuIoctlGetCharacteristics(ServiceCtx Context)
{
long Position = Context.Request.PtrBuff[0].Position;
MemReader Reader = new MemReader(Context.Memory, Position);
MemWriter Writer = new MemWriter(Context.Memory, Position);
//Note: We should just ignore the BuffAddr, because official code
//does __memcpy_device from Position + 0x10 to BuffAddr.
long BuffSize = Reader.ReadInt64();
long BuffAddr = Reader.ReadInt64();
BuffSize = 0xa0;
Writer.WriteInt64(BuffSize);
Writer.WriteInt64(BuffAddr);
Writer.WriteInt32(0x120); //NVGPU_GPU_ARCH_GM200
Writer.WriteInt32(0xb); //NVGPU_GPU_IMPL_GM20B
Writer.WriteInt32(0xa1);
Writer.WriteInt32(1);
Writer.WriteInt64(0x40000);
Writer.WriteInt64(0);
Writer.WriteInt32(2);
Writer.WriteInt32(0x20); //NVGPU_GPU_BUS_TYPE_AXI
Writer.WriteInt32(0x20000);
Writer.WriteInt32(0x20000);
Writer.WriteInt32(0x1b);
Writer.WriteInt32(0x30000);
Writer.WriteInt32(1);
Writer.WriteInt32(0x503);
Writer.WriteInt32(0x503);
Writer.WriteInt32(0x80);
Writer.WriteInt32(0x28);
Writer.WriteInt32(0);
Writer.WriteInt64(0x55);
Writer.WriteInt32(0x902d); //FERMI_TWOD_A
Writer.WriteInt32(0xb197); //MAXWELL_B
Writer.WriteInt32(0xb1c0); //MAXWELL_COMPUTE_B
Writer.WriteInt32(0xb06f); //MAXWELL_CHANNEL_GPFIFO_A
Writer.WriteInt32(0xa140); //KEPLER_INLINE_TO_MEMORY_B
Writer.WriteInt32(0xb0b5); //MAXWELL_DMA_COPY_A
Writer.WriteInt32(1);
Writer.WriteInt32(0);
Writer.WriteInt32(2);
Writer.WriteInt32(1);
Writer.WriteInt32(0);
Writer.WriteInt32(1);
Writer.WriteInt32(0x21d70);
Writer.WriteInt32(0);
Writer.WriteByte((byte)'g');
Writer.WriteByte((byte)'m');
Writer.WriteByte((byte)'2');
Writer.WriteByte((byte)'0');
Writer.WriteByte((byte)'b');
Writer.WriteByte((byte)'\0');
Writer.WriteByte((byte)'\0');
Writer.WriteByte((byte)'\0');
Writer.WriteInt64(0);
return 0;
}
private static long NvGpuIoctlGetTpcMasks(ServiceCtx Context)
{
long Position = Context.Request.PtrBuff[0].Position;
MemReader Reader = new MemReader(Context.Memory, Position);
int MaskBuffSize = Reader.ReadInt32();
int Reserved = Reader.ReadInt32();
long MaskBuffAddr = Reader.ReadInt64();
long Unknown = Reader.ReadInt64();
return 0;
}
private static long NvGpuIoctlZbcGetActiveSlotMask(ServiceCtx Context)
{
long Position = Context.Request.PtrBuff[0].Position;
Context.Memory.WriteInt32(Position + 0, 7);
Context.Memory.WriteInt32(Position + 4, 1);
return 0;
}
private static long NvMapIoctlChannelSetUserData(ServiceCtx Context)
{
long Position = Context.Request.PtrBuff[0].Position;
return 0;
}
private static long NvMapIoctlChannelSetNvMap(ServiceCtx Context)
{
long Position = Context.Request.PtrBuff[0].Position;
int Fd = Context.Memory.ReadInt32(Position);
return 0;
}
private static long NvMapIoctlChannelSubmitGpFifo(ServiceCtx Context)
{
long Position = Context.Request.PtrBuff[0].Position;
MemReader Reader = new MemReader(Context.Memory, Position);
MemWriter Writer = new MemWriter(Context.Memory, Position + 0x10);
long GpFifo = Reader.ReadInt64();
int Count = Reader.ReadInt32();
int Flags = Reader.ReadInt32();
int FenceId = Reader.ReadInt32();
int FenceVal = Reader.ReadInt32();
for (int Index = 0; Index < Count; Index++)
{
long GpFifoHdr = Reader.ReadInt64();
long GpuAddr = GpFifoHdr & 0xffffffffff;
int Size = (int)(GpFifoHdr >> 40) & 0x7ffffc;
long CpuAddr = Context.Ns.Gpu.GetCpuAddr(GpuAddr);
if (CpuAddr != -1)
{
byte[] Data = AMemoryHelper.ReadBytes(Context.Memory, CpuAddr, Size);
NsGpuPBEntry[] PushBuffer = NsGpuPBEntry.DecodePushBuffer(Data);
Context.Ns.Gpu.ProcessPushBuffer(PushBuffer, Context.Memory);
}
}
Writer.WriteInt32(0);
Writer.WriteInt32(0);
return 0;
}
private static long NvMapIoctlChannelAllocObjCtx(ServiceCtx Context)
{
long Position = Context.Request.PtrBuff[0].Position;
int ClassNum = Context.Memory.ReadInt32(Position + 0);
int Flags = Context.Memory.ReadInt32(Position + 4);
Context.Memory.WriteInt32(Position + 8, 0);
return 0;
}
private static long NvMapIoctlChannelZcullBind(ServiceCtx Context)
{
long Position = Context.Request.PtrBuff[0].Position;
MemReader Reader = new MemReader(Context.Memory, Position);
long GpuVa = Reader.ReadInt64();
int Mode = Reader.ReadInt32();
int Padding = Reader.ReadInt32();
return 0;
}
private static long NvMapIoctlChannelSetErrorNotifier(ServiceCtx Context)
{
long Position = Context.Request.PtrBuff[0].Position;
MemReader Reader = new MemReader(Context.Memory, Position);
long Offset = Reader.ReadInt64();
long Size = Reader.ReadInt64();
int Mem = Reader.ReadInt32();
int Padding = Reader.ReadInt32();
return 0;
}
private static long NvMapIoctlChannelSetPriority(ServiceCtx Context)
{
long Position = Context.Request.PtrBuff[0].Position;
int Priority = Context.Memory.ReadInt32(Position);
return 0;
}
private static long NvMapIoctlChannelAllocGpFifoEx2(ServiceCtx Context)
{
long Position = Context.Request.PtrBuff[0].Position;
MemReader Reader = new MemReader(Context.Memory, Position);
MemWriter Writer = new MemWriter(Context.Memory, Position + 0xc);
int Count = Reader.ReadInt32();
int Flags = Reader.ReadInt32();
int Unknown8 = Reader.ReadInt32();
long Fence = Reader.ReadInt64();
int Unknown14 = Reader.ReadInt32();
int Unknown18 = Reader.ReadInt32();
Writer.WriteInt32(0);
Writer.WriteInt32(0);
return 0;
}
private static long NvMapIocCreate(ServiceCtx Context)
{
long Position = Context.Request.GetSendBuffPtr();
int Size = Context.Memory.ReadInt32(Position);
int Id = Context.Ns.Os.NvMapIds.GenerateId();
int Handle = Context.Ns.Os.Handles.GenerateId(new HNvMap(Id, Size));
Context.Memory.WriteInt32(Position + 4, Handle);
return 0;
}
private static long NvMapIocFromId(ServiceCtx Context)
{
long Position = Context.Request.GetSendBuffPtr();
int Id = Context.Memory.ReadInt32(Position);
int Handle = -1;
foreach (KeyValuePair<int, object> KV in Context.Ns.Os.Handles)
{
if (KV.Value is HNvMap NvMap && NvMap.Id == Id)
{
Handle = KV.Key;
break;
}
}
Context.Memory.WriteInt32(Position + 4, Handle);
return 0;
}
private static long NvMapIocAlloc(ServiceCtx Context)
{
long Position = Context.Request.GetSendBuffPtr();
MemReader Reader = new MemReader(Context.Memory, Position);
int Handle = Reader.ReadInt32();
int HeapMask = Reader.ReadInt32();
int Flags = Reader.ReadInt32();
int Align = Reader.ReadInt32();
byte Kind = (byte)Reader.ReadInt64();
long Addr = Reader.ReadInt64();
HNvMap NvMap = Context.Ns.Os.Handles.GetData<HNvMap>(Handle);
if (NvMap != null)
{
NvMap.Address = Addr;
NvMap.Align = Align;
NvMap.Kind = Kind;
}
Logging.Debug($"NvMapIocAlloc at {NvMap.Address:x16}");
return 0;
}
private static long NvMapIocParam(ServiceCtx Context)
{
long Position = Context.Request.GetSendBuffPtr();
MemReader Reader = new MemReader(Context.Memory, Position);
int Handle = Reader.ReadInt32();
int Param = Reader.ReadInt32();
HNvMap NvMap = Context.Ns.Os.Handles.GetData<HNvMap>(Handle);
int Response = 0;
switch (Param)
{
case 1: Response = NvMap.Size; break;
case 2: Response = NvMap.Align; break;
case 4: Response = 0x40000000; break;
case 5: Response = NvMap.Kind; break;
}
Context.Memory.WriteInt32(Position + 8, Response);
return 0;
}
private static long NvMapIocGetId(ServiceCtx Context)
{
long Position = Context.Request.GetSendBuffPtr();
int Handle = Context.Memory.ReadInt32(Position + 4);
HNvMap NvMap = Context.Ns.Os.Handles.GetData<HNvMap>(Handle);
Context.Memory.WriteInt32(Position, NvMap.Id);
return 0;
}
}
}

View file

@ -0,0 +1,16 @@
using Ryujinx.Core.OsHle.Objects.Am;
using static Ryujinx.Core.OsHle.Objects.ObjHelper;
namespace Ryujinx.Core.OsHle.Services
{
static partial class Service
{
public static long PctlCreateService(ServiceCtx Context)
{
MakeObject(Context, new IParentalControlService());
return 0;
}
}
}

View file

@ -0,0 +1,35 @@
using Ryujinx.Core.OsHle.Ipc;
namespace Ryujinx.Core.OsHle.Services
{
static partial class Service
{
public static long PlGetLoadState(ServiceCtx Context)
{
Context.ResponseData.Write(1); //Loaded
return 0;
}
public static long PlGetFontSize(ServiceCtx Context)
{
Context.ResponseData.Write(Horizon.FontSize);
return 0;
}
public static long PlGetSharedMemoryAddressOffset(ServiceCtx Context)
{
Context.ResponseData.Write(0);
return 0;
}
public static long PlGetSharedMemoryNativeHandle(ServiceCtx Context)
{
Context.Response.HandleDesc = IpcHandleDesc.MakeCopy(Context.Ns.Os.FontHandle);
return 0;
}
}
}

View file

@ -0,0 +1,32 @@
using ChocolArm64.Memory;
namespace Ryujinx.Core.OsHle.Services
{
static partial class Service
{
private const int LangCodesCount = 13;
public static long SetGetAvailableLanguageCodes(ServiceCtx Context)
{
int PtrBuffSize = Context.RequestData.ReadInt32();
if (Context.Request.RecvListBuff.Count > 0)
{
long Position = Context.Request.RecvListBuff[0].Position;
short Size = Context.Request.RecvListBuff[0].Size;
//This should return an array of ints with values matching the LanguageCode enum.
byte[] Data = new byte[Size];
Data[0] = 0;
Data[1] = 1;
AMemoryHelper.WriteBytes(Context.Memory, Position, Data);
}
Context.ResponseData.Write(LangCodesCount);
return 0;
}
}
}

View file

@ -0,0 +1,48 @@
using Ryujinx.Core.OsHle.Handles;
using Ryujinx.Core.OsHle.Ipc;
namespace Ryujinx.Core.OsHle.Services
{
static partial class Service
{
private const int SmNotInitialized = 0x415;
public static long SmInitialize(ServiceCtx Context)
{
Context.Session.Initialize();
return 0;
}
public static long SmGetService(ServiceCtx Context)
{
//Only for kernel version > 3.0.0.
if (!Context.Session.IsInitialized)
{
//return SmNotInitialized;
}
string Name = string.Empty;
for (int Index = 0; Index < 8 &&
Context.RequestData.BaseStream.Position <
Context.RequestData.BaseStream.Length; Index++)
{
byte Chr = Context.RequestData.ReadByte();
if (Chr >= 0x20 && Chr < 0x7f)
{
Name += (char)Chr;
}
}
HSession Session = new HSession(Name);
int Handle = Context.Ns.Os.Handles.GenerateId(Session);
Context.Response.HandleDesc = IpcHandleDesc.MakeMove(Handle);
return 0;
}
}
}

View file

@ -0,0 +1,45 @@
using Ryujinx.Core.OsHle.Objects.Time;
using static Ryujinx.Core.OsHle.Objects.ObjHelper;
namespace Ryujinx.Core.OsHle.Services
{
static partial class Service
{
public static long TimeGetStandardUserSystemClock(ServiceCtx Context)
{
MakeObject(Context, new ISystemClock(SystemClockType.User));
return 0;
}
public static long TimeGetStandardNetworkSystemClock(ServiceCtx Context)
{
MakeObject(Context, new ISystemClock(SystemClockType.Network));
return 0;
}
public static long TimeGetStandardSteadyClock(ServiceCtx Context)
{
MakeObject(Context, new ISteadyClock());
return 0;
}
public static long TimeGetTimeZoneService(ServiceCtx Context)
{
MakeObject(Context, new ITimeZoneService());
return 0;
}
public static long TimeGetStandardLocalSystemClock(ServiceCtx Context)
{
MakeObject(Context, new ISystemClock(SystemClockType.Local));
return 0;
}
}
}

View file

@ -0,0 +1,18 @@
using Ryujinx.Core.OsHle.Objects.Vi;
using static Ryujinx.Core.OsHle.Objects.ObjHelper;
namespace Ryujinx.Core.OsHle.Services
{
static partial class Service
{
public static long ViGetDisplayService(ServiceCtx Context)
{
int Unknown = Context.RequestData.ReadInt32();
MakeObject(Context, new IApplicationDisplayService());
return 0;
}
}
}

View file

@ -0,0 +1,80 @@
using ChocolArm64.Memory;
using ChocolArm64.State;
using System;
using System.Collections.Generic;
namespace Ryujinx.Core.OsHle.Svc
{
partial class SvcHandler
{
private delegate void SvcFunc(AThreadState ThreadState);
private Dictionary<int, SvcFunc> SvcFuncs;
private Switch Ns;
private Process Process;
private AMemory Memory;
private static Random Rng;
public SvcHandler(Switch Ns, Process Process)
{
SvcFuncs = new Dictionary<int, SvcFunc>()
{
{ 0x01, SvcSetHeapSize },
{ 0x03, SvcSetMemoryAttribute },
{ 0x04, SvcMapMemory },
{ 0x06, SvcQueryMemory },
{ 0x07, SvcExitProcess },
{ 0x08, SvcCreateThread },
{ 0x09, SvcStartThread },
{ 0x0b, SvcSleepThread },
{ 0x0c, SvcGetThreadPriority },
{ 0x13, SvcMapSharedMemory },
{ 0x14, SvcUnmapSharedMemory },
{ 0x15, SvcCreateTransferMemory },
{ 0x16, SvcCloseHandle },
{ 0x17, SvcResetSignal },
{ 0x18, SvcWaitSynchronization },
{ 0x1a, SvcArbitrateLock },
{ 0x1b, SvcArbitrateUnlock },
{ 0x1c, SvcWaitProcessWideKeyAtomic },
{ 0x1d, SvcSignalProcessWideKey },
{ 0x1e, SvcGetSystemTick },
{ 0x1f, SvcConnectToNamedPort },
{ 0x21, SvcSendSyncRequest },
{ 0x22, SvcSendSyncRequestWithUserBuffer },
{ 0x26, SvcBreak },
{ 0x27, SvcOutputDebugString },
{ 0x29, SvcGetInfo }
};
this.Ns = Ns;
this.Process = Process;
this.Memory = Process.Memory;
}
static SvcHandler()
{
Rng = new Random();
}
public void SvcCall(object sender, AInstExceptEventArgs e)
{
AThreadState ThreadState = (AThreadState)sender;
if (SvcFuncs.TryGetValue(e.Id, out SvcFunc Func))
{
Logging.Trace($"(Thread {ThreadState.ThreadId}) {Func.Method.Name} called.");
Func(ThreadState);
Logging.Trace($"(Thread {ThreadState.ThreadId}) {Func.Method.Name} ended.");
}
else
{
throw new NotImplementedException(e.Id.ToString("x4"));
}
}
}
}

View file

@ -0,0 +1,121 @@
using ChocolArm64.Memory;
using ChocolArm64.State;
using Ryujinx.Core.OsHle.Handles;
namespace Ryujinx.Core.OsHle.Svc
{
partial class SvcHandler
{
private void SvcSetHeapSize(AThreadState ThreadState)
{
uint Size = (uint)ThreadState.X1;
Memory.Manager.SetHeapSize(Size, (int)MemoryType.Heap);
ThreadState.X0 = (int)SvcResult.Success;
ThreadState.X1 = (ulong)Memory.Manager.HeapAddr;
}
private void SvcSetMemoryAttribute(AThreadState ThreadState)
{
long Position = (long)ThreadState.X0;
long Size = (long)ThreadState.X1;
int State0 = (int)ThreadState.X2;
int State1 = (int)ThreadState.X3;
//TODO
ThreadState.X0 = (int)SvcResult.Success;
}
private void SvcMapMemory(AThreadState ThreadState)
{
long Dst = (long)ThreadState.X0;
long Src = (long)ThreadState.X1;
long Size = (long)ThreadState.X2;
Memory.Manager.MapMirror(Src, Dst, Size, (int)MemoryType.MappedMemory);
ThreadState.X0 = (int)SvcResult.Success;
}
private void SvcQueryMemory(AThreadState ThreadState)
{
long InfoPtr = (long)ThreadState.X0;
long Position = (long)ThreadState.X2;
AMemoryMapInfo MapInfo = Memory.Manager.GetMapInfo(Position);
MemoryInfo Info = new MemoryInfo(MapInfo);
Memory.WriteInt64(InfoPtr + 0x00, Info.BaseAddress);
Memory.WriteInt64(InfoPtr + 0x08, Info.Size);
Memory.WriteInt32(InfoPtr + 0x10, Info.MemType);
Memory.WriteInt32(InfoPtr + 0x14, Info.MemAttr);
Memory.WriteInt32(InfoPtr + 0x18, Info.MemPerm);
Memory.WriteInt32(InfoPtr + 0x1c, Info.IpcRefCount);
Memory.WriteInt32(InfoPtr + 0x20, Info.DeviceRefCount);
Memory.WriteInt32(InfoPtr + 0x24, Info.Padding);
//TODO: X1.
ThreadState.X0 = (int)SvcResult.Success;
ThreadState.X1 = 0;
}
private void SvcMapSharedMemory(AThreadState ThreadState)
{
int Handle = (int)ThreadState.X0;
long Src = (long)ThreadState.X1;
long Size = (long)ThreadState.X2;
int Perm = (int)ThreadState.X3;
HSharedMem SharedMem = Ns.Os.Handles.GetData<HSharedMem>(Handle);
if (SharedMem != null)
{
SharedMem.AddVirtualPosition(Src);
Memory.Manager.MapPhys(Src, Size, (int)MemoryType.SharedMemory, (AMemoryPerm)Perm);
ThreadState.X0 = (int)SvcResult.Success;
}
//TODO: Error codes.
}
private void SvcUnmapSharedMemory(AThreadState ThreadState)
{
int Handle = (int)ThreadState.X0;
long Position = (long)ThreadState.X1;
long Size = (long)ThreadState.X2;
HSharedMem HndData = Ns.Os.Handles.GetData<HSharedMem>(Handle);
if (HndData != null)
{
ThreadState.X0 = (int)SvcResult.Success;
}
//TODO: Error codes.
}
private void SvcCreateTransferMemory(AThreadState ThreadState)
{
long Position = (long)ThreadState.X1;
long Size = (long)ThreadState.X2;
int Perm = (int)ThreadState.X3;
AMemoryMapInfo MapInfo = Memory.Manager.GetMapInfo(Position);
Memory.Manager.Reprotect(Position, Size, (AMemoryPerm)Perm);
HTransferMem HndData = new HTransferMem(Memory, MapInfo.Perm, Position, Size);
int Handle = Ns.Os.Handles.GenerateId(HndData);
ThreadState.X1 = (ulong)Handle;
ThreadState.X0 = (int)SvcResult.Success;
}
}
}

View file

@ -0,0 +1,11 @@
namespace Ryujinx.Core.OsHle.Svc
{
enum SvcResult
{
Success = 0,
ErrBadHandle = 0xe401,
ErrTimeout = 0xea01,
ErrBadInfo = 0xf001,
ErrBadIpcReq = 0xf601
}
}

View file

@ -0,0 +1,227 @@
using ChocolArm64.Memory;
using ChocolArm64.State;
using Ryujinx.Core.OsHle.Exceptions;
using Ryujinx.Core.OsHle.Handles;
using Ryujinx.Core.OsHle.Ipc;
using System;
using System.Threading;
namespace Ryujinx.Core.OsHle.Svc
{
partial class SvcHandler
{
private void SvcExitProcess(AThreadState ThreadState) => Ns.Os.ExitProcess(ThreadState.ProcessId);
private void SvcCloseHandle(AThreadState ThreadState)
{
int Handle = (int)ThreadState.X0;
Ns.Os.CloseHandle(Handle);
ThreadState.X0 = (int)SvcResult.Success;
}
private void SvcResetSignal(AThreadState ThreadState)
{
int Handle = (int)ThreadState.X0;
//TODO: Implement events.
ThreadState.X0 = (int)SvcResult.Success;
}
private void SvcWaitSynchronization(AThreadState ThreadState)
{
long HandlesPtr = (long)ThreadState.X0;
int HandlesCount = (int)ThreadState.X2;
long Timeout = (long)ThreadState.X3;
//TODO: Implement events.
HThread CurrThread = Process.GetThread(ThreadState.Tpidr);
Process.Scheduler.Suspend(CurrThread.ProcessorId);
Process.Scheduler.Resume(CurrThread);
ThreadState.X0 = (int)SvcResult.Success;
}
private void SvcGetSystemTick(AThreadState ThreadState)
{
ThreadState.X0 = (ulong)ThreadState.CntpctEl0;
}
private void SvcConnectToNamedPort(AThreadState ThreadState)
{
long StackPtr = (long)ThreadState.X0;
long NamePtr = (long)ThreadState.X1;
string Name = AMemoryHelper.ReadAsciiString(Memory, NamePtr, 8);
//TODO: Validate that app has perms to access the service, and that the service
//actually exists, return error codes otherwise.
HSession Session = new HSession(Name);
ThreadState.X1 = (ulong)Ns.Os.Handles.GenerateId(Session);
ThreadState.X0 = (int)SvcResult.Success;
}
private void SvcSendSyncRequest(AThreadState ThreadState)
{
SendSyncRequest(ThreadState, false);
}
private void SvcSendSyncRequestWithUserBuffer(AThreadState ThreadState)
{
SendSyncRequest(ThreadState, true);
}
private void SendSyncRequest(AThreadState ThreadState, bool UserBuffer)
{
long CmdPtr = ThreadState.Tpidr;
long Size = 0x100;
int Handle = 0;
if (UserBuffer)
{
CmdPtr = (long)ThreadState.X0;
Size = (long)ThreadState.X1;
Handle = (int)ThreadState.X2;
}
else
{
Handle = (int)ThreadState.X0;
}
HThread CurrThread = Process.GetThread(ThreadState.Tpidr);
Process.Scheduler.Suspend(CurrThread.ProcessorId);
byte[] CmdData = AMemoryHelper.ReadBytes(Memory, CmdPtr, (int)Size);
HSession Session = Ns.Os.Handles.GetData<HSession>(Handle);
IpcMessage Cmd = new IpcMessage(CmdData, CmdPtr, Session is HDomain);
if (Session != null)
{
IpcHandler.IpcCall(Ns, Memory, Session, Cmd, CmdPtr, Handle);
byte[] Response = AMemoryHelper.ReadBytes(Memory, CmdPtr, (int)Size);
ThreadState.X0 = (int)SvcResult.Success;
}
else
{
ThreadState.X0 = (int)SvcResult.ErrBadIpcReq;
}
Thread.Yield();
Process.Scheduler.Resume(CurrThread);
}
private void SvcBreak(AThreadState ThreadState)
{
long Reason = (long)ThreadState.X0;
long Unknown = (long)ThreadState.X1;
long Info = (long)ThreadState.X2;
throw new GuestBrokeExecutionException();
}
private void SvcOutputDebugString(AThreadState ThreadState)
{
long Position = (long)ThreadState.X0;
long Size = (long)ThreadState.X1;
string Str = AMemoryHelper.ReadAsciiString(Memory, Position, (int)Size);
Logging.Info($"SvcOutputDebugString: {Str}");
ThreadState.X0 = (int)SvcResult.Success;
}
private void SvcGetInfo(AThreadState ThreadState)
{
long StackPtr = (long)ThreadState.X0;
int InfoType = (int)ThreadState.X1;
long Handle = (long)ThreadState.X2;
int InfoId = (int)ThreadState.X3;
//Fail for info not available on older Kernel versions.
if (InfoType == 18 ||
InfoType == 19)
{
ThreadState.X0 = (int)SvcResult.ErrBadInfo;
return;
}
switch (InfoType)
{
case 2: ThreadState.X1 = GetMapRegionBaseAddr(); break;
case 3: ThreadState.X1 = GetMapRegionSize(); break;
case 4: ThreadState.X1 = GetHeapRegionBaseAddr(); break;
case 5: ThreadState.X1 = GetHeapRegionSize(); break;
case 6: ThreadState.X1 = GetTotalMem(); break;
case 7: ThreadState.X1 = GetUsedMem(); break;
case 11: ThreadState.X1 = GetRnd64(); break;
case 12: ThreadState.X1 = GetAddrSpaceBaseAddr(); break;
case 13: ThreadState.X1 = GetAddrSpaceSize(); break;
case 14: ThreadState.X1 = GetMapRegionBaseAddr(); break;
case 15: ThreadState.X1 = GetMapRegionSize(); break;
default: throw new NotImplementedException($"SvcGetInfo: {InfoType} {Handle} {InfoId}");
}
ThreadState.X0 = (int)SvcResult.Success;
}
private ulong GetTotalMem()
{
return (ulong)Memory.Manager.GetTotalMemorySize();
}
private ulong GetUsedMem()
{
return (ulong)Memory.Manager.GetUsedMemorySize();
}
private ulong GetRnd64()
{
return (ulong)Rng.Next() + ((ulong)Rng.Next() << 32);
}
private ulong GetAddrSpaceBaseAddr()
{
return 0x08000000;
}
private ulong GetAddrSpaceSize()
{
return AMemoryMgr.AddrSize - GetAddrSpaceBaseAddr();
}
private ulong GetMapRegionBaseAddr()
{
return 0x80000000;
}
private ulong GetMapRegionSize()
{
return 0x40000000;
}
private ulong GetHeapRegionBaseAddr()
{
return GetMapRegionBaseAddr() + GetMapRegionSize();
}
private ulong GetHeapRegionSize()
{
return 0x40000000;
}
}
}

View file

@ -0,0 +1,85 @@
using ChocolArm64.State;
using Ryujinx.Core.OsHle.Handles;
namespace Ryujinx.Core.OsHle.Svc
{
partial class SvcHandler
{
private void SvcCreateThread(AThreadState ThreadState)
{
long EntryPoint = (long)ThreadState.X1;
long ArgsPtr = (long)ThreadState.X2;
long StackTop = (long)ThreadState.X3;
int Priority = (int)ThreadState.X4;
int ProcessorId = (int)ThreadState.X5;
if (Ns.Os.TryGetProcess(ThreadState.ProcessId, out Process Process))
{
if (ProcessorId == -2)
{
//TODO: Get this value from the NPDM file.
ProcessorId = 0;
}
int Handle = Process.MakeThread(
EntryPoint,
StackTop,
ArgsPtr,
Priority,
ProcessorId);
ThreadState.X0 = (int)SvcResult.Success;
ThreadState.X1 = (ulong)Handle;
}
//TODO: Error codes.
}
private void SvcStartThread(AThreadState ThreadState)
{
int Handle = (int)ThreadState.X0;
HThread Thread = Ns.Os.Handles.GetData<HThread>(Handle);
if (Thread != null)
{
Process.Scheduler.StartThread(Thread);
ThreadState.X0 = (int)SvcResult.Success;
}
//TODO: Error codes.
}
private void SvcSleepThread(AThreadState ThreadState)
{
ulong NanoSecs = ThreadState.X0;
HThread CurrThread = Process.GetThread(ThreadState.Tpidr);
if (NanoSecs == 0)
{
Process.Scheduler.Yield(CurrThread);
}
else
{
Process.Scheduler.WaitForSignal(CurrThread, (int)(NanoSecs / 1000000));
}
}
private void SvcGetThreadPriority(AThreadState ThreadState)
{
int Handle = (int)ThreadState.X1;
HThread Thread = Ns.Os.Handles.GetData<HThread>(Handle);
if (Thread != null)
{
ThreadState.X1 = (ulong)Thread.Priority;
ThreadState.X0 = (int)SvcResult.Success;
}
//TODO: Error codes.
}
}
}

View file

@ -0,0 +1,78 @@
using ChocolArm64.State;
using Ryujinx.Core.OsHle.Handles;
namespace Ryujinx.Core.OsHle.Svc
{
partial class SvcHandler
{
private void SvcArbitrateLock(AThreadState ThreadState)
{
int OwnerThreadHandle = (int)ThreadState.X0;
long MutexAddress = (long)ThreadState.X1;
int RequestingThreadHandle = (int)ThreadState.X2;
HThread RequestingThread = Ns.Os.Handles.GetData<HThread>(RequestingThreadHandle);
Mutex M = new Mutex(Process, MutexAddress, OwnerThreadHandle);
M = Ns.Os.Mutexes.GetOrAdd(MutexAddress, M);
M.WaitForLock(RequestingThread, RequestingThreadHandle);
ThreadState.X0 = (int)SvcResult.Success;
}
private void SvcArbitrateUnlock(AThreadState ThreadState)
{
long MutexAddress = (long)ThreadState.X0;
if (Ns.Os.Mutexes.TryGetValue(MutexAddress, out Mutex M))
{
M.Unlock();
}
ThreadState.X0 = (int)SvcResult.Success;
}
private void SvcWaitProcessWideKeyAtomic(AThreadState ThreadState)
{
long MutexAddress = (long)ThreadState.X0;
long CondVarAddress = (long)ThreadState.X1;
int ThreadHandle = (int)ThreadState.X2;
long Timeout = (long)ThreadState.X3;
HThread Thread = Ns.Os.Handles.GetData<HThread>(ThreadHandle);
Mutex M = new Mutex(Process, MutexAddress, ThreadHandle);
M = Ns.Os.Mutexes.GetOrAdd(MutexAddress, M);
M.GiveUpLock(ThreadHandle);
CondVar Cv = new CondVar(Process, CondVarAddress, Timeout);
Cv = Ns.Os.CondVars.GetOrAdd(CondVarAddress, Cv);
Cv.WaitForSignal(Thread);
M.WaitForLock(Thread, ThreadHandle);
ThreadState.X0 = (int)SvcResult.Success;
}
private void SvcSignalProcessWideKey(AThreadState ThreadState)
{
long CondVarAddress = (long)ThreadState.X0;
int Count = (int)ThreadState.X1;
HThread CurrThread = Process.GetThread(ThreadState.Tpidr);
if (Ns.Os.CondVars.TryGetValue(CondVarAddress, out CondVar Cv))
{
Cv.SetSignal(CurrThread, Count);
}
ThreadState.X0 = (int)SvcResult.Success;
}
}
}

View file

@ -0,0 +1,53 @@
using System.Collections.Generic;
namespace Ryujinx.Core.OsHle.Utilities
{
class IdPool
{
private HashSet<int> Ids;
private int CurrId;
private int MinId;
private int MaxId;
public IdPool(int Min, int Max)
{
Ids = new HashSet<int>();
CurrId = Min;
MinId = Min;
MaxId = Max;
}
public IdPool() : this(1, int.MaxValue) { }
public int GenerateId()
{
lock (Ids)
{
for (int Cnt = MinId; Cnt < MaxId; Cnt++)
{
if (Ids.Add(CurrId))
{
return CurrId;
}
if (CurrId++ == MaxId)
{
CurrId = MinId;
}
}
return -1;
}
}
public bool DeleteId(int Id)
{
lock (Ids)
{
return Ids.Remove(Id);
}
}
}
}

View file

@ -0,0 +1,78 @@
using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
namespace Ryujinx.Core.OsHle.Utilities
{
class IdPoolWithObj : IEnumerable<KeyValuePair<int, object>>
{
private IdPool Ids;
private ConcurrentDictionary<int, object> Objs;
public IdPoolWithObj()
{
Ids = new IdPool();
Objs = new ConcurrentDictionary<int, object>();
}
public int GenerateId(object Data)
{
int Id = Ids.GenerateId();
if (Id == -1 || !Objs.TryAdd(Id, Data))
{
throw new InvalidOperationException();
}
return Id;
}
public bool ReplaceData(int Id, object Data)
{
if (Objs.ContainsKey(Id))
{
Objs[Id] = Data;
return true;
}
return false;
}
public T GetData<T>(int Id)
{
if (Objs.TryGetValue(Id, out object Data) && Data is T)
{
return (T)Data;
}
return default(T);
}
public void Delete(int Id)
{
if (Objs.TryRemove(Id, out object Obj))
{
if (Obj is IDisposable DisposableObj)
{
DisposableObj.Dispose();
}
Ids.DeleteId(Id);
}
}
IEnumerator<KeyValuePair<int, object>> IEnumerable<KeyValuePair<int, object>>.GetEnumerator()
{
return Objs.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return Objs.GetEnumerator();
}
}
}

View file

@ -0,0 +1,44 @@
using ChocolArm64.Memory;
namespace Ryujinx.Core.OsHle.Utilities
{
class MemReader
{
private AMemory Memory;
public long Position { get; private set; }
public MemReader(AMemory Memory, long Position)
{
this.Memory = Memory;
this.Position = Position;
}
public byte ReadByte()
{
byte Value = Memory.ReadByte(Position);
Position++;
return Value;
}
public int ReadInt32()
{
int Value = Memory.ReadInt32(Position);
Position += 4;
return Value;
}
public long ReadInt64()
{
long Value = Memory.ReadInt64(Position);
Position += 8;
return Value;
}
}
}

View file

@ -0,0 +1,38 @@
using ChocolArm64.Memory;
namespace Ryujinx.Core.OsHle.Utilities
{
class MemWriter
{
private AMemory Memory;
public long Position { get; private set; }
public MemWriter(AMemory Memory, long Position)
{
this.Memory = Memory;
this.Position = Position;
}
public void WriteByte(byte Value)
{
Memory.WriteByte(Position, Value);
Position++;
}
public void WriteInt32(int Value)
{
Memory.WriteInt32(Position, Value);
Position += 4;
}
public void WriteInt64(long Value)
{
Memory.WriteInt64(Position, Value);
Position += 8;
}
}
}