Merge branch 'master' into ICommonStateGetter

This commit is contained in:
Lordmau5 2018-06-11 06:03:37 +02:00
commit c636c74dd2
248 changed files with 2266 additions and 2244 deletions

View file

@ -0,0 +1,62 @@
using Ryujinx.HLE.OsHle.Handles;
using Ryujinx.HLE.OsHle.Services.Am;
using System;
using System.Collections.Concurrent;
namespace Ryujinx.HLE.OsHle
{
class AppletStateMgr : IDisposable
{
private ConcurrentQueue<MessageInfo> Messages;
public FocusState FocusState { get; private set; }
public KEvent MessageEvent { get; private set; }
public AppletStateMgr()
{
Messages = new ConcurrentQueue<MessageInfo>();
MessageEvent = new KEvent();
}
public void SetFocus(bool IsFocused)
{
FocusState = IsFocused
? FocusState.InFocus
: FocusState.OutOfFocus;
EnqueueMessage(MessageInfo.FocusStateChanged);
}
public void EnqueueMessage(MessageInfo Message)
{
Messages.Enqueue(Message);
MessageEvent.WaitEvent.Set();
}
public bool TryDequeueMessage(out MessageInfo Message)
{
if (Messages.Count < 2)
{
MessageEvent.WaitEvent.Reset();
}
return Messages.TryDequeue(out Message);
}
public void Dispose()
{
Dispose(true);
}
protected virtual void Dispose(bool Disposing)
{
if (Disposing)
{
MessageEvent.Dispose();
}
}
}
}

View file

@ -0,0 +1,416 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace Ryujinx.HLE.OsHle.Diagnostics
{
static class Demangler
{
private static readonly Dictionary<string, string> BuiltinTypes = new Dictionary<string, string>
{
{ "v", "void" },
{ "w", "wchar_t" },
{ "b", "bool" },
{ "c", "char" },
{ "a", "signed char" },
{ "h", "unsigned char" },
{ "s", "short" },
{ "t", "unsigned short" },
{ "i", "int" },
{ "j", "unsigned int" },
{ "l", "long" },
{ "m", "unsigned long" },
{ "x", "long long" },
{ "y", "unsigned long long" },
{ "n", "__int128" },
{ "o", "unsigned __int128" },
{ "f", "float" },
{ "d", "double" },
{ "e", "long double" },
{ "g", "__float128" },
{ "z", "..." },
{ "Dd", "__iec559_double" },
{ "De", "__iec559_float128" },
{ "Df", "__iec559_float" },
{ "Dh", "__iec559_float16" },
{ "Di", "char32_t" },
{ "Ds", "char16_t" },
{ "Da", "decltype(auto)" },
{ "Dn", "std::nullptr_t" },
};
private static readonly Dictionary<string, string> SubstitutionExtra = new Dictionary<string, string>
{
{"Sa", "std::allocator"},
{"Sb", "std::basic_string"},
{"Ss", "std::basic_string<char, ::std::char_traits<char>, ::std::allocator<char>>"},
{"Si", "std::basic_istream<char, ::std::char_traits<char>>"},
{"So", "std::basic_ostream<char, ::std::char_traits<char>>"},
{"Sd", "std::basic_iostream<char, ::std::char_traits<char>>"}
};
private static int FromBase36(string encoded)
{
string base36 = "0123456789abcdefghijklmnopqrstuvwxyz";
char[] reversedEncoded = encoded.ToLower().ToCharArray().Reverse().ToArray();
int result = 0;
for (int i = 0; i < reversedEncoded.Length; i++)
{
char c = reversedEncoded[i];
int value = base36.IndexOf(c);
if (value == -1)
return -1;
result += value * (int)Math.Pow(36, i);
}
return result;
}
private static string GetCompressedValue(string compression, List<string> compressionData, out int pos)
{
string res = null;
bool canHaveUnqualifiedName = false;
pos = -1;
if (compressionData.Count == 0 || !compression.StartsWith("S"))
return null;
if (compression.Length >= 2 && SubstitutionExtra.TryGetValue(compression.Substring(0, 2), out string substitutionValue))
{
pos = 1;
res = substitutionValue;
compression = compression.Substring(2);
}
else if (compression.StartsWith("St"))
{
pos = 1;
canHaveUnqualifiedName = true;
res = "std";
compression = compression.Substring(2);
}
else if (compression.StartsWith("S_"))
{
pos = 1;
res = compressionData[0];
canHaveUnqualifiedName = true;
compression = compression.Substring(2);
}
else
{
int id = -1;
int underscorePos = compression.IndexOf('_');
if (underscorePos == -1)
return null;
string partialId = compression.Substring(1, underscorePos - 1);
id = FromBase36(partialId);
if (id == -1 || compressionData.Count <= (id + 1))
{
return null;
}
res = compressionData[id + 1];
pos = partialId.Length + 1;
canHaveUnqualifiedName= true;
compression = compression.Substring(pos);
}
if (res != null)
{
if (canHaveUnqualifiedName)
{
List<string> type = ReadName(compression, compressionData, out int endOfNameType);
if (endOfNameType != -1 && type != null)
{
pos += endOfNameType;
res = res + "::" + type[type.Count - 1];
}
}
}
return res;
}
private static List<string> ReadName(string mangled, List<string> compressionData, out int pos, bool isNested = true)
{
List<string> res = new List<string>();
string charCountString = null;
int charCount = 0;
int i;
pos = -1;
for (i = 0; i < mangled.Length; i++)
{
char chr = mangled[i];
if (charCountString == null)
{
if (ReadCVQualifiers(chr) != null)
{
continue;
}
if (chr == 'S')
{
string data = GetCompressedValue(mangled.Substring(i), compressionData, out pos);
if (pos == -1)
{
return null;
}
if (res.Count == 0)
res.Add(data);
else
res.Add(res[res.Count - 1] + "::" + data);
i += pos;
if (i < mangled.Length && mangled[i] == 'E')
{
break;
}
continue;
}
else if (chr == 'E')
{
break;
}
}
if (Char.IsDigit(chr))
{
charCountString += chr;
}
else
{
if (!int.TryParse(charCountString, out charCount))
{
return null;
}
string demangledPart = mangled.Substring(i, charCount);
if (res.Count == 0)
res.Add(demangledPart);
else
res.Add(res[res.Count - 1] + "::" + demangledPart);
i = i + charCount - 1;
charCount = 0;
charCountString = null;
if (!isNested)
break;
}
}
if (res.Count == 0)
{
return null;
}
pos = i;
return res;
}
private static string ReadBuiltinType(string mangledType, out int pos)
{
string res = null;
string possibleBuiltinType;
pos = -1;
possibleBuiltinType = mangledType[0].ToString();
if (!BuiltinTypes.TryGetValue(possibleBuiltinType, out res))
{
if (mangledType.Length >= 2)
{
// Try to match the first 2 chars if the first call failed
possibleBuiltinType = mangledType.Substring(0, 2);
BuiltinTypes.TryGetValue(possibleBuiltinType, out res);
}
}
if (res != null)
pos = possibleBuiltinType.Length;
return res;
}
private static string ReadCVQualifiers(char qualifier)
{
if (qualifier == 'r')
return "restricted";
else if (qualifier == 'V')
return "volatile";
else if (qualifier == 'K')
return "const";
return null;
}
private static string ReadRefQualifiers(char qualifier)
{
if (qualifier == 'R')
return "&";
else if (qualifier == 'O')
return "&&";
return null;
}
private static string ReadSpecialQualifiers(char qualifier)
{
if (qualifier == 'P')
return "*";
else if (qualifier == 'C')
return "complex";
else if (qualifier == 'G')
return "imaginary";
return null;
}
private static List<string> ReadParameters(string mangledParams, List<string> compressionData, out int pos)
{
List<string> res = new List<string>();
List<string> refQualifiers = new List<string>();
string parsedTypePart = null;
string currentRefQualifiers = null;
string currentBuiltinType = null;
string currentSpecialQualifiers = null;
string currentCompressedValue = null;
int i = 0;
pos = -1;
for (i = 0; i < mangledParams.Length; i++)
{
if (currentBuiltinType != null)
{
string currentCVQualifier = String.Join(" ", refQualifiers);
// Try to mimic the compression indexing
if (currentRefQualifiers != null)
{
compressionData.Add(currentBuiltinType + currentRefQualifiers);
}
if (refQualifiers.Count != 0)
{
compressionData.Add(currentBuiltinType + " " + currentCVQualifier + currentRefQualifiers);
}
if (currentSpecialQualifiers != null)
{
compressionData.Add(currentBuiltinType + " " + currentCVQualifier + currentRefQualifiers + currentSpecialQualifiers);
}
if (currentRefQualifiers == null && currentCVQualifier == null && currentSpecialQualifiers == null)
{
compressionData.Add(currentBuiltinType);
}
currentBuiltinType = null;
currentCompressedValue = null;
currentCVQualifier = null;
currentRefQualifiers = null;
refQualifiers.Clear();
currentSpecialQualifiers = null;
}
char chr = mangledParams[i];
string part = mangledParams.Substring(i);
// Try to read qualifiers
parsedTypePart = ReadCVQualifiers(chr);
if (parsedTypePart != null)
{
refQualifiers.Add(parsedTypePart);
// need more data
continue;
}
parsedTypePart = ReadRefQualifiers(chr);
if (parsedTypePart != null)
{
currentRefQualifiers = parsedTypePart;
// need more data
continue;
}
parsedTypePart = ReadSpecialQualifiers(chr);
if (parsedTypePart != null)
{
currentSpecialQualifiers = parsedTypePart;
// need more data
continue;
}
// TODO: extended-qualifier?
if (part.StartsWith("S"))
{
parsedTypePart = GetCompressedValue(part, compressionData, out pos);
if (pos != -1 && parsedTypePart != null)
{
currentCompressedValue = parsedTypePart;
i += pos;
res.Add(currentCompressedValue + " " + String.Join(" ", refQualifiers) + currentRefQualifiers + currentSpecialQualifiers);
currentBuiltinType = null;
currentCompressedValue = null;
currentRefQualifiers = null;
refQualifiers.Clear();
currentSpecialQualifiers = null;
continue;
}
pos = -1;
return null;
}
else if (part.StartsWith("N"))
{
part = part.Substring(1);
List<string> name = ReadName(part, compressionData, out pos);
if (pos != -1 && name != null)
{
i += pos + 1;
res.Add(name[name.Count - 1] + " " + String.Join(" ", refQualifiers) + currentRefQualifiers + currentSpecialQualifiers);
currentBuiltinType = null;
currentCompressedValue = null;
currentRefQualifiers = null;
refQualifiers.Clear();
currentSpecialQualifiers = null;
continue;
}
}
// Try builting
parsedTypePart = ReadBuiltinType(part, out pos);
if (pos == -1)
{
return null;
}
currentBuiltinType = parsedTypePart;
res.Add(currentBuiltinType + " " + String.Join(" ", refQualifiers) + currentRefQualifiers + currentSpecialQualifiers);
i = i + pos -1;
}
pos = i;
return res;
}
private static string ParseFunctionName(string mangled)
{
List<string> compressionData = new List<string>();
int pos = 0;
string res;
bool isNested = mangled.StartsWith("N");
// If it's start with "N" it must be a nested function name
if (isNested)
mangled = mangled.Substring(1);
compressionData = ReadName(mangled, compressionData, out pos, isNested);
if (pos == -1)
return null;
res = compressionData[compressionData.Count - 1];
compressionData.Remove(res);
mangled = mangled.Substring(pos + 1);
// more data? maybe not a data name so...
if (mangled != String.Empty)
{
List<string> parameters = ReadParameters(mangled, compressionData, out pos);
// parameters parsing error, we return the original data to avoid information loss.
if (pos == -1)
return null;
parameters = parameters.Select(outer => outer.Trim()).ToList();
res += "(" + String.Join(", ", parameters) + ")";
}
return res;
}
public static string Parse(string originalMangled)
{
if (originalMangled.StartsWith("_Z"))
{
// We assume that we have a name (TOOD: support special names)
string res = ParseFunctionName(originalMangled.Substring(2));
if (res == null)
return originalMangled;
return res;
}
return originalMangled;
}
}
}

View file

@ -0,0 +1,10 @@
namespace Ryujinx.HLE.OsHle
{
static class ErrorCode
{
public static uint MakeError(ErrorModule Module, int Code)
{
return (uint)Module | ((uint)Code << 9);
}
}
}

View file

@ -0,0 +1,101 @@
namespace Ryujinx.HLE.OsHle
{
enum ErrorModule
{
Kernel = 1,
Fs = 2,
Os = 3, // (Memory, Thread, Mutex, NVIDIA)
Htcs = 4,
Ncm = 5,
Dd = 6,
Debug_Monitor = 7,
Lr = 8,
Loader = 9,
IPC_Command_Interface = 10,
IPC = 11,
Pm = 15,
Ns = 16,
Socket = 17,
Htc = 18,
Ncm_Content = 20,
Sm = 21,
RO_Userland = 22,
SdMmc = 24,
Ovln = 25,
Spl = 26,
Ethc = 100,
I2C = 101,
Gpio = 102,
Uart = 103,
Settings = 105,
Wlan = 107,
Xcd = 108,
Nifm = 110,
Hwopus = 111,
Bluetooth = 113,
Vi = 114,
Nfp = 115,
Time = 116,
Fgm = 117,
Oe = 118,
Pcie = 120,
Friends = 121,
Bcat = 122,
SSL = 123,
Account = 124,
News = 125,
Mii = 126,
Nfc = 127,
Am = 128,
Play_Report = 129,
Ahid = 130,
Qlaunch = 132,
Pcv = 133,
Omm = 134,
Bpc = 135,
Psm = 136,
Nim = 137,
Psc = 138,
Tc = 139,
Usb = 140,
Nsd = 141,
Pctl = 142,
Btm = 143,
Ec = 144,
ETicket = 145,
Ngc = 146,
Error_Report = 147,
Apm = 148,
Profiler = 150,
Error_Upload = 151,
Audio = 153,
Npns = 154,
Npns_Http_Stream = 155,
Arp = 157,
Swkbd = 158,
Boot = 159,
Nfc_Mifare = 161,
Userland_Assert = 162,
Fatal = 163,
Nim_Shop = 164,
Spsm = 165,
Bgtc = 167,
Userland_Crash = 168,
SRepo = 180,
Dauth = 181,
Hid = 202,
Ldn = 203,
Irsensor = 205,
Capture = 206,
Manu = 208,
Atk = 209,
Web = 210,
Grc = 212,
Migration = 216,
Migration_Ldc_Server = 217,
General_Web_Applet = 800,
Wifi_Web_Auth_Applet = 809,
Whitelisted_Applet = 810,
ShopN = 811
}
}

View file

@ -0,0 +1,11 @@
using System;
namespace Ryujinx.HLE.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.HLE.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,69 @@
using System.Collections.Concurrent;
using System.Collections.Generic;
namespace Ryujinx.HLE.OsHle
{
class GlobalStateTable
{
private ConcurrentDictionary<Process, IdDictionary> DictByProcess;
public GlobalStateTable()
{
DictByProcess = new ConcurrentDictionary<Process, IdDictionary>();
}
public bool Add(Process Process, int Id, object Data)
{
IdDictionary Dict = DictByProcess.GetOrAdd(Process, (Key) => new IdDictionary());
return Dict.Add(Id, Data);
}
public int Add(Process Process, object Data)
{
IdDictionary Dict = DictByProcess.GetOrAdd(Process, (Key) => new IdDictionary());
return Dict.Add(Data);
}
public object GetData(Process Process, int Id)
{
if (DictByProcess.TryGetValue(Process, out IdDictionary Dict))
{
return Dict.GetData(Id);
}
return null;
}
public T GetData<T>(Process Process, int Id)
{
if (DictByProcess.TryGetValue(Process, out IdDictionary Dict))
{
return Dict.GetData<T>(Id);
}
return default(T);
}
public object Delete(Process Process, int Id)
{
if (DictByProcess.TryGetValue(Process, out IdDictionary Dict))
{
return Dict.Delete(Id);
}
return null;
}
public ICollection<object> DeleteProcess(Process Process)
{
if (DictByProcess.TryRemove(Process, out IdDictionary Dict))
{
return Dict.Clear();
}
return null;
}
}
}

View file

@ -0,0 +1,44 @@
using ChocolArm64.Memory;
using System;
using System.Collections.Generic;
namespace Ryujinx.HLE.OsHle.Handles
{
class HSharedMem
{
private List<(AMemory, long)> Positions;
public EventHandler<EventArgs> MemoryMapped;
public EventHandler<EventArgs> MemoryUnmapped;
public HSharedMem()
{
Positions = new List<(AMemory, long)>();
}
public void AddVirtualPosition(AMemory Memory, long Position)
{
lock (Positions)
{
Positions.Add((Memory, Position));
MemoryMapped?.Invoke(this, EventArgs.Empty);
}
}
public void RemoveVirtualPosition(AMemory Memory, long Position)
{
lock (Positions)
{
Positions.Remove((Memory, Position));
MemoryUnmapped?.Invoke(this, EventArgs.Empty);
}
}
public (AMemory, long)[] GetVirtualPositions()
{
return Positions.ToArray();
}
}
}

View file

@ -0,0 +1,21 @@
using ChocolArm64.Memory;
namespace Ryujinx.HLE.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,4 @@
namespace Ryujinx.HLE.OsHle.Handles
{
class KEvent : KSynchronizationObject { }
}

View file

@ -0,0 +1,34 @@
using System.Collections.Generic;
namespace Ryujinx.HLE.OsHle.Handles
{
class KProcessHandleTable
{
private IdDictionary Handles;
public KProcessHandleTable()
{
Handles = new IdDictionary();
}
public int OpenHandle(object Obj)
{
return Handles.Add(Obj);
}
public T GetData<T>(int Handle)
{
return Handles.GetData<T>(Handle);
}
public object CloseHandle(int Handle)
{
return Handles.Delete(Handle);
}
public ICollection<object> Clear()
{
return Handles.Clear();
}
}
}

View file

@ -0,0 +1,351 @@
using Ryujinx.HLE.Logging;
using System;
using System.Collections.Concurrent;
using System.Threading;
namespace Ryujinx.HLE.OsHle.Handles
{
class KProcessScheduler : IDisposable
{
private ConcurrentDictionary<KThread, SchedulerThread> AllThreads;
private ThreadQueue WaitingToRun;
private KThread[] CoreThreads;
private bool[] CoreReschedule;
private object SchedLock;
private Logger Log;
public KProcessScheduler(Logger Log)
{
this.Log = Log;
AllThreads = new ConcurrentDictionary<KThread, SchedulerThread>();
WaitingToRun = new ThreadQueue();
CoreThreads = new KThread[4];
CoreReschedule = new bool[4];
SchedLock = new object();
}
public void StartThread(KThread Thread)
{
lock (SchedLock)
{
SchedulerThread SchedThread = new SchedulerThread(Thread);
if (!AllThreads.TryAdd(Thread, SchedThread))
{
return;
}
if (TryAddToCore(Thread))
{
Thread.Thread.Execute();
PrintDbgThreadInfo(Thread, "running.");
}
else
{
WaitingToRun.Push(SchedThread);
PrintDbgThreadInfo(Thread, "waiting to run.");
}
}
}
public void RemoveThread(KThread Thread)
{
PrintDbgThreadInfo(Thread, "exited.");
lock (SchedLock)
{
if (AllThreads.TryRemove(Thread, out SchedulerThread SchedThread))
{
WaitingToRun.Remove(SchedThread);
SchedThread.Dispose();
}
int ActualCore = Thread.ActualCore;
SchedulerThread NewThread = WaitingToRun.Pop(ActualCore);
if (NewThread == null)
{
Log.PrintDebug(LogClass.KernelScheduler, $"Nothing to run on core {ActualCore}!");
CoreThreads[ActualCore] = null;
return;
}
NewThread.Thread.ActualCore = ActualCore;
RunThread(NewThread);
}
}
public void SetThreadActivity(KThread Thread, bool Active)
{
if (!AllThreads.TryGetValue(Thread, out SchedulerThread SchedThread))
{
throw new InvalidOperationException();
}
SchedThread.IsActive = Active;
if (Active)
{
SchedThread.WaitActivity.Set();
}
else
{
SchedThread.WaitActivity.Reset();
}
}
public void EnterWait(KThread Thread, int TimeoutMs = Timeout.Infinite)
{
SchedulerThread SchedThread = AllThreads[Thread];
Suspend(Thread);
SchedThread.WaitSync.WaitOne(TimeoutMs);
TryResumingExecution(SchedThread);
}
public void WakeUp(KThread Thread)
{
AllThreads[Thread].WaitSync.Set();
}
public void TryToRun(KThread Thread)
{
lock (SchedLock)
{
if (AllThreads.TryGetValue(Thread, out SchedulerThread SchedThread))
{
if (WaitingToRun.HasThread(SchedThread) && TryAddToCore(Thread))
{
RunThread(SchedThread);
}
else
{
SetReschedule(Thread.ProcessorId);
}
}
}
}
public void Suspend(KThread Thread)
{
lock (SchedLock)
{
PrintDbgThreadInfo(Thread, "suspended.");
int ActualCore = Thread.ActualCore;
CoreReschedule[ActualCore] = false;
SchedulerThread SchedThread = WaitingToRun.Pop(ActualCore);
if (SchedThread != null)
{
SchedThread.Thread.ActualCore = ActualCore;
CoreThreads[ActualCore] = SchedThread.Thread;
RunThread(SchedThread);
}
else
{
Log.PrintDebug(LogClass.KernelScheduler, $"Nothing to run on core {Thread.ActualCore}!");
CoreThreads[ActualCore] = null;
}
}
}
public void SetReschedule(int Core)
{
lock (SchedLock)
{
CoreReschedule[Core] = true;
}
}
public void Reschedule(KThread Thread)
{
bool NeedsReschedule;
lock (SchedLock)
{
int ActualCore = Thread.ActualCore;
NeedsReschedule = CoreReschedule[ActualCore];
CoreReschedule[ActualCore] = false;
}
if (NeedsReschedule)
{
Yield(Thread, Thread.ActualPriority - 1);
}
}
public void Yield(KThread Thread)
{
Yield(Thread, Thread.ActualPriority);
}
private void Yield(KThread Thread, int MinPriority)
{
PrintDbgThreadInfo(Thread, "yielded execution.");
lock (SchedLock)
{
int ActualCore = Thread.ActualCore;
SchedulerThread NewThread = WaitingToRun.Pop(ActualCore, MinPriority);
if (NewThread == null)
{
PrintDbgThreadInfo(Thread, "resumed because theres nothing better to run.");
return;
}
NewThread.Thread.ActualCore = ActualCore;
CoreThreads[ActualCore] = NewThread.Thread;
RunThread(NewThread);
}
Resume(Thread);
}
public void Resume(KThread Thread)
{
TryResumingExecution(AllThreads[Thread]);
}
private void TryResumingExecution(SchedulerThread SchedThread)
{
KThread Thread = SchedThread.Thread;
PrintDbgThreadInfo(Thread, "trying to resume...");
SchedThread.WaitActivity.WaitOne();
lock (SchedLock)
{
if (TryAddToCore(Thread))
{
PrintDbgThreadInfo(Thread, "resuming execution...");
return;
}
WaitingToRun.Push(SchedThread);
SetReschedule(Thread.ProcessorId);
PrintDbgThreadInfo(Thread, "entering wait state...");
}
SchedThread.WaitSched.WaitOne();
PrintDbgThreadInfo(Thread, "resuming execution...");
}
private void RunThread(SchedulerThread SchedThread)
{
if (!SchedThread.Thread.Thread.Execute())
{
PrintDbgThreadInfo(SchedThread.Thread, "waked.");
SchedThread.WaitSched.Set();
}
else
{
PrintDbgThreadInfo(SchedThread.Thread, "running.");
}
}
public void Resort(KThread Thread)
{
if (AllThreads.TryGetValue(Thread, out SchedulerThread SchedThread))
{
WaitingToRun.Resort(SchedThread);
}
}
private bool TryAddToCore(KThread Thread)
{
//First, try running it on Ideal Core.
int IdealCore = Thread.IdealCore;
if (IdealCore != -1 && CoreThreads[IdealCore] == null)
{
Thread.ActualCore = IdealCore;
CoreThreads[IdealCore] = Thread;
return true;
}
//If that fails, then try running on any core allowed by Core Mask.
int CoreMask = Thread.CoreMask;
for (int Core = 0; Core < CoreThreads.Length; Core++, CoreMask >>= 1)
{
if ((CoreMask & 1) != 0 && CoreThreads[Core] == null)
{
Thread.ActualCore = Core;
CoreThreads[Core] = Thread;
return true;
}
}
return false;
}
private void PrintDbgThreadInfo(KThread Thread, string Message)
{
Log.PrintDebug(LogClass.KernelScheduler, "(" +
"ThreadId = " + Thread.ThreadId + ", " +
"CoreMask = 0x" + Thread.CoreMask.ToString("x1") + ", " +
"ActualCore = " + Thread.ActualCore + ", " +
"IdealCore = " + Thread.IdealCore + ", " +
"ActualPriority = " + Thread.ActualPriority + ", " +
"WantedPriority = " + Thread.WantedPriority + ") " + Message);
}
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,31 @@
using Ryujinx.HLE.OsHle.Services;
using System;
namespace Ryujinx.HLE.OsHle.Handles
{
class KSession : IDisposable
{
public IpcService Service { get; private set; }
public string ServiceName { get; private set; }
public KSession(IpcService Service, string ServiceName)
{
this.Service = Service;
this.ServiceName = ServiceName;
}
public void Dispose()
{
Dispose(true);
}
protected virtual void Dispose(bool Disposing)
{
if (Disposing && Service is IDisposable DisposableService)
{
DisposableService.Dispose();
}
}
}
}

View file

@ -0,0 +1,28 @@
using System;
using System.Threading;
namespace Ryujinx.HLE.OsHle.Handles
{
class KSynchronizationObject : IDisposable
{
public ManualResetEvent WaitEvent { get; private set; }
public KSynchronizationObject()
{
WaitEvent = new ManualResetEvent(false);
}
public void Dispose()
{
Dispose(true);
}
protected virtual void Dispose(bool Disposing)
{
if (Disposing)
{
WaitEvent.Dispose();
}
}
}
}

View file

@ -0,0 +1,86 @@
using ChocolArm64;
using System.Collections.Generic;
namespace Ryujinx.HLE.OsHle.Handles
{
class KThread : KSynchronizationObject
{
public AThread Thread { get; private set; }
public int CoreMask { get; set; }
public long MutexAddress { get; set; }
public long CondVarAddress { get; set; }
public bool CondVarSignaled { get; set; }
private Process Process;
public List<KThread> MutexWaiters { get; private set; }
public KThread MutexOwner { get; set; }
public int ActualPriority { get; private set; }
public int WantedPriority { get; private set; }
public int ActualCore { get; set; }
public int ProcessorId { get; set; }
public int IdealCore { get; set; }
public int WaitHandle { get; set; }
public int ThreadId => Thread.ThreadId;
public KThread(
AThread Thread,
Process Process,
int ProcessorId,
int Priority)
{
this.Thread = Thread;
this.Process = Process;
this.ProcessorId = ProcessorId;
this.IdealCore = ProcessorId;
MutexWaiters = new List<KThread>();
CoreMask = 1 << ProcessorId;
ActualPriority = WantedPriority = Priority;
}
public void SetPriority(int Priority)
{
WantedPriority = Priority;
UpdatePriority();
}
public void UpdatePriority()
{
int OldPriority = ActualPriority;
int CurrPriority = WantedPriority;
lock (Process.ThreadSyncLock)
{
foreach (KThread Thread in MutexWaiters)
{
if (CurrPriority > Thread.WantedPriority)
{
CurrPriority = Thread.WantedPriority;
}
}
}
if (CurrPriority != OldPriority)
{
ActualPriority = CurrPriority;
Process.Scheduler.Resort(this);
MutexOwner?.UpdatePriority();
}
}
}
}

View file

@ -0,0 +1,48 @@
using System;
using System.Threading;
namespace Ryujinx.HLE.OsHle.Handles
{
class SchedulerThread : IDisposable
{
public KThread Thread { get; private set; }
public SchedulerThread Next { get; set; }
public bool IsActive { get; set; }
public AutoResetEvent WaitSync { get; private set; }
public ManualResetEvent WaitActivity { get; private set; }
public AutoResetEvent WaitSched { get; private set; }
public SchedulerThread(KThread Thread)
{
this.Thread = Thread;
IsActive = true;
WaitSync = new AutoResetEvent(false);
WaitActivity = new ManualResetEvent(true);
WaitSched = new AutoResetEvent(false);
}
public void Dispose()
{
Dispose(true);
}
protected virtual void Dispose(bool Disposing)
{
if (Disposing)
{
WaitSync.Dispose();
WaitActivity.Dispose();
WaitSched.Dispose();
}
}
}
}

View file

@ -0,0 +1,158 @@
namespace Ryujinx.HLE.OsHle.Handles
{
class ThreadQueue
{
private const int LowestPriority = 0x3f;
private SchedulerThread Head;
private object ListLock;
public ThreadQueue()
{
ListLock = new object();
}
public void Push(SchedulerThread Wait)
{
lock (ListLock)
{
//Ensure that we're not creating circular references
//by adding a thread that is already on the list.
if (HasThread(Wait))
{
return;
}
if (Head == null || Head.Thread.ActualPriority > Wait.Thread.ActualPriority)
{
Wait.Next = Head;
Head = Wait;
return;
}
SchedulerThread Curr = Head;
while (Curr.Next != null)
{
if (Curr.Next.Thread.ActualPriority > Wait.Thread.ActualPriority)
{
break;
}
Curr = Curr.Next;
}
Wait.Next = Curr.Next;
Curr.Next = Wait;
}
}
public SchedulerThread Pop(int Core, int MinPriority = LowestPriority)
{
lock (ListLock)
{
int CoreMask = 1 << Core;
SchedulerThread Prev = null;
SchedulerThread Curr = Head;
while (Curr != null)
{
KThread Thread = Curr.Thread;
if (Thread.ActualPriority <= MinPriority && (Thread.CoreMask & CoreMask) != 0)
{
if (Prev != null)
{
Prev.Next = Curr.Next;
}
else
{
Head = Head.Next;
}
break;
}
Prev = Curr;
Curr = Curr.Next;
}
return Curr;
}
}
public bool Remove(SchedulerThread Thread)
{
lock (ListLock)
{
if (Head == null)
{
return false;
}
else if (Head == Thread)
{
Head = Head.Next;
return true;
}
SchedulerThread Prev = Head;
SchedulerThread Curr = Head.Next;
while (Curr != null)
{
if (Curr == Thread)
{
Prev.Next = Curr.Next;
return true;
}
Prev = Curr;
Curr = Curr.Next;
}
return false;
}
}
public bool Resort(SchedulerThread Thread)
{
lock (ListLock)
{
if (Remove(Thread))
{
Push(Thread);
return true;
}
return false;
}
}
public bool HasThread(SchedulerThread Thread)
{
lock (ListLock)
{
SchedulerThread Curr = Head;
while (Curr != null)
{
if (Curr == Thread)
{
return true;
}
Curr = Curr.Next;
}
return false;
}
}
}
}

View file

@ -0,0 +1,69 @@
using ChocolArm64.Memory;
namespace Ryujinx.HLE.OsHle
{
static class Homebrew
{
//http://switchbrew.org/index.php?title=Homebrew_ABI
public static void WriteHbAbiData(AMemory Memory, long Position, int MainThreadHandle)
{
Memory.Manager.Map(Position, AMemoryMgr.PageSize, (int)MemoryType.Normal, AMemoryPerm.RW);
//MainThreadHandle
WriteConfigEntry(Memory, ref Position, 1, 0, MainThreadHandle);
//NextLoadPath
WriteConfigEntry(Memory, ref Position, 2, 0, Position + 0x200, Position + 0x400);
//AppletType
WriteConfigEntry(Memory, ref Position, 7);
//EndOfList
WriteConfigEntry(Memory, ref Position, 0);
}
private static void WriteConfigEntry(
AMemory Memory,
ref long Position,
int Key,
int Flags = 0,
long Value0 = 0,
long Value1 = 0)
{
Memory.WriteInt32(Position + 0x00, Key);
Memory.WriteInt32(Position + 0x04, Flags);
Memory.WriteInt64(Position + 0x08, Value0);
Memory.WriteInt64(Position + 0x10, Value1);
Position += 0x18;
}
public static string ReadHbAbiNextLoadPath(AMemory Memory, long Position)
{
string FileName = null;
while (true)
{
long Key = Memory.ReadInt64(Position);
if (Key == 2)
{
long Value0 = Memory.ReadInt64(Position + 0x08);
long Value1 = Memory.ReadInt64(Position + 0x10);
FileName = AMemoryHelper.ReadAsciiString(Memory, Value0, Value1 - Value0);
break;
}
else if (Key == 0)
{
break;
}
Position += 0x18;
}
return FileName;
}
}
}

View file

@ -0,0 +1,200 @@
using Ryujinx.HLE.Loaders.Executables;
using Ryujinx.HLE.Logging;
using Ryujinx.HLE.OsHle.Handles;
using System;
using System.Collections.Concurrent;
using System.IO;
namespace Ryujinx.HLE.OsHle
{
public class Horizon : IDisposable
{
internal const int HidSize = 0x40000;
internal const int FontSize = 0x50;
private Switch Ns;
private KProcessScheduler Scheduler;
private ConcurrentDictionary<int, Process> Processes;
public SystemStateMgr SystemState { get; private set; }
internal MemoryAllocator Allocator { get; private set; }
internal HSharedMem HidSharedMem { get; private set; }
internal HSharedMem FontSharedMem { get; private set; }
internal KEvent VsyncEvent { get; private set; }
public Horizon(Switch Ns)
{
this.Ns = Ns;
Scheduler = new KProcessScheduler(Ns.Log);
Processes = new ConcurrentDictionary<int, Process>();
SystemState = new SystemStateMgr();
Allocator = new MemoryAllocator();
HidSharedMem = new HSharedMem();
FontSharedMem = new HSharedMem();
VsyncEvent = new KEvent();
}
public void LoadCart(string ExeFsDir, string RomFsFile = null)
{
if (RomFsFile != null)
{
Ns.VFs.LoadRomFs(RomFsFile);
}
Process MainProcess = MakeProcess();
void LoadNso(string FileName)
{
foreach (string File in Directory.GetFiles(ExeFsDir, FileName))
{
if (Path.GetExtension(File) != string.Empty)
{
continue;
}
Ns.Log.PrintInfo(LogClass.Loader, $"Loading {Path.GetFileNameWithoutExtension(File)}...");
using (FileStream Input = new FileStream(File, FileMode.Open))
{
string Name = Path.GetFileNameWithoutExtension(File);
Nso Program = new Nso(Input, Name);
MainProcess.LoadProgram(Program);
}
}
}
LoadNso("rtld");
MainProcess.SetEmptyArgs();
LoadNso("main");
LoadNso("subsdk*");
LoadNso("sdk");
MainProcess.Run();
}
public void LoadProgram(string FileName)
{
bool IsNro = Path.GetExtension(FileName).ToLower() == ".nro";
string Name = Path.GetFileNameWithoutExtension(FileName);
Process MainProcess = MakeProcess();
using (FileStream Input = new FileStream(FileName, FileMode.Open))
{
MainProcess.LoadProgram(IsNro
? (IExecutable)new Nro(Input, Name)
: (IExecutable)new Nso(Input, Name));
}
MainProcess.SetEmptyArgs();
MainProcess.Run(IsNro);
}
public void SignalVsync() => VsyncEvent.WaitEvent.Set();
private Process MakeProcess()
{
Process Process;
lock (Processes)
{
int ProcessId = 0;
while (Processes.ContainsKey(ProcessId))
{
ProcessId++;
}
Process = new Process(Ns, Scheduler, ProcessId);
Processes.TryAdd(ProcessId, Process);
}
InitializeProcess(Process);
return Process;
}
private void InitializeProcess(Process Process)
{
Process.AppletState.SetFocus(true);
}
internal void ExitProcess(int ProcessId)
{
if (Processes.TryGetValue(ProcessId, out Process Process) && Process.NeedsHbAbi)
{
string NextNro = Homebrew.ReadHbAbiNextLoadPath(Process.Memory, Process.HbAbiDataPosition);
Ns.Log.PrintInfo(LogClass.Loader, $"HbAbi NextLoadPath {NextNro}");
if (NextNro == string.Empty)
{
NextNro = "sdmc:/hbmenu.nro";
}
NextNro = NextNro.Replace("sdmc:", string.Empty);
NextNro = Ns.VFs.GetFullPath(Ns.VFs.GetSdCardPath(), NextNro);
if (File.Exists(NextNro))
{
LoadProgram(NextNro);
}
}
if (Processes.TryRemove(ProcessId, out Process))
{
Process.StopAllThreadsAsync();
Process.Dispose();
if (Processes.Count == 0)
{
Ns.OnFinish(EventArgs.Empty);
}
}
}
internal bool TryGetProcess(int ProcessId, out Process Process)
{
return Processes.TryGetValue(ProcessId, out Process);
}
public void Dispose()
{
Dispose(true);
}
protected virtual void Dispose(bool Disposing)
{
if (Disposing)
{
foreach (Process Process in Processes.Values)
{
Process.StopAllThreadsAsync();
Process.Dispose();
}
VsyncEvent.Dispose();
Scheduler.Dispose();
}
}
}
}

View file

@ -0,0 +1,87 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
namespace Ryujinx.HLE.OsHle
{
class IdDictionary
{
private ConcurrentDictionary<int, object> Objs;
private int FreeIdHint = 1;
public IdDictionary()
{
Objs = new ConcurrentDictionary<int, object>();
}
public bool Add(int Id, object Data)
{
return Objs.TryAdd(Id, Data);
}
public int Add(object Data)
{
if (Objs.TryAdd(FreeIdHint, Data))
{
return FreeIdHint++;
}
return AddSlow(Data);
}
private int AddSlow(object Data)
{
for (int Id = 1; Id < int.MaxValue; Id++)
{
if (Objs.TryAdd(Id, Data))
{
return Id;
}
}
throw new InvalidOperationException();
}
public object GetData(int Id)
{
if (Objs.TryGetValue(Id, out object Data))
{
return Data;
}
return null;
}
public T GetData<T>(int Id)
{
if (Objs.TryGetValue(Id, out object Data) && Data is T)
{
return (T)Data;
}
return default(T);
}
public object Delete(int Id)
{
if (Objs.TryRemove(Id, out object Obj))
{
FreeIdHint = Id;
return Obj;
}
return null;
}
public ICollection<object> Clear()
{
ICollection<object> Values = Objs.Values;
Objs.Clear();
return Values;
}
}
}

View file

@ -0,0 +1,27 @@
using System.IO;
namespace Ryujinx.HLE.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,90 @@
using System;
using System.IO;
namespace Ryujinx.HLE.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,138 @@
using ChocolArm64.Memory;
using Ryujinx.HLE.OsHle.Handles;
using System;
using System.IO;
namespace Ryujinx.HLE.OsHle.Ipc
{
static class IpcHandler
{
public static long IpcCall(
Switch Ns,
Process Process,
AMemory Memory,
KSession Session,
IpcMessage Request,
long CmdPtr)
{
IpcMessage Response = new IpcMessage();
using (MemoryStream Raw = new MemoryStream(Request.RawData))
{
BinaryReader ReqReader = new BinaryReader(Raw);
if (Request.Type == IpcMessageType.Request)
{
Response.Type = IpcMessageType.Response;
using (MemoryStream ResMS = new MemoryStream())
{
BinaryWriter ResWriter = new BinaryWriter(ResMS);
ServiceCtx Context = new ServiceCtx(
Ns,
Process,
Memory,
Session,
Request,
Response,
ReqReader,
ResWriter);
Session.Service.CallMethod(Context);
Response.RawData = ResMS.ToArray();
}
}
else if (Request.Type == IpcMessageType.Control)
{
long Magic = ReqReader.ReadInt64();
long CmdId = ReqReader.ReadInt64();
switch (CmdId)
{
case 0:
{
Request = FillResponse(Response, 0, Session.Service.ConvertToDomain());
break;
}
case 3:
{
Request = FillResponse(Response, 0, 0x500);
break;
}
//TODO: Whats the difference between IpcDuplicateSession/Ex?
case 2:
case 4:
{
int Unknown = ReqReader.ReadInt32();
int Handle = Process.HandleTable.OpenHandle(Session);
Response.HandleDesc = IpcHandleDesc.MakeMove(Handle);
Request = FillResponse(Response, 0);
break;
}
default: throw new NotImplementedException(CmdId.ToString());
}
}
else if (Request.Type == IpcMessageType.CloseSession)
{
//TODO
}
else
{
throw new NotImplementedException(Request.Type.ToString());
}
Memory.WriteBytes(CmdPtr, Response.GetBytes(CmdPtr));
}
return 0;
}
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(IpcMagic.Sfco);
Writer.Write(Result);
if (Data != null)
{
Writer.Write(Data);
}
Response.RawData = MS.ToArray();
}
return Response;
}
}
}

View file

@ -0,0 +1,8 @@
namespace Ryujinx.HLE.OsHle.Ipc
{
abstract class IpcMagic
{
public const long Sfci = 'S' << 0 | 'F' << 8 | 'C' << 16 | 'I' << 24;
public const long Sfco = 'S' << 0 | 'F' << 8 | 'C' << 16 | 'O' << 24;
}
}

View file

@ -0,0 +1,215 @@
using System.Collections.Generic;
using System.IO;
namespace Ryujinx.HLE.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 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(byte[] Data, long CmdPtr) : this()
{
using (MemoryStream MS = new MemoryStream(Data))
{
BinaryReader Reader = new BinaryReader(MS);
Initialize(Reader, CmdPtr);
}
}
private void Initialize(BinaryReader Reader, long CmdPtr)
{
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;
}
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)
{
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) / 4;
Word1 = DataLength & 0x3ff;
if (HandleDesc != null)
{
Word1 |= 1 << 31;
}
Writer.Write(Word0);
Writer.Write(Word1);
Writer.Write(HandleData);
MS.Seek(Pad0, SeekOrigin.Current);
if (RawData != null)
{
Writer.Write(RawData);
}
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 Position, long Size) GetBufferType0x21()
{
if (PtrBuff.Count != 0 &&
PtrBuff[0].Position != 0 &&
PtrBuff[0].Size != 0)
{
return (PtrBuff[0].Position, PtrBuff[0].Size);
}
if (SendBuff.Count != 0 &&
SendBuff[0].Position != 0 &&
SendBuff[0].Size != 0)
{
return (SendBuff[0].Position, SendBuff[0].Size);
}
return (0, 0);
}
public (long Position, long Size) GetBufferType0x22()
{
if (RecvListBuff.Count != 0 &&
RecvListBuff[0].Position != 0 &&
RecvListBuff[0].Size != 0)
{
return (RecvListBuff[0].Position, RecvListBuff[0].Size);
}
if (ReceiveBuff.Count != 0 &&
ReceiveBuff[0].Position != 0 &&
ReceiveBuff[0].Size != 0)
{
return (ReceiveBuff[0].Position, ReceiveBuff[0].Size);
}
return (0, 0);
}
}
}

View file

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

View file

@ -0,0 +1,26 @@
using System.IO;
namespace Ryujinx.HLE.OsHle.Ipc
{
struct IpcPtrBuffDesc
{
public long Position { get; private set; }
public int Index { get; private set; }
public long 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 = (ushort)(Word0 >> 16);
}
}
}

View file

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

View file

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

View file

@ -0,0 +1,17 @@
namespace Ryujinx.HLE.OsHle.Kernel
{
static class KernelErr
{
public const int InvalidAlignment = 102;
public const int InvalidAddress = 106;
public const int InvalidMemRange = 110;
public const int InvalidPriority = 112;
public const int InvalidCoreId = 113;
public const int InvalidHandle = 114;
public const int InvalidCoreMask = 116;
public const int Timeout = 117;
public const int Canceled = 118;
public const int CountOutOfRange = 119;
public const int InvalidInfo = 120;
}
}

View file

@ -0,0 +1,19 @@
namespace Ryujinx.HLE.OsHle.Kernel
{
static class NsTimeConverter
{
public static int GetTimeMs(ulong Ns)
{
ulong Ms = Ns / 1_000_000;
if (Ms < int.MaxValue)
{
return (int)Ms;
}
else
{
return int.MaxValue;
}
}
}
}

View file

@ -0,0 +1,147 @@
using ChocolArm64.Events;
using ChocolArm64.Memory;
using ChocolArm64.State;
using Ryujinx.HLE.Logging;
using Ryujinx.HLE.OsHle.Handles;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Threading;
namespace Ryujinx.HLE.OsHle.Kernel
{
partial class SvcHandler : IDisposable
{
private delegate void SvcFunc(AThreadState ThreadState);
private Dictionary<int, SvcFunc> SvcFuncs;
private Switch Ns;
private Process Process;
private AMemory Memory;
private ConcurrentDictionary<KThread, AutoResetEvent> SyncWaits;
private HashSet<(HSharedMem, long)> MappedSharedMems;
private ulong CurrentHeapSize;
private const uint SelfThreadHandle = 0xffff8000;
private const uint SelfProcessHandle = 0xffff8001;
private static Random Rng;
public SvcHandler(Switch Ns, Process Process)
{
SvcFuncs = new Dictionary<int, SvcFunc>()
{
{ 0x01, SvcSetHeapSize },
{ 0x03, SvcSetMemoryAttribute },
{ 0x04, SvcMapMemory },
{ 0x05, SvcUnmapMemory },
{ 0x06, SvcQueryMemory },
{ 0x07, SvcExitProcess },
{ 0x08, SvcCreateThread },
{ 0x09, SvcStartThread },
{ 0x0a, SvcExitThread },
{ 0x0b, SvcSleepThread },
{ 0x0c, SvcGetThreadPriority },
{ 0x0d, SvcSetThreadPriority },
{ 0x0e, SvcGetThreadCoreMask },
{ 0x0f, SvcSetThreadCoreMask },
{ 0x10, SvcGetCurrentProcessorNumber },
{ 0x12, SvcClearEvent },
{ 0x13, SvcMapSharedMemory },
{ 0x14, SvcUnmapSharedMemory },
{ 0x15, SvcCreateTransferMemory },
{ 0x16, SvcCloseHandle },
{ 0x17, SvcResetSignal },
{ 0x18, SvcWaitSynchronization },
{ 0x19, SvcCancelSynchronization },
{ 0x1a, SvcArbitrateLock },
{ 0x1b, SvcArbitrateUnlock },
{ 0x1c, SvcWaitProcessWideKeyAtomic },
{ 0x1d, SvcSignalProcessWideKey },
{ 0x1e, SvcGetSystemTick },
{ 0x1f, SvcConnectToNamedPort },
{ 0x21, SvcSendSyncRequest },
{ 0x22, SvcSendSyncRequestWithUserBuffer },
{ 0x25, SvcGetThreadId },
{ 0x26, SvcBreak },
{ 0x27, SvcOutputDebugString },
{ 0x29, SvcGetInfo },
{ 0x2c, SvcMapPhysicalMemory },
{ 0x2d, SvcUnmapPhysicalMemory },
{ 0x32, SvcSetThreadActivity }
};
this.Ns = Ns;
this.Process = Process;
this.Memory = Process.Memory;
SyncWaits = new ConcurrentDictionary<KThread, AutoResetEvent>();
MappedSharedMems = new HashSet<(HSharedMem, long)>();
}
static SvcHandler()
{
Rng = new Random();
}
public void SvcCall(object sender, AInstExceptionEventArgs e)
{
AThreadState ThreadState = (AThreadState)sender;
if (SvcFuncs.TryGetValue(e.Id, out SvcFunc Func))
{
Ns.Log.PrintDebug(LogClass.KernelSvc, $"{Func.Method.Name} called.");
Func(ThreadState);
Process.Scheduler.Reschedule(Process.GetThread(ThreadState.Tpidr));
Ns.Log.PrintDebug(LogClass.KernelSvc, $"{Func.Method.Name} ended.");
}
else
{
Process.PrintStackTrace(ThreadState);
throw new NotImplementedException(e.Id.ToString("x4"));
}
}
private KThread GetThread(long Tpidr, int Handle)
{
if ((uint)Handle == SelfThreadHandle)
{
return Process.GetThread(Tpidr);
}
else
{
return Process.HandleTable.GetData<KThread>(Handle);
}
}
public void Dispose()
{
Dispose(true);
}
protected virtual void Dispose(bool Disposing)
{
if (Disposing)
{
lock (MappedSharedMems)
{
foreach ((HSharedMem SharedMem, long Position) in MappedSharedMems)
{
SharedMem.RemoveVirtualPosition(Memory, Position);
}
MappedSharedMems.Clear();
}
}
}
}
}

View file

@ -0,0 +1,285 @@
using ChocolArm64.Memory;
using ChocolArm64.State;
using Ryujinx.HLE.Logging;
using Ryujinx.HLE.OsHle.Handles;
using static Ryujinx.HLE.OsHle.ErrorCode;
namespace Ryujinx.HLE.OsHle.Kernel
{
partial class SvcHandler
{
private void SvcSetHeapSize(AThreadState ThreadState)
{
uint Size = (uint)ThreadState.X1;
long Position = MemoryRegions.HeapRegionAddress;
if (Size > CurrentHeapSize)
{
Memory.Manager.Map(Position, Size, (int)MemoryType.Heap, AMemoryPerm.RW);
}
else
{
Memory.Manager.Unmap(Position + Size, (long)CurrentHeapSize - Size);
}
CurrentHeapSize = Size;
ThreadState.X0 = 0;
ThreadState.X1 = (ulong)Position;
}
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;
if ((State0 == 0 && State1 == 0) ||
(State0 == 8 && State1 == 0))
{
Memory.Manager.ClearAttrBit(Position, Size, 3);
}
else if (State0 == 8 && State1 == 8)
{
Memory.Manager.SetAttrBit(Position, Size, 3);
}
ThreadState.X0 = 0;
}
private void SvcMapMemory(AThreadState ThreadState)
{
long Dst = (long)ThreadState.X0;
long Src = (long)ThreadState.X1;
long Size = (long)ThreadState.X2;
if (!IsValidPosition(Src))
{
Ns.Log.PrintWarning(LogClass.KernelSvc, $"Invalid src address {Src:x16}!");
ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidMemRange);
return;
}
if (!IsValidMapPosition(Dst))
{
Ns.Log.PrintWarning(LogClass.KernelSvc, $"Invalid dst address {Dst:x16}!");
ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidMemRange);
return;
}
AMemoryMapInfo SrcInfo = Memory.Manager.GetMapInfo(Src);
Memory.Manager.Map(Dst, Size, (int)MemoryType.MappedMemory, SrcInfo.Perm);
Memory.Manager.Reprotect(Src, Size, AMemoryPerm.None);
Memory.Manager.SetAttrBit(Src, Size, 0);
ThreadState.X0 = 0;
}
private void SvcUnmapMemory(AThreadState ThreadState)
{
long Dst = (long)ThreadState.X0;
long Src = (long)ThreadState.X1;
long Size = (long)ThreadState.X2;
if (!IsValidPosition(Src))
{
Ns.Log.PrintWarning(LogClass.KernelSvc, $"Invalid src address {Src:x16}!");
ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidMemRange);
return;
}
if (!IsValidMapPosition(Dst))
{
Ns.Log.PrintWarning(LogClass.KernelSvc, $"Invalid dst address {Dst:x16}!");
ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidMemRange);
return;
}
AMemoryMapInfo DstInfo = Memory.Manager.GetMapInfo(Dst);
Memory.Manager.Unmap(Dst, Size, (int)MemoryType.MappedMemory);
Memory.Manager.Reprotect(Src, Size, DstInfo.Perm);
Memory.Manager.ClearAttrBit(Src, Size, 0);
ThreadState.X0 = 0;
}
private void SvcQueryMemory(AThreadState ThreadState)
{
long InfoPtr = (long)ThreadState.X0;
long Position = (long)ThreadState.X2;
AMemoryMapInfo MapInfo = Memory.Manager.GetMapInfo(Position);
if (MapInfo == null)
{
long AddrSpaceEnd = MemoryRegions.AddrSpaceStart + MemoryRegions.AddrSpaceSize;
long ReservedSize = (long)(ulong.MaxValue - (ulong)AddrSpaceEnd) + 1;
MapInfo = new AMemoryMapInfo(AddrSpaceEnd, ReservedSize, (int)MemoryType.Reserved, 0, AMemoryPerm.None);
}
Memory.WriteInt64(InfoPtr + 0x00, MapInfo.Position);
Memory.WriteInt64(InfoPtr + 0x08, MapInfo.Size);
Memory.WriteInt32(InfoPtr + 0x10, MapInfo.Type);
Memory.WriteInt32(InfoPtr + 0x14, MapInfo.Attr);
Memory.WriteInt32(InfoPtr + 0x18, (int)MapInfo.Perm);
Memory.WriteInt32(InfoPtr + 0x1c, 0);
Memory.WriteInt32(InfoPtr + 0x20, 0);
Memory.WriteInt32(InfoPtr + 0x24, 0);
//TODO: X1.
ThreadState.X0 = 0;
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;
if (!IsValidPosition(Src))
{
Ns.Log.PrintWarning(LogClass.KernelSvc, $"Invalid address {Src:x16}!");
ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidMemRange);
return;
}
HSharedMem SharedMem = Process.HandleTable.GetData<HSharedMem>(Handle);
if (SharedMem != null)
{
Memory.Manager.Map(Src, Size, (int)MemoryType.SharedMemory, AMemoryPerm.Write);
AMemoryHelper.FillWithZeros(Memory, Src, (int)Size);
Memory.Manager.Reprotect(Src, Size, (AMemoryPerm)Perm);
lock (MappedSharedMems)
{
MappedSharedMems.Add((SharedMem, Src));
}
SharedMem.AddVirtualPosition(Memory, Src);
ThreadState.X0 = 0;
}
//TODO: Error codes.
}
private void SvcUnmapSharedMemory(AThreadState ThreadState)
{
int Handle = (int)ThreadState.X0;
long Src = (long)ThreadState.X1;
long Size = (long)ThreadState.X2;
if (!IsValidPosition(Src))
{
Ns.Log.PrintWarning(LogClass.KernelSvc, $"Invalid address {Src:x16}!");
ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidMemRange);
return;
}
HSharedMem SharedMem = Process.HandleTable.GetData<HSharedMem>(Handle);
if (SharedMem != null)
{
Memory.Manager.Unmap(Src, Size, (int)MemoryType.SharedMemory);
SharedMem.RemoveVirtualPosition(Memory, Src);
lock (MappedSharedMems)
{
MappedSharedMems.Remove((SharedMem, Src));
}
ThreadState.X0 = 0;
}
//TODO: Error codes.
}
private void SvcCreateTransferMemory(AThreadState ThreadState)
{
long Src = (long)ThreadState.X1;
long Size = (long)ThreadState.X2;
int Perm = (int)ThreadState.X3;
if (!IsValidPosition(Src))
{
Ns.Log.PrintWarning(LogClass.KernelSvc, $"Invalid address {Src:x16}!");
ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidMemRange);
return;
}
AMemoryMapInfo MapInfo = Memory.Manager.GetMapInfo(Src);
Memory.Manager.Reprotect(Src, Size, (AMemoryPerm)Perm);
HTransferMem TMem = new HTransferMem(Memory, MapInfo.Perm, Src, Size);
ulong Handle = (ulong)Process.HandleTable.OpenHandle(TMem);
ThreadState.X0 = 0;
ThreadState.X1 = Handle;
}
private void SvcMapPhysicalMemory(AThreadState ThreadState)
{
long Position = (long)ThreadState.X0;
uint Size = (uint)ThreadState.X1;
Memory.Manager.Map(Position, Size, (int)MemoryType.Heap, AMemoryPerm.RW);
ThreadState.X0 = 0;
}
private void SvcUnmapPhysicalMemory(AThreadState ThreadState)
{
long Position = (long)ThreadState.X0;
uint Size = (uint)ThreadState.X1;
Memory.Manager.Unmap(Position, Size);
ThreadState.X0 = 0;
}
private static bool IsValidPosition(long Position)
{
return Position >= MemoryRegions.AddrSpaceStart &&
Position < MemoryRegions.AddrSpaceStart + MemoryRegions.AddrSpaceSize;
}
private static bool IsValidMapPosition(long Position)
{
return Position >= MemoryRegions.MapRegionAddress &&
Position < MemoryRegions.MapRegionAddress + MemoryRegions.MapRegionSize;
}
}
}

View file

@ -0,0 +1,369 @@
using ChocolArm64.Memory;
using ChocolArm64.State;
using Ryujinx.HLE.Logging;
using Ryujinx.HLE.OsHle.Exceptions;
using Ryujinx.HLE.OsHle.Handles;
using Ryujinx.HLE.OsHle.Ipc;
using Ryujinx.HLE.OsHle.Services;
using System;
using System.Threading;
using static Ryujinx.HLE.OsHle.ErrorCode;
namespace Ryujinx.HLE.OsHle.Kernel
{
partial class SvcHandler
{
private const int AllowedCpuIdBitmask = 0b1111;
private const bool EnableProcessDebugging = false;
private const bool IsVirtualMemoryEnabled = true; //This is always true(?)
private void SvcExitProcess(AThreadState ThreadState)
{
Ns.Os.ExitProcess(ThreadState.ProcessId);
}
private void SvcClearEvent(AThreadState ThreadState)
{
int Handle = (int)ThreadState.X0;
//TODO: Implement events.
ThreadState.X0 = 0;
}
private void SvcCloseHandle(AThreadState ThreadState)
{
int Handle = (int)ThreadState.X0;
object Obj = Process.HandleTable.CloseHandle(Handle);
if (Obj == null)
{
Ns.Log.PrintWarning(LogClass.KernelSvc, $"Invalid handle 0x{Handle:x8}!");
ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidHandle);
return;
}
if (Obj is KSession Session)
{
Session.Dispose();
}
else if (Obj is HTransferMem TMem)
{
TMem.Memory.Manager.Reprotect(
TMem.Position,
TMem.Size,
TMem.Perm);
}
ThreadState.X0 = 0;
}
private void SvcResetSignal(AThreadState ThreadState)
{
int Handle = (int)ThreadState.X0;
KEvent Event = Process.HandleTable.GetData<KEvent>(Handle);
if (Event != null)
{
Event.WaitEvent.Reset();
ThreadState.X0 = 0;
}
else
{
Ns.Log.PrintWarning(LogClass.KernelSvc, $"Invalid event handle 0x{Handle:x8}!");
ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidHandle);
}
}
private void SvcWaitSynchronization(AThreadState ThreadState)
{
long HandlesPtr = (long)ThreadState.X1;
int HandlesCount = (int)ThreadState.X2;
ulong Timeout = ThreadState.X3;
Ns.Log.PrintDebug(LogClass.KernelSvc,
"HandlesPtr = " + HandlesPtr .ToString("x16") + ", " +
"HandlesCount = " + HandlesCount.ToString("x8") + ", " +
"Timeout = " + Timeout .ToString("x16"));
if ((uint)HandlesCount > 0x40)
{
ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.CountOutOfRange);
return;
}
KThread CurrThread = Process.GetThread(ThreadState.Tpidr);
WaitHandle[] Handles = new WaitHandle[HandlesCount + 1];
for (int Index = 0; Index < HandlesCount; Index++)
{
int Handle = Memory.ReadInt32(HandlesPtr + Index * 4);
KSynchronizationObject SyncObj = Process.HandleTable.GetData<KSynchronizationObject>(Handle);
if (SyncObj == null)
{
Ns.Log.PrintWarning(LogClass.KernelSvc, $"Invalid handle 0x{Handle:x8}!");
ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidHandle);
return;
}
Handles[Index] = SyncObj.WaitEvent;
}
using (AutoResetEvent WaitEvent = new AutoResetEvent(false))
{
if (!SyncWaits.TryAdd(CurrThread, WaitEvent))
{
throw new InvalidOperationException();
}
Handles[HandlesCount] = WaitEvent;
Process.Scheduler.Suspend(CurrThread);
int HandleIndex;
ulong Result = 0;
if (Timeout != ulong.MaxValue)
{
HandleIndex = WaitHandle.WaitAny(Handles, NsTimeConverter.GetTimeMs(Timeout));
}
else
{
HandleIndex = WaitHandle.WaitAny(Handles);
}
if (HandleIndex == WaitHandle.WaitTimeout)
{
Result = MakeError(ErrorModule.Kernel, KernelErr.Timeout);
}
else if (HandleIndex == HandlesCount)
{
Result = MakeError(ErrorModule.Kernel, KernelErr.Canceled);
}
SyncWaits.TryRemove(CurrThread, out _);
Process.Scheduler.Resume(CurrThread);
ThreadState.X0 = Result;
if (Result == 0)
{
ThreadState.X1 = (ulong)HandleIndex;
}
}
}
private void SvcCancelSynchronization(AThreadState ThreadState)
{
int ThreadHandle = (int)ThreadState.X0;
KThread Thread = GetThread(ThreadState.Tpidr, ThreadHandle);
if (Thread == null)
{
Ns.Log.PrintWarning(LogClass.KernelSvc, $"Invalid thread handle 0x{ThreadHandle:x8}!");
ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidHandle);
return;
}
if (SyncWaits.TryRemove(Thread, out AutoResetEvent WaitEvent))
{
WaitEvent.Set();
}
ThreadState.X0 = 0;
}
private void SvcGetSystemTick(AThreadState ThreadState)
{
ThreadState.X0 = 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.
KSession Session = new KSession(ServiceFactory.MakeService(Name), Name);
ulong Handle = (ulong)Process.HandleTable.OpenHandle(Session);
ThreadState.X0 = 0;
ThreadState.X1 = Handle;
}
private void SvcSendSyncRequest(AThreadState ThreadState)
{
SendSyncRequest(ThreadState, ThreadState.Tpidr, 0x100, (int)ThreadState.X0);
}
private void SvcSendSyncRequestWithUserBuffer(AThreadState ThreadState)
{
SendSyncRequest(
ThreadState,
(long)ThreadState.X0,
(long)ThreadState.X1,
(int)ThreadState.X2);
}
private void SendSyncRequest(AThreadState ThreadState, long CmdPtr, long Size, int Handle)
{
KThread CurrThread = Process.GetThread(ThreadState.Tpidr);
byte[] CmdData = Memory.ReadBytes(CmdPtr, Size);
KSession Session = Process.HandleTable.GetData<KSession>(Handle);
if (Session != null)
{
Process.Scheduler.Suspend(CurrThread);
IpcMessage Cmd = new IpcMessage(CmdData, CmdPtr);
long Result = IpcHandler.IpcCall(Ns, Process, Memory, Session, Cmd, CmdPtr);
Thread.Yield();
Process.Scheduler.Resume(CurrThread);
ThreadState.X0 = (ulong)Result;
}
else
{
Ns.Log.PrintWarning(LogClass.KernelSvc, $"Invalid session handle 0x{Handle:x8}!");
ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidHandle);
}
}
private void SvcBreak(AThreadState ThreadState)
{
long Reason = (long)ThreadState.X0;
long Unknown = (long)ThreadState.X1;
long Info = (long)ThreadState.X2;
Process.PrintStackTrace(ThreadState);
throw new GuestBrokeExecutionException();
}
private void SvcOutputDebugString(AThreadState ThreadState)
{
long Position = (long)ThreadState.X0;
long Size = (long)ThreadState.X1;
string Str = AMemoryHelper.ReadAsciiString(Memory, Position, Size);
Ns.Log.PrintWarning(LogClass.KernelSvc, Str);
ThreadState.X0 = 0;
}
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 ||
InfoType == 20)
{
ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidInfo);
return;
}
switch (InfoType)
{
case 0:
ThreadState.X1 = AllowedCpuIdBitmask;
break;
case 2:
ThreadState.X1 = MemoryRegions.MapRegionAddress;
break;
case 3:
ThreadState.X1 = MemoryRegions.MapRegionSize;
break;
case 4:
ThreadState.X1 = MemoryRegions.HeapRegionAddress;
break;
case 5:
ThreadState.X1 = MemoryRegions.HeapRegionSize;
break;
case 6:
ThreadState.X1 = MemoryRegions.TotalMemoryAvailable;
break;
case 7:
ThreadState.X1 = MemoryRegions.TotalMemoryUsed + CurrentHeapSize;
break;
case 8:
ThreadState.X1 = EnableProcessDebugging ? 1 : 0;
break;
case 11:
ThreadState.X1 = (ulong)Rng.Next() + ((ulong)Rng.Next() << 32);
break;
case 12:
ThreadState.X1 = MemoryRegions.AddrSpaceStart;
break;
case 13:
ThreadState.X1 = MemoryRegions.AddrSpaceSize;
break;
case 14:
ThreadState.X1 = MemoryRegions.MapRegionAddress;
break;
case 15:
ThreadState.X1 = MemoryRegions.MapRegionSize;
break;
case 16:
ThreadState.X1 = IsVirtualMemoryEnabled ? 1 : 0;
break;
default:
Process.PrintStackTrace(ThreadState);
throw new NotImplementedException($"SvcGetInfo: {InfoType} {Handle:x8} {InfoId}");
}
ThreadState.X0 = 0;
}
}
}

View file

@ -0,0 +1,291 @@
using ChocolArm64.State;
using Ryujinx.HLE.Logging;
using Ryujinx.HLE.OsHle.Handles;
using System.Threading;
using static Ryujinx.HLE.OsHle.ErrorCode;
namespace Ryujinx.HLE.OsHle.Kernel
{
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 ((uint)Priority > 0x3f)
{
Ns.Log.PrintWarning(LogClass.KernelSvc, $"Invalid priority 0x{Priority:x8}!");
ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidPriority);
return;
}
if (ProcessorId == -2)
{
//TODO: Get this value from the NPDM file.
ProcessorId = 0;
}
else if ((uint)ProcessorId > 3)
{
Ns.Log.PrintWarning(LogClass.KernelSvc, $"Invalid core id 0x{ProcessorId:x8}!");
ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidCoreId);
return;
}
int Handle = Process.MakeThread(
EntryPoint,
StackTop,
ArgsPtr,
Priority,
ProcessorId);
ThreadState.X0 = 0;
ThreadState.X1 = (ulong)Handle;
}
private void SvcStartThread(AThreadState ThreadState)
{
int Handle = (int)ThreadState.X0;
KThread NewThread = Process.HandleTable.GetData<KThread>(Handle);
if (NewThread != null)
{
Process.Scheduler.StartThread(NewThread);
Process.Scheduler.SetReschedule(NewThread.ProcessorId);
ThreadState.X0 = 0;
}
else
{
Ns.Log.PrintWarning(LogClass.KernelSvc, $"Invalid thread handle 0x{Handle:x8}!");
ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidHandle);
}
}
private void SvcExitThread(AThreadState ThreadState)
{
KThread CurrThread = Process.GetThread(ThreadState.Tpidr);
CurrThread.Thread.StopExecution();
}
private void SvcSleepThread(AThreadState ThreadState)
{
ulong TimeoutNs = ThreadState.X0;
KThread CurrThread = Process.GetThread(ThreadState.Tpidr);
if (TimeoutNs == 0)
{
Process.Scheduler.Yield(CurrThread);
}
else
{
Process.Scheduler.Suspend(CurrThread);
Thread.Sleep(NsTimeConverter.GetTimeMs(TimeoutNs));
Process.Scheduler.Resume(CurrThread);
}
}
private void SvcGetThreadPriority(AThreadState ThreadState)
{
int Handle = (int)ThreadState.X1;
KThread Thread = GetThread(ThreadState.Tpidr, Handle);
if (Thread != null)
{
ThreadState.X0 = 0;
ThreadState.X1 = (ulong)Thread.ActualPriority;
}
else
{
Ns.Log.PrintWarning(LogClass.KernelSvc, $"Invalid thread handle 0x{Handle:x8}!");
ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidHandle);
}
}
private void SvcSetThreadPriority(AThreadState ThreadState)
{
int Handle = (int)ThreadState.X0;
int Priority = (int)ThreadState.X1;
KThread Thread = GetThread(ThreadState.Tpidr, Handle);
if (Thread != null)
{
Thread.SetPriority(Priority);
ThreadState.X0 = 0;
}
else
{
Ns.Log.PrintWarning(LogClass.KernelSvc, $"Invalid thread handle 0x{Handle:x8}!");
ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidHandle);
}
}
private void SvcGetThreadCoreMask(AThreadState ThreadState)
{
int Handle = (int)ThreadState.X2;
Ns.Log.PrintDebug(LogClass.KernelSvc, "Handle = " + Handle.ToString("x8"));
KThread Thread = GetThread(ThreadState.Tpidr, Handle);
if (Thread != null)
{
ThreadState.X0 = 0;
ThreadState.X1 = (ulong)Thread.IdealCore;
ThreadState.X2 = (ulong)Thread.CoreMask;
}
else
{
Ns.Log.PrintWarning(LogClass.KernelSvc, $"Invalid thread handle 0x{Handle:x8}!");
ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidHandle);
}
}
private void SvcSetThreadCoreMask(AThreadState ThreadState)
{
//FIXME: This is wrong, but the "correct" way to handle
//this svc causes deadlocks when more often.
//There is probably something wrong with it still.
ThreadState.X0 = 0;
return;
int Handle = (int)ThreadState.X0;
int IdealCore = (int)ThreadState.X1;
long CoreMask = (long)ThreadState.X2;
Ns.Log.PrintDebug(LogClass.KernelSvc,
"Handle = " + Handle .ToString("x8") + ", " +
"IdealCore = " + IdealCore.ToString("x8") + ", " +
"CoreMask = " + CoreMask .ToString("x16"));
KThread Thread = GetThread(ThreadState.Tpidr, Handle);
if (IdealCore == -2)
{
//TODO: Get this value from the NPDM file.
IdealCore = 0;
CoreMask = 1 << IdealCore;
}
else if (IdealCore != -3)
{
if ((uint)IdealCore > 3)
{
if ((IdealCore | 2) != -1)
{
Ns.Log.PrintWarning(LogClass.KernelSvc, $"Invalid core id 0x{IdealCore:x8}!");
ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidCoreId);
return;
}
}
else if ((CoreMask & (1 << IdealCore)) == 0)
{
Ns.Log.PrintWarning(LogClass.KernelSvc, $"Invalid core mask 0x{CoreMask:x8}!");
ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidCoreMask);
return;
}
}
if (Thread == null)
{
Ns.Log.PrintWarning(LogClass.KernelSvc, $"Invalid thread handle 0x{Handle:x8}!");
ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidHandle);
return;
}
//-1 is used as "don't care", so the IdealCore value is ignored.
//-2 is used as "use NPDM default core id" (handled above).
//-3 is used as "don't update", the old IdealCore value is kept.
if (IdealCore != -3)
{
Thread.IdealCore = IdealCore;
}
else if ((CoreMask & (1 << Thread.IdealCore)) == 0)
{
Ns.Log.PrintWarning(LogClass.KernelSvc, $"Invalid core mask 0x{CoreMask:x8}!");
ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidCoreMask);
return;
}
Thread.CoreMask = (int)CoreMask;
Process.Scheduler.TryToRun(Thread);
ThreadState.X0 = 0;
}
private void SvcGetCurrentProcessorNumber(AThreadState ThreadState)
{
ThreadState.X0 = (ulong)Process.GetThread(ThreadState.Tpidr).ActualCore;
}
private void SvcGetThreadId(AThreadState ThreadState)
{
int Handle = (int)ThreadState.X1;
KThread Thread = GetThread(ThreadState.Tpidr, Handle);
if (Thread != null)
{
ThreadState.X0 = 0;
ThreadState.X1 = (ulong)Thread.ThreadId;
}
else
{
Ns.Log.PrintWarning(LogClass.KernelSvc, $"Invalid thread handle 0x{Handle:x8}!");
ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidHandle);
}
}
private void SvcSetThreadActivity(AThreadState ThreadState)
{
int Handle = (int)ThreadState.X0;
bool Active = (int)ThreadState.X1 == 0;
KThread Thread = Process.HandleTable.GetData<KThread>(Handle);
if (Thread != null)
{
Process.Scheduler.SetThreadActivity(Thread, Active);
ThreadState.X0 = 0;
}
else
{
Ns.Log.PrintWarning(LogClass.KernelSvc, $"Invalid thread handle 0x{Handle:x8}!");
ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidHandle);
}
}
}
}

View file

@ -0,0 +1,435 @@
using ChocolArm64.State;
using Ryujinx.HLE.Logging;
using Ryujinx.HLE.OsHle.Handles;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using static Ryujinx.HLE.OsHle.ErrorCode;
namespace Ryujinx.HLE.OsHle.Kernel
{
partial class SvcHandler
{
private const int MutexHasListenersMask = 0x40000000;
private void SvcArbitrateLock(AThreadState ThreadState)
{
int OwnerThreadHandle = (int)ThreadState.X0;
long MutexAddress = (long)ThreadState.X1;
int WaitThreadHandle = (int)ThreadState.X2;
Ns.Log.PrintDebug(LogClass.KernelSvc,
"OwnerThreadHandle = " + OwnerThreadHandle.ToString("x8") + ", " +
"MutexAddress = " + MutexAddress .ToString("x16") + ", " +
"WaitThreadHandle = " + WaitThreadHandle .ToString("x8"));
if (IsPointingInsideKernel(MutexAddress))
{
Ns.Log.PrintWarning(LogClass.KernelSvc, $"Invalid mutex address 0x{MutexAddress:x16}!");
ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidAddress);
return;
}
if (IsWordAddressUnaligned(MutexAddress))
{
Ns.Log.PrintWarning(LogClass.KernelSvc, $"Unaligned mutex address 0x{MutexAddress:x16}!");
ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidAlignment);
return;
}
KThread OwnerThread = Process.HandleTable.GetData<KThread>(OwnerThreadHandle);
if (OwnerThread == null)
{
Ns.Log.PrintWarning(LogClass.KernelSvc, $"Invalid owner thread handle 0x{OwnerThreadHandle:x8}!");
ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidHandle);
return;
}
KThread WaitThread = Process.HandleTable.GetData<KThread>(WaitThreadHandle);
if (WaitThread == null)
{
Ns.Log.PrintWarning(LogClass.KernelSvc, $"Invalid requesting thread handle 0x{WaitThreadHandle:x8}!");
ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidHandle);
return;
}
KThread CurrThread = Process.GetThread(ThreadState.Tpidr);
MutexLock(CurrThread, WaitThread, OwnerThreadHandle, WaitThreadHandle, MutexAddress);
ThreadState.X0 = 0;
}
private void SvcArbitrateUnlock(AThreadState ThreadState)
{
long MutexAddress = (long)ThreadState.X0;
Ns.Log.PrintDebug(LogClass.KernelSvc, "MutexAddress = " + MutexAddress.ToString("x16"));
if (IsPointingInsideKernel(MutexAddress))
{
Ns.Log.PrintWarning(LogClass.KernelSvc, $"Invalid mutex address 0x{MutexAddress:x16}!");
ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidAddress);
return;
}
if (IsWordAddressUnaligned(MutexAddress))
{
Ns.Log.PrintWarning(LogClass.KernelSvc, $"Unaligned mutex address 0x{MutexAddress:x16}!");
ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidAlignment);
return;
}
MutexUnlock(Process.GetThread(ThreadState.Tpidr), MutexAddress);
ThreadState.X0 = 0;
}
private void SvcWaitProcessWideKeyAtomic(AThreadState ThreadState)
{
long MutexAddress = (long)ThreadState.X0;
long CondVarAddress = (long)ThreadState.X1;
int ThreadHandle = (int)ThreadState.X2;
ulong Timeout = ThreadState.X3;
Ns.Log.PrintDebug(LogClass.KernelSvc,
"MutexAddress = " + MutexAddress .ToString("x16") + ", " +
"CondVarAddress = " + CondVarAddress.ToString("x16") + ", " +
"ThreadHandle = " + ThreadHandle .ToString("x8") + ", " +
"Timeout = " + Timeout .ToString("x16"));
if (IsPointingInsideKernel(MutexAddress))
{
Ns.Log.PrintWarning(LogClass.KernelSvc, $"Invalid mutex address 0x{MutexAddress:x16}!");
ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidAddress);
return;
}
if (IsWordAddressUnaligned(MutexAddress))
{
Ns.Log.PrintWarning(LogClass.KernelSvc, $"Unaligned mutex address 0x{MutexAddress:x16}!");
ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidAlignment);
return;
}
KThread Thread = Process.HandleTable.GetData<KThread>(ThreadHandle);
if (Thread == null)
{
Ns.Log.PrintWarning(LogClass.KernelSvc, $"Invalid thread handle 0x{ThreadHandle:x8}!");
ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidHandle);
return;
}
KThread CurrThread = Process.GetThread(ThreadState.Tpidr);
MutexUnlock(CurrThread, MutexAddress);
if (!CondVarWait(CurrThread, ThreadHandle, MutexAddress, CondVarAddress, Timeout))
{
ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.Timeout);
return;
}
ThreadState.X0 = 0;
}
private void SvcSignalProcessWideKey(AThreadState ThreadState)
{
long CondVarAddress = (long)ThreadState.X0;
int Count = (int)ThreadState.X1;
Ns.Log.PrintDebug(LogClass.KernelSvc,
"CondVarAddress = " + CondVarAddress.ToString("x16") + ", " +
"Count = " + Count .ToString("x8"));
KThread CurrThread = Process.GetThread(ThreadState.Tpidr);
CondVarSignal(CurrThread, CondVarAddress, Count);
ThreadState.X0 = 0;
}
private void MutexLock(
KThread CurrThread,
KThread WaitThread,
int OwnerThreadHandle,
int WaitThreadHandle,
long MutexAddress)
{
lock (Process.ThreadSyncLock)
{
int MutexValue = Process.Memory.ReadInt32(MutexAddress);
Ns.Log.PrintDebug(LogClass.KernelSvc, "MutexValue = " + MutexValue.ToString("x8"));
if (MutexValue != (OwnerThreadHandle | MutexHasListenersMask))
{
return;
}
CurrThread.WaitHandle = WaitThreadHandle;
CurrThread.MutexAddress = MutexAddress;
InsertWaitingMutexThread(OwnerThreadHandle, WaitThread);
}
Ns.Log.PrintDebug(LogClass.KernelSvc, "Entering wait state...");
Process.Scheduler.EnterWait(CurrThread);
}
private void MutexUnlock(KThread CurrThread, long MutexAddress)
{
lock (Process.ThreadSyncLock)
{
//This is the new thread that will now own the mutex.
//If no threads are waiting for the lock, then it should be null.
KThread OwnerThread = PopThread(CurrThread.MutexWaiters, x => x.MutexAddress == MutexAddress);
if (OwnerThread != null)
{
//Remove all waiting mutex from the old owner,
//and insert then on the new owner.
UpdateMutexOwner(CurrThread, OwnerThread, MutexAddress);
CurrThread.UpdatePriority();
int HasListeners = OwnerThread.MutexWaiters.Count > 0 ? MutexHasListenersMask : 0;
Process.Memory.WriteInt32(MutexAddress, HasListeners | OwnerThread.WaitHandle);
OwnerThread.WaitHandle = 0;
OwnerThread.MutexAddress = 0;
OwnerThread.CondVarAddress = 0;
OwnerThread.MutexOwner = null;
OwnerThread.UpdatePriority();
Process.Scheduler.WakeUp(OwnerThread);
Ns.Log.PrintDebug(LogClass.KernelSvc, "Gave mutex to thread id " + OwnerThread.ThreadId + "!");
}
else
{
Process.Memory.WriteInt32(MutexAddress, 0);
Ns.Log.PrintDebug(LogClass.KernelSvc, "No threads waiting mutex!");
}
}
}
private bool CondVarWait(
KThread WaitThread,
int WaitThreadHandle,
long MutexAddress,
long CondVarAddress,
ulong Timeout)
{
WaitThread.WaitHandle = WaitThreadHandle;
WaitThread.MutexAddress = MutexAddress;
WaitThread.CondVarAddress = CondVarAddress;
lock (Process.ThreadSyncLock)
{
WaitThread.CondVarSignaled = false;
Process.ThreadArbiterList.Add(WaitThread);
}
Ns.Log.PrintDebug(LogClass.KernelSvc, "Entering wait state...");
if (Timeout != ulong.MaxValue)
{
Process.Scheduler.EnterWait(WaitThread, NsTimeConverter.GetTimeMs(Timeout));
lock (Process.ThreadSyncLock)
{
WaitThread.MutexOwner?.MutexWaiters.Remove(WaitThread);
if (!WaitThread.CondVarSignaled || WaitThread.MutexOwner != null)
{
WaitThread.MutexOwner = null;
Process.ThreadArbiterList.Remove(WaitThread);
Ns.Log.PrintDebug(LogClass.KernelSvc, "Timed out...");
return false;
}
}
}
else
{
Process.Scheduler.EnterWait(WaitThread);
}
return true;
}
private void CondVarSignal(KThread CurrThread, long CondVarAddress, int Count)
{
lock (Process.ThreadSyncLock)
{
while (Count == -1 || Count-- > 0)
{
KThread WaitThread = PopThread(Process.ThreadArbiterList, x => x.CondVarAddress == CondVarAddress);
if (WaitThread == null)
{
Ns.Log.PrintDebug(LogClass.KernelSvc, "No more threads to wake up!");
break;
}
WaitThread.CondVarSignaled = true;
AcquireMutexValue(WaitThread.MutexAddress);
int MutexValue = Process.Memory.ReadInt32(WaitThread.MutexAddress);
Ns.Log.PrintDebug(LogClass.KernelSvc, "MutexValue = " + MutexValue.ToString("x8"));
if (MutexValue == 0)
{
//Give the lock to this thread.
Process.Memory.WriteInt32(WaitThread.MutexAddress, WaitThread.WaitHandle);
WaitThread.WaitHandle = 0;
WaitThread.MutexAddress = 0;
WaitThread.CondVarAddress = 0;
WaitThread.MutexOwner?.UpdatePriority();
WaitThread.MutexOwner = null;
Process.Scheduler.WakeUp(WaitThread);
}
else
{
//Wait until the lock is released.
MutexValue &= ~MutexHasListenersMask;
InsertWaitingMutexThread(MutexValue, WaitThread);
MutexValue |= MutexHasListenersMask;
Process.Memory.WriteInt32(WaitThread.MutexAddress, MutexValue);
}
ReleaseMutexValue(WaitThread.MutexAddress);
}
}
}
private void UpdateMutexOwner(KThread CurrThread, KThread NewOwner, long MutexAddress)
{
//Go through all threads waiting for the mutex,
//and update the MutexOwner field to point to the new owner.
lock (Process.ThreadSyncLock)
{
for (int Index = 0; Index < CurrThread.MutexWaiters.Count; Index++)
{
KThread Thread = CurrThread.MutexWaiters[Index];
if (Thread.MutexAddress == MutexAddress)
{
CurrThread.MutexWaiters.RemoveAt(Index--);
Thread.MutexOwner = NewOwner;
InsertWaitingMutexThread(NewOwner, Thread);
}
}
}
}
private void InsertWaitingMutexThread(int OwnerThreadHandle, KThread WaitThread)
{
KThread OwnerThread = Process.HandleTable.GetData<KThread>(OwnerThreadHandle);
if (OwnerThread == null)
{
Ns.Log.PrintWarning(LogClass.KernelSvc, $"Invalid thread handle 0x{OwnerThreadHandle:x8}!");
return;
}
InsertWaitingMutexThread(OwnerThread, WaitThread);
}
private void InsertWaitingMutexThread(KThread OwnerThread, KThread WaitThread)
{
lock (Process.ThreadSyncLock)
{
WaitThread.MutexOwner = OwnerThread;
if (!OwnerThread.MutexWaiters.Contains(WaitThread))
{
OwnerThread.MutexWaiters.Add(WaitThread);
OwnerThread.UpdatePriority();
}
}
}
private KThread PopThread(List<KThread> Threads, Func<KThread, bool> Predicate)
{
KThread Thread = Threads.OrderBy(x => x.ActualPriority).FirstOrDefault(Predicate);
if (Thread != null)
{
Threads.Remove(Thread);
}
return Thread;
}
private void AcquireMutexValue(long MutexAddress)
{
while (!Process.Memory.AcquireAddress(MutexAddress))
{
Thread.Yield();
}
}
private void ReleaseMutexValue(long MutexAddress)
{
Process.Memory.ReleaseAddress(MutexAddress);
}
private bool IsPointingInsideKernel(long Address)
{
return ((ulong)Address + 0x1000000000) < 0xffffff000;
}
private bool IsWordAddressUnaligned(long Address)
{
return (Address & 3) != 0;
}
}
}

View file

@ -0,0 +1,12 @@
using System;
namespace Ryujinx.HLE.OsHle
{
class MemoryAllocator
{
public bool TryAllocate(long Size, out long Address)
{
throw new NotImplementedException();
}
}
}

View file

@ -0,0 +1,29 @@
using ChocolArm64.Memory;
namespace Ryujinx.HLE.OsHle
{
static class MemoryRegions
{
public const long AddrSpaceStart = 0x08000000;
public const long MapRegionAddress = 0x10000000;
public const long MapRegionSize = 0x20000000;
public const long HeapRegionAddress = MapRegionAddress + MapRegionSize;
public const long HeapRegionSize = TlsPagesAddress - HeapRegionAddress;
public const long MainStackSize = 0x100000;
public const long MainStackAddress = AMemoryMgr.AddrSize - MainStackSize;
public const long TlsPagesSize = 0x20000;
public const long TlsPagesAddress = MainStackAddress - TlsPagesSize;
public const long TotalMemoryUsed = HeapRegionAddress + TlsPagesSize + MainStackSize;
public const long TotalMemoryAvailable = AMemoryMgr.RamSize - AddrSpaceStart;
public const long AddrSpaceSize = AMemoryMgr.AddrSize - AddrSpaceStart;
}
}

View file

@ -0,0 +1,25 @@
namespace Ryujinx.HLE.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
}
}

View file

@ -0,0 +1,436 @@
using ChocolArm64;
using ChocolArm64.Events;
using ChocolArm64.Memory;
using ChocolArm64.State;
using Ryujinx.HLE.Loaders;
using Ryujinx.HLE.Loaders.Executables;
using Ryujinx.HLE.Logging;
using Ryujinx.HLE.OsHle.Diagnostics;
using Ryujinx.HLE.OsHle.Exceptions;
using Ryujinx.HLE.OsHle.Handles;
using Ryujinx.HLE.OsHle.Kernel;
using Ryujinx.HLE.OsHle.Services.Nv;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Text;
namespace Ryujinx.HLE.OsHle
{
class Process : IDisposable
{
private const int TlsSize = 0x200;
private const int TotalTlsSlots = (int)MemoryRegions.TlsPagesSize / TlsSize;
private const int TickFreq = 19_200_000;
private Switch Ns;
public bool NeedsHbAbi { get; private set; }
public long HbAbiDataPosition { get; private set; }
public int ProcessId { get; private set; }
private ATranslator Translator;
public AMemory Memory { get; private set; }
public KProcessScheduler Scheduler { get; private set; }
public List<KThread> ThreadArbiterList { get; private set; }
public object ThreadSyncLock { get; private set; }
public KProcessHandleTable HandleTable { get; private set; }
public AppletStateMgr AppletState { get; private set; }
private SvcHandler SvcHandler;
private ConcurrentDictionary<int, AThread> TlsSlots;
private ConcurrentDictionary<long, KThread> Threads;
private KThread MainThread;
private List<Executable> Executables;
private Dictionary<long, string> SymbolTable;
private long ImageBase;
private bool ShouldDispose;
private bool Disposed;
public Process(Switch Ns, KProcessScheduler Scheduler, int ProcessId)
{
this.Ns = Ns;
this.Scheduler = Scheduler;
this.ProcessId = ProcessId;
Memory = new AMemory();
ThreadArbiterList = new List<KThread>();
ThreadSyncLock = new object();
HandleTable = new KProcessHandleTable();
AppletState = new AppletStateMgr();
SvcHandler = new SvcHandler(Ns, this);
TlsSlots = new ConcurrentDictionary<int, AThread>();
Threads = new ConcurrentDictionary<long, KThread>();
Executables = new List<Executable>();
ImageBase = MemoryRegions.AddrSpaceStart;
MapRWMemRegion(
MemoryRegions.TlsPagesAddress,
MemoryRegions.TlsPagesSize,
MemoryType.ThreadLocal);
}
public void LoadProgram(IExecutable Program)
{
if (Disposed)
{
throw new ObjectDisposedException(nameof(Process));
}
Ns.Log.PrintInfo(LogClass.Loader, $"Image base at 0x{ImageBase:x16}.");
Executable Executable = new Executable(Program, Memory, ImageBase);
Executables.Add(Executable);
ImageBase = AMemoryHelper.PageRoundUp(Executable.ImageEnd);
}
public void SetEmptyArgs()
{
//TODO: This should be part of Run.
ImageBase += AMemoryMgr.PageSize;
}
public bool Run(bool NeedsHbAbi = false)
{
if (Disposed)
{
throw new ObjectDisposedException(nameof(Process));
}
this.NeedsHbAbi = NeedsHbAbi;
if (Executables.Count == 0)
{
return false;
}
MakeSymbolTable();
MapRWMemRegion(
MemoryRegions.MainStackAddress,
MemoryRegions.MainStackSize,
MemoryType.Normal);
long StackTop = MemoryRegions.MainStackAddress + MemoryRegions.MainStackSize;
int Handle = MakeThread(Executables[0].ImageBase, StackTop, 0, 44, 0);
if (Handle == -1)
{
return false;
}
MainThread = HandleTable.GetData<KThread>(Handle);
if (NeedsHbAbi)
{
HbAbiDataPosition = AMemoryHelper.PageRoundUp(Executables[0].ImageEnd);
Homebrew.WriteHbAbiData(Memory, HbAbiDataPosition, Handle);
MainThread.Thread.ThreadState.X0 = (ulong)HbAbiDataPosition;
MainThread.Thread.ThreadState.X1 = ulong.MaxValue;
}
Scheduler.StartThread(MainThread);
return true;
}
private void MapRWMemRegion(long Position, long Size, MemoryType Type)
{
Memory.Manager.Map(Position, Size, (int)Type, AMemoryPerm.RW);
}
public void StopAllThreadsAsync()
{
if (Disposed)
{
throw new ObjectDisposedException(nameof(Process));
}
if (MainThread != null)
{
MainThread.Thread.StopExecution();
}
foreach (AThread Thread in TlsSlots.Values)
{
Thread.StopExecution();
}
}
public int MakeThread(
long EntryPoint,
long StackTop,
long ArgsPtr,
int Priority,
int ProcessorId)
{
if (Disposed)
{
throw new ObjectDisposedException(nameof(Process));
}
AThread CpuThread = new AThread(GetTranslator(), Memory, EntryPoint);
KThread Thread = new KThread(CpuThread, this, ProcessorId, Priority);
int Handle = HandleTable.OpenHandle(Thread);
int ThreadId = GetFreeTlsSlot(CpuThread);
long Tpidr = MemoryRegions.TlsPagesAddress + ThreadId * TlsSize;
CpuThread.ThreadState.ProcessId = ProcessId;
CpuThread.ThreadState.ThreadId = ThreadId;
CpuThread.ThreadState.CntfrqEl0 = TickFreq;
CpuThread.ThreadState.Tpidr = Tpidr;
CpuThread.ThreadState.X0 = (ulong)ArgsPtr;
CpuThread.ThreadState.X1 = (ulong)Handle;
CpuThread.ThreadState.X31 = (ulong)StackTop;
CpuThread.ThreadState.Break += BreakHandler;
CpuThread.ThreadState.SvcCall += SvcHandler.SvcCall;
CpuThread.ThreadState.Undefined += UndefinedHandler;
CpuThread.WorkFinished += ThreadFinished;
Threads.TryAdd(CpuThread.ThreadState.Tpidr, Thread);
return Handle;
}
private void BreakHandler(object sender, AInstExceptionEventArgs e)
{
throw new GuestBrokeExecutionException();
}
private void UndefinedHandler(object sender, AInstUndefinedEventArgs e)
{
throw new UndefinedInstructionException(e.Position, e.RawOpCode);
}
private void MakeSymbolTable()
{
SymbolTable = new Dictionary<long, string>();
foreach (Executable Exe in Executables)
{
foreach (KeyValuePair<long, string> KV in Exe.SymbolTable)
{
SymbolTable.TryAdd(Exe.ImageBase + KV.Key, KV.Value);
}
}
}
private ATranslator GetTranslator()
{
if (Translator == null)
{
Translator = new ATranslator(SymbolTable);
Translator.CpuTrace += CpuTraceHandler;
}
return Translator;
}
public void EnableCpuTracing()
{
Translator.EnableCpuTrace = true;
}
public void DisableCpuTracing()
{
Translator.EnableCpuTrace = false;
}
private void CpuTraceHandler(object sender, ACpuTraceEventArgs e)
{
string NsoName = string.Empty;
for (int Index = Executables.Count - 1; Index >= 0; Index--)
{
if (e.Position >= Executables[Index].ImageBase)
{
NsoName = $"{(e.Position - Executables[Index].ImageBase):x16}";
break;
}
}
Ns.Log.PrintDebug(LogClass.Cpu, $"Executing at 0x{e.Position:x16} {e.SubName} {NsoName}");
}
public void PrintStackTrace(AThreadState ThreadState)
{
long[] Positions = ThreadState.GetCallStack();
StringBuilder Trace = new StringBuilder();
Trace.AppendLine("Guest stack trace:");
foreach (long Position in Positions)
{
if (!SymbolTable.TryGetValue(Position, out string SubName))
{
SubName = $"Sub{Position:x16}";
}
else if (SubName.StartsWith("_Z"))
{
SubName = Demangler.Parse(SubName);
}
Trace.AppendLine(" " + SubName + " (" + GetNsoNameAndAddress(Position) + ")");
}
Ns.Log.PrintInfo(LogClass.Cpu, Trace.ToString());
}
private string GetNsoNameAndAddress(long Position)
{
string Name = string.Empty;
for (int Index = Executables.Count - 1; Index >= 0; Index--)
{
if (Position >= Executables[Index].ImageBase)
{
long Offset = Position - Executables[Index].ImageBase;
Name = $"{Executables[Index].Name}:{Offset:x8}";
break;
}
}
return Name;
}
private int GetFreeTlsSlot(AThread Thread)
{
for (int Index = 1; Index < TotalTlsSlots; Index++)
{
if (TlsSlots.TryAdd(Index, Thread))
{
return Index;
}
}
throw new InvalidOperationException();
}
private void ThreadFinished(object sender, EventArgs e)
{
if (sender is AThread Thread)
{
TlsSlots.TryRemove(GetTlsSlot(Thread.ThreadState.Tpidr), out _);
Threads.TryRemove(Thread.ThreadState.Tpidr, out KThread KernelThread);
Scheduler.RemoveThread(KernelThread);
KernelThread.WaitEvent.Set();
}
if (TlsSlots.Count == 0)
{
if (ShouldDispose)
{
Dispose();
}
Ns.Os.ExitProcess(ProcessId);
}
}
private int GetTlsSlot(long Position)
{
return (int)((Position - MemoryRegions.TlsPagesAddress) / TlsSize);
}
public KThread GetThread(long Tpidr)
{
if (!Threads.TryGetValue(Tpidr, out KThread Thread))
{
throw new InvalidOperationException();
}
return Thread;
}
public void Dispose()
{
Dispose(true);
}
protected virtual void Dispose(bool Disposing)
{
if (Disposing && !Disposed)
{
//If there is still some thread running, disposing the objects is not
//safe as the thread may try to access those resources. Instead, we set
//the flag to have the Process disposed when all threads finishes.
//Note: This may not happen if the guest code gets stuck on a infinite loop.
if (TlsSlots.Count > 0)
{
ShouldDispose = true;
Ns.Log.PrintInfo(LogClass.Loader, $"Process {ProcessId} waiting all threads terminate...");
return;
}
Disposed = true;
foreach (object Obj in HandleTable.Clear())
{
if (Obj is KSession Session)
{
Session.Dispose();
}
}
INvDrvServices.UnloadProcess(this);
AppletState.Dispose();
SvcHandler.Dispose();
Memory.Dispose();
Ns.Log.PrintInfo(LogClass.Loader, $"Process {ProcessId} exiting...");
}
}
}
}

View file

@ -0,0 +1,39 @@
using ChocolArm64.Memory;
using Ryujinx.HLE.OsHle.Handles;
using Ryujinx.HLE.OsHle.Ipc;
using System.IO;
namespace Ryujinx.HLE.OsHle
{
class ServiceCtx
{
public Switch Ns { get; private set; }
public Process Process { get; private set; }
public AMemory Memory { get; private set; }
public KSession 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,
Process Process,
AMemory Memory,
KSession Session,
IpcMessage Request,
IpcMessage Response,
BinaryReader RequestData,
BinaryWriter ResponseData)
{
this.Ns = Ns;
this.Process = Process;
this.Memory = Memory;
this.Session = Session;
this.Request = Request;
this.Response = Response;
this.RequestData = RequestData;
this.ResponseData = ResponseData;
}
}
}

View file

@ -0,0 +1,91 @@
using Ryujinx.HLE.Logging;
using Ryujinx.HLE.OsHle.Ipc;
using System.Collections.Generic;
namespace Ryujinx.HLE.OsHle.Services.Acc
{
class IAccountServiceForApplication : IpcService
{
private Dictionary<int, ServiceProcessRequest> m_Commands;
public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
public IAccountServiceForApplication()
{
m_Commands = new Dictionary<int, ServiceProcessRequest>()
{
{ 0, GetUserCount },
{ 1, GetUserExistence },
{ 2, ListAllUsers },
{ 3, ListOpenUsers },
{ 4, GetLastOpenedUser },
{ 5, GetProfile },
{ 100, InitializeApplicationInfo },
{ 101, GetBaasAccountManagerForApplication }
};
}
public long GetUserCount(ServiceCtx Context)
{
Context.ResponseData.Write(0);
Context.Ns.Log.PrintStub(LogClass.ServiceAcc, "Stubbed.");
return 0;
}
public long GetUserExistence(ServiceCtx Context)
{
Context.ResponseData.Write(1);
Context.Ns.Log.PrintStub(LogClass.ServiceAcc, "Stubbed.");
return 0;
}
public long ListAllUsers(ServiceCtx Context)
{
Context.Ns.Log.PrintStub(LogClass.ServiceAcc, "Stubbed.");
return 0;
}
public long ListOpenUsers(ServiceCtx Context)
{
Context.Ns.Log.PrintStub(LogClass.ServiceAcc, "Stubbed.");
return 0;
}
public long GetLastOpenedUser(ServiceCtx Context)
{
Context.ResponseData.Write(0L);
Context.ResponseData.Write(0L);
Context.Ns.Log.PrintStub(LogClass.ServiceAcc, "Stubbed.");
return 0;
}
public long GetProfile(ServiceCtx Context)
{
MakeObject(Context, new IProfile());
return 0;
}
public long InitializeApplicationInfo(ServiceCtx Context)
{
Context.Ns.Log.PrintStub(LogClass.ServiceAcc, "Stubbed.");
return 0;
}
public long GetBaasAccountManagerForApplication(ServiceCtx Context)
{
MakeObject(Context, new IManagerForApplication());
return 0;
}
}
}

View file

@ -0,0 +1,38 @@
using Ryujinx.HLE.Logging;
using Ryujinx.HLE.OsHle.Ipc;
using System.Collections.Generic;
namespace Ryujinx.HLE.OsHle.Services.Acc
{
class IManagerForApplication : IpcService
{
private Dictionary<int, ServiceProcessRequest> m_Commands;
public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
public IManagerForApplication()
{
m_Commands = new Dictionary<int, ServiceProcessRequest>()
{
{ 0, CheckAvailability },
{ 1, GetAccountId }
};
}
public long CheckAvailability(ServiceCtx Context)
{
Context.Ns.Log.PrintStub(LogClass.ServiceAcc, "Stubbed.");
return 0;
}
public long GetAccountId(ServiceCtx Context)
{
Context.Ns.Log.PrintStub(LogClass.ServiceAcc, "Stubbed.");
Context.ResponseData.Write(0xcafeL);
return 0;
}
}
}

View file

@ -0,0 +1,36 @@
using Ryujinx.HLE.Logging;
using Ryujinx.HLE.OsHle.Ipc;
using System.Collections.Generic;
namespace Ryujinx.HLE.OsHle.Services.Acc
{
class IProfile : IpcService
{
private Dictionary<int, ServiceProcessRequest> m_Commands;
public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
public IProfile()
{
m_Commands = new Dictionary<int, ServiceProcessRequest>()
{
{ 1, GetBase }
};
}
public long GetBase(ServiceCtx Context)
{
Context.Ns.Log.PrintStub(LogClass.ServiceAcc, "Stubbed.");
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,7 @@
namespace Ryujinx.HLE.OsHle.Services.Am
{
static class AmErr
{
public const int NoMessages = 3;
}
}

View file

@ -0,0 +1,8 @@
namespace Ryujinx.HLE.OsHle.Services.Am
{
enum FocusState
{
InFocus = 1,
OutOfFocus = 2
}
}

View file

@ -0,0 +1,27 @@
using Ryujinx.HLE.OsHle.Ipc;
using System.Collections.Generic;
namespace Ryujinx.HLE.OsHle.Services.Am
{
class IAllSystemAppletProxiesService : IpcService
{
private Dictionary<int, ServiceProcessRequest> m_Commands;
public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
public IAllSystemAppletProxiesService()
{
m_Commands = new Dictionary<int, ServiceProcessRequest>()
{
{ 100, OpenSystemAppletProxy }
};
}
public long OpenSystemAppletProxy(ServiceCtx Context)
{
MakeObject(Context, new ISystemAppletProxy());
return 0;
}
}
}

View file

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

View file

@ -0,0 +1,117 @@
using Ryujinx.HLE.Logging;
using Ryujinx.HLE.OsHle.Ipc;
using System.Collections.Generic;
namespace Ryujinx.HLE.OsHle.Services.Am
{
class IApplicationFunctions : IpcService
{
private Dictionary<int, ServiceProcessRequest> m_Commands;
public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
public IApplicationFunctions()
{
m_Commands = new Dictionary<int, ServiceProcessRequest>()
{
{ 1, PopLaunchParameter },
{ 20, EnsureSaveData },
{ 21, GetDesiredLanguage },
{ 22, SetTerminateResult },
{ 23, GetDisplayVersion },
{ 40, NotifyRunning },
{ 50, GetPseudoDeviceId },
{ 66, InitializeGamePlayRecording },
{ 67, SetGamePlayRecordingState }
};
}
public long PopLaunchParameter(ServiceCtx Context)
{
//Only the first 0x18 bytes of the Data seems to be actually used.
MakeObject(Context, new IStorage(StorageHelper.MakeLaunchParams()));
return 0;
}
public long EnsureSaveData(ServiceCtx Context)
{
long UIdLow = Context.RequestData.ReadInt64();
long UIdHigh = Context.RequestData.ReadInt64();
Context.Ns.Log.PrintStub(LogClass.ServiceAm, "Stubbed.");
Context.ResponseData.Write(0L);
return 0;
}
public long GetDesiredLanguage(ServiceCtx Context)
{
Context.ResponseData.Write(Context.Ns.Os.SystemState.DesiredLanguageCode);
return 0;
}
public long SetTerminateResult(ServiceCtx Context)
{
int ErrorCode = Context.RequestData.ReadInt32();
string Result = GetFormattedErrorCode(ErrorCode);
Context.Ns.Log.PrintInfo(LogClass.ServiceAm, $"Result = 0x{ErrorCode:x8} ({Result}).");
return 0;
}
private string GetFormattedErrorCode(int ErrorCode)
{
int Module = (ErrorCode >> 0) & 0x1ff;
int Description = (ErrorCode >> 9) & 0x1fff;
return $"{(2000 + Module):d4}-{Description:d4}";
}
public long GetDisplayVersion(ServiceCtx Context)
{
//FIXME: Need to check correct version on a switch.
Context.ResponseData.Write(1L);
Context.ResponseData.Write(0L);
return 0;
}
public long NotifyRunning(ServiceCtx Context)
{
Context.ResponseData.Write(1);
return 0;
}
public long GetPseudoDeviceId(ServiceCtx Context)
{
Context.Ns.Log.PrintStub(LogClass.ServiceAm, "Stubbed.");
Context.ResponseData.Write(0L);
Context.ResponseData.Write(0L);
return 0;
}
public long InitializeGamePlayRecording(ServiceCtx Context)
{
Context.Ns.Log.PrintStub(LogClass.ServiceAm, "Stubbed.");
return 0;
}
public long SetGamePlayRecordingState(ServiceCtx Context)
{
int State = Context.RequestData.ReadInt32();
Context.Ns.Log.PrintStub(LogClass.ServiceAm, "Stubbed.");
return 0;
}
}
}

View file

@ -0,0 +1,83 @@
using Ryujinx.HLE.OsHle.Ipc;
using System.Collections.Generic;
namespace Ryujinx.HLE.OsHle.Services.Am
{
class IApplicationProxy : IpcService
{
private Dictionary<int, ServiceProcessRequest> m_Commands;
public override 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,27 @@
using Ryujinx.HLE.OsHle.Ipc;
using System.Collections.Generic;
namespace Ryujinx.HLE.OsHle.Services.Am
{
class IApplicationProxyService : IpcService
{
private Dictionary<int, ServiceProcessRequest> m_Commands;
public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
public IApplicationProxyService()
{
m_Commands = new Dictionary<int, ServiceProcessRequest>()
{
{ 0, OpenApplicationProxy }
};
}
public long OpenApplicationProxy(ServiceCtx Context)
{
MakeObject(Context, new IApplicationProxy());
return 0;
}
}
}

View file

@ -0,0 +1,72 @@
using Ryujinx.HLE.Logging;
using Ryujinx.HLE.OsHle.Ipc;
using System.Collections.Generic;
namespace Ryujinx.HLE.OsHle.Services.Am
{
class IAudioController : IpcService
{
private Dictionary<int, ServiceProcessRequest> m_Commands;
public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
public IAudioController()
{
m_Commands = new Dictionary<int, ServiceProcessRequest>()
{
{ 0, SetExpectedMasterVolume },
{ 1, GetMainAppletExpectedMasterVolume },
{ 2, GetLibraryAppletExpectedMasterVolume },
{ 3, ChangeMainAppletMasterVolume },
{ 4, SetTransparentVolumeRate }
};
}
public long SetExpectedMasterVolume(ServiceCtx Context)
{
float AppletVolume = Context.RequestData.ReadSingle();
float LibraryAppletVolume = Context.RequestData.ReadSingle();
Context.Ns.Log.PrintStub(LogClass.ServiceAm, "Stubbed.");
return 0;
}
public long GetMainAppletExpectedMasterVolume(ServiceCtx Context)
{
Context.ResponseData.Write(1f);
Context.Ns.Log.PrintStub(LogClass.ServiceAm, "Stubbed.");
return 0;
}
public long GetLibraryAppletExpectedMasterVolume(ServiceCtx Context)
{
Context.ResponseData.Write(1f);
Context.Ns.Log.PrintStub(LogClass.ServiceAm, "Stubbed.");
return 0;
}
public long ChangeMainAppletMasterVolume(ServiceCtx Context)
{
float Unknown0 = Context.RequestData.ReadSingle();
long Unknown1 = Context.RequestData.ReadInt64();
Context.Ns.Log.PrintStub(LogClass.ServiceAm, "Stubbed.");
return 0;
}
public long SetTransparentVolumeRate(ServiceCtx Context)
{
float Unknown0 = Context.RequestData.ReadSingle();
Context.Ns.Log.PrintStub(LogClass.ServiceAm, "Stubbed.");
return 0;
}
}
}

View file

@ -0,0 +1,109 @@
using Ryujinx.HLE.Logging;
using Ryujinx.HLE.OsHle.Handles;
using Ryujinx.HLE.OsHle.Ipc;
using System.Collections.Generic;
using static Ryujinx.HLE.OsHle.ErrorCode;
namespace Ryujinx.HLE.OsHle.Services.Am
{
class ICommonStateGetter : IpcService
{
private Dictionary<int, ServiceProcessRequest> m_Commands;
public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
private KEvent DisplayResolutionChangeEvent;
public ICommonStateGetter()
{
m_Commands = new Dictionary<int, ServiceProcessRequest>()
{
{ 0, GetEventHandle },
{ 1, ReceiveMessage },
{ 5, GetOperationMode },
{ 6, GetPerformanceMode },
{ 8, GetBootMode },
{ 9, GetCurrentFocusState },
{ 60, GetDefaultDisplayResolution },
{ 61, GetDefaultDisplayResolutionChangeEvent }
};
DisplayResolutionChangeEvent = new KEvent();
}
public long GetEventHandle(ServiceCtx Context)
{
KEvent Event = Context.Process.AppletState.MessageEvent;
int Handle = Context.Process.HandleTable.OpenHandle(Event);
Context.Response.HandleDesc = IpcHandleDesc.MakeCopy(Handle);
return 0;
}
public long ReceiveMessage(ServiceCtx Context)
{
if (!Context.Process.AppletState.TryDequeueMessage(out MessageInfo Message))
{
return MakeError(ErrorModule.Am, AmErr.NoMessages);
}
Context.ResponseData.Write((int)Message);
return 0;
}
public long GetOperationMode(ServiceCtx Context)
{
Context.ResponseData.Write((byte)OperationMode.Handheld);
return 0;
}
public long GetPerformanceMode(ServiceCtx Context)
{
Context.ResponseData.Write((byte)Apm.PerformanceMode.Handheld);
return 0;
}
public long GetBootMode(ServiceCtx Context)
{
Context.ResponseData.Write((byte)0); //Unknown value.
Context.Ns.Log.PrintStub(LogClass.ServiceAm, "Stubbed.");
return 0;
}
public long GetCurrentFocusState(ServiceCtx Context)
{
Context.ResponseData.Write((byte)Context.Process.AppletState.FocusState);
return 0;
}
public long GetDefaultDisplayResolution(ServiceCtx Context)
{
Context.ResponseData.Write(1280);
Context.ResponseData.Write(720);
// Context.Ns.Log.PrintStub(LogClass.ServiceAm, "Stubbed.");
return 0;
}
public long GetDefaultDisplayResolutionChangeEvent(ServiceCtx Context)
{
int Handle = Context.Process.HandleTable.OpenHandle(DisplayResolutionChangeEvent);
Context.Response.HandleDesc = IpcHandleDesc.MakeCopy(Handle);
Context.Ns.Log.PrintStub(LogClass.ServiceAm, "Stubbed.");
return 0;
}
}
}

View file

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

View file

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

View file

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

View file

@ -0,0 +1,46 @@
using Ryujinx.HLE.Logging;
using Ryujinx.HLE.OsHle.Handles;
using Ryujinx.HLE.OsHle.Ipc;
using System.Collections.Generic;
namespace Ryujinx.HLE.OsHle.Services.Am
{
class IHomeMenuFunctions : IpcService
{
private Dictionary<int, ServiceProcessRequest> m_Commands;
public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
private KEvent ChannelEvent;
public IHomeMenuFunctions()
{
m_Commands = new Dictionary<int, ServiceProcessRequest>()
{
{ 10, RequestToGetForeground },
{ 21, GetPopFromGeneralChannelEvent }
};
//ToDo: Signal this Event somewhere in future.
ChannelEvent = new KEvent();
}
public long RequestToGetForeground(ServiceCtx Context)
{
Context.Ns.Log.PrintStub(LogClass.ServiceAm, "Stubbed.");
return 0;
}
public long GetPopFromGeneralChannelEvent(ServiceCtx Context)
{
int Handle = Context.Process.HandleTable.OpenHandle(ChannelEvent);
Context.Response.HandleDesc = IpcHandleDesc.MakeCopy(Handle);
Context.Ns.Log.PrintStub(LogClass.ServiceAm, "Stubbed.");
return 0;
}
}
}

View file

@ -0,0 +1,71 @@
using Ryujinx.HLE.Logging;
using Ryujinx.HLE.OsHle.Handles;
using Ryujinx.HLE.OsHle.Ipc;
using System.Collections.Generic;
namespace Ryujinx.HLE.OsHle.Services.Am
{
class ILibraryAppletAccessor : IpcService
{
private Dictionary<int, ServiceProcessRequest> m_Commands;
public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
private KEvent StateChangedEvent;
public ILibraryAppletAccessor()
{
m_Commands = new Dictionary<int, ServiceProcessRequest>()
{
{ 0, GetAppletStateChangedEvent },
{ 10, Start },
{ 30, GetResult },
{ 100, PushInData },
{ 101, PopOutData }
};
StateChangedEvent = new KEvent();
}
public long GetAppletStateChangedEvent(ServiceCtx Context)
{
StateChangedEvent.WaitEvent.Set();
int Handle = Context.Process.HandleTable.OpenHandle(StateChangedEvent);
Context.Response.HandleDesc = IpcHandleDesc.MakeCopy(Handle);
Context.Ns.Log.PrintStub(LogClass.ServiceAm, "Stubbed.");
return 0;
}
public long Start(ServiceCtx Context)
{
Context.Ns.Log.PrintStub(LogClass.ServiceAm, "Stubbed.");
return 0;
}
public long GetResult(ServiceCtx Context)
{
Context.Ns.Log.PrintStub(LogClass.ServiceAm, "Stubbed.");
return 0;
}
public long PushInData(ServiceCtx Context)
{
Context.Ns.Log.PrintStub(LogClass.ServiceAm, "Stubbed.");
return 0;
}
public long PopOutData(ServiceCtx Context)
{
MakeObject(Context, new IStorage(StorageHelper.MakeLaunchParams()));
return 0;
}
}
}

View file

@ -0,0 +1,37 @@
using Ryujinx.HLE.OsHle.Ipc;
using System.Collections.Generic;
namespace Ryujinx.HLE.OsHle.Services.Am
{
class ILibraryAppletCreator : IpcService
{
private Dictionary<int, ServiceProcessRequest> m_Commands;
public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
public ILibraryAppletCreator()
{
m_Commands = new Dictionary<int, ServiceProcessRequest>()
{
{ 0, CreateLibraryApplet },
{ 10, CreateStorage }
};
}
public long CreateLibraryApplet(ServiceCtx Context)
{
MakeObject(Context, new ILibraryAppletAccessor());
return 0;
}
public long CreateStorage(ServiceCtx Context)
{
long Size = Context.RequestData.ReadInt64();
MakeObject(Context, new IStorage(new byte[Size]));
return 0;
}
}
}

View file

@ -0,0 +1,117 @@
using Ryujinx.HLE.Logging;
using Ryujinx.HLE.OsHle.Handles;
using Ryujinx.HLE.OsHle.Ipc;
using System.Collections.Generic;
namespace Ryujinx.HLE.OsHle.Services.Am
{
class ISelfController : IpcService
{
private Dictionary<int, ServiceProcessRequest> m_Commands;
public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
private KEvent LaunchableEvent;
public ISelfController()
{
m_Commands = new Dictionary<int, ServiceProcessRequest>()
{
{ 1, LockExit },
{ 9, GetLibraryAppletLaunchableEvent },
{ 10, SetScreenShotPermission },
{ 11, SetOperationModeChangedNotification },
{ 12, SetPerformanceModeChangedNotification },
{ 13, SetFocusHandlingMode },
{ 14, SetRestartMessageEnabled },
{ 16, SetOutOfFocusSuspendingEnabled },
{ 50, SetHandlesRequestToDisplay }
};
LaunchableEvent = new KEvent();
}
public long LockExit(ServiceCtx Context)
{
return 0;
}
public long GetLibraryAppletLaunchableEvent(ServiceCtx Context)
{
LaunchableEvent.WaitEvent.Set();
int Handle = Context.Process.HandleTable.OpenHandle(LaunchableEvent);
Context.Response.HandleDesc = IpcHandleDesc.MakeCopy(Handle);
Context.Ns.Log.PrintStub(LogClass.ServiceAm, "Stubbed.");
return 0;
}
public long SetScreenShotPermission(ServiceCtx Context)
{
bool Enable = Context.RequestData.ReadByte() != 0 ? true : false;
Context.Ns.Log.PrintStub(LogClass.ServiceAm, "Stubbed.");
return 0;
}
public long SetOperationModeChangedNotification(ServiceCtx Context)
{
bool Enable = Context.RequestData.ReadByte() != 0 ? true : false;
Context.Ns.Log.PrintStub(LogClass.ServiceAm, "Stubbed.");
return 0;
}
public long SetPerformanceModeChangedNotification(ServiceCtx Context)
{
bool Enable = Context.RequestData.ReadByte() != 0 ? true : false;
Context.Ns.Log.PrintStub(LogClass.ServiceAm, "Stubbed.");
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;
Context.Ns.Log.PrintStub(LogClass.ServiceAm, "Stubbed.");
return 0;
}
public long SetRestartMessageEnabled(ServiceCtx Context)
{
bool Enable = Context.RequestData.ReadByte() != 0 ? true : false;
Context.Ns.Log.PrintStub(LogClass.ServiceAm, "Stubbed.");
return 0;
}
public long SetOutOfFocusSuspendingEnabled(ServiceCtx Context)
{
bool Enable = Context.RequestData.ReadByte() != 0 ? true : false;
Context.Ns.Log.PrintStub(LogClass.ServiceAm, "Stubbed.");
return 0;
}
public long SetHandlesRequestToDisplay(ServiceCtx Context)
{
bool Enable = Context.RequestData.ReadByte() != 0 ? true : false;
Context.Ns.Log.PrintStub(LogClass.ServiceAm, "Stubbed.");
return 0;
}
}
}

View file

@ -0,0 +1,31 @@
using Ryujinx.HLE.OsHle.Ipc;
using System.Collections.Generic;
namespace Ryujinx.HLE.OsHle.Services.Am
{
class IStorage : IpcService
{
private Dictionary<int, ServiceProcessRequest> m_Commands;
public override 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,83 @@
using Ryujinx.HLE.OsHle.Ipc;
using System;
using System.Collections.Generic;
namespace Ryujinx.HLE.OsHle.Services.Am
{
class IStorageAccessor : IpcService
{
private Dictionary<int, ServiceProcessRequest> m_Commands;
public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
private IStorage Storage;
public IStorageAccessor(IStorage Storage)
{
m_Commands = new Dictionary<int, ServiceProcessRequest>()
{
{ 0, GetSize },
{ 10, Write },
{ 11, Read }
};
this.Storage = Storage;
}
public long GetSize(ServiceCtx Context)
{
Context.ResponseData.Write((long)Storage.Data.Length);
return 0;
}
public long Write(ServiceCtx Context)
{
//TODO: Error conditions.
long WritePosition = Context.RequestData.ReadInt64();
(long Position, long Size) = Context.Request.GetBufferType0x21();
if (Size > 0)
{
long MaxSize = Storage.Data.Length - WritePosition;
if (Size > MaxSize)
{
Size = MaxSize;
}
byte[] Data = Context.Memory.ReadBytes(Position, Size);
Buffer.BlockCopy(Data, 0, Storage.Data, (int)WritePosition, (int)Size);
}
return 0;
}
public long Read(ServiceCtx Context)
{
//TODO: Error conditions.
long ReadPosition = Context.RequestData.ReadInt64();
(long Position, long Size) = Context.Request.GetBufferType0x22();
byte[] Data;
if (Storage.Data.Length > Size)
{
Data = new byte[Size];
Buffer.BlockCopy(Storage.Data, 0, Data, 0, (int)Size);
}
else
{
Data = Storage.Data;
}
Context.Memory.WriteBytes(Position, Data);
return 0;
}
}
}

View file

@ -0,0 +1,99 @@
using Ryujinx.HLE.OsHle.Ipc;
using System.Collections.Generic;
namespace Ryujinx.HLE.OsHle.Services.Am
{
class ISystemAppletProxy : IpcService
{
private Dictionary<int, ServiceProcessRequest> m_Commands;
public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
public ISystemAppletProxy()
{
m_Commands = new Dictionary<int, ServiceProcessRequest>()
{
{ 0, GetCommonStateGetter },
{ 1, GetSelfController },
{ 2, GetWindowController },
{ 3, GetAudioController },
{ 4, GetDisplayController },
{ 11, GetLibraryAppletCreator },
{ 20, GetHomeMenuFunctions },
{ 21, GetGlobalStateController },
{ 22, GetApplicationCreator },
{ 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 GetHomeMenuFunctions(ServiceCtx Context)
{
MakeObject(Context, new IHomeMenuFunctions());
return 0;
}
public long GetGlobalStateController(ServiceCtx Context)
{
MakeObject(Context, new IGlobalStateController());
return 0;
}
public long GetApplicationCreator(ServiceCtx Context)
{
MakeObject(Context, new IApplicationCreator());
return 0;
}
public long GetDebugFunctions(ServiceCtx Context)
{
MakeObject(Context, new IDebugFunctions());
return 0;
}
}
}

View file

@ -0,0 +1,38 @@
using Ryujinx.HLE.Logging;
using Ryujinx.HLE.OsHle.Ipc;
using System.Collections.Generic;
namespace Ryujinx.HLE.OsHle.Services.Am
{
class IWindowController : IpcService
{
private Dictionary<int, ServiceProcessRequest> m_Commands;
public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
public IWindowController()
{
m_Commands = new Dictionary<int, ServiceProcessRequest>()
{
{ 1, GetAppletResourceUserId },
{ 10, AcquireForegroundRights }
};
}
public long GetAppletResourceUserId(ServiceCtx Context)
{
Context.Ns.Log.PrintStub(LogClass.ServiceAm, "Stubbed.");
Context.ResponseData.Write(0L);
return 0;
}
public long AcquireForegroundRights(ServiceCtx Context)
{
Context.Ns.Log.PrintStub(LogClass.ServiceAm, "Stubbed.");
return 0;
}
}
}

View file

@ -0,0 +1,9 @@
namespace Ryujinx.HLE.OsHle.Services.Am
{
enum MessageInfo
{
FocusStateChanged = 0xf,
OperationModeChanged = 0x1e,
PerformanceModeChanged = 0x1f
}
}

View file

@ -0,0 +1,8 @@
namespace Ryujinx.HLE.OsHle.Services.Am
{
enum OperationMode
{
Handheld = 0,
Docked = 1
}
}

View file

@ -0,0 +1,27 @@
using System.IO;
namespace Ryujinx.HLE.OsHle.Services.Am
{
class StorageHelper
{
private const uint LaunchParamsMagic = 0xc79497ca;
public static 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,27 @@
using Ryujinx.HLE.OsHle.Ipc;
using System.Collections.Generic;
namespace Ryujinx.HLE.OsHle.Services.Apm
{
class IManager : IpcService
{
private Dictionary<int, ServiceProcessRequest> m_Commands;
public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
public IManager()
{
m_Commands = new Dictionary<int, ServiceProcessRequest>()
{
{ 0, OpenSession }
};
}
public long OpenSession(ServiceCtx Context)
{
MakeObject(Context, new ISession());
return 0;
}
}
}

View file

@ -0,0 +1,41 @@
using Ryujinx.HLE.Logging;
using Ryujinx.HLE.OsHle.Ipc;
using System.Collections.Generic;
namespace Ryujinx.HLE.OsHle.Services.Apm
{
class ISession : IpcService
{
private Dictionary<int, ServiceProcessRequest> m_Commands;
public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
public ISession()
{
m_Commands = new Dictionary<int, ServiceProcessRequest>()
{
{ 0, SetPerformanceConfiguration },
{ 1, GetPerformanceConfiguration }
};
}
public long SetPerformanceConfiguration(ServiceCtx Context)
{
PerformanceMode PerfMode = (PerformanceMode)Context.RequestData.ReadInt32();
PerformanceConfiguration PerfConfig = (PerformanceConfiguration)Context.RequestData.ReadInt32();
return 0;
}
public long GetPerformanceConfiguration(ServiceCtx Context)
{
PerformanceMode PerfMode = (PerformanceMode)Context.RequestData.ReadInt32();
Context.ResponseData.Write((uint)PerformanceConfiguration.PerformanceConfiguration1);
Context.Ns.Log.PrintStub(LogClass.ServiceApm, "Stubbed.");
return 0;
}
}
}

View file

@ -0,0 +1,18 @@
namespace Ryujinx.HLE.OsHle.Services.Apm
{
enum PerformanceConfiguration : uint
{
PerformanceConfiguration1 = 0x00010000,
PerformanceConfiguration2 = 0x00010001,
PerformanceConfiguration3 = 0x00010002,
PerformanceConfiguration4 = 0x00020000,
PerformanceConfiguration5 = 0x00020001,
PerformanceConfiguration6 = 0x00020002,
PerformanceConfiguration7 = 0x00020003,
PerformanceConfiguration8 = 0x00020004,
PerformanceConfiguration9 = 0x00020005,
PerformanceConfiguration10 = 0x00020006,
PerformanceConfiguration11 = 0x92220007,
PerformanceConfiguration12 = 0x92220008
}
}

View file

@ -0,0 +1,8 @@
namespace Ryujinx.HLE.OsHle.Services.Apm
{
enum PerformanceMode
{
Handheld = 0,
Docked = 1
}
}

View file

@ -0,0 +1,14 @@
using System.Runtime.InteropServices;
namespace Ryujinx.HLE.OsHle.Services.Aud
{
[StructLayout(LayoutKind.Sequential)]
struct AudioOutData
{
public long NextBufferPtr;
public long SampleBufferPtr;
public long SampleBufferCapacity;
public long SampleBufferSize;
public long SampleBufferInnerOffset;
}
}

View file

@ -0,0 +1,222 @@
using Ryujinx.HLE.Logging;
using Ryujinx.HLE.OsHle.Handles;
using Ryujinx.HLE.OsHle.Ipc;
using System.Collections.Generic;
using System.Text;
namespace Ryujinx.HLE.OsHle.Services.Aud
{
class IAudioDevice : IpcService
{
private Dictionary<int, ServiceProcessRequest> m_Commands;
public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
private KEvent SystemEvent;
public IAudioDevice()
{
m_Commands = new Dictionary<int, ServiceProcessRequest>()
{
{ 0, ListAudioDeviceName },
{ 1, SetAudioDeviceOutputVolume },
{ 3, GetActiveAudioDeviceName },
{ 4, QueryAudioDeviceSystemEvent },
{ 5, GetActiveChannelCount },
{ 6, ListAudioDeviceNameAuto },
{ 7, SetAudioDeviceOutputVolumeAuto },
{ 8, GetAudioDeviceOutputVolumeAuto },
{ 10, GetActiveAudioDeviceNameAuto },
{ 11, QueryAudioDeviceInputEvent },
{ 12, QueryAudioDeviceOutputEvent }
};
SystemEvent = new KEvent();
//TODO: We shouldn't be signaling this here.
SystemEvent.WaitEvent.Set();
}
public long ListAudioDeviceName(ServiceCtx Context)
{
string[] DeviceNames = SystemStateMgr.AudioOutputs;
Context.ResponseData.Write(DeviceNames.Length);
long Position = Context.Request.ReceiveBuff[0].Position;
long Size = Context.Request.ReceiveBuff[0].Size;
long BasePosition = Position;
foreach (string Name in DeviceNames)
{
byte[] Buffer = Encoding.ASCII.GetBytes(Name + "\0");
if ((Position - BasePosition) + Buffer.Length > Size)
{
Context.Ns.Log.PrintError(LogClass.ServiceAudio, $"Output buffer size {Size} too small!");
break;
}
Context.Memory.WriteBytes(Position, Buffer);
Position += Buffer.Length;
}
return 0;
}
public long SetAudioDeviceOutputVolume(ServiceCtx Context)
{
float Volume = Context.RequestData.ReadSingle();
long Position = Context.Request.SendBuff[0].Position;
long Size = Context.Request.SendBuff[0].Size;
byte[] DeviceNameBuffer = Context.Memory.ReadBytes(Position, Size);
string DeviceName = Encoding.ASCII.GetString(DeviceNameBuffer);
Context.Ns.Log.PrintStub(LogClass.ServiceAudio, "Stubbed.");
return 0;
}
public long GetActiveAudioDeviceName(ServiceCtx Context)
{
string Name = Context.Ns.Os.SystemState.ActiveAudioOutput;
long Position = Context.Request.ReceiveBuff[0].Position;
long Size = Context.Request.ReceiveBuff[0].Size;
byte[] DeviceNameBuffer = Encoding.ASCII.GetBytes(Name + "\0");
if ((ulong)DeviceNameBuffer.Length <= (ulong)Size)
{
Context.Memory.WriteBytes(Position, DeviceNameBuffer);
}
else
{
Context.Ns.Log.PrintError(LogClass.ServiceAudio, $"Output buffer size {Size} too small!");
}
return 0;
}
public long QueryAudioDeviceSystemEvent(ServiceCtx Context)
{
int Handle = Context.Process.HandleTable.OpenHandle(SystemEvent);
Context.Response.HandleDesc = IpcHandleDesc.MakeCopy(Handle);
Context.Ns.Log.PrintStub(LogClass.ServiceAudio, "Stubbed.");
return 0;
}
public long GetActiveChannelCount(ServiceCtx Context)
{
Context.ResponseData.Write(2);
Context.Ns.Log.PrintStub(LogClass.ServiceAudio, "Stubbed.");
return 0;
}
public long ListAudioDeviceNameAuto(ServiceCtx Context)
{
string[] DeviceNames = SystemStateMgr.AudioOutputs;
Context.ResponseData.Write(DeviceNames.Length);
(long Position, long Size) = Context.Request.GetBufferType0x22();
long BasePosition = Position;
foreach (string Name in DeviceNames)
{
byte[] Buffer = Encoding.UTF8.GetBytes(Name + '\0');
if ((Position - BasePosition) + Buffer.Length > Size)
{
Context.Ns.Log.PrintError(LogClass.ServiceAudio, $"Output buffer size {Size} too small!");
break;
}
Context.Memory.WriteBytes(Position, Buffer);
Position += Buffer.Length;
}
return 0;
}
public long SetAudioDeviceOutputVolumeAuto(ServiceCtx Context)
{
float Volume = Context.RequestData.ReadSingle();
(long Position, long Size) = Context.Request.GetBufferType0x21();
byte[] DeviceNameBuffer = Context.Memory.ReadBytes(Position, Size);
string DeviceName = Encoding.UTF8.GetString(DeviceNameBuffer);
Context.Ns.Log.PrintStub(LogClass.ServiceAudio, "Stubbed.");
return 0;
}
public long GetAudioDeviceOutputVolumeAuto(ServiceCtx Context)
{
Context.ResponseData.Write(1f);
Context.Ns.Log.PrintStub(LogClass.ServiceAudio, "Stubbed.");
return 0;
}
public long GetActiveAudioDeviceNameAuto(ServiceCtx Context)
{
string Name = Context.Ns.Os.SystemState.ActiveAudioOutput;
(long Position, long Size) = Context.Request.GetBufferType0x22();
byte[] DeviceNameBuffer = Encoding.UTF8.GetBytes(Name + '\0');
if ((ulong)DeviceNameBuffer.Length <= (ulong)Size)
{
Context.Memory.WriteBytes(Position, DeviceNameBuffer);
}
else
{
Context.Ns.Log.PrintError(LogClass.ServiceAudio, $"Output buffer size {Size} too small!");
}
return 0;
}
public long QueryAudioDeviceInputEvent(ServiceCtx Context)
{
int Handle = Context.Process.HandleTable.OpenHandle(SystemEvent);
Context.Response.HandleDesc = IpcHandleDesc.MakeCopy(Handle);
Context.Ns.Log.PrintStub(LogClass.ServiceAudio, "Stubbed.");
return 0;
}
public long QueryAudioDeviceOutputEvent(ServiceCtx Context)
{
int Handle = Context.Process.HandleTable.OpenHandle(SystemEvent);
Context.Response.HandleDesc = IpcHandleDesc.MakeCopy(Handle);
Context.Ns.Log.PrintStub(LogClass.ServiceAudio, "Stubbed.");
return 0;
}
}
}

View file

@ -0,0 +1,154 @@
using ChocolArm64.Memory;
using Ryujinx.Audio;
using Ryujinx.HLE.Logging;
using Ryujinx.HLE.OsHle.Handles;
using Ryujinx.HLE.OsHle.Ipc;
using System;
using System.Collections.Generic;
namespace Ryujinx.HLE.OsHle.Services.Aud
{
class IAudioOut : IpcService, IDisposable
{
private Dictionary<int, ServiceProcessRequest> m_Commands;
public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
private IAalOutput AudioOut;
private KEvent ReleaseEvent;
private int Track;
public IAudioOut(IAalOutput AudioOut, KEvent ReleaseEvent, int Track)
{
m_Commands = new Dictionary<int, ServiceProcessRequest>()
{
{ 0, GetAudioOutState },
{ 1, StartAudioOut },
{ 2, StopAudioOut },
{ 3, AppendAudioOutBuffer },
{ 4, RegisterBufferEvent },
{ 5, GetReleasedAudioOutBuffer },
{ 6, ContainsAudioOutBuffer },
{ 7, AppendAudioOutBufferEx },
{ 8, GetReleasedAudioOutBufferEx }
};
this.AudioOut = AudioOut;
this.ReleaseEvent = ReleaseEvent;
this.Track = Track;
}
public long GetAudioOutState(ServiceCtx Context)
{
Context.ResponseData.Write((int)AudioOut.GetState(Track));
return 0;
}
public long StartAudioOut(ServiceCtx Context)
{
AudioOut.Start(Track);
return 0;
}
public long StopAudioOut(ServiceCtx Context)
{
AudioOut.Stop(Track);
return 0;
}
public long AppendAudioOutBuffer(ServiceCtx Context)
{
long Tag = Context.RequestData.ReadInt64();
AudioOutData Data = AMemoryHelper.Read<AudioOutData>(
Context.Memory,
Context.Request.SendBuff[0].Position);
byte[] Buffer = Context.Memory.ReadBytes(
Data.SampleBufferPtr,
Data.SampleBufferSize);
AudioOut.AppendBuffer(Track, Tag, Buffer);
return 0;
}
public long RegisterBufferEvent(ServiceCtx Context)
{
int Handle = Context.Process.HandleTable.OpenHandle(ReleaseEvent);
Context.Response.HandleDesc = IpcHandleDesc.MakeCopy(Handle);
return 0;
}
public long GetReleasedAudioOutBuffer(ServiceCtx Context)
{
long Position = Context.Request.ReceiveBuff[0].Position;
long Size = Context.Request.ReceiveBuff[0].Size;
uint Count = (uint)((ulong)Size >> 3);
long[] ReleasedBuffers = AudioOut.GetReleasedBuffers(Track, (int)Count);
for (uint Index = 0; Index < Count; Index++)
{
long Tag = 0;
if (Index < ReleasedBuffers.Length)
{
Tag = ReleasedBuffers[Index];
}
Context.Memory.WriteInt64(Position + Index * 8, Tag);
}
Context.ResponseData.Write(ReleasedBuffers.Length);
return 0;
}
public long ContainsAudioOutBuffer(ServiceCtx Context)
{
long Tag = Context.RequestData.ReadInt64();
Context.ResponseData.Write(AudioOut.ContainsBuffer(Track, Tag) ? 1 : 0);
return 0;
}
public long AppendAudioOutBufferEx(ServiceCtx Context)
{
Context.Ns.Log.PrintStub(LogClass.ServiceAudio, "Stubbed.");
return 0;
}
public long GetReleasedAudioOutBufferEx(ServiceCtx Context)
{
Context.Ns.Log.PrintStub(LogClass.ServiceAudio, "Stubbed.");
return 0;
}
public void Dispose()
{
Dispose(true);
}
protected virtual void Dispose(bool Disposing)
{
if (Disposing)
{
AudioOut.CloseTrack(Track);
ReleaseEvent.Dispose();
}
}
}
}

View file

@ -0,0 +1,115 @@
using ChocolArm64.Memory;
using Ryujinx.Audio;
using Ryujinx.HLE.Logging;
using Ryujinx.HLE.OsHle.Handles;
using Ryujinx.HLE.OsHle.Ipc;
using System.Collections.Generic;
using System.Text;
namespace Ryujinx.HLE.OsHle.Services.Aud
{
class IAudioOutManager : IpcService
{
private const string DefaultAudioOutput = "DeviceOut";
private Dictionary<int, ServiceProcessRequest> m_Commands;
public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
public IAudioOutManager()
{
m_Commands = new Dictionary<int, ServiceProcessRequest>()
{
{ 0, ListAudioOuts },
{ 1, OpenAudioOut }
};
}
public long ListAudioOuts(ServiceCtx Context)
{
long Position = Context.Request.ReceiveBuff[0].Position;
long Size = Context.Request.ReceiveBuff[0].Size;
int NameCount = 0;
byte[] DeviceNameBuffer = Encoding.ASCII.GetBytes(DefaultAudioOutput + "\0");
if ((ulong)DeviceNameBuffer.Length <= (ulong)Size)
{
Context.Memory.WriteBytes(Position, DeviceNameBuffer);
NameCount++;
}
else
{
Context.Ns.Log.PrintError(LogClass.ServiceAudio, $"Output buffer size {Size} too small!");
}
Context.ResponseData.Write(NameCount);
return 0;
}
public long OpenAudioOut(ServiceCtx Context)
{
IAalOutput AudioOut = Context.Ns.AudioOut;
string DeviceName = AMemoryHelper.ReadAsciiString(
Context.Memory,
Context.Request.SendBuff[0].Position,
Context.Request.SendBuff[0].Size);
if (DeviceName == string.Empty)
{
DeviceName = DefaultAudioOutput;
}
long Position = Context.Request.ReceiveBuff[0].Position;
long Size = Context.Request.ReceiveBuff[0].Size;
byte[] DeviceNameBuffer = Encoding.ASCII.GetBytes(DeviceName + "\0");
if ((ulong)DeviceNameBuffer.Length <= (ulong)Size)
{
Context.Memory.WriteBytes(Position, DeviceNameBuffer);
}
else
{
Context.Ns.Log.PrintError(LogClass.ServiceAudio, $"Output buffer size {Size} too small!");
}
int SampleRate = Context.RequestData.ReadInt32();
int Channels = Context.RequestData.ReadInt32();
Channels = (ushort)(Channels >> 16);
if (SampleRate == 0)
{
SampleRate = 48000;
}
if (Channels < 1 || Channels > 2)
{
Channels = 2;
}
KEvent ReleaseEvent = new KEvent();
ReleaseCallback Callback = () =>
{
ReleaseEvent.WaitEvent.Set();
};
int Track = AudioOut.OpenTrack(SampleRate, Channels, Callback, out AudioFormat Format);
MakeObject(Context, new IAudioOut(AudioOut, ReleaseEvent, Track));
Context.ResponseData.Write(SampleRate);
Context.ResponseData.Write(Channels);
Context.ResponseData.Write((int)Format);
Context.ResponseData.Write((int)PlaybackState.Stopped);
return 0;
}
}
}

View file

@ -0,0 +1,92 @@
using Ryujinx.HLE.Logging;
using Ryujinx.HLE.OsHle.Handles;
using Ryujinx.HLE.OsHle.Ipc;
using System;
using System.Collections.Generic;
namespace Ryujinx.HLE.OsHle.Services.Aud
{
class IAudioRenderer : IpcService, IDisposable
{
private Dictionary<int, ServiceProcessRequest> m_Commands;
public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
private KEvent UpdateEvent;
public IAudioRenderer()
{
m_Commands = new Dictionary<int, ServiceProcessRequest>()
{
{ 4, RequestUpdateAudioRenderer },
{ 5, StartAudioRenderer },
{ 6, StopAudioRenderer },
{ 7, QuerySystemEvent }
};
UpdateEvent = new KEvent();
}
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);
}
//TODO: We shouldn't be signaling this here.
UpdateEvent.WaitEvent.Set();
return 0;
}
public long StartAudioRenderer(ServiceCtx Context)
{
Context.Ns.Log.PrintStub(LogClass.ServiceAudio, "Stubbed.");
return 0;
}
public long StopAudioRenderer(ServiceCtx Context)
{
Context.Ns.Log.PrintStub(LogClass.ServiceAudio, "Stubbed.");
return 0;
}
public long QuerySystemEvent(ServiceCtx Context)
{
int Handle = Context.Process.HandleTable.OpenHandle(UpdateEvent);
Context.Response.HandleDesc = IpcHandleDesc.MakeCopy(Handle);
return 0;
}
public void Dispose()
{
Dispose(true);
}
protected virtual void Dispose(bool Disposing)
{
if (Disposing)
{
UpdateEvent.Dispose();
}
}
}
}

View file

@ -0,0 +1,135 @@
using Ryujinx.HLE.Logging;
using Ryujinx.HLE.OsHle.Ipc;
using System.Collections.Generic;
namespace Ryujinx.HLE.OsHle.Services.Aud
{
class IAudioRendererManager : IpcService
{
private const int Rev0Magic = ('R' << 0) |
('E' << 8) |
('V' << 16) |
('0' << 24);
private Dictionary<int, ServiceProcessRequest> m_Commands;
public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
public IAudioRendererManager()
{
m_Commands = new Dictionary<int, ServiceProcessRequest>()
{
{ 0, OpenAudioRenderer },
{ 1, GetAudioRendererWorkBufferSize },
{ 2, GetAudioDevice }
};
}
public long OpenAudioRenderer(ServiceCtx Context)
{
//Same buffer as GetAudioRendererWorkBufferSize is receive here.
MakeObject(Context, new IAudioRenderer());
return 0;
}
public long GetAudioRendererWorkBufferSize(ServiceCtx Context)
{
long SampleRate = Context.RequestData.ReadUInt32();
long Unknown4 = Context.RequestData.ReadUInt32();
long Unknown8 = Context.RequestData.ReadUInt32();
long UnknownC = Context.RequestData.ReadUInt32();
long Unknown10 = Context.RequestData.ReadUInt32(); //VoiceCount
long Unknown14 = Context.RequestData.ReadUInt32(); //SinkCount
long Unknown18 = Context.RequestData.ReadUInt32(); //EffectCount
long Unknown1c = Context.RequestData.ReadUInt32(); //Boolean
long Unknown20 = Context.RequestData.ReadUInt32(); //Not used here in FW3.0.1 - Boolean
long Unknown24 = Context.RequestData.ReadUInt32();
long Unknown28 = Context.RequestData.ReadUInt32(); //SplitterCount
long Unknown2c = Context.RequestData.ReadUInt32(); //Not used here in FW3.0.1
int RevMagic = Context.RequestData.ReadInt32();
int Version = (RevMagic - Rev0Magic) >> 24;
if (Version <= 3) //REV3 Max is supported
{
long Size = RoundUp(Unknown8 * 4, 64);
Size += (UnknownC << 10);
Size += (UnknownC + 1) * 0x940;
Size += Unknown10 * 0x3F0;
Size += RoundUp((UnknownC + 1) * 8, 16);
Size += RoundUp(Unknown10 * 8, 16);
Size += RoundUp((0x3C0 * (Unknown14 + UnknownC) + 4 * Unknown4) * (Unknown8 + 6), 64);
Size += 0x2C0 * (Unknown14 + UnknownC) + 0x30 * (Unknown18 + (4 * Unknown10)) + 0x50;
if (Version >= 3) //IsSplitterSupported
{
Size += RoundUp((NodeStatesGetWorkBufferSize((int)UnknownC + 1) + EdgeMatrixGetWorkBufferSize((int)UnknownC + 1)), 16);
Size += 0xE0 * Unknown28 + 0x20 * Unknown24 + RoundUp(Unknown28 * 4, 16);
}
Size = 0x4C0 * Unknown18 + RoundUp(Size, 64) + 0x170 * Unknown14 + ((Unknown10 << 8) | 0x40);
if (Unknown1c >= 1)
{
Size += ((((Unknown18 + Unknown14 + Unknown10 + UnknownC + 1) * 16) + 0x658) * (Unknown1c + 1) + 0x13F) & ~0x3FL;
}
long WorkBufferSize = (Size + 0x1907D) & ~0xFFFL;
Context.ResponseData.Write(WorkBufferSize);
Context.Ns.Log.PrintDebug(LogClass.ServiceAudio, $"WorkBufferSize is 0x{WorkBufferSize:x16}.");
return 0;
}
else
{
Context.ResponseData.Write(0L);
Context.Ns.Log.PrintError(LogClass.ServiceAudio, $"Library Revision 0x{RevMagic:x8} is not supported!");
return 0x499;
}
}
private static long RoundUp(long Value, int Size)
{
return (Value + (Size - 1)) & ~((long)Size - 1);
}
private static int NodeStatesGetWorkBufferSize(int Value)
{
int Result = (int)RoundUp(Value, 64);
if (Result < 0)
{
Result |= 7;
}
return 4 * (Value * Value) + 0x12 * Value + 2 * (Result / 8);
}
private static int EdgeMatrixGetWorkBufferSize(int Value)
{
int Result = (int)RoundUp(Value * Value, 64);
if (Result < 0)
{
Result |= 7;
}
return Result / 8;
}
public long GetAudioDevice(ServiceCtx Context)
{
long UserId = Context.RequestData.ReadInt64();
MakeObject(Context, new IAudioDevice());
return 0;
}
}
}

View file

@ -0,0 +1,8 @@
namespace Ryujinx.HLE.OsHle.Services.Bsd
{
//bsd_errno == (SocketException.ErrorCode - 10000)
public enum BsdError
{
Timeout = 60
}
}

View file

@ -0,0 +1,18 @@
using System.Net;
using System.Net.Sockets;
namespace Ryujinx.HLE.OsHle.Services.Bsd
{
class BsdSocket
{
public int Family;
public int Type;
public int Protocol;
public IPAddress IpAddress;
public IPEndPoint RemoteEP;
public Socket Handle;
}
}

View file

@ -0,0 +1,445 @@
using Ryujinx.HLE.OsHle.Ipc;
using Ryujinx.HLE.OsHle.Utilities;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Threading.Tasks;
namespace Ryujinx.HLE.OsHle.Services.Bsd
{
class IClient : IpcService
{
private Dictionary<int, ServiceProcessRequest> m_Commands;
public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
private List<BsdSocket> Sockets = new List<BsdSocket>();
public IClient()
{
m_Commands = new Dictionary<int, ServiceProcessRequest>()
{
{ 0, Initialize },
{ 1, StartMonitoring },
{ 2, Socket },
{ 6, Poll },
{ 8, Recv },
{ 10, Send },
{ 11, SendTo },
{ 12, Accept },
{ 13, Bind },
{ 14, Connect },
{ 18, Listen },
{ 21, SetSockOpt },
{ 26, Close }
};
}
//(u32, u32, u32, u32, u32, u32, u32, u32, u64 pid, u64 transferMemorySize, pid, KObject) -> u32 bsd_errno
public long Initialize(ServiceCtx Context)
{
/*
typedef struct {
u32 version; // Observed 1 on 2.0 LibAppletWeb, 2 on 3.0.
u32 tcp_tx_buf_size; // Size of the TCP transfer (send) buffer (initial or fixed).
u32 tcp_rx_buf_size; // Size of the TCP recieve buffer (initial or fixed).
u32 tcp_tx_buf_max_size; // Maximum size of the TCP transfer (send) buffer. If it is 0, the size of the buffer is fixed to its initial value.
u32 tcp_rx_buf_max_size; // Maximum size of the TCP receive buffer. If it is 0, the size of the buffer is fixed to its initial value.
u32 udp_tx_buf_size; // Size of the UDP transfer (send) buffer (typically 0x2400 bytes).
u32 udp_rx_buf_size; // Size of the UDP receive buffer (typically 0xA500 bytes).
u32 sb_efficiency; // Number of buffers for each socket (standard values range from 1 to 8).
} BsdBufferConfig;
*/
Context.ResponseData.Write(0);
//Todo: Stub
return 0;
}
//(u64, pid)
public long StartMonitoring(ServiceCtx Context)
{
//Todo: Stub
return 0;
}
//(u32 domain, u32 type, u32 protocol) -> (i32 ret, u32 bsd_errno)
public long Socket(ServiceCtx Context)
{
BsdSocket NewBsdSocket = new BsdSocket
{
Family = Context.RequestData.ReadInt32(),
Type = Context.RequestData.ReadInt32(),
Protocol = Context.RequestData.ReadInt32()
};
Sockets.Add(NewBsdSocket);
NewBsdSocket.Handle = new Socket((AddressFamily)NewBsdSocket.Family,
(SocketType)NewBsdSocket.Type,
(ProtocolType)NewBsdSocket.Protocol);
Context.ResponseData.Write(Sockets.Count - 1);
Context.ResponseData.Write(0);
return 0;
}
//(u32, u32, buffer<unknown, 0x21, 0>) -> (i32 ret, u32 bsd_errno, buffer<unknown, 0x22, 0>)
public long Poll(ServiceCtx Context)
{
int PollCount = Context.RequestData.ReadInt32();
int TimeOut = Context.RequestData.ReadInt32();
//https://github.com/torvalds/linux/blob/master/include/uapi/asm-generic/poll.h
//https://msdn.microsoft.com/fr-fr/library/system.net.sockets.socket.poll(v=vs.110).aspx
//https://github.com/switchbrew/libnx/blob/e0457c4534b3c37426d83e1a620f82cb28c3b528/nx/source/services/bsd.c#L343
//https://github.com/TuxSH/ftpd/blob/switch_pr/source/ftp.c#L1634
//https://linux.die.net/man/2/poll
byte[] SentBuffer = Context.Memory.ReadBytes(Context.Request.SendBuff[0].Position,
Context.Request.SendBuff[0].Size);
int SocketId = Get32(SentBuffer, 0);
int RequestedEvents = Get16(SentBuffer, 4);
int ReturnedEvents = Get16(SentBuffer, 6);
//Todo: Stub - Need to implemented the Type-22 buffer.
Context.ResponseData.Write(1);
Context.ResponseData.Write(0);
return 0;
}
//(u32 socket, u32 flags) -> (i32 ret, u32 bsd_errno, buffer<i8, 0x22, 0> message)
public long Recv(ServiceCtx Context)
{
int SocketId = Context.RequestData.ReadInt32();
int SocketFlags = Context.RequestData.ReadInt32();
byte[] ReceivedBuffer = new byte[Context.Request.ReceiveBuff[0].Size];
try
{
int BytesRead = Sockets[SocketId].Handle.Receive(ReceivedBuffer);
//Logging.Debug("Received Buffer:" + Environment.NewLine + Logging.HexDump(ReceivedBuffer));
Context.Memory.WriteBytes(Context.Request.ReceiveBuff[0].Position, ReceivedBuffer);
Context.ResponseData.Write(BytesRead);
Context.ResponseData.Write(0);
}
catch (SocketException Ex)
{
Context.ResponseData.Write(-1);
Context.ResponseData.Write(Ex.ErrorCode - 10000);
}
return 0;
}
//(u32 socket, u32 flags, buffer<i8, 0x21, 0>) -> (i32 ret, u32 bsd_errno)
public long Send(ServiceCtx Context)
{
int SocketId = Context.RequestData.ReadInt32();
int SocketFlags = Context.RequestData.ReadInt32();
byte[] SentBuffer = Context.Memory.ReadBytes(Context.Request.SendBuff[0].Position,
Context.Request.SendBuff[0].Size);
try
{
//Logging.Debug("Sent Buffer:" + Environment.NewLine + Logging.HexDump(SentBuffer));
int BytesSent = Sockets[SocketId].Handle.Send(SentBuffer);
Context.ResponseData.Write(BytesSent);
Context.ResponseData.Write(0);
}
catch (SocketException Ex)
{
Context.ResponseData.Write(-1);
Context.ResponseData.Write(Ex.ErrorCode - 10000);
}
return 0;
}
//(u32 socket, u32 flags, buffer<i8, 0x21, 0>, buffer<sockaddr, 0x21, 0>) -> (i32 ret, u32 bsd_errno)
public long SendTo(ServiceCtx Context)
{
int SocketId = Context.RequestData.ReadInt32();
int SocketFlags = Context.RequestData.ReadInt32();
byte[] SentBuffer = Context.Memory.ReadBytes(Context.Request.SendBuff[0].Position,
Context.Request.SendBuff[0].Size);
byte[] AddressBuffer = Context.Memory.ReadBytes(Context.Request.SendBuff[1].Position,
Context.Request.SendBuff[1].Size);
if (!Sockets[SocketId].Handle.Connected)
{
try
{
ParseAddrBuffer(SocketId, AddressBuffer);
Sockets[SocketId].Handle.Connect(Sockets[SocketId].RemoteEP);
}
catch (SocketException Ex)
{
Context.ResponseData.Write(-1);
Context.ResponseData.Write(Ex.ErrorCode - 10000);
}
}
try
{
//Logging.Debug("Sent Buffer:" + Environment.NewLine + Logging.HexDump(SentBuffer));
int BytesSent = Sockets[SocketId].Handle.Send(SentBuffer);
Context.ResponseData.Write(BytesSent);
Context.ResponseData.Write(0);
}
catch (SocketException Ex)
{
Context.ResponseData.Write(-1);
Context.ResponseData.Write(Ex.ErrorCode - 10000);
}
return 0;
}
//(u32 socket) -> (i32 ret, u32 bsd_errno, u32 addrlen, buffer<sockaddr, 0x22, 0> addr)
public long Accept(ServiceCtx Context)
{
int SocketId = Context.RequestData.ReadInt32();
long AddrBufferPtr = Context.Request.ReceiveBuff[0].Position;
Socket HandleAccept = null;
Task TimeOut = Task.Factory.StartNew(() =>
{
try
{
HandleAccept = Sockets[SocketId].Handle.Accept();
}
catch (SocketException Ex)
{
Context.ResponseData.Write(-1);
Context.ResponseData.Write(Ex.ErrorCode - 10000);
}
});
TimeOut.Wait(10000);
if (HandleAccept != null)
{
BsdSocket NewBsdSocket = new BsdSocket
{
IpAddress = ((IPEndPoint)Sockets[SocketId].Handle.LocalEndPoint).Address,
RemoteEP = ((IPEndPoint)Sockets[SocketId].Handle.LocalEndPoint),
Handle = HandleAccept
};
Sockets.Add(NewBsdSocket);
using (MemoryStream MS = new MemoryStream())
{
BinaryWriter Writer = new BinaryWriter(MS);
Writer.Write((byte)0);
Writer.Write((byte)NewBsdSocket.Handle.AddressFamily);
Writer.Write((short)((IPEndPoint)NewBsdSocket.Handle.LocalEndPoint).Port);
byte[] IpAddress = NewBsdSocket.IpAddress.GetAddressBytes();
Writer.Write(IpAddress);
Context.Memory.WriteBytes(AddrBufferPtr, MS.ToArray());
Context.ResponseData.Write(Sockets.Count - 1);
Context.ResponseData.Write(0);
Context.ResponseData.Write(MS.Length);
}
}
else
{
Context.ResponseData.Write(-1);
Context.ResponseData.Write((int)BsdError.Timeout);
}
return 0;
}
//(u32 socket, buffer<sockaddr, 0x21, 0>) -> (i32 ret, u32 bsd_errno)
public long Bind(ServiceCtx Context)
{
int SocketId = Context.RequestData.ReadInt32();
byte[] AddressBuffer = Context.Memory.ReadBytes(Context.Request.SendBuff[0].Position,
Context.Request.SendBuff[0].Size);
try
{
ParseAddrBuffer(SocketId, AddressBuffer);
Context.ResponseData.Write(0);
Context.ResponseData.Write(0);
}
catch (SocketException Ex)
{
Context.ResponseData.Write(-1);
Context.ResponseData.Write(Ex.ErrorCode - 10000);
}
return 0;
}
//(u32 socket, buffer<sockaddr, 0x21, 0>) -> (i32 ret, u32 bsd_errno)
public long Connect(ServiceCtx Context)
{
int SocketId = Context.RequestData.ReadInt32();
byte[] AddressBuffer = Context.Memory.ReadBytes(Context.Request.SendBuff[0].Position,
Context.Request.SendBuff[0].Size);
try
{
ParseAddrBuffer(SocketId, AddressBuffer);
Sockets[SocketId].Handle.Connect(Sockets[SocketId].RemoteEP);
Context.ResponseData.Write(0);
Context.ResponseData.Write(0);
}
catch (SocketException Ex)
{
Context.ResponseData.Write(-1);
Context.ResponseData.Write(Ex.ErrorCode - 10000);
}
return 0;
}
//(u32 socket, u32 backlog) -> (i32 ret, u32 bsd_errno)
public long Listen(ServiceCtx Context)
{
int SocketId = Context.RequestData.ReadInt32();
int BackLog = Context.RequestData.ReadInt32();
try
{
Sockets[SocketId].Handle.Bind(Sockets[SocketId].RemoteEP);
Sockets[SocketId].Handle.Listen(BackLog);
Context.ResponseData.Write(0);
Context.ResponseData.Write(0);
}
catch (SocketException Ex)
{
Context.ResponseData.Write(-1);
Context.ResponseData.Write(Ex.ErrorCode - 10000);
}
return 0;
}
//(u32 socket, u32 level, u32 option_name, buffer<unknown, 0x21, 0>) -> (i32 ret, u32 bsd_errno)
public long SetSockOpt(ServiceCtx Context)
{
int SocketId = Context.RequestData.ReadInt32();
SocketOptionLevel SocketLevel = (SocketOptionLevel)Context.RequestData.ReadInt32();
SocketOptionName SocketOptionName = (SocketOptionName)Context.RequestData.ReadInt32();
byte[] SocketOptionValue = Context.Memory.ReadBytes(Context.Request.PtrBuff[0].Position,
Context.Request.PtrBuff[0].Size);
int OptionValue = Get32(SocketOptionValue, 0);
try
{
Sockets[SocketId].Handle.SetSocketOption(SocketLevel, SocketOptionName, OptionValue);
Context.ResponseData.Write(0);
Context.ResponseData.Write(0);
}
catch (SocketException Ex)
{
Context.ResponseData.Write(-1);
Context.ResponseData.Write(Ex.ErrorCode - 10000);
}
return 0;
}
//(u32 socket) -> (i32 ret, u32 bsd_errno)
public long Close(ServiceCtx Context)
{
int SocketId = Context.RequestData.ReadInt32();
try
{
Sockets[SocketId].Handle.Close();
Sockets[SocketId] = null;
Context.ResponseData.Write(0);
Context.ResponseData.Write(0);
}
catch (SocketException Ex)
{
Context.ResponseData.Write(-1);
Context.ResponseData.Write(Ex.ErrorCode - 10000);
}
return 0;
}
public void ParseAddrBuffer(int SocketId, byte[] AddrBuffer)
{
using (MemoryStream MS = new MemoryStream(AddrBuffer))
{
BinaryReader Reader = new BinaryReader(MS);
int Size = Reader.ReadByte();
int Family = Reader.ReadByte();
int Port = EndianSwap.Swap16(Reader.ReadInt16());
string IpAddress = Reader.ReadByte().ToString() + "." +
Reader.ReadByte().ToString() + "." +
Reader.ReadByte().ToString() + "." +
Reader.ReadByte().ToString();
Sockets[SocketId].IpAddress = IPAddress.Parse(IpAddress);
Sockets[SocketId].RemoteEP = new IPEndPoint(Sockets[SocketId].IpAddress, Port);
}
}
private int Get16(byte[] Data, int Address)
{
return
Data[Address + 0] << 0 |
Data[Address + 1] << 8;
}
private int Get32(byte[] Data, int Address)
{
return
Data[Address + 0] << 0 |
Data[Address + 1] << 8 |
Data[Address + 2] << 16 |
Data[Address + 3] << 24;
}
}
}

View file

@ -0,0 +1,20 @@
using Ryujinx.HLE.OsHle.Ipc;
using System.Collections.Generic;
namespace Ryujinx.HLE.OsHle.Services.Caps
{
class IAlbumAccessorService : IpcService
{
private Dictionary<int, ServiceProcessRequest> m_Commands;
public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
public IAlbumAccessorService()
{
m_Commands = new Dictionary<int, ServiceProcessRequest>()
{
//...
};
}
}
}

View file

@ -0,0 +1,20 @@
using Ryujinx.HLE.OsHle.Ipc;
using System.Collections.Generic;
namespace Ryujinx.HLE.OsHle.Services.Caps
{
class IScreenshotService : IpcService
{
private Dictionary<int, ServiceProcessRequest> m_Commands;
public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
public IScreenshotService()
{
m_Commands = new Dictionary<int, ServiceProcessRequest>()
{
//...
};
}
}
}

View file

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

View file

@ -0,0 +1,27 @@
using Ryujinx.HLE.OsHle.Ipc;
using System.Collections.Generic;
namespace Ryujinx.HLE.OsHle.Services.Friend
{
class IServiceCreator : IpcService
{
private Dictionary<int, ServiceProcessRequest> m_Commands;
public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
public IServiceCreator()
{
m_Commands = new Dictionary<int, ServiceProcessRequest>()
{
{ 0, CreateFriendService }
};
}
public static long CreateFriendService(ServiceCtx Context)
{
MakeObject(Context, new IFriendService());
return 0;
}
}
}

View file

@ -0,0 +1,9 @@
namespace Ryujinx.HLE.OsHle.Services.FspSrv
{
static class FsErr
{
public const int PathDoesNotExist = 1;
public const int PathAlreadyExists = 2;
public const int PathAlreadyInUse = 7;
}
}

View file

@ -0,0 +1,116 @@
using Ryujinx.HLE.OsHle.Ipc;
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
namespace Ryujinx.HLE.OsHle.Services.FspSrv
{
class IDirectory : IpcService, IDisposable
{
private const int DirectoryEntrySize = 0x310;
private Dictionary<int, ServiceProcessRequest> m_Commands;
public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
private List<string> DirectoryEntries;
private int CurrentItemIndex;
public event EventHandler<EventArgs> Disposed;
public string HostPath { get; private set; }
public IDirectory(string HostPath, int Flags)
{
m_Commands = new Dictionary<int, ServiceProcessRequest>()
{
{ 0, Read },
{ 1, GetEntryCount }
};
this.HostPath = HostPath;
DirectoryEntries = new List<string>();
if ((Flags & 1) != 0)
{
DirectoryEntries.AddRange(Directory.GetDirectories(HostPath));
}
if ((Flags & 2) != 0)
{
DirectoryEntries.AddRange(Directory.GetFiles(HostPath));
}
CurrentItemIndex = 0;
}
public long Read(ServiceCtx Context)
{
long BufferPosition = Context.Request.ReceiveBuff[0].Position;
long BufferLen = Context.Request.ReceiveBuff[0].Size;
int MaxReadCount = (int)(BufferLen / DirectoryEntrySize);
int Count = Math.Min(DirectoryEntries.Count - CurrentItemIndex, MaxReadCount);
for (int Index = 0; Index < Count; Index++)
{
long Position = BufferPosition + Index * DirectoryEntrySize;
WriteDirectoryEntry(Context, Position, DirectoryEntries[CurrentItemIndex++]);
}
Context.ResponseData.Write((long)Count);
return 0;
}
private void WriteDirectoryEntry(ServiceCtx Context, long Position, string FullPath)
{
for (int Offset = 0; Offset < 0x300; Offset += 8)
{
Context.Memory.WriteInt64(Position + Offset, 0);
}
byte[] NameBuffer = Encoding.UTF8.GetBytes(Path.GetFileName(FullPath));
Context.Memory.WriteBytes(Position, NameBuffer);
int Type = 0;
long Size = 0;
if (File.Exists(FullPath))
{
Type = 1;
Size = new FileInfo(FullPath).Length;
}
Context.Memory.WriteInt32(Position + 0x300, 0); //Padding?
Context.Memory.WriteInt32(Position + 0x304, Type);
Context.Memory.WriteInt64(Position + 0x308, Size);
}
public long GetEntryCount(ServiceCtx Context)
{
Context.ResponseData.Write((long)DirectoryEntries.Count);
return 0;
}
public void Dispose()
{
Dispose(true);
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
Disposed?.Invoke(this, EventArgs.Empty);
}
}
}
}

View file

@ -0,0 +1,110 @@
using Ryujinx.HLE.OsHle.Ipc;
using System;
using System.Collections.Generic;
using System.IO;
namespace Ryujinx.HLE.OsHle.Services.FspSrv
{
class IFile : IpcService, IDisposable
{
private Dictionary<int, ServiceProcessRequest> m_Commands;
public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
private Stream BaseStream;
public event EventHandler<EventArgs> Disposed;
public string HostPath { get; private set; }
public IFile(Stream BaseStream, string HostPath)
{
m_Commands = new Dictionary<int, ServiceProcessRequest>()
{
{ 0, Read },
{ 1, Write },
{ 2, Flush },
{ 3, SetSize },
{ 4, GetSize }
};
this.BaseStream = BaseStream;
this.HostPath = HostPath;
}
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);
Context.Memory.WriteBytes(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 = Context.Memory.ReadBytes(Position, Size);
BaseStream.Seek(Offset, SeekOrigin.Begin);
BaseStream.Write(Data, 0, (int)Size);
return 0;
}
public long Flush(ServiceCtx Context)
{
BaseStream.Flush();
return 0;
}
public long SetSize(ServiceCtx Context)
{
long Size = Context.RequestData.ReadInt64();
BaseStream.SetLength(Size);
return 0;
}
public long GetSize(ServiceCtx Context)
{
Context.ResponseData.Write(BaseStream.Length);
return 0;
}
public void Dispose()
{
Dispose(true);
}
protected virtual void Dispose(bool disposing)
{
if (disposing && BaseStream != null)
{
BaseStream.Dispose();
Disposed?.Invoke(this, EventArgs.Empty);
}
}
}
}

View file

@ -0,0 +1,399 @@
using Ryujinx.HLE.OsHle.Ipc;
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using static Ryujinx.HLE.OsHle.ErrorCode;
namespace Ryujinx.HLE.OsHle.Services.FspSrv
{
class IFileSystem : IpcService
{
private Dictionary<int, ServiceProcessRequest> m_Commands;
public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
private HashSet<string> OpenPaths;
private string Path;
public IFileSystem(string Path)
{
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 }
};
OpenPaths = new HashSet<string>();
this.Path = Path;
}
public long CreateFile(ServiceCtx Context)
{
long Position = Context.Request.PtrBuff[0].Position;
string Name = ReadUtf8String(Context);
long Mode = Context.RequestData.ReadInt64();
int Size = Context.RequestData.ReadInt32();
string FileName = Context.Ns.VFs.GetFullPath(Path, Name);
if (FileName == null)
{
return MakeError(ErrorModule.Fs, FsErr.PathDoesNotExist);
}
if (File.Exists(FileName))
{
return MakeError(ErrorModule.Fs, FsErr.PathAlreadyExists);
}
if (IsPathAlreadyInUse(FileName))
{
return MakeError(ErrorModule.Fs, FsErr.PathAlreadyInUse);
}
using (FileStream NewFile = File.Create(FileName))
{
NewFile.SetLength(Size);
}
return 0;
}
public long DeleteFile(ServiceCtx Context)
{
long Position = Context.Request.PtrBuff[0].Position;
string Name = ReadUtf8String(Context);
string FileName = Context.Ns.VFs.GetFullPath(Path, Name);
if (!File.Exists(FileName))
{
return MakeError(ErrorModule.Fs, FsErr.PathDoesNotExist);
}
if (IsPathAlreadyInUse(FileName))
{
return MakeError(ErrorModule.Fs, FsErr.PathAlreadyInUse);
}
File.Delete(FileName);
return 0;
}
public long CreateDirectory(ServiceCtx Context)
{
long Position = Context.Request.PtrBuff[0].Position;
string Name = ReadUtf8String(Context);
string DirName = Context.Ns.VFs.GetFullPath(Path, Name);
if (DirName == null)
{
return MakeError(ErrorModule.Fs, FsErr.PathDoesNotExist);
}
if (Directory.Exists(DirName))
{
return MakeError(ErrorModule.Fs, FsErr.PathAlreadyExists);
}
if (IsPathAlreadyInUse(DirName))
{
return MakeError(ErrorModule.Fs, FsErr.PathAlreadyInUse);
}
Directory.CreateDirectory(DirName);
return 0;
}
public long DeleteDirectory(ServiceCtx Context)
{
return DeleteDirectory(Context, false);
}
public long DeleteDirectoryRecursively(ServiceCtx Context)
{
return DeleteDirectory(Context, true);
}
private long DeleteDirectory(ServiceCtx Context, bool Recursive)
{
long Position = Context.Request.PtrBuff[0].Position;
string Name = ReadUtf8String(Context);
string DirName = Context.Ns.VFs.GetFullPath(Path, Name);
if (!Directory.Exists(DirName))
{
return MakeError(ErrorModule.Fs, FsErr.PathDoesNotExist);
}
if (IsPathAlreadyInUse(DirName))
{
return MakeError(ErrorModule.Fs, FsErr.PathAlreadyInUse);
}
Directory.Delete(DirName, Recursive);
return 0;
}
public long RenameFile(ServiceCtx Context)
{
string OldName = ReadUtf8String(Context, 0);
string NewName = ReadUtf8String(Context, 1);
string OldFileName = Context.Ns.VFs.GetFullPath(Path, OldName);
string NewFileName = Context.Ns.VFs.GetFullPath(Path, NewName);
if (!File.Exists(OldFileName))
{
return MakeError(ErrorModule.Fs, FsErr.PathDoesNotExist);
}
if (File.Exists(NewFileName))
{
return MakeError(ErrorModule.Fs, FsErr.PathAlreadyExists);
}
if (IsPathAlreadyInUse(OldFileName))
{
return MakeError(ErrorModule.Fs, FsErr.PathAlreadyInUse);
}
File.Move(OldFileName, NewFileName);
return 0;
}
public long RenameDirectory(ServiceCtx Context)
{
string OldName = ReadUtf8String(Context, 0);
string NewName = ReadUtf8String(Context, 1);
string OldDirName = Context.Ns.VFs.GetFullPath(Path, OldName);
string NewDirName = Context.Ns.VFs.GetFullPath(Path, NewName);
if (!Directory.Exists(OldDirName))
{
return MakeError(ErrorModule.Fs, FsErr.PathDoesNotExist);
}
if (Directory.Exists(NewDirName))
{
return MakeError(ErrorModule.Fs, FsErr.PathAlreadyExists);
}
if (IsPathAlreadyInUse(OldDirName))
{
return MakeError(ErrorModule.Fs, FsErr.PathAlreadyInUse);
}
Directory.Move(OldDirName, NewDirName);
return 0;
}
public long GetEntryType(ServiceCtx Context)
{
long Position = Context.Request.PtrBuff[0].Position;
string Name = ReadUtf8String(Context);
string FileName = Context.Ns.VFs.GetFullPath(Path, Name);
if (File.Exists(FileName))
{
Context.ResponseData.Write(1);
}
else if (Directory.Exists(FileName))
{
Context.ResponseData.Write(0);
}
else
{
Context.ResponseData.Write(0);
return MakeError(ErrorModule.Fs, FsErr.PathDoesNotExist);
}
return 0;
}
public long OpenFile(ServiceCtx Context)
{
long Position = Context.Request.PtrBuff[0].Position;
int FilterFlags = Context.RequestData.ReadInt32();
string Name = ReadUtf8String(Context);
string FileName = Context.Ns.VFs.GetFullPath(Path, Name);
if (!File.Exists(FileName))
{
return MakeError(ErrorModule.Fs, FsErr.PathDoesNotExist);
}
if (IsPathAlreadyInUse(FileName))
{
return MakeError(ErrorModule.Fs, FsErr.PathAlreadyInUse);
}
FileStream Stream = new FileStream(FileName, FileMode.Open);
IFile FileInterface = new IFile(Stream, FileName);
FileInterface.Disposed += RemoveFileInUse;
lock (OpenPaths)
{
OpenPaths.Add(FileName);
}
MakeObject(Context, FileInterface);
return 0;
}
public long OpenDirectory(ServiceCtx Context)
{
long Position = Context.Request.PtrBuff[0].Position;
int FilterFlags = Context.RequestData.ReadInt32();
string Name = ReadUtf8String(Context);
string DirName = Context.Ns.VFs.GetFullPath(Path, Name);
if (!Directory.Exists(DirName))
{
return MakeError(ErrorModule.Fs, FsErr.PathDoesNotExist);
}
if (IsPathAlreadyInUse(DirName))
{
return MakeError(ErrorModule.Fs, FsErr.PathAlreadyInUse);
}
IDirectory DirInterface = new IDirectory(DirName, FilterFlags);
DirInterface.Disposed += RemoveDirectoryInUse;
lock (OpenPaths)
{
OpenPaths.Add(DirName);
}
MakeObject(Context, DirInterface);
return 0;
}
public long Commit(ServiceCtx Context)
{
return 0;
}
public long GetFreeSpaceSize(ServiceCtx Context)
{
long Position = Context.Request.PtrBuff[0].Position;
string Name = ReadUtf8String(Context);
Context.ResponseData.Write(Context.Ns.VFs.GetDrive().AvailableFreeSpace);
return 0;
}
public long GetTotalSpaceSize(ServiceCtx Context)
{
long Position = Context.Request.PtrBuff[0].Position;
string Name = ReadUtf8String(Context);
Context.ResponseData.Write(Context.Ns.VFs.GetDrive().TotalSize);
return 0;
}
private bool IsPathAlreadyInUse(string Path)
{
lock (OpenPaths)
{
return OpenPaths.Contains(Path);
}
}
private void RemoveFileInUse(object sender, EventArgs e)
{
IFile FileInterface = (IFile)sender;
lock (OpenPaths)
{
FileInterface.Disposed -= RemoveFileInUse;
OpenPaths.Remove(FileInterface.HostPath);
}
}
private void RemoveDirectoryInUse(object sender, EventArgs e)
{
IDirectory DirInterface = (IDirectory)sender;
lock (OpenPaths)
{
DirInterface.Disposed -= RemoveDirectoryInUse;
OpenPaths.Remove(DirInterface.HostPath);
}
}
private string ReadUtf8String(ServiceCtx Context, int Index = 0)
{
long Position = Context.Request.PtrBuff[Index].Position;
long Size = Context.Request.PtrBuff[Index].Size;
using (MemoryStream MS = new MemoryStream())
{
while (Size-- > 0)
{
byte Value = Context.Memory.ReadByte(Position++);
if (Value == 0)
{
break;
}
MS.WriteByte(Value);
}
return Encoding.UTF8.GetString(MS.ToArray());
}
}
}
}

View file

@ -0,0 +1,74 @@
using Ryujinx.HLE.Logging;
using Ryujinx.HLE.OsHle.Ipc;
using System.Collections.Generic;
namespace Ryujinx.HLE.OsHle.Services.FspSrv
{
class IFileSystemProxy : IpcService
{
private Dictionary<int, ServiceProcessRequest> m_Commands;
public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
public IFileSystemProxy()
{
m_Commands = new Dictionary<int, ServiceProcessRequest>()
{
{ 1, SetCurrentProcess },
{ 18, OpenSdCardFileSystem },
{ 22, CreateSaveDataFileSystem },
{ 51, OpenSaveDataFileSystem },
{ 200, OpenDataStorageByCurrentProcess },
{ 203, OpenPatchDataStorageByCurrentProcess },
{ 1005, GetGlobalAccessLogMode }
};
}
public long SetCurrentProcess(ServiceCtx Context)
{
return 0;
}
public long OpenSdCardFileSystem(ServiceCtx Context)
{
MakeObject(Context, new IFileSystem(Context.Ns.VFs.GetSdCardPath()));
return 0;
}
public long CreateSaveDataFileSystem(ServiceCtx Context)
{
Context.Ns.Log.PrintStub(LogClass.ServiceFs, "Stubbed.");
return 0;
}
public long OpenSaveDataFileSystem(ServiceCtx Context)
{
MakeObject(Context, new IFileSystem(Context.Ns.VFs.GetGameSavesPath()));
return 0;
}
public long OpenDataStorageByCurrentProcess(ServiceCtx Context)
{
MakeObject(Context, new IStorage(Context.Ns.VFs.RomFs));
return 0;
}
public long OpenPatchDataStorageByCurrentProcess(ServiceCtx Context)
{
MakeObject(Context, new IStorage(Context.Ns.VFs.RomFs));
return 0;
}
public long GetGlobalAccessLogMode(ServiceCtx Context)
{
Context.ResponseData.Write(0);
return 0;
}
}
}

View file

@ -0,0 +1,51 @@
using Ryujinx.HLE.OsHle.Ipc;
using System.Collections.Generic;
using System.IO;
namespace Ryujinx.HLE.OsHle.Services.FspSrv
{
class IStorage : IpcService
{
private Dictionary<int, ServiceProcessRequest> m_Commands;
public override 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);
Context.Memory.WriteBytes(BuffDesc.Position, Data);
}
return 0;
}
}
}

View file

@ -0,0 +1,27 @@
using Ryujinx.HLE.OsHle.Ipc;
using System.Collections.Generic;
namespace Ryujinx.HLE.OsHle.Services.Hid
{
class IActiveApplicationDeviceList : IpcService
{
private Dictionary<int, ServiceProcessRequest> m_Commands;
public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
public IActiveApplicationDeviceList()
{
m_Commands = new Dictionary<int, ServiceProcessRequest>()
{
{ 0, ActivateVibrationDevice }
};
}
public long ActivateVibrationDevice(ServiceCtx Context)
{
int VibrationDeviceHandle = Context.RequestData.ReadInt32();
return 0;
}
}
}

View file

@ -0,0 +1,34 @@
using Ryujinx.HLE.OsHle.Handles;
using Ryujinx.HLE.OsHle.Ipc;
using System.Collections.Generic;
namespace Ryujinx.HLE.OsHle.Services.Hid
{
class IAppletResource : IpcService
{
private Dictionary<int, ServiceProcessRequest> m_Commands;
public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
private HSharedMem HidSharedMem;
public IAppletResource(HSharedMem HidSharedMem)
{
m_Commands = new Dictionary<int, ServiceProcessRequest>()
{
{ 0, GetSharedMemoryHandle }
};
this.HidSharedMem = HidSharedMem;
}
public long GetSharedMemoryHandle(ServiceCtx Context)
{
int Handle = Context.Process.HandleTable.OpenHandle(HidSharedMem);
Context.Response.HandleDesc = IpcHandleDesc.MakeCopy(Handle);
return 0;
}
}
}

View file

@ -0,0 +1,270 @@
using Ryujinx.HLE.Input;
using Ryujinx.HLE.Logging;
using Ryujinx.HLE.OsHle.Ipc;
using System.Collections.Generic;
namespace Ryujinx.HLE.OsHle.Services.Hid
{
class IHidServer : IpcService
{
private Dictionary<int, ServiceProcessRequest> m_Commands;
public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
public IHidServer()
{
m_Commands = new Dictionary<int, ServiceProcessRequest>()
{
{ 0, CreateAppletResource },
{ 1, ActivateDebugPad },
{ 11, ActivateTouchScreen },
{ 21, ActivateMouse },
{ 31, ActivateKeyboard },
{ 66, StartSixAxisSensor },
{ 79, SetGyroscopeZeroDriftMode },
{ 100, SetSupportedNpadStyleSet },
{ 101, GetSupportedNpadStyleSet },
{ 102, SetSupportedNpadIdType },
{ 103, ActivateNpad },
{ 108, GetPlayerLedPattern },
{ 120, SetNpadJoyHoldType },
{ 121, GetNpadJoyHoldType },
{ 122, SetNpadJoyAssignmentModeSingleByDefault },
{ 123, SetNpadJoyAssignmentModeSingle },
{ 124, SetNpadJoyAssignmentModeDual },
{ 125, MergeSingleJoyAsDualJoy },
{ 128, SetNpadHandheldActivationMode },
{ 200, GetVibrationDeviceInfo },
{ 201, SendVibrationValue },
{ 203, CreateActiveVibrationDeviceList },
{ 206, SendVibrationValues }
};
}
public long CreateAppletResource(ServiceCtx Context)
{
MakeObject(Context, new IAppletResource(Context.Ns.Os.HidSharedMem));
return 0;
}
public long ActivateDebugPad(ServiceCtx Context)
{
Context.Ns.Log.PrintStub(LogClass.ServiceHid, "Stubbed.");
return 0;
}
public long ActivateTouchScreen(ServiceCtx Context)
{
long AppletResourceUserId = Context.RequestData.ReadInt64();
Context.Ns.Log.PrintStub(LogClass.ServiceHid, "Stubbed.");
return 0;
}
public long ActivateMouse(ServiceCtx Context)
{
long AppletResourceUserId = Context.RequestData.ReadInt64();
Context.Ns.Log.PrintStub(LogClass.ServiceHid, "Stubbed.");
return 0;
}
public long ActivateKeyboard(ServiceCtx Context)
{
long AppletResourceUserId = Context.RequestData.ReadInt64();
Context.Ns.Log.PrintStub(LogClass.ServiceHid, "Stubbed.");
return 0;
}
public long StartSixAxisSensor(ServiceCtx Context)
{
int Handle = Context.RequestData.ReadInt32();
long AppletResourceUserId = Context.RequestData.ReadInt64();
Context.Ns.Log.PrintStub(LogClass.ServiceHid, "Stubbed.");
return 0;
}
public long SetGyroscopeZeroDriftMode(ServiceCtx Context)
{
int Handle = Context.RequestData.ReadInt32();
int Unknown = Context.RequestData.ReadInt32();
long AppletResourceUserId = Context.RequestData.ReadInt64();
Context.Ns.Log.PrintStub(LogClass.ServiceHid, "Stubbed.");
return 0;
}
public long GetSupportedNpadStyleSet(ServiceCtx Context)
{
Context.ResponseData.Write(0);
Context.Ns.Log.PrintStub(LogClass.ServiceHid, "Stubbed.");
return 0;
}
public long SetSupportedNpadStyleSet(ServiceCtx Context)
{
long Unknown0 = Context.RequestData.ReadInt64();
long Unknown8 = Context.RequestData.ReadInt64();
Context.Ns.Log.PrintStub(LogClass.ServiceHid, "Stubbed.");
return 0;
}
public long SetSupportedNpadIdType(ServiceCtx Context)
{
long Unknown = Context.RequestData.ReadInt64();
Context.Ns.Log.PrintStub(LogClass.ServiceHid, "Stubbed.");
return 0;
}
public long ActivateNpad(ServiceCtx Context)
{
long Unknown = Context.RequestData.ReadInt64();
Context.Ns.Log.PrintStub(LogClass.ServiceHid, "Stubbed.");
return 0;
}
public long GetPlayerLedPattern(ServiceCtx Context)
{
long Unknown = Context.RequestData.ReadInt32();
Context.ResponseData.Write(0L);
Context.Ns.Log.PrintStub(LogClass.ServiceHid, "Stubbed.");
return 0;
}
public long SetNpadJoyHoldType(ServiceCtx Context)
{
long Unknown0 = Context.RequestData.ReadInt64();
long Unknown8 = Context.RequestData.ReadInt64();
Context.Ns.Log.PrintStub(LogClass.ServiceHid, "Stubbed.");
return 0;
}
public long GetNpadJoyHoldType(ServiceCtx Context)
{
Context.ResponseData.Write(0L);
Context.Ns.Log.PrintStub(LogClass.ServiceHid, "Stubbed.");
return 0;
}
public long SetNpadJoyAssignmentModeSingleByDefault(ServiceCtx Context)
{
HidControllerId HidControllerId = (HidControllerId)Context.RequestData.ReadInt32();
long AppletUserResourceId = Context.RequestData.ReadInt64();
Context.Ns.Log.PrintStub(LogClass.ServiceHid, "Stubbed.");
return 0;
}
public long SetNpadJoyAssignmentModeSingle(ServiceCtx Context)
{
HidControllerId HidControllerId = (HidControllerId)Context.RequestData.ReadInt32();
long AppletUserResourceId = Context.RequestData.ReadInt64();
long NpadJoyDeviceType = Context.RequestData.ReadInt64();
Context.Ns.Log.PrintStub(LogClass.ServiceHid, "Stubbed.");
return 0;
}
public long SetNpadJoyAssignmentModeDual(ServiceCtx Context)
{
HidControllerId HidControllerId = (HidControllerId)Context.RequestData.ReadInt32();
long AppletUserResourceId = Context.RequestData.ReadInt64();
Context.Ns.Log.PrintStub(LogClass.ServiceHid, "Stubbed.");
return 0;
}
public long MergeSingleJoyAsDualJoy(ServiceCtx Context)
{
long Unknown0 = Context.RequestData.ReadInt32();
long Unknown8 = Context.RequestData.ReadInt32();
long AppletUserResourceId = Context.RequestData.ReadInt64();
Context.Ns.Log.PrintStub(LogClass.ServiceHid, "Stubbed.");
return 0;
}
public long SetNpadHandheldActivationMode(ServiceCtx Context)
{
long AppletUserResourceId = Context.RequestData.ReadInt64();
long Unknown = Context.RequestData.ReadInt64();
Context.Ns.Log.PrintStub(LogClass.ServiceHid, "Stubbed.");
return 0;
}
public long GetVibrationDeviceInfo(ServiceCtx Context)
{
int VibrationDeviceHandle = Context.RequestData.ReadInt32();
Context.Ns.Log.PrintStub(LogClass.ServiceHid, "Stubbed.");
Context.ResponseData.Write(0L); //VibrationDeviceInfoForIpc
return 0;
}
public long SendVibrationValue(ServiceCtx Context)
{
int VibrationDeviceHandle = Context.RequestData.ReadInt32();
int VibrationValue1 = Context.RequestData.ReadInt32();
int VibrationValue2 = Context.RequestData.ReadInt32();
int VibrationValue3 = Context.RequestData.ReadInt32();
int VibrationValue4 = Context.RequestData.ReadInt32();
long AppletUserResourceId = Context.RequestData.ReadInt64();
Context.Ns.Log.PrintStub(LogClass.ServiceHid, "Stubbed.");
return 0;
}
public long CreateActiveVibrationDeviceList(ServiceCtx Context)
{
MakeObject(Context, new IActiveApplicationDeviceList());
return 0;
}
public long SendVibrationValues(ServiceCtx Context)
{
Context.Ns.Log.PrintStub(LogClass.ServiceHid, "Stubbed.");
return 0;
}
}
}

View file

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

View file

@ -0,0 +1,154 @@
using Ryujinx.HLE.Logging;
using Ryujinx.HLE.OsHle.Handles;
using Ryujinx.HLE.OsHle.Ipc;
using System;
using System.Collections.Generic;
using System.IO;
namespace Ryujinx.HLE.OsHle.Services
{
abstract class IpcService : IIpcService
{
public abstract IReadOnlyDictionary<int, ServiceProcessRequest> Commands { get; }
private IdDictionary DomainObjects;
private int SelfId;
private bool IsDomain;
public IpcService()
{
DomainObjects = new IdDictionary();
SelfId = -1;
}
public int ConvertToDomain()
{
if (SelfId == -1)
{
SelfId = DomainObjects.Add(this);
}
IsDomain = true;
return SelfId;
}
public void ConvertToSession()
{
IsDomain = false;
}
public void CallMethod(ServiceCtx Context)
{
IIpcService Service = this;
if (IsDomain)
{
int DomainWord0 = Context.RequestData.ReadInt32();
int DomainObjId = Context.RequestData.ReadInt32();
long Padding = Context.RequestData.ReadInt64();
int DomainCmd = DomainWord0 & 0xff;
if (DomainCmd == 1)
{
Service = GetObject(DomainObjId);
Context.ResponseData.Write(0L);
Context.ResponseData.Write(0L);
}
else if (DomainCmd == 2)
{
Delete(DomainObjId);
Context.ResponseData.Write(0L);
return;
}
else
{
throw new NotImplementedException($"Domain command: {DomainCmd}");
}
}
long SfciMagic = Context.RequestData.ReadInt64();
int CommandId = (int)Context.RequestData.ReadInt64();
if (Service.Commands.TryGetValue(CommandId, out ServiceProcessRequest ProcessRequest))
{
Context.ResponseData.BaseStream.Seek(IsDomain ? 0x20 : 0x10, SeekOrigin.Begin);
Context.Ns.Log.PrintDebug(LogClass.KernelIpc, $"{Service.GetType().Name}: {ProcessRequest.Method.Name}");
long Result = ProcessRequest(Context);
if (IsDomain)
{
foreach (int Id in Context.Response.ResponseObjIds)
{
Context.ResponseData.Write(Id);
}
Context.ResponseData.BaseStream.Seek(0, SeekOrigin.Begin);
Context.ResponseData.Write(Context.Response.ResponseObjIds.Count);
}
Context.ResponseData.BaseStream.Seek(IsDomain ? 0x10 : 0, SeekOrigin.Begin);
Context.ResponseData.Write(IpcMagic.Sfco);
Context.ResponseData.Write(Result);
}
else
{
string DbgMessage = $"{Context.Session.ServiceName} {Service.GetType().Name}: {CommandId}";
throw new NotImplementedException(DbgMessage);
}
}
protected static void MakeObject(ServiceCtx Context, IpcService Obj)
{
IpcService Service = Context.Session.Service;
if (Service.IsDomain)
{
Context.Response.ResponseObjIds.Add(Service.Add(Obj));
}
else
{
KSession Session = new KSession(Obj, Context.Session.ServiceName);
int Handle = Context.Process.HandleTable.OpenHandle(Session);
Context.Response.HandleDesc = IpcHandleDesc.MakeMove(Handle);
}
}
private int Add(IIpcService Obj)
{
return DomainObjects.Add(Obj);
}
private bool Delete(int Id)
{
object Obj = DomainObjects.Delete(Id);
if (Obj is IDisposable DisposableObj)
{
DisposableObj.Dispose();
}
return Obj != null;
}
private IIpcService GetObject(int Id)
{
return DomainObjects.GetData<IIpcService>(Id);
}
}
}

View file

@ -0,0 +1,27 @@
using Ryujinx.HLE.OsHle.Ipc;
using System.Collections.Generic;
namespace Ryujinx.HLE.OsHle.Services.Lm
{
class ILogService : IpcService
{
private Dictionary<int, ServiceProcessRequest> m_Commands;
public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
public ILogService()
{
m_Commands = new Dictionary<int, ServiceProcessRequest>()
{
{ 0, Initialize }
};
}
public long Initialize(ServiceCtx Context)
{
MakeObject(Context, new ILogger());
return 0;
}
}
}

View file

@ -0,0 +1,86 @@
using Ryujinx.HLE.Logging;
using Ryujinx.HLE.OsHle.Ipc;
using System.Collections.Generic;
using System.IO;
using System.Text;
namespace Ryujinx.HLE.OsHle.Services.Lm
{
class ILogger : IpcService
{
private Dictionary<int, ServiceProcessRequest> m_Commands;
public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
public ILogger()
{
m_Commands = new Dictionary<int, ServiceProcessRequest>()
{
{ 0, Log }
};
}
public long Log(ServiceCtx Context)
{
byte[] LogBuffer = Context.Memory.ReadBytes(
Context.Request.PtrBuff[0].Position,
Context.Request.PtrBuff[0].Size);
using (MemoryStream MS = new MemoryStream(LogBuffer))
{
BinaryReader Reader = new BinaryReader(MS);
long Pid = Reader.ReadInt64();
long ThreadContext = Reader.ReadInt64();
short Flags = Reader.ReadInt16();
byte Level = Reader.ReadByte();
byte Verbosity = Reader.ReadByte();
int PayloadLength = Reader.ReadInt32();
StringBuilder SB = new StringBuilder();
SB.AppendLine("Guest log:");
while (MS.Position < MS.Length)
{
byte Type = Reader.ReadByte();
byte Size = Reader.ReadByte();
LmLogField Field = (LmLogField)Type;
string FieldStr = string.Empty;
if (Field == LmLogField.Skip)
{
Reader.ReadByte();
continue;
}
else if (Field == LmLogField.Line)
{
FieldStr = Field + ": " + Reader.ReadInt32();
}
else
{
FieldStr = Field + ": \"" + Encoding.UTF8.GetString(Reader.ReadBytes(Size)) + "\"";
}
SB.AppendLine(" " + FieldStr);
}
string Text = SB.ToString();
switch((LmLogLevel)Level)
{
case LmLogLevel.Trace: Context.Ns.Log.PrintDebug (LogClass.ServiceLm, Text); break;
case LmLogLevel.Info: Context.Ns.Log.PrintInfo (LogClass.ServiceLm, Text); break;
case LmLogLevel.Warning: Context.Ns.Log.PrintWarning(LogClass.ServiceLm, Text); break;
case LmLogLevel.Error: Context.Ns.Log.PrintError (LogClass.ServiceLm, Text); break;
case LmLogLevel.Critical: Context.Ns.Log.PrintError (LogClass.ServiceLm, Text); break;
}
}
return 0;
}
}
}

View file

@ -0,0 +1,13 @@
namespace Ryujinx.HLE.OsHle.Services.Lm
{
enum LmLogField
{
Skip = 1,
Message = 2,
Line = 3,
Filename = 4,
Function = 5,
Module = 6,
Thread = 7
}
}

View file

@ -0,0 +1,11 @@
namespace Ryujinx.HLE.OsHle.Services.Lm
{
enum LmLogLevel
{
Trace,
Info,
Warning,
Error,
Critical
}
}

View file

@ -0,0 +1,46 @@
using Ryujinx.HLE.Logging;
using Ryujinx.HLE.OsHle.Ipc;
using System.Collections.Generic;
namespace Ryujinx.HLE.OsHle.Services.Mm
{
class IRequest : IpcService
{
private Dictionary<int, ServiceProcessRequest> m_Commands;
public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
public IRequest()
{
m_Commands = new Dictionary<int, ServiceProcessRequest>()
{
{ 4, Initialize },
{ 6, SetAndWait },
{ 7, Get }
};
}
public long Initialize(ServiceCtx Context)
{
Context.Ns.Log.PrintStub(LogClass.ServiceMm, "Stubbed.");
return 0;
}
public long SetAndWait(ServiceCtx Context)
{
Context.Ns.Log.PrintStub(LogClass.ServiceMm, "Stubbed.");
return 0;
}
public long Get(ServiceCtx Context)
{
Context.ResponseData.Write(0);
Context.Ns.Log.PrintStub(LogClass.ServiceMm, "Stubbed.");
return 0;
}
}
}

Some files were not shown because too many files have changed in this diff Show more